{"assessments":[],"deployments":[],"fuzz":[],"identity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"interpretation":"Records acceptance and evidence. Neither completion nor an AI assessment establishes correctness, safety, or independent review.","jobId":"3de72123-ec64-4827-8476-6acf09b3cceb","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"2c8bd7c9b98b924cd245bb202a5a06a08bc2847d9c5a33ae834a4024fd7bb43c","dependsOn":["build_website","build_contract_project","write_foundry_tests","manifest"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"d56133785dab5e452277a4b658ce32b0bf33d28ae309b32050d1744fa4435804","dependsOn":["build_website","build_contract_project","write_foundry_tests","manifest"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"1feed6ec0a02d86b8bf9981d4dfcd1071364755f184c6fbf2bca27eb0fe912b4","dependsOn":["build_website","build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"03eb4dfae85bd364c3e24bdb058fe12ef7748a908ca9832c285e5280ebf4e085","dependsOn":["build_website","build_contract_project","write_foundry_tests","manifest"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"054e3edc114b6064dd1cde8bd12702e0cdf70c2f06d1c0861e090b66ba260f26","dependsOn":["build_website","build_contract_project","write_foundry_tests","manifest"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"bb669335aa699c214ff156f023c459cb911074f8c3c3e646c96331cadafb4c15","dependsOn":["build_website"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"6b8d2c2ebd3301cff2be8edb1e1cc6ba65c858c02fe7247be81722b969842265","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"6b8d2c2ebd3301cff2be8edb1e1cc6ba65c858c02fe7247be81722b969842265","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"d7db6cc6fa62cef05895058956ee9eb5d15037cbafa88c15f09bd66155cc3972","dependsOn":[],"execution":{"mustProduce":["dist/index.html"],"network":true,"optionalTools":["browser"],"profile":"foundry","requires":["network"],"skillHash":"a60bc8b42446c13d993341179fe13c712a5addb118fc532fb2d15e71f912efc8","skillId":"build-website","tools":[]},"key":"build_website","kind":"code","role":"implement","skillHash":"a60bc8b42446c13d993341179fe13c712a5addb118fc532fb2d15e71f912efc8","skillId":"build-website","state":"accepted"},{"acceptedSubmissionHash":"c55ac8dbd1ff7a75b63d25e3061d1d900810ae336da2b0bb7d94358940f5dfb0","dependsOn":["build_website","build_contract_project","write_foundry_tests"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"806a9e0908a310af61d0cd87e9e6468d0aac0289350e9a76e12c72c2d6b235d6","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"4c76766b00591dfe982fe85d3de589fdb2f810e5137c301155d1c5e36a1d3b23","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"4c76766b00591dfe982fe85d3de589fdb2f810e5137c301155d1c5e36a1d3b23","skillId":"write-foundry-tests","state":"accepted"}],"objective":"[SIMD-LAUNCH]\nToken name: Everted Hand Swarm\nToken symbol: EHS\n\nEverted Hand Swarm (EHS) launches a fixed 1,000,000,000 token supply with 18 decimals on Ethereum mainnet, locking 90% of supply into an IMD-paired Uniswap v4 liquidity pool with a fixed 1.25% pool fee (PoolManager 0x000000000004444c5dc75cB358380D2e3dE08A90).\n\nMechanics specification (authoritative: where anything above differs, follow these exactly):\nM1. Buyer rewards: the hook takes a fee of 2% of every swap in the paired currency through beforeSwap/afterSwap return deltas; it accrues in the hook. Every buy of at least 10 units of the paired currency (whole tokens) records a reward of 1% of that buy's paired-currency amount for the buyer (the swap recipient), capped by the accrued balance not already owed. Buyers claim with claimRewards(); payRecorded(address) lets anyone pay a recorded reward to its own beneficiary and can never redirect it. Rewards are never paid inside a swap callback. Immutable constants; views pending(address) and owedTotal().\n\nBuild requirements (mandatory):\n- Context: an ordinary, legitimate token launch on Ethereum. The coin's name, story and theme are branding only; nothing here is real-world harm or offensive security work.\n- A complete Foundry project at the repository root: foundry.toml with solc 0.8.26, evm_version cancun, optimizer on and bytecode_hash = \"none\", so the build is reproducible and verifiable: deployed contracts live in src/ and every import resolves to a committed file.\n- Contracts: EHSHook. The hook is the hook of this launch's pool; keep its creation code within the EIP-3860 size limit.\n- No selfdestruct and no delegatecall anywhere in runtime code. No proxies, no owner, no upgradeability.\n- Chain: Ethereum mainnet (set by the order, not a launch.json field). Uniswap v4 PoolManager: 0x000000000004444c5dc75cB358380D2e3dE08A90 (pass it to the hook constructor).\n- launch.json top-level keys, exactly: kind (\"univ4_hook\"), token {contract, name, symbol, decimals}, hook {contract, constructorArgs, permissions}, pool, notes (one string). No chainId, economics or other keys.\n- Paired currency: IMD, the ERC-20 at 0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7 on Ethereum mainnet (18 decimals).\n- Every address the hook needs is known now and fixed at deployment; nothing may require an owner or a setter after launch.\n- Supply distribution is done by the launch factory: it mints the supply, seeds the pool, sends the swarm's 10% through its Merkle distributor and any remainder to remainderTo. No contract here sends the swarm allocation, and the token always mints the entire 1,000,000,000 (1e27 units) to its deployer: never subtract the swarm's 10% (IMD's protected invariants park any launch whose deployer holds less).\n- Hook fees are collected through beforeSwap/afterSwap return deltas, on top of the pool's static 1.25% LP fee (fee tier 12500). Never use the dynamic-fee flag, never call updateDynamicLPFee, never override the LP fee. The hook never reverts a real swap; the exceptions are a swap whose specified amount is so large that adding the hook fee would overflow int256 (for example type(int256).max requests): it may revert with UnrepresentableFee. That is the accepted swap domain.\n- The hook is a plain immutable contract deployed directly at a CREATE2-mined address with the right permission bits, and launch.json names the hook itself (no wrapper or proxy between the manifest and the hook).\n- Tests: Foundry unit, fuzz and mainnet-fork tests that swap through the real PoolManager with the hook (exact-input and exact-output, buys and sells), plus permission bits matching the hook address.\n- Every hook fee is proportional to what actually filled. Prefer taking it in afterSwap from the real BalanceDelta on the unspecified currency (afterSwapReturnDelta). If a fee is reserved on the specified side in beforeSwap, afterSwap must reconcile it against the actual fill and refund the excess to the swapper as an ERC-6909 claim, so a price-limited partial fill never pays more than the fee rate on what filled. Test exact-input and exact-output partial fills with a price limit.\n- launch.json pool: pairedCurrency 0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7, fee 12500, tickSpacing 60, initialPrice \"79228162514264337593543950336\" (provenance only; the launch factory sets the opening price from the economics). Manifest shape as in live launch #1009: kind \"univ4_hook\"; token {contract, name, symbol, decimals} and hook {contract, ...} where each contract is a bare Solidity contract name like \"EHS\" or \"EHSHook\" (never a path or \"File.sol:Name\"); hook {contract, constructorArgs (e.g. [\"$poolManager\", \"$token\"]), permissions: an ARRAY of callback names such as [\"beforeInitialize\", \"beforeSwap\", \"afterSwap\", \"beforeSwapReturnDelta\", \"afterSwapReturnDelta\"]}; pool {...}; notes: a string explaining constructor args, permission bits and fees.\nLogo: https://fxumiqjngmabtgvruvka.supabase.co/storage/v1/object/public/forum-attachments/posts/mv9gfo9j-hzbbwl.jpg","parentJobId":null,"planHash":"cfd19494d85c4954e53cf887f56d4ed8317dd76bbaae6db9dbfc53389508bd6c","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"3de72123-ec64-4827-8476-6acf09b3cceb","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1844-everted-hand-swarm"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51285","feedbackHash":"81d05aaecabc63fe8492bd9c2c2e7eb6a111431b51ac6c37dbf2c29f7021bbae","nodeKey":"audit_economics","submissionHash":"2c8bd7c9b98b924cd245bb202a5a06a08bc2847d9c5a33ae834a4024fd7bb43c","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52166","feedbackHash":"0b9f721c125dac6700c9ce897787bbe81b5adab709992d118c3b9ab53fe661e2","nodeKey":"audit_flow","submissionHash":"d56133785dab5e452277a4b658ce32b0bf33d28ae309b32050d1744fa4435804","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51540","feedbackHash":"e0401302a44d39ea29053f11762b676a99b83547c9c954f93f5ddd625c31f036","nodeKey":"audit_judge","submissionHash":"cca9837839dfe95bd0dbb29083889077fc56c4221e2349f16c4d1f8590be7754","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50939","feedbackHash":"c7438a78922dd482b332a0120aba042f9ec19cdb52062d205613dd074bceb209","nodeKey":"audit_judge","submissionHash":"1feed6ec0a02d86b8bf9981d4dfcd1071364755f184c6fbf2bca27eb0fe912b4","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52232","feedbackHash":"236a7eb7ccf316b81dbd7bbc33b7aa9c57aebca02ccc0c08a772afb678bcd16c","nodeKey":"audit_math","submissionHash":"03eb4dfae85bd364c3e24bdb058fe12ef7748a908ca9832c285e5280ebf4e085","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50981","feedbackHash":"619ad37e278f21980541dd68923d6acb3483243139cb8a3ca4b9a7d646f8bcc9","nodeKey":"audit_permissions","submissionHash":"054e3edc114b6064dd1cde8bd12702e0cdf70c2f06d1c0861e090b66ba260f26","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51168","feedbackHash":"fa3ff8f5a62cd177c4dd1d24f179b675557fcfbe9a7cdf3bdd8c9a1437af8cba","nodeKey":"build_contract_project","submissionHash":"bb669335aa699c214ff156f023c459cb911074f8c3c3e646c96331cadafb4c15","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51377","feedbackHash":"72dd7785d04c18492ec0cac99e1f26024b4be36e00c2907e4a5db8c4215e8acf","nodeKey":"build_contract_project","submissionHash":"74f9f8b0c8012a7aaf26ee5da867f4423d5328a312f17437f0b903a340d0d1ff","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51890","feedbackHash":"20026336032c32086dfc4244fdd566160bbf56b2435aaec7a8d1be278bfdf2c3","nodeKey":"build_website","submissionHash":"d7db6cc6fa62cef05895058956ee9eb5d15037cbafa88c15f09bd66155cc3972","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52164","feedbackHash":"62616fa9bbfc6c34fe25adc9450edc311fc20cbaffac7ee94d0459fd34c24b20","nodeKey":"manifest","submissionHash":"c55ac8dbd1ff7a75b63d25e3061d1d900810ae336da2b0bb7d94358940f5dfb0","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51380","feedbackHash":"f62d0bc6718d53ceeef98011d1d393213afe9e03d8fd37a36bc08299589e17f7","nodeKey":"manifest","submissionHash":"8b02bcc11ac39809c8dbbbba9f839cc17fa7d5bade83160cc427afce45b69c7d","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51401","feedbackHash":"9f73c5fcf9aab9a7fc2c8a6b4b003cabef66717bcdd4eb6bade646a68bfc2c84","nodeKey":"write_foundry_tests","submissionHash":"3cd370746fbde1fc8ce5663733298da10fa5eec5da5105f9ff795d3c20851423","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51375","feedbackHash":"915daefceb280a45a330972384de4df6d0608ca09b8a6391afe0d55eba844fa1","nodeKey":"write_foundry_tests","submissionHash":"806a9e0908a310af61d0cd87e9e6468d0aac0289350e9a76e12c72c2d6b235d6","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"2ae9189aeb3c579f7f71b315879719e8504b583abeb3de8e0399c1967f9a9914","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"82692ab5f7f7e54e","findings":[{"citation":"resolved","description":"Seam: boundary x precision. The brief says every buy of at least 10 units of the paired currency records a reward of 1% of the buy's paired-currency amount. For an exact-input buy, beforeSwap (line 82-84) first reserves ceil(|amountSpecified|/51) from the specified IMD, so the PoolManager only swaps |amountSpecified| - reserve, and afterSwap derives `filled` from that pool-side delta (line 95-96). The threshold at line 112 and the reward base at line 119 therefore see 50/51 of what the buyer actually spent. A buyer who specifies exactly 10e18 IMD input spends 10e18 IMD (fee included) but fills only 9803921568627450980 wei, below MIN_BUY, and records no reward. The same holds for an exact-output buy whose IMD cost is 10 IMD: the buyer pays 10.2 IMD (fill 10e18 + fee 0.2e18). The project's own test `test_exactTenIMDThreshold` encodes the 10.2 IMD spend as the threshold. Whether this matches the brief ('buy of at least 10 units', '1% of that buy's paired-currency amount') is an interpretation the author should confirm; as written, 10 IMD of spend never qualifies and the 1% is 1/102 of spend, not 1/100.","line":112,"path":"src/EHSHook.sol","reproduction":"State: fresh PoolManager, EHS/IMD pool at fee 12500, tick spacing 60, price Q96, liquidity 1e24 over [-60000, 60000]. Call: manager.swap(key, SwapParams(zeroForOne = (IMD is currency0), amountSpecified = -10e18, sqrtPriceLimit = extreme), abi.encode(buyer)) inside unlock, then settle. Arithmetic: _reserve(-10e18) = ceil(1e19/51) = 196078431372549020; amountToSwap = -9803921568627450980; filled = 9803921568627450980; fee = filled/50 = 196078431372549019; refund = 1 wei; filled < 10e18 so no RewardRecorded. Observed (test/scratch/Probe.t.sol test_exactInputTenIMDBuyEarnsNothing): IMD spent 10000000000000000000, refund claim 1, accruedBalance 196078431372549019, pending(buyer) 0. Expected per brief wording: pending(buyer) = 0.1e18 (1% of a 10 IMD buy). The buyer must specify 10.2e18 input (fill exactly 10e18) to be eligible.","severity":"low","snippet":"        if (pairDelta < 0 && filled >= MIN_BUY) {","title":"Reward eligibility is tested on the post-reserve fill, so a buyer spending exactly 10 IMD (exact input) earns nothing; the effective spend threshold is 10.2 IMD"},{"citation":"resolved","description":"Seam: boundary x invariant. When IMD is the specified output (an exact-output sell), beforeSwap reserves ceil(amountSpecified/49) IMD as a positive hook delta on the specified side, and the swapper's IMD delta becomes (pool output filled) - reserve. The refund (reserve - fee) is returned only as an ERC-6909 claim minted to `sender`. For a full fill this nets to +amountSpecified, but when a price limit (or exhausted liquidity) fills less than the reserve, the sign flips: a seller who brought only EHS ends the swap with a negative IMD delta of up to ~2.04% of the requested output. The hook itself does not revert, but (a) a router that settles the input currency and takes the output currency (the Universal Router SETTLE_ALL/TAKE_ALL pattern) reverts on the negative IMD delta, so the price-limited sell cannot complete, and (b) a router that settles deltas in cash must front IMD it was not told to hold. README line 80 documents the burn-then-settle flow, but not that a sell can require the seller to pay IMD. Exact-input buys never flip sign (the swapper is already paying IMD). The reserve-on-specified-side design is what the brief permits for this direction, so the fix is documentation plus router requirement, or sizing the reserve from the pool's reachable output when a price limit is given.","line":101,"path":"src/EHSHook.sol","reproduction":"State: same pool as finding 1 (liquidity 1e24 over [-60000,60000], price Q96). Call inside unlock, with the router holding EHS only and no IMD claims: manager.swap(key, SwapParams(zeroForOne = (EHS is currency0), amountSpecified = +100000e18, sqrtPriceLimitX96 = TickMath.getSqrtPriceAtTick(1 tick past spot toward the sell direction)), abi.encode(seller)). Arithmetic: reserve = ceil(1e23/49) = 2040816326530612244898; pool outputs filled = 49996250312472658710 IMD (~50 IMD, one tick); fee = filled/50 = 999925006249453174; refund = 2039816401524362791724 minted as a claim; swapper IMD delta = filled - reserve = -1990820076218139586188. Observed (test/scratch/Probe.t.sol test_exactOutputSellPartialFillFlipsSign): IMD delta after swap -1990820076218139586188, EHS delta -50631645632907436987, refund claim 2039816401524362791724. Expected for a sell: the seller's IMD delta is never negative; a router doing take(IMD) after the swap reverts, and one paying deltas in cash must transfer 1990.82 IMD to the manager to receive a 2039.82 IMD claim.","severity":"low","snippet":"            uint256 refund = _reserve(params.amountSpecified) - fee;\n            if (refund != 0) {\n                poolManager.mint(sender, id, refund);","title":"Exact-output sell with a price-limited partial fill below ~2% of the request leaves the seller owing IMD to the PoolManager (negative IMD delta), payable only by burning the refund claim"},{"citation":"resolved","description":"Math Precision checklist item 'fees round UP'. The fee is floor(filled/50): a fill of 49 wei IMD pays nothing, and every fee truncates up to 49 wei in the swapper's favour. Gas cost dwarfs the leak, so this is not exploitable; the reward floor(filled/100) at line 119 also rounds in the hook's favour and the reserve rounds up, so the hook is never short. Recorded for completeness of the precision review, not as a defect to fix.","line":97,"path":"src/EHSHook.sol","reproduction":"Exact-input buy of 50 wei: reserve = ceil(50/51) = 1, pool swaps 49 wei, fee = 49/50 = 0, refund 1 wei; accruedBalance stays 0 (test/scratch/Probe.t.sol test_dustFeeZero logged 'accrued fee after 50 wei exact input: 0'). An exact-input sell of 2000 wei EHS yielded 39 wei of fee (fill ~1974 wei, 2% = 39.48 truncated to 39).","severity":"info","snippet":"        uint256 fee = filled / 50;","title":"Hook fee rounds down, so fills under 50 wei of IMD pay no hook fee and every fee loses up to 49 wei"}],"hash":"03eb4dfae85bd364c3e24bdb058fe12ef7748a908ca9832c285e5280ebf4e085","nodeId":"94e0ff91-570d-4cc9-b3ff-00d194ba2c73","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds the result.\n\n**Outcome: no fund-loss defects in the math.** The fee, reserve, refund and reward arithmetic held under every check from the Math Precision, Boundary and Numerical Gap guides. Three edge findings are recorded, none above low.\n\n**What I verified** (hand-traced, then confirmed with a scratch Foundry test):\n- **Reserve always covers the fee.** Exact-input reserves ceil(|a|/51) and exact-output ceil(a/49); the refund subtraction can never underflow, including at the 2^127/50+1 cap and at int256.min.\n- **Every narrow cast is bounded.** The reserve and fee fit int128 by the cap. The UnrepresentableFee revert fires only when the request plus reserve would overflow int256, which is the accepted domain.\n- **Accounting invariants hold.** The hook's ERC-6909 balance equals accruedBalance, rewards are capped by accrued minus owed, and payRecorded's decrements cannot underflow.\n- **EHS token arithmetic is safe.** Fixed supply bounds every addition, and the max-allowance sentinel is handled.\n\n**Findings recorded:**\n1. **Low. Threshold on post-reserve fill.** A buyer who specifies exactly 10 IMD exact input fills only about 9.80 IMD and earns no reward. The effective spend threshold is 10.2 IMD, and the 1% is taken on fill, not spend. The project's own test encodes the 10.2 figure, so the author should confirm this reading of the brief.\n2. **Low. Sign flip on partial exact-output sells.** With a tight price limit, a 100,000 IMD exact-output sell filled about 50 IMD and left the seller owing about 1,991 IMD to the manager, offset by a 2,040 IMD refund claim. A router that takes the output currency after the swap reverts there. The README documents the burn-then-settle flow but not that a sell can require IMD.\n3. **Info. Fee floors.** Fills under 50 wei pay no fee. Not exploitable, recorded for completeness.\n\n**Coverage:** all 7 verifier entry points have rows, plus three invariant rows. Only afterSwap is marked as having findings. The scratch probe lives in `test/scratch/Probe.t.sol` and is not part of the submission.","treeHash":null,"usage":{"cachedInputTokens":863570,"inputTokens":258,"model":"claude-fable-5-1","outputTokens":37443,"runtime":"claude","turns":19,"wallClockMs":604767}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"5739ce0d803a43cd","findings":[{"citation":"resolved","description":"afterSwap attributes the 1% buyer reward to `sender`, which in Uniswap v4 is the contract that called PoolManager.swap (the router), unless hookData carries a 32-byte beneficiary. The Uniswap interface and the Universal Router send empty hookData for v4 pools, so every UI buy of >= 10 IMD records its reward to the Universal Router address. payRecorded(address) is permissionless (by spec) and pushes IMD to that address, and the Universal Router's SWEEP command lets any caller move tokens it holds. Access (permissionless payRecorded) x economics (reward credited to a shared contract) x asymmetry (the beneficiary is the swap recipient only when hookData is set) combine so that an unprivileged third party, not the buyer, collects the buyers' rewards. The README acknowledges rewards go to such routers, but the spec says the reward is for 'the buyer (the swap recipient)', and the consequence is that the accrued fee pool is paid to whoever sweeps it. Fix: do not fall back to `sender` (record no reward when hookData names no beneficiary), or make the beneficiary mandatory for reward recording; refunds (finding 2) deserve the same treatment.","line":113,"path":"src/EHSHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {TransientStateLibrary} from \"v4-core/src/libraries/TransientStateLibrary.sol\";\nimport {EHS} from \"src/EHS.sol\";\nimport {EHSHook} from \"src/EHSHook.sol\";\n\n/// @dev Stand-in for the mainnet IMD ERC-20 (etched at its address).\ncontract PlainERC20 {\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n\n    function mint(address to, uint256 amount) external {\n        balanceOf[to] += amount;\n    }\n\n    function approve(address spender, uint256 amount) external returns (bool) {\n        allowance[msg.sender][spender] = amount;\n        return true;\n    }\n\n    function transfer(address to, uint256 amount) external returns (bool) {\n        balanceOf[msg.sender] -= amount;\n        balanceOf[to] += amount;\n        return true;\n    }\n\n    function transferFrom(address from, address to, uint256 amount) external returns (bool) {\n        allowance[from][msg.sender] -= amount;\n        balanceOf[from] -= amount;\n        balanceOf[to] += amount;\n        return true;\n    }\n}\n\n/// @dev A shared, permissionless router in the shape of the Universal Router: it swaps for\n/// whoever calls it, sends the output to the caller's chosen recipient, passes the hookData the\n/// caller gives it (the Uniswap interface gives none), and lets anyone sweep tokens it holds.\ncontract SharedRouter is IUnlockCallback {\n    IPoolManager immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    function swap(PoolKey calldata key, SwapParams calldata params, address recipient, bytes calldata hookData)\n        external\n    {\n        manager.unlock(abi.encode(msg.sender, key, params, recipient, hookData));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (address payer, PoolKey memory key, SwapParams memory params, address recipient, bytes memory hookData) =\n            abi.decode(data, (address, PoolKey, SwapParams, address, bytes));\n        BalanceDelta delta = manager.swap(key, params, hookData);\n        _settle(key.currency0, delta.amount0(), payer, recipient);\n        _settle(key.currency1, delta.amount1(), payer, recipient);\n        return \"\";\n    }\n\n    function _settle(Currency c, int128 amount, address payer, address recipient) internal {\n        if (amount < 0) {\n            manager.sync(c);\n            PlainERC20(Currency.unwrap(c)).transferFrom(payer, address(manager), uint256(uint128(-amount)));\n            manager.settle();\n        } else if (amount > 0) {\n            manager.take(c, recipient, uint256(uint128(amount)));\n        }\n    }\n\n    /// @dev Universal Router's SWEEP command: anyone may take whatever the router holds.\n    function sweep(address token, address to) external {\n        PlainERC20(token).transfer(to, PlainERC20(token).balanceOf(address(this)));\n    }\n}\n\ncontract RouterRewardLeakTest is Test, IUnlockCallback {\n    address constant IMD = 0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7;\n    uint160 constant Q96 = 79228162514264337593543950336;\n\n    PoolManager manager;\n    EHS token;\n    EHSHook hook;\n    SharedRouter router;\n    PoolKey key;\n    bool pair0;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        token = new EHS();\n        vm.etch(IMD, address(new PlainERC20()).code);\n\n        bytes memory code = abi.encodePacked(type(EHSHook).creationCode, abi.encode(manager, address(token)));\n        bytes32 hash = keccak256(code);\n        address deployed;\n        for (uint256 i; i < 300_000; ++i) {\n            bytes32 salt = bytes32(i);\n            address predicted =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, hash)))));\n            if (uint160(predicted) & 0x3fff != 0x20cc) continue;\n            assembly (\"memory-safe\") {\n                deployed := create2(0, add(code, 32), mload(code), salt)\n            }\n            break;\n        }\n        require(deployed != address(0), \"mining failed\");\n        hook = EHSHook(deployed);\n        pair0 = IMD < address(token);\n        key = PoolKey(\n            Currency.wrap(pair0 ? IMD : address(token)),\n            Currency.wrap(pair0 ? address(token) : IMD),\n            12500,\n            60,\n            IHooks(deployed)\n        );\n        manager.initialize(key, Q96);\n        PlainERC20(IMD).mint(address(this), 10_000_000 ether);\n        manager.unlock(\"\");\n        router = new SharedRouter(manager);\n    }\n\n    // Liquidity provision for the test's own pool.\n    function unlockCallback(bytes calldata) external returns (bytes memory) {\n        manager.modifyLiquidity(key, ModifyLiquidityParams(-60000, 60000, 1_000_000 ether, bytes32(0)), \"\");\n        _pay(key.currency0);\n        _pay(key.currency1);\n        return \"\";\n    }\n\n    function _pay(Currency c) internal {\n        int256 d = TransientStateLibrary.currencyDelta(manager, address(this), c);\n        if (d < 0) {\n            manager.sync(c);\n            PlainERC20(Currency.unwrap(c)).transfer(address(manager), uint256(-d));\n            manager.settle();\n        }\n    }\n\n    /// A buyer buys through a shared router with the empty hookData the Uniswap interface sends.\n    /// The reward for that buy must not be credited to the router, where anyone can take it.\n    function test_rewardIsNotCreditedToASharedRouterOnEmptyHookData() public {\n        address buyer = makeAddr(\"buyer\");\n        address attacker = makeAddr(\"attacker\");\n        PlainERC20(IMD).mint(buyer, 1_000 ether);\n        vm.prank(buyer);\n        PlainERC20(IMD).approve(address(router), type(uint256).max);\n\n        // Exact-output buy of EHS: IMD is the unspecified side, so the fee and the reward\n        // follow afterSwap's real fill. The buy fills well above the 10 IMD threshold.\n        bool zeroForOne = pair0;\n        uint160 limit = zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1;\n        vm.prank(buyer);\n        router.swap(key, SwapParams(zeroForOne, int256(500 ether), limit), buyer, \"\");\n\n        assertGt(token.balanceOf(buyer), 0, \"buyer received EHS\");\n        assertGt(hook.accruedBalance(), 10 ether, \"hook took its 2% fee\");\n\n        // The buyer is the swap recipient; the router is a shared contract. Nothing should be owed\n        // to the router.\n        assertEq(hook.pending(address(router)), 0, \"reward recorded to the shared router\");\n\n        // Anyone can pay the router's record and sweep it away from the buyer.\n        vm.startPrank(attacker);\n        hook.payRecorded(address(router));\n        router.sweep(IMD, attacker);\n        vm.stopPrank();\n        assertEq(PlainERC20(IMD).balanceOf(attacker), 0, \"attacker swept the buyer's reward\");\n    }\n}","reproduction":"State: pool at 1:1 with 1e6 liquidity, a shared router contract (shape of Universal Router: swaps for msg.sender, takes output to the chosen recipient, has a permissionless sweep). 1) buyer calls router.swap(exact-output 500 EHS, hookData = \"\") with output to buyer. Pool fills ~506.6 IMD, hook accrues ~10.13 IMD fee, and pending[router] == 5065824051266139398 while pending[buyer] == 0. 2) attacker calls hook.payRecorded(router): 5.0658 IMD is transferred to the router. 3) attacker calls router.sweep(IMD, attacker): attacker's IMD balance == 5065824051266139398. Expected: the reward is recorded for the swap recipient or not at all; a shared router must never be credited. Actual: any third party collects it. Proof test test/scratch/RouterRewardLeak.t.sol fails on the current code with 'reward recorded to the shared router: 5065824051266139398 != 0'.","severity":"high","snippet":"        if (pairDelta < 0 && filled >= MIN_BUY) {\n            address beneficiary = sender;\n            uint256 word;\n            if (data.length == 32) {\n                assembly (\"memory-safe\") { word := calldataload(data.offset) }\n                if (word != 0 && word <= type(uint160).max) beneficiary = address(uint160(word));\n            }","title":"Buy rewards fall back to the router (`sender`) when hookData is empty; anyone can then pay and sweep them from a shared router"},{"citation":"resolved","description":"When IMD is the specified currency (exact-input buy, exact-output sell) beforeSwap reserves ceil(amount/51) (or /49) on the specified side and afterSwap reconciles by minting the excess as an ERC-6909 claim to `sender`, the router. The reserve is always charged against the router's delta, so the router's payer funds it in full. A router that does not burn claims inside the same unlock (the Universal Router / V4Router _settle path only syncs and transfers, it never burns ERC-6909, and nobody else can burn claims owned by the router without operator approval) leaves the refund permanently at the router and the swapper has paid reserve, not fee, on a partial fill. Trust gap: the hook's fee guarantee ('never pays more than the fee rate on what filled') depends on an external contract behaving in a way the dominant routers do not. The mechanism is the one the build requirements mandate, so the gap sits at the router boundary; the hook could instead keep the refund claim and record it as pending for the hookData beneficiary (payable via payRecorded), or document that refund-unaware routers must not be used for limit-priced IMD-specified swaps.","line":103,"path":"src/EHSHook.sol","reproduction":"State: pool at 1:1 with 1e6 liquidity, router as in finding 1 (settles by transferFrom/settle and take, never burns claims). buyer calls router.swap(exact-input 10,000 IMD, sqrtPriceLimit = 0.5% move from current price, hookData = \"\"). Observed: pool fill 5063.29 IMD, hook fee 101265822784810126582 (2% of fill), buyer's IMD balance decreased by 5259369570613055348722, router ERC-6909 IMD claims 94812608587738893026. So the buyer paid 196.08 IMD above the fill (3.87% of the fill) and 94.81 IMD sits at the router with no way for the buyer to recover it (PoolManager.burn requires from == msg.sender or operator approval; the router grants none). Expected per the requirement: a price-limited partial fill never pays more than 2% on what filled. Scratch test test/scratch/Leads.t.sol test_partialFillRefundStrandedAtRouter logs these numbers.","severity":"medium","snippet":"            uint256 refund = _reserve(params.amountSpecified) - fee;\n            if (refund != 0) {\n                poolManager.mint(sender, id, refund);\n                emit FeeRefunded(sender, refund);\n            }","title":"Reserve refund on price-limited IMD-specified swaps is minted to the router, so buyers through refund-unaware routers pay more than 2% of the fill and the excess is stranded"},{"citation":"resolved","description":"The spec says 'Every buy of at least 10 units of the paired currency records a reward'. For an exact-input buy the swapper pays amountSpecified in IMD but the pool only receives amountSpecified minus the beforeSwap reserve (ceil(amount/51)), and `filled` is that pool delta. So a buyer who pays exactly 10 IMD has filled = 9.8039 IMD < MIN_BUY and gets nothing, while the exact-output branch measures the same pool amount and the buyer pays 10.2 IMD total. The two branches are consistent with each other but asymmetric against the spec's wording from the buyer's point of view: the exact-input buyer must pay at least 10.21 IMD. Either measure the threshold on the buyer's gross IMD outlay (filled + fee) or document the threshold as the pool-side amount.","line":112,"path":"src/EHSHook.sol","reproduction":"Pool at 1:1 with 1e6 liquidity. 1) exact-input buy with amountSpecified = -10e18 IMD, hookData = abi.encode(buyer): afterSwap sees filled = 9803921568627450981 (< 10e18), pending[buyer] stays 0. 2) exact-input buy with amountSpecified = -10.21e18: pending[buyer] == 100098039215686274. Expected: a buy paying 10 IMD records a reward of about 0.1 IMD. Scratch test test/scratch/Leads.t.sol test_tenImdExactInputBuyRecordsNoReward.","severity":"low","snippet":"        if (pairDelta < 0 && filled >= MIN_BUY) {","title":"MIN_BUY threshold is applied to the pool fill, so an exact-input buy paying exactly 10 IMD records no reward"}],"hash":"054e3edc114b6064dd1cde8bd12702e0cdf70c2f06d1c0861e090b66ba260f26","nodeId":"ce5d95b7-8052-48b6-9268-ab34ed66ceb8","outcome":"completed","summary":"The review is complete. Findings are in `.imd-findings.json` at the repository root, with a coverage row for every listed entry point plus the view callbacks, the permission bits, the paired IMD token, and two invariants. No repository files were changed; the scratch tests live under the gitignored `test/scratch/`.\n\n**Findings (3):**\n\n- **High. Rewards fall back to the router.** When a buy carries no 32-byte beneficiary in hookData, afterSwap records the 1% reward to `sender`, which is the router contract. The Uniswap interface sends empty hookData, so UI buys credit the Universal Router. Because payRecorded is permissionless and the Universal Router's SWEEP is open to anyone, a third party collects the buyers' rewards. Reproduced end to end: a 500 EHS buy records 5.0658 IMD to the router, and an attacker calls payRecorded then sweeps the full amount. The proof test at `test/scratch/RouterRewardLeak.t.sol` fails on the current code and is embedded in the findings file.\n- **Medium. Refund claims stranded at refund-unaware routers.** For IMD-specified swaps the reserve is charged to the router's delta and the excess is minted back to the router as an ERC-6909 claim. Routers that do not burn claims in the same unlock, including the Universal Router, leave it there permanently. A 10,000 IMD exact-input buy with a tight price limit filled 5063 IMD, paid 5259 IMD, and left 94.8 IMD at the router, a 3.87% effective hook fee instead of 2%. The mechanism is the one the requirements mandate, so I flagged it as a boundary gap with a possible in-hook remedy.\n- **Low. Threshold measured on pool fill.** An exact-input buy paying exactly 10 IMD fills 9.80 IMD at the pool and records no reward. The buyer must pay at least 10.21 IMD.\n\n**Verified as holding:** every callback is restricted to the PoolManager, beforeInitialize pins the single launch pool, permission bits match the address flags and the manifest, fee and refund deltas balance in all four swap shapes, the reward accounting cannot underflow or be drained, unlockCallback is reachable only through payRecorded, the EHS token has no privileged paths, and mainnet IMD has no pause, blacklist, tax or callback selectors. One lead was dropped after testing: a 1 wei exact-input buy does not revert.\n\n**Not reached:** a live mainnet-fork swap against the real PoolManager, which the project's own fork suite skips without an RPC key.","treeHash":null,"usage":{"cachedInputTokens":1438266,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":40615,"runtime":"claude","turns":30,"wallClockMs":718781}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"72b617d4b615473a","findings":[],"hash":"1feed6ec0a02d86b8bf9981d4dfcd1071364755f184c6fbf2bca27eb0fe912b4","nodeId":"bed0c0dc-d5b0-4538-8fec-bcf1ae91472a","outcome":"completed","summary":"All prior findings are settled and no new defect reproduced. The record is in `.imd-findings.json` with an empty findings list and 15 coverage rows.\n\n**Round-one high finding (reward fell back to the router): fixed.** Commit 5aa6239 replaced the sender fallback in afterSwap with a `_recipient` helper that returns an address only for a canonical, nonzero 32-byte hookData word. Otherwise the buy records no reward and the fee stays unowed. I copied my proof from `.imd/reads/proofs/Proof_6ca6792ef4dd.t.sol` under test/scratch and ran it: it passes on the revised code. The three specialist proofs also pass. The author's new shared-router test asserts the router is never credited, that paying its record moves nothing, and that a sweep yields only the payer's leftover.\n\n**Three advisories: settled as documentation, as I asked.**\n- Stranded claim refunds and the sell sign flip are now stated in the README router requirement paragraph, the manifest notes and the security review. Hook accounting is unchanged and still exact.\n- The permanent fee remainder is confirmed intended and documented. The invariant suite asserts the hook's claims back accruedBalance.\n- The fill-based 10 IMD threshold is confirmed intended, with the 10.2 IMD spend example, and pinned by two existing tests.\n\n**Verification run.** The full suite passes: 95 unit and fuzz tests, 8 invariants, and the fork test skips cleanly without an RPC. The manifest has exactly the five schema keys, constructor args and permissions match the hook, and notes are under the length cap. I traced all seven listed entry points and marked each `holds`. The static-analysis reentrancy leads do not reproduce: the only external calls go to the PoolManager, with state settled before the unlock and a nested payment or unlock reverting.\n\n**Not done.** No fork run against live mainnet state was possible here, as in round one. Nothing in the tree was changed; my scratch copies live under the gitignored test/scratch directory.","treeHash":null,"usage":{"cachedInputTokens":576725,"inputTokens":194,"model":"claude-fable-5-1","outputTokens":9908,"runtime":"claude","turns":19,"wallClockMs":351484}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"7e929507773df661","findings":[{"citation":"resolved","description":"M1 says the 1% reward belongs to the buyer (the swap recipient). When hookData is empty or malformed, afterSwap falls back to `sender`, which in v4 is the router, never the recipient. The Uniswap app and most integrations send empty hookData, so every such buy credits pending[router]. For a shared, stateless router such as the Universal Router, that record is worth nothing to the buyer and is free money for anyone else: payRecorded(router) is permissionless and transfers real IMD to the router, and the Universal Router's SWEEP command (0x04) lets any caller move the router's whole IMD balance to themselves in the same transaction. The buyer's reward therefore goes to whichever bot calls payRecorded+SWEEP first, instead of to the buyer or staying in the hook. Economic lens: unprivileged actor, profit = 1% of every buy that arrives without recipient context, cost = gas. Recording nothing when the context does not name a recipient (leaving the fee unowed in the hook) removes the extraction path without changing the 2% fee.","line":113,"path":"src/EHSHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {TransientStateLibrary} from \"v4-core/src/libraries/TransientStateLibrary.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {EHS} from \"src/EHS.sol\";\nimport {EHSHook} from \"src/EHSHook.sol\";\n\n/// @dev Minimal ERC-20 standing in for IMD at its mainnet address.\ncontract StubIMD {\n    string public constant symbol = \"IMD\";\n    uint8 public constant decimals = 18;\n    mapping(address => uint256) public balanceOf;\n\n    function mint(address to, uint256 amount) external {\n        balanceOf[to] += amount;\n    }\n\n    function transfer(address to, uint256 amount) external returns (bool) {\n        balanceOf[msg.sender] -= amount;\n        balanceOf[to] += amount;\n        return true;\n    }\n}\n\n/// @dev A shared, stateless router in the shape of the Universal Router: anyone may route a swap\n/// through it with any hookData (the Uniswap app sends empty hookData), and anyone may SWEEP whatever\n/// token balance sits in it (Universal Router command 0x04, no access control).\ncontract SharedRouter is IUnlockCallback {\n    using TransientStateLibrary for IPoolManager;\n\n    IPoolManager internal immutable manager;\n    address internal immutable imd;\n\n    constructor(IPoolManager manager_, address imd_) {\n        manager = manager_;\n        imd = imd_;\n    }\n\n    function swap(PoolKey memory key, SwapParams memory params, bytes memory hookData, address recipient) external {\n        manager.unlock(abi.encode(msg.sender, recipient, key, params, hookData));\n    }\n\n    function sweep(address token, address to) external {\n        StubIMD(token).transfer(to, StubIMD(token).balanceOf(address(this)));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"manager only\");\n        (address payer, address recipient, PoolKey memory key, SwapParams memory params, bytes memory hookData) =\n            abi.decode(data, (address, address, PoolKey, SwapParams, bytes));\n        manager.swap(key, params, hookData);\n        settle(key.currency0, manager.currencyDelta(address(this), key.currency0), payer, recipient);\n        settle(key.currency1, manager.currencyDelta(address(this), key.currency1), payer, recipient);\n        return \"\";\n    }\n\n    function settle(Currency currency, int256 amount, address payer, address recipient) internal {\n        if (amount < 0) {\n            manager.sync(currency);\n            // The payer pre-funded the router (Permit2 pull in the real router).\n            (bool ok,) = Currency.unwrap(currency).call(\n                abi.encodeWithSignature(\"transfer(address,uint256)\", address(manager), uint256(-amount))\n            );\n            require(ok, \"transfer\");\n            manager.settle();\n            payer;\n        } else if (amount > 0) {\n            manager.take(currency, recipient, uint256(amount));\n        }\n    }\n}\n\ncontract RouterRewardSweepTest is Test, IUnlockCallback {\n    address internal constant IMD = 0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7;\n    uint160 internal constant Q96 = 79228162514264337593543950336;\n    IPoolManager internal manager;\n    EHS internal token;\n    EHSHook internal hook;\n    PoolKey internal key;\n    SharedRouter internal router;\n    bool internal pair0;\n\n    function setUp() public {\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        vm.etch(IMD, address(new StubIMD()).code);\n        StubIMD(IMD).mint(address(this), 1e30);\n        token = new EHS();\n        bytes memory code = abi.encodePacked(type(EHSHook).creationCode, abi.encode(manager, address(token)));\n        bytes32 hash = keccak256(code);\n        address deployed;\n        for (uint256 i; i < 300_000; ++i) {\n            address predicted =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), hash)))));\n            if (uint160(predicted) & 0x3fff != 0x20cc) continue;\n            bytes32 salt = bytes32(i);\n            assembly (\"memory-safe\") {\n                deployed := create2(0, add(code, 32), mload(code), salt)\n            }\n            break;\n        }\n        require(deployed != address(0), \"mining failed\");\n        hook = EHSHook(deployed);\n        pair0 = IMD < address(token);\n        key = PoolKey(\n            Currency.wrap(pair0 ? IMD : address(token)),\n            Currency.wrap(pair0 ? address(token) : IMD),\n            12500,\n            60,\n            IHooks(deployed)\n        );\n        manager.initialize(key, Q96);\n        manager.unlock(abi.encode(int24(-60000), int24(60000), int256(1_000_000 ether)));\n        router = new SharedRouter(manager, IMD);\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        (int24 lower, int24 upper, int256 liquidity) = abi.decode(data, (int24, int24, int256));\n        manager.modifyLiquidity(key, ModifyLiquidityParams(lower, upper, liquidity, bytes32(0)), \"\");\n        settle(key.currency0);\n        settle(key.currency1);\n        return \"\";\n    }\n\n    function settle(Currency currency) internal {\n        int256 amount = TransientStateLibrary.currencyDelta(manager, address(this), currency);\n        if (amount < 0) {\n            manager.sync(currency);\n            currency.transfer(address(manager), uint256(-amount));\n            manager.settle();\n        } else if (amount > 0) {\n            manager.take(currency, address(this), uint256(amount));\n        }\n    }\n\n    /// A buyer trades through a shared router with empty hookData (the Uniswap app's default). The\n    /// hook records the buyer's 1% reward to the router. A stranger then pays that record to the\n    /// router and sweeps it out. Expected: the buyer's reward is not payable to a stranger.\n    function test_rewardRecordedToSharedRouterIsSweptByAnyone() public {\n        address buyer = makeAddr(\"buyer\");\n        address stranger = makeAddr(\"stranger\");\n        StubIMD(IMD).mint(address(router), 1_000 ether); // the buyer's funds, pulled by the router\n        vm.prank(buyer);\n        router.swap(key, SwapParams(pair0, -int256(1_000 ether), pair0 ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1), \"\", buyer);\n        assertGt(token.balanceOf(buyer), 0, \"buyer received EHS\");\n        assertEq(hook.pending(buyer), 0, \"buyer has no record\");\n        emit log_named_uint(\"reward recorded to the shared router\", hook.pending(address(router)));\n\n        vm.startPrank(stranger);\n        hook.payRecorded(address(router));\n        router.sweep(IMD, stranger);\n        vm.stopPrank();\n\n        assertEq(StubIMD(IMD).balanceOf(stranger), 0, \"a stranger must not receive the buyer's reward\");\n    }\n}","reproduction":"State: launch pool at 1:1 with 1e6 liquidity. 1) buyer calls SharedRouter.swap(key, SwapParams(zeroForOne=buy, amountSpecified=-1000e18, limit), hookData=\"\", recipient=buyer). The router is sender; afterSwap records floor(filled/100) = 9.803921568627450980 IMD to pending[router], pending[buyer] stays 0. 2) stranger calls hook.payRecorded(router): IMD is taken to the router. 3) stranger calls router.sweep(IMD, stranger) (Universal Router SWEEP): stranger balance = 9.803921568627450980 IMD. Expected: the buyer's reward is not payable to an unrelated third party (the test asserts the stranger ends with 0 IMD and fails on the current code).","severity":"medium","snippet":"            address beneficiary = sender;","title":"Reward of a buy routed with empty hookData is recorded to the router, and on a shared router anyone can pay it out and sweep it"},{"citation":"resolved","description":"Every swap mints 2% of the filled IMD as ERC-6909 claims to the hook and adds it to accruedBalance. Only rewards (at most 1% of qualifying buys) ever leave through payRecorded. Nothing can move the rest: there is no sweep, donate, burn or beneficiary for accruedBalance - owedTotal, and the hook has no other function that calls burn/take. So 100% of sell fees, 100% of fees on buys below 10 IMD, and at least 50% of fees on qualifying buys are permanently stranded as claims inside the PoolManager under the hook's address. The brief says the fee 'accrues in the hook' but names no destination, so this may be intended; it is reported because the Invariant guide treats value with no exit as stranded, and because a permanent IMD sink of ~1.5% of pool volume is a material economic property of the launch that the README and manifest notes do not state.","line":109,"path":"src/EHSHook.sol","reproduction":"State: launch pool at 1:1. 1) exact-input buy of 1020 IMD: filled 1000 IMD, fee 20 IMD, reward 10 IMD recorded. 2) exact-input sell of 1000 EHS: pool pays 988.474193284141240783 IMD, fee 19.769483865682824815 IMD, no reward. 3) payRecorded(buyer) pays 10 IMD. Result: owedTotal = 0, accruedBalance = 29.769483865682824815 IMD = the hook's ERC-6909 IMD balance, and no entry point of EHSHook can ever reduce it (claimRewards/payRecorded only pay pending[], which is 0). Expected per the economics: a stated destination for the remainder (a burn, the pool, a treasury) or an explicit statement that it is a permanent sink.","severity":"low","snippet":"            accruedBalance += fee;","title":"The unowed fee remainder (all sell fees and at least half of every buy fee) has no recipient and is locked in the hook forever"},{"citation":"resolved","description":"When IMD is the specified currency (exact-input buy, exact-output sell) beforeSwap reserves ceil(X/51) or ceil(Y/49) and afterSwap mints reserve - fee back to `sender` as an ERC-6909 IMD claim. This is the shape the brief prescribes, but the economic consequence for the dominant trading path is not neutral: the Universal Router / V4Router has no action that burns ERC-6909 claims and cannot grant operator approval, so (a) on an exact-input buy that stops early (price limit or exhausted EHS liquidity) the payer's SETTLE_ALL pays the whole reserve and the refund stays in the router forever, and (b) on an exact-output sell that fills less than the reserve the router's IMD delta is negative, so TAKE_ALL reverts with DeltaNotPositive and the sell cannot execute through that router at all (including the launch's first sells into a pool that holds no IMD yet). Full fills refund at most 1 wei, so ordinary trades are unaffected; the exposure is confined to partial fills, which the README acknowledges. Reported so the judge can weigh whether the integration note is enough, since the loss falls on end users who never see hookData or claims.","line":103,"path":"src/EHSHook.sol","reproduction":"State: launch pool at 1:1 with 1e6 liquidity. Exact-input buy of 10,000 IMD with sqrtPriceLimit at tick +/-60 (one spacing) through a router that settles its full debt: filled 3042.383861004481674916 IMD, fee kept 60.847677220089633498 IMD, IMD taken from the payer 3238.462292377030694524, refund minted as a claim to the router 135.230754152459386110 IMD. A router without an ERC-6909 burn path leaves those 135.23 IMD (4.4% of what filled) unrecoverable by the payer. Exact-output sell of 100,000 IMD into a pool whose IMD side is empty: reserve ceil(1e23/49) = 2040.8 IMD, fill 0, router IMD delta = -2040.8 IMD, TAKE_ALL reverts. Expected: a partial fill costs the payer exactly fee(filled) through the launch's standard router, or the launch documentation names the router users must use.","severity":"low","snippet":"                poolManager.mint(sender, id, refund);","title":"Specified-side refunds are ERC-6909 claims minted to the router; a claims-unaware router (Universal Router) strands them for the payer or reverts the sell"},{"citation":"resolved","description":"The hook enables no liquidity callbacks, so modifyLiquidity on the launch pool is unmetered. A holder who wants IMD for EHS without the 2% fee adds EHS-only liquidity in the tick range just above the price, waits for buyers to push the price through it, and removes the position in IMD; they pay nothing to the hook and even collect the 1.25% LP fee from the buyers who crossed them. Likewise anyone may initialize a second EHS/IMD pool with no hook. Neither is a code defect against the brief (the factory itself needs unrestricted liquidity operations), but it bounds the economics: the 2% fee and the reward pool are only as large as the share of exits that go through swaps.","line":59,"path":"src/EHSHook.sol","reproduction":"State: launch pool at 1:1, 1e6 liquidity. 1) seller adds 2e21 liquidity in [60,120] (ticks above price; EHS only): deposits 5.972765609197763323 EHS, 0 IMD. 2) a buyer swaps 50,000 IMD exact-input, moving the price through that range (the buyer pays 980.39 IMD of hook fee). 3) seller removes the position: receives 6.103048518635421910 IMD and 0 EHS. Hook fee charged to the seller: 0. Expected under the brief's economics: every EHS->IMD exit of at least 10 IMD pays 2%; here the seller paid none.","severity":"info","snippet":"    function getHookPermissions() public pure returns (Hooks.Permissions memory p) {","title":"The 2% hook fee applies only to swaps: a seller can exit via a single-sided range order (or an un-hooked EHS/IMD pool) and pay no hook fee"}],"hash":"2c8bd7c9b98b924cd245bb202a5a06a08bc2847d9c5a33ae834a4024fd7bb43c","nodeId":"64e568dc-e87c-4fc8-bc96-5d3e02056d43","outcome":"completed","summary":"Review complete. The findings file is written at `.imd-findings.json` with four findings and twelve coverage rows, and no tracked file was changed.\n\n**What I checked.** I traced the fee and reward accounting through v4-core's `Hooks.afterSwap` delta subtraction and the ERC-6909 mint/burn paths for all four swap modes and partial fills. The core math holds: the reserve is always at least the fee, refunds never underflow, the hook's claim balance always backs what it owes, the reward cap never binds, and payouts cannot be redirected or reentered. The existing suite of 91 tests and the 256-run invariant suite pass.\n\n**Findings, most severe first:**\n\n- **Medium, reward attribution.** A buy with empty hookData (the Uniswap app default) records the 1% reward to the router. On a shared router with a permissionless sweep, such as the Universal Router, any stranger calls `payRecorded(router)` and sweeps the IMD. My scratch proof shows a stranger collecting a buyer's reward of about 9.8 IMD on a 1,000 IMD buy. The proof test fails on the current code and passes if the hook records nothing when no recipient is named.\n- **Low, stranded remainder.** All sell fees and at least half of every buy fee accrue in the hook with no exit path. After one buy and one sell of roughly 1,000 IMD each and a full payout, about 29.8 IMD sit in the hook forever. The brief is silent on a destination, so this may be intended, but nothing documents the sink.\n- **Low, claim refunds on partial fills.** Refunds on the specified side are ERC-6909 claims to the router. A claims-unaware router loses them for the payer on price-limited buys, and exact-output sells that fill below the reserve revert through such routers. Full fills refund at most 1 wei, so this is confined to partial fills, which the README acknowledges.\n- **Info, fee bypass by range order.** With no liquidity callbacks, a seller can exit through single-sided liquidity and pay no hook fee. This is inherent to the design and not a code defect.\n\n**Coverage.** All seven listed entry points have rows: the token functions, `claimRewards`, `payRecorded` and `unlockCallback` hold; `afterSwap` carries finding 1. I added rows for the view-only `beforeSwap` reserve math, `beforeInitialize`, two invariants and the manifest. Nothing in my area was left unreached. The mainnet-fork suite could not run here, so live IMD transfer behaviour remains owed to a fork run.","treeHash":null,"usage":{"cachedInputTokens":1384856,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":42847,"runtime":"claude","turns":27,"wallClockMs":818584}},{"artifacts":[],"attempt":2,"bundleHash":"571e3794e7d8edaa76c7434e42ada1066e9d6922e9c0ca20e07355a5fa1c08e6","device":"2ab0b432477fde6c","findings":[{"description":"accruedBalance is the hook's own count of fee claims; it is never reconciled against poolManager.balanceOf(hook, IMD id). Anyone can mint or transfer ERC-6909 IMD claims to the hook address (the PoolManager allows it), and because the hook has no owner, sweep or reconciliation path those claims can never be paid out as rewards or withdrawn. No user funds are at risk and the brief forbids an owner, so this is recorded for the README rather than as a defect; the invariant suite asserts balanceOf(hook) == accruedBalance + donated claims so the gap is exactly the outside deposits.","line":32,"path":"src/EHSHook.sol","reproduction":"Inside any unlock, call poolManager.mint(address(hook), uint256(uint160(IMD)), 1e18) and settle. Expected: nothing is recoverable (by design). Actual: nothing is recoverable; hook.accruedBalance() is unchanged and poolManager.balanceOf(hook, id) is 1e18 higher forever. Covered by invariant_claimsBackAccruedBalance in test/EHSHook.invariant.t.sol.","severity":"info","title":"ERC-6909 IMD claims pushed at the hook from outside are unrecoverable"},{"description":"On exact-input buys and exact-output sells the hook reserves ceil(amount/51) or ceil(amount/49) of IMD in beforeSwap and in afterSwap mints the unused part (reserve minus 2% of the actual fill) as an ERC-6909 claim to `sender`, which under PoolManager is the router. This is what the brief asks for and the accounting is exact (tests show the swapper nets filled + fee on buys and filled - fee on sells including the claim). Operationally, a router that does not burn or redeem claims for its payer strands the refund in the router's claim balance; routers integrating the pool must handle it. launch.json's notes already say so; recorded here so the audit record carries it.","line":103,"path":"src/EHSHook.sol","reproduction":"Exact-input buy of 100,000 IMD with a price limit one tick away (test_exactInputPartialFillRefund): the pool fills a few hundred IMD, the hook keeps 2% of the fill, and the remaining ~1,960 IMD of reserve is minted as an ERC-6909 claim to the router, not transferred to the user's wallet. Expected per brief: refund as an ERC-6909 claim to the swapper. Actual: identical; the claim's owner is the router contract.","severity":"info","title":"Specified-side fee refunds are ERC-6909 claims owned by the calling router, not the end user"}],"hash":"3cd370746fbde1fc8ce5663733298da10fa5eec5da5105f9ff795d3c20851423","nodeId":"a8f26cd5-e2df-4aad-8feb-df8a36289b52","outcome":"completed","summary":"The work is complete and verified. Here is the recap.\n\n**Outcome.** The full suite passes under the verifier's fuzz seed: 93 tests passed, 0 failed, 1 skipped (the mainnet-fork suite, which skips cleanly with no RPC). The build is clean. No defects were found in the hook or token that the brief's rules would have me report as broken behaviour.\n\n**What I added, all under test/:**\n\n- `test/EHSHook.invariant.t.sol`: a true Foundry invariant suite with a handler contract. The handler trades in every mode (buys and sells, exact-input and exact-output) with random price limits and five hookData shapes, pays rewards from random callers, claims as beneficiaries and strangers, redeems refund claims, adds and removes liquidity, runs the factory-style zero-liquidity collect, donates to the pool, and pushes outside ERC-6909 claims at the hook. It asserts per-swap that the fee is exactly 2% of the real fill, the swapper nets exactly fill plus or minus fee including refund claims, and rewards go only to the expected beneficiary. Eight invariants check claim backing, fee conservation, owed-equals-sum-of-pending, beneficiaries receive exactly their records while strangers receive nothing, manager IMD backs every claim, the hook never holds tokens or taxes EHS, and pool terms stay fixed. Run with fail-on-revert enabled, 256 runs by depth 40, and it recorded zero handler reverts across 10,240 calls.\n- `test/EHSHookEdge.t.sol`: constructor rejections (zero manager, zero token, IMD as token, wrong address bits), direct beforeSwap arithmetic incl. one wei and int256.min, a 2000-run fuzz over the whole int256 specified range proving the reserve is capped and never below any possible fill's fee (so the refund cannot underflow), event emission, malformed hookData fallback, sells never rewarding, huge exact-output buys under a price limit, a second pool being refused through the real manager, no-op claims, payouts not touching refund claims, payouts after all liquidity leaves, and third-party LP and donation flows being untouched by the hook.\n- `test/EHSEdge.t.sol`: token failure paths (over-balance, allowance one wei short, allowance over an empty balance, zero recipient via transferFrom), infinite allowance, approve overwrite and events, absence of admin surface, and two fuzz tests over transferFrom and random multi-party transfers conserving supply.\n\n**Findings file.** `.imd-findings.json` carries two info-level observations with reproductions, neither a defect against the brief: outside ERC-6909 claims pushed at the hook are unrecoverable by design, and specified-side refunds are claims owned by the calling router, so integrating routers must redeem them.\n\n**Still owed.** The fork tests against live mainnet state cannot run in this offline environment and remain to be exercised with a fork URL. Nothing was committed, since the brief did not ask for a commit.","treeHash":"6580d2bcd07e49e5d72725d1d00401d1da5c0fb4","usage":{"cachedInputTokens":2448222,"inputTokens":610,"model":"claude-fable-5-1","outputTokens":52095,"runtime":"claude","turns":32,"wallClockMs":1013567}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"8eebc53449bafe7b","findings":[],"hash":"7317f4367480c1de93d88ff155d8d34c53f016bf16feda970d55de04b2d85466","nodeId":"8cbe45f3-1c41-4e28-9498-22289d8f7208","outcome":"failed","summary":"could not restore accepted work: accepted work conflicts with a dependency that changed the same files after it was accepted: ","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":null,"outputTokens":0,"runtime":"claude","turns":0,"wallClockMs":2219}},{"artifacts":[],"attempt":1,"bundleHash":"899414879c71bc1e7826a7922c4692f38ff4c5759e35a0154baab7db1734f264","device":"d2ea4ab6ad35e6a1","findings":[],"hash":"74f9f8b0c8012a7aaf26ee5da867f4423d5328a312f17437f0b903a340d0d1ff","nodeId":"a1d1dff8-20d2-4e08-952d-6a893bbb0c84","outcome":"completed","summary":"Completed the existing launch project’s validation and documentation. Added tests for refund accounting, zero fills, rounding, threshold boundaries, and isolated reward payments.\n\nVerified:\n\n- `forge build` passed.\n- 60 tests passed with the required fuzz seed.\n- 2 mainnet-fork tests passed at block 26,198,113.\n- `forge fmt --check` passed.\n\nAccepted contracts, manifest, protected configuration, and dependencies remain unchanged.","treeHash":"47a4caaf44ddb78a613d4654313b9923057d5e99","usage":{"cachedInputTokens":3274240,"inputTokens":136677,"model":"gpt-6-astra","outputTokens":19128,"runtime":"codex","turns":8,"wallClockMs":878445}},{"artifacts":[],"attempt":1,"bundleHash":"5dd3104b91c8b0249afe34b0561107c8843de34a50641aca3bd90e1d2afcd6c1","device":"93c37f17670e4d98","findings":[],"hash":"806a9e0908a310af61d0cd87e9e6468d0aac0289350e9a76e12c72c2d6b235d6","nodeId":"a8f26cd5-e2df-4aad-8feb-df8a36289b52","outcome":"completed","summary":"The revision is complete and verified. Only the two test files I own changed.\n\n**What was wrong.** The author's current hook no longer credits the calling swapper when hookData is empty or malformed; it records no reward at all, because PoolManager's `sender` is the router and a reward owed to a shared router is sweepable by anyone. My previously accepted edge test and invariant handler still assumed the old fallback, so one unit test and all eight invariants failed against this tree under the verifier's seed. That is the fault I removed.\n\n**Changes in test/EHSHookEdge.t.sol**\n- Rewrote the malformed-context test: four non-canonical hookData shapes on qualifying buys all take the 2% fee, credit nobody, and leave owedTotal at zero, while the same buy with a canonical word is rewarded.\n- Added a test settling the outside-claims finding: a helper mints ERC-6909 IMD claims to the hook, and the test shows accruedBalance, owedTotal, later fees, payments and claims never touch them, matching the README's \"Permanent remainder\" paragraph.\n- Added two tests settling the refund-owner finding: a Universal-Router-shaped router making a price-limited exact-input buy ends up owning the refund claim while its payer holds none, and a router that burns its refund before settling charges its payer exactly fill plus 2%.\n\n**Changes in test/EHSHook.invariant.t.sol**\n- The handler now expects a reward only for the canonical-word mode, requires the swapper itself is never credited, and counts unattributed qualifying buys.\n- The owed-total invariant asserts the router-shaped swapper is never owed.\n\n**Results**\n- Full suite with the verifier's fuzz seed: 96 passed, 0 failed, 1 skipped (the mainnet fork test, which skips without an RPC URL).\n- Invariants also pass under a second seed. A stray root-level cache directory that Foundry's failure persistence created was removed.\n\n**Findings.** Both earlier findings are settled by the tests above and are not re-reported. I found no new defect, so no findings file was written. The fork tests against live mainnet state remain owed, since this machine and the verifier have no network.","treeHash":"6212aca0b4530157ec151cafe3f7226d3635e6c0","usage":{"cachedInputTokens":945260,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":19536,"runtime":"claude","turns":27,"wallClockMs":485892}},{"artifacts":[],"attempt":2,"bundleHash":"a03b2d21fc6aff8593cf87a874b6ea8d57a1a0665d76698711d601f98075a0a2","device":"f7cb61cceaa27e78","findings":[],"hash":"8b02bcc11ac39809c8dbbbba9f839cc17fa7d5bade83160cc427afce45b69c7d","nodeId":"8cbe45f3-1c41-4e28-9498-22289d8f7208","outcome":"completed","summary":"Updated only [launch.json](/home/imd-worker/.identitymd/work/3de72123-ec64-4827-8476-6acf09b3cceb/8cbe45f3-1c41-4e28-9498-22289d8f7208/launch.json), verifying constructor arguments, permissions and pool values against the implementation.\n\n`forge build` passed with existing lint warnings. Tests with the specified seed: **91 passed, 0 failed**; the mainnet-fork suite skipped because no fork was configured.","treeHash":"333216fe1ddbcdc5ab238c8b906123897f25ecaf","usage":{"cachedInputTokens":934528,"inputTokens":74424,"model":"gpt-6-astra","outputTokens":5215,"runtime":"codex","turns":6,"wallClockMs":363665}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ea5566a759ea17b3","findings":[],"hash":"94b641fff5717561c1ac53e55206d72ab55de014254c54603a080750b2001928","nodeId":"a8f26cd5-e2df-4aad-8feb-df8a36289b52","outcome":"failed","summary":"This content was flagged for possible cybersecurity risk. If this seems wrong, try rephrasing your request. If you’re doing authorized security work that requires more cyber permissive safeguards, apply for Daybreak access via https://platform.openai.com/settings/organization/status-and-access before retrying.","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":"gpt-6-astra","outputTokens":0,"runtime":"codex","turns":6,"wallClockMs":1051849}},{"artifacts":[],"attempt":1,"bundleHash":"e04b7aa9ce0e2a88c895ac151ef5673c6d1e31026cfcbf0f5bc86b2257bb1a3e","device":"6208734cdf5317a1","findings":[],"hash":"bb669335aa699c214ff156f023c459cb911074f8c3c3e646c96331cadafb4c15","nodeId":"a1d1dff8-20d2-4e08-952d-6a893bbb0c84","outcome":"completed","summary":"The revision is complete. Build, the full test suite with the verifier's fuzz seed, and the format check all pass.\n\n**The high finding reproduced and is fixed.** The reviewer's proof failed on the starting tree exactly as reported: a buy through a router with empty hook data recorded its reward to the router. In `src/EHSHook.sol` the afterSwap reward path now uses a small helper that returns a recipient only when hook data is exactly one canonical nonzero address word. Without one, the swap still goes through and the 2% fee is still taken, but no reward is recorded and the calling router is never credited. The proof passes on the result.\n\n**Tests changed.**\n- The malformed-context test now asserts that empty, short, noncanonical and zero-address hook data leave no pending reward for anyone and leave owedTotal at zero, while the fee is still accrued.\n- A new test deploys a Universal-Router-shaped router with a permissionless sweep, buys with empty hook data, and checks that payRecorded on the router moves nothing and the sweep yields only the payer's unspent leftover. It then shows the same router naming the buyer earns the reward for the buyer.\n\n| Check | Result |\n| --- | --- |\n| forge test with the verifier's seed | 63 passed, 0 failed, 1 skipped (fork suite, no RPC) |\n| forge fmt --check | clean |\n| EHSHook runtime / creation bytes | 4,153 / 4,864 |\n\n**Advisory findings.** None required code changes, and the responses file marks them as fixed by documentation. The README now states the router requirement for ERC-6909 refund claims, the exact-output sell sign flip on small fills, that the threshold and reward base are measured on the pool-side fill, that the unrewarded fee remainder is a permanent sink, and that outside claims pushed at the hook are unrecoverable. The manifest notes and the security review carry the same statements, and the old sender-fallback wording is gone everywhere.\n\n**Files touched:** `src/EHSHook.sol`, `test/EHSHook.t.sol`, `README.md`, `launch.json` notes, `artifacts/security-review.md`, and the new `.imd-responses.json` answering all six finding ids. Nothing was committed, and the fork tests were not re-run because this machine has no RPC; the change does not touch the paths they exercise.","treeHash":"3bfbf5e111962b72555e62c23b9cff771298d343","usage":{"cachedInputTokens":959007,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":19499,"runtime":"claude","turns":35,"wallClockMs":522603}},{"artifacts":[],"attempt":1,"bundleHash":"7afade2bcff2f9407b688ead4d2dc3d9222071568f4bd8879cc22c881d75915a","device":"63458a1bd2b2d376","findings":[],"hash":"c55ac8dbd1ff7a75b63d25e3061d1d900810ae336da2b0bb7d94358940f5dfb0","nodeId":"8cbe45f3-1c41-4e28-9498-22289d8f7208","outcome":"completed","summary":"launch.json is written and verified. Only that file changed.\n\n**What the manifest declares**\n\n- **Top-level keys** in order: kind (\"univ4_hook\"), token, hook, pool, notes. Nothing else.\n- **Token**: contract EHS, name Everted Hand Swarm, symbol EHS, 18 decimals, matching `src/EHS.sol`.\n- **Hook**: contract EHSHook with constructorArgs [\"$poolManager\", \"$token\"], matching the two-parameter constructor in `src/EHSHook.sol:46` in declaration order.\n- **Permissions**: beforeInitialize, beforeSwap, afterSwap, beforeSwapReturnDelta, afterSwapReturnDelta. These are exactly what getHookPermissions returns, and they correspond to the FLAGS constant 0x20cc the constructor validates.\n- **Pool**: pairedCurrency is the IMD address, fee 12500, tickSpacing 60, initialPrice 79228162514264337593543950336, all as the launch facts give them.\n- **Notes**: one string of 2765 characters covering constructor args, permission bits, the beforeInitialize pool check, the 2% hook fee mechanics on both specified and unspecified sides, the reconciliation refund as an ERC-6909 claim, the UnrepresentableFee exception, buyer rewards and claiming, and the factory-handled supply.\n\n**Checks run**\n\n| Check | Result |\n| --- | --- |\n| forge build | passes, one pre-existing lint warning in the hook |\n| forge test with the verifier fuzz seed | 96 passed, 0 failed, 1 skipped (the mainnet fork test, no RPC) |\n| JSON shape, key order, placeholders, lowercase addresses | confirmed by script |\n| git status | only launch.json modified |\n\nThe hook needs no owner or factory address, so no placeholder beyond $poolManager and $token is required. No file outside launch.json was touched, and no dependency was installed.","treeHash":"189cf3f4e978893cba343d529a30cdc21ca42613","usage":{"cachedInputTokens":450791,"inputTokens":226,"model":"claude-fable-5-1","outputTokens":6793,"runtime":"claude","turns":13,"wallClockMs":243051}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"589ef002581a5371","findings":[{"citation":"resolved","description":"M1 says the 1% reward belongs to the buyer, the swap recipient. afterSwap attributes it to `sender`, which in Uniswap v4 is the contract that called PoolManager.swap (the router), unless hookData is exactly one 32-byte word holding an address. The Uniswap interface and the Universal Router send empty hookData for v4 pools, so every UI buy of at least 10 IMD records its reward to the shared router. payRecorded(address) is permissionless by design and pushes real IMD to that router, and the Universal Router's SWEEP command lets any caller move tokens the router holds. The buyer's reward is therefore paid to whichever bot calls payRecorded(router) and sweeps first; for routers without a sweep it is an obligation nobody entitled can claim, inflating owedTotal forever. The README documents the fallback as a limitation, but the spec names the beneficiary as the buyer, and the consequence is that the fee pool meant for buyers is paid to a third party on the default trading path. Reported by audit_economics, audit_flow and audit_permissions (same root cause, merged); all three attached proofs fail on the current code for this reason. Fix direction: record no reward when hookData does not name a beneficiary (leave the fee unowed in the hook) instead of crediting `sender`.","line":113,"path":"src/EHSHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {TransientStateLibrary} from \"v4-core/src/libraries/TransientStateLibrary.sol\";\nimport {EHS} from \"src/EHS.sol\";\nimport {EHSHook} from \"src/EHSHook.sol\";\n\n/// @dev Stand-in for the mainnet IMD ERC-20 (etched at its address).\ncontract PlainERC20 {\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n\n    function mint(address to, uint256 amount) external {\n        balanceOf[to] += amount;\n    }\n\n    function approve(address spender, uint256 amount) external returns (bool) {\n        allowance[msg.sender][spender] = amount;\n        return true;\n    }\n\n    function transfer(address to, uint256 amount) external returns (bool) {\n        balanceOf[msg.sender] -= amount;\n        balanceOf[to] += amount;\n        return true;\n    }\n\n    function transferFrom(address from, address to, uint256 amount) external returns (bool) {\n        allowance[from][msg.sender] -= amount;\n        balanceOf[from] -= amount;\n        balanceOf[to] += amount;\n        return true;\n    }\n}\n\n/// @dev A shared, permissionless router in the shape of the Universal Router: it swaps for\n/// whoever calls it, sends the output to the caller's chosen recipient, passes the hookData the\n/// caller gives it (the Uniswap interface gives none), and lets anyone sweep tokens it holds.\ncontract SharedRouter is IUnlockCallback {\n    IPoolManager immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    function swap(PoolKey calldata key, SwapParams calldata params, address recipient, bytes calldata hookData)\n        external\n    {\n        manager.unlock(abi.encode(msg.sender, key, params, recipient, hookData));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (address payer, PoolKey memory key, SwapParams memory params, address recipient, bytes memory hookData) =\n            abi.decode(data, (address, PoolKey, SwapParams, address, bytes));\n        BalanceDelta delta = manager.swap(key, params, hookData);\n        _settle(key.currency0, delta.amount0(), payer, recipient);\n        _settle(key.currency1, delta.amount1(), payer, recipient);\n        return \"\";\n    }\n\n    function _settle(Currency c, int128 amount, address payer, address recipient) internal {\n        if (amount < 0) {\n            manager.sync(c);\n            PlainERC20(Currency.unwrap(c)).transferFrom(payer, address(manager), uint256(uint128(-amount)));\n            manager.settle();\n        } else if (amount > 0) {\n            manager.take(c, recipient, uint256(uint128(amount)));\n        }\n    }\n\n    /// @dev Universal Router's SWEEP command: anyone may take whatever the router holds.\n    function sweep(address token, address to) external {\n        PlainERC20(token).transfer(to, PlainERC20(token).balanceOf(address(this)));\n    }\n}\n\ncontract RouterRewardLeakTest is Test, IUnlockCallback {\n    address constant IMD = 0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7;\n    uint160 constant Q96 = 79228162514264337593543950336;\n\n    PoolManager manager;\n    EHS token;\n    EHSHook hook;\n    SharedRouter router;\n    PoolKey key;\n    bool pair0;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        token = new EHS();\n        vm.etch(IMD, address(new PlainERC20()).code);\n\n        bytes memory code = abi.encodePacked(type(EHSHook).creationCode, abi.encode(manager, address(token)));\n        bytes32 hash = keccak256(code);\n        address deployed;\n        for (uint256 i; i < 300_000; ++i) {\n            bytes32 salt = bytes32(i);\n            address predicted =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, hash)))));\n            if (uint160(predicted) & 0x3fff != 0x20cc) continue;\n            assembly (\"memory-safe\") {\n                deployed := create2(0, add(code, 32), mload(code), salt)\n            }\n            break;\n        }\n        require(deployed != address(0), \"mining failed\");\n        hook = EHSHook(deployed);\n        pair0 = IMD < address(token);\n        key = PoolKey(\n            Currency.wrap(pair0 ? IMD : address(token)),\n            Currency.wrap(pair0 ? address(token) : IMD),\n            12500,\n            60,\n            IHooks(deployed)\n        );\n        manager.initialize(key, Q96);\n        PlainERC20(IMD).mint(address(this), 10_000_000 ether);\n        manager.unlock(\"\");\n        router = new SharedRouter(manager);\n    }\n\n    // Liquidity provision for the test's own pool.\n    function unlockCallback(bytes calldata) external returns (bytes memory) {\n        manager.modifyLiquidity(key, ModifyLiquidityParams(-60000, 60000, 1_000_000 ether, bytes32(0)), \"\");\n        _pay(key.currency0);\n        _pay(key.currency1);\n        return \"\";\n    }\n\n    function _pay(Currency c) internal {\n        int256 d = TransientStateLibrary.currencyDelta(manager, address(this), c);\n        if (d < 0) {\n            manager.sync(c);\n            PlainERC20(Currency.unwrap(c)).transfer(address(manager), uint256(-d));\n            manager.settle();\n        }\n    }\n\n    /// A buyer buys through a shared router with the empty hookData the Uniswap interface sends.\n    /// The reward for that buy must not be credited to the router, where anyone can take it.\n    function test_rewardIsNotCreditedToASharedRouterOnEmptyHookData() public {\n        address buyer = makeAddr(\"buyer\");\n        address attacker = makeAddr(\"attacker\");\n        PlainERC20(IMD).mint(buyer, 1_000 ether);\n        vm.prank(buyer);\n        PlainERC20(IMD).approve(address(router), type(uint256).max);\n\n        // Exact-output buy of EHS: IMD is the unspecified side, so the fee and the reward\n        // follow afterSwap's real fill. The buy fills well above the 10 IMD threshold.\n        bool zeroForOne = pair0;\n        uint160 limit = zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1;\n        vm.prank(buyer);\n        router.swap(key, SwapParams(zeroForOne, int256(500 ether), limit), buyer, \"\");\n\n        assertGt(token.balanceOf(buyer), 0, \"buyer received EHS\");\n        assertGt(hook.accruedBalance(), 10 ether, \"hook took its 2% fee\");\n\n        // The buyer is the swap recipient; the router is a shared contract. Nothing should be owed\n        // to the router.\n        assertEq(hook.pending(address(router)), 0, \"reward recorded to the shared router\");\n\n        // Anyone can pay the router's record and sweep it away from the buyer.\n        vm.startPrank(attacker);\n        hook.payRecorded(address(router));\n        router.sweep(IMD, attacker);\n        vm.stopPrank();\n        assertEq(PlainERC20(IMD).balanceOf(attacker), 0, \"attacker swept the buyer's reward\");\n    }\n}","reproduction":"State: fresh PoolManager, EHS/IMD pool (fee 12500, spacing 60, price Q96, 1e6 ether liquidity over [-60000, 60000]); a shared router in the shape of the Universal Router (swaps for msg.sender, takes output to the chosen recipient, has a permissionless sweep). 1) buyer calls router.swap(key, SwapParams(zeroForOne = IMD->EHS, amountSpecified = +500e18 exact-output EHS, extreme limit), recipient = buyer, hookData = \"\"). Pool fills ~506.6 IMD; hook accrues 10131648102532278796 wei fee. Expected: pending(buyer) > 0 or nothing recorded; pending(router) == 0. Actual: pending(router) == 5065824051266139398, pending(buyer) == 0. 2) attacker calls hook.payRecorded(router): 5065824051266139398 wei IMD is transferred to the router. 3) attacker calls router.sweep(IMD, attacker): attacker's IMD balance == 5065824051266139398. Same result for an exact-input buy of 100 IMD on a token-only seeded pool (pending(router) == 980392156862745098) and of 1000 IMD (9803921568627450980). Run: forge test --match-path test/scratch/Proof_e9ddcd6f72e6.t.sol (fails: 'reward recorded to the shared router: 5065824051266139398 != 0').","severity":"high","snippet":"            address beneficiary = sender;","title":"Buy rewards fall back to the calling router when hookData names no recipient; payRecorded(router) plus the router's public sweep hands the buyer's reward to anyone"},{"citation":"resolved","description":"When IMD is the specified currency (exact-input buy, exact-output sell) beforeSwap reserves ceil(amount/51) or ceil(amount/49) on the specified side and afterSwap reconciles against the real fill by minting reserve minus fee back to `sender` as a PoolManager ERC-6909 claim. This is the mechanism the build requirements prescribe, and the hook's own accounting is correct: net of claims the swapper pays exactly floor(fill/50). Two consequences follow at the router boundary. (a) The swapper's transient IMD delta is debited the whole reserve, so a router that never burns claims (Universal Router / V4Router settle only by sync/transfer/settle, and nobody else can burn claims owned by the router) leaves the refund stranded on the router and its payer has paid reserve, not fee, on a price-limited partial fill. Full fills leave at most 1 wei, so ordinary trades are unaffected. (b) On an exact-output sell whose fill is smaller than the reserve the IMD delta sign flips: a seller who brought only EHS owes IMD to the manager, so a TAKE_ALL-style router reverts (DeltaNotPositive) and the sell cannot execute through it, and a router paying deltas in cash must front IMD it was not told to hold. The README documents the burn-then-settle flow but not that a sell can require the seller to pay IMD. Reported by audit_economics, audit_math, audit_flow and audit_permissions (merged; one root cause). Kept at low because the brief mandates the claim refund and accepts it as the reconciliation; the author should state the router requirement and the sign-flip case in the manifest notes/README or, if a redesign is wanted, size exact-output reserves against the pool's reachable output.","line":103,"path":"src/EHSHook.sol","reproduction":"State: fresh PoolManager, EHS/IMD pool (fee 12500, spacing 60, price Q96, 1e6 ether liquidity over [-60000, 60000]); the swapper calls manager.swap inside its own unlock with hookData abi.encode(swapper). Case (a): SwapParams(zeroForOne = IMD->EHS, amountSpecified = -10000e18, sqrtPriceLimit = 0.5% move from spot). Observed: swapper IMD delta after swap = -5259369570613055348722 (reserve charged in full), hook fee accrued = 101265822784810126582 (2% of the 5063.29 IMD fill), refund minted as claims to sender = 94812608587738893026. A router that does not burn claims leaves those 94.81 IMD stranded and its payer has paid 196.08 IMD above the fill (3.87%), not 2%. Case (b): SwapParams(zeroForOne = EHS->IMD, amountSpecified = +100000e18, sqrtPriceLimit = one tick past spot). Observed: reserve = ceil(1e23/49) = 2040816326530612244898; pool outputs 49996250312472658710 IMD; fee = 999925006249453174; refund claims = 2039816401524362791724; swapper IMD delta after swap = -1990820076218139586188 (negative on a sell), EHS delta = -50631645632907436987. Expected per the requirement text: a partial fill costs the swapper exactly fee(filled) through the launch's standard router, and a sell never ends with the seller owing IMD. Reproduced in test/scratch/Judge.t.sol (tests test_exactInputBuyPartialFillRefundIsClaims and test_exactOutputSellPartialFillFlipsSign, which log these values).","severity":"low","snippet":"                poolManager.mint(sender, id, refund);","title":"IMD-specified partial fills return the unused reserve only as ERC-6909 claims minted to the router; claims-unaware routers strand them, and a fill under ~2% of an exact-output sell leaves the seller w"},{"citation":"resolved","description":"Every swap mints 2% of the filled IMD as ERC-6909 claims to the hook and adds it to accruedBalance. The only outflow is payRecorded, which pays recorded buyer rewards (at most 1% of qualifying buys). Nothing can move the rest: there is no sweep, donate, burn or beneficiary for accruedBalance minus owedTotal, and no other function calls burn or take. So 100% of sell fees, 100% of fees on buys that fill under 10 IMD, and at least 50% of fees on qualifying buys are permanently held as claims under the hook's address. M1 says the fee 'accrues in the hook' and names no destination, so this matches the mechanics as written and is not a defect; it is recorded (as audit_economics and audit_flow did) so the author confirms the permanent sink is intended and states it in the README and manifest notes, which currently do not.","line":109,"path":"src/EHSHook.sol","reproduction":"State: pool at Q96 with 1e6 ether liquidity. 1) exact-input buy of 1020 IMD with hookData abi.encode(buyer): fill 1000 IMD, fee 20 IMD, reward 10 IMD recorded. 2) exact-input sell of 1000 EHS: pool pays ~988.47 IMD, fee ~19.77 IMD, no reward. 3) payRecorded(buyer) pays 10 IMD. Result: owedTotal == 0, accruedBalance == ~29.77 IMD == manager.balanceOf(hook, IMD id), and no entry point of EHSHook can reduce it (claimRewards and payRecorded only pay pending[], which is 0). Expected: a stated destination for the remainder or an explicit statement that it is a permanent sink.","severity":"info","snippet":"            accruedBalance += fee;","title":"The unowed fee remainder (all sell fees and at least half of every buy fee) has no recipient and no exit; it stays as hook-owned claims forever"},{"citation":"resolved","description":"M1 says every buy of at least 10 units of the paired currency records a reward of 1% of that buy's paired-currency amount. For an exact-input buy, beforeSwap first reserves ceil(|amountSpecified|/51), the pool swaps the rest, and afterSwap derives `filled` from that pool-side delta, so the threshold and the reward base see 50/51 of what the buyer spent. A buyer specifying exactly 10e18 IMD input fills 9803921568627450980 wei, below MIN_BUY, and records no reward; the exact-output branch measures the same pool amount and that buyer pays 10.2 IMD in total. The two branches are consistent with each other (fee and reward are always 2% and 1% of the pool-side IMD, as the 'proportional to what actually filled' requirement asks) and the README states the threshold as 'actual incoming IMD'. Whether 'buy of at least 10 units' means the gross spend or the fill is an interpretation the author should confirm; it is not a correctness defect. Reported by audit_math and audit_permissions (merged).","line":112,"path":"src/EHSHook.sol","reproduction":"State: pool at Q96 with 1e6 ether liquidity. Call manager.swap(key, SwapParams(zeroForOne = IMD->EHS, amountSpecified = -10e18, extreme limit), abi.encode(buyer)) inside an unlock and settle. Arithmetic: reserve = ceil(1e19/51) = 196078431372549020; pool swaps 9803921568627450980; fee = 196078431372549019; refund 1 wei; filled < 10e18 so no RewardRecorded and pending(buyer) == 0, while the buyer's IMD balance fell by exactly 10e18. With amountSpecified = -10.2e18 the fill is 10e18 and pending(buyer) == 1e17. Expected under the gross-spend reading: pending(buyer) == 1e17 for the 10 IMD spend.","severity":"info","snippet":"        if (pairDelta < 0 && filled >= MIN_BUY) {","title":"The 10 IMD reward threshold and the 1% base are measured on the pool-side fill, so an exact-input buyer spending exactly 10 IMD earns nothing and must spend about 10.2 IMD"}],"hash":"cca9837839dfe95bd0dbb29083889077fc56c4221e2349f16c4d1f8590be7754","nodeId":"bed0c0dc-d5b0-4538-8fec-bcf1ae91472a","outcome":"completed","summary":"The review is complete. `.imd-findings.json` holds four findings and a ten-row coverage record, and no tracked file was changed.\n\n**What I did**\n\n- Built the project and ran its suite: 91 tests pass, the fork suite skips offline.\n- Ran all three specialist proofs under `test/scratch/`. Each fails on the current code for exactly the reason its finding states.\n- Reproduced the partial-fill claims behaviour with my own scratch test. The numbers match the specialists' figures to the wei.\n- Checked the live IMD contract on mainnet. It has code, reports symbol \"IMD\" and 18 decimals, and looks like a LayerZero OFT ERC-20 with no transfer callbacks.\n- Reviewed the hook, token, manifest and the three protected floor tests myself.\n\n**Findings kept, after merging duplicates**\n\n1. **High.** Buy rewards fall back to `sender` (the router) when hookData names no recipient. The Uniswap interface sends empty hookData, so rewards land on the Universal Router, where anyone can call `payRecorded(router)` and sweep the IMD. Three specialists reported this; merged into one, carrying the permissions specialist's proof, which fails on current code.\n2. **Low.** Partial fills on IMD-specified swaps refund the reserve only as ERC-6909 claims to the router. Claims-unaware routers strand them, and a tiny exact-output sell leaves the seller with a negative IMD delta. Four specialist reports merged. Kept low because the brief mandates this refund mechanism.\n3. **Info.** The unowed fee remainder has no exit and stays as hook claims forever. This matches M1 as written, so it is a confirmation request, not a defect.\n4. **Info.** The 10 IMD threshold and reward base use the pool-side fill, so a 10 IMD exact-input spend earns nothing. The implementation is internally consistent; the wording is ambiguous.\n\n**Dropped**\n\n- Fee rounding down by under 50 wei per swap. Not exploitable and documented.\n- Range-order exits avoiding the hook fee. Inherent to any swap-fee hook and the factory needs ungated liquidity.\n\n**Coverage**\n\nAll seven listed entry points are answered. `afterSwap` carries finding 1; the other six hold. I added rows for the view callbacks, the manifest and the protected floor, all of which hold. Delta accounting nets the hook to zero in all four swap modes, the reserve can never be smaller than the realised fee, and the deployability constraints are met.","treeHash":null,"usage":{"cachedInputTokens":1028864,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":29626,"runtime":"claude","turns":23,"wallClockMs":643104}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"7f1dec5ffcbde1d8","findings":[{"citation":"resolved","description":"afterSwap records the 1% buyer reward to `sender`, the PoolManager caller, whenever hookData is not exactly one 32-byte word naming an address. On mainnet the caller is almost always a shared router (Uniswap's Universal Router, aggregator adapters) and standard frontends pass empty hookData, so the reward is recorded to the router, not to the buyer the mechanics name ('the swap recipient'). The record is then payable by anyone through payRecorded(router): the IMD lands on the router contract, and the Universal Router's permissionless SWEEP command moves any token balance it holds to the caller. The buyer's reward is therefore paid to whichever bot calls payRecorded(router) and sweeps first. Even for routers without a sweep, the record is an obligation nobody entitled can claim, inflating owedTotal forever. The README documents the fallback as a limitation, but the hook has the information to avoid it: when no beneficiary context is present it does not know the buyer and should record nothing (or record only when hookData names one), rather than crediting a party that is never the buyer.","line":113,"path":"src/EHSHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {TransientStateLibrary} from \"v4-core/src/libraries/TransientStateLibrary.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {EHS} from \"src/EHS.sol\";\nimport {EHSHook} from \"src/EHSHook.sol\";\n\ninterface IERC20Like {\n    function transfer(address, uint256) external returns (bool);\n    function transferFrom(address, address, uint256) external returns (bool);\n    function balanceOf(address) external view returns (uint256);\n    function approve(address, uint256) external returns (bool);\n}\n\n/// @dev Plain ERC-20 standing in for IMD at its mainnet address.\ncontract PlainIMD {\n    string public constant symbol = \"IMD\";\n    uint8 public constant decimals = 18;\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n    function mint(address to, uint256 a) external { balanceOf[to] += a; }\n    function approve(address s, uint256 a) external returns (bool) { allowance[msg.sender][s] = a; return true; }\n    function transfer(address to, uint256 a) external returns (bool) {\n        balanceOf[msg.sender] -= a; balanceOf[to] += a; return true;\n    }\n    function transferFrom(address f, address to, uint256 a) external returns (bool) {\n        allowance[f][msg.sender] -= a; balanceOf[f] -= a; balanceOf[to] += a; return true;\n    }\n}\n\n/// @dev A shared router shaped like the Universal Router's v4 path: the caller supplies no\n/// hookData, the router settles its debt from the payer, takes the credit to the user, and has a\n/// public sweep for tokens left on it (Universal Router's SWEEP command).\ncontract SharedRouter is IUnlockCallback {\n    using TransientStateLibrary for IPoolManager;\n    IPoolManager immutable manager;\n    constructor(IPoolManager m) { manager = m; }\n\n    error DeltaNotPositive();\n\n    function swap(PoolKey memory key, SwapParams memory p, address recipient, bytes memory hookData) external {\n        manager.unlock(abi.encode(key, p, msg.sender, recipient, hookData));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (PoolKey memory key, SwapParams memory p, address payer, address recipient, bytes memory hookData) =\n            abi.decode(data, (PoolKey, SwapParams, address, address, bytes));\n        manager.swap(key, p, hookData);\n        Currency input = p.zeroForOne ? key.currency0 : key.currency1;\n        Currency output = p.zeroForOne ? key.currency1 : key.currency0;\n        int256 d = manager.currencyDelta(address(this), input);\n        if (d < 0) {\n            manager.sync(input);\n            IERC20Like(Currency.unwrap(input)).transferFrom(payer, address(manager), uint256(-d));\n            manager.settle();\n        }\n        int256 o = manager.currencyDelta(address(this), output);\n        if (o < 0) revert DeltaNotPositive();\n        if (o > 0) manager.take(output, recipient, uint256(o));\n        return \"\";\n    }\n\n    function sweep(address token, address to) external {\n        IERC20Like(token).transfer(to, IERC20Like(token).balanceOf(address(this)));\n    }\n}\n\ncontract RewardToRouterProof is Test, IUnlockCallback {\n    address constant IMD = 0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7;\n    uint160 constant Q96 = 79228162514264337593543950336;\n    PoolManager manager;\n    EHS token;\n    EHSHook hook;\n    PoolKey key;\n    SharedRouter router;\n    bool pair0;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        vm.etch(IMD, address(new PlainIMD()).code);\n        token = new EHS();\n        bytes memory code = abi.encodePacked(type(EHSHook).creationCode, abi.encode(manager, address(token)));\n        bytes32 hash = keccak256(code);\n        address deployed;\n        for (uint256 i; i < 300_000; ++i) {\n            bytes32 salt = bytes32(i);\n            address predicted =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, hash)))));\n            if (uint160(predicted) & 0x3fff != 0x20cc) continue;\n            assembly (\"memory-safe\") { deployed := create2(0, add(code, 32), mload(code), salt) }\n            break;\n        }\n        hook = EHSHook(deployed);\n        pair0 = IMD < address(token);\n        key = PoolKey(\n            Currency.wrap(pair0 ? IMD : address(token)),\n            Currency.wrap(pair0 ? address(token) : IMD),\n            12500,\n            60,\n            IHooks(deployed)\n        );\n        manager.initialize(key, Q96);\n        router = new SharedRouter(manager);\n        // Token-only seed, as the launch factory does.\n        int24 lower = pair0 ? int24(-600) : int24(60);\n        int24 upper = pair0 ? int24(-60) : int24(600);\n        manager.unlock(abi.encode(lower, upper, int256(1_000_000 ether)));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        (int24 lower, int24 upper, int256 liq) = abi.decode(data, (int24, int24, int256));\n        (BalanceDelta delta,) = manager.modifyLiquidity(key, ModifyLiquidityParams(lower, upper, liq, bytes32(0)), \"\");\n        settle(key.currency0, delta.amount0());\n        settle(key.currency1, delta.amount1());\n        return \"\";\n    }\n\n    function settle(Currency c, int128 a) internal {\n        if (a < 0) {\n            manager.sync(c);\n            IERC20Like(Currency.unwrap(c)).transfer(address(manager), uint256(uint128(-a)));\n            manager.settle();\n        } else if (a > 0) {\n            manager.take(c, address(this), uint256(uint128(a)));\n        }\n    }\n\n    /// A buy routed through a shared router with no hookData must not credit the router: the\n    /// router is not the buyer, and anyone can pay it with payRecorded(router) and sweep the IMD.\n    function test_rewardWithoutHookDataMustNotGoToTheRouter() public {\n        address alice = makeAddr(\"alice\");\n        PlainIMD(IMD).mint(alice, 100 ether);\n        vm.prank(alice);\n        IERC20Like(IMD).approve(address(router), type(uint256).max);\n        uint160 limit = pair0 ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1;\n        vm.prank(alice);\n        router.swap(key, SwapParams(pair0, -int256(100 ether), limit), alice, \"\");\n        assertGt(token.balanceOf(alice), 0, \"alice bought EHS\");\n        assertEq(hook.pending(alice), 0, \"alice is unknown to the hook\");\n\n        // Current code: pending[router] == 0.980392156862745098e18 and a bot takes it.\n        uint256 recorded = hook.pending(address(router));\n        address bot = makeAddr(\"bot\");\n        vm.prank(bot);\n        hook.payRecorded(address(router));\n        vm.prank(bot);\n        router.sweep(IMD, bot);\n        assertEq(IERC20Like(IMD).balanceOf(bot), recorded, \"payRecorded(router) pays the router; sweep takes it\");\n\n        assertEq(recorded, 0, \"reward recorded to the shared router instead of the buyer\");\n        assertEq(hook.owedTotal(), 0, \"an obligation nobody entitled can claim was recorded\");\n    }\n}","reproduction":"State: launch pool seeded token-only (EHS), IMD a plain ERC-20 at 0xd34a...63b7. Alice holds 100 IMD and approves a shared router that passes empty hookData (the test's SharedRouter mirrors the Universal Router's SETTLE_ALL/TAKE_ALL/SWEEP). Alice calls router.swap(key, SwapParams(zeroForOne = IMD->EHS, amountSpecified = -100e18, no price limit), alice, \"\"). Expected: either pending[alice] = 0.980392156862745098e18 or nothing recorded. Actual: pending[alice] = 0, pending[router] = 980392156862745098 (0.98 IMD), owedTotal = 0.98 IMD. Then any address calls hook.payRecorded(router) -> 0.98 IMD transferred to the router; router.sweep(IMD, bot) -> bot holds 980392156862745098 wei of IMD. The buyer receives nothing. Run `forge test --match-path test/scratch/RewardToRouterProof.t.sol` (fails on current code with 'reward recorded to the shared router instead of the buyer: 980392156862745098 != 0').","severity":"medium","snippet":"            address beneficiary = sender;\n            uint256 word;\n            if (data.length == 32) {\n                assembly (\"memory-safe\") { word := calldataload(data.offset) }\n                if (word != 0 && word <= type(uint160).max) beneficiary = address(uint160(word));\n            }","title":"Buy rewards fall back to the calling router when hookData is absent; payRecorded(router) plus the router's public sweep hands the buyer's reward to anyone"},{"citation":"resolved","description":"When IMD is the specified currency (exact-input buy or exact-output sell), beforeSwap reserves ceil(amount/51) or ceil(amount/49) as a positive specified hook delta and afterSwap returns the unused part as a PoolManager ERC-6909 claim minted to `sender`. The swapper's transient delta is debited the whole reserve regardless of fill, so after a partial fill the router must pay IMD it never traded and is compensated only in claims. The Universal Router and most aggregator adapters have no action that burns or redeems claims: the refund is permanently stranded on the router, and the user receives the fill minus the entire reserve instead of the fill minus 2%. If the fill is smaller than the reserve (exact-output sell asking for more than ~50x the IMD the pool holds), the IMD delta is negative and a TAKE_ALL-style router reverts with DeltaNotPositive. Exact-output sells that outrun the pool's IMD are realistic early in a token-only-seeded launch, where the pool's IMD is only what buyers have paid in. The mechanics explicitly accept the claim refund and the README warns routers, so this is reported at low: it is a broken 'never pays more than the fee rate on what filled' guarantee for the common router class rather than a hook accounting bug. Full fills leave 0 or 1 wei of claim dust per swap on the router (checked: ceil rounding leaves exactly 0 or 1 wei).","line":101,"path":"src/EHSHook.sol","reproduction":"State: pool seeded token-only; one buyer paid 100 IMD so the PoolManager holds exactly 100e18 IMD. A seller with EHS calls a Universal-Router-shaped router (settles input from payer, takes output to recipient, reverts if the output delta is negative) with SwapParams(zeroForOne = EHS->IMD, amountSpecified = +1000e18 exact output, no price limit) and hookData abi.encode(seller). beforeSwap reserves ceil(1000e18/49) = 20408163265306122449. The pool delivers 96817609245694356873 (all it can). fee = 1936352184913887137; refund = 18471888755502235312 minted as claims to the router. Expected (per the brief): seller receives fill minus 2% = 94881257060780469736 IMD. Actual: seller receives 76405562224889955980 IMD in tokens; 18471888755502200881 IMD sits as ERC-6909 claims on the router with no code path to redeem them (observed with forge: 'IMD claims stranded on router: 18471888755502200881'). Second input from the same state: amountSpecified = +100000e18 -> reserve = 2040816326530612244898, fill ~96.8e18, swapper IMD delta = 96.8e18 - 2040.8e18 < 0; the router's TAKE_ALL reverts DeltaNotPositive() and the sell cannot execute through that router at all. Demonstration test (passes, as a demonstration): test/scratch/StrandedRefundProof.t.sol.","severity":"low","snippet":"            uint256 refund = _reserve(params.amountSpecified) - fee;\n            if (refund != 0) {\n                poolManager.mint(sender, id, refund);\n                emit FeeRefunded(sender, refund);","title":"IMD-specified partial fills leave the unused reserve as ERC-6909 claims on the calling router; routers that cannot burn claims strand them, and a fill under ~2% of the request flips the swapper's IMD "},{"citation":"resolved","description":"The only outflow from accruedBalance is payRecorded, which pays recorded buyer rewards. Every sell's 2% fee and the half of every buy fee that exceeds the 1% reward are credited to accruedBalance with no beneficiary, no recipient and no owner. The brief says the fee 'accrues in the hook', so this matches the mechanics as written; it is recorded so the author confirms it is intended that at least half of all hook fees are locked forever in the PoolManager under the hook's claims.","line":108,"path":"src/EHSHook.sol","reproduction":"After one exact-input buy of 102 IMD: fee = 2e18 credited, reward = 1e18 recorded; accruedBalance - owedTotal = 1e18 and no function can ever move that 1e18 out. After one sell netting 100 IMD: fee = 2e18 credited, owedTotal unchanged; the full 2e18 is unreachable. Expected by the brief: accrues in the hook (as implemented). Flagged only so the lock-up is a deliberate choice.","severity":"info","snippet":"            poolManager.mint(address(this), id, fee);\n            accruedBalance += fee;","title":"Hook fee surplus (all sell fees plus half of every buy fee) accrues as claims the hook can never release"}],"hash":"d56133785dab5e452277a4b658ce32b0bf33d28ae309b32050d1744fa4435804","nodeId":"1ae0ac4b-1c69-4301-a883-eff8b6d8e3a4","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds three findings plus a 13-row coverage record. No tracked file was changed; the scratch tests live under the gitignored `test/scratch/` directory.\n\n**What I found**\n\n- **Medium, reward attribution to routers.** When hookData is not a single 32-byte address, afterSwap records the 1% buyer reward to the PoolManager caller. Through a shared router that passes no hookData, the reward lands on the router. Anyone can then call payRecorded(router) and use the Universal Router's permissionless sweep to take it. Reproduced with a router shaped like the Universal Router: a 100 IMD buy credited the router 0.98 IMD and a bot swept it. A proof test under `test/scratch/RewardToRouterProof.t.sol` fails on the current code.\n- **Low, stranded refund claims on partial fills.** When IMD is the specified currency, the unused reserve is refunded as ERC-6909 claims to the router. With a token-only-seeded pool holding 100 IMD, an exact-output sell asking for 1,000 IMD paid the seller about 76.4 IMD in tokens and left about 18.5 IMD as claims the router cannot redeem. Asking for 100,000 IMD flips the swapper's IMD delta negative, and a TAKE_ALL-style router reverts. The brief sanctions the claim refund and the README warns routers, so this is graded low.\n- **Info, locked fee surplus.** All sell fees and half of each buy fee accrue with no recipient, so they are unrecoverable. This matches the brief's wording and is flagged only for confirmation.\n\n**What holds**\n\nThe delta accounting nets the hook to zero on every path, the reserve never exceeds the specified amount, refund never underflows, payRecorded is reentrancy-safe with effects before the unlock, the pool binding in beforeInitialize is exact, permissions match address bits 0x20cc, and the manifest is schema-correct. The Slither and Aderyn reentrancy leads are benign. The existing suite of 91 tests passes.\n\n**Not reached**\n\nThe mainnet fork swaps could not be re-executed here because no RPC is available. The fork test skips cleanly in the default run.","treeHash":null,"usage":{"cachedInputTokens":2194546,"inputTokens":546,"model":"claude-fable-5-1","outputTokens":46960,"runtime":"claude","turns":30,"wallClockMs":692325}},{"artifacts":[],"attempt":1,"bundleHash":"9c3119997d62ccd7ffd172a37d7115b3903b7f559c495d222208d6d92ee7173a","device":"e66f9bf5eca15ea2","findings":[],"hash":"d7db6cc6fa62cef05895058956ee9eb5d15037cbafa88c15f09bd66155cc3972","nodeId":"faa7fd33-68b1-43f6-9bfe-db60d9917fee","outcome":"completed","summary":"Implemented the multilingual website, `dist/` export, EHS token, EHSHook and launch manifest.\n\nValidated: 42 local Foundry tests, 2 mainnet-fork tests, frontend build/typecheck, and Chromium/WebKit interactions. Submission estimate: **3.96 MB**, below 8 MiB.\n\nOfficial social URLs remain configurable. Setup, integration requirements and review results are documented in [README.md](/root/.identitymd/work/3de72123-ec64-4827-8476-6acf09b3cceb/faa7fd33-68b1-43f6-9bfe-db60d9917fee/README.md).","treeHash":"ad759341554d8f7ebf2ebbb2381cdeb0b24b52f7","usage":{"cachedInputTokens":10703488,"inputTokens":411479,"model":"gpt-6-astra","outputTokens":122568,"runtime":"codex","turns":13,"wallClockMs":4739558}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"fc0a1efa39657191","findings":[],"hash":"f2582d8e76b7843f13c2422178e303eb4497700b84f5523b4e798076578030b0","nodeId":"8cbe45f3-1c41-4e28-9498-22289d8f7208","outcome":"failed","summary":"wrote outside the task's allowed paths: .imd-tmp/codex-bwrap-synthetic-mount-targets-1009/lock","treeHash":null,"usage":{"cachedInputTokens":1005440,"inputTokens":71627,"model":"gpt-6-astra","outputTokens":5683,"runtime":"codex","turns":5,"wallClockMs":307546}}],"verification":[{"checks":[{"durationMs":61198,"exitCode":0,"name":"build","output":"Compiling 74 files with Solc 0.8.26\nSolc 0.8.26 finished in 61.09s\nCompiler run successful!\n","passed":true},{"durationMs":4689,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/EHSHook.t.sol:EHSMainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 833.48µs (0.00ns CPU time)\n\nRan 1 test for test/EHSHook.t.sol:FreshManagerTest\n[PASS] test_firstBuyTokenOnlyLiquidityNoIMDBalance() (gas: 6842756)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.05ms (2.71ms CPU time)\n\nRan 18 tests for test/EHSHookEdge.t.sol:EHSHookEdgeTest\n[PASS] testFuzz_reserveIsCappedAndNeverBelowTheFeeOnAnyFill(int256) (runs: 2000, μ: 26394, ~: 26430)\n[PASS] test_afterSwapWithNothingFilledIsANoOpForTheUnspecifiedSide() (gas: 52297)\n[PASS] test_beforeSwapReservesOnlyWhenIMDIsSpecified() (gas: 125652)\n[PASS] test_claimWithNothingPendingIsANoOp() (gas: 108226)\n[PASS] test_constructorRejectsAnAddressWithoutTheRightBits() (gas: 10369)\n[PASS] test_constructorRejectsIMDAsTheLaunchToken() (gas: 6319)\n[PASS] test_constructorRejectsMissingManagerOrToken() (gas: 6328)\n[PASS] test_feeRefundedEventNamesTheSwapper() (gas: 320247)\n[PASS] test_hookDataOfTheWrongLengthFallsBackToTheSwapper() (gas: 1064644)\n[PASS] test_hookRunsOnlyOneLegitimatePoolPerManager() (gas: 79351)\n[PASS] test_hugeExactOutputBuyWithPriceLimitTakesFeeOnIMD() (gas: 407153)\n[PASS] test_immutablesAndConstantsMatchTheBrief() (gas: 72058)\n[PASS] test_otherLPsAndDonationsAreUntouchedByTheHook() (gas: 1992867)\n[PASS] test_paymentNeverTouchesOtherSwappersRefundClaims() (gas: 553837)\n[PASS] test_rewardIsAtMostHalfOfTheSameBuysFee() (gas: 1630999)\n[PASS] test_rewardsRemainPayableAfterLiquidityLeavesThePool() (gas: 1272808)\n[PASS] test_sellsNeverRecordRewardsForAnyBeneficiary() (gas: 695021)\n[PASS] test_swapEventsReportFeeRefundAndReward() (gas: 847623)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 82.62ms (81.61ms CPU time)\n\nRan 28 tests for test/EHSHook.t.sol:EHSHookReverseOrderTest\n[PASS] testFuzz_allDirectionsAndPriceLimitedFills(uint96,bool,bool,uint16) (runs: 256, μ: 390081, ~: 379313)\nLogs:\n  Bound result 22\n  Bound result 193\n\n[PASS] testFuzz_sequenceSolvency(uint256) (runs: 256, μ: 3384322, ~: 3391845)\n[PASS] testFuzz_smallSwapRounding(uint8,bool,bool) (runs: 256, μ: 336350, ~: 338848)\n[PASS] test_belowThresholdAndMalformedContextDoNotRevert() (gas: 1358931)\n[PASS] test_blockedBeneficiaryDoesNotBlockOtherBuyers() (gas: 1490945)\n[PASS] test_buyExactInput() (gas: 458409)\n[PASS] test_buyExactOutput() (gas: 426773)\n[PASS] test_claimAndPermissionlessPaymentCannotRedirect() (gas: 1034989)\n[PASS] test_exactInputPartialFillRefund() (gas: 505925)\n[PASS] test_exactInputSellPartialFill() (gas: 348046)\n[PASS] test_exactOutputBuyPartialFill() (gas: 399755)\n[PASS] test_exactOutputPartialFillRefund() (gas: 451748)\n[PASS] test_exactTenIMDThreshold() (gas: 412930)\n[PASS] test_extremeSpecifiedAmountRejectsOnlyUnrepresentableFee() (gas: 449873)\n[PASS] test_factoryCanCollectWithZeroLiquidityChange() (gas: 543872)\n[PASS] test_multipleBeneficiariesRetainIndependentBacking() (gas: 1253722)\n[PASS] test_noRuntimeEscapeHatches() (gas: 1138470)\n[PASS] test_oneWeiBelowThresholdDoesNotEarnReward() (gas: 393784)\n[PASS] test_partialFillBelowThresholdDoesNotEarnReward() (gas: 395841)\n[PASS] test_paymentFailurePreservesDebtAndDoesNotBlockSwaps() (gas: 1050621)\n[PASS] test_permissionsAccessAndSizes() (gas: 152816)\n[PASS] test_poolBindingRejectsEveryMismatchedField() (gas: 99896)\n[PASS] test_refundBurnAvoidsPrefundingHugeInputReservation() (gas: 890699)\n[PASS] test_refundBurnAvoidsPrefundingHugeOutputReservation() (gas: 921620)\n[PASS] test_sellExactInput() (gas: 373596)\n[PASS] test_sellExactOutput() (gas: 404847)\n[PASS] test_wrongPoolCannotInitialize() (gas: 44291)\n[PASS] test_zeroFillRefundsEntireReserve() (gas: 1131294)\nSuite result: ok. 28 passed; 0 failed; 0 skipped; finished in 414.04ms (450.53ms CPU time)\n\nRan 12 tests for test/EHSEdge.t.sol:EHSEdgeTest\n[PASS] testFuzz_randomTransfersNeverCreateOrDestroySupply(uint256) (runs: 1000, μ: 705233, ~: 711118)\nLogs:\n  Bound result 0\n  Bound result 413405549779671790225904275\n  Bound result 517412373917101085226619038\n  Bound result 461169200530404480528825537\n  Bound result 60793132164320879463906947\n  Bound result 287761734288030279116614889\n  Bound result 120148981295841804381590402\n  Bound result 2990659754642944594621835\n  Bound result 2490467368566188972832024\n  Bound result 7832002656555503044806\n\n[PASS] testFuzz_transferFromConservesSupplyAndAllowance(uint256,uint256,address) (runs: 1000, μ: 129859, ~: 152800)\nLogs:\n  Bound result 873522380687810701018022763484207019915259049446442590526193020098295843837\n  Bound result 136912525759997479092395552\n\n[PASS] test_allowanceDoesNotCoverAnEmptyBalance() (gas: 90657)\n[PASS] test_approveOverwritesAndEmits() (gas: 132109)\n[PASS] test_finiteAllowanceOneWeiShortReverts() (gas: 90852)\n[PASS] test_infiniteAllowanceIsNotDecremented() (gas: 130682)\n[PASS] test_metadataAndConstructorEmission() (gas: 41843)\n[PASS] test_noOwnerMintBurnOrPauseSurface() (gas: 198564)\n[PASS] test_selfTransferOfEntireBalanceKeepsIt() (gas: 41065)\n[PASS] test_transferFromToZeroAddressReverts() (gas: 85213)\n[PASS] test_transferMoreThanBalanceReverts() (gas: 66752)\n[PASS] test_zeroValueTransfersSucceedAndEmit() (gas: 71715)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 451.39ms (888.84ms CPU time)\n\nRan 3 tests for test/EHS.t.sol:EHSTest\n[PASS] testFuzz_transferConservesSupply(uint256) (runs: 1000, μ: 111239, ~: 111469)\nLogs:\n  Bound result 178\n\n[PASS] test_allowanceLimitsAndZeroRecipient() (gas: 170223)\n[PASS] test_fullSupplyAndNoMintPath() (gas: 47880)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 451.58ms (449.58ms CPU time)\n\nRan 28 tests for test/EHSHook.t.sol:EHSHookTest\n[PASS] testFuzz_allDirectionsAndPriceLimitedFills(uint96,bool,bool,uint16) (runs: 1000, μ: 391576, ~: 396045)\nLogs:\n  Bound result 2721\n  Bound result 95\n\n[PASS] testFuzz_sequenceSolvency(uint256) (runs: 1000, μ: 3378799, ~: 3379879)\n[PASS] testFuzz_smallSwapRounding(uint8,bool,bool) (runs: 1000, μ: 335269, ~: 337178)\n[PASS] test_belowThresholdAndMalformedContextDoNotRevert() (gas: 1353184)\n[PASS] test_blockedBeneficiaryDoesNotBlockOtherBuyers() (gas: 1486578)\n[PASS] test_buyExactInput() (gas: 452990)\n[PASS] test_buyExactOutput() (gas: 422670)\n[PASS] test_claimAndPermissionlessPaymentCannotRedirect() (gas: 1030873)\n[PASS] test_exactInputPartialFillRefund() (gas: 500787)\n[PASS] test_exactInputSellPartialFill() (gas: 353250)\n[PASS] test_exactOutputBuyPartialFill() (gas: 394808)\n[PASS] test_exactOutputPartialFillRefund() (gas: 456761)\n[PASS] test_exactTenIMDThreshold() (gas: 407238)\n[PASS] test_extremeSpecifiedAmountRejectsOnlyUnrepresentableFee() (gas: 444689)\n[PASS] test_factoryCanCollectWithZeroLiquidityChange() (gas: 538453)\n[PASS] test_multipleBeneficiariesRetainIndependentBacking() (gas: 1249394)\n[PASS] test_noRuntimeEscapeHatches() (gas: 1058748)\n[PASS] test_oneWeiBelowThresholdDoesNotEarnReward() (gas: 388092)\n[PASS] test_partialFillBelowThresholdDoesNotEarnReward() (gas: 390657)\n[PASS] test_paymentFailurePreservesDebtAndDoesNotBlockSwaps() (gas: 1046293)\n[PASS] test_permissionsAccessAndSizes() (gas: 152804)\n[PASS] test_poolBindingRejectsEveryMismatchedField() (gas: 99851)\n[PASS] test_refundBurnAvoidsPrefundingHugeInputReservation() (gas: 885788)\n[PASS] test_refundBurnAvoidsPrefundingHugeOutputReservation() (gas: 921761)\n[PASS] test_sellExactInput() (gas: 379081)\n[PASS] test_sellExactOutput() (gas: 409016)\n[PASS] test_wrongPoolCannotInitialize() (gas: 44282)\n[PASS] test_zeroFillRefundsEntireReserve() (gas: 1131456)\nSuite result: ok. 28 passed; 0 failed; 0 skipped; finished in 462.84ms (1.07s CPU time)\n\nRan 1 test for test/EHSHook.invariant.t.sol:EHSHookInvariantTest\n[PASS]\nEHSHookInvariantTest invariants:\n[PASS] invariant_beneficiariesReceiveExactlyTheirRecords\n[PASS] invariant_callSummary\n[PASS] invariant_claimsBackAccruedBalance\n[PASS] invariant_feeConservation\n[PASS] invariant_hookHoldsNoTokensAndTaxesNoEHS\n[PASS] invariant_managerBacksEveryClaim\n[PASS] invariant_owedTotalIsSumOfPending\n[PASS] invariant_poolTermsAreFixed\n EHSHookInvariantTest invariants (runs: 256, calls: 10240, reverts: 0)\n\n╭----------------+--------------------+-------+---------+----------╮\n| Contract       | Selector           | Calls | Reverts | Discards |\n+==================================================================+\n| EHSHookHandler | claimRewards       | 995   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | collectFees        | 886   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | donateClaimsToHook | 955   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | donateToPool       | 890   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | modifyLiquidity    | 952   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | payRecorded        | 938   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | redeemRefunds      | 852   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | strangerClaims     | 945   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | swap               | 2827  | 0       | 0        |\n╰----------------+--------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 14427\n  Bound result 412\n  Bound result 4728\n  Bound result 1899\n  Bound result 11454\n  Bound result 100\n  Bound result 10000000000000000000\n  Bound result 10000000000000000000000\n  Bound result 1696\n  Bound result 9999999000000000002632\n  Bound result 60\n  Bound result 50\n  Bound result 1000000000000000000000\n  Bound result 242\n  Bound result 5101\n  Bound result 8\n  Bound result 64\n  Bound result 0\n  Bound result 1881\n  Bound result 479\n  Bound result 200\n  Bound result 618163376055794095043\n  Bound result 128\n  Bound result 494\n  Bound result 2614225886796278\n  Bound result 898403851775807900504\n  Bound result 696894918322069798\n  Bound result 196078431372549020122\n  Bound result 10\n  Bound result 9409008634818719330209\n  Bound result 733982281\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.59s (4.56s CPU time)\n\nRan 8 test suites in 4.59s (6.46s CPU time): 91 tests passed, 0 failed, 1 skipped (92 total tests)\n","passed":true},{"durationMs":113,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"EHS.approve(address,uint256)\",\"EHS.transfer(address,uint256)\",\"EHS.transferFrom(address,address,uint256)\",\"EHSHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"EHSHook.claimRewards()\",\"EHSHook.payRecorded(address)\",\"EHSHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":13,\"DESIGN.md\":105,\"README.md\":112,\"artifacts/brief-coverage.md\":33,\"artifacts/checks/chromium.json\":74,\"artifacts/checks/foundry-build.txt\":200,\"artifacts/checks/foundry-fork-archive-unavailable.txt\":17,\"artifacts/checks/foundry-fork.txt\":8,\"artifacts/checks/foundry-offline.txt\":81,\"artifacts/checks/taste-preview.json\":116,\"artifacts/checks/taste-static.json\":116,\"artifacts/checks/webkit.json\":70,\"artifacts/dependencies.md\":18,\"artifacts/licenses/better-interface.txt\":220,\"artifacts/licenses/eth-testing.txt\":30,\"artifacts/licenses/uniswap-v4-security.txt\":21,\"artifacts/screenshots/desktop-dark.jpg\":434,\"artifacts/screenshots/mobile-dark.jpg\":355,\"artifacts/security-review.md\":63,\"artifacts/submission-inventory.json\":894,\"artifacts/validation.md\":88,\"dist/404.html\":3,\"dist/app.js\":425,\"dist/assets/beat-1.svg\":14,\"dist/assets/beat-2.svg\":14,\"dist/assets/beat-3.svg\":14,\"dist/assets/hand.svg\":14,\"dist/assets/ibmplexmono-LICENSE.txt\":93,\"dist/assets/logo.jpg\":1307,\"dist/assets/meme-1.png\":636,\"dist/assets/meme-1.svg\":14,\"dist/assets/meme-2.png\":211,\"dist/assets/meme-2.svg\":14,\"dist/assets/meme-3.png\":347,\"dist/assets/meme-3.svg\":14,\"dist/assets/meme-4.png\":300,\"dist/assets/meme-4.svg\":14,\"dist/assets/meme-5.png\":375,\"dist/assets/meme-5.svg\":14,\"dist/assets/meme-6.png\":497,\"dist/assets/meme-6.svg\":14,\"dist/assets/plex-regular.woff2\":58,\"dist/assets/plex-semibold.woff2\":78,\"dist/assets/unifraktur.woff2\":70,\"dist/assets/unifrakturcook-LICENSE.txt\":94,\"dist/config.js\":3,\"dist/data.js\":39,\"dist/error.js\":70,\"dist/index.html\":144,\"dist/styles.css\":1482,\"dist/swarm.js\":237,\"dist/theme.js\":15,\"dist/translations.js\":392,\"foundry.toml\":13,\"launch.json\":16,\"package-lock.json\":83,\"package.json\":16,\"scripts/art.py\":61,\"scripts/build.mjs\":29,\"scripts/check-submission.mjs\":56,\"scripts/preview.mjs\":52,\"scripts/render-art.mjs\":29,\"scripts/validate-browser.mjs\":492,\"site/404.html\":3,\"site/app.js\":425,\"site/assets/beat-1.svg\":14,\"site/assets/beat-2.svg\":14,\"site/assets/beat-3.svg\":14,\"site/assets/hand.svg\":14,\"site/assets/ibmplexmono-LICENSE.txt\":93,\"site/assets/logo.jpg\":1307,\"site/assets/meme-1.png\":636,\"site/assets/meme-1.svg\":14,\"site/assets/meme-2.png\":211,\"site/assets/meme-2.svg\":14,\"site/assets/meme-3.png\":347,\"site/assets/meme-3.svg\":14,\"site/assets/meme-4.png\":300,\"site/assets/meme-4.svg\":14,\"site/assets/meme-5.png\":375,\"site/assets/meme-5.svg\":14,\"site/assets/meme-6.png\":497,\"site/assets/meme-6.svg\":14,\"site/assets/plex-regular.woff2\":58,\"site/assets/plex-semibold.woff2\":78,\"site/assets/unifraktur.woff2\":70,\"site/assets/unifrakturcook-LICENSE.txt\":94,\"site/config.js\":3,\"site/data.js\":39,\"site/error.js\":70,\"site/index.html\":144,\"site/styles.css\":1482,\"site/swarm.js\":237,\"site/theme.js\":15,\"site/translations.js\":392,\"src/EHS.sol\":53,\"src/EHSHook.sol\":172,\"test/EHS.t.sol\":45,\"test/EHSEdge.t.sol\":142,\"test/EHSHook.invariant.t.sol\":426,\"test/EHSHook.t.sol\":593,\"test/EHSHookEdge.t.sol\":343,\"test/mocks/MockIMD.sol\":39,\"test/site.test.mjs\":77,\"tsconfig.json\":8},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"3cd370746fbde1fc8ce5663733298da10fa5eec5da5105f9ff795d3c20851423","verifiedTreeHash":"6580d2bcd07e49e5d72725d1d00401d1da5c0fb4","verifierVersion":"0.1.0+8fbf00a5"},{"checks":[{"durationMs":48590,"exitCode":0,"name":"build","output":"Compiling 71 files with Solc 0.8.26\nSolc 0.8.26 finished in 48.49s\nCompiler run successful!\n","passed":true},{"durationMs":565,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/EHSHook.t.sol:EHSMainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 404.56µs (0.00ns CPU time)\n\nRan 1 test for test/EHSHook.t.sol:FreshManagerTest\n[PASS] test_firstBuyTokenOnlyLiquidityNoIMDBalance() (gas: 6842756)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.70ms (3.39ms CPU time)\n\nRan 28 tests for test/EHSHook.t.sol:EHSHookReverseOrderTest\n[PASS] testFuzz_allDirectionsAndPriceLimitedFills(uint96,bool,bool,uint16) (runs: 256, μ: 390086, ~: 379319)\nLogs:\n  Bound result 22\n  Bound result 193\n\n[PASS] testFuzz_sequenceSolvency(uint256) (runs: 256, μ: 3383790, ~: 3389751)\n[PASS] testFuzz_smallSwapRounding(uint8,bool,bool) (runs: 256, μ: 335619, ~: 338848)\n[PASS] test_belowThresholdAndMalformedContextDoNotRevert() (gas: 1358931)\n[PASS] test_blockedBeneficiaryDoesNotBlockOtherBuyers() (gas: 1490945)\n[PASS] test_buyExactInput() (gas: 458409)\n[PASS] test_buyExactOutput() (gas: 426773)\n[PASS] test_claimAndPermissionlessPaymentCannotRedirect() (gas: 1034989)\n[PASS] test_exactInputPartialFillRefund() (gas: 505925)\n[PASS] test_exactInputSellPartialFill() (gas: 348046)\n[PASS] test_exactOutputBuyPartialFill() (gas: 399755)\n[PASS] test_exactOutputPartialFillRefund() (gas: 451748)\n[PASS] test_exactTenIMDThreshold() (gas: 412930)\n[PASS] test_extremeSpecifiedAmountRejectsOnlyUnrepresentableFee() (gas: 449873)\n[PASS] test_factoryCanCollectWithZeroLiquidityChange() (gas: 543872)\n[PASS] test_multipleBeneficiariesRetainIndependentBacking() (gas: 1253722)\n[PASS] test_noRuntimeEscapeHatches() (gas: 1138470)\n[PASS] test_oneWeiBelowThresholdDoesNotEarnReward() (gas: 393784)\n[PASS] test_partialFillBelowThresholdDoesNotEarnReward() (gas: 395841)\n[PASS] test_paymentFailurePreservesDebtAndDoesNotBlockSwaps() (gas: 1050621)\n[PASS] test_permissionsAccessAndSizes() (gas: 152816)\n[PASS] test_poolBindingRejectsEveryMismatchedField() (gas: 99896)\n[PASS] test_refundBurnAvoidsPrefundingHugeInputReservation() (gas: 890699)\n[PASS] test_refundBurnAvoidsPrefundingHugeOutputReservation() (gas: 921620)\n[PASS] test_sellExactInput() (gas: 373596)\n[PASS] test_sellExactOutput() (gas: 404847)\n[PASS] test_wrongPoolCannotInitialize() (gas: 44291)\n[PASS] test_zeroFillRefundsEntireReserve() (gas: 1131294)\nSuite result: ok. 28 passed; 0 failed; 0 skipped; finished in 356.98ms (531.23ms CPU time)\n\nRan 3 tests for test/EHS.t.sol:EHSTest\n[PASS] testFuzz_transferConservesSupply(uint256) (runs: 1000, μ: 111243, ~: 111469)\nLogs:\n  Bound result 5730\n\n[PASS] test_allowanceLimitsAndZeroRecipient() (gas: 170223)\n[PASS] test_fullSupplyAndNoMintPath() (gas: 47880)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 427.24ms (426.92ms CPU time)\n\nRan 28 tests for test/EHSHook.t.sol:EHSHookTest\n[PASS] testFuzz_allDirectionsAndPriceLimitedFills(uint96,bool,bool,uint16) (runs: 1000, μ: 391589, ~: 396045)\nLogs:\n  Bound result 62\n  Bound result 95\n\n[PASS] testFuzz_sequenceSolvency(uint256) (runs: 1000, μ: 3377047, ~: 3378545)\n[PASS] testFuzz_smallSwapRounding(uint8,bool,bool) (runs: 1000, μ: 335276, ~: 337178)\n[PASS] test_belowThresholdAndMalformedContextDoNotRevert() (gas: 1353184)\n[PASS] test_blockedBeneficiaryDoesNotBlockOtherBuyers() (gas: 1486578)\n[PASS] test_buyExactInput() (gas: 452990)\n[PASS] test_buyExactOutput() (gas: 422670)\n[PASS] test_claimAndPermissionlessPaymentCannotRedirect() (gas: 1030873)\n[PASS] test_exactInputPartialFillRefund() (gas: 500787)\n[PASS] test_exactInputSellPartialFill() (gas: 353250)\n[PASS] test_exactOutputBuyPartialFill() (gas: 394808)\n[PASS] test_exactOutputPartialFillRefund() (gas: 456761)\n[PASS] test_exactTenIMDThreshold() (gas: 407238)\n[PASS] test_extremeSpecifiedAmountRejectsOnlyUnrepresentableFee() (gas: 444689)\n[PASS] test_factoryCanCollectWithZeroLiquidityChange() (gas: 538453)\n[PASS] test_multipleBeneficiariesRetainIndependentBacking() (gas: 1249394)\n[PASS] test_noRuntimeEscapeHatches() (gas: 1058748)\n[PASS] test_oneWeiBelowThresholdDoesNotEarnReward() (gas: 388092)\n[PASS] test_partialFillBelowThresholdDoesNotEarnReward() (gas: 390657)\n[PASS] test_paymentFailurePreservesDebtAndDoesNotBlockSwaps() (gas: 1046293)\n[PASS] test_permissionsAccessAndSizes() (gas: 152804)\n[PASS] test_poolBindingRejectsEveryMismatchedField() (gas: 99851)\n[PASS] test_refundBurnAvoidsPrefundingHugeInputReservation() (gas: 885788)\n[PASS] test_refundBurnAvoidsPrefundingHugeOutputReservation() (gas: 921761)\n[PASS] test_sellExactInput() (gas: 379081)\n[PASS] test_sellExactOutput() (gas: 409016)\n[PASS] test_wrongPoolCannotInitialize() (gas: 44282)\n[PASS] test_zeroFillRefundsEntireReserve() (gas: 1131456)\nSuite result: ok. 28 passed; 0 failed; 0 skipped; finished in 462.47ms (1.16s CPU time)\n\nRan 5 test suites in 464.06ms (1.25s CPU time): 60 tests passed, 0 failed, 1 skipped (61 total tests)\n","passed":true},{"durationMs":144,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"EHS.approve(address,uint256)\",\"EHS.transfer(address,uint256)\",\"EHS.transferFrom(address,address,uint256)\",\"EHSHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"EHSHook.claimRewards()\",\"EHSHook.payRecorded(address)\",\"EHSHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":13,\"DESIGN.md\":105,\"README.md\":112,\"artifacts/brief-coverage.md\":33,\"artifacts/checks/chromium.json\":74,\"artifacts/checks/foundry-build.txt\":200,\"artifacts/checks/foundry-fork-archive-unavailable.txt\":17,\"artifacts/checks/foundry-fork.txt\":8,\"artifacts/checks/foundry-offline.txt\":81,\"artifacts/checks/taste-preview.json\":116,\"artifacts/checks/taste-static.json\":116,\"artifacts/checks/webkit.json\":70,\"artifacts/dependencies.md\":18,\"artifacts/licenses/better-interface.txt\":220,\"artifacts/licenses/eth-testing.txt\":30,\"artifacts/licenses/uniswap-v4-security.txt\":21,\"artifacts/screenshots/desktop-dark.jpg\":434,\"artifacts/screenshots/mobile-dark.jpg\":355,\"artifacts/security-review.md\":63,\"artifacts/submission-inventory.json\":894,\"artifacts/validation.md\":88,\"dist/404.html\":3,\"dist/app.js\":425,\"dist/assets/beat-1.svg\":14,\"dist/assets/beat-2.svg\":14,\"dist/assets/beat-3.svg\":14,\"dist/assets/hand.svg\":14,\"dist/assets/ibmplexmono-LICENSE.txt\":93,\"dist/assets/logo.jpg\":1307,\"dist/assets/meme-1.png\":636,\"dist/assets/meme-1.svg\":14,\"dist/assets/meme-2.png\":211,\"dist/assets/meme-2.svg\":14,\"dist/assets/meme-3.png\":347,\"dist/assets/meme-3.svg\":14,\"dist/assets/meme-4.png\":300,\"dist/assets/meme-4.svg\":14,\"dist/assets/meme-5.png\":375,\"dist/assets/meme-5.svg\":14,\"dist/assets/meme-6.png\":497,\"dist/assets/meme-6.svg\":14,\"dist/assets/plex-regular.woff2\":58,\"dist/assets/plex-semibold.woff2\":78,\"dist/assets/unifraktur.woff2\":70,\"dist/assets/unifrakturcook-LICENSE.txt\":94,\"dist/config.js\":3,\"dist/data.js\":39,\"dist/error.js\":70,\"dist/index.html\":144,\"dist/styles.css\":1482,\"dist/swarm.js\":237,\"dist/theme.js\":15,\"dist/translations.js\":392,\"foundry.toml\":13,\"launch.json\":16,\"package-lock.json\":83,\"package.json\":16,\"scripts/art.py\":61,\"scripts/build.mjs\":29,\"scripts/check-submission.mjs\":56,\"scripts/preview.mjs\":52,\"scripts/render-art.mjs\":29,\"scripts/validate-browser.mjs\":492,\"site/404.html\":3,\"site/app.js\":425,\"site/assets/beat-1.svg\":14,\"site/assets/beat-2.svg\":14,\"site/assets/beat-3.svg\":14,\"site/assets/hand.svg\":14,\"site/assets/ibmplexmono-LICENSE.txt\":93,\"site/assets/logo.jpg\":1307,\"site/assets/meme-1.png\":636,\"site/assets/meme-1.svg\":14,\"site/assets/meme-2.png\":211,\"site/assets/meme-2.svg\":14,\"site/assets/meme-3.png\":347,\"site/assets/meme-3.svg\":14,\"site/assets/meme-4.png\":300,\"site/assets/meme-4.svg\":14,\"site/assets/meme-5.png\":375,\"site/assets/meme-5.svg\":14,\"site/assets/meme-6.png\":497,\"site/assets/meme-6.svg\":14,\"site/assets/plex-regular.woff2\":58,\"site/assets/plex-semibold.woff2\":78,\"site/assets/unifraktur.woff2\":70,\"site/assets/unifrakturcook-LICENSE.txt\":94,\"site/config.js\":3,\"site/data.js\":39,\"site/error.js\":70,\"site/index.html\":144,\"site/styles.css\":1482,\"site/swarm.js\":237,\"site/theme.js\":15,\"site/translations.js\":392,\"src/EHS.sol\":53,\"src/EHSHook.sol\":172,\"test/EHS.t.sol\":45,\"test/EHSHook.t.sol\":593,\"test/mocks/MockIMD.sol\":39,\"test/site.test.mjs\":77,\"tsconfig.json\":8},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"74f9f8b0c8012a7aaf26ee5da867f4423d5328a312f17437f0b903a340d0d1ff","verifiedTreeHash":"47a4caaf44ddb78a613d4654313b9923057d5e99","verifierVersion":"0.1.0+8fbf00a5"},{"checks":[{"durationMs":58513,"exitCode":0,"name":"build","output":"Compiling 74 files with Solc 0.8.26\nSolc 0.8.26 finished in 58.43s\nCompiler run successful!\n","passed":true},{"durationMs":4292,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/EHSHook.t.sol:EHSMainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 435.22µs (0.00ns CPU time)\n\nRan 1 test for test/EHSHook.t.sol:FreshManagerTest\n[PASS] test_firstBuyTokenOnlyLiquidityNoIMDBalance() (gas: 27482475)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 37.00ms (36.66ms CPU time)\n\nRan 21 tests for test/EHSHookEdge.t.sol:EHSHookEdgeTest\n[PASS] testFuzz_reserveIsCappedAndNeverBelowTheFeeOnAnyFill(int256) (runs: 2000, μ: 26442, ~: 26474)\n[PASS] test_afterSwapWithNothingFilledIsANoOpForTheUnspecifiedSide() (gas: 52366)\n[PASS] test_beforeSwapReservesOnlyWhenIMDIsSpecified() (gas: 125718)\n[PASS] test_claimWithNothingPendingIsANoOp() (gas: 108270)\n[PASS] test_constructorRejectsAnAddressWithoutTheRightBits() (gas: 10391)\n[PASS] test_constructorRejectsIMDAsTheLaunchToken() (gas: 6341)\n[PASS] test_constructorRejectsMissingManagerOrToken() (gas: 6350)\n[PASS] test_feeRefundedEventNamesTheSwapper() (gas: 320337)\n[PASS] test_hookDataOfTheWrongLengthRecordsNoRewardForAnyone() (gas: 1582478)\n[PASS] test_hookRunsOnlyOneLegitimatePoolPerManager() (gas: 79373)\n[PASS] test_hugeExactOutputBuyWithPriceLimitTakesFeeOnIMD() (gas: 407302)\n[PASS] test_immutablesAndConstantsMatchTheBrief() (gas: 72124)\n[PASS] test_otherLPsAndDonationsAreUntouchedByTheHook() (gas: 1993041)\n[PASS] test_outsideClaimsPushedAtTheHookAreNeitherCountedNorPayable() (gas: 1328499)\n[PASS] test_paymentNeverTouchesOtherSwappersRefundClaims() (gas: 554011)\n[PASS] test_refundClaimBelongsToTheRouterNotItsPayer() (gas: 1108616)\n[PASS] test_rewardIsAtMostHalfOfTheSameBuysFee() (gas: 1631879)\n[PASS] test_rewardsRemainPayableAfterLiquidityLeavesThePool() (gas: 1273126)\n[PASS] test_routerThatBurnsRefundChargesPayerOnlyFillPlusFee() (gas: 346887)\n[PASS] test_sellsNeverRecordRewardsForAnyBeneficiary() (gas: 695255)\n[PASS] test_swapEventsReportFeeRefundAndReward() (gas: 847897)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 404.83ms (392.66ms CPU time)\n\nRan 29 tests for test/EHSHook.t.sol:EHSHookReverseOrderTest\n[PASS] testFuzz_allDirectionsAndPriceLimitedFills(uint96,bool,bool,uint16) (runs: 256, μ: 390141, ~: 379342)\nLogs:\n  Bound result 22\n  Bound result 193\n\n[PASS] testFuzz_sequenceSolvency(uint256) (runs: 256, μ: 3373345, ~: 3379912)\n[PASS] testFuzz_smallSwapRounding(uint8,bool,bool) (runs: 256, μ: 336674, ~: 338898)\n[PASS] test_belowThresholdAndMalformedContextDoNotRevert() (gas: 1601629)\n[PASS] test_blockedBeneficiaryDoesNotBlockOtherBuyers() (gas: 1491036)\n[PASS] test_buyExactInput() (gas: 458433)\n[PASS] test_buyExactOutput() (gas: 426794)\n[PASS] test_claimAndPermissionlessPaymentCannotRedirect() (gas: 1035056)\n[PASS] test_exactInputPartialFillRefund() (gas: 505993)\n[PASS] test_exactInputSellPartialFill() (gas: 348093)\n[PASS] test_exactOutputBuyPartialFill() (gas: 399776)\n[PASS] test_exactOutputPartialFillRefund() (gas: 451798)\n[PASS] test_exactTenIMDThreshold() (gas: 412976)\n[PASS] test_extremeSpecifiedAmountRejectsOnlyUnrepresentableFee() (gas: 449897)\n[PASS] test_factoryCanCollectWithZeroLiquidityChange() (gas: 543940)\n[PASS] test_multipleBeneficiariesRetainIndependentBacking() (gas: 1253789)\n[PASS] test_noRuntimeEscapeHatches() (gas: 1140318)\n[PASS] test_oneWeiBelowThresholdDoesNotEarnReward() (gas: 393812)\n[PASS] test_partialFillBelowThresholdDoesNotEarnReward() (gas: 395894)\n[PASS] test_paymentFailurePreservesDebtAndDoesNotBlockSwaps() (gas: 1050666)\n[PASS] test_permissionsAccessAndSizes() (gas: 152841)\n[PASS] test_poolBindingRejectsEveryMismatchedField() (gas: 99918)\n[PASS] test_refundBurnAvoidsPrefundingHugeInputReservation() (gas: 890747)\n[PASS] test_refundBurnAvoidsPrefundingHugeOutputReservation() (gas: 921694)\n[PASS] test_sellExactInput() (gas: 373621)\n[PASS] test_sellExactOutput() (gas: 404897)\n[PASS] test_sharedRouterWithEmptyHookDataEarnsNothingToSweep() (gas: 1356048)\n[PASS] test_wrongPoolCannotInitialize() (gas: 44291)\n[PASS] test_zeroFillRefundsEntireReserve() (gas: 1131418)\nSuite result: ok. 29 passed; 0 failed; 0 skipped; finished in 405.12ms (404.76ms CPU time)\n\nRan 12 tests for test/EHSEdge.t.sol:EHSEdgeTest\n[PASS] testFuzz_randomTransfersNeverCreateOrDestroySupply(uint256) (runs: 1000, μ: 705344, ~: 712234)\nLogs:\n  Bound result 930987970736849221539091881\n  Bound result 411936617255443566109636796\n  Bound result 404180670173603666920520575\n  Bound result 456869623320322389356149015\n  Bound result 429709820278477345965784491\n  Bound result 5288286520986938333465534\n  Bound result 4466583706795876795816305\n  Bound result 368953380240302496637525136\n  Bound result 46737933977424632926706489\n  Bound result 66084577852475387913892819\n\n[PASS] testFuzz_transferFromConservesSupplyAndAllowance(uint256,uint256,address) (runs: 1000, μ: 129256, ~: 152782)\nLogs:\n  Bound result 11900970720562\n  Bound result 528511541957501818438660581\n\n[PASS] test_allowanceDoesNotCoverAnEmptyBalance() (gas: 90657)\n[PASS] test_approveOverwritesAndEmits() (gas: 132109)\n[PASS] test_finiteAllowanceOneWeiShortReverts() (gas: 90852)\n[PASS] test_infiniteAllowanceIsNotDecremented() (gas: 130682)\n[PASS] test_metadataAndConstructorEmission() (gas: 41843)\n[PASS] test_noOwnerMintBurnOrPauseSurface() (gas: 198564)\n[PASS] test_selfTransferOfEntireBalanceKeepsIt() (gas: 41065)\n[PASS] test_transferFromToZeroAddressReverts() (gas: 85213)\n[PASS] test_transferMoreThanBalanceReverts() (gas: 66752)\n[PASS] test_zeroValueTransfersSucceedAndEmit() (gas: 71715)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 421.94ms (839.15ms CPU time)\n\nRan 3 tests for test/EHS.t.sol:EHSTest\n[PASS] testFuzz_transferConservesSupply(uint256) (runs: 1000, μ: 111214, ~: 111469)\nLogs:\n  Bound result 4900\n\n[PASS] test_allowanceLimitsAndZeroRecipient() (gas: 170223)\n[PASS] test_fullSupplyAndNoMintPath() (gas: 47880)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 422.07ms (422.15ms CPU time)\n\nRan 29 tests for test/EHSHook.t.sol:EHSHookTest\n[PASS] testFuzz_allDirectionsAndPriceLimitedFills(uint96,bool,bool,uint16) (runs: 1000, μ: 391648, ~: 396036)\nLogs:\n  Bound result 37138895472877412\n  Bound result 119\n\n[PASS] testFuzz_sequenceSolvency(uint256) (runs: 1000, μ: 3380257, ~: 3376022)\n[PASS] testFuzz_smallSwapRounding(uint8,bool,bool) (runs: 1000, μ: 335149, ~: 337228)\n[PASS] test_belowThresholdAndMalformedContextDoNotRevert() (gas: 1595869)\n[PASS] test_blockedBeneficiaryDoesNotBlockOtherBuyers() (gas: 1486669)\n[PASS] test_buyExactInput() (gas: 453014)\n[PASS] test_buyExactOutput() (gas: 422691)\n[PASS] test_claimAndPermissionlessPaymentCannotRedirect() (gas: 1030940)\n[PASS] test_exactInputPartialFillRefund() (gas: 500855)\n[PASS] test_exactInputSellPartialFill() (gas: 353297)\n[PASS] test_exactOutputBuyPartialFill() (gas: 394829)\n[PASS] test_exactOutputPartialFillRefund() (gas: 456811)\n[PASS] test_exactTenIMDThreshold() (gas: 407284)\n[PASS] test_extremeSpecifiedAmountRejectsOnlyUnrepresentableFee() (gas: 444713)\n[PASS] test_factoryCanCollectWithZeroLiquidityChange() (gas: 538521)\n[PASS] test_multipleBeneficiariesRetainIndependentBacking() (gas: 1249461)\n[PASS] test_noRuntimeEscapeHatches() (gas: 1060465)\n[PASS] test_oneWeiBelowThresholdDoesNotEarnReward() (gas: 388120)\n[PASS] test_partialFillBelowThresholdDoesNotEarnReward() (gas: 390710)\n[PASS] test_paymentFailurePreservesDebtAndDoesNotBlockSwaps() (gas: 1046338)\n[PASS] test_permissionsAccessAndSizes() (gas: 152829)\n[PASS] test_poolBindingRejectsEveryMismatchedField() (gas: 99873)\n[PASS] test_refundBurnAvoidsPrefundingHugeInputReservation() (gas: 885836)\n[PASS] test_refundBurnAvoidsPrefundingHugeOutputReservation() (gas: 921835)\n[PASS] test_sellExactInput() (gas: 379106)\n[PASS] test_sellExactOutput() (gas: 409066)\n[PASS] test_sharedRouterWithEmptyHookDataEarnsNothingToSweep() (gas: 1352930)\n[PASS] test_wrongPoolCannotInitialize() (gas: 44282)\n[PASS] test_zeroFillRefundsEntireReserve() (gas: 1131580)\nSuite result: ok. 29 passed; 0 failed; 0 skipped; finished in 422.04ms (1.19s CPU time)\n\nRan 1 test for test/EHSHook.invariant.t.sol:EHSHookInvariantTest\n[PASS]\nEHSHookInvariantTest invariants:\n[PASS] invariant_beneficiariesReceiveExactlyTheirRecords\n[PASS] invariant_callSummary\n[PASS] invariant_claimsBackAccruedBalance\n[PASS] invariant_feeConservation\n[PASS] invariant_hookHoldsNoTokensAndTaxesNoEHS\n[PASS] invariant_managerBacksEveryClaim\n[PASS] invariant_owedTotalIsSumOfPending\n[PASS] invariant_poolTermsAreFixed\n EHSHookInvariantTest invariants (runs: 256, calls: 10240, reverts: 0)\n\n╭----------------+--------------------+-------+---------+----------╮\n| Contract       | Selector           | Calls | Reverts | Discards |\n+==================================================================+\n| EHSHookHandler | claimRewards       | 995   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | collectFees        | 886   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | donateClaimsToHook | 955   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | donateToPool       | 890   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | modifyLiquidity    | 952   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | payRecorded        | 938   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | redeemRefunds      | 852   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | strangerClaims     | 945   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | swap               | 2827  | 0       | 0        |\n╰----------------+--------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 14427\n  Bound result 412\n  Bound result 5220\n  Bound result 8058\n  Bound result 12086\n  Bound result 1125\n  Bound result 12500\n  Bound result 10000000000000000000000\n  Bound result 1532\n  Bound result 9999999000000000008412\n  Bound result 60\n  Bound result 32\n  Bound result 99\n  Bound result 49152\n  Bound result 101\n  Bound result 8\n  Bound result 6\n  Bound result 1808\n  Bound result 2049\n  Bound result 1224\n  Bound result 200\n  Bound result 618163376055794095043\n  Bound result 128\n  Bound result 494\n  Bound result 2614225886796278\n  Bound result 898403851775807900504\n  Bound result 2795756557490418240\n  Bound result 196078431372549019607\n  Bound result 16\n  Bound result 874873417802516458141\n  Bound result 10238\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.20s (4.19s CPU time)\n\nRan 8 test suites in 4.20s (6.31s CPU time): 96 tests passed, 0 failed, 1 skipped (97 total tests)\n","passed":true},{"durationMs":86,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"EHS.approve(address,uint256)\",\"EHS.transfer(address,uint256)\",\"EHS.transferFrom(address,address,uint256)\",\"EHSHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"EHSHook.claimRewards()\",\"EHSHook.payRecorded(address)\",\"EHSHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":13,\"DESIGN.md\":105,\"README.md\":118,\"artifacts/brief-coverage.md\":33,\"artifacts/checks/chromium.json\":74,\"artifacts/checks/foundry-build.txt\":200,\"artifacts/checks/foundry-fork-archive-unavailable.txt\":17,\"artifacts/checks/foundry-fork.txt\":8,\"artifacts/checks/foundry-offline.txt\":81,\"artifacts/checks/taste-preview.json\":116,\"artifacts/checks/taste-static.json\":116,\"artifacts/checks/webkit.json\":70,\"artifacts/dependencies.md\":18,\"artifacts/licenses/better-interface.txt\":220,\"artifacts/licenses/eth-testing.txt\":30,\"artifacts/licenses/uniswap-v4-security.txt\":21,\"artifacts/screenshots/desktop-dark.jpg\":434,\"artifacts/screenshots/mobile-dark.jpg\":355,\"artifacts/security-review.md\":63,\"artifacts/submission-inventory.json\":894,\"artifacts/validation.md\":88,\"dist/404.html\":3,\"dist/app.js\":425,\"dist/assets/beat-1.svg\":14,\"dist/assets/beat-2.svg\":14,\"dist/assets/beat-3.svg\":14,\"dist/assets/hand.svg\":14,\"dist/assets/ibmplexmono-LICENSE.txt\":93,\"dist/assets/logo.jpg\":1307,\"dist/assets/meme-1.png\":636,\"dist/assets/meme-1.svg\":14,\"dist/assets/meme-2.png\":211,\"dist/assets/meme-2.svg\":14,\"dist/assets/meme-3.png\":347,\"dist/assets/meme-3.svg\":14,\"dist/assets/meme-4.png\":300,\"dist/assets/meme-4.svg\":14,\"dist/assets/meme-5.png\":375,\"dist/assets/meme-5.svg\":14,\"dist/assets/meme-6.png\":497,\"dist/assets/meme-6.svg\":14,\"dist/assets/plex-regular.woff2\":58,\"dist/assets/plex-semibold.woff2\":78,\"dist/assets/unifraktur.woff2\":70,\"dist/assets/unifrakturcook-LICENSE.txt\":94,\"dist/config.js\":3,\"dist/data.js\":39,\"dist/error.js\":70,\"dist/index.html\":144,\"dist/styles.css\":1482,\"dist/swarm.js\":237,\"dist/theme.js\":15,\"dist/translations.js\":392,\"foundry.toml\":13,\"launch.json\":30,\"package-lock.json\":83,\"package.json\":16,\"scripts/art.py\":61,\"scripts/build.mjs\":29,\"scripts/check-submission.mjs\":56,\"scripts/preview.mjs\":52,\"scripts/render-art.mjs\":29,\"scripts/validate-browser.mjs\":492,\"site/404.html\":3,\"site/app.js\":425,\"site/assets/beat-1.svg\":14,\"site/assets/beat-2.svg\":14,\"site/assets/beat-3.svg\":14,\"site/assets/hand.svg\":14,\"site/assets/ibmplexmono-LICENSE.txt\":93,\"site/assets/logo.jpg\":1307,\"site/assets/meme-1.png\":636,\"site/assets/meme-1.svg\":14,\"site/assets/meme-2.png\":211,\"site/assets/meme-2.svg\":14,\"site/assets/meme-3.png\":347,\"site/assets/meme-3.svg\":14,\"site/assets/meme-4.png\":300,\"site/assets/meme-4.svg\":14,\"site/assets/meme-5.png\":375,\"site/assets/meme-5.svg\":14,\"site/assets/meme-6.png\":497,\"site/assets/meme-6.svg\":14,\"site/assets/plex-regular.woff2\":58,\"site/assets/plex-semibold.woff2\":78,\"site/assets/unifraktur.woff2\":70,\"site/assets/unifrakturcook-LICENSE.txt\":94,\"site/config.js\":3,\"site/data.js\":39,\"site/error.js\":70,\"site/index.html\":144,\"site/styles.css\":1482,\"site/swarm.js\":237,\"site/theme.js\":15,\"site/translations.js\":392,\"src/EHS.sol\":53,\"src/EHSHook.sol\":181,\"test/EHS.t.sol\":45,\"test/EHSEdge.t.sol\":142,\"test/EHSHook.invariant.t.sol\":431,\"test/EHSHook.t.sol\":671,\"test/EHSHookEdge.t.sol\":482,\"test/mocks/MockIMD.sol\":39,\"test/site.test.mjs\":77,\"tsconfig.json\":8},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"806a9e0908a310af61d0cd87e9e6468d0aac0289350e9a76e12c72c2d6b235d6","verifiedTreeHash":"6212aca0b4530157ec151cafe3f7226d3635e6c0","verifierVersion":"0.1.0+8fbf00a5"},{"checks":[{"durationMs":61871,"exitCode":0,"name":"build","output":"Compiling 74 files with Solc 0.8.26\nSolc 0.8.26 finished in 61.77s\nCompiler run successful!\n","passed":true},{"durationMs":4488,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/EHSHook.t.sol:EHSMainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 324.07µs (0.00ns CPU time)\n\nRan 1 test for test/EHSHook.t.sol:FreshManagerTest\n[PASS] test_firstBuyTokenOnlyLiquidityNoIMDBalance() (gas: 6842756)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.98ms (3.70ms CPU time)\n\nRan 28 tests for test/EHSHook.t.sol:EHSHookReverseOrderTest\n[PASS] testFuzz_allDirectionsAndPriceLimitedFills(uint96,bool,bool,uint16) (runs: 256, μ: 390081, ~: 379313)\nLogs:\n  Bound result 22\n  Bound result 193\n\n[PASS] testFuzz_sequenceSolvency(uint256) (runs: 256, μ: 3384322, ~: 3391845)\n[PASS] testFuzz_smallSwapRounding(uint8,bool,bool) (runs: 256, μ: 336350, ~: 338848)\n[PASS] test_belowThresholdAndMalformedContextDoNotRevert() (gas: 1358931)\n[PASS] test_blockedBeneficiaryDoesNotBlockOtherBuyers() (gas: 1490945)\n[PASS] test_buyExactInput() (gas: 458409)\n[PASS] test_buyExactOutput() (gas: 426773)\n[PASS] test_claimAndPermissionlessPaymentCannotRedirect() (gas: 1034989)\n[PASS] test_exactInputPartialFillRefund() (gas: 505925)\n[PASS] test_exactInputSellPartialFill() (gas: 348046)\n[PASS] test_exactOutputBuyPartialFill() (gas: 399755)\n[PASS] test_exactOutputPartialFillRefund() (gas: 451748)\n[PASS] test_exactTenIMDThreshold() (gas: 412930)\n[PASS] test_extremeSpecifiedAmountRejectsOnlyUnrepresentableFee() (gas: 449873)\n[PASS] test_factoryCanCollectWithZeroLiquidityChange() (gas: 543872)\n[PASS] test_multipleBeneficiariesRetainIndependentBacking() (gas: 1253722)\n[PASS] test_noRuntimeEscapeHatches() (gas: 1138470)\n[PASS] test_oneWeiBelowThresholdDoesNotEarnReward() (gas: 393784)\n[PASS] test_partialFillBelowThresholdDoesNotEarnReward() (gas: 395841)\n[PASS] test_paymentFailurePreservesDebtAndDoesNotBlockSwaps() (gas: 1050621)\n[PASS] test_permissionsAccessAndSizes() (gas: 152816)\n[PASS] test_poolBindingRejectsEveryMismatchedField() (gas: 99896)\n[PASS] test_refundBurnAvoidsPrefundingHugeInputReservation() (gas: 890699)\n[PASS] test_refundBurnAvoidsPrefundingHugeOutputReservation() (gas: 921620)\n[PASS] test_sellExactInput() (gas: 373596)\n[PASS] test_sellExactOutput() (gas: 404847)\n[PASS] test_wrongPoolCannotInitialize() (gas: 44291)\n[PASS] test_zeroFillRefundsEntireReserve() (gas: 1131294)\nSuite result: ok. 28 passed; 0 failed; 0 skipped; finished in 419.69ms (476.65ms CPU time)\n\nRan 3 tests for test/EHS.t.sol:EHSTest\n[PASS] testFuzz_transferConservesSupply(uint256) (runs: 1000, μ: 111239, ~: 111469)\nLogs:\n  Bound result 650922751805573716393580064\n\n[PASS] test_allowanceLimitsAndZeroRecipient() (gas: 170223)\n[PASS] test_fullSupplyAndNoMintPath() (gas: 47880)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 431.62ms (432.27ms CPU time)\n\nRan 28 tests for test/EHSHook.t.sol:EHSHookTest\n[PASS] testFuzz_allDirectionsAndPriceLimitedFills(uint96,bool,bool,uint16) (runs: 1000, μ: 391576, ~: 396045)\nLogs:\n  Bound result 2721\n  Bound result 95\n\n[PASS] testFuzz_sequenceSolvency(uint256) (runs: 1000, μ: 3378799, ~: 3379879)\n[PASS] testFuzz_smallSwapRounding(uint8,bool,bool) (runs: 1000, μ: 335269, ~: 337178)\n[PASS] test_belowThresholdAndMalformedContextDoNotRevert() (gas: 1353184)\n[PASS] test_blockedBeneficiaryDoesNotBlockOtherBuyers() (gas: 1486578)\n[PASS] test_buyExactInput() (gas: 452990)\n[PASS] test_buyExactOutput() (gas: 422670)\n[PASS] test_claimAndPermissionlessPaymentCannotRedirect() (gas: 1030873)\n[PASS] test_exactInputPartialFillRefund() (gas: 500787)\n[PASS] test_exactInputSellPartialFill() (gas: 353250)\n[PASS] test_exactOutputBuyPartialFill() (gas: 394808)\n[PASS] test_exactOutputPartialFillRefund() (gas: 456761)\n[PASS] test_exactTenIMDThreshold() (gas: 407238)\n[PASS] test_extremeSpecifiedAmountRejectsOnlyUnrepresentableFee() (gas: 444689)\n[PASS] test_factoryCanCollectWithZeroLiquidityChange() (gas: 538453)\n[PASS] test_multipleBeneficiariesRetainIndependentBacking() (gas: 1249394)\n[PASS] test_noRuntimeEscapeHatches() (gas: 1058748)\n[PASS] test_oneWeiBelowThresholdDoesNotEarnReward() (gas: 388092)\n[PASS] test_partialFillBelowThresholdDoesNotEarnReward() (gas: 390657)\n[PASS] test_paymentFailurePreservesDebtAndDoesNotBlockSwaps() (gas: 1046293)\n[PASS] test_permissionsAccessAndSizes() (gas: 152804)\n[PASS] test_poolBindingRejectsEveryMismatchedField() (gas: 99851)\n[PASS] test_refundBurnAvoidsPrefundingHugeInputReservation() (gas: 885788)\n[PASS] test_refundBurnAvoidsPrefundingHugeOutputReservation() (gas: 921761)\n[PASS] test_sellExactInput() (gas: 379081)\n[PASS] test_sellExactOutput() (gas: 409016)\n[PASS] test_wrongPoolCannotInitialize() (gas: 44282)\n[PASS] test_zeroFillRefundsEntireReserve() (gas: 1131456)\nSuite result: ok. 28 passed; 0 failed; 0 skipped; finished in 433.16ms (1.28s CPU time)\n\nRan 18 tests for test/EHSHookEdge.t.sol:EHSHookEdgeTest\n[PASS] testFuzz_reserveIsCappedAndNeverBelowTheFeeOnAnyFill(int256) (runs: 2000, μ: 26394, ~: 26430)\n[PASS] test_afterSwapWithNothingFilledIsANoOpForTheUnspecifiedSide() (gas: 52297)\n[PASS] test_beforeSwapReservesOnlyWhenIMDIsSpecified() (gas: 125652)\n[PASS] test_claimWithNothingPendingIsANoOp() (gas: 108226)\n[PASS] test_constructorRejectsAnAddressWithoutTheRightBits() (gas: 10369)\n[PASS] test_constructorRejectsIMDAsTheLaunchToken() (gas: 6319)\n[PASS] test_constructorRejectsMissingManagerOrToken() (gas: 6328)\n[PASS] test_feeRefundedEventNamesTheSwapper() (gas: 320247)\n[PASS] test_hookDataOfTheWrongLengthFallsBackToTheSwapper() (gas: 1064644)\n[PASS] test_hookRunsOnlyOneLegitimatePoolPerManager() (gas: 79351)\n[PASS] test_hugeExactOutputBuyWithPriceLimitTakesFeeOnIMD() (gas: 407153)\n[PASS] test_immutablesAndConstantsMatchTheBrief() (gas: 72058)\n[PASS] test_otherLPsAndDonationsAreUntouchedByTheHook() (gas: 1992867)\n[PASS] test_paymentNeverTouchesOtherSwappersRefundClaims() (gas: 553837)\n[PASS] test_rewardIsAtMostHalfOfTheSameBuysFee() (gas: 1630999)\n[PASS] test_rewardsRemainPayableAfterLiquidityLeavesThePool() (gas: 1272808)\n[PASS] test_sellsNeverRecordRewardsForAnyBeneficiary() (gas: 695021)\n[PASS] test_swapEventsReportFeeRefundAndReward() (gas: 847623)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 433.17ms (432.53ms CPU time)\n\nRan 12 tests for test/EHSEdge.t.sol:EHSEdgeTest\n[PASS] testFuzz_randomTransfersNeverCreateOrDestroySupply(uint256) (runs: 1000, μ: 705233, ~: 711118)\nLogs:\n  Bound result 562886961203685118748794823\n  Bound result 0\n  Bound result 0\n  Bound result 274489894515496947884600895\n  Bound result 0\n  Bound result 61141899653319300196301377\n  Bound result 256581283466802920682238042\n  Bound result 79383718338917418084559984\n  Bound result 57893250504710625728849672\n  Bound result 108984562721432777297404187\n\n[PASS] testFuzz_transferFromConservesSupplyAndAllowance(uint256,uint256,address) (runs: 1000, μ: 129859, ~: 152800)\nLogs:\n  Bound result 16054670639493041115955379\n  Bound result 651981850910265615357075708\n\n[PASS] test_allowanceDoesNotCoverAnEmptyBalance() (gas: 90657)\n[PASS] test_approveOverwritesAndEmits() (gas: 132109)\n[PASS] test_finiteAllowanceOneWeiShortReverts() (gas: 90852)\n[PASS] test_infiniteAllowanceIsNotDecremented() (gas: 130682)\n[PASS] test_metadataAndConstructorEmission() (gas: 41843)\n[PASS] test_noOwnerMintBurnOrPauseSurface() (gas: 198564)\n[PASS] test_selfTransferOfEntireBalanceKeepsIt() (gas: 41065)\n[PASS] test_transferFromToZeroAddressReverts() (gas: 85213)\n[PASS] test_transferMoreThanBalanceReverts() (gas: 66752)\n[PASS] test_zeroValueTransfersSucceedAndEmit() (gas: 71715)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 433.32ms (854.70ms CPU time)\n\nRan 1 test for test/EHSHook.invariant.t.sol:EHSHookInvariantTest\n[PASS]\nEHSHookInvariantTest invariants:\n[PASS] invariant_beneficiariesReceiveExactlyTheirRecords\n[PASS] invariant_callSummary\n[PASS] invariant_claimsBackAccruedBalance\n[PASS] invariant_feeConservation\n[PASS] invariant_hookHoldsNoTokensAndTaxesNoEHS\n[PASS] invariant_managerBacksEveryClaim\n[PASS] invariant_owedTotalIsSumOfPending\n[PASS] invariant_poolTermsAreFixed\n EHSHookInvariantTest invariants (runs: 256, calls: 10240, reverts: 0)\n\n╭----------------+--------------------+-------+---------+----------╮\n| Contract       | Selector           | Calls | Reverts | Discards |\n+==================================================================+\n| EHSHookHandler | claimRewards       | 995   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | collectFees        | 886   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | donateClaimsToHook | 955   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | donateToPool       | 890   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | modifyLiquidity    | 952   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | payRecorded        | 938   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | redeemRefunds      | 852   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | strangerClaims     | 945   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | swap               | 2827  | 0       | 0        |\n╰----------------+--------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 14427\n  Bound result 412\n  Bound result 4728\n  Bound result 1899\n  Bound result 11454\n  Bound result 100\n  Bound result 10000000000000000000\n  Bound result 10000000000000000000000\n  Bound result 1696\n  Bound result 9999999000000000002632\n  Bound result 60\n  Bound result 50\n  Bound result 1000000000000000000000\n  Bound result 242\n  Bound result 5101\n  Bound result 8\n  Bound result 64\n  Bound result 0\n  Bound result 1881\n  Bound result 479\n  Bound result 200\n  Bound result 618163376055794095043\n  Bound result 128\n  Bound result 494\n  Bound result 2614225886796278\n  Bound result 898403851775807900504\n  Bound result 315671287777642595309\n  Bound result 989900000000000047662\n  Bound result 10\n  Bound result 1744546275370070666619\n  Bound result 733982281\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.39s (4.37s CPU time)\n\nRan 8 test suites in 4.39s (6.54s CPU time): 91 tests passed, 0 failed, 1 skipped (92 total tests)\n","passed":true},{"durationMs":101,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"EHS.approve(address,uint256)\",\"EHS.transfer(address,uint256)\",\"EHS.transferFrom(address,address,uint256)\",\"EHSHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"EHSHook.claimRewards()\",\"EHSHook.payRecorded(address)\",\"EHSHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":13,\"DESIGN.md\":105,\"README.md\":112,\"artifacts/brief-coverage.md\":33,\"artifacts/checks/chromium.json\":74,\"artifacts/checks/foundry-build.txt\":200,\"artifacts/checks/foundry-fork-archive-unavailable.txt\":17,\"artifacts/checks/foundry-fork.txt\":8,\"artifacts/checks/foundry-offline.txt\":81,\"artifacts/checks/taste-preview.json\":116,\"artifacts/checks/taste-static.json\":116,\"artifacts/checks/webkit.json\":70,\"artifacts/dependencies.md\":18,\"artifacts/licenses/better-interface.txt\":220,\"artifacts/licenses/eth-testing.txt\":30,\"artifacts/licenses/uniswap-v4-security.txt\":21,\"artifacts/screenshots/desktop-dark.jpg\":434,\"artifacts/screenshots/mobile-dark.jpg\":355,\"artifacts/security-review.md\":63,\"artifacts/submission-inventory.json\":894,\"artifacts/validation.md\":88,\"dist/404.html\":3,\"dist/app.js\":425,\"dist/assets/beat-1.svg\":14,\"dist/assets/beat-2.svg\":14,\"dist/assets/beat-3.svg\":14,\"dist/assets/hand.svg\":14,\"dist/assets/ibmplexmono-LICENSE.txt\":93,\"dist/assets/logo.jpg\":1307,\"dist/assets/meme-1.png\":636,\"dist/assets/meme-1.svg\":14,\"dist/assets/meme-2.png\":211,\"dist/assets/meme-2.svg\":14,\"dist/assets/meme-3.png\":347,\"dist/assets/meme-3.svg\":14,\"dist/assets/meme-4.png\":300,\"dist/assets/meme-4.svg\":14,\"dist/assets/meme-5.png\":375,\"dist/assets/meme-5.svg\":14,\"dist/assets/meme-6.png\":497,\"dist/assets/meme-6.svg\":14,\"dist/assets/plex-regular.woff2\":58,\"dist/assets/plex-semibold.woff2\":78,\"dist/assets/unifraktur.woff2\":70,\"dist/assets/unifrakturcook-LICENSE.txt\":94,\"dist/config.js\":3,\"dist/data.js\":39,\"dist/error.js\":70,\"dist/index.html\":144,\"dist/styles.css\":1482,\"dist/swarm.js\":237,\"dist/theme.js\":15,\"dist/translations.js\":392,\"foundry.toml\":13,\"launch.json\":16,\"package-lock.json\":83,\"package.json\":16,\"scripts/art.py\":61,\"scripts/build.mjs\":29,\"scripts/check-submission.mjs\":56,\"scripts/preview.mjs\":52,\"scripts/render-art.mjs\":29,\"scripts/validate-browser.mjs\":492,\"site/404.html\":3,\"site/app.js\":425,\"site/assets/beat-1.svg\":14,\"site/assets/beat-2.svg\":14,\"site/assets/beat-3.svg\":14,\"site/assets/hand.svg\":14,\"site/assets/ibmplexmono-LICENSE.txt\":93,\"site/assets/logo.jpg\":1307,\"site/assets/meme-1.png\":636,\"site/assets/meme-1.svg\":14,\"site/assets/meme-2.png\":211,\"site/assets/meme-2.svg\":14,\"site/assets/meme-3.png\":347,\"site/assets/meme-3.svg\":14,\"site/assets/meme-4.png\":300,\"site/assets/meme-4.svg\":14,\"site/assets/meme-5.png\":375,\"site/assets/meme-5.svg\":14,\"site/assets/meme-6.png\":497,\"site/assets/meme-6.svg\":14,\"site/assets/plex-regular.woff2\":58,\"site/assets/plex-semibold.woff2\":78,\"site/assets/unifraktur.woff2\":70,\"site/assets/unifrakturcook-LICENSE.txt\":94,\"site/config.js\":3,\"site/data.js\":39,\"site/error.js\":70,\"site/index.html\":144,\"site/styles.css\":1482,\"site/swarm.js\":237,\"site/theme.js\":15,\"site/translations.js\":392,\"src/EHS.sol\":53,\"src/EHSHook.sol\":172,\"test/EHS.t.sol\":45,\"test/EHSEdge.t.sol\":142,\"test/EHSHook.invariant.t.sol\":426,\"test/EHSHook.t.sol\":593,\"test/EHSHookEdge.t.sol\":343,\"test/mocks/MockIMD.sol\":39,\"test/site.test.mjs\":77,\"tsconfig.json\":8},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"8b02bcc11ac39809c8dbbbba9f839cc17fa7d5bade83160cc427afce45b69c7d","verifiedTreeHash":"333216fe1ddbcdc5ab238c8b906123897f25ecaf","verifierVersion":"0.1.0+8fbf00a5"},{"checks":[{"durationMs":39789,"exitCode":0,"name":"build","output":"Compiling 71 files with Solc 0.8.26\nSolc 0.8.26 finished in 39.71s\nCompiler run successful!\n","passed":true},{"durationMs":524,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/EHSHook.t.sol:EHSMainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 415.50µs (0.00ns CPU time)\n\nRan 3 tests for test/EHS.t.sol:EHSTest\n[PASS] testFuzz_transferConservesSupply(uint256) (runs: 1000, μ: 111241, ~: 111469)\nLogs:\n  Bound result 312401944478171944949740754\n\n[PASS] test_allowanceLimitsAndZeroRecipient() (gas: 170223)\n[PASS] test_fullSupplyAndNoMintPath() (gas: 47880)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 12.38ms (12.52ms CPU time)\n\nRan 1 test for test/EHSHook.t.sol:FreshManagerTest\n[PASS] test_firstBuyTokenOnlyLiquidityNoIMDBalance() (gas: 27482475)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 24.81ms (24.58ms CPU time)\n\nRan 29 tests for test/EHSHook.t.sol:EHSHookReverseOrderTest\n[PASS] testFuzz_allDirectionsAndPriceLimitedFills(uint96,bool,bool,uint16) (runs: 256, μ: 390146, ~: 379346)\nLogs:\n  Bound result 1011949\n  Bound result 224\n\n[PASS] testFuzz_sequenceSolvency(uint256) (runs: 256, μ: 3370899, ~: 3375627)\n[PASS] testFuzz_smallSwapRounding(uint8,bool,bool) (runs: 256, μ: 335942, ~: 338898)\n[PASS] test_belowThresholdAndMalformedContextDoNotRevert() (gas: 1601629)\n[PASS] test_blockedBeneficiaryDoesNotBlockOtherBuyers() (gas: 1491036)\n[PASS] test_buyExactInput() (gas: 458433)\n[PASS] test_buyExactOutput() (gas: 426794)\n[PASS] test_claimAndPermissionlessPaymentCannotRedirect() (gas: 1035056)\n[PASS] test_exactInputPartialFillRefund() (gas: 505993)\n[PASS] test_exactInputSellPartialFill() (gas: 348093)\n[PASS] test_exactOutputBuyPartialFill() (gas: 399776)\n[PASS] test_exactOutputPartialFillRefund() (gas: 451798)\n[PASS] test_exactTenIMDThreshold() (gas: 412976)\n[PASS] test_extremeSpecifiedAmountRejectsOnlyUnrepresentableFee() (gas: 449897)\n[PASS] test_factoryCanCollectWithZeroLiquidityChange() (gas: 543940)\n[PASS] test_multipleBeneficiariesRetainIndependentBacking() (gas: 1253789)\n[PASS] test_noRuntimeEscapeHatches() (gas: 1140318)\n[PASS] test_oneWeiBelowThresholdDoesNotEarnReward() (gas: 393812)\n[PASS] test_partialFillBelowThresholdDoesNotEarnReward() (gas: 395894)\n[PASS] test_paymentFailurePreservesDebtAndDoesNotBlockSwaps() (gas: 1050666)\n[PASS] test_permissionsAccessAndSizes() (gas: 152841)\n[PASS] test_poolBindingRejectsEveryMismatchedField() (gas: 99918)\n[PASS] test_refundBurnAvoidsPrefundingHugeInputReservation() (gas: 890747)\n[PASS] test_refundBurnAvoidsPrefundingHugeOutputReservation() (gas: 921694)\n[PASS] test_sellExactInput() (gas: 373621)\n[PASS] test_sellExactOutput() (gas: 404897)\n[PASS] test_sharedRouterWithEmptyHookDataEarnsNothingToSweep() (gas: 1356048)\n[PASS] test_wrongPoolCannotInitialize() (gas: 44291)\n[PASS] test_zeroFillRefundsEntireReserve() (gas: 1131418)\nSuite result: ok. 29 passed; 0 failed; 0 skipped; finished in 383.88ms (690.19ms CPU time)\n\nRan 29 tests for test/EHSHook.t.sol:EHSHookTest\n[PASS] testFuzz_allDirectionsAndPriceLimitedFills(uint96,bool,bool,uint16) (runs: 1000, μ: 391649, ~: 396036)\nLogs:\n  Bound result 507389988804381553\n  Bound result 2\n\n[PASS] testFuzz_sequenceSolvency(uint256) (runs: 1000, μ: 3380164, ~: 3375784)\n[PASS] testFuzz_smallSwapRounding(uint8,bool,bool) (runs: 1000, μ: 335155, ~: 337228)\n[PASS] test_belowThresholdAndMalformedContextDoNotRevert() (gas: 1595869)\n[PASS] test_blockedBeneficiaryDoesNotBlockOtherBuyers() (gas: 1486669)\n[PASS] test_buyExactInput() (gas: 453014)\n[PASS] test_buyExactOutput() (gas: 422691)\n[PASS] test_claimAndPermissionlessPaymentCannotRedirect() (gas: 1030940)\n[PASS] test_exactInputPartialFillRefund() (gas: 500855)\n[PASS] test_exactInputSellPartialFill() (gas: 353297)\n[PASS] test_exactOutputBuyPartialFill() (gas: 394829)\n[PASS] test_exactOutputPartialFillRefund() (gas: 456811)\n[PASS] test_exactTenIMDThreshold() (gas: 407284)\n[PASS] test_extremeSpecifiedAmountRejectsOnlyUnrepresentableFee() (gas: 444713)\n[PASS] test_factoryCanCollectWithZeroLiquidityChange() (gas: 538521)\n[PASS] test_multipleBeneficiariesRetainIndependentBacking() (gas: 1249461)\n[PASS] test_noRuntimeEscapeHatches() (gas: 1060465)\n[PASS] test_oneWeiBelowThresholdDoesNotEarnReward() (gas: 388120)\n[PASS] test_partialFillBelowThresholdDoesNotEarnReward() (gas: 390710)\n[PASS] test_paymentFailurePreservesDebtAndDoesNotBlockSwaps() (gas: 1046338)\n[PASS] test_permissionsAccessAndSizes() (gas: 152829)\n[PASS] test_poolBindingRejectsEveryMismatchedField() (gas: 99873)\n[PASS] test_refundBurnAvoidsPrefundingHugeInputReservation() (gas: 885836)\n[PASS] test_refundBurnAvoidsPrefundingHugeOutputReservation() (gas: 921835)\n[PASS] test_sellExactInput() (gas: 379106)\n[PASS] test_sellExactOutput() (gas: 409066)\n[PASS] test_sharedRouterWithEmptyHookDataEarnsNothingToSweep() (gas: 1352930)\n[PASS] test_wrongPoolCannotInitialize() (gas: 44282)\n[PASS] test_zeroFillRefundsEntireReserve() (gas: 1131580)\nSuite result: ok. 29 passed; 0 failed; 0 skipped; finished in 432.15ms (665.14ms CPU time)\n\nRan 5 test suites in 433.13ms (853.63ms CPU time): 62 tests passed, 0 failed, 1 skipped (63 total tests)\n","passed":true},{"durationMs":93,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"EHS.approve(address,uint256)\",\"EHS.transfer(address,uint256)\",\"EHS.transferFrom(address,address,uint256)\",\"EHSHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"EHSHook.claimRewards()\",\"EHSHook.payRecorded(address)\",\"EHSHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":13,\"DESIGN.md\":105,\"README.md\":118,\"artifacts/brief-coverage.md\":33,\"artifacts/checks/chromium.json\":74,\"artifacts/checks/foundry-build.txt\":200,\"artifacts/checks/foundry-fork-archive-unavailable.txt\":17,\"artifacts/checks/foundry-fork.txt\":8,\"artifacts/checks/foundry-offline.txt\":81,\"artifacts/checks/taste-preview.json\":116,\"artifacts/checks/taste-static.json\":116,\"artifacts/checks/webkit.json\":70,\"artifacts/dependencies.md\":18,\"artifacts/licenses/better-interface.txt\":220,\"artifacts/licenses/eth-testing.txt\":30,\"artifacts/licenses/uniswap-v4-security.txt\":21,\"artifacts/screenshots/desktop-dark.jpg\":434,\"artifacts/screenshots/mobile-dark.jpg\":355,\"artifacts/security-review.md\":63,\"artifacts/submission-inventory.json\":894,\"artifacts/validation.md\":88,\"dist/404.html\":3,\"dist/app.js\":425,\"dist/assets/beat-1.svg\":14,\"dist/assets/beat-2.svg\":14,\"dist/assets/beat-3.svg\":14,\"dist/assets/hand.svg\":14,\"dist/assets/ibmplexmono-LICENSE.txt\":93,\"dist/assets/logo.jpg\":1307,\"dist/assets/meme-1.png\":636,\"dist/assets/meme-1.svg\":14,\"dist/assets/meme-2.png\":211,\"dist/assets/meme-2.svg\":14,\"dist/assets/meme-3.png\":347,\"dist/assets/meme-3.svg\":14,\"dist/assets/meme-4.png\":300,\"dist/assets/meme-4.svg\":14,\"dist/assets/meme-5.png\":375,\"dist/assets/meme-5.svg\":14,\"dist/assets/meme-6.png\":497,\"dist/assets/meme-6.svg\":14,\"dist/assets/plex-regular.woff2\":58,\"dist/assets/plex-semibold.woff2\":78,\"dist/assets/unifraktur.woff2\":70,\"dist/assets/unifrakturcook-LICENSE.txt\":94,\"dist/config.js\":3,\"dist/data.js\":39,\"dist/error.js\":70,\"dist/index.html\":144,\"dist/styles.css\":1482,\"dist/swarm.js\":237,\"dist/theme.js\":15,\"dist/translations.js\":392,\"foundry.toml\":13,\"launch.json\":30,\"package-lock.json\":83,\"package.json\":16,\"scripts/art.py\":61,\"scripts/build.mjs\":29,\"scripts/check-submission.mjs\":56,\"scripts/preview.mjs\":52,\"scripts/render-art.mjs\":29,\"scripts/validate-browser.mjs\":492,\"site/404.html\":3,\"site/app.js\":425,\"site/assets/beat-1.svg\":14,\"site/assets/beat-2.svg\":14,\"site/assets/beat-3.svg\":14,\"site/assets/hand.svg\":14,\"site/assets/ibmplexmono-LICENSE.txt\":93,\"site/assets/logo.jpg\":1307,\"site/assets/meme-1.png\":636,\"site/assets/meme-1.svg\":14,\"site/assets/meme-2.png\":211,\"site/assets/meme-2.svg\":14,\"site/assets/meme-3.png\":347,\"site/assets/meme-3.svg\":14,\"site/assets/meme-4.png\":300,\"site/assets/meme-4.svg\":14,\"site/assets/meme-5.png\":375,\"site/assets/meme-5.svg\":14,\"site/assets/meme-6.png\":497,\"site/assets/meme-6.svg\":14,\"site/assets/plex-regular.woff2\":58,\"site/assets/plex-semibold.woff2\":78,\"site/assets/unifraktur.woff2\":70,\"site/assets/unifrakturcook-LICENSE.txt\":94,\"site/config.js\":3,\"site/data.js\":39,\"site/error.js\":70,\"site/index.html\":144,\"site/styles.css\":1482,\"site/swarm.js\":237,\"site/theme.js\":15,\"site/translations.js\":392,\"src/EHS.sol\":53,\"src/EHSHook.sol\":181,\"test/EHS.t.sol\":45,\"test/EHSHook.t.sol\":671,\"test/mocks/MockIMD.sol\":39,\"test/site.test.mjs\":77,\"tsconfig.json\":8},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1210,"exitCode":0,"name":"slither","output":"[medium/medium] reentrancy-no-eth at src/EHSHook.sol:135: Reentrancy in EHSHook.payRecorded(address) (src/EHSHook.sol#135-146):\n[medium/medium] unused-return at src/EHSHook.sol:135: EHSHook.payRecorded(address) (src/EHSHook.sol#135-146) ignores return value by poolManager.unlock(abi.encode(beneficiary,amount)) (src/EHSHook.sol#143)\n[low/medium] reentrancy-benign at src/EHSHook.sol:88: Reentrancy in EHSHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/EHSHook.sol#88-128):\n[low/medium] reentrancy-events at src/EHSHook.sol:88: Reentrancy in EHSHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/EHSHook.sol#88-128):\n[low/medium] reentrancy-events at src/EHSHook.sol:88: Reentrancy in EHSHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/EHSHook.sol#88-128):\n[low/medium] reentrancy-events at src/EHSHook.sol:135: Reentrancy in EHSHook.payRecorded(address) (src/EHSHook.sol#135-146):","passed":true},{"durationMs":339,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/EHSHook.sol:104: Reentrancy: State change after external call (3 places)\n[low] large-numeric-literal at src/EHS.sol:9: Large Numeric Literal\n[low] literal-instead-of-constant at src/EHSHook.sol:98: Literal Instead of Constant (2 places)\n[low] missing-inheritance at src/EHS.sol:5: Missing Inheritance\n[low] unchecked-return at src/EHSHook.sol:143: Unchecked Return","passed":true},{"durationMs":6631,"exitCode":0,"name":"proof 6ca6792ef4dd","output":"Compiling 69 files with Solc 0.8.26\nSolc 0.8.26 finished in 6.12s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-1f2f3e85/Proof_6ca6792ef4dd.t.sol:RouterRewardLeakTest\n[PASS] test_rewardIsNotCreditedToASharedRouterOnEmptyHookData() (gas: 421689)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.22ms (380.21µs CPU time)\n\nRan 1 test suite in 5.04ms (4.22ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"bb669335aa699c214ff156f023c459cb911074f8c3c3e646c96331cadafb4c15","verifiedTreeHash":"3bfbf5e111962b72555e62c23b9cff771298d343","verifierVersion":"0.1.0+8fbf00a5"},{"checks":[{"durationMs":66520,"exitCode":0,"name":"build","output":"Compiling 74 files with Solc 0.8.26\nSolc 0.8.26 finished in 66.41s\nCompiler run successful!\n","passed":true},{"durationMs":4581,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/EHSHook.t.sol:EHSMainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 389.53µs (0.00ns CPU time)\n\nRan 1 test for test/EHSHook.t.sol:FreshManagerTest\n[PASS] test_firstBuyTokenOnlyLiquidityNoIMDBalance() (gas: 27482475)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 21.70ms (21.43ms CPU time)\n\nRan 29 tests for test/EHSHook.t.sol:EHSHookReverseOrderTest\n[PASS] testFuzz_allDirectionsAndPriceLimitedFills(uint96,bool,bool,uint16) (runs: 256, μ: 390141, ~: 379342)\nLogs:\n  Bound result 3864\n  Bound result 51\n\n[PASS] testFuzz_sequenceSolvency(uint256) (runs: 256, μ: 3373345, ~: 3379912)\n[PASS] testFuzz_smallSwapRounding(uint8,bool,bool) (runs: 256, μ: 336674, ~: 338898)\n[PASS] test_belowThresholdAndMalformedContextDoNotRevert() (gas: 1601629)\n[PASS] test_blockedBeneficiaryDoesNotBlockOtherBuyers() (gas: 1491036)\n[PASS] test_buyExactInput() (gas: 458433)\n[PASS] test_buyExactOutput() (gas: 426794)\n[PASS] test_claimAndPermissionlessPaymentCannotRedirect() (gas: 1035056)\n[PASS] test_exactInputPartialFillRefund() (gas: 505993)\n[PASS] test_exactInputSellPartialFill() (gas: 348093)\n[PASS] test_exactOutputBuyPartialFill() (gas: 399776)\n[PASS] test_exactOutputPartialFillRefund() (gas: 451798)\n[PASS] test_exactTenIMDThreshold() (gas: 412976)\n[PASS] test_extremeSpecifiedAmountRejectsOnlyUnrepresentableFee() (gas: 449897)\n[PASS] test_factoryCanCollectWithZeroLiquidityChange() (gas: 543940)\n[PASS] test_multipleBeneficiariesRetainIndependentBacking() (gas: 1253789)\n[PASS] test_noRuntimeEscapeHatches() (gas: 1140318)\n[PASS] test_oneWeiBelowThresholdDoesNotEarnReward() (gas: 393812)\n[PASS] test_partialFillBelowThresholdDoesNotEarnReward() (gas: 395894)\n[PASS] test_paymentFailurePreservesDebtAndDoesNotBlockSwaps() (gas: 1050666)\n[PASS] test_permissionsAccessAndSizes() (gas: 152841)\n[PASS] test_poolBindingRejectsEveryMismatchedField() (gas: 99918)\n[PASS] test_refundBurnAvoidsPrefundingHugeInputReservation() (gas: 890747)\n[PASS] test_refundBurnAvoidsPrefundingHugeOutputReservation() (gas: 921694)\n[PASS] test_sellExactInput() (gas: 373621)\n[PASS] test_sellExactOutput() (gas: 404897)\n[PASS] test_sharedRouterWithEmptyHookDataEarnsNothingToSweep() (gas: 1356048)\n[PASS] test_wrongPoolCannotInitialize() (gas: 44291)\n[PASS] test_zeroFillRefundsEntireReserve() (gas: 1131418)\nSuite result: ok. 29 passed; 0 failed; 0 skipped; finished in 459.89ms (895.60ms CPU time)\n\nRan 3 tests for test/EHS.t.sol:EHSTest\n[PASS] testFuzz_transferConservesSupply(uint256) (runs: 1000, μ: 111214, ~: 111469)\nLogs:\n  Bound result 178\n\n[PASS] test_allowanceLimitsAndZeroRecipient() (gas: 170223)\n[PASS] test_fullSupplyAndNoMintPath() (gas: 47880)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 460.02ms (443.10ms CPU time)\n\nRan 29 tests for test/EHSHook.t.sol:EHSHookTest\n[PASS] testFuzz_allDirectionsAndPriceLimitedFills(uint96,bool,bool,uint16) (runs: 1000, μ: 391648, ~: 396036)\nLogs:\n  Bound result 3890996639\n  Bound result 234\n\n[PASS] testFuzz_sequenceSolvency(uint256) (runs: 1000, μ: 3380257, ~: 3376022)\n[PASS] testFuzz_smallSwapRounding(uint8,bool,bool) (runs: 1000, μ: 335149, ~: 337228)\n[PASS] test_belowThresholdAndMalformedContextDoNotRevert() (gas: 1595869)\n[PASS] test_blockedBeneficiaryDoesNotBlockOtherBuyers() (gas: 1486669)\n[PASS] test_buyExactInput() (gas: 453014)\n[PASS] test_buyExactOutput() (gas: 422691)\n[PASS] test_claimAndPermissionlessPaymentCannotRedirect() (gas: 1030940)\n[PASS] test_exactInputPartialFillRefund() (gas: 500855)\n[PASS] test_exactInputSellPartialFill() (gas: 353297)\n[PASS] test_exactOutputBuyPartialFill() (gas: 394829)\n[PASS] test_exactOutputPartialFillRefund() (gas: 456811)\n[PASS] test_exactTenIMDThreshold() (gas: 407284)\n[PASS] test_extremeSpecifiedAmountRejectsOnlyUnrepresentableFee() (gas: 444713)\n[PASS] test_factoryCanCollectWithZeroLiquidityChange() (gas: 538521)\n[PASS] test_multipleBeneficiariesRetainIndependentBacking() (gas: 1249461)\n[PASS] test_noRuntimeEscapeHatches() (gas: 1060465)\n[PASS] test_oneWeiBelowThresholdDoesNotEarnReward() (gas: 388120)\n[PASS] test_partialFillBelowThresholdDoesNotEarnReward() (gas: 390710)\n[PASS] test_paymentFailurePreservesDebtAndDoesNotBlockSwaps() (gas: 1046338)\n[PASS] test_permissionsAccessAndSizes() (gas: 152829)\n[PASS] test_poolBindingRejectsEveryMismatchedField() (gas: 99873)\n[PASS] test_refundBurnAvoidsPrefundingHugeInputReservation() (gas: 885836)\n[PASS] test_refundBurnAvoidsPrefundingHugeOutputReservation() (gas: 921835)\n[PASS] test_sellExactInput() (gas: 379106)\n[PASS] test_sellExactOutput() (gas: 409066)\n[PASS] test_sharedRouterWithEmptyHookDataEarnsNothingToSweep() (gas: 1352930)\n[PASS] test_wrongPoolCannotInitialize() (gas: 44282)\n[PASS] test_zeroFillRefundsEntireReserve() (gas: 1131580)\nSuite result: ok. 29 passed; 0 failed; 0 skipped; finished in 462.27ms (1.24s CPU time)\n\nRan 21 tests for test/EHSHookEdge.t.sol:EHSHookEdgeTest\n[PASS] testFuzz_reserveIsCappedAndNeverBelowTheFeeOnAnyFill(int256) (runs: 2000, μ: 26442, ~: 26474)\n[PASS] test_afterSwapWithNothingFilledIsANoOpForTheUnspecifiedSide() (gas: 52366)\n[PASS] test_beforeSwapReservesOnlyWhenIMDIsSpecified() (gas: 125718)\n[PASS] test_claimWithNothingPendingIsANoOp() (gas: 108270)\n[PASS] test_constructorRejectsAnAddressWithoutTheRightBits() (gas: 10391)\n[PASS] test_constructorRejectsIMDAsTheLaunchToken() (gas: 6341)\n[PASS] test_constructorRejectsMissingManagerOrToken() (gas: 6350)\n[PASS] test_feeRefundedEventNamesTheSwapper() (gas: 320337)\n[PASS] test_hookDataOfTheWrongLengthRecordsNoRewardForAnyone() (gas: 1582478)\n[PASS] test_hookRunsOnlyOneLegitimatePoolPerManager() (gas: 79373)\n[PASS] test_hugeExactOutputBuyWithPriceLimitTakesFeeOnIMD() (gas: 407302)\n[PASS] test_immutablesAndConstantsMatchTheBrief() (gas: 72124)\n[PASS] test_otherLPsAndDonationsAreUntouchedByTheHook() (gas: 1993041)\n[PASS] test_outsideClaimsPushedAtTheHookAreNeitherCountedNorPayable() (gas: 1328499)\n[PASS] test_paymentNeverTouchesOtherSwappersRefundClaims() (gas: 554011)\n[PASS] test_refundClaimBelongsToTheRouterNotItsPayer() (gas: 1108616)\n[PASS] test_rewardIsAtMostHalfOfTheSameBuysFee() (gas: 1631879)\n[PASS] test_rewardsRemainPayableAfterLiquidityLeavesThePool() (gas: 1273126)\n[PASS] test_routerThatBurnsRefundChargesPayerOnlyFillPlusFee() (gas: 346887)\n[PASS] test_sellsNeverRecordRewardsForAnyBeneficiary() (gas: 695255)\n[PASS] test_swapEventsReportFeeRefundAndReward() (gas: 847897)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 462.29ms (447.32ms CPU time)\n\nRan 12 tests for test/EHSEdge.t.sol:EHSEdgeTest\n[PASS] testFuzz_randomTransfersNeverCreateOrDestroySupply(uint256) (runs: 1000, μ: 705344, ~: 712234)\nLogs:\n  Bound result 785805856950407674121898358\n  Bound result 0\n  Bound result 643241410068725446925830694\n  Bound result 0\n  Bound result 0\n  Bound result 324373971951799488948961725\n  Bound result 268956814740243752061921093\n  Bound result 220435521028065743847878385\n  Bound result 313782868120337869268235820\n  Bound result 552844158913514563142332663\n\n[PASS] testFuzz_transferFromConservesSupplyAndAllowance(uint256,uint256,address) (runs: 1000, μ: 129256, ~: 152782)\nLogs:\n  Bound result 8396\n  Bound result 3771\n\n[PASS] test_allowanceDoesNotCoverAnEmptyBalance() (gas: 90657)\n[PASS] test_approveOverwritesAndEmits() (gas: 132109)\n[PASS] test_finiteAllowanceOneWeiShortReverts() (gas: 90852)\n[PASS] test_infiniteAllowanceIsNotDecremented() (gas: 130682)\n[PASS] test_metadataAndConstructorEmission() (gas: 41843)\n[PASS] test_noOwnerMintBurnOrPauseSurface() (gas: 198564)\n[PASS] test_selfTransferOfEntireBalanceKeepsIt() (gas: 41065)\n[PASS] test_transferFromToZeroAddressReverts() (gas: 85213)\n[PASS] test_transferMoreThanBalanceReverts() (gas: 66752)\n[PASS] test_zeroValueTransfersSucceedAndEmit() (gas: 71715)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 462.38ms (500.47ms CPU time)\n\nRan 1 test for test/EHSHook.invariant.t.sol:EHSHookInvariantTest\n[PASS]\nEHSHookInvariantTest invariants:\n[PASS] invariant_beneficiariesReceiveExactlyTheirRecords\n[PASS] invariant_callSummary\n[PASS] invariant_claimsBackAccruedBalance\n[PASS] invariant_feeConservation\n[PASS] invariant_hookHoldsNoTokensAndTaxesNoEHS\n[PASS] invariant_managerBacksEveryClaim\n[PASS] invariant_owedTotalIsSumOfPending\n[PASS] invariant_poolTermsAreFixed\n EHSHookInvariantTest invariants (runs: 256, calls: 10240, reverts: 0)\n\n╭----------------+--------------------+-------+---------+----------╮\n| Contract       | Selector           | Calls | Reverts | Discards |\n+==================================================================+\n| EHSHookHandler | claimRewards       | 995   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | collectFees        | 886   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | donateClaimsToHook | 955   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | donateToPool       | 890   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | modifyLiquidity    | 952   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | payRecorded        | 938   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | redeemRefunds      | 852   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | strangerClaims     | 945   | 0       | 0        |\n|----------------+--------------------+-------+---------+----------|\n| EHSHookHandler | swap               | 2827  | 0       | 0        |\n╰----------------+--------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 14427\n  Bound result 412\n  Bound result 5220\n  Bound result 8058\n  Bound result 12086\n  Bound result 1125\n  Bound result 12500\n  Bound result 10000000000000000000000\n  Bound result 1532\n  Bound result 9999999000000000008412\n  Bound result 60\n  Bound result 32\n  Bound result 99\n  Bound result 49152\n  Bound result 101\n  Bound result 8\n  Bound result 6\n  Bound result 1808\n  Bound result 2049\n  Bound result 1224\n  Bound result 200\n  Bound result 618163376055794095043\n  Bound result 128\n  Bound result 494\n  Bound result 2614225886796278\n  Bound result 898403851775807900504\n  Bound result 9974364696200979560259\n  Bound result 196078431372549019607\n  Bound result 16\n  Bound result 17241078106018288365\n  Bound result 10238\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.48s (4.47s CPU time)\n\nRan 8 test suites in 4.48s (6.81s CPU time): 96 tests passed, 0 failed, 1 skipped (97 total tests)\n","passed":true},{"durationMs":96,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"EHS.approve(address,uint256)\",\"EHS.transfer(address,uint256)\",\"EHS.transferFrom(address,address,uint256)\",\"EHSHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"EHSHook.claimRewards()\",\"EHSHook.payRecorded(address)\",\"EHSHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":13,\"DESIGN.md\":105,\"README.md\":118,\"artifacts/brief-coverage.md\":33,\"artifacts/checks/chromium.json\":74,\"artifacts/checks/foundry-build.txt\":200,\"artifacts/checks/foundry-fork-archive-unavailable.txt\":17,\"artifacts/checks/foundry-fork.txt\":8,\"artifacts/checks/foundry-offline.txt\":81,\"artifacts/checks/taste-preview.json\":116,\"artifacts/checks/taste-static.json\":116,\"artifacts/checks/webkit.json\":70,\"artifacts/dependencies.md\":18,\"artifacts/licenses/better-interface.txt\":220,\"artifacts/licenses/eth-testing.txt\":30,\"artifacts/licenses/uniswap-v4-security.txt\":21,\"artifacts/screenshots/desktop-dark.jpg\":434,\"artifacts/screenshots/mobile-dark.jpg\":355,\"artifacts/security-review.md\":63,\"artifacts/submission-inventory.json\":894,\"artifacts/validation.md\":88,\"dist/404.html\":3,\"dist/app.js\":425,\"dist/assets/beat-1.svg\":14,\"dist/assets/beat-2.svg\":14,\"dist/assets/beat-3.svg\":14,\"dist/assets/hand.svg\":14,\"dist/assets/ibmplexmono-LICENSE.txt\":93,\"dist/assets/logo.jpg\":1307,\"dist/assets/meme-1.png\":636,\"dist/assets/meme-1.svg\":14,\"dist/assets/meme-2.png\":211,\"dist/assets/meme-2.svg\":14,\"dist/assets/meme-3.png\":347,\"dist/assets/meme-3.svg\":14,\"dist/assets/meme-4.png\":300,\"dist/assets/meme-4.svg\":14,\"dist/assets/meme-5.png\":375,\"dist/assets/meme-5.svg\":14,\"dist/assets/meme-6.png\":497,\"dist/assets/meme-6.svg\":14,\"dist/assets/plex-regular.woff2\":58,\"dist/assets/plex-semibold.woff2\":78,\"dist/assets/unifraktur.woff2\":70,\"dist/assets/unifrakturcook-LICENSE.txt\":94,\"dist/config.js\":3,\"dist/data.js\":39,\"dist/error.js\":70,\"dist/index.html\":144,\"dist/styles.css\":1482,\"dist/swarm.js\":237,\"dist/theme.js\":15,\"dist/translations.js\":392,\"foundry.toml\":13,\"launch.json\":30,\"package-lock.json\":83,\"package.json\":16,\"scripts/art.py\":61,\"scripts/build.mjs\":29,\"scripts/check-submission.mjs\":56,\"scripts/preview.mjs\":52,\"scripts/render-art.mjs\":29,\"scripts/validate-browser.mjs\":492,\"site/404.html\":3,\"site/app.js\":425,\"site/assets/beat-1.svg\":14,\"site/assets/beat-2.svg\":14,\"site/assets/beat-3.svg\":14,\"site/assets/hand.svg\":14,\"site/assets/ibmplexmono-LICENSE.txt\":93,\"site/assets/logo.jpg\":1307,\"site/assets/meme-1.png\":636,\"site/assets/meme-1.svg\":14,\"site/assets/meme-2.png\":211,\"site/assets/meme-2.svg\":14,\"site/assets/meme-3.png\":347,\"site/assets/meme-3.svg\":14,\"site/assets/meme-4.png\":300,\"site/assets/meme-4.svg\":14,\"site/assets/meme-5.png\":375,\"site/assets/meme-5.svg\":14,\"site/assets/meme-6.png\":497,\"site/assets/meme-6.svg\":14,\"site/assets/plex-regular.woff2\":58,\"site/assets/plex-semibold.woff2\":78,\"site/assets/unifraktur.woff2\":70,\"site/assets/unifrakturcook-LICENSE.txt\":94,\"site/config.js\":3,\"site/data.js\":39,\"site/error.js\":70,\"site/index.html\":144,\"site/styles.css\":1482,\"site/swarm.js\":237,\"site/theme.js\":15,\"site/translations.js\":392,\"src/EHS.sol\":53,\"src/EHSHook.sol\":181,\"test/EHS.t.sol\":45,\"test/EHSEdge.t.sol\":142,\"test/EHSHook.invariant.t.sol\":431,\"test/EHSHook.t.sol\":671,\"test/EHSHookEdge.t.sol\":482,\"test/mocks/MockIMD.sol\":39,\"test/site.test.mjs\":77,\"tsconfig.json\":8},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"c55ac8dbd1ff7a75b63d25e3061d1d900810ae336da2b0bb7d94358940f5dfb0","verifiedTreeHash":"189cf3f4e978893cba343d529a30cdc21ca42613","verifierVersion":"0.1.0+8fbf00a5"},{"checks":[{"durationMs":27838,"exitCode":0,"name":"build","output":"Compiling 70 files with Solc 0.8.26\nSolc 0.8.26 finished in 27.75s\nCompiler run successful!\n","passed":true},{"durationMs":475,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/EHSHook.t.sol:EHSMainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 341.64µs (0.00ns CPU time)\n\nRan 1 test for test/EHSHook.t.sol:FreshManagerTest\n[PASS] test_firstBuyTokenOnlyLiquidityNoIMDBalance() (gas: 6737671)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 7.22ms (6.85ms CPU time)\n\nRan 19 tests for test/EHSHook.t.sol:EHSHookReverseOrderTest\n[PASS] testFuzz_allDirectionsAndPriceLimitedFills(uint96,bool,bool,uint16) (runs: 256, μ: 330753, ~: 318975)\nLogs:\n  Bound result 22\n  Bound result 193\n\n[PASS] testFuzz_sequenceSolvency(uint256) (runs: 256, μ: 3098755, ~: 3102127)\n[PASS] test_belowThresholdAndMalformedContextDoNotRevert() (gas: 1125715)\n[PASS] test_buyExactInput() (gas: 400047)\n[PASS] test_buyExactOutput() (gas: 366433)\n[PASS] test_claimAndPermissionlessPaymentCannotRedirect() (gas: 913160)\n[PASS] test_exactInputPartialFillRefund() (gas: 445318)\n[PASS] test_exactInputSellPartialFill() (gas: 287624)\n[PASS] test_exactOutputBuyPartialFill() (gas: 339436)\n[PASS] test_exactOutputPartialFillRefund() (gas: 391259)\n[PASS] test_exactTenIMDThreshold() (gas: 352522)\n[PASS] test_extremeSpecifiedAmountRejectsOnlyUnrepresentableFee() (gas: 391598)\n[PASS] test_factoryCanCollectWithZeroLiquidityChange() (gas: 480443)\n[PASS] test_noRuntimeEscapeHatches() (gas: 1138423)\n[PASS] test_paymentFailurePreservesDebtAndDoesNotBlockSwaps() (gas: 930439)\n[PASS] test_permissionsAccessAndSizes() (gas: 121177)\n[PASS] test_sellExactInput() (gas: 313319)\n[PASS] test_sellExactOutput() (gas: 346438)\n[PASS] test_wrongPoolCannotInitialize() (gas: 44195)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 263.18ms (272.01ms CPU time)\n\nRan 3 tests for test/EHS.t.sol:EHSTest\n[PASS] testFuzz_transferConservesSupply(uint256) (runs: 1000, μ: 111200, ~: 111469)\nLogs:\n  Bound result 312401944478171944949740754\n\n[PASS] test_allowanceLimitsAndZeroRecipient() (gas: 170223)\n[PASS] test_fullSupplyAndNoMintPath() (gas: 47880)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 326.11ms (326.40ms CPU time)\n\nRan 19 tests for test/EHSHook.t.sol:EHSHookTest\n[PASS] testFuzz_allDirectionsAndPriceLimitedFills(uint96,bool,bool,uint16) (runs: 1000, μ: 331693, ~: 337282)\nLogs:\n  Bound result 507389988804381553\n  Bound result 2\n\n[PASS] testFuzz_sequenceSolvency(uint256) (runs: 1000, μ: 3108134, ~: 3104360)\n[PASS] test_belowThresholdAndMalformedContextDoNotRevert() (gas: 1118227)\n[PASS] test_buyExactInput() (gas: 394130)\n[PASS] test_buyExactOutput() (gas: 361952)\n[PASS] test_claimAndPermissionlessPaymentCannotRedirect() (gas: 908048)\n[PASS] test_exactInputPartialFillRefund() (gas: 439681)\n[PASS] test_exactInputSellPartialFill() (gas: 292542)\n[PASS] test_exactOutputBuyPartialFill() (gas: 334143)\n[PASS] test_exactOutputPartialFillRefund() (gas: 395833)\n[PASS] test_exactTenIMDThreshold() (gas: 346452)\n[PASS] test_extremeSpecifiedAmountRejectsOnlyUnrepresentableFee() (gas: 385948)\n[PASS] test_factoryCanCollectWithZeroLiquidityChange() (gas: 474452)\n[PASS] test_noRuntimeEscapeHatches() (gas: 874841)\n[PASS] test_paymentFailurePreservesDebtAndDoesNotBlockSwaps() (gas: 925165)\n[PASS] test_permissionsAccessAndSizes() (gas: 121119)\n[PASS] test_sellExactInput() (gas: 318550)\n[PASS] test_sellExactOutput() (gas: 350233)\n[PASS] test_wrongPoolCannotInitialize() (gas: 44186)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 349.64ms (623.65ms CPU time)\n\nRan 5 test suites in 350.47ms (946.50ms CPU time): 42 tests passed, 0 failed, 1 skipped (43 total tests)\n","passed":true},{"durationMs":105,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"EHS.approve(address,uint256)\",\"EHS.transfer(address,uint256)\",\"EHS.transferFrom(address,address,uint256)\",\"EHSHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"EHSHook.claimRewards()\",\"EHSHook.payRecorded(address)\",\"EHSHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":13,\"DESIGN.md\":105,\"README.md\":110,\"artifacts/brief-coverage.md\":33,\"artifacts/checks/chromium.json\":74,\"artifacts/checks/foundry-fork.txt\":8,\"artifacts/checks/foundry-offline.txt\":63,\"artifacts/checks/taste-preview.json\":116,\"artifacts/checks/taste-static.json\":116,\"artifacts/checks/webkit.json\":70,\"artifacts/dependencies.md\":18,\"artifacts/licenses/better-interface.txt\":220,\"artifacts/licenses/eth-testing.txt\":30,\"artifacts/licenses/uniswap-v4-security.txt\":21,\"artifacts/screenshots/desktop-dark.jpg\":434,\"artifacts/screenshots/mobile-dark.jpg\":355,\"artifacts/security-review.md\":63,\"artifacts/submission-inventory.json\":894,\"artifacts/validation.md\":85,\"dist/404.html\":3,\"dist/app.js\":425,\"dist/assets/beat-1.svg\":14,\"dist/assets/beat-2.svg\":14,\"dist/assets/beat-3.svg\":14,\"dist/assets/hand.svg\":14,\"dist/assets/ibmplexmono-LICENSE.txt\":93,\"dist/assets/logo.jpg\":1307,\"dist/assets/meme-1.png\":636,\"dist/assets/meme-1.svg\":14,\"dist/assets/meme-2.png\":211,\"dist/assets/meme-2.svg\":14,\"dist/assets/meme-3.png\":347,\"dist/assets/meme-3.svg\":14,\"dist/assets/meme-4.png\":300,\"dist/assets/meme-4.svg\":14,\"dist/assets/meme-5.png\":375,\"dist/assets/meme-5.svg\":14,\"dist/assets/meme-6.png\":497,\"dist/assets/meme-6.svg\":14,\"dist/assets/plex-regular.woff2\":58,\"dist/assets/plex-semibold.woff2\":78,\"dist/assets/unifraktur.woff2\":70,\"dist/assets/unifrakturcook-LICENSE.txt\":94,\"dist/config.js\":3,\"dist/data.js\":39,\"dist/error.js\":70,\"dist/index.html\":144,\"dist/styles.css\":1482,\"dist/swarm.js\":237,\"dist/theme.js\":15,\"dist/translations.js\":392,\"foundry.toml\":13,\"launch.json\":16,\"package-lock.json\":83,\"package.json\":16,\"scripts/art.py\":61,\"scripts/build.mjs\":29,\"scripts/check-submission.mjs\":56,\"scripts/preview.mjs\":52,\"scripts/render-art.mjs\":29,\"scripts/validate-browser.mjs\":492,\"site/404.html\":3,\"site/app.js\":425,\"site/assets/beat-1.svg\":14,\"site/assets/beat-2.svg\":14,\"site/assets/beat-3.svg\":14,\"site/assets/hand.svg\":14,\"site/assets/ibmplexmono-LICENSE.txt\":93,\"site/assets/logo.jpg\":1307,\"site/assets/meme-1.png\":636,\"site/assets/meme-1.svg\":14,\"site/assets/meme-2.png\":211,\"site/assets/meme-2.svg\":14,\"site/assets/meme-3.png\":347,\"site/assets/meme-3.svg\":14,\"site/assets/meme-4.png\":300,\"site/assets/meme-4.svg\":14,\"site/assets/meme-5.png\":375,\"site/assets/meme-5.svg\":14,\"site/assets/meme-6.png\":497,\"site/assets/meme-6.svg\":14,\"site/assets/plex-regular.woff2\":58,\"site/assets/plex-semibold.woff2\":78,\"site/assets/unifraktur.woff2\":70,\"site/assets/unifrakturcook-LICENSE.txt\":94,\"site/config.js\":3,\"site/data.js\":39,\"site/error.js\":70,\"site/index.html\":144,\"site/styles.css\":1482,\"site/swarm.js\":237,\"site/theme.js\":15,\"site/translations.js\":392,\"src/EHS.sol\":53,\"src/EHSHook.sol\":172,\"test/EHS.t.sol\":45,\"test/EHSHook.t.sol\":385,\"test/mocks/MockIMD.sol\":33,\"test/site.test.mjs\":77,\"tsconfig.json\":8},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1302,"exitCode":0,"name":"slither","output":"[medium/medium] reentrancy-no-eth at src/EHSHook.sol:136: Reentrancy in EHSHook.payRecorded(address) (src/EHSHook.sol#136-147):\n[medium/medium] unused-return at src/EHSHook.sol:136: EHSHook.payRecorded(address) (src/EHSHook.sol#136-147) ignores return value by poolManager.unlock(abi.encode(beneficiary,amount)) (src/EHSHook.sol#144)\n[low/medium] reentrancy-benign at src/EHSHook.sol:87: Reentrancy in EHSHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/EHSHook.sol#87-129):\n[low/medium] reentrancy-events at src/EHSHook.sol:87: Reentrancy in EHSHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/EHSHook.sol#87-129):\n[low/medium] reentrancy-events at src/EHSHook.sol:136: Reentrancy in EHSHook.payRecorded(address) (src/EHSHook.sol#136-147):\n[low/medium] reentrancy-events at src/EHSHook.sol:87: Reentrancy in EHSHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/EHSHook.sol#87-129):","passed":true},{"durationMs":391,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/EHSHook.sol:103: Reentrancy: State change after external call (3 places)\n[low] large-numeric-literal at src/EHS.sol:9: Large Numeric Literal\n[low] literal-instead-of-constant at src/EHSHook.sol:97: Literal Instead of Constant (2 places)\n[low] missing-inheritance at src/EHS.sol:5: Missing Inheritance\n[low] unchecked-return at src/EHSHook.sol:144: Unchecked Return","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"d7db6cc6fa62cef05895058956ee9eb5d15037cbafa88c15f09bd66155cc3972","verifiedTreeHash":"ad759341554d8f7ebf2ebbb2381cdeb0b24b52f7","verifierVersion":"0.1.0+8fbf00a5"}]}