{"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":"bf51d2c0-b390-4a8d-bd1e-c6c6e86d3c69","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"e28e149636baa7fe1c0fe6fa80bb17499aeecc30d2406a983da704b59749450c","dependsOn":["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":"f9701bfdb008da425cf7d4c4fd097d0ada504369f48c58edc53ae53abc72dd5a","dependsOn":["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":"e01c75628d343990e0f5ee9ec87874a589516b49c7216d42413f736cf2f8fbfc","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"c36d5cf7bb486beb8b8268b550a3deaf6639cf16418536064229e010ceb4c262","dependsOn":["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":"f5f0a04bf10f603961eef57f5ca1375eb6f932c4af7d3e732dc30b7d40eab5c7","dependsOn":["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":"6fc1e7b9c7ed650f3448c4a06152845e85a3682e6310358c01c8ec001efb84b5","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"587567a5ef71546b5973ceaf6ef1dbbd590f49503b5ac9a6973ed86b89584857","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"587567a5ef71546b5973ceaf6ef1dbbd590f49503b5ac9a6973ed86b89584857","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"93b496e6ec3054c464611024fa340825da214b7d0551af08413b7b55c6143c27","dependsOn":["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":"07a5b042f81f26bb5e0a9a30e93e7953ef70afe88bb4097b565c7a5d6065ee3d","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","state":"accepted"}],"objective":"[SIMD-LAUNCH]\nToken name: SIMDTEST\nToken symbol: SIMDTEST\n\nSIMDTESTHook (the launch pool's hook):\n1. Immutable constants: buyerRewardFeeBps = 200 (2%), minBuyUnits = 10 IMD units, poolFeeBps = 12500 (1.25%).\n2. Fee application: Take a 2% fee on the paired currency side (IMD) of every swap via beforeSwap/afterSwap return deltas, accruing fees in the hook contract balance.\n3. Reward recording: On each qualifying buy (swap where the recipient is the buyer), if the paired currency amount spent is at least minBuyUnits (10), record a reward equal to 1% of that amount for the buyer address.\n4. Reward claims: Buyers call claimRewards() to receive their accrued rewards in paired currency; anyone can call payRecorded(address) to pay the recorded reward directly to its beneficiary; payRecorded cannot redirect rewards.\n5. Rewards are never paid or triggered inside swap callbacks to avoid reentrancy or accounting issues.\n6. Views: pending(address buyer) returns the buyer's claimable reward amount; owedTotal() returns total pending rewards owed by the hook.\n7. No owner, no admin functions; all parameters are fixed at deployment and immutable.\n\nTOKEN:\n- Symbol: SIMDTEST\n- Name: SIMDTEST\n- Standard ERC20 token with 1,000,000,000 supply and 18 decimals, no transfer taxes or custom logic.\n- The entire supply is minted once on deployment.\n- 90% of the total supply is seeded to the pool; the swarm's 10% allocation is handled externally by the launch factory.\n\nTESTS AND REVIEW:\n1. Swap test: buying SIMDTEST with IMD for amounts below and above 10 IMD units, verifying rewards are recorded only for buys >= 10 units.\n2. Claim test: buyers successfully claim pending rewards via claimRewards().\n3. payRecorded test: third parties can pay recorded rewards without redirect.\n4. Confirm no rewards accrue or pay during non-buyer swaps (e.g., sells).\n5. Mainnet fork tests with real pool deployment to verify correct fee accrual, reward recording, and claiming.\n6. Adversarial security audits covering reentrancy, fee accounting, and invariants.\n\nThis launch implements a simple, immutable buyer rewards hook aligned with SIMD rules: a 2% fee funds 1% buyer rewards on buys of at least 10 IMD units, fully compatible with Uniswap v4, and ensuring safe, transparent, and incentive-driven token launches on the SIMD Launchpad.\n\nBuild requirements (mandatory):\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.\n- Contracts: SIMDTESTHook. 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 (chainId 1). Uniswap v4 PoolManager: 0x000000000004444c5dc75cB358380D2e3dE08A90 (pass it to the hook constructor).\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 swap.\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- launch.json pool: pairedCurrency 0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7, fee 12500, tickSpacing 60, initialPrice \"79228162514264337593543950336\" (provenance only; the launch factory sets the opening price from the economics).","parentJobId":null,"planHash":"351befb7773d54b669d74b04e17d358c97cc7f6666a871ece9fab74caf55a21d","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"bf51d2c0-b390-4a8d-bd1e-c6c6e86d3c69","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-996-simdtest"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51025","feedbackHash":"c67cb3498b612a787d846dcd04b530621dddafda0bd91f2d31d7c27a4fcd65af","nodeKey":"audit_economics","submissionHash":"e28e149636baa7fe1c0fe6fa80bb17499aeecc30d2406a983da704b59749450c","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50969","feedbackHash":"feda969e7ef3b248407864df407d814f8ebe1daad5139e1fbcc46fecb2e2f2f8","nodeKey":"audit_flow","submissionHash":"f9701bfdb008da425cf7d4c4fd097d0ada504369f48c58edc53ae53abc72dd5a","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52292","feedbackHash":"e35c90aea09217523d7ad20b8dfd03ac89dbfedde2d8fba7e0610ba7eecaffc2","nodeKey":"audit_judge","submissionHash":"e01c75628d343990e0f5ee9ec87874a589516b49c7216d42413f736cf2f8fbfc","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50989","feedbackHash":"fe6130aa78cb9978be07cc5189eeea6fa0feae1f3ecf50ba52c817c4798f2154","nodeKey":"audit_math","submissionHash":"c36d5cf7bb486beb8b8268b550a3deaf6639cf16418536064229e010ceb4c262","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51020","feedbackHash":"94954318b031ebfefc73cdca06e50613aae2fa839fe2db852d5d8dd39947b4a0","nodeKey":"audit_permissions","submissionHash":"f5f0a04bf10f603961eef57f5ca1375eb6f932c4af7d3e732dc30b7d40eab5c7","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51417","feedbackHash":"e0736f7835967d358403222b68010834008238f9908b777e72f933c153e210be","nodeKey":"build_contract_project","submissionHash":"6fc1e7b9c7ed650f3448c4a06152845e85a3682e6310358c01c8ec001efb84b5","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52213","feedbackHash":"623c16f1601b62170d2774f0643749eedd9e169499b921569713367e88b66df2","nodeKey":"build_contract_project","submissionHash":"999e4901647c930b78e5ae3a44c82eafd530bc690ac8e00c65887152a54a2f18","tag1":"verification:checks","tag2":"acceptance-v2","value":0},{"agentId":"51747","feedbackHash":"433310a08c2a2cc62e94015cd782883a109b067b777fd87f010d51f7ab3e7640","nodeKey":"build_contract_project","submissionHash":"5f287d77dc17537b4dc059a9179855331b46a0dcec4f2553599197280fdcb7d8","tag1":"verification:checks","tag2":"acceptance-v2","value":0},{"agentId":"51017","feedbackHash":"d3c940fb55a08b66348594576679e35dd2695bf3757a46e105630940d0247918","nodeKey":"manifest","submissionHash":"93b496e6ec3054c464611024fa340825da214b7d0551af08413b7b55c6143c27","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"50975","feedbackHash":"c00758d5fda5a6acae795d22dc4e5010e8dce850551ea9f79bdcb4f9ff414211","nodeKey":"write_foundry_tests","submissionHash":"07a5b042f81f26bb5e0a9a30e93e7953ef70afe88bb4097b565c7a5d6065ee3d","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"7951bcaf88de941855883bc16a399390cf80a07282a69a85a6113c9aea5b4557","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"afb6c6631f240d6a687e836ab9eae6b049ea78a825d6cf3d0c1b90f253442e4c","device":"377843575071cdb1","findings":[],"hash":"07a5b042f81f26bb5e0a9a30e93e7953ef70afe88bb4097b565c7a5d6065ee3d","nodeId":"c878e519-878d-4ae0-b46d-c31e5eb8e152","outcome":"completed","summary":"Added failure-path tests, swap-accounting fuzz tests, stronger invariants, and expanded fork checks. All changes are under `test/`.\n\n`forge build` passes. `forge test`: **57 passed, 0 failed**.\n\nMainnet fork suite skipped because RPC access was unavailable; live verification remains outstanding.","treeHash":"d36e6968112da7d138c5a739e8991743ada4dedb","usage":{"cachedInputTokens":1588992,"inputTokens":101408,"model":"gpt-6-astra","outputTokens":19950,"runtime":"codex","turns":8,"wallClockMs":647031}},{"artifacts":[],"attempt":2,"bundleHash":"7868c84521b8308fc9a271db4a4309850cd1b826c1aff8c6a58e9d6eb97257d3","device":"e15fc098eeb318a3","findings":[],"hash":"5f287d77dc17537b4dc059a9179855331b46a0dcec4f2553599197280fdcb7d8","nodeId":"6d844bed-a26c-45ea-bcef-4921d5647d6f","outcome":"completed","summary":"Implemented the complete Foundry project, including the hook, token, corrected router, manifest, vendored dependencies, and documentation.\n\n- Build and formatting checks pass.\n- 62 local tests and 11 protected checks pass.\n- Mainnet fork test passes at block 26,145,306.\n- Slither reports no high, medium, or low findings; two informational findings remain.\n\nReward-eligible buys use the included immutable router. Deployment responsibilities and review details are documented in [README.md](./README.md) and [VALIDATION.md](./docs/VALIDATION.md).","treeHash":"079f6c5118ef97040744be2d963c8bb3a33b3b19","usage":{"cachedInputTokens":3564160,"inputTokens":135717,"model":"gpt-6-astra","outputTokens":43740,"runtime":"codex","turns":8,"wallClockMs":1339800}},{"artifacts":[],"attempt":3,"bundleHash":"ce9daaf4aa4a8c61cfde82491a675aa552669a0ef4b9ce1b93c87d0f17cb6dc6","device":"163f8bc7d28274e2","findings":[],"hash":"6fc1e7b9c7ed650f3448c4a06152845e85a3682e6310358c01c8ec001efb84b5","nodeId":"6d844bed-a26c-45ea-bcef-4921d5647d6f","outcome":"completed","summary":"Implemented the token, immutable rewards hook, authenticated router, corrected `launch.json`, tests, and documentation.\n\nValidation passed: build, formatting, 41 tests, 11 protected checks, and second-seed fuzzing at 2,000 runs per test.\n\nMainnet fork tests are included but remain unverified because public RPC access failed. Independent review remains outstanding.","treeHash":"5c52b3b7f6a4338cc8ec467ec9d953cbb51d104a","usage":{"cachedInputTokens":1938816,"inputTokens":220270,"model":"gpt-6-astra","outputTokens":39704,"runtime":"codex","turns":8,"wallClockMs":1329683}},{"artifacts":[],"attempt":1,"bundleHash":"42c56c14938ab7aa02018af7e8b036a9820436cde81a89c93918f9b0b87dd02e","device":"69bbcd9de2fdd6d0","findings":[],"hash":"93b496e6ec3054c464611024fa340825da214b7d0551af08413b7b55c6143c27","nodeId":"6494f914-d3ee-41b6-ae0e-ead7b1b0278c","outcome":"completed","summary":"Updated [launch.json](/root/.identitymd/work/bf51d2c0-b390-4a8d-bd1e-c6c6e86d3c69/6494f914-d3ee-41b6-ae0e-ead7b1b0278c/launch.json) to describe the implemented hook and reward behavior.\n\n- Schema, constructor arguments, and permissions validated.\n- `forge build` passed.\n- `forge test`: 57 passed, 0 failed; mainnet-fork suite skipped without a fork.\n- Only `launch.json` changed.","treeHash":"ffbe2011af13167210ff6daa7927022c29e444b2","usage":{"cachedInputTokens":416512,"inputTokens":53468,"model":"gpt-6-astra","outputTokens":4090,"runtime":"codex","turns":3,"wallClockMs":146919}},{"artifacts":[],"attempt":1,"bundleHash":"05b17b574042adb2190e3ec39c3a3a293041c9d4f8b3699468698e03885f8e7b","device":"70dd8643efb15b75","findings":[],"hash":"999e4901647c930b78e5ae3a44c82eafd530bc690ac8e00c65887152a54a2f18","nodeId":"6d844bed-a26c-45ea-bcef-4921d5647d6f","outcome":"completed","summary":"Implemented the token, immutable rewards hook/router, launch manifest, vendored dependencies, and adversarial tests.\n\n- `forge build` and `forge fmt --check` pass.\n- `forge test`: **51 passed, 1 skipped**.\n- Mainnet fork validation remains unrun: public RPCs returned HTTP 403.\n- Reward-eligible buys must use the included router.\n\nDeployment instructions and review limitations are in [README.md](./README.md).","treeHash":"2f3e11b36e76a404f5dbbfc897df10bdbac2445e","usage":{"cachedInputTokens":1738880,"inputTokens":111075,"model":"gpt-6-astra","outputTokens":38344,"runtime":"codex","turns":7,"wallClockMs":1132007}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"e36579e0223ff908","findings":[{"citation":"resolved","description":"Area: Math Precision / Numerical Gap (precision x invariant: two formulas that should agree use different bases). The brief fixes the hook fee at 2% of the paired-currency side of every swap. For exact-input buys beforeSwap reserves floor(gross/50) (2.0000% of what the buyer pays); for exact-input sells and exact-output sells afterSwap/beforeSwap take floor(grossOut/50) and floor(N/49) respectively, both exactly 2% of the gross IMD leaving the pool. The exact-output buy is the odd one out: afterSwap sets fee = ammIn/50 where ammIn is the pool's IMD input, so the buyer pays ammIn + ammIn/50 and the fee is 1/51 = 1.9608% of gross. The hook already knows the right conversion: line 101 caps a partially filled exact-input buy at `filled / 49` (2% of gross for a given pool input), so the same pool input is charged floor(x/49) on one path and floor(x/50) on the other. Consequence: a buyer who routes the same trade as exact-output pays about 2% less hook fee than one who routes it exact-input, and the hook's fee-over-reward surplus (which is all that backs rewards when claims are later redeemed) is 1.96x the reward on this path instead of 2x. Solvency still holds (1.96% > 1%), so impact is limited to under-collection and an inconsistent fee schedule. Fix: use `amount / 49` for the exact-output-buy branch (pairedDelta < 0 && !specified), matching line 101, or document the 1/51 base in the brief.","line":122,"path":"src/SIMDTESTHook.sol","reproduction":"Local PoolManager, pool at sqrtPrice 2^96 with 10,000,000e18 liquidity in [-600,600] (test/helpers/HookFixture.sol). (1) alice calls rewardRouter.swap(buy=true, amountSpecified=+4900e18, maxInput=14701e18, 0, limit, now). Observed: pool IMD input 4964457900827101482259, gross debited 5063747058843643511904, hook fee 99289158016542029645 = 1.9607% of gross. (2) Revert state, alice calls rewardRouter.swap(buy=true, amountSpecified=-5063747058843643511904, maxInput=same, ...). Observed: same gross 5063747058843643511904 debited, hook fee 101274941176872870238 = 1.9999% of gross. Expected: equal fee for equal gross (brief: 2% of the paired side). Actual: exact-output buy pays 1985783160330840593 wei (~1.99 IMD) less; applying the hook's own cap formula ammIn/49 to the same pool input gives 101315467363818397597, i.e. the two formulas disagree by about 2% of the fee. Both buyers receive the identical reward 50637470588436435119, so the surplus backing rewards differs.","severity":"low","snippet":"        uint256 fee = specified ? _feeForSwap : amount / 50;","title":"Exact-output buys are charged 2% of the pool input (1/51 of gross) while every other path and the hook's own partial-fill cap charge 2% of gross"},{"citation":"resolved","description":"Area: Boundary (unit/scale assumption at the reward threshold). The brief says `minBuyUnits = 10 IMD units` and `if the paired currency amount spent is at least minBuyUnits (10)`. The implementation and launch.json interpret this as 10 whole IMD (10e18 base units); the getter therefore returns 10000000000000000000 rather than 10. The reading is reasonable and documented in README/launch.json notes, but it is the one assumption in the threshold math that the brief does not settle, and it changes which buys earn rewards by eighteen orders of magnitude. Any verifier or integrator that compares `minBuyUnits()` against the literal 10 from the brief will see a mismatch. Not a math defect in itself; recorded so the author can confirm the intended scale.","line":27,"path":"src/SIMDTESTHook.sol","reproduction":"Call SIMDTESTHook.minBuyUnits(): returns 10000000000000000000. Boundary behaviour (test/Hook.t.sol test_thresholdAndDeferredClaims): exact-input buy of 9999999999999999999 wei IMD records pending(alice)=0; exact-input buy of 10000000000000000000 wei records 100000000000000000. If the brief meant 10 base units, every buy of >= 10 wei should record floor(spent/100) and the first swap above would record 99999999999999999.","severity":"info","snippet":"    uint256 public constant minBuyUnits = 10 ether;","title":"minBuyUnits() returns 10e18, not the literal 10 the brief states; threshold is interpreted as 10 whole IMD"}],"hash":"c36d5cf7bb486beb8b8268b550a3deaf6639cf16418536064229e010ceb4c262","nodeId":"12657907-4b1b-4b1a-a97b-d91d616d05ed","outcome":"completed","summary":"The review is complete. The findings file is written, the snippets match the tree exactly, and the only file I created outside it (the scratch test) is removed, so the working tree holds just `.imd-findings.json`.\n\n**Outcome.** The fee and reward arithmetic in the hook holds up under the Math Precision, Boundary and Numerical Gap checks. I found one low-severity inconsistency and one informational unit question. No critical or high finding, so no proof file was needed.\n\n**What I verified by hand and by running the suite.**\n- Exact-input buys: the reserved fee is 2% of gross, and the reverting preview's cap can never undercut a full fill. I proved the floor arithmetic for every residue of the amount modulo 50, and that a limit-bound partial fill consumes the same amount in the preview and the real swap.\n- Exact-output sells: the gross-up divisor of 49 equals 2% of gross exactly, and the capped partial case cannot leave the swapper short of the requested net amount.\n- Solvency: on every rewarding path the fee exceeds the reward, including at the 10 IMD threshold with rounding, so hook assets always cover owed rewards.\n- The preview relies on the vendored Hooks library skipping callbacks when the hook is the swap caller. I confirmed that at the relevant lines, so the preview cannot nest callbacks or corrupt the transient fee word.\n- The mainnet IMD contract is a plain OpenZeppelin ERC-20 with LayerZero extensions, 18 decimals, no transfer fee, so the plain-token assumption in the fee math is sound.\n\n**Findings reported.**\n1. **Low.** Exact-output buys are charged 2% of the pool's IMD input, which is 1/51 of gross. Every other path, and the hook's own partial-fill cap on line 101, charge 2% of gross. For the same gross spend of about 5,064 IMD, an exact-output buy pays about 99.29 IMD in fees and an exact-input buy pays about 101.27 IMD. Rewards are identical, so only the hook's surplus is under-collected. The fix is a divisor of 49 on that branch.\n2. **Info.** The brief writes the threshold constant as 10, while the getter returns 10e18 (ten whole IMD). The README documents the choice, but the author should confirm the intended scale.\n\n**Coverage.** All 12 listed entry points have rows: 11 hold, afterSwap carries finding 1. The one unreached item is the live mainnet fork rehearsal, which is skipped offline and remains owed to the deployer.","treeHash":null,"usage":{"cachedInputTokens":1195843,"inputTokens":290,"model":"claude-fable-5-1","outputTokens":45561,"runtime":"claude","turns":36,"wallClockMs":718851}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ddcecd7483f623f7","findings":[{"citation":"resolved","description":"Merged from audit_economics. _accrueFee first tries poolManager.take(IMD, hook, fee) inside afterSwap. take transfers IMD out of the manager immediately. v4 settlement credits the payer with balanceNow - reservesAtSync, and the manager allows sync -> transfer -> swap -> settle as a legal ordering. With this hook, the take inside afterSwap lowers the manager's IMD balance by the fee between such a router's sync and settle, so settle credits (payment - fee) against a debit of (pool input + fee) = payment, and the unlock reverts with CurrencyNotSettled. The mirror sell succeeds because the synced currency is SIMDTEST. The brief requires that the hook never reverts a swap. The bundled reward router, PoolSwapTest and Uniswap's V4Router all settle after the swap and are unaffected, so impact is limited to pay-first integrators and no funds are lost: low. The mint fallback that already exists is the fix: mint the ERC-6909 claim (always, or when TransientStateLibrary.getSyncedCurrency(poolManager) == IMD) and let collectFees redeem it.","line":208,"path":"src/SIMDTESTHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolModifyLiquidityTest} from \"v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.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 {SIMDTEST} from \"src/SIMDTEST.sol\";\nimport {SIMDTESTHook} from \"src/SIMDTESTHook.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\n\ncontract PlainERC20 is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {}\n    function mint(address to, uint256 amount) external { _mint(to, amount); }\n}\n\n/// @dev A router that pays before swapping: sync -> transfer -> swap -> settle. v4 only requires that\n/// the balance change between sync and settle equals the payment; the hook's take breaks that.\ncontract PrepayRouter is IUnlockCallback {\n    IPoolManager immutable manager;\n    constructor(IPoolManager m) { manager = m; }\n\n    function swap(PoolKey memory key, SwapParams memory p, uint256 pay) external returns (BalanceDelta d) {\n        Currency input = p.zeroForOne ? key.currency0 : key.currency1;\n        IERC20(Currency.unwrap(input)).transferFrom(msg.sender, address(this), pay);\n        d = abi.decode(manager.unlock(abi.encode(msg.sender, key, p, pay)), (BalanceDelta));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (address user, PoolKey memory key, SwapParams memory p, uint256 pay) =\n            abi.decode(data, (address, PoolKey, SwapParams, uint256));\n        Currency input = p.zeroForOne ? key.currency0 : key.currency1;\n        Currency output = p.zeroForOne ? key.currency1 : key.currency0;\n        manager.sync(input);\n        IERC20(Currency.unwrap(input)).transfer(address(manager), pay);\n        BalanceDelta d = manager.swap(key, p, \"\");\n        manager.settle();\n        int128 inD = p.zeroForOne ? d.amount0() : d.amount1();\n        int128 outD = p.zeroForOne ? d.amount1() : d.amount0();\n        uint256 spent = uint256(-int256(inD));\n        if (pay > spent) manager.take(input, user, pay - spent);\n        manager.take(output, user, uint128(outD));\n        return abi.encode(d);\n    }\n}\n\ncontract PrepaidRouterProofTest is Test {\n    address constant IMD = 0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7;\n    uint160 constant Q96 = 79228162514264337593543950336;\n\n    PoolManager manager;\n    SIMDTEST token;\n    SIMDTESTHook hook;\n    PoolKey key;\n    PrepayRouter router;\n    address alice = makeAddr(\"alice\");\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        vm.etch(IMD, address(new PlainERC20()).code);\n        token = new SIMDTEST();\n        hook = _deployHook();\n        key = hook.rewardRouter().poolKey();\n        manager.initialize(key, Q96);\n        PoolModifyLiquidityTest lp = new PoolModifyLiquidityTest(manager);\n        PlainERC20(IMD).mint(address(this), 100_000_000 ether);\n        IERC20(IMD).approve(address(lp), type(uint256).max);\n        token.approve(address(lp), type(uint256).max);\n        lp.modifyLiquidity(key, ModifyLiquidityParams(-600, 600, 10_000_000 ether, 0), \"\");\n        router = new PrepayRouter(manager);\n        PlainERC20(IMD).mint(alice, 1_000 ether);\n        token.transfer(alice, 1_000 ether);\n        vm.startPrank(alice);\n        IERC20(IMD).approve(address(router), type(uint256).max);\n        token.approve(address(router), type(uint256).max);\n        vm.stopPrank();\n    }\n\n    function _deployHook() internal returns (SIMDTESTHook) {\n        bytes memory code = abi.encodePacked(type(SIMDTESTHook).creationCode, abi.encode(manager, token));\n        bytes32 hash = keccak256(code);\n        for (uint256 i; i < 200_000; ++i) {\n            bytes32 salt = bytes32(i);\n            address at =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, hash)))));\n            if (!HookFlags.matches(at, HookFlags.FLAGS)) continue;\n            address deployed;\n            assembly (\"memory-safe\") { deployed := create2(0, add(code, 32), mload(code), salt) }\n            require(deployed == at, \"mined deployment failed\");\n            return SIMDTESTHook(deployed);\n        }\n        revert(\"salt not found\");\n    }\n\n    function _limit(bool zeroForOne) internal pure returns (uint160) {\n        return zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1;\n    }\n\n    /// A sell (token in) through the prepaying router works: the hook's take moves IMD, not the synced token.\n    function test_prepaidSellSucceeds() public {\n        bool zeroForOne = address(token) < IMD;\n        vm.prank(alice);\n        router.swap(key, SwapParams(zeroForOne, -100 ether, _limit(zeroForOne)), 100 ether);\n        assertEq(IERC20(IMD).balanceOf(address(hook)), IERC20(IMD).balanceOf(address(hook)));\n    }\n\n    /// The same router buying (IMD in) reverts: afterSwap takes the 2 IMD fee out of the manager\n    /// between the router's sync and settle, so settle credits only 98 of the 100 IMD paid.\n    function test_prepaidExactInputBuySucceeds() public {\n        bool zeroForOne = IMD < address(token);\n        uint256 before = token.balanceOf(alice);\n        vm.prank(alice);\n        router.swap(key, SwapParams(zeroForOne, -100 ether, _limit(zeroForOne)), 100 ether);\n        assertGt(token.balanceOf(alice), before, \"buyer received no SIMDTEST\");\n        assertEq(IERC20(IMD).balanceOf(alice), 900 ether);\n        assertEq(IERC20(IMD).balanceOf(address(hook)) + manager.balanceOf(address(hook), uint160(IMD)), 2 ether);\n    }\n}","reproduction":"Local PoolManager, SIMDTEST/IMD pool at 1:1 with 10,000,000e18 liquidity in [-600,600], hook at a mined 0x20cc address. Router whose unlockCallback does sync(input); transfer(manager, pay); swap(key, params, \"\"); settle(); then takes output and refund. (1) alice sells 100e18 SIMDTEST exact-input: succeeds. (2) alice buys with 100e18 IMD exact-input, pay = 100e18. Expected: succeeds, alice receives SIMDTEST, hook holds 2e18 IMD. Actual: PoolManager.unlock reverts with CurrencyNotSettled() because settle credited 98e18 while the router's IMD delta was -100e18. Ran test/scratch/Proof_prepaid.t.sol: test_prepaidExactInputBuySucceeds FAILS with CurrencyNotSettled(), test_prepaidSellSucceeds passes.","severity":"low","snippet":"        try poolManager.take(Currency.wrap(pairedCurrency), address(this), fee) {","title":"afterSwap takes the IMD fee out of the PoolManager mid-swap, so a router that syncs IMD before the swap and settles after it cannot buy (CurrencyNotSettled)"},{"citation":"resolved","description":"Merged from audit_permissions and audit_flow (same root cause). For an exact-output sell the IMD side is specified, _specifiedFee returns amountSpecified/49, and the preview adds it to amountSpecified with checked int256 arithmetic before the fill-based cap is applied. For amountSpecified > type(int256).max * 49/50 (for example type(int256).max, a 'fill up to the price limit' request) the addition overflows. PoolManager wraps the panic as WrappedError(hook, beforeSwap.selector, Panic(0x11), HookCallFailed) and the whole swap reverts, while the same request on an identical hookless pool partially fills to sqrtPriceLimitX96. This breaks the brief's rule that the hook never reverts a swap. The exact-input branch is safe (fee is added toward zero) and the bundled router plus V4Router (uint128 amounts) cannot reach it, so only a direct PoolManager integrator is affected: low. Fix: saturate the preview amount (e.g. if amountSpecified > type(int256).max - int256(fee), preview with type(int256).max) or cap fee before adding it.","line":99,"path":"src/SIMDTESTHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.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 {PoolModifyLiquidityTest} from \"v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {SIMDTEST} from \"src/SIMDTEST.sol\";\nimport {SIMDTESTHook} from \"src/SIMDTESTHook.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\n\ncontract PlainIMD is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {}\n    function mint(address to, uint256 amount) external { _mint(to, amount); }\n}\n\n/// @dev Minimal direct PoolManager integrator: swap, then settle input and take output.\ncontract RawSwapper is IUnlockCallback {\n    IPoolManager immutable manager;\n    constructor(IPoolManager m) { manager = m; }\n\n    function swap(PoolKey memory key, SwapParams memory p) external returns (BalanceDelta d) {\n        d = abi.decode(manager.unlock(abi.encode(key, p)), (BalanceDelta));\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) = abi.decode(data, (PoolKey, SwapParams));\n        BalanceDelta d = manager.swap(key, p, \"\");\n        _settleOrTake(key.currency0, d.amount0());\n        _settleOrTake(key.currency1, d.amount1());\n        return abi.encode(d);\n    }\n\n    function _settleOrTake(Currency c, int128 amount) internal {\n        if (amount < 0) {\n            manager.sync(c);\n            IERC20(Currency.unwrap(c)).transfer(address(manager), uint256(-int256(amount)));\n            manager.settle();\n        } else if (amount > 0) {\n            manager.take(c, address(this), uint128(amount));\n        }\n    }\n}\n\ncontract OverflowProofTest is Test {\n    address constant IMD = 0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7;\n    uint160 constant Q96 = 79228162514264337593543950336;\n\n    PoolManager manager;\n    SIMDTEST token;\n    SIMDTESTHook hook;\n    PoolKey key;\n    PoolKey plain;\n    RawSwapper swapper;\n    PoolModifyLiquidityTest lp;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        vm.etch(IMD, address(new PlainIMD()).code);\n        token = new SIMDTEST();\n        hook = _deployHook();\n        key = hook.rewardRouter().poolKey();\n        plain = PoolKey(key.currency0, key.currency1, 12500, 60, IHooks(address(0)));\n        manager.initialize(key, Q96);\n        manager.initialize(plain, Q96);\n        lp = new PoolModifyLiquidityTest(manager);\n        PlainIMD(IMD).mint(address(this), 100_000_000 ether);\n        IERC20(IMD).approve(address(lp), type(uint256).max);\n        token.approve(address(lp), type(uint256).max);\n        lp.modifyLiquidity(key, ModifyLiquidityParams(-600, 600, 10_000_000 ether, 0), \"\");\n        lp.modifyLiquidity(plain, ModifyLiquidityParams(-600, 600, 10_000_000 ether, 0), \"\");\n        swapper = new RawSwapper(manager);\n        token.transfer(address(swapper), 10_000_000 ether);\n    }\n\n    function _deployHook() internal returns (SIMDTESTHook) {\n        bytes memory code = abi.encodePacked(type(SIMDTESTHook).creationCode, abi.encode(manager, token));\n        bytes32 hash = keccak256(code);\n        for (uint256 i; i < 200_000; ++i) {\n            bytes32 salt = bytes32(i);\n            address at =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, hash)))));\n            if (!HookFlags.matches(at, HookFlags.FLAGS)) continue;\n            address deployed;\n            assembly (\"memory-safe\") { deployed := create2(0, add(code, 32), mload(code), salt) }\n            require(deployed == at, \"mined deployment failed\");\n            return SIMDTESTHook(deployed);\n        }\n        revert(\"salt not found\");\n    }\n\n    /// An exact-output sell asking for \"as much IMD as the price limit allows\" (type(int256).max)\n    /// fills partially on a plain pool, but the hooked pool reverts: beforeSwap adds N/49 to N with\n    /// checked arithmetic and panics (0x11) before the fill-based cap is applied.\n    function test_maxExactOutputSellDoesNotRevert() public {\n        bool zeroForOne = address(token) < IMD; // sell SIMDTEST for IMD\n        uint160 limit = zeroForOne ? uint160(Q96 * 99 / 100) : uint160(Q96 * 101 / 100);\n\n        // Control: the same request on the hookless twin pool partially fills to the limit.\n        BalanceDelta ref = swapper.swap(plain, SwapParams(zeroForOne, type(int256).max, limit));\n        int128 refIMD = (IMD < address(token)) ? ref.amount0() : ref.amount1();\n        assertGt(refIMD, 0, \"plain pool fills to the limit\");\n\n        // Control: a merely huge request on the hooked pool works (fee is capped to the fill).\n        BalanceDelta big = swapper.swap(key, SwapParams(zeroForOne, int256(1 << 250), limit));\n        int128 bigIMD = (IMD < address(token)) ? big.amount0() : big.amount1();\n        assertGt(bigIMD, 0, \"hooked pool fills 2^250 request\");\n\n        // Defect: type(int256).max reverts on the hooked pool.\n        BalanceDelta d = swapper.swap(key, SwapParams(zeroForOne, type(int256).max, limit));\n        int128 gotIMD = (IMD < address(token)) ? d.amount0() : d.amount1();\n        assertGt(gotIMD, 0, \"hooked pool must also fill the max request instead of reverting\");\n    }\n}","reproduction":"Local setup as in test/helpers/HookFixture.sol (token < IMD so currency0 = SIMDTEST, liquidity -600..600 at 1:1), plus a hookless twin pool with the same liquidity. A plain unlock-callback contract holding SIMDTEST calls manager.swap(key, SwapParams({zeroForOne: token < IMD, amountSpecified: type(int256).max, sqrtPriceLimitX96: Q96*99/100}), \"\"). Controls: the same call with amountSpecified = 2^250 on the hooked pool succeeds (partial fill, fee capped), and type(int256).max on the hookless twin succeeds. Actual on the hooked pool: revert WrappedError(hook, 0x575e24b4, 0x4e487b71..11, 0xa9e35b2f) from line 99. Ran test/scratch/OverflowProof.t.sol: test_maxExactOutputSellDoesNotRevert FAILS with exactly that WrappedError.","severity":"low","snippet":"            preview.amountSpecified += int256(fee);","title":"beforeSwap preview overflows (Panic 0x11) on an exact-output sell larger than ~0.98 * 2^255, reverting a swap the pool would partially fill"},{"citation":"resolved","description":"Merged from audit_permissions, audit_math and audit_economics (same root cause, three reports). Exact-input buys reserve floor(budget/50) (2% of gross spend); exact-input sells withhold floor(grossOut/50); exact-output sells gross up with N/49 so the fee is 2% of gross; and the partial-fill cap at line 101 charges filled/49 for a given pool input. The exact-output buy is the only mode where the IMD side is unspecified: afterSwap sets fee = ammIn/50 and the buyer pays ammIn + fee, so the fee is 1/51 = 1.9608% of gross. Two buyers spending the same IMD therefore pay different hook fees depending only on which side they specify, and the same pool input is charged x/49 on one path (line 101) and x/50 on another (line 122). The brief says 'Take a 2% fee on the paired currency side (IMD) of every swap'. Rewards are 1% of gross on both paths, fee > reward still holds (1.96% > 1%), and the hook stays solvent, so this is under-collection and an inconsistent fee schedule: low. Fix: use amount / 49 for the unspecified-side buy (pairedDelta < 0 && !specified), or document the 1/51 base as intended.","line":122,"path":"src/SIMDTESTHook.sol","reproduction":"Local PoolManager, pool at sqrtPrice 2^96 with 10,000,000e18 liquidity in [-600,600] (test/helpers/HookFixture.sol). (1) alice: router.swap(true, +100e18 /*exact-output buy of 100 SIMDTEST*/, 301e18, 0, limit, now). Observed: IMD spent 103292172162227951394, hook fee 2025336709063293164 (1.961% of spent; floor(spent/50) would be 2065843443244559027), reward 1032921721622279513. (2) revert state; alice: router.swap(true, -103292172162227951394 /*exact input, same gross*/, 103292172162227951394, 0, limit, now). Observed: same spend, hook fee 2065843443244559027 (2.000%), same reward. Expected: equal fee for equal gross spend. Actual: the exact-output buyer pays 40506734181265863 wei less. Reproduced in a scratch test (test_exactOutputBuyFeeAsymmetry) with the logged numbers above.","severity":"low","snippet":"        uint256 fee = specified ? _feeForSwap : amount / 50;","title":"Exact-output buys are charged 2% of the pool input (1/51 = 1.96% of gross IMD spent) while every other swap mode and the hook's own partial-fill cap charge 2% of gross"},{"citation":"resolved","description":"From audit_permissions. The brief defines a qualifying buy as 'swap where the recipient is the buyer'. The hook credits only buys whose v4 sender is its own immutable rewardRouter. A buyer using the Universal Router or any other v4 router, sending output to themselves, meets the brief's definition, pays the full 2% fee, and gets nothing; the 1% that would have been their reward joins the permanently locked surplus. This is documented in README.md and the launch.json notes as a deliberate defence against hookData impersonation, and a v4 callback cannot authenticate a foreign router's final recipient, so it is a scope decision for the requester rather than a code defect. Recorded so the requester confirms that buyers on standard Uniswap front-ends earning no rewards is acceptable.","line":126,"path":"src/SIMDTESTHook.sol","reproduction":"HookFixture local setup. alice calls PoolSwapTest.swap(key, SwapParams(IMD < token, -100e18, limit), TestSettings(false,false), abi.encode(alice)), so alice is both payer and recipient. Observed: hook IMD assets +2e18, pending(alice) = 0, owedTotal = 0. The same trade via rewardRouter.swap(true, -100e18, 100e18, 0, limit, now) records pending(alice) = 1e18. Reproduced in a scratch test (test_foreignRouterSelfBuyEarnsNothing).","severity":"info","snippet":"        if (sender == address(rewardRouter) && pairedDelta < 0 && tokenDelta > 0 && hookData.length == 32) {","title":"Buys where the recipient is the buyer through any router other than rewardRouter pay the 2% fee but earn no reward"},{"citation":"resolved","description":"From audit_economics. Every swap pays 2% of the IMD side to the hook; only qualifying reward-router buys record 1% back as rewards. The hook has no function that moves IMD anywhere except to a recorded beneficiary (_pay) or to itself (collectFees), and there is no owner or sweep by design, so the difference is unrecoverable forever. This is consistent with the brief ('accruing fees in the hook contract balance', 'no owner, no admin functions') and is not a defect; recorded so the requester chooses it knowingly.","line":136,"path":"src/SIMDTESTHook.sol","reproduction":"HookFixture local setup. alice: router.swap(true, -100e18, 100e18, 0, limit, now) then router.swap(false, -100e18, 100e18, 0, limit, now), then claimRewards(). Observed: owedTotal = 0 and the hook holds 2975018722811335618 wei IMD (2e18 buy fee + ~1.975e18 sell fee - 1e18 reward). No entry point can reduce that balance. Reproduced in a scratch test (test_surplusLocked).","severity":"info","snippet":"        if (fee != 0) _accrueFee(fee);","title":"Half of every qualifying buy fee and all sell, sub-threshold and foreign-router fees are permanently locked in the hook with no recipient"},{"citation":"resolved","description":"From audit_math. The brief writes 'minBuyUnits = 10 IMD units' and 'at least minBuyUnits (10)'. The implementation, README and launch.json notes read this as 10 whole IMD (10e18 base units), so the getter returns 10000000000000000000, not 10. The reading is the sensible one for an 18-decimal token and is documented, but it is the one threshold assumption the brief does not settle and it changes which buys earn rewards by eighteen orders of magnitude. Recorded so the author confirms the intended scale.","line":27,"path":"src/SIMDTESTHook.sol","reproduction":"Call SIMDTESTHook.minBuyUnits(): returns 10000000000000000000. test/Hook.t.sol test_thresholdAndDeferredClaims: an exact-input buy of 9999999999999999999 wei records pending(alice) = 0; a buy of 10000000000000000000 wei records 100000000000000000. If the brief meant 10 base units, the first buy should record 99999999999999999.","severity":"info","snippet":"    uint256 public constant minBuyUnits = 10 ether;","title":"minBuyUnits() returns 10e18, interpreting the brief's '10 IMD units' as ten whole IMD rather than the literal 10"}],"hash":"e01c75628d343990e0f5ee9ec87874a589516b49c7216d42413f736cf2f8fbfc","nodeId":"6d21f047-9121-4dcd-a663-de4ccda7630f","outcome":"completed","summary":"Review complete. The findings file is written at `.imd-findings.json` with 6 findings and 16 coverage rows, every snippet verified against the tree.\n\n**What I did.** Built the project and ran the delivered suite (57 passed, the fork suite skipped offline). Reproduced every specialist lead with scratch tests under `test/scratch/`, ran the economics specialist's attached proof, and read the hook, router, token and v4 library code myself.\n\n**Findings kept, after merging duplicates:**\n\n1. **Low. Mid-swap take breaks pay-first routers.** A router that syncs IMD, transfers, swaps, then settles reverts with CurrencyNotSettled on every buy because afterSwap takes the fee out of the manager inside that window. The specialist's proof fails on this code for that reason, and it is attached.\n2. **Low. beforeSwap overflow on huge exact-output sells.** Two specialists reported the same root cause. A type(int256).max exact-output sell panics in the preview add, while the same request fills partially on a hookless pool. I wrote a self-contained proof that fails with the exact WrappedError.\n3. **Low. Exact-output buy fee asymmetry.** Three specialists reported this. Same gross spend of about 103.29 IMD pays 2.025 IMD via exact output and 2.066 IMD via exact input. Reproduced with logged numbers.\n4. **Info.** Self-recipient buys through foreign routers pay the fee and earn no reward. Documented design boundary.\n5. **Info.** Unallocated fee surplus is permanently locked. Consistent with the brief.\n6. **Info.** minBuyUnits is 10e18, an interpretation the brief leaves open.\n\n**Nothing more severe surfaced.** I traced caller checks, delta accounting, reentrancy guards, the reverting preview, the ERC-6909 fallback and solvency of rewards against fees on every path, and all hold. Of the 12 entry points, 10 hold and 2 carry findings. The one gap I could not close is the mainnet fork rehearsal, which needs an RPC this environment does not have, and it is recorded as unreached.","treeHash":null,"usage":{"cachedInputTokens":1199213,"inputTokens":290,"model":"claude-fable-5-1","outputTokens":46572,"runtime":"claude","turns":36,"wallClockMs":660937}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"b273d407784470b4","findings":[{"citation":"resolved","description":"Flow gap (execution x periphery). _accrueFee first tries poolManager.take, which transfers the fee's IMD out of the manager immediately, inside the swap. v4 settlement is sync -> transfer -> settle, and settle credits the payer with balanceNow - reservesAtSync. v4 does not require the swap to happen outside that window; a prepaying router that does sync(IMD); transfer(100); swap(); settle() is a legal ordering and the manager's own CurrencyReserves logic supports it. With this hook, the take inside afterSwap reduces the manager's IMD balance by the fee between the router's sync and its settle, so settle credits only 98 IMD against a 100 IMD debit (98 pool input + 2 hook delta). The unlock then reverts with CurrencyNotSettled. The matching sell succeeds, because the take moves IMD while the synced currency is SIMDTEST. The brief says the hook never reverts a swap; here every IMD-input swap (exact-input and exact-output buy) through such a router reverts, while sells go through, and the reward router and settle-after routers are unaffected. No funds are lost, hence low. The existing fallback already shows the fix: mint the ERC-6909 claim instead of taking (always, or whenever TransientStateLibrary.getSyncedCurrency(poolManager) == IMD), and let collectFees redeem it as it does today. The mainnet IMD at 0xd34a...63b7 was checked over RPC and sourcify: it is a LayerZero OFT with plain ERC-20 transfers, so the take itself does not fail there and the claim fallback is only reached on a token-only manager.","line":208,"path":"src/SIMDTESTHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolModifyLiquidityTest} from \"v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.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 {SIMDTEST} from \"src/SIMDTEST.sol\";\nimport {SIMDTESTHook} from \"src/SIMDTESTHook.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\n\ncontract PlainERC20 is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {}\n    function mint(address to, uint256 amount) external { _mint(to, amount); }\n}\n\n/// @dev A router that pays before swapping: sync -> transfer -> swap -> settle. v4 only requires that\n/// the balance change between sync and settle equals the payment; the hook's take breaks that.\ncontract PrepayRouter is IUnlockCallback {\n    IPoolManager immutable manager;\n    constructor(IPoolManager m) { manager = m; }\n\n    function swap(PoolKey memory key, SwapParams memory p, uint256 pay) external returns (BalanceDelta d) {\n        Currency input = p.zeroForOne ? key.currency0 : key.currency1;\n        IERC20(Currency.unwrap(input)).transferFrom(msg.sender, address(this), pay);\n        d = abi.decode(manager.unlock(abi.encode(msg.sender, key, p, pay)), (BalanceDelta));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (address user, PoolKey memory key, SwapParams memory p, uint256 pay) =\n            abi.decode(data, (address, PoolKey, SwapParams, uint256));\n        Currency input = p.zeroForOne ? key.currency0 : key.currency1;\n        Currency output = p.zeroForOne ? key.currency1 : key.currency0;\n        manager.sync(input);\n        IERC20(Currency.unwrap(input)).transfer(address(manager), pay);\n        BalanceDelta d = manager.swap(key, p, \"\");\n        manager.settle();\n        int128 inD = p.zeroForOne ? d.amount0() : d.amount1();\n        int128 outD = p.zeroForOne ? d.amount1() : d.amount0();\n        uint256 spent = uint256(-int256(inD));\n        if (pay > spent) manager.take(input, user, pay - spent);\n        manager.take(output, user, uint128(outD));\n        return abi.encode(d);\n    }\n}\n\ncontract PrepaidRouterProofTest is Test {\n    address constant IMD = 0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7;\n    uint160 constant Q96 = 79228162514264337593543950336;\n\n    PoolManager manager;\n    SIMDTEST token;\n    SIMDTESTHook hook;\n    PoolKey key;\n    PrepayRouter router;\n    address alice = makeAddr(\"alice\");\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        vm.etch(IMD, address(new PlainERC20()).code);\n        token = new SIMDTEST();\n        hook = _deployHook();\n        key = hook.rewardRouter().poolKey();\n        manager.initialize(key, Q96);\n        PoolModifyLiquidityTest lp = new PoolModifyLiquidityTest(manager);\n        PlainERC20(IMD).mint(address(this), 100_000_000 ether);\n        IERC20(IMD).approve(address(lp), type(uint256).max);\n        token.approve(address(lp), type(uint256).max);\n        lp.modifyLiquidity(key, ModifyLiquidityParams(-600, 600, 10_000_000 ether, 0), \"\");\n        router = new PrepayRouter(manager);\n        PlainERC20(IMD).mint(alice, 1_000 ether);\n        token.transfer(alice, 1_000 ether);\n        vm.startPrank(alice);\n        IERC20(IMD).approve(address(router), type(uint256).max);\n        token.approve(address(router), type(uint256).max);\n        vm.stopPrank();\n    }\n\n    function _deployHook() internal returns (SIMDTESTHook) {\n        bytes memory code = abi.encodePacked(type(SIMDTESTHook).creationCode, abi.encode(manager, token));\n        bytes32 hash = keccak256(code);\n        for (uint256 i; i < 200_000; ++i) {\n            bytes32 salt = bytes32(i);\n            address at =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, hash)))));\n            if (!HookFlags.matches(at, HookFlags.FLAGS)) continue;\n            address deployed;\n            assembly (\"memory-safe\") { deployed := create2(0, add(code, 32), mload(code), salt) }\n            require(deployed == at, \"mined deployment failed\");\n            return SIMDTESTHook(deployed);\n        }\n        revert(\"salt not found\");\n    }\n\n    function _limit(bool zeroForOne) internal pure returns (uint160) {\n        return zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1;\n    }\n\n    /// A sell (token in) through the prepaying router works: the hook's take moves IMD, not the synced token.\n    function test_prepaidSellSucceeds() public {\n        bool zeroForOne = address(token) < IMD;\n        vm.prank(alice);\n        router.swap(key, SwapParams(zeroForOne, -100 ether, _limit(zeroForOne)), 100 ether);\n        assertEq(IERC20(IMD).balanceOf(address(hook)), IERC20(IMD).balanceOf(address(hook)));\n    }\n\n    /// The same router buying (IMD in) reverts: afterSwap takes the 2 IMD fee out of the manager\n    /// between the router's sync and settle, so settle credits only 98 of the 100 IMD paid.\n    function test_prepaidExactInputBuySucceeds() public {\n        bool zeroForOne = IMD < address(token);\n        uint256 before = token.balanceOf(alice);\n        vm.prank(alice);\n        router.swap(key, SwapParams(zeroForOne, -100 ether, _limit(zeroForOne)), 100 ether);\n        assertGt(token.balanceOf(alice), before, \"buyer received no SIMDTEST\");\n        assertEq(IERC20(IMD).balanceOf(alice), 900 ether);\n        assertEq(IERC20(IMD).balanceOf(address(hook)) + manager.balanceOf(address(hook), uint160(IMD)), 2 ether);\n    }\n}","reproduction":"State: local PoolManager, SIMDTEST/IMD pool initialised at 1:1 with 10,000,000 units of full-range liquidity (-600..600), hook deployed at a mined 0x20cc address. Router: a contract whose unlockCallback does manager.sync(input); IERC20(input).transfer(manager, pay); manager.swap(key, params, \"\"); manager.settle(); then takes output and refund. Call 1 (sell): alice swaps 100 SIMDTEST exact-input (zeroForOne = token < IMD). Expected and actual: succeeds, hook holds 2% of the IMD output. Call 2 (buy): alice swaps 100 IMD exact-input (zeroForOne = IMD < token), pay = 100e18. Expected: succeeds, alice receives SIMDTEST, hook holds 2e18 IMD fee. Actual: PoolManager.unlock reverts with CurrencyNotSettled() (0x5212cba1) because afterSwap's take moved 2e18 IMD out of the manager between sync and settle, so settle credited 98e18 while the router's IMD delta was -100e18. test/scratch/PrepaidRouterProof.t.sol: test_prepaidExactInputBuySucceeds fails, test_prepaidSellSucceeds passes.","severity":"low","snippet":"        try poolManager.take(Currency.wrap(pairedCurrency), address(this), fee) {\n            emit FeeAccrued(fee, false);\n        } catch {\n            poolManager.mint(address(this), uint160(pairedCurrency), fee);","title":"afterSwap takes the IMD fee out of the PoolManager mid-swap, so any router that syncs IMD before swapping cannot buy (CurrencyNotSettled)"},{"citation":"resolved","description":"Economic observation, consistent with the brief (fees accrue in the hook, no owner, no sweep) and therefore not a defect; recorded so the requester chooses it knowingly. Every qualifying buy pays floor(spent/50) in fees and records floor(spent/100) in rewards; sells, sub-threshold buys and swaps through any router other than rewardRouter pay the same 2% and record nothing. The hook has no function that can move IMD anywhere except to a recorded beneficiary (_pay) or to itself (collectFees), so the difference is unrecoverable for ever. With the mainnet IMD being a bridged OFT whose owner can only edit metadata, nothing external can recover it either.","line":130,"path":"src/SIMDTESTHook.sol","reproduction":"Local pool as in the delivered fixture. alice: router.swap(true, -100e18, 100e18, 0, limit, now) then router.swap(false, -100e18, 100e18, 0, limit, now). Fees accrued: 2e18 on the buy plus ~1.9e18 on the sell (2% of the IMD output). Rewards recorded: 1e18. After alice claims, the hook holds ~2.9e18 IMD and owedTotal is 0; no entry point can reduce that balance. test/Hook.t.sol test_allFourSwapModes already shows the fee and reward amounts; there is no call sequence that lowers the hook's IMD balance below assets - owedTotal.","severity":"info","snippet":"                uint256 reward = spent / 100;\n                pending[buyer] += reward;\n                owedTotal += reward;","title":"Half of every buy fee and all sell/foreign-router fees are permanently locked in the hook with no recipient"},{"citation":"resolved","description":"Invariant note (path divergence), not exploitable beyond choosing the cheaper mode. For an exact-input buy the fee is floor(budget/50), i.e. 2% of the gross IMD debited. For an exact-output buy the IMD side is unspecified, so the fee is floor(poolInput/50) and the gross debit is poolInput + fee; the fee is 2%/1.02 = 1.96% of gross. The reward is 1% of gross in both modes, so fee >= reward still holds on every path (verified with the delivered fuzz suites and a 160-run x 60-depth mixed-router invariant campaign including partial fills and liquidity changes). The README documents both formulas; this row only makes the 0.04-percentage-point difference explicit.","line":122,"path":"src/SIMDTESTHook.sol","reproduction":"Local pool at 1:1 with deep liquidity. (a) alice: router.swap(true, -102e18, 102e18, 0, limit, now): fee = 2.04e18, pool input = 99.96e18, reward = 1.02e18. (b) bob: router.swap(true, X, 300e18, 0, limit, now) with X chosen so the pool input is 100e18: fee = 2.00e18, gross spend = 102e18, reward = 1.02e18. Same gross spend and reward, 0.04e18 less fee on path (b). Expected if symmetric: 2.04e18 on both; actual: 2.04e18 vs 2.00e18.","severity":"info","snippet":"        uint256 fee = specified ? _feeForSwap : amount / 50;","title":"Exact-output buys are charged 2% of the pool input (1.96% of gross spend) while exact-input buys are charged 2% of gross: a small, documented path divergence"}],"hash":"e28e149636baa7fe1c0fe6fa80bb17499aeecc30d2406a983da704b59749450c","nodeId":"f1b5eaae-9187-4fb4-ade8-e373feaaf4d6","outcome":"completed","summary":"Review complete. The findings are in `.imd-findings.json` at the repository root, the working tree is otherwise unchanged, and my scratch tests have been removed.\n\n**Outcome.** The economics of SIMDTESTHook hold up. Fees back rewards on every path, the solvency and coupling invariants survive both the delivered campaigns and my own mixed-router campaign, and the mainnet IMD token behaves like a plain ERC-20. I found one low-severity flow-gap defect and recorded two informational economics notes.\n\n**Finding 1, low, with proof.** The hook takes its IMD fee out of the PoolManager inside `afterSwap`. A router that follows the legal ordering sync, transfer, swap, settle gets credited only 98 of the 100 IMD it paid, because the take moved 2 IMD out of the manager between its sync and its settle. Every buy through such a router reverts with `CurrencyNotSettled`, while sells succeed. No funds are lost. The self-contained proof under the finding fails on the current code and documents the fix: mint the ERC-6909 claim (already the fallback path) instead of taking, at least whenever IMD is the synced currency.\n\n**Info notes.** Half of every buy fee plus all sell and foreign-router fees are locked in the hook forever, which the brief implies but never states. Exact-output buys pay 2% of pool input, which is 1.96% of gross spend, versus 2% of gross on exact-input buys. Both are documented in the README, and neither breaks fee-covers-reward.\n\n**What I verified beyond the findings.**\n- Mainnet IMD at the paired address is a LayerZero OFT: 18 decimals, plain transfers, owner limited to metadata and bridge config. Confirmed over RPC and sourcify.\n- A 160-run, depth-60 invariant campaign interleaving the reward router and a generic router with random partial fills, liquidity changes, claims, third-party payouts and fee collection: no reverts, assets always covered owed rewards, owed always equalled the sum of pending.\n- Coverage rows cover all twelve entry points plus three invariants. Only `afterSwap` carries a finding reference; every other entry point holds.\n\n**Not reached.** No live mainnet fork run, since no RPC is configured for the verifier. The delivered fork tests skip cleanly offline, as the README states.","treeHash":null,"usage":{"cachedInputTokens":4639563,"inputTokens":802,"model":"claude-fable-5-1","outputTokens":63661,"runtime":"claude","turns":59,"wallClockMs":1213833}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"a31e321b410aaa02","findings":[{"citation":"resolved","description":"Branch-symmetry gap between the four swap modes. Exact-input buys charge floor(gross/50) (the fee is inside the budget), exact-input sells withhold floor(gross output/50), and exact-output sells deliberately gross up with N/49 (line 221: `return uint256(amountSpecified) / 49;`) so the fee is 2% of gross. Exact-output buys are the only mode where the fee is added on top of the pool input as floor(A/50), so the fee is A/50 of a gross spend of A*51/50, i.e. 1/51 = 1.961%. Two buyers who spend exactly the same IMD on the same pool therefore pay different hook fees depending only on which side they specify. This breaks the brief's 'Take a 2% fee on the paired currency side of every swap', and the shortfall goes to the buyer who picks exact output. The mirror of the exact-output sell treatment is `amount / 49` for unspecified-side buys. Impact: about 0.039% of exact-output buy volume is under-collected. No funds are lost and the hook stays solvent, so severity is low.","line":122,"path":"src/SIMDTESTHook.sol","reproduction":"Local PoolManager, HookFixture setup (liquidity of 10,000,000e18 at ticks -600..600, price 1:1). (1) alice calls router.swap(true, +100e18 /*exact-output buy of 100 SIMDTEST*/, 301e18, 0, MAX_SQRT_PRICE-1, now). Her IMD spent is 103292172162227951394 and the hook's assets increase by 2025336709063293164, which is 1.961% of the spend (floor(spent/50) would be 2065843443244559027). (2) bob calls router.swap(true, -103292172162227951394 /*exact input, same gross spend*/, ...). The hook's assets increase by 2065843443244559027 (2.000%). Expected: both pay 2% of the IMD they spend, 2065843443244559027. Actual: the exact-output buyer pays 40506734181265863 IMD less (0.0405 IMD per 103 IMD). Fix: in afterSwap, compute the unspecified-side fee for buys as amount / 49 (mirroring _specifiedFee's exact-output treatment), or document 'fee on pool amount' as the intended definition consistently for all four modes.","severity":"low","snippet":"        uint256 fee = specified ? _feeForSwap : amount / 50;","title":"Asymmetric hook fee: exact-output buys pay 1/51 (1.96%) of IMD spent while every other swap mode pays 2% of gross"},{"citation":"resolved","description":"For an exact-output sell (IMD output specified), _specifiedFee returns amountSpecified/49, and the preview adds it to amountSpecified with checked int256 arithmetic. For amountSpecified > ~0.98 * 2^255 (for example type(int256).max, a 'swap up to the price limit' exact-output request), the addition overflows and beforeSwap panics. PoolManager wraps the panic as WrappedError(hook, beforeSwap.selector, Panic(0x11), HookCallFailed), so the whole swap reverts. The same swap on an identical pool without the hook succeeds and fills to sqrtPriceLimitX96. This contradicts the brief's rule that 'The hook never reverts a swap'. Callers must pass absurd amounts to reach it, so impact is limited to such integrations: low.","line":99,"path":"src/SIMDTESTHook.sol","reproduction":"HookFixture setup, sell direction zeroForOne = !(IMD < token), price limit = Q96 -/+ 0.1%. bob calls PoolSwapTest.swap(key, SwapParams(zeroForOne, type(int256).max, limit), TestSettings(false,false), \"\"). Actual: it reverts with WrappedError(hook, 0x575e24b4, 0x4e487b71...11, 0xa9e35b2f) from line 99. Control: the same call with amountSpecified = 2**200 succeeds, and the same type(int256).max call on PoolKey(c0, c1, 12500, 60, address(0)) with identical liquidity succeeds. Expected: the hook caps or saturates the preview amount (e.g. skip the gross-up when amountSpecified > type(int256).max - fee, or compute the fee from the preview fill) and never reverts the swap.","severity":"low","snippet":"            preview.amountSpecified += int256(fee);","title":"beforeSwap overflows (panic 0x11) on a very large exact-output sell, so the hook reverts a swap that v4 accepts without the hook"},{"citation":"resolved","description":"The brief says a reward is recorded 'On each qualifying buy (swap where the recipient is the buyer)'. The hook only credits buys whose v4 `sender` is its own rewardRouter. A buyer who uses the Universal Router or any v4 router, sending output to themselves, meets the brief's definition but receives nothing. That buyer still pays the full 2% fee, which no one can ever withdraw (no owner or sweep), so the reward half of that fee is permanently stuck rather than funding rewards. This is documented in README.md and launch.json notes as a deliberate defence against hookData impersonation, and v4 callbacks cannot authenticate an external router's recipient. A fix therefore needs a scope decision, not a code change. Recorded so the requester confirms that buyers who use standard Uniswap front-ends getting no rewards is acceptable.","line":126,"path":"src/SIMDTESTHook.sol","reproduction":"HookFixture: alice calls PoolSwapTest.swap(key, SwapParams(IMD < token, -100e18, MIN/MAX limit), TestSettings(false,false), abi.encode(alice)), so alice is payer and recipient. The hook's IMD assets increase by 2e18 and pending(alice) stays 0. The same call via rewardRouter.swap(true, -100e18, 100e18, 0, limit, now) records pending(alice) = 1e18. Expected by the brief: a 1e18 reward in both cases.","severity":"info","snippet":"        if (sender == address(rewardRouter) && pairedDelta < 0 && tokenDelta > 0 && hookData.length == 32) {","title":"Trust gap: buys with recipient == buyer through any router other than rewardRouter pay the 2% fee but earn no reward, and the unrewarded 1% is locked permanently"}],"hash":"f5f0a04bf10f603961eef57f5ca1375eb6f932c4af7d3e732dc30b7d40eab5c7","nodeId":"a1e0c691-c6ec-46cc-a651-07dd330ae4f2","outcome":"completed","summary":"The permissions audit is done. I found nothing critical, high or medium, and nothing in the access-control model can be bypassed. There are two low findings and one info note, all reproduced with scratch Foundry tests. The results are in `.imd-findings.json` with a coverage row for each of the 12 entry points, plus `beforeInitialize` and two invariants. I changed no tracked files and deleted the scratch tests afterwards. The existing suite passes: 57 tests pass and 1 (the mainnet fork test) is skipped, as designed, because there is no network.\n\n**Findings**\n1. **Low – exact-output buys pay a smaller fee** (`src/SIMDTESTHook.sol:122`). Three of the four swap modes charge 2% of the gross IMD spent. Exact-output buys add `amount / 50` on top of the pool input, which works out to 1/51 ≈ 1.96%.\n   - **Reproduction:** an exact-output buy of 100 SIMDTEST spends 103.292 IMD and pays a 2.0253 IMD fee. An exact-input buy with the same 103.292 IMD spend pays 2.0658 IMD.\n   - **Fix:** charge `amount / 49` on that path, which matches how exact-output sells already work (line 221).\n2. **Low – the hook can revert a valid swap** (`src/SIMDTESTHook.sol:99`). An exact-output sell with `amountSpecified = type(int256).max` and a price limit overflows on `preview.amountSpecified += int256(fee)`, and the whole swap reverts. The same swap succeeds on an identical pool without the hook. This breaks the rule that the hook never reverts a swap, but only for absurdly large requests.\n3. **Info – the trust gap in buyer rewards** (`src/SIMDTESTHook.sol:126`). Rewards are only recorded for buys made through the hook's own `rewardRouter`. Someone who buys through any other router and receives the tokens themselves still pays the 2% fee but gets no reward, and the unused half of that fee stays locked in the hook forever. The README documents this as deliberate, and a v4 callback can't verify who an outside router pays out to, so changing it is a design decision rather than a bug fix.\n\n**What I checked and found sound**\n- **Access control:** every hook callback accepts only the PoolManager. `quoteSwap` accepts only the hook itself. Both `unlockCallback`s also require their own contract's reentrancy guard to be active. There is no owner, setter or initializer, and every address is fixed at deployment.\n- **Trust gaps:**\n  - The router only takes tokens from whoever calls it, so it can't be used to spend someone else's approval.\n  - Fake `hookData` from other routers is ignored, and `payRecorded` can't redirect a payment.\n  - `collectFees` only turns the hook's own fee claims into IMD held by the hook, so the caller can't take anything.\n- **Asymmetry:** `claimRewards` and `payRecorded` use the same checks and update the same records. `pending` and `owedTotal` only increase when a buy is recorded and only decrease when a reward is paid. The fee always covers the reward, so the hook can always pay what it owes.\n- **Static-analysis leads:** the ignored `settle()` return value at `src/SIMDTESTRouter.sol:96` is harmless, because the exact amount is transferred just before it. I also checked the reentrancy and zero-address warnings and found nothing reachable.\n\nI reviewed every entry point and left none unreached. I did not look at the deploy script beyond a quick read, or at the gas cost of the swap preview the hook runs before each swap.","treeHash":null,"usage":{"cachedInputTokens":1024176,"inputTokens":26,"model":"claude-opus-5-5","outputTokens":32315,"runtime":"claude","turns":14,"wallClockMs":350761}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"93ca4a1020037bf1","findings":[{"citation":"resolved","description":"Execution trace of beforeSwap when the IMD side is specified on an exact-output sell: _specifiedFee returns amountSpecified/49, and the preview adds it to amountSpecified with checked int256 arithmetic before any cap is applied. For amountSpecified > type(int256).max*49/50 the addition overflows (Panic 0x11). The hook then reverts the swap, and the PoolManager wraps the panic in WrappedError. Without the hook, the same PoolManager swap partially fills to the price limit and succeeds, because v4 Pool.swap accepts any non-zero int256 amount and stops at sqrtPriceLimitX96. This breaks the brief's mandatory rule that the hook never reverts a swap. The exact-input branch is safe because _specifiedFee handles type(int256).min and the fee is added toward zero. The bundled router and V4Router (uint128 amounts) cannot reach this input. Only a direct PoolManager integrator using 'max output up to the price limit' semantics hits it, so severity is low. Fix: saturate the preview amount, e.g. compute it as min(amountSpecified, type(int256).max - fee), or cap fee before adding it.","line":99,"path":"src/SIMDTESTHook.sol","reproduction":"Local setup from test/helpers/HookFixture.sol (_localSetup: token < IMD, so currency0 = SIMDTEST, liquidity -600..600). A plain unlock-callback contract holding SIMDTEST calls manager.swap(key, SwapParams({zeroForOne:true, amountSpecified:int256(1<<250), sqrtPriceLimitX96: Q96*99/100}), \"\"). This succeeds: the preview returns SwapQuote and the swap partially fills at the limit. The same call with amountSpecified = type(int256).max reverts with WrappedError(hook, beforeSwap selector, Panic(0x11)) from SIMDTESTHook.sol:99. Expected: a partial fill at the price limit, with the fee capped by filled/50. Actual: the swap reverts. Verified with a scratch Foundry test, now deleted.","severity":"low","snippet":"            preview.amountSpecified += int256(fee);","title":"beforeSwap preview overflows on very large exact-output sells, reverting a swap the pool would partially fill"}],"hash":"f9701bfdb008da425cf7d4c4fd097d0ada504369f48c58edc53ae53abc72dd5a","nodeId":"484c06be-6821-495a-833f-268ba3665ec1","outcome":"completed","summary":"I found one defect, rated low, and recorded it in `.imd-findings.json` with a coverage row for each of the 12 entry points. Nothing else in the code broke under the Execution Trace, Periphery and First Principles checks.\n\n**Finding 1 (low): `src/SIMDTESTHook.sol:99`, `preview.amountSpecified += int256(fee);`**\n- **Trigger:** an exact-output sell (IMD out specified) sent straight to the PoolManager with `amountSpecified = type(int256).max`, or anything above about 98% of it.\n- **What goes wrong:** the fee is `amountSpecified/49`, and adding it overflows before the fee cap applies. The hook panics and the whole swap reverts, which breaks the rule that the hook never reverts a swap.\n- **Without the hook:** the same swap partially fills up to the price limit and succeeds.\n- **Proof:** I ran a scratch test against the real v4 PoolManager. A 2^250 exact-output sell partially filled fine; `type(int256).max` reverted with `WrappedError(…, Panic 0x11)` from line 99.\n- **Why only low:** neither the bundled router nor V4Router (which caps amounts at uint128) can send that input.\n- **Fix:** cap the preview amount (or the fee) so the sum can't overflow.\n\n**What I checked and found sound:**\n- **Fee preview on partial fills:** the reverting preview gives the same fill as the real swap even after the fee is reduced to the capped amount. I worked through the math for exact-input buys and exact-output sells.\n- **Fee accounting:** fees are charged and settled correctly through `beforeSwap` (`_feeForSwap`) and `afterSwap` (`amount/50`). If the PoolManager is short of IMD, `take` falls back to `mint` correctly.\n- **Rewards:** only buys made through the hook's own router earn a reward, and only when the buyer spends at least 10e18 IMD (fee included). Spoofed `hookData` from other routers is ignored. Each reward (1%) is covered by a larger fee (about 1.96% or more) taken in the same swap, so the hook can always pay what it owes.\n- **Claims:** `claimRewards`, `payRecorded` and `collectFees` refuse to run while the PoolManager is unlocked. They clear the debt before paying and can't be re-entered, and `payRecorded` can't redirect a payment.\n- **Router:** it only pulls tokens from the caller. The caller is always the one who pays, receives the output and gets the reward. Its slippage, deadline and refund checks are correct. Slither flagged the ignored `settle()` return; it is harmless with a standard IMD token.\n- **Real IMD token:** I checked it on mainnet. It is a plain LayerZero OFT ERC-20 with 18 decimals and no pause or blacklist functions, and the PoolManager already holds about 212k IMD.\n- **Token and size:** the token is a stock OpenZeppelin ERC-20. The hook's creation code is 13,160 bytes, within the EIP-3860 limit.\n\nI didn't run the mainnet fork tests. I deleted my scratch test, and no tracked files were changed.","treeHash":null,"usage":{"cachedInputTokens":829282,"inputTokens":22,"model":"claude-opus-5-5","outputTokens":24074,"runtime":"claude","turns":12,"wallClockMs":267794}}],"verification":[{"checks":[{"durationMs":5305,"exitCode":0,"name":"build","output":"Compiling 103 files with Solc 0.8.26\nSolc 0.8.26 finished in 5.18s\nCompiler run successful!\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTRouter.sol:37:60\n   │\n37 │     constructor(IPoolManager manager, address launchToken, address pair) {\n   │                                                            ━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTHook.sol:53:39\n   │\n53 │     constructor(IPoolManager manager, address launchToken) {\n   │                                       ━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTRouter.sol:37:39\n   │\n37 │     constructor(IPoolManager manager, address launchToken, address pair) {\n   │                                       ━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/SIMDTESTRouter.sol:59:13\n   │\n59 │         if (block.timestamp > deadline) revert Expired();\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/SIMDTESTRouter.sol:63:36\n   │\n63 │         if (amountSpecified < 0 && uint256(-amountSpecified) != maxInput) revert InvalidSwap();\n   │                                    ━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/SIMDTESTRouter.sol:87:25\n   │\n87 │         uint256 spent = uint256(-int256(inputDelta));\n   │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/SIMDTESTRouter.sol:88:28\n   │\n88 │         uint256 received = uint128(outputDelta);\n   │                            ━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint128' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/SIMDTESTRouter.sol:90:56\n   │\n90 │         if (r.params.amountSpecified > 0 && received < uint256(r.params.amountSpecified)) revert Slippage();\n   │                                                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n   ╭▸ src/SIMDTESTRouter.sol:96:13\n   │\n96 │             poolManager.settle();\n   │             ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/SIMDTESTHook.sol:99:40\n   │\n99 │             preview.amountSpecified += int256(fee);\n   │                                        ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:121:44\n    │\n121 │         uint256 amount = pairedDelta < 0 ? uint256(-int256(pairedDelta)) : uint128(pairedDelta);\n    │                                            ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:121:76\n    │\n121 │         uint256 amount = pairedDelta < 0 ? uint256(-int256(pairedDelta)) : uint128(pairedDelta);\n    │                                                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:151:34\n    │\n151 │         if (data.length != 36 || bytes4(data) != SwapQuote.selector) {\n    │                                  ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'bytes4' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:159:29\n    │\n159 │         return paired < 0 ? uint256(-int256(paired)) : uint128(paired);\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:159:56\n    │\n159 │         return paired < 0 ? uint256(-int256(paired)) : uint128(paired);\n    │                                                        ━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:196:9\n    │\n196 │         emit RewardPaid(buyer, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:202:9\n    │\n202 │         emit FeesCollected(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/SIMDTESTHook.sol:200:13\n    │\n200 │         if (poolManager.balanceOf(address(this), uint160(pairedCurrency)) == 0) return 0;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:209:13\n    │\n209 │             emit FeeAccrued(fee, false);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:212:13\n    │\n212 │             emit FeeAccrued(fee, true);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:218:21\n    │\n218 │             return (uint256(-(amountSpecified + 1)) + 1) / 50;\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:221:16\n    │\n221 │         return uint256(amountSpecified) / 49;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\n","passed":true},{"durationMs":2952,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 362.19µs (0.00ns CPU time)\n\nRan 16 tests for test/ReverseOrder.t.sol:ReverseOrderTest\n[PASS] testFuzz_feesRewardsAndClaims(uint96,bool,bool) (runs: 1000, μ: 418420, ~: 401583)\n[PASS] test_allCallbacksRestricted() (gas: 178716)\n[PASS] test_allFourSwapModes() (gas: 1145821)\n[PASS] test_externalQuoteCannotBeUsedAsARouter() (gas: 41133)\n[PASS] test_malformedHookDataNeverBlocksOtherRouter() (gas: 291933)\n[PASS] test_partialFillBelowThresholdDoesNotEarnReward() (gas: 521748)\n[PASS] test_partialInputFeeUsesActualFill() (gas: 433654)\n[PASS] test_partialOutputSellDoesNotOverchargeOrRevert() (gas: 402250)\n[PASS] test_payRecordedCannotRedirect() (gas: 467879)\n[PASS] test_permissionsAndSize() (gas: 3080272)\n[PASS] test_routerCannotSpendAnApprovedVictim() (gas: 337815)\n[PASS] test_routerSlippageAndDeadlineRollback() (gas: 612838)\n[PASS] test_thresholdAndDeferredClaims() (gas: 940643)\n[PASS] test_tinyTradeRounding() (gas: 302947)\n[PASS] test_untrustedRouterCannotSpoofBuyerAndStillSwaps() (gas: 394307)\n[PASS] test_wrongPoolCannotInitialize() (gas: 76146)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 179.84ms (181.69ms CPU time)\n\nRan 16 tests for test/Hook.t.sol:HookTest\n[PASS] testFuzz_feesRewardsAndClaims(uint96,bool,bool) (runs: 1000, μ: 417685, ~: 396750)\n[PASS] test_allCallbacksRestricted() (gas: 179087)\n[PASS] test_allFourSwapModes() (gas: 1144212)\n[PASS] test_externalQuoteCannotBeUsedAsARouter() (gas: 41312)\n[PASS] test_malformedHookDataNeverBlocksOtherRouter() (gas: 278850)\n[PASS] test_partialFillBelowThresholdDoesNotEarnReward() (gas: 509505)\n[PASS] test_partialInputFeeUsesActualFill() (gas: 421423)\n[PASS] test_partialOutputSellDoesNotOverchargeOrRevert() (gas: 414313)\n[PASS] test_payRecordedCannotRedirect() (gas: 454843)\n[PASS] test_permissionsAndSize() (gas: 3080272)\n[PASS] test_routerCannotSpendAnApprovedVictim() (gas: 324779)\n[PASS] test_routerSlippageAndDeadlineRollback() (gas: 594859)\n[PASS] test_thresholdAndDeferredClaims() (gas: 924467)\n[PASS] test_tinyTradeRounding() (gas: 285273)\n[PASS] test_untrustedRouterCannotSpoofBuyerAndStillSwaps() (gas: 381224)\n[PASS] test_wrongPoolCannotInitialize() (gas: 76102)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 189.36ms (194.97ms CPU time)\n\nRan 3 tests for test/Token.t.sol:TokenTest\n[PASS] testFuzz_allowanceCannotBeExceeded(uint96) (runs: 1000, μ: 212760, ~: 213731)\n[PASS] test_entireSupplyToDeployerNoTax() (gas: 121034)\n[PASS] test_noAdminOrMint() (gas: 100841)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 190.21ms (189.98ms CPU time)\n\nRan 5 tests for test/Security.t.sol:SecurityTest\n[PASS] test_claimCannotReenterOrPayTwice() (gas: 1344654)\n[PASS] test_failedFeeTransferDoesNotBlockSwap() (gas: 1602776)\n[PASS] test_feeTransferCannotTriggerRewardDuringSwap() (gas: 1295328)\n[PASS] test_freshTokenOnlyPoolAccruesClaimsThenPays() (gas: 15467568)\n[PASS] test_noLiquidityNoFeeNoReward() (gas: 1078004)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 195.77ms (15.31ms CPU time)\n\nRan 2 tests for test/FillAccounting.t.sol:ReverseFillAccountingTest\n[PASS] testFuzz_partialFillsChargeOnlyActualIMD(uint96,uint80,uint16,bool,bool) (runs: 1000, μ: 586494, ~: 564050)\n[PASS] testFuzz_previewDoesNotDoubleMovePriceOrLPFees(uint96,bool) (runs: 1000, μ: 881325, ~: 872708)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 195.87ms (367.78ms CPU time)\n\nRan 2 tests for test/FillAccounting.t.sol:FillAccountingTest\n[PASS] testFuzz_partialFillsChargeOnlyActualIMD(uint96,uint80,uint16,bool,bool) (runs: 1000, μ: 586318, ~: 573978)\n[PASS] testFuzz_previewDoesNotDoubleMovePriceOrLPFees(uint96,bool) (runs: 1000, μ: 878350, ~: 886512)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 195.87ms (379.45ms CPU time)\n\nRan 11 tests for test/RewardFailurePaths.t.sol:RewardFailurePathsTest\n[PASS] testFuzz_exactOutputPartialFillRollsBackEverything(bool,uint96) (runs: 1000, μ: 899188, ~: 917143)\n[PASS] test_claimThenEarnAgainKeepsBuyersIndependent() (gas: 1371680)\n[PASS] test_everyPaymentEntryRejectsAnUnrelatedManagerUnlock() (gas: 521332)\n[PASS] test_failedBeneficiaryTransferRollsBackSuccessfulClaimRedemption() (gas: 1706106)\n[PASS] test_failedTokenPullDoesNotRecordFeesOrRewards() (gas: 420101)\n[PASS] test_invalidConstructorDependenciesRevertBeforeDeployment() (gas: 8289)\n[PASS] test_missingTransferReturnSupportsSwapRedemptionAndPayout() (gas: 1501494)\n[PASS] test_routerInvalidAmountsLeaveAllAccountingUnchanged() (gas: 471836)\n[PASS] test_subthresholdPurchasesDoNotAggregateIntoEligibility() (gas: 920916)\n[PASS] test_unsolicitedManagerCallbackCannotRedeemFees() (gas: 36826)\n[PASS] test_wrongCreate2PermissionBitsAreRejected() (gas: 23775)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 195.91ms (196.88ms CPU time)\n\nRan 1 test for test/Invariant.t.sol:RewardInvariantTest\n[PASS] invariant_allRewardsBackedAndAllFeesConserved() (runs: 256, calls: 8192, reverts: 0)\n\n╭---------------+----------+-------+---------+----------╮\n| Contract      | Selector | Calls | Reverts | Discards |\n+=======================================================+\n| RewardHandler | collect  | 2772  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | pay      | 2673  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | trade    | 2747  | 0       | 0        |\n╰---------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.41s (1.40s CPU time)\n\nRan 1 test for test/AccountingSequences.t.sol:AccountingSequencesTest\n[PASS] invariant_independentDebtsFeesAndSupplyAreConserved() (runs: 256, calls: 16384, reverts: 0)\n\n╭---------------------------+-----------+-------+---------+----------╮\n| Contract                  | Selector  | Calls | Reverts | Discards |\n+====================================================================+\n| AccountingSequenceHandler | collect   | 3237  | 0       | 0        |\n|---------------------------+-----------+-------+---------+----------|\n| AccountingSequenceHandler | donate    | 3291  | 0       | 0        |\n|---------------------------+-----------+-------+---------+----------|\n| AccountingSequenceHandler | failedPay | 3328  | 0       | 0        |\n|---------------------------+-----------+-------+---------+----------|\n| AccountingSequenceHandler | pay       | 3222  | 0       | 0        |\n|---------------------------+-----------+-------+---------+----------|\n| AccountingSequenceHandler | trade     | 3306  | 0       | 0        |\n╰---------------------------+-----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.87s (2.86s CPU time)\n\nRan 10 test suites in 2.87s (5.61s CPU time): 57 tests passed, 0 failed, 1 skipped (58 total tests)\n","passed":true},{"durationMs":42,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SIMDTEST.approve(address,uint256)\",\"SIMDTEST.transfer(address,uint256)\",\"SIMDTEST.transferFrom(address,address,uint256)\",\"SIMDTESTHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SIMDTESTHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SIMDTESTHook.claimRewards()\",\"SIMDTESTHook.collectFees()\",\"SIMDTESTHook.payRecorded(address)\",\"SIMDTESTHook.quoteSwap((address,address,uint24,int24,address),(bool,int256,uint160))\",\"SIMDTESTHook.unlockCallback(bytes)\",\"SIMDTESTRouter.swap(bool,int256,uint256,uint256,uint160,uint256)\",\"SIMDTESTRouter.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"README.md\":85,\"REVIEW.md\":37,\"docs/DEPENDENCIES.md\":10,\"docs/abi/SIMDTEST.json\":315,\"docs/abi/SIMDTESTHook.json\":722,\"docs/abi/SIMDTESTRouter.json\":214,\"docs/dependency-sha256.json\":207,\"foundry.toml\":26,\"launch.json\":30,\"script/Deploy.s.sol\":48,\"scripts/check-launch.py\":37,\"src/HookFlags.sol\":30,\"src/SIMDTEST.sol\":11,\"src/SIMDTESTHook.sol\":234,\"src/SIMDTESTRouter.sol\":101,\"test/AccountingSequences.t.sol\":211,\"test/FillAccounting.t.sol\":180,\"test/Hook.t.sol\":275,\"test/Invariant.t.sol\":93,\"test/MainnetFork.t.sol\":127,\"test/README.md\":35,\"test/ReverseOrder.t.sol\":22,\"test/RewardFailurePaths.t.sol\":227,\"test/Security.t.sol\":107,\"test/Token.t.sol\":63,\"test/helpers/HookFixture.sol\":132,\"test/helpers/SwapObservation.sol\":29,\"test/mocks/AdversarialIMD.sol\":37,\"test/mocks/MockERC20.sol\":14,\"test/mocks/PayoutIMD.sol\":27},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"07a5b042f81f26bb5e0a9a30e93e7953ef70afe88bb4097b565c7a5d6065ee3d","verifiedTreeHash":"d36e6968112da7d138c5a739e8991743ada4dedb","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":5110,"exitCode":0,"name":"build","output":"Compiling 95 files with Solc 0.8.26\nSolc 0.8.26 finished in 4.97s\nCompiler run successful!\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n   ╭▸ src/SIMDTESTRouter.sol:73:13\n   │\n73 │             poolManager.unlock(abi.encode(Request(key, params, msg.sender, recipient, maxInput, minOutput)));\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n   ╭▸ src/SIMDTESTRouter.sol:77:29\n   │\n77 │         uint256 collected = IFeeCollector(hook).collectFees();\n   │                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n   ╭▸ src/SIMDTESTRouter.sol:78:9\n   │\n78 │         emit SwapExecuted(msg.sender, recipient, spent, received, collected);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n   ╭▸ src/SIMDTESTRouter.sol:70:13\n   │\n70 │         if (input.balanceOf(address(this)) - balanceBefore != maxInput) revert UnsupportedToken();\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/SIMDTESTRouter.sol:91:25\n   │\n91 │         uint256 spent = uint256(-int256(inputDelta));\n   │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/SIMDTESTRouter.sol:92:28\n   │\n92 │         uint256 received = uint256(int256(outputDelta));\n   │                            ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTRouter.sol:109:64\n    │\n109 │                 || (r.params.amountSpecified > 0 && received < uint256(r.params.amountSpecified))\n    │                                                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:104:63\n    │\n104 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(fee)), 0), 0);\n    │                                                               ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:104:70\n    │\n104 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(fee)), 0), 0);\n    │                                                                      ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:117:44\n    │\n117 │         uint256 amount = pairedDelta < 0 ? uint256(-int256(pairedDelta)) : uint256(int256(pairedDelta));\n    │                                            ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:117:76\n    │\n117 │         uint256 amount = pairedDelta < 0 ? uint256(-int256(pairedDelta)) : uint256(int256(pairedDelta));\n    │                                                                            ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:135:72\n    │\n135 │         return (IHooks.afterSwap.selector, pairSpecified ? int128(0) : int128(int256(fee)));\n    │                                                                        ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:135:79\n    │\n135 │         return (IHooks.afterSwap.selector, pairSpecified ? int128(0) : int128(int256(fee)));\n    │                                                                               ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:143:30\n    │\n143 │             uint256 budget = uint256(-(params.amountSpecified + 1)) + 1;\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:144:63\n    │\n144 │             quoted.amountSpecified = params.amountSpecified + int256(budget / 50);\n    │                                                               ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:146:29\n    │\n146 │             uint256 extra = uint256(params.amountSpecified) / 49;\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:147:28\n    │\n147 │             uint256 room = uint256(type(int256).max - params.amountSpecified);\n    │                            ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:148:39\n    │\n148 │             quoted.amountSpecified += int256(extra > room ? room : extra);\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:155:26\n    │\n155 │         uint256 budget = uint256(-(params.amountSpecified + 1)) + 1;\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:166:40\n    │\n166 │             if (reason.length == 36 && bytes4(reason) == QuotedAmount.selector) {\n    │                                        ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'bytes4' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:182:47\n    │\n182 │         revert QuotedAmount(pairedDelta < 0 ? uint256(-int256(pairedDelta)) : uint256(int256(pairedDelta)));\n    │                                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:182:79\n    │\n182 │         revert QuotedAmount(pairedDelta < 0 ? uint256(-int256(pairedDelta)) : uint256(int256(pairedDelta)));\n    │                                                                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\n","passed":true},{"durationMs":1451,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 1.48ms (0.00ns CPU time)\n\nRan 3 tests for test/FreshManager.t.sol:FreshManagerTest\n[PASS] test_FirstExactInputBuyFundsFeesAndClaims() (gas: 622213)\n[PASS] test_FirstExactOutputBuyFundsFeesAndClaims() (gas: 503172)\n[PASS] test_GenericRouterAccruesRedeemableClaimsWithoutCash() (gas: 567684)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 9.32ms (1.69ms CPU time)\n\nRan 16 tests for test/CurrencyOrder.t.sol:HookPair0Test\n[PASS] testFuzz_AllSwapModes(uint96,bool,bool) (runs: 256, μ: 407493, ~: 429648)\n[PASS] testFuzz_PartialFills(uint96,bool,bool) (runs: 256, μ: 415583, ~: 413119)\n[PASS] test_AnyonePaysOnlyBeneficiary() (gas: 824000)\n[PASS] test_CallbackAuthentication() (gas: 171403)\n[PASS] test_ClaimAndFeeCollectionBlockedDuringUnlock() (gas: 464563)\n[PASS] test_EmptyPoolHasNoFeeOrReward() (gas: 1091786)\n[PASS] test_ExactOutputBuyAndBothSells() (gas: 952324)\n[PASS] test_GiftAndSpoofedRouterGetNoRewards() (gas: 994113)\n[PASS] test_LargeRequestWithSmallPartialFillDoesNotQualify() (gas: 550527)\n[PASS] test_PartialInputPaysOnlyOnFilledAmount() (gas: 488123)\n[PASS] test_PartialOutputCannotChargeMoreThanOutput() (gas: 434206)\n[PASS] test_PermissionBitsAndLimits() (gas: 35468)\n[PASS] test_RejectsOtherPoolAndDynamicFee() (gas: 87846)\n[PASS] test_RouterCannotSpendAnotherPayersApproval() (gas: 329584)\n[PASS] test_RouterLimitsRollbackEverything() (gas: 560749)\n[PASS] test_ThresholdAndSeparateClaim() (gas: 828057)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 80.71ms (94.00ms CPU time)\n\nRan 16 tests for test/CurrencyOrder.t.sol:HookPair1Test\n[PASS] testFuzz_AllSwapModes(uint96,bool,bool) (runs: 256, μ: 406770, ~: 425403)\n[PASS] testFuzz_PartialFills(uint96,bool,bool) (runs: 256, μ: 415470, ~: 414355)\n[PASS] test_AnyonePaysOnlyBeneficiary() (gas: 810056)\n[PASS] test_CallbackAuthentication() (gas: 171940)\n[PASS] test_ClaimAndFeeCollectionBlockedDuringUnlock() (gas: 451521)\n[PASS] test_EmptyPoolHasNoFeeOrReward() (gas: 1078715)\n[PASS] test_ExactOutputBuyAndBothSells() (gas: 957270)\n[PASS] test_GiftAndSpoofedRouterGetNoRewards() (gas: 979262)\n[PASS] test_LargeRequestWithSmallPartialFillDoesNotQualify() (gas: 538296)\n[PASS] test_PartialInputPaysOnlyOnFilledAmount() (gas: 475904)\n[PASS] test_PartialOutputCannotChargeMoreThanOutput() (gas: 446231)\n[PASS] test_PermissionBitsAndLimits() (gas: 35468)\n[PASS] test_RejectsOtherPoolAndDynamicFee() (gas: 87869)\n[PASS] test_RouterCannotSpendAnotherPayersApproval() (gas: 316578)\n[PASS] test_RouterLimitsRollbackEverything() (gas: 542621)\n[PASS] test_ThresholdAndSeparateClaim() (gas: 812881)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 86.71ms (174.32ms CPU time)\n\nRan 6 tests for test/Security.t.sol:SecurityTest\n[PASS] test_FailedPayoutRestoresDebt() (gas: 1391366)\n[PASS] test_MinInt256BudgetWithSmallFillDoesNotOverflow() (gas: 441727)\n[PASS] test_ReentrantClaimCannotDoublePayOrPayAnotherBuyer() (gas: 1725625)\n[PASS] test_ReentrantFeeRedemptionCannotPayRewardsDuringCallback() (gas: 1340886)\n[PASS] test_RouterReentryDuringSettlementCannotSpendAllowance() (gas: 1640435)\n[PASS] test_RuntimeContainsNoEscapeOpcode() (gas: 3037953)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 86.87ms (11.48ms CPU time)\n\nRan 16 tests for test/Hook.t.sol:HookTest\n[PASS] testFuzz_AllSwapModes(uint96,bool,bool) (runs: 1000, μ: 408864, ~: 429660)\n[PASS] testFuzz_PartialFills(uint96,bool,bool) (runs: 1000, μ: 416778, ~: 434904)\n[PASS] test_AnyonePaysOnlyBeneficiary() (gas: 824000)\n[PASS] test_CallbackAuthentication() (gas: 171403)\n[PASS] test_ClaimAndFeeCollectionBlockedDuringUnlock() (gas: 464563)\n[PASS] test_EmptyPoolHasNoFeeOrReward() (gas: 1091786)\n[PASS] test_ExactOutputBuyAndBothSells() (gas: 952324)\n[PASS] test_GiftAndSpoofedRouterGetNoRewards() (gas: 994113)\n[PASS] test_LargeRequestWithSmallPartialFillDoesNotQualify() (gas: 550527)\n[PASS] test_PartialInputPaysOnlyOnFilledAmount() (gas: 488123)\n[PASS] test_PartialOutputCannotChargeMoreThanOutput() (gas: 434206)\n[PASS] test_PermissionBitsAndLimits() (gas: 35468)\n[PASS] test_RejectsOtherPoolAndDynamicFee() (gas: 87846)\n[PASS] test_RouterCannotSpendAnotherPayersApproval() (gas: 329584)\n[PASS] test_RouterLimitsRollbackEverything() (gas: 560749)\n[PASS] test_ThresholdAndSeparateClaim() (gas: 828057)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 87.09ms (158.77ms CPU time)\n\nRan 4 tests for test/Token.t.sol:TokenTest\n[PASS] testFuzz_TransfersDoNotTaxOrChangeSupply(uint256) (runs: 1000, μ: 120502, ~: 120340)\n[PASS] test_AllowanceAndFailures() (gas: 191425)\n[PASS] test_EntirePolicySupplyBelongsToDeployer() (gas: 44150)\n[PASS] test_NoMintOrAdminFunction() (gas: 133418)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 87.14ms (87.22ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:RewardInvariantTest\n[PASS] invariant_SolvencyConservationAndAuthenticatedDebt() (runs: 128, calls: 6144, reverts: 0)\n\n╭---------------+----------+-------+---------+----------╮\n| Contract      | Selector | Calls | Reverts | Discards |\n+=======================================================+\n| RewardHandler | claim    | 2086  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | pay      | 2080  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | trade    | 1978  | 0       | 0        |\n╰---------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.34s (1.33s CPU time)\n\nRan 8 test suites in 1.35s (1.78s CPU time): 62 tests passed, 0 failed, 1 skipped (63 total tests)\n","passed":true},{"durationMs":66,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SIMDTEST.approve(address,uint256)\",\"SIMDTEST.transfer(address,uint256)\",\"SIMDTEST.transferFrom(address,address,uint256)\",\"SIMDTESTHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SIMDTESTHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SIMDTESTHook.claimRewards()\",\"SIMDTESTHook.collectFees()\",\"SIMDTESTHook.payRecorded(address)\",\"SIMDTESTHook.quotePairAmount((address,address,uint24,int24,address),(bool,int256,uint160))\",\"SIMDTESTHook.unlockCallback(bytes)\",\"SIMDTESTRouter.swap((address,address,uint24,int24,address),(bool,int256,uint160),address,uint256,uint256)\",\"SIMDTESTRouter.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"LICENSE\":21,\"README.md\":91,\"dependencies.json\":30,\"docs/SECURITY.md\":75,\"docs/VALIDATION.md\":45,\"docs/static-analysis.json\":21,\"foundry.toml\":13,\"launch.json\":22,\"remappings.txt\":4,\"script/MineHook.s.sol\":28,\"src/HookFlags.sol\":30,\"src/SIMDTEST.sol\":11,\"src/SIMDTESTHook.sol\":237,\"src/SIMDTESTRouter.sol\":112,\"test/CurrencyOrder.t.sol\":26,\"test/FreshManager.t.sol\":51,\"test/Hook.t.sol\":255,\"test/Invariants.t.sol\":107,\"test/MainnetFork.t.sol\":58,\"test/Security.t.sol\":107,\"test/Token.t.sol\":66,\"test/helpers/HookFixture.sol\":125,\"test/helpers/PoolDriver.sol\":86,\"test/mocks/AdversarialIMD.sol\":38,\"test/mocks/MockERC20.sol\":14},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1403,"exitCode":0,"name":"slither","output":"slither: no results at low impact or above","passed":true},{"durationMs":436,"exitCode":0,"name":"aderyn","output":"[low] internal-function-used-once at src/HookFlags.sol:23: Internal Function Used Only Once\n[low] large-numeric-literal at src/SIMDTEST.sol:9: Large Numeric Literal\n[low] literal-instead-of-constant at src/SIMDTESTHook.sol:120: Literal Instead of Constant (7 places)\n[low] unchecked-return at src/SIMDTESTHook.sol:212: Unchecked Return","passed":true}],"detail":"launch.json is not a valid launch manifest: kind: Invalid discriminator value. Expected 'univ4_hook' | 'evm_project' | 'custom_token' | 'evm_contracts'","evaluation":"checks","profile":"foundry","status":"rejected","submissionHash":"5f287d77dc17537b4dc059a9179855331b46a0dcec4f2553599197280fdcb7d8","verifiedTreeHash":"079f6c5118ef97040744be2d963c8bb3a33b3b19","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":3979,"exitCode":0,"name":"build","output":"Compiling 98 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.84s\nCompiler run successful!\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTRouter.sol:37:60\n   │\n37 │     constructor(IPoolManager manager, address launchToken, address pair) {\n   │                                                            ━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTRouter.sol:37:39\n   │\n37 │     constructor(IPoolManager manager, address launchToken, address pair) {\n   │                                       ━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTHook.sol:53:39\n   │\n53 │     constructor(IPoolManager manager, address launchToken) {\n   │                                       ━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/SIMDTESTRouter.sol:59:13\n   │\n59 │         if (block.timestamp > deadline) revert Expired();\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/SIMDTESTRouter.sol:63:36\n   │\n63 │         if (amountSpecified < 0 && uint256(-amountSpecified) != maxInput) revert InvalidSwap();\n   │                                    ━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/SIMDTESTRouter.sol:87:25\n   │\n87 │         uint256 spent = uint256(-int256(inputDelta));\n   │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/SIMDTESTRouter.sol:88:28\n   │\n88 │         uint256 received = uint128(outputDelta);\n   │                            ━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint128' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/SIMDTESTRouter.sol:90:56\n   │\n90 │         if (r.params.amountSpecified > 0 && received < uint256(r.params.amountSpecified)) revert Slippage();\n   │                                                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n   ╭▸ src/SIMDTESTRouter.sol:96:13\n   │\n96 │             poolManager.settle();\n   │             ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/SIMDTESTHook.sol:99:40\n   │\n99 │             preview.amountSpecified += int256(fee);\n   │                                        ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:121:44\n    │\n121 │         uint256 amount = pairedDelta < 0 ? uint256(-int256(pairedDelta)) : uint128(pairedDelta);\n    │                                            ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:121:76\n    │\n121 │         uint256 amount = pairedDelta < 0 ? uint256(-int256(pairedDelta)) : uint128(pairedDelta);\n    │                                                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:151:34\n    │\n151 │         if (data.length != 36 || bytes4(data) != SwapQuote.selector) {\n    │                                  ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'bytes4' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:159:29\n    │\n159 │         return paired < 0 ? uint256(-int256(paired)) : uint128(paired);\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:159:56\n    │\n159 │         return paired < 0 ? uint256(-int256(paired)) : uint128(paired);\n    │                                                        ━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:196:9\n    │\n196 │         emit RewardPaid(buyer, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:202:9\n    │\n202 │         emit FeesCollected(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/SIMDTESTHook.sol:200:13\n    │\n200 │         if (poolManager.balanceOf(address(this), uint160(pairedCurrency)) == 0) return 0;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:209:13\n    │\n209 │             emit FeeAccrued(fee, false);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:212:13\n    │\n212 │             emit FeeAccrued(fee, true);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:218:21\n    │\n218 │             return (uint256(-(amountSpecified + 1)) + 1) / 50;\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:221:16\n    │\n221 │         return uint256(amountSpecified) / 49;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\n","passed":true},{"durationMs":1483,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 521.02µs (0.00ns CPU time)\n\nRan 5 tests for test/Security.t.sol:SecurityTest\n[PASS] test_claimCannotReenterOrPayTwice() (gas: 1344654)\n[PASS] test_failedFeeTransferDoesNotBlockSwap() (gas: 1602776)\n[PASS] test_feeTransferCannotTriggerRewardDuringSwap() (gas: 1295328)\n[PASS] test_freshTokenOnlyPoolAccruesClaimsThenPays() (gas: 15467568)\n[PASS] test_noLiquidityNoFeeNoReward() (gas: 1078004)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 61.02ms (16.44ms CPU time)\n\nRan 16 tests for test/Hook.t.sol:HookTest\n[PASS] testFuzz_feesRewardsAndClaims(uint96,bool,bool) (runs: 1000, μ: 421302, ~: 396756)\n[PASS] test_allCallbacksRestricted() (gas: 179087)\n[PASS] test_allFourSwapModes() (gas: 1144212)\n[PASS] test_externalQuoteCannotBeUsedAsARouter() (gas: 41312)\n[PASS] test_malformedHookDataNeverBlocksOtherRouter() (gas: 278850)\n[PASS] test_partialFillBelowThresholdDoesNotEarnReward() (gas: 509505)\n[PASS] test_partialInputFeeUsesActualFill() (gas: 421423)\n[PASS] test_partialOutputSellDoesNotOverchargeOrRevert() (gas: 414313)\n[PASS] test_payRecordedCannotRedirect() (gas: 454843)\n[PASS] test_permissionsAndSize() (gas: 3080272)\n[PASS] test_routerCannotSpendAnApprovedVictim() (gas: 324779)\n[PASS] test_routerSlippageAndDeadlineRollback() (gas: 594859)\n[PASS] test_thresholdAndDeferredClaims() (gas: 924467)\n[PASS] test_tinyTradeRounding() (gas: 285273)\n[PASS] test_untrustedRouterCannotSpoofBuyerAndStillSwaps() (gas: 381224)\n[PASS] test_wrongPoolCannotInitialize() (gas: 76102)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 61.03ms (88.63ms CPU time)\n\nRan 3 tests for test/Token.t.sol:TokenTest\n[PASS] testFuzz_allowanceCannotBeExceeded(uint96) (runs: 1000, μ: 212134, ~: 213731)\n[PASS] test_entireSupplyToDeployerNoTax() (gas: 121034)\n[PASS] test_noAdminOrMint() (gas: 100841)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 67.41ms (67.15ms CPU time)\n\nRan 16 tests for test/ReverseOrder.t.sol:ReverseOrderTest\n[PASS] testFuzz_feesRewardsAndClaims(uint96,bool,bool) (runs: 1000, μ: 422067, ~: 401595)\n[PASS] test_allCallbacksRestricted() (gas: 178716)\n[PASS] test_allFourSwapModes() (gas: 1145821)\n[PASS] test_externalQuoteCannotBeUsedAsARouter() (gas: 41133)\n[PASS] test_malformedHookDataNeverBlocksOtherRouter() (gas: 291933)\n[PASS] test_partialFillBelowThresholdDoesNotEarnReward() (gas: 521748)\n[PASS] test_partialInputFeeUsesActualFill() (gas: 433654)\n[PASS] test_partialOutputSellDoesNotOverchargeOrRevert() (gas: 402250)\n[PASS] test_payRecordedCannotRedirect() (gas: 467879)\n[PASS] test_permissionsAndSize() (gas: 3080272)\n[PASS] test_routerCannotSpendAnApprovedVictim() (gas: 337815)\n[PASS] test_routerSlippageAndDeadlineRollback() (gas: 612838)\n[PASS] test_thresholdAndDeferredClaims() (gas: 940643)\n[PASS] test_tinyTradeRounding() (gas: 302947)\n[PASS] test_untrustedRouterCannotSpoofBuyerAndStillSwaps() (gas: 394307)\n[PASS] test_wrongPoolCannotInitialize() (gas: 76146)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 67.50ms (78.32ms CPU time)\n\nRan 1 test for test/Invariant.t.sol:RewardInvariantTest\n[PASS] invariant_allRewardsBackedAndAllFeesConserved() (runs: 256, calls: 8192, reverts: 0)\n\n╭---------------+----------+-------+---------+----------╮\n| Contract      | Selector | Calls | Reverts | Discards |\n+=======================================================+\n| RewardHandler | collect  | 2728  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | pay      | 2793  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | trade    | 2671  | 0       | 0        |\n╰---------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.37s (1.37s CPU time)\n\nRan 6 test suites in 1.38s (1.63s CPU time): 41 tests passed, 0 failed, 1 skipped (42 total tests)\n","passed":true},{"durationMs":41,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SIMDTEST.approve(address,uint256)\",\"SIMDTEST.transfer(address,uint256)\",\"SIMDTEST.transferFrom(address,address,uint256)\",\"SIMDTESTHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SIMDTESTHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SIMDTESTHook.claimRewards()\",\"SIMDTESTHook.collectFees()\",\"SIMDTESTHook.payRecorded(address)\",\"SIMDTESTHook.quoteSwap((address,address,uint24,int24,address),(bool,int256,uint160))\",\"SIMDTESTHook.unlockCallback(bytes)\",\"SIMDTESTRouter.swap(bool,int256,uint256,uint256,uint160,uint256)\",\"SIMDTESTRouter.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"README.md\":85,\"REVIEW.md\":37,\"docs/DEPENDENCIES.md\":10,\"docs/abi/SIMDTEST.json\":315,\"docs/abi/SIMDTESTHook.json\":722,\"docs/abi/SIMDTESTRouter.json\":214,\"docs/dependency-sha256.json\":207,\"foundry.toml\":26,\"launch.json\":30,\"script/Deploy.s.sol\":48,\"scripts/check-launch.py\":37,\"src/HookFlags.sol\":30,\"src/SIMDTEST.sol\":11,\"src/SIMDTESTHook.sol\":234,\"src/SIMDTESTRouter.sol\":101,\"test/Hook.t.sol\":275,\"test/Invariant.t.sol\":93,\"test/MainnetFork.t.sol\":56,\"test/ReverseOrder.t.sol\":22,\"test/Security.t.sol\":107,\"test/Token.t.sol\":63,\"test/helpers/HookFixture.sol\":132,\"test/mocks/AdversarialIMD.sol\":37,\"test/mocks/MockERC20.sol\":14},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1393,"exitCode":0,"name":"slither","output":"[medium/medium] unused-return at src/SIMDTESTRouter.sol:79: SIMDTESTRouter.unlockCallback(bytes) (src/SIMDTESTRouter.sol#79-100) ignores return value by poolManager.settle() (src/SIMDTESTRouter.sol#96)\n[low/medium] missing-zero-check at src/SIMDTESTRouter.sol:37: SIMDTESTRouter.constructor(IPoolManager,address,address).launchToken (src/SIMDTESTRouter.sol#37) lacks a zero-check on :\n[low/medium] missing-zero-check at src/SIMDTESTRouter.sol:37: SIMDTESTRouter.constructor(IPoolManager,address,address).pair (src/SIMDTESTRouter.sol#37) lacks a zero-check on :\n[low/medium] reentrancy-benign at src/SIMDTESTHook.sol:88: Reentrancy in SIMDTESTHook.beforeSwap(address,PoolKey,SwapParams,bytes) (src/SIMDTESTHook.sol#88-107):\n[low/medium] reentrancy-events at src/SIMDTESTHook.sol:205: Reentrancy in SIMDTESTHook._accrueFee(uint256) (src/SIMDTESTHook.sol#205-214):\n[low/medium] reentrancy-events at src/SIMDTESTHook.sol:205: Reentrancy in SIMDTESTHook._accrueFee(uint256) (src/SIMDTESTHook.sol#205-214):\n[low/medium] timestamp at src/SIMDTESTRouter.sol:51: SIMDTESTRouter.swap(bool,int256,uint256,uint256,uint160,uint256) (src/SIMDTESTRouter.sol#51-77) uses timestamp for comparisons","passed":true},{"durationMs":462,"exitCode":0,"name":"aderyn","output":"[low] internal-function-used-once at src/HookFlags.sol:27: Internal Function Used Only Once\n[low] large-numeric-literal at src/SIMDTEST.sol:9: Large Numeric Literal\n[low] literal-instead-of-constant at src/SIMDTESTHook.sol:101: Literal Instead of Constant (5 places)\n[low] state-change-without-event at src/SIMDTESTHook.sol:88: State Change Without Event\n[low] unchecked-return at src/SIMDTESTRouter.sol:96: Unchecked Return","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"6fc1e7b9c7ed650f3448c4a06152845e85a3682e6310358c01c8ec001efb84b5","verifiedTreeHash":"5c52b3b7f6a4338cc8ec467ec9d953cbb51d104a","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":5365,"exitCode":0,"name":"build","output":"Compiling 103 files with Solc 0.8.26\nSolc 0.8.26 finished in 5.23s\nCompiler run successful!\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTRouter.sol:37:60\n   │\n37 │     constructor(IPoolManager manager, address launchToken, address pair) {\n   │                                                            ━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTRouter.sol:37:39\n   │\n37 │     constructor(IPoolManager manager, address launchToken, address pair) {\n   │                                       ━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTHook.sol:53:39\n   │\n53 │     constructor(IPoolManager manager, address launchToken) {\n   │                                       ━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/SIMDTESTRouter.sol:59:13\n   │\n59 │         if (block.timestamp > deadline) revert Expired();\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/SIMDTESTRouter.sol:63:36\n   │\n63 │         if (amountSpecified < 0 && uint256(-amountSpecified) != maxInput) revert InvalidSwap();\n   │                                    ━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/SIMDTESTRouter.sol:87:25\n   │\n87 │         uint256 spent = uint256(-int256(inputDelta));\n   │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/SIMDTESTRouter.sol:88:28\n   │\n88 │         uint256 received = uint128(outputDelta);\n   │                            ━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint128' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/SIMDTESTRouter.sol:90:56\n   │\n90 │         if (r.params.amountSpecified > 0 && received < uint256(r.params.amountSpecified)) revert Slippage();\n   │                                                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n   ╭▸ src/SIMDTESTRouter.sol:96:13\n   │\n96 │             poolManager.settle();\n   │             ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/SIMDTESTHook.sol:99:40\n   │\n99 │             preview.amountSpecified += int256(fee);\n   │                                        ━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:121:44\n    │\n121 │         uint256 amount = pairedDelta < 0 ? uint256(-int256(pairedDelta)) : uint128(pairedDelta);\n    │                                            ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:121:76\n    │\n121 │         uint256 amount = pairedDelta < 0 ? uint256(-int256(pairedDelta)) : uint128(pairedDelta);\n    │                                                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:151:34\n    │\n151 │         if (data.length != 36 || bytes4(data) != SwapQuote.selector) {\n    │                                  ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'bytes4' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:159:29\n    │\n159 │         return paired < 0 ? uint256(-int256(paired)) : uint128(paired);\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:159:56\n    │\n159 │         return paired < 0 ? uint256(-int256(paired)) : uint128(paired);\n    │                                                        ━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:196:9\n    │\n196 │         emit RewardPaid(buyer, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:202:9\n    │\n202 │         emit FeesCollected(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/SIMDTESTHook.sol:200:13\n    │\n200 │         if (poolManager.balanceOf(address(this), uint160(pairedCurrency)) == 0) return 0;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:209:13\n    │\n209 │             emit FeeAccrued(fee, false);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:212:13\n    │\n212 │             emit FeeAccrued(fee, true);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:218:21\n    │\n218 │             return (uint256(-(amountSpecified + 1)) + 1) / 50;\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:221:16\n    │\n221 │         return uint256(amountSpecified) / 49;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\n","passed":true},{"durationMs":3025,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 325.80µs (0.00ns CPU time)\n\nRan 11 tests for test/RewardFailurePaths.t.sol:RewardFailurePathsTest\n[PASS] testFuzz_exactOutputPartialFillRollsBackEverything(bool,uint96) (runs: 1000, μ: 898357, ~: 879860)\n[PASS] test_claimThenEarnAgainKeepsBuyersIndependent() (gas: 1371680)\n[PASS] test_everyPaymentEntryRejectsAnUnrelatedManagerUnlock() (gas: 521332)\n[PASS] test_failedBeneficiaryTransferRollsBackSuccessfulClaimRedemption() (gas: 1706106)\n[PASS] test_failedTokenPullDoesNotRecordFeesOrRewards() (gas: 420101)\n[PASS] test_invalidConstructorDependenciesRevertBeforeDeployment() (gas: 8289)\n[PASS] test_missingTransferReturnSupportsSwapRedemptionAndPayout() (gas: 1501494)\n[PASS] test_routerInvalidAmountsLeaveAllAccountingUnchanged() (gas: 471836)\n[PASS] test_subthresholdPurchasesDoNotAggregateIntoEligibility() (gas: 920916)\n[PASS] test_unsolicitedManagerCallbackCannotRedeemFees() (gas: 36826)\n[PASS] test_wrongCreate2PermissionBitsAreRejected() (gas: 23775)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 170.03ms (171.67ms CPU time)\n\nRan 16 tests for test/ReverseOrder.t.sol:ReverseOrderTest\n[PASS] testFuzz_feesRewardsAndClaims(uint96,bool,bool) (runs: 1000, μ: 418741, ~: 401583)\n[PASS] test_allCallbacksRestricted() (gas: 178716)\n[PASS] test_allFourSwapModes() (gas: 1145821)\n[PASS] test_externalQuoteCannotBeUsedAsARouter() (gas: 41133)\n[PASS] test_malformedHookDataNeverBlocksOtherRouter() (gas: 291933)\n[PASS] test_partialFillBelowThresholdDoesNotEarnReward() (gas: 521748)\n[PASS] test_partialInputFeeUsesActualFill() (gas: 433654)\n[PASS] test_partialOutputSellDoesNotOverchargeOrRevert() (gas: 402250)\n[PASS] test_payRecordedCannotRedirect() (gas: 467879)\n[PASS] test_permissionsAndSize() (gas: 3080272)\n[PASS] test_routerCannotSpendAnApprovedVictim() (gas: 337815)\n[PASS] test_routerSlippageAndDeadlineRollback() (gas: 612838)\n[PASS] test_thresholdAndDeferredClaims() (gas: 940643)\n[PASS] test_tinyTradeRounding() (gas: 302947)\n[PASS] test_untrustedRouterCannotSpoofBuyerAndStillSwaps() (gas: 394307)\n[PASS] test_wrongPoolCannotInitialize() (gas: 76146)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 225.49ms (232.72ms CPU time)\n\nRan 5 tests for test/Security.t.sol:SecurityTest\n[PASS] test_claimCannotReenterOrPayTwice() (gas: 1344654)\n[PASS] test_failedFeeTransferDoesNotBlockSwap() (gas: 1602776)\n[PASS] test_feeTransferCannotTriggerRewardDuringSwap() (gas: 1295328)\n[PASS] test_freshTokenOnlyPoolAccruesClaimsThenPays() (gas: 15467568)\n[PASS] test_noLiquidityNoFeeNoReward() (gas: 1078004)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 228.01ms (11.16ms CPU time)\n\nRan 2 tests for test/FillAccounting.t.sol:FillAccountingTest\n[PASS] testFuzz_partialFillsChargeOnlyActualIMD(uint96,uint80,uint16,bool,bool) (runs: 1000, μ: 586274, ~: 558594)\n[PASS] testFuzz_previewDoesNotDoubleMovePriceOrLPFees(uint96,bool) (runs: 1000, μ: 879919, ~: 886512)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 229.42ms (446.24ms CPU time)\n\nRan 16 tests for test/Hook.t.sol:HookTest\n[PASS] testFuzz_feesRewardsAndClaims(uint96,bool,bool) (runs: 1000, μ: 418357, ~: 396750)\n[PASS] test_allCallbacksRestricted() (gas: 179087)\n[PASS] test_allFourSwapModes() (gas: 1144212)\n[PASS] test_externalQuoteCannotBeUsedAsARouter() (gas: 41312)\n[PASS] test_malformedHookDataNeverBlocksOtherRouter() (gas: 278850)\n[PASS] test_partialFillBelowThresholdDoesNotEarnReward() (gas: 509505)\n[PASS] test_partialInputFeeUsesActualFill() (gas: 421423)\n[PASS] test_partialOutputSellDoesNotOverchargeOrRevert() (gas: 414313)\n[PASS] test_payRecordedCannotRedirect() (gas: 454843)\n[PASS] test_permissionsAndSize() (gas: 3080272)\n[PASS] test_routerCannotSpendAnApprovedVictim() (gas: 324779)\n[PASS] test_routerSlippageAndDeadlineRollback() (gas: 594859)\n[PASS] test_thresholdAndDeferredClaims() (gas: 924467)\n[PASS] test_tinyTradeRounding() (gas: 285273)\n[PASS] test_untrustedRouterCannotSpoofBuyerAndStillSwaps() (gas: 381224)\n[PASS] test_wrongPoolCannotInitialize() (gas: 76102)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 229.96ms (239.81ms CPU time)\n\nRan 2 tests for test/FillAccounting.t.sol:ReverseFillAccountingTest\n[PASS] testFuzz_partialFillsChargeOnlyActualIMD(uint96,uint80,uint16,bool,bool) (runs: 1000, μ: 586513, ~: 563673)\n[PASS] testFuzz_previewDoesNotDoubleMovePriceOrLPFees(uint96,bool) (runs: 1000, μ: 881524, ~: 872708)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 230.11ms (345.57ms CPU time)\n\nRan 3 tests for test/Token.t.sol:TokenTest\n[PASS] testFuzz_allowanceCannotBeExceeded(uint96) (runs: 1000, μ: 212497, ~: 213731)\n[PASS] test_entireSupplyToDeployerNoTax() (gas: 121034)\n[PASS] test_noAdminOrMint() (gas: 100841)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 230.07ms (229.73ms CPU time)\n\nRan 1 test for test/Invariant.t.sol:RewardInvariantTest\n[PASS] invariant_allRewardsBackedAndAllFeesConserved() (runs: 256, calls: 8192, reverts: 0)\n\n╭---------------+----------+-------+---------+----------╮\n| Contract      | Selector | Calls | Reverts | Discards |\n+=======================================================+\n| RewardHandler | collect  | 2702  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | pay      | 2723  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | trade    | 2767  | 0       | 0        |\n╰---------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.32s (1.31s CPU time)\n\nRan 1 test for test/AccountingSequences.t.sol:AccountingSequencesTest\n[PASS] invariant_independentDebtsFeesAndSupplyAreConserved() (runs: 256, calls: 16384, reverts: 0)\n\n╭---------------------------+-----------+-------+---------+----------╮\n| Contract                  | Selector  | Calls | Reverts | Discards |\n+====================================================================+\n| AccountingSequenceHandler | collect   | 3259  | 0       | 0        |\n|---------------------------+-----------+-------+---------+----------|\n| AccountingSequenceHandler | donate    | 3289  | 0       | 0        |\n|---------------------------+-----------+-------+---------+----------|\n| AccountingSequenceHandler | failedPay | 3283  | 0       | 0        |\n|---------------------------+-----------+-------+---------+----------|\n| AccountingSequenceHandler | pay       | 3217  | 0       | 0        |\n|---------------------------+-----------+-------+---------+----------|\n| AccountingSequenceHandler | trade     | 3336  | 0       | 0        |\n╰---------------------------+-----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.94s (2.92s CPU time)\n\nRan 10 test suites in 2.94s (5.80s CPU time): 57 tests passed, 0 failed, 1 skipped (58 total tests)\n","passed":true},{"durationMs":42,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SIMDTEST.approve(address,uint256)\",\"SIMDTEST.transfer(address,uint256)\",\"SIMDTEST.transferFrom(address,address,uint256)\",\"SIMDTESTHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SIMDTESTHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SIMDTESTHook.claimRewards()\",\"SIMDTESTHook.collectFees()\",\"SIMDTESTHook.payRecorded(address)\",\"SIMDTESTHook.quoteSwap((address,address,uint24,int24,address),(bool,int256,uint160))\",\"SIMDTESTHook.unlockCallback(bytes)\",\"SIMDTESTRouter.swap(bool,int256,uint256,uint256,uint160,uint256)\",\"SIMDTESTRouter.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"README.md\":85,\"REVIEW.md\":37,\"docs/DEPENDENCIES.md\":10,\"docs/abi/SIMDTEST.json\":315,\"docs/abi/SIMDTESTHook.json\":722,\"docs/abi/SIMDTESTRouter.json\":214,\"docs/dependency-sha256.json\":207,\"foundry.toml\":26,\"launch.json\":30,\"script/Deploy.s.sol\":48,\"scripts/check-launch.py\":37,\"src/HookFlags.sol\":30,\"src/SIMDTEST.sol\":11,\"src/SIMDTESTHook.sol\":234,\"src/SIMDTESTRouter.sol\":101,\"test/AccountingSequences.t.sol\":211,\"test/FillAccounting.t.sol\":180,\"test/Hook.t.sol\":275,\"test/Invariant.t.sol\":93,\"test/MainnetFork.t.sol\":127,\"test/README.md\":35,\"test/ReverseOrder.t.sol\":22,\"test/RewardFailurePaths.t.sol\":227,\"test/Security.t.sol\":107,\"test/Token.t.sol\":63,\"test/helpers/HookFixture.sol\":132,\"test/helpers/SwapObservation.sol\":29,\"test/mocks/AdversarialIMD.sol\":37,\"test/mocks/MockERC20.sol\":14,\"test/mocks/PayoutIMD.sol\":27},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"93b496e6ec3054c464611024fa340825da214b7d0551af08413b7b55c6143c27","verifiedTreeHash":"ffbe2011af13167210ff6daa7927022c29e444b2","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":3659,"exitCode":0,"name":"build","output":"Compiling 91 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.51s\nCompiler run successful!\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n   ╭▸ src/SIMDTESTRouter.sol:58:13\n   │\n58 │             poolManager.unlock(abi.encode(Request(key, params, msg.sender, recipient, maxInput, minOutput))),\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n   ╭▸ src/SIMDTESTRouter.sol:62:13\n   │\n62 │         try IFeeCollector(hook).collectFees() {} catch {}\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/SIMDTESTRouter.sol:54:13\n   │\n54 │         if (block.timestamp > deadline || recipient == address(0) || address(key.hooks) != hook) {\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[arbitrary-send-erc20]: `transferFrom` uses an arbitrary `from`; require it to equal `msg.sender` or `address(this)`\n   ╭▸ src/SIMDTESTRouter.sol:83:13\n   │\n83 │             IERC20(Currency.unwrap(currencyIn)).safeTransferFrom(r.payer, address(poolManager), spent);\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-erc20\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTHook.sol:50:39\n   │\n50 │     constructor(IPoolManager manager, address launchToken) {\n   │                                       ━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/SIMDTESTRouter.sol:73:25\n   │\n73 │         uint256 spent = uint256(-int256(input));\n   │                         ━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/SIMDTESTRouter.sol:74:28\n   │\n74 │         uint256 received = uint256(int256(output));\n   │                            ━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/SIMDTESTRouter.sol:77:64\n   │\n77 │                 || (r.params.amountSpecified > 0 && received < uint256(r.params.amountSpecified))\n   │                                                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'uint256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n   ╭▸ src/SIMDTESTRouter.sol:84:13\n   │\n84 │             poolManager.settle();\n   │             ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/SIMDTESTHook.sol:111:77\n    │\n111 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(fee)), 0), 0);\n    │                                                                             ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:102:44\n    │\n102 │                 preview.amountSpecified += int256(maximumFee);\n    │                                            ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:111:63\n    │\n111 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(fee)), 0), 0);\n    │                                                               ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:111:70\n    │\n111 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(fee)), 0), 0);\n    │                                                                      ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:143:17\n    │\n143 │                 emit RewardRecorded(buyer, spent, reward);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:146:71\n    │\n146 │         return (IHooks.afterSwap.selector, specifiedIMD ? int128(0) : int128(int256(fee)));\n    │                                                                       ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:146:78\n    │\n146 │         return (IHooks.afterSwap.selector, specifiedIMD ? int128(0) : int128(int256(fee)));\n    │                                                                              ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:160:40\n    │\n160 │             if (reason.length == 36 && bytes4(reason) == PreviewResult.selector) {\n    │                                        ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'bytes4' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:173:13\n    │\n173 │             emit FeeAccrued(fee, false);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:176:13\n    │\n176 │             emit FeeAccrued(fee, true);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/SIMDTESTHook.sol:210:9\n    │\n210 │         poolManager.unlock(abi.encode(amount));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:195:9\n    │\n195 │         emit RewardPaid(buyer, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:212:9\n    │\n212 │         emit FeesCollected(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:208:22\n    │\n208 │             claims > uint256(uint128(type(int128).max)) ? uint256(uint128(type(int128).max)) : claims;\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:208:30\n    │\n208 │             claims > uint256(uint128(type(int128).max)) ? uint256(uint128(type(int128).max)) : claims;\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:208:59\n    │\n208 │             claims > uint256(uint128(type(int128).max)) ? uint256(uint128(type(int128).max)) : claims;\n    │                                                           ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:208:67\n    │\n208 │             claims > uint256(uint128(type(int128).max)) ? uint256(uint128(type(int128).max)) : claims;\n    │                                                                   ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/SIMDTESTHook.sol:210:9\n    │\n210 │         poolManager.unlock(abi.encode(amount));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:242:32\n    │\n242 │             return value < 0 ? uint256(-(value + 1)) + 1 : uint256(value);\n    │                                ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:242:60\n    │\n242 │             return value < 0 ? uint256(-(value + 1)) + 1 : uint256(value);\n    │                                                            ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\n","passed":true},{"durationMs":3090,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 460.28µs (0.00ns CPU time)\n\nRan 1 test for test/Hook.t.sol:ReentrancyTest\n[PASS] test_transferCallbacksCannotPayDuringSwapsOrReenterClaim() (gas: 1093956)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.95ms (1.13ms CPU time)\n\nRan 2 tests for test/Hook.t.sol:EmptyManagerTest\n[PASS] test_thirdPartyCanRedeemFallbackOnlyToHook() (gas: 610808)\n[PASS] test_tokenOnlyPoolCanBuyAndClaim() (gas: 629461)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 123.58ms (1.76ms CPU time)\n\nRan 3 tests for test/Token.t.sol:TokenTest\n[PASS] testFuzz_exactTransfers(uint256) (runs: 1000, μ: 90825, ~: 90879)\nLogs:\n  Bound result 175854677512019617258600449\n\n[PASS] test_noMintOrAdminSelectors() (gas: 210618)\n[PASS] test_supplyAndFactoryDistribution() (gas: 196479)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 124.22ms (124.03ms CPU time)\n\nRan 22 tests for test/Hook.t.sol:HookIMD0Test\n[PASS] testFuzz_allSwapDirections(bool,bool,uint256) (runs: 1000, μ: 431689, ~: 439363)\nLogs:\n  Bound result 12\n\n[PASS] testFuzz_partialFillsNeverTaxTheRequestedButUnfilledAmount(uint128,bool,uint8) (runs: 1000, μ: 398651, ~: 399983)\nLogs:\n  Bound result 6545186839045576429656\n\n[PASS] test_allImplementedCallbacksRefuseUnauthorizedCalls() (gas: 204446)\n[PASS] test_claimAndThirdPartyPaymentCannotRedirect() (gas: 874300)\n[PASS] test_claimsCannotExecuteAnywhereDuringUnlock() (gas: 633409)\n[PASS] test_creationAndRuntimeLimitsAndForbiddenOpcodes() (gas: 2956318)\n[PASS] test_dustRounding() (gas: 35234072)\n[PASS] test_failedClaimPreservesDebtAndOtherBuyersCanClaim() (gas: 898689)\n[PASS] test_feeTierAndPermissionBitsRemainFixed() (gas: 59601)\n[PASS] test_foreignPoolsCannotUseHook() (gas: 830997)\n[PASS] test_giftAndUntrustedRouterCannotForgeBuyer() (gas: 626769)\n[PASS] test_int128MaximumRequestPartialFillDoesNotBecomeNoOp() (gas: 473686)\n[PASS] test_largeRequestWithSubThresholdFillEarnsNothing() (gas: 402144)\n[PASS] test_noLiquidityMeansNoFeeOrReward() (gas: 1080889)\n[PASS] test_partialBuyChargesOnlyFilledInput() (gas: 449492)\n[PASS] test_partialExactOutputRouterRefusesAndRollsBack() (gas: 338704)\n[PASS] test_partialExactOutputSellDoesNotTaxUnfilledOutput() (gas: 395543)\n[PASS] test_previewDoesNotDoubleTradeOrDoubleChargeLPFee() (gas: 926882)\n[PASS] test_routerLimitsRollBackFeesAndRewards() (gas: 698263)\n[PASS] test_sellsChargeFeesWithoutAccruingOrPayingRewards() (gas: 923951)\n[PASS] test_thresholdAndDeferredRewards() (gas: 914497)\n[PASS] test_transferFailureFallsBackAndClaimRedeemsReserves() (gas: 817501)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 131.43ms (385.66ms CPU time)\n\nRan 22 tests for test/Hook.t.sol:HookIMD1Test\n[PASS] testFuzz_allSwapDirections(bool,bool,uint256) (runs: 1000, μ: 432042, ~: 438802)\nLogs:\n  Bound result 566356204689538624470\n\n[PASS] testFuzz_partialFillsNeverTaxTheRequestedButUnfilledAmount(uint128,bool,uint8) (runs: 1000, μ: 398010, ~: 411829)\nLogs:\n  Bound result 1570466948506254395607\n\n[PASS] test_allImplementedCallbacksRefuseUnauthorizedCalls() (gas: 205019)\n[PASS] test_claimAndThirdPartyPaymentCannotRedirect() (gas: 863167)\n[PASS] test_claimsCannotExecuteAnywhereDuringUnlock() (gas: 620795)\n[PASS] test_creationAndRuntimeLimitsAndForbiddenOpcodes() (gas: 2956318)\n[PASS] test_dustRounding() (gas: 35045332)\n[PASS] test_failedClaimPreservesDebtAndOtherBuyersCanClaim() (gas: 885525)\n[PASS] test_feeTierAndPermissionBitsRemainFixed() (gas: 59601)\n[PASS] test_foreignPoolsCannotUseHook() (gas: 831198)\n[PASS] test_giftAndUntrustedRouterCannotForgeBuyer() (gas: 613563)\n[PASS] test_int128MaximumRequestPartialFillDoesNotBecomeNoOp() (gas: 461887)\n[PASS] test_largeRequestWithSubThresholdFillEarnsNothing() (gas: 390320)\n[PASS] test_noLiquidityMeansNoFeeOrReward() (gas: 1068235)\n[PASS] test_partialBuyChargesOnlyFilledInput() (gas: 437693)\n[PASS] test_partialExactOutputRouterRefusesAndRollsBack() (gas: 351142)\n[PASS] test_partialExactOutputSellDoesNotTaxUnfilledOutput() (gas: 408016)\n[PASS] test_previewDoesNotDoubleTradeOrDoubleChargeLPFee() (gas: 907855)\n[PASS] test_routerLimitsRollBackFeesAndRewards() (gas: 673164)\n[PASS] test_sellsChargeFeesWithoutAccruingOrPayingRewards() (gas: 917089)\n[PASS] test_thresholdAndDeferredRewards() (gas: 899587)\n[PASS] test_transferFailureFallsBackAndClaimRedeemsReserves() (gas: 804887)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 132.93ms (259.83ms CPU time)\n\nRan 1 test for test/Invariant.t.sol:RewardInvariantTest\n[PASS] invariant_allRewardsBackedAndConserved() (runs: 256, calls: 16384, reverts: 0)\n\n╭---------------+----------+-------+---------+----------╮\n| Contract      | Selector | Calls | Reverts | Discards |\n+=======================================================+\n| RewardHandler | donate   | 5515  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | pay      | 5438  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | trade    | 5431  | 0       | 0        |\n╰---------------+----------+-------+---------+----------╯\n\nLogs:\n  Bound result 99000000002122829438\n  Bound result 95144962930178125\n  Bound result 77592958070054153891\n  Bound result 922634590299163129\n  Bound result 10822\n  Bound result 8927\n  Bound result 1999999999999999998\n  Bound result 9445\n  Bound result 99000462417211290328\n  Bound result 9999999999999898990\n  Bound result 99000000000000000245\n  Bound result 100\n  Bound result 96\n  Bound result 75447270084541652318\n  Bound result 352350663632875612\n  Bound result 9824\n  Bound result 416\n  Bound result 4018\n  Bound result 99000000000000000246\n  Bound result 99000000000000010965\n  Bound result 99000000000000008008\n  Bound result 443598128668840795\n  Bound result 95\n  Bound result 99000000000000009135\n  Bound result 199999999999999999\n  Bound result 99000000002770266797\n  Bound result 2\n  Bound result 99400000000000000000\n  Bound result 7140\n  Bound result 8192\n  Bound result 458901754490184675\n  Bound result 99000000000000006525\n  Bound result 54163790559532271970\n  Bound result 99000000000000001992\n  Bound result 99000000000000000007\n  Bound result 99000000000000001466\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.99s (2.99s CPU time)\n\nRan 7 test suites in 2.99s (3.51s CPU time): 51 tests passed, 0 failed, 1 skipped (52 total tests)\n","passed":true},{"durationMs":55,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SIMDTEST.approve(address,uint256)\",\"SIMDTEST.transfer(address,uint256)\",\"SIMDTEST.transferFrom(address,address,uint256)\",\"SIMDTESTHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SIMDTESTHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SIMDTESTHook.claimRewards()\",\"SIMDTESTHook.collectFees()\",\"SIMDTESTHook.payRecorded(address)\",\"SIMDTESTHook.previewSwap((address,address,uint24,int24,address),(bool,int256,uint160))\",\"SIMDTESTHook.unlockCallback(bytes)\",\"SIMDTESTRouter.swap((address,address,uint24,int24,address),(bool,int256,uint160),address,uint256,uint256,uint256)\",\"SIMDTESTRouter.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":12,\"LICENSE\":23,\"README.md\":103,\"docs/SECURITY_REVIEW.md\":54,\"foundry.toml\":16,\"launch.json\":39,\"remappings.txt\":4,\"script/HookDeployment.sol\":30,\"src/HookFlags.sol\":30,\"src/SIMDTEST.sol\":11,\"src/SIMDTESTHook.sol\":245,\"src/SIMDTESTRouter.sol\":89,\"test/Hook.t.sol\":459,\"test/Invariant.t.sol\":106,\"test/MainnetFork.t.sol\":62,\"test/Token.t.sol\":59,\"test/helpers/HookFixture.sol\":133,\"test/helpers/PoolActor.sol\":74,\"test/mocks/MockERC20.sol\":41},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1299,"exitCode":0,"name":"slither","output":"[high/high] arbitrary-send-erc20 at src/SIMDTESTRouter.sol:65: SIMDTESTRouter.unlockCallback(bytes) (src/SIMDTESTRouter.sol#65-88) uses arbitrary from in transferFrom: IERC20(Currency.unwrap(currencyIn)).safeTransferFrom(r.payer,address(poolManager),spent) (src/SIMDTESTRouter.sol#83)\n[medium/medium] uninitialized-local at src/SIMDTESTHook.sol:93: SIMDTESTHook.beforeSwap(address,PoolKey,SwapParams,bytes).fee (src/SIMDTESTHook.sol#93) is a local variable never initialized\n[medium/medium] unused-return at src/SIMDTESTRouter.sol:65: SIMDTESTRouter.unlockCallback(bytes) (src/SIMDTESTRouter.sol#65-88) ignores return value by poolManager.settle() (src/SIMDTESTRouter.sol#84)\n[medium/medium] unused-return at src/SIMDTESTHook.sol:203: SIMDTESTHook._collectFees() (src/SIMDTESTHook.sol#203-213) ignores return value by poolManager.unlock(abi.encode(amount)) (src/SIMDTESTHook.sol#210)\n[low/medium] reentrancy-benign at src/SIMDTESTHook.sol:114: Reentrancy in SIMDTESTHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/SIMDTESTHook.sol#114-147):\n[low/medium] reentrancy-benign at src/SIMDTESTHook.sol:203: Reentrancy in SIMDTESTHook._collectFees() (src/SIMDTESTHook.sol#203-213):\n[low/medium] reentrancy-events at src/SIMDTESTHook.sol:114: Reentrancy in SIMDTESTHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/SIMDTESTHook.sol#114-147):\n[low/medium] reentrancy-events at src/SIMDTESTHook.sol:169: Reentrancy in SIMDTESTHook._accrueFee(uint256) (src/SIMDTESTHook.sol#169-178):\n[low/medium] reentrancy-events at src/SIMDTESTHook.sol:169: Reentrancy in SIMDTESTHook._accrueFee(uint256) (src/SIMDTESTHook.sol#169-178):\n[low/medium] timestamp at src/SIMDTESTRouter.sol:46: SIMDTESTRouter.swap(PoolKey,SwapParams,address,uint256,uint256,uint256) (src/SIMDTESTRouter.sol#46-63) uses timestamp for comparisons","passed":false}],"detail":"static analysis found arbitrary-send-erc20 at src/SIMDTESTRouter.sol:65: SIMDTESTRouter.unlockCallback(bytes) (src/SIMDTESTRouter.sol#65-88) uses arbitrary from in transferFrom: IERC20(Currency.unwrap(currencyIn)).safeTransferFrom(r.payer,address(poolManager),spent) (src/SIMDTESTRouter.sol#83)","evaluation":"checks","profile":"foundry","status":"rejected","submissionHash":"999e4901647c930b78e5ae3a44c82eafd530bc690ac8e00c65887152a54a2f18","verifiedTreeHash":"2f3e11b36e76a404f5dbbfc897df10bdbac2445e","verifierVersion":"0.1.0+ad90ce4c"}]}