{"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":"8fc9dbca-1ed6-41f5-b61b-ce5c0375a20a","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"af97a045c543b4d68b14ad1f2111d892c6b768a6a441f8840fe8b399eaa0a420","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":"09c0869adde12114608c1cc47c94be84e0da4735bb7708f937b144e7ebdcfed1","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":"739f3d0fb25ebb4f166415c0e755db86b27b849b1baeb0b7d03c7e2f7be3d130","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"aaff8fa84540a9329002cdf570e80c482e683c7b692f2cf3125a1ca0deedf5aa","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":"5b260fa4821cd5cf3890793a5f2c9f98e89864c3e2a81070af9bc4f23f4bc82e","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":"58bc4a3fe96c21db434c9c6186d6c2f6a9ae5b882653eede8a67a7ad27a187f0","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"d59561da3b378461b7c749f6bd5084477d44993aa94972ddea90f542535751df","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"d59561da3b378461b7c749f6bd5084477d44993aa94972ddea90f542535751df","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"0657847a1323cbc54919e2fa5dea522483bff1b4b14fedd81d2a98d5b556daab","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":"c95d59375cdfe8d4b6f4ee1f4804dd2eff669842e8971633a99e0402ccce491c","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"4c76766b00591dfe982fe85d3de589fdb2f810e5137c301155d1c5e36a1d3b23","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"4c76766b00591dfe982fe85d3de589fdb2f810e5137c301155d1c5e36a1d3b23","skillId":"write-foundry-tests","state":"accepted"}],"objective":"Token name: Test\r\nToken symbol: TEST\r\nLogo: https://www.si-md.xyz/logo.jpg\r\n\r\nTest of the SIMD v2 tokenomics on Robinhood Chain. A plain token whose pool hook sends a small fee to the SIMD Vault, which splits it every week between holders, Identity.md swarm jobs, the dev and marketing. 10% of the supply and the launch's anti-sniper fee go to a CompensationVault that refunds the wallets that lost $SIMD in the October 2026 migration.\r\n\r\nWho can change it: nobody. No owner, no admin, no setters, no upgrade path, no pause, no blacklist. Every address and rate is fixed at deploy.\r\n\r\nContracts:\r\n1. TestHook, the Uniswap v4 hook on the TEST/IMD pool this launch opens. On every buy and every sell it takes 0.75% of the swap from the IMD side and keeps it in IMD (do NOT sell it for ETH), sent to the SIMD Vault at 0xbba92992309c61e1c483ab630d0530f2c9edede7. Together with the factory's 1.25% trading fee a trade costs 2% in total.\r\n   - Anti-sniper on buys only: for the first 30 minutes (1800 seconds) after the pool opens, measured from the block the pool was initialized, the hook's total fee on a buy starts at 50% and falls linearly to the standing 0.75%. Everything above the standing 0.75% goes, in IMD, to the CompensationVault at 0x9f4419712cc22f615e393cc846b927be3d7d802c. Sells never pay it.\r\n   - The hook never reverts a real swap. No other contracts.\r\n\r\nTests: fork tests against Robinhood Chain: the 0.75% on buys and sells lands in the vault in IMD, the anti-sniper curve (50% at 0 s, 25.375% at 900 s, 0.75% from 1800 s) and that its extra part lands in the CompensationVault, sells never pay it, and nothing can redirect the fee.\r\n","parentJobId":null,"planHash":"d66ca67ee686df7996a519ff5b2191047376cd0f2bbb74e27a19d749b454ed85","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"8fc9dbca-1ed6-41f5-b61b-ce5c0375a20a","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-2391-test"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"52586","feedbackHash":"cf79200bf383678fd21e62ef3c560b29fbbf1ecb46116b3a948a22acc76f6a80","nodeKey":"audit_economics","submissionHash":"af97a045c543b4d68b14ad1f2111d892c6b768a6a441f8840fe8b399eaa0a420","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52251","feedbackHash":"5c71501a5d4ff1d0ab91cbc9e275cbdacce31b4db482df20c6c1246e6903d82c","nodeKey":"audit_flow","submissionHash":"09c0869adde12114608c1cc47c94be84e0da4735bb7708f937b144e7ebdcfed1","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51168","feedbackHash":"7dff150314cb483c0def3ebcdb5f166b425324a0d98fb7d216982cccf3a5646b","nodeKey":"audit_judge","submissionHash":"739f3d0fb25ebb4f166415c0e755db86b27b849b1baeb0b7d03c7e2f7be3d130","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52164","feedbackHash":"7e1c5bb67fd41be24cc1869742835446d7e63ed81a226cdf68f5e16050ca0d75","nodeKey":"audit_math","submissionHash":"aaff8fa84540a9329002cdf570e80c482e683c7b692f2cf3125a1ca0deedf5aa","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51232","feedbackHash":"961b367d620bb372a3a4dd09beab8982670d2327e5ef6b6a9d746d60d5e39794","nodeKey":"audit_permissions","submissionHash":"5b260fa4821cd5cf3890793a5f2c9f98e89864c3e2a81070af9bc4f23f4bc82e","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52324","feedbackHash":"6dda6be1f4b59c0a89434d0125bcffb0504118d0eda3586d922ec510dd65ab30","nodeKey":"build_contract_project","submissionHash":"58bc4a3fe96c21db434c9c6186d6c2f6a9ae5b882653eede8a67a7ad27a187f0","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52011","feedbackHash":"f8c634a9df8634562f8c0a99d6183a9c0c0f88aa6b8d8807078c0fee7c5f8690","nodeKey":"manifest","submissionHash":"0657847a1323cbc54919e2fa5dea522483bff1b4b14fedd81d2a98d5b556daab","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51019","feedbackHash":"e645d52b2de0e7ca042107c240a536ae1d0a3e62215153ed70d9ed1a6d16200d","nodeKey":"write_foundry_tests","submissionHash":"c95d59375cdfe8d4b6f4ee1f4804dd2eff669842e8971633a99e0402ccce491c","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"055d0b851558d6f006c31bdd5ee9db8a0fb95038a955b0f897390e1f638d0904","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"65f6d78ca0fba28da3324f6662b11bb7345cb7fe93d10d4e700d5d438ac8004c","device":"a406deaac63a93b0","findings":[],"hash":"0657847a1323cbc54919e2fa5dea522483bff1b4b14fedd81d2a98d5b556daab","nodeId":"7ccf8ee5-7134-411a-af9a-44b7095d73ca","outcome":"completed","summary":"launch.json is written and verified. Only that file is new in the working tree.\n\n**What the manifest declares**\n\n- Hook `TestHook` with constructor arguments `$poolManager`, `$token`, `$factory`, matching the three-parameter constructor in declaration order. There is no owner argument because the brief forbids any owner or admin, and the hook has none.\n- Permissions beforeInitialize, beforeSwap, afterSwap, beforeSwapReturnDelta, afterSwapReturnDelta. These are exactly the five flags the implementation's getHookPermissions returns.\n- Token `TestToken`, name Test, symbol TEST, 18 decimals, no constructor arguments.\n- Pool pairs with IMD at the required lowercase address, fee 12500, tick spacing 60, initial price as the launch facts give it. The hook's beforeInitialize accepts that fee and spacing.\n- Notes run to 3742 characters, under the 4000 cap, and cover the fee mechanics, anti-sniper curve, the fixed vault recipients, the claim fallback, and that supply and allocations come from the launch policy.\n\n**Checks run**\n\n| Check | Result |\n| --- | --- |\n| JSON parses, first key is `kind: univ4_hook` | pass |\n| forge build | pass |\n| forge test with the verifier's fuzz seed | 81 passed, 0 failed, 2 skipped |\n\nThe two skipped tests are the Robinhood fork suite, which skips cleanly when no RPC is configured. No file other than launch.json was changed, and no protected path was touched.","treeHash":"79786d7964c9c7736651b45ac18c34ba5efcb3d8","usage":{"cachedInputTokens":342295,"inputTokens":194,"model":"claude-fable-5-1","outputTokens":4850,"runtime":"claude","turns":8,"wallClockMs":138306}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"1731fbfe0c4574fb","findings":[{"citation":"resolved","description":"The hook charges only inside beforeSwap/afterSwap. It enables no beforeAddLiquidity/beforeRemoveLiquidity callback, so anyone can place a single-sided position in this pool with no fee. A position entirely above the current price (when IMD is currency0: tick range above the current tick) holds only IMD and is a resting buy order; every seller that pushes the price through it converts that IMD into TEST at the range price. The position owner then removes the liquidity and holds TEST without having paid the 50%-to-0.75% anti-sniper levy (none of it reaches the CompensationVault) or even the standing 0.75% (nothing reaches the SIMD Vault). The mirror position (TEST-only below the price) lets any TEST holder sell to buyers forever without the 0.75% that the brief says every sell pays. The counterparty swapper pays its own fee as usual; the leak is the fee on the LP side's conversion. This is reachable by any unprivileged wallet during the anti-sniper window (sellers in the first 30 minutes are the swarm MerkleDistributor and CompensationVault recipients) and indefinitely afterwards for the 0.75% leg. Because the brief says the vault must receive 0.75% of 'every buy and every sell' and the anti-sniper must tax early acquisitions, this is a broken guarantee rather than a loss of existing funds, so medium. Fixes within the one-contract design: enable beforeAddLiquidity and charge the IMD side of single-sided (out-of-range) deposits at the current buy rate, or restrict liquidity provision to the factory/position owner while the anti-sniper window is open, or document the gap explicitly if accepted. Note the factory's zero-change fee collection must still pass any added callback (it is a modifyLiquidity with liquidityDelta 0).","line":82,"path":"src/TestHook.sol","reproduction":"Local v4 PoolManager, MockIMD etched at the IMD address, TestToken deployed so IMD is currency0, pool initialized at 1:1 by the factory, seeded with 1e24 liquidity over [-600,600]; block.timestamp == initializedAt (t = 0, so a buy pays 50%). (1) Sniper (a PoolActor holding 10,000 IMD, no TEST) calls modifyLiquidity(key, tickLower=60, tickUpper=120, liquidityDelta=1e24): delta.amount0 = -2986382804598881661260 (2,986.38 IMD), delta.amount1 = 0; hook not called; vault and CompensationVault balances unchanged. (2) An ordinary seller swaps 10,000 TEST exact-input (oneForZero): price rises through [60,120]; SIMD Vault gets 0.75% of the seller's IMD output; CompensationVault gets 0. (3) Sniper calls modifyLiquidity(key, 60, 120, -1e24): receives 3051524259317710955470 TEST (3,051.52 TEST) and ~0 IMD. Expected per brief: an acquisition of TEST for 2,986.38 IMD at t = 0 pays 50% hook fee, i.e. 1470793531264949218170 (1,470.79 IMD) to the CompensationVault and 22.40 IMD to the SIMD Vault. Actual: CompensationVault delta 0, SIMD Vault delta 0 from the sniper, sniper holds 3,051.52 TEST. Verified with test/scratch/LpBypass.t.sol (built on test/helpers/HookTestBase.sol and PoolActor.sol): forge test --match-path test/scratch/LpBypass.t.sol -vv passes with those logged values. Sell side, verified with test/scratch/LpBypassSell.t.sol at t = 3600 s: a TEST holder adds liquidity 1e24 over [-120,-60] (amount1 = -2986382804598881661260 TEST, amount0 = 0), a buyer swaps 10,000 IMD exact-input through it, the holder removes and receives 3051524259317710955470 IMD; SIMD Vault delta from the holder's sale is 0 where a direct sell of that IMD would pay 22886431944882832166 (22.89 IMD).","severity":"medium","snippet":"    function getHookPermissions() public pure returns (Hooks.Permissions memory p) {\n        p.beforeInitialize = true;\n        p.beforeSwap = true;\n        p.afterSwap = true;\n        p.beforeSwapReturnDelta = true;\n        p.afterSwapReturnDelta = true;\n    }","title":"Hook fees (anti-sniper and standing 0.75%) are bypassed by single-sided liquidity positions, which no liquidity callback gates"},{"citation":"resolved","description":"The hook's safety argument ('never reverts a real swap', fee always lands in IMD) rests on IMD being a plain ERC-20. Read live at block 94,406,433 (chain id 4663), 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127 is a LayerZero OFT whose runtime exposes setBlocked(address,bool), blocked(address), transfersEnabled(), enableTransfers(), setV4Config(address,address,bool) and poolManager(), owned by 0x047F606fD5b2BaA5f5C6c4aB8958E45CB6B054B7. Today transfersEnabled() is true, poolManager() is zero and neither 0xbBa92992309C61E1c483ab630d0530F2C9edEDE7 nor 0x9f4419712cc22f615e393cC846b927BE3d7D802c is blocked, so the fork suite (run live, 50/50 pass) is representative now. The hook already tolerates a blocked recipient (take fails, claim is minted, redeemFees retries), so that case is covered; what the code and README do not state is that IMD's owner can (a) block a vault, after which that vault's share accrues as a claim in the hook with no path but waiting for the unblock, and (b) via setBlocked on the PoolManager or setV4Config, stop IMD moving to or from the manager, which halts every swap in the pool independent of the hook. No unprivileged amplifier exists, so this is a trust note for the author and deployer, not a defect to fix in the hook. The fork tests also use a freshly deployed PoolManager rather than the chain's real manager, so no test exercises IMD against whatever setV4Config may later gate.","line":18,"path":"src/TestHook.sol","reproduction":"cast call 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127 'owner()(address)' --rpc-url https://rpc.mainnet.chain.robinhood.com -> 0x047F606fD5b2BaA5f5C6c4aB8958E45CB6B054B7; 'transfersEnabled()(bool)' -> true; 'poolManager()(address)' -> 0x0; 'blocked(address)(bool)' for both vault addresses -> false; cast selectors --resolve on its runtime code lists setBlocked(address,bool), enableTransfers(), setV4Config(address,address,bool). Expected per README line 67: a standard ERC-20 with no owner-controlled transfer restrictions. Actual: owner-controlled blocklist and transfer gating exist; the hook's behaviour under a blocked vault is covered by test_failedVaultTransferDoesNotRevertSwapAndAnyoneCanRedeem, under a blocked PoolManager it is a full pool halt outside the hook's control.","severity":"info","snippet":"/// @dev IMD must be a standard, non-rebasing ERC-20. All amounts are in its smallest units.","title":"IMD on Robinhood Chain is an owner-administered OFT with a blocklist and a v4 config switch, not the plain ERC-20 the hook assumes"}],"hash":"09c0869adde12114608c1cc47c94be84e0da4735bb7708f937b144e7ebdcfed1","nodeId":"04e1e2b8-daf0-49d8-849b-2c3f8d98b32e","outcome":"completed","summary":"The review is complete and the findings file is written at the repository root.\n\n**What I covered.** I traced every entry point of `src/TestHook.sol` and `src/TestToken.sol` against the Execution Trace, Periphery and First Principles guides: the rollback quote in beforeSwap, the delta sign conventions against the vendored v4 `Hooks` and `PoolManager` code, the gross-up and split arithmetic on every swap mode and partial fill, the claim-and-redeem fallback, and the manifest against the implementation. The local suite passes (77 tests) and the Robinhood fork suite passes live against the real IMD contract (50 tests at block 94,406,433). The hook's swap control flow holds on every path I traced.\n\n**Findings written to `.imd-findings.json`:**\n\n- **Medium: single-sided liquidity bypasses both hook fees.** No liquidity callback is enabled, so any wallet can place an IMD-only position just above the price during the anti-sniper window and let sellers fill it. Reproduced in a scratch test: about 2,986 IMD became about 3,051 TEST at t = 0 with zero paid to either vault, where a direct buy would have sent about 1,471 IMD to the CompensationVault. The mirror TEST-only position lets any holder sell to buyers forever without the standing 0.75%, also reproduced. Fix options are listed in the finding.\n- **Info: IMD is not a plain ERC-20.** The live token is an owner-administered LayerZero OFT with `setBlocked`, transfer gating and a `setV4Config` switch. Today its state is permissive and the hook already tolerates a blocked recipient via claims, but the README's standard-token assumption is not accurate, and the fork suite uses a fresh PoolManager rather than the chain's real one.\n\n**Coverage.** All nine listed entry points have rows, all `holds`, plus five invariant rows, one of which points to the medium finding. No proofs are attached since nothing reached high severity. The two scratch tests live under the ignored `test/scratch/` directory and no tracked file was changed.","treeHash":null,"usage":{"cachedInputTokens":1517879,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":43531,"runtime":"claude","turns":30,"wallClockMs":651014}},{"artifacts":[],"attempt":1,"bundleHash":"c70982d93ba9751afa213ef86e600b41dabf93016c7abe934eab8d186b48cd7d","device":"3d55f5b0ed8fa3df","findings":[],"hash":"58bc4a3fe96c21db434c9c6186d6c2f6a9ae5b882653eede8a67a7ad27a187f0","nodeId":"53115ffb-6a31-4375-b1c1-5d3c35191a49","outcome":"completed","summary":"Implemented the immutable TEST token and fee hook, with IMD routing, anti-sniper decay, and deferred-fee redemption.\n\nVerified:\n- `forge build` and `forge fmt --check` pass.\n- 55 local tests pass.\n- 36 Robinhood fork tests pass.\n\n[README](README.md) documents deployment and assumptions, including fee compounding: the stated rates yield 1.990625% on fully filled exact-input buys after launch.","treeHash":"cad8ad14550ae05ad95d8050e5d405f66e757984","usage":{"cachedInputTokens":3274752,"inputTokens":170409,"model":"gpt-6-astra","outputTokens":44969,"runtime":"codex","turns":9,"wallClockMs":1268569}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"a18a0c6087e1362f","findings":[{"citation":"resolved","description":"The hook's whole value flow is denominated in the hard-coded IMD token. On Robinhood Chain (chain id 4663) that address is not a plain ERC-20: it is a LayerZero OFT ('BridgedFP') with an owner (owner() = 0x047F606fD5b2BaA5f5C6c4aB8958E45CB6B054B7) and the privileged entry points setBlocked(address,bool), enableTransfers()/transfersEnabled(), and setV4Config(address poolManager,address,bool)/poolManager() (selectors extracted from the deployed runtime code; poolManager() is currently the zero address, transfersEnabled() is true, no address is blocked). I exercised those powers on a fork of live state against the hook: (a) setBlocked(SIMD_VAULT,true) makes every PoolManager.take to the vault revert, so each swap's 0.75% is minted as an ERC-6909 claim (pendingVault grows, FeeDeferred) and redeemFees() cannot deliver it until the IMD owner unblocks the vault; the swap itself still succeeds, so the hook's deferral path behaves as designed. (b) setBlocked(poolManager,true) or setV4Config(poolManager, <any address>, true) makes every IMD transfer to or from the PoolManager revert with 'BridgedFP: blocked' / 'BridgedFP: v4 transfer not approved', so every buy and sell on the TEST/IMD pool reverts at the trader's settlement or take, and any already-deferred fee can never be redeemed while the setting stands. None of this is a bug in the hook's own code: its onlyPoolManager/onlyFactory guards hold, the fee math is symmetric across all four swap modes, and a failed take degrades to a claim rather than a revert. It is a trust gap in the assurances around it: README.md and the manifest notes say 'nobody can change it' and that the fee 'is never swapped for ETH' and 'lands in the vault in IMD', the hook's NatSpec says 'IMD must be a standard, non-rebasing ERC-20', and the Robinhood fork suite deploys a fresh PoolManager, so IMD's v4-mode restriction (which keys on the configured poolManager) is never exercised against the real manager the factory will use. The IMD owner, a third party to this launch, can therefore pause the pool, hold the vaults' fees as unredeemable claims, or require an approval the hook does not know how to obtain, and a reader of the deliverables is told the opposite. The author should state this dependency in the README/notes, and the launch rehearsal must trade through the real Robinhood PoolManager with IMD's live configuration (including the case where poolManager() is set) before the pool opens.","line":22,"path":"src/TestHook.sol","reproduction":"Fork Robinhood Chain (forge test --fork-url https://rpc.mainnet.chain.robinhood.com), deploy PoolManager, TestToken and TestHook as test/helpers/HookTestBase.sol does with _setup(true,true,true), vm.warp(opened+1800). Case A: vm.prank(0x047F606fD5b2BaA5f5C6c4aB8958E45CB6B054B7); IMD.setBlocked(0xbBa92992309C61E1c483ab630d0530F2C9edEDE7,true); exact-input buy of 1000e18 IMD. Observed: swap succeeds, vault IMD balance +0, hook.pendingVault()=7.5e18, redeemFees() leaves pendingVault at 7.5e18; after setBlocked(vault,false) redeemFees() pays 7.5e18. Expected per README: 7.5e18 lands in the vault on the swap. Case B: vm.prank(owner); IMD.setV4Config(address(manager), address(hook), true) (or any second address, or setBlocked(address(manager),true)); then the same buy and an exact-input sell of 1000e18 TEST. Observed: both revert, buy with Error('BridgedFP: v4 transfer not approved') from the trader's IMD settlement, sell with the PoolManager's wrapped ERC-20 transfer failure carrying the same string from the trader's take; no fee is recorded. Expected per README/notes: trading and fee delivery cannot be stopped by anyone. Baseline with no owner action: the same buy pays exactly 7.5e18 IMD to the vault.","severity":"low","snippet":"    address public constant IMD = 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127;","title":"Trust gap: the live IMD token is owner-controlled and can halt the TEST/IMD pool or strand the hook's deferred fees, which no document or test states"},{"citation":"resolved","description":"unlockCallback burns and pays only the amounts the hook itself recorded in pendingVault and pendingCompensation. The hook's ERC-6909 IMD balance at the PoolManager can be raised by any holder with PoolManager.transfer(hook, uint256(uint160(IMD)), amount), and plain IMD or TEST can be transferred to the hook address directly; neither path updates the pending counters, and the contract has no sweep, so such funds are frozen forever. README.md already says 'Unsolicited transfers or claims sent to the hook have no administrative rescue path', so this is by design and recorded for the judge as an accepted, documented limitation rather than a defect. The hook's own accounting is unaffected: its claim balance is always at least pendingVault + pendingCompensation, and redeemFees() remains callable.","line":181,"path":"src/TestHook.sol","reproduction":"Local setup as test/helpers/HookTestBase.sol _setup(false,true,true). A holder of IMD claims (e.g. a router that minted claims) calls manager.transfer(address(hook), uint256(uint160(IMD)), 1e18). Then hook.redeemFees(): pendingVault and pendingCompensation are 0, so unlockCallback burns nothing; manager.balanceOf(hook, imdId) stays 1e18 with no function able to move it. Expected: nothing (documented); noted so the judge does not count it against the author twice.","severity":"info","snippet":"        uint256 vault = pendingVault;\n        uint256 compensation = pendingCompensation;","title":"IMD claims or tokens sent to the hook by anyone other than its own fee path are unrecoverable"}],"hash":"5b260fa4821cd5cf3890793a5f2c9f98e89864c3e2a81070af9bc4f23f4bc82e","nodeId":"cfd4bf36-b1c6-4b5a-8f2b-bceeff6ece07","outcome":"completed","summary":"Review complete. The findings file is written and valid, scratch files are removed, and the working tree has no stray changes.\n\n**Outcome:** The hook's permission model holds. I found no exploitable access-control, trust-gap or asymmetry defect in `src/TestHook.sol` or `src/TestToken.sol`. The one substantive result is an undocumented external trust dependency, reported as low.\n\n**What I verified**\n\n- Every callback is guarded by `onlyPoolManager`, initialization is restricted to the constructor-given factory and the exact TEST/IMD 12500/60 key, and it can run once only.\n- The self-quote in `quoteSwap` is self-call-only and always reverts, so the nested swap rolls back. I confirmed v4 skips the hook on its own nested swap.\n- Buy and sell legs charge the same rate on the same gross IMD base across all four swap modes. Sells never pay the anti-sniper part.\n- `redeemFees` is permissionless but takes no address or amount, and a failed delivery re-mints the claim. Reentry during a swap reverts.\n- The manifest matches the constructor order, permissions and pool constants. Notes are under the 4,000 character limit.\n- The project's hook suite passes locally. I also ran its non-fuzz fork suite against live Robinhood Chain state, and all 38 tests passed.\n\n**Findings written to `.imd-findings.json`**\n\n1. **Low.** The live IMD token at the hard-coded address is an owner-controlled LayerZero OFT with `setBlocked`, `enableTransfers` and `setV4Config`. On a fork I showed the owner can block the vault, which defers every fee as a claim until unblocked, or enable the token's v4 mode, which makes every buy and sell on the pool revert. The hook behaves correctly in both cases, but the README and manifest notes say nobody can stop trading or fee delivery, and the fork suite uses a fresh PoolManager so this mode is never exercised.\n2. **Info.** Claims or tokens sent to the hook by outsiders are unrecoverable. The README already documents this, so it is recorded as an accepted limitation.\n\n**Coverage:** All nine listed entry points are marked `holds` with reasons, plus rows for the no-admin invariant, the fee-split asymmetry, the manifest, and the IMD dependency that carries finding 1.\n\n**Not reached:** I could not determine what `setV4Config`'s second argument approves, since the IMD source is not published on any explorer I could reach. The launch rehearsal should trade through the real Robinhood PoolManager with IMD's live configuration before the pool opens.","treeHash":null,"usage":{"cachedInputTokens":2122708,"inputTokens":610,"model":"claude-fable-5-1","outputTokens":50723,"runtime":"claude","turns":32,"wallClockMs":814696}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"6208734cdf5317a1","findings":[{"citation":"resolved","description":"Merged from audit_flow. The hook charges only in beforeSwap/afterSwap and enables no liquidity callback, so anyone may mint an out-of-range position in the TEST/IMD pool. An IMD-only position just above the current price is a resting buy order: every TEST seller that pushes the price through it converts the position's IMD into TEST at the range price, and the position owner then withdraws TEST having paid neither the 50%-to-0.75% anti-sniper levy (nothing reaches the CompensationVault) nor the standing 0.75% (nothing reaches the SIMD Vault); the mirror TEST-only position lets a holder sell to buyers without the 0.75% on sells. The counterparty swapper still pays its own fee, so this is not a loss of existing funds; it is a gap between the brief's intent (every acquisition in the first 30 minutes pays the levy) and what a swap-only fee can enforce. Recalibrated to low rather than the specialist's medium: the brief defines the fee on swaps and asks for no liquidity gating, the bypass needs willing sellers inside the window and carries range-order execution risk, and it is a property of every swap-fee hook rather than a defect in this code. The author should either accept and document it, or (if the levy must cover LP-side acquisition) add beforeAddLiquidity gating for out-of-range deposits during the window while still letting the factory's zero-change fee collection through.","line":82,"path":"src/TestHook.sol","reproduction":"test/scratch/Judge.t.sol test_lpBypassAtOpening (HookTestBase._setup(false,false,true): IMD is currency0, pool at 1:1 seeded with 1e24 liquidity on [-600,600], block.timestamp == initializedAt so a buy would pay 50%). A sniper PoolActor holding 10,000 IMD and no TEST calls modifyLiquidity(key, 60, 120, 1e24): amount0 = -2986382804598881661260 IMD, amount1 = 0, no hook call. An ordinary seller swaps 10,000 TEST exact-input: SIMD Vault receives 73509312212437253919 wei (the seller's own 0.75%), CompensationVault receives 0. The sniper then removes the position and receives 3051524259317710955470 TEST and 0 IMD; vault and CompensationVault balances are unchanged by the sniper. Expected under the brief's anti-sniper intent: acquiring ~3,051 TEST for 2,986 IMD at t = 0 pays 50% (1470793531264949218170 wei to the CompensationVault). Actual: 0 to either vault.","severity":"low","snippet":"    function getHookPermissions() public pure returns (Hooks.Permissions memory p) {\n        p.beforeInitialize = true;\n        p.beforeSwap = true;\n        p.afterSwap = true;\n        p.beforeSwapReturnDelta = true;\n        p.afterSwapReturnDelta = true;\n    }","title":"Single-sided liquidity positions acquire TEST (or sell it) with no hook fee, so the anti-sniper levy and the 0.75% can be sidestepped by an LP during and after the window"},{"citation":"resolved","description":"Merged from audit_economics and audit_math (same root cause). For an exact-output sell (TEST in, IMD out, amountSpecified > 0) beforeSwap grosses the request up by fee = requested * rate / (FEE_DENOMINATOR - rate) and adds it with checked int256 arithmetic. Whenever requested > type(int256).max - fee (about 0.9925 * 2^255 at the standing rate) the addition panics with 0x11. PoolManager wraps the panic in WrappedError(hook, 0x575e24b4, Panic(0x11), 0xa9e35b2f) and the whole swap reverts, while the identical request on a hookless pool is a legal v4 'take as much IMD as the price limit allows' exact-output swap that partially fills. This breaks the brief's 'never reverts a real swap' guarantee for routers that use int256.max as an exact-output sentinel; no funds are at risk. The buy-side boundary (type(int256).min) is handled by _abs and tested; the sell side has no equivalent guard. Fix: compute the gross-up in uint256 and clamp the quoted amount (e.g. to type(int128).max or type(int256).max) before converting.","line":132,"path":"src/TestHook.sol","reproduction":"test/scratch/Judge.t.sol test_exactOutputSellMaxRequestReverts: HookTestBase._setup(false,false,true), vm.warp(opened + 2000), hookless control pool with the same currencies, fee 12500, spacing 60, 1e24 liquidity on [-600,600]. swap(zeroForOne = !imdIsCurrency0, amountSpecified = type(int256).max, sqrtPriceLimit = MAX_SQRT_PRICE - 1): control pool fills partially, delta0 = +29553010879137169680827 IMD, delta1 = -30838469241430640163516 TEST. Same call on the hook pool: reverts with WrappedError(hook, 0x575e24b4, 0x4e487b71...11, 0xa9e35b2f). The same call with amountSpecified = 0.990 * type(int256).max succeeds. Expected: the same partial fill minus 0.75% of the IMD output to the SIMD Vault.","severity":"low","snippet":"            quoted.amountSpecified = params.amountSpecified + fee.toInt256();","title":"Exact-output sell with a near-maximum amountSpecified overflows in beforeSwap and reverts a swap the bare pool would partially fill"},{"citation":"resolved","description":"From audit_math; reproduced. _pay moves the fee out of the PoolManager with take() inside beforeSwap/afterSwap, while the swapper's unlock is still open. v4's settle() credits balanceOf(manager) - reserves recorded at sync(). A router following the pay-first pattern (sync(IMD), transfer input, swap, settle) is therefore credited input - fee instead of input, finishes the callback with an IMD delta of -fee, and PoolManager.unlock reverts with CurrencyNotSettled. The same router call succeeds on a hookless pool. Uniswap's own V4Router/Universal Router settle after the swap and are unaffected, so the impact is integrator compatibility, not loss; but README.md says the hook has 'no intentional restriction on valid swaps' and nothing documents the ordering requirement. Fix options: mint the ERC-6909 claim during the swap and deliver only through redeemFees (the brief's 'sent to the SIMD Vault' is met by the permissionless redemption), or document that integrators must settle after the swap.","line":221,"path":"src/TestHook.sol","reproduction":"test/scratch/Judge.t.sol test_payFirstRouterRevertsOnHookPool: HookTestBase._setup(false,false,true), vm.warp(opened + 1800) so the rate is 0.75%. A router whose unlockCallback does sync(IMD); IMD.transfer(manager, 100e18); swap(key, SwapParams(imdIsCurrency0, -100e18, MIN_SQRT_PRICE+1), \"\"); settle(); take(TEST, router, output). On a hookless pool with the same liquidity settle() returns 100000000000000000000 and the unlock succeeds. On the hook pool beforeSwap takes 0.75e18 IMD to the SIMD Vault between the router's sync and settle, settle credits 99.25e18, and unlock reverts with CurrencyNotSettled (0x5212cba1). Expected: the swap executes and the vault receives 0.75e18.","severity":"low","snippet":"        try poolManager.take(Currency.wrap(IMD), recipient, amount) {","title":"The mid-swap take under-credits routers that pay their IMD input before swapping, so their trades revert with CurrencyNotSettled on this pool only"},{"citation":"resolved","description":"Merged from audit_flow, audit_economics and audit_permissions (same root cause, three reports). The hook has no admin, but every fee is paid in IMD at 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127, which on Robinhood Chain (chain id 4663) is an owned LayerZero OFT exposing setBlocked(address,bool), blocked(address), transfersEnabled()/enableTransfers() and setV4Config(address,address,bool)/poolManager(). Its owner can (a) block either vault, after which each swap's share is minted as an ERC-6909 claim (pendingVault/pendingCompensation grow, FeeDeferred) and redeemFees() cannot deliver it until unblocked, which the hook's deferral path handles correctly; and (b) block the PoolManager or set a v4 config that refuses it, which halts every buy and sell on the TEST/IMD pool independently of the hook. No unprivileged amplifier exists and nothing can redirect the fee, so this is not a defect in the hook. It is a trust dependency the README and launch notes should state next to 'no owner, no admin', and the launch rehearsal should trade through the real Robinhood PoolManager with IMD's live configuration (the fork suite deploys a fresh manager, so IMD's poolManager()-keyed gate is never exercised).","line":18,"path":"src/TestHook.sol","reproduction":"Live reads on chain 4663 (public endpoint https://rpc.mainnet.chain.robinhood.com) on 2026-10-21: cast call 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127 'owner()(address)' -> 0x047F606fD5b2BaA5f5C6c4aB8958E45CB6B054B7; 'transfersEnabled()(bool)' -> true; 'poolManager()(address)' -> 0x0; 'blocked(address)(bool)' for 0xbBa92992309C61E1c483ab630d0530F2C9edEDE7 and 0x9f4419712cc22f615e393cC846b927BE3d7D802c -> false; symbol() -> IMD. Local equivalent of (a): MockIMD.reject(VAULT,true) then any buy: swap succeeds, vault balance unchanged, hook.pendingVault() == fee, redeemFees() leaves it pending until reject(VAULT,false) (the repository's test_failedVaultTransferDoesNotRevertSwapAndAnyoneCanRedeem). Expected per README line 67 and the launch notes: a standard ERC-20 that nobody can gate. Actual: owner-controlled blocklist and transfer gating exist on the live token.","severity":"info","snippet":"/// @dev IMD must be a standard, non-rebasing ERC-20. All amounts are in its smallest units.","title":"Fee delivery and the pool itself depend on the owner-administered IMD OFT, which the README's 'nobody can change it' and 'standard ERC-20' assumptions do not state"},{"citation":"resolved","description":"From audit_math; confirmed by arithmetic and the repository's own test. The hook takes 0.75% of the gross IMD side and the 1.25% LP fee applies to the amount that reaches the AMM, so an exact-input buy of 100 IMD after the window pays 0.75 IMD to the vault and 1.25% of 99.25 = 1.240625 IMD in LP fee: 1.990625% in total. The README and launch.json notes disclose this explicitly, so it is a documented property of composing two percentage fees rather than a defect; recorded so the brief's 'a trade costs 2% in total' can be matched against it.","line":27,"path":"src/TestHook.sol","reproduction":"HookTestBase._setup, vm.warp(opened + 1800), exact-input buy of 100e18 IMD: SIMD Vault receives 0.75e18 (test_buyAt1800SecondsChargesOnlyStandingFee); the AMM receives 99.25e18 of which 1.25% = 1.240625e18 is LP fee; total deductions 1.990625e18 of 100e18. Expected per brief wording: 2.0e18.","severity":"info","snippet":"    uint256 public constant BASE_FEE_PPM = 7_500;","title":"Total cost of a fully filled trade is 1.990625%, not the brief's flat 2%"}],"hash":"739f3d0fb25ebb4f166415c0e755db86b27b849b1baeb0b7d03c7e2f7be3d130","nodeId":"99e10c0c-7494-4b19-824b-16c5b8e24465","outcome":"completed","summary":"The review is complete and `.imd-findings.json` is written with 5 findings and a 12-row coverage record.\n\n**Outcome.** The hook's code holds up: every entry point was traced, the repository's own 81 local tests pass, and no critical or high defect exists. I kept 5 findings, all low or info, after reproducing them with a scratch suite in `test/scratch/Judge.t.sol`.\n\n**Findings kept**\n- **Low, LP-side fee bypass.** An IMD-only out-of-range position acquires TEST from sellers with no anti-sniper levy or 0.75%. Reproduced at t = 0: the sniper ends with 3,051 TEST and both vaults receive nothing from it. Downgraded from the specialist's medium because the brief defines the fee on swaps and this is inherent to any swap-fee hook.\n- **Low, int256 overflow on exact-output sells.** The gross-up addition at `src/TestHook.sol:132` panics for requests above about 0.9925 times the int256 maximum, where a hookless pool partially fills. Two specialists reported it; merged into one.\n- **Low, pay-first routers revert.** The mid-swap take between a router's sync and settle leaves it short by the fee, so its unlock fails with CurrencyNotSettled. Standard Uniswap routers settle after the swap and are unaffected.\n- **Info, IMD owner trust dependency.** Three specialists reported the same root cause; merged. Live reads on Robinhood Chain confirm the IMD token has an owner with blocklist and v4-gating powers. The hook's deferral path handles a blocked vault correctly, but the README should state the dependency.\n- **Info, 1.990625% total versus the brief's flat 2%.** Documented composition of two percentage fees.\n\n**Dropped.** The fee-rounding dust note and the unsolicited-claims note, both documented limitations with no defect.\n\n**Coverage.** Seven of nine entry points hold. beforeSwap and afterSwap are marked as findings for the overflow and the take-ordering issues. Three extra invariant rows record bytecode sizes, the fee-split underflow check, and claim backing.","treeHash":null,"usage":{"cachedInputTokens":973058,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":28481,"runtime":"claude","turns":19,"wallClockMs":415286}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"63458a1bd2b2d376","findings":[{"citation":"resolved","description":"beforeSwap grosses up an exact-output sell by adding the hook fee to the requested IMD output with checked int256 arithmetic. For requested >= ~0.9925 * 2^255 (fee/(D-fee) = 13,500,000/1,786,500,000 = 0.7557%, so requested + fee > 2^255 - 1) the addition panics with arithmetic overflow (Panic 0x11), which PoolManager wraps in Wrap__FailedHookCall, and the whole swap reverts. The same request on a hookless pool is a legal v4 'take as much IMD as the price limit allows' exact-output swap: the pool loops until the limit and settles only the filled part. The symmetric buy boundary (amountSpecified = type(int256).min) is handled by _abs and tested (test_minimumSignedBuyBudgetChargesOnlyItsPartialFill), but the sell side has no equivalent guard, so the 'never reverts a real swap' guarantee is broken for max-request exact-output sells. Boundary x precision seam: the overflow only appears at the top of the input domain and only in the IMD-specified sell branch; afterSwap-based modes are unaffected. No funds are at risk; a router that uses int256.max as a 'max' sentinel for an exact-output sell cannot trade through this pool. Fix: compute the gross-up in uint256 and clamp it (e.g. to type(int128).max, already the largest delta v4 can return), or treat an overflowing request as a full-liquidity request.","line":132,"path":"src/TestHook.sol","reproduction":"Local PoolManager, TEST/IMD pool initialised at 1:1 and seeded with liquidity 1e24 over ticks [-600, 600] (test/helpers/HookTestBase._setup(false, true, true)). Also initialise a hookless control pool with the same key and seed. Call swap(key, SwapParams(zeroForOne = !imdIsCurrency0, amountSpecified = type(int256).max, sqrtPriceLimit = MAX_SQRT_PRICE - 1), \"\"). Expected (what the control pool does): partial fill, trader receives 29,553,010,879,137,169,680,827 wei IMD (price limited), hook fee 0.75% on it. Actual on the hook pool: revert 0x90bfb865 (Wrap__FailedHookCall) with inner data 0x4e487b71...0011 (Panic: arithmetic overflow) from beforeSwap line 132. Threshold check: amountSpecified = 0.990 * 2^255 succeeds, 0.993 * 2^255 reverts.","severity":"low","snippet":"            quoted.amountSpecified = params.amountSpecified + fee.toInt256();","title":"Exact-output sell near the int256 maximum reverts in the hook's gross-up where the bare pool would partially fill"},{"citation":"resolved","description":"The hook pays its fee with PoolManager.take inside beforeSwap/afterSwap, which moves IMD out of the manager while the swapper's unlock is still open. v4's settle() credits balanceOf(manager) - reservesOf(sync). A router that follows the pay-first pattern (sync(IMD), transfer input, then swap, then settle) therefore gets credited input - fee instead of input: the fee the hook took in between is subtracted from what the router is deemed to have paid. The router's IMD delta ends at -fee, and the unlock reverts with CurrencyNotSettled (0x5212cba1). The identical router call succeeds on a hookless pool with the same key and liquidity. Boundary (external call) seam: _pay assumes a successful take is observationally neutral for the swapper; it is not for any caller whose sync precedes the swap. Uniswap's own periphery routers settle after the swap and are unaffected, but the README's claim that the hook 'has no intentional restriction on valid swaps' and that fee delivery is 'handled by the claim fallback' does not cover this case, and nothing in the repository documents the ordering requirement. Fix options: always mint the ERC-6909 claim during the swap and deliver only through redeemFees (the brief's 'sent to the SIMD Vault' is then satisfied by the permissionless redemption), or document that integrators must settle after swap. No funds are lost either way; the trade simply cannot execute through such a router.","line":221,"path":"src/TestHook.sol","reproduction":"Local PoolManager, TEST/IMD pool at 1:1 seeded with 1e24 liquidity over [-600, 600], warp to opened + 1800 so the rate is the standing 0.75%. Deploy a router whose unlockCallback does: sync(IMD); IMD.transfer(manager, 100e18); swap(key, SwapParams(zeroForOne = imdIsCurrency0, amountSpecified = -100e18, limit = MIN_SQRT_PRICE+1), \"\"); settle(); take(TEST, router, output). Expected: swap executes, settle credits 100e18, router nets 0 on IMD (this is what the hookless control pool returns: settle() credited 100000000000000000000). Actual on the hook pool: beforeSwap takes 0.75e18 IMD to the SIMD Vault between the router's sync and settle, settle credits 99.25e18, the router's IMD delta is -0.75e18 at the end of the callback, and PoolManager.unlock reverts with CurrencyNotSettled (returndata 0x5212cba1).","severity":"low","snippet":"        try poolManager.take(Currency.wrap(IMD), recipient, amount) {","title":"Mid-swap take under-credits routers that pay before swapping, so their buys revert with CurrencyNotSettled on this pool only"},{"citation":"resolved","description":"All four fee formulas floor. At the standing rate 13,500,000/1,800,000,000 = 0.75%, any gross IMD leg below 134 wei pays 0 (133 * 7500 / 1,000,000 = 0.9975 -> 0); at 50% the threshold is 2 wei. The README documents rounding down, and the dust is far below the gas cost of a swap, so it is not exploitable (a trader splitting 1 IMD into 134-wei swaps would need ~7.5e15 transactions). Recorded for completeness of the Math Precision 'fees round up' check; no change required.","line":128,"path":"src/TestHook.sol","reproduction":"Seeded local pool, warp to opened + 1800. Exact-input buy of 133 wei IMD: vault receives 0, compensation 0, swap succeeds (test_tinyBuysAndSellsDoNotRevertFromFeeRounding shows the 1-wei case). Exact-input buy of 134 wei: vault receives 1 wei.","severity":"info","snippet":"            fee = FullMath.mulDiv(requested, rate, FEE_DENOMINATOR);","title":"Hook fee rounds down to zero for IMD legs below 134 wei at the standing rate"},{"citation":"resolved","description":"The hook takes 0.75% of the gross IMD side and the pool's LP fee of 1.25% applies to the amount that reaches the AMM, so on an exact-input buy of 100 IMD after the window the vault gets 0.75 IMD and the LP fee is 1.25% of 99.25 = 1.240625 IMD: 1.990625% total, not 2.000%. Sells are symmetric (1.25% on TEST in, 0.75% on the resulting IMD out). The README and launch.json notes state this explicitly, so it is a disclosed property of composing two percentage fees rather than a defect; recorded so the judge can match it against the brief's 'a trade costs 2% in total'.","line":27,"path":"src/TestHook.sol","reproduction":"Seeded local pool, warp to opened + 1800, exact-input buy of 100e18 IMD: vault balance increases by 0.75e18 (test_buyAt1800SecondsChargesOnlyStandingFee); the AMM receives 99.25e18 of which 1.25% = 1.240625e18 is LP fee. Total deductions 1.990625e18 of 100e18.","severity":"info","snippet":"    uint256 public constant BASE_FEE_PPM = 7_500;","title":"Combined cost of a fully filled trade is 1.990625%, not the brief's flat 2%"}],"hash":"aaff8fa84540a9329002cdf570e80c482e683c7b692f2cf3125a1ca0deedf5aa","nodeId":"5e067d40-8920-40f4-8318-c8eccc0f7aea","outcome":"completed","summary":"The review is complete. The findings file is valid JSON with four findings and fourteen coverage rows, every cited line matches the tree, and the scratch tests are removed so the working tree is clean apart from the findings file.\n\n**What I checked**\n\nI audited the fee arithmetic in `src/TestHook.sol` against the Math Precision, Boundary and Numerical Gap guides. I traced all four swap modes by hand, proved the vault/compensation split cannot underflow, and confirmed that an exact-output sell nets exactly the requested IMD after rounding. The repository's 81 local tests pass, and its fork suite passed 50 of 50 against live Robinhood Chain state, where IMD has code, 18 decimals and both vault addresses hold code. A scratch fuzz of partial fills across amounts, price limits and times matched a hookless control pool exactly.\n\n**Findings**\n\n- **Low, line 132.** An exact-output sell requesting about 0.9925 times 2^255 or more overflows the hook's gross-up and reverts with an arithmetic panic. The bare pool accepts the same request as a price-limited partial fill, so the \"never reverts a real swap\" guarantee breaks at that boundary. The buy side at int256.min is guarded and tested; the sell side is not.\n- **Low, line 221.** The mid-swap take moves IMD out of the manager between a pre-paying router's sync and settle. Such a router is credited the input minus the fee and its unlock reverts with CurrencyNotSettled. The same router succeeds on a hookless pool. Uniswap's own routers settle after swapping and are unaffected, but the behaviour is undocumented.\n- **Info.** Fees floor to zero below 134 wei at the standing rate, which is disclosed and not exploitable.\n- **Info.** A fully filled trade costs 1.990625 percent rather than a flat 2 percent, which the README already states.\n\n**Coverage**\n\nAll nine listed entry points have rows. Only beforeSwap carries a finding; the rest hold. No part of the assigned area was left unreached.","treeHash":null,"usage":{"cachedInputTokens":1315746,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":43723,"runtime":"claude","turns":34,"wallClockMs":652314}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"66ceb1fcb4b0b455","findings":[{"citation":"resolved","description":"For an exact-output sell (TEST in, IMD out, amountSpecified > 0), beforeSwap grosses the requested IMD output up by fee = requested * 0.75% / 99.25% and adds it to the signed amountSpecified to build the internal quote. Checked int256 addition overflows whenever amountSpecified > type(int256).max - fee, which means any amountSpecified above about type(int256).max / 1.00756. PoolManager itself accepts such a swap: with a sqrtPriceLimitX96 it partially fills to the limit, and integrators use this pattern to mean \"sell until price X\". With this hook attached, the panic is wrapped by Hooks.callHook and the whole swap reverts. This breaks the brief's guarantee that the hook never reverts a real swap. The exact-input-buy path is not affected, because _abs and the subtraction requested - fee stay in range for type(int256).min. Fix: cap the grossed-up amount before converting it, e.g. compute it in uint256 and clamp it to type(int256).max (the quote then partial-fills at the price limit as before). Another option is to skip the gross-up when requested + fee would exceed int256.max, since such a request can only ever partial-fill.","line":132,"path":"src/TestHook.sol","reproduction":"Local v4 setup from test/helpers/HookTestBase.sol: _setup(false, true, true), so TEST is currency0 and the pool is seeded with liquidity 1e24 on ticks [-600, 600]. Warp 2000 s, then swap through PoolActor with SwapParams(zeroForOne = true (sell), amountSpecified = type(int256).max, sqrtPriceLimitX96 = getSqrtPriceAtTick(-120)). Control: the same call on an identical hookless pool (hooks = address(0)) with the same liquidity succeeds and partial-fills (delta0 = -6.093e21 TEST, delta1 = +5.981e21 IMD). Expected with TestHook: the same partial fill, minus 0.75% of the IMD output sent to the SIMD Vault. Actual: TestHook.beforeSwap reverts with panic 0x11 (arithmetic overflow), surfaced as WrappedError(hook, beforeSwap selector 0x575e24b4, 0x4e487b71...11, 0xa9e35b2f), and the swap fails. I reproduced this in a scratch Foundry test, which has since been removed.","severity":"low","snippet":"            fee = FullMath.mulDiv(requested, rate, FEE_DENOMINATOR - rate);\n            quoted.amountSpecified = params.amountSpecified + fee.toInt256();","title":"Exact-output sell with a near-max amountSpecified (\"sell until price limit\") overflows in beforeSwap and reverts a swap the pool itself would fill"},{"citation":"resolved","description":"The brief says nobody can change the fee flow. The hook itself has no admin, but the fee is always paid in IMD, and IMD (0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127) on Robinhood Chain is an owned LayerZero OFT. Its owner is 0x047F606fD5b2BaA5f5C6c4aB8958E45CB6B054B7, read on-chain at block ~94,404,141, and its bytecode exposes setBlocked(address,bool), blocked(address), enableTransfers()/transfersEnabled() and setV4Config(address,address,bool). If that owner blocks SIMD_VAULT or COMPENSATION_VAULT, every take() to that vault reverts. _pay then mints an ERC-6909 claim, and redeemFees() keeps failing for as long as the block stays. The fees are not lost and cannot be redirected, since the claims stay with the hook for the fixed recipients, but their delivery can be held off indefinitely by a party outside the hook. The deferral fallback handles this correctly. The point is that this trust assumption belongs in the README and the launch notes next to \"no owner, no admin\". Today both vaults return blocked = false, transfersEnabled() = true and poolManager() = 0.","line":221,"path":"src/TestHook.sol","reproduction":"State: the IMD owner calls setBlocked(0xbBa92992309C61E1c483ab630d0530F2C9edEDE7, true) (the local equivalent is MockIMD.reject(VAULT, true)). Any buy or sell on the TEST/IMD pool still succeeds. The vault's 0.75% goes to pendingVault instead of the vault: event FeeDeferred(SIMD_VAULT, fee), and the IMD balance of SIMD_VAULT does not change. redeemFees() leaves pendingVault unchanged for as long as the block holds. Expected under 'nobody can change it': fees reach the SIMD Vault on every swap. Actual: delivery is suspended at the discretion of the IMD owner.","severity":"info","snippet":"        try poolManager.take(Currency.wrap(IMD), recipient, amount) {","title":"Fee delivery depends on the owner-administered IMD token: its owner can block fee delivery to either vault indefinitely"}],"hash":"af97a045c543b4d68b14ad1f2111d892c6b768a6a441f8840fe8b399eaa0a420","nodeId":"0bde512e-2c41-4307-a004-ebbfceb9f739","outcome":"completed","summary":"I found no high or critical problems in the economics. The fee logic matches the brief, and I have two findings, written to `.imd-findings.json` with a coverage row for every entry point. No source files changed: I deleted the scratch test, and `git status` is clean.\n\n**Findings**\n1. **Low: the hook can revert a swap the pool would fill** (`src/TestHook.sol:132`).\n   - **Trigger:** an exact-output sell asking for an enormous IMD amount (`type(int256).max`) with a price limit, the \"sell until price X\" pattern.\n   - **What goes wrong:** the hook adds its fee to that amount, the signed sum overflows, and the swap fails.\n   - **Evidence:** in a scratch test, a pool without the hook partly filled this exact swap (about 6.09e21 TEST in, 5.98e21 IMD out). With TestHook the same swap failed with an overflow error (panic 0x11).\n   - **Reach:** only callers passing amounts above about 2^254. Standard routers cap amounts at uint128, so they never trigger it. It still breaks the brief's \"never reverts a real swap\".\n2. **Info: IMD's owner can hold up fee delivery** (`src/TestHook.sol:221`).\n   - **On-chain:** IMD on Robinhood Chain has an owner with a blocklist (`setBlocked`) and a transfer switch.\n   - **Effect of a block:** if either vault is blocked, the hook records the fee as a claim instead of paying it, and `redeemFees()` keeps failing until the block is lifted.\n   - **Not lost:** the fees can't be lost or redirected. But \"nobody can change it\" needs this caveat in the README and launch notes. Both vaults are unblocked today.\n\n**What I checked and found correct**\n- **Anti-sniper curve:** exactly 50% at 0 s, 25.375% at 900 s and 0.75% from 1800 s. It applies to buys only.\n- **Sells:** they never pay the CompensationVault. Their fee always equals the 0.75% base exactly.\n- **Rounding:** the split between the two vaults can never go negative on any swap path.\n- **Partial fills:** on buys where the user fixes the IMD they pay in, the hook test-runs the swap first and then undoes it. When a price limit cuts the trade short, the fee is charged on what actually traded.\n- **Fee side:** every fee is taken in IMD.\n- **No redirection:** recipients are fixed constants, the hook ignores caller data, and nobody can move its claims.\n- **Claim backing:** deferred claims always equal the pending vault and compensation totals.\n- **TestToken:** a plain OpenZeppelin ERC-20 with no owner, tax or mint after deployment.\n\nI did not report Slither's leads. On inspection they are harmless: the zero-amount check is intended, the hook only re-enters through a PoolManager that is already unlocked, and the return value it ignores is empty.\n\nThe existing local suite passes (81 tests; the 2 fork suites skip without an RPC). I did not re-run the fork suite against live chain state; I only read IMD's configuration and the vault addresses from Robinhood Chain.","treeHash":null,"usage":{"cachedInputTokens":1276740,"inputTokens":34,"model":"claude-opus-5-5","outputTokens":32602,"runtime":"claude","turns":17,"wallClockMs":405808}},{"artifacts":[],"attempt":1,"bundleHash":"d38b2947d486d52bcc20102482fd232ee7a8b763d077678e2da4c35b368b9b4b","device":"8b35536ee4821baa","findings":[{"description":"beforeSwap grosses up the entire requested IMD output before quoting actual execution. For a request near int256.max, requested + estimatedFee overflows even when a price limit restricts the executed trade and its fee to small, representable values. This violates the requested non-reversion behavior for valid swaps and the implementation's stated partial-fill accounting. The effect is limited to extreme caller-selected output requests; the reproduction shows no theft or persistent pool denial of service. Bound the quote request without overflowing, and compute the charged fee from the executed amount. The proof leaves 1e30 of signed headroom so the executed fee also fits when v4 later adds the beforeSwap delta.","line":132,"path":"src/TestHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {TestHook} from \"src/TestHook.sol\";\nimport {TestToken} from \"src/TestToken.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 {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\n\ncontract MaxOutputProofIMD is ERC20 {\n    constructor() ERC20(\"Identity.md\", \"IMD\") {}\n    function mint(address to, uint256 amount) external { _mint(to, amount); }\n}\n\ncontract MaxOutputProofTrader is IUnlockCallback {\n    IPoolManager private immutable manager;\n    constructor(IPoolManager manager_) { manager = manager_; }\n    function seed(PoolKey memory key) external {\n        manager.unlock(abi.encode(key, false, SwapParams(false, 0, 0)));\n    }\n    function swap(PoolKey memory key, SwapParams memory params) external returns (BalanceDelta) {\n        return BalanceDelta.wrap(abi.decode(manager.unlock(abi.encode(key, true, params)), (int256)));\n    }\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (PoolKey memory key, bool isSwap, SwapParams memory params) = abi.decode(data, (PoolKey, bool, SwapParams));\n        BalanceDelta delta;\n        if (isSwap) delta = manager.swap(key, params, \"\");\n        else (delta,) = manager.modifyLiquidity(key, ModifyLiquidityParams(-600, 600, 1_000_000 ether, bytes32(0)), \"\");\n        settle(key.currency0, delta.amount0());\n        settle(key.currency1, delta.amount1());\n        return abi.encode(BalanceDelta.unwrap(delta));\n    }\n    function settle(Currency currency, int128 delta) private {\n        if (delta < 0) {\n            manager.sync(currency);\n            require(IERC20(Currency.unwrap(currency)).transfer(address(manager), uint256(-int256(delta))));\n            manager.settle();\n        } else if (delta > 0) manager.take(currency, address(this), uint256(int256(delta)));\n    }\n}\n\ncontract MaxExactOutputProofTest is Test {\n    address private constant IMD = 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127;\n    uint160 private constant ONE = 79228162514264337593543950336;\n\n    function test_priceLimitedSellMustAcceptTheSameLargeRequestAsCanonicalV4() public {\n        IPoolManager manager = IPoolManager(address(new PoolManager(address(this))));\n        vm.etch(IMD, address(new MaxOutputProofIMD()).code);\n        TestToken token = new TestToken();\n        TestHook hook = deployHook(manager, address(token));\n        bool imdFirst = IMD < address(token);\n        PoolKey memory hooked = PoolKey(\n            Currency.wrap(imdFirst ? IMD : address(token)), Currency.wrap(imdFirst ? address(token) : IMD),\n            12_500, 60, IHooks(address(hook))\n        );\n        // Construct a separate struct, not a memory alias of the hooked key.\n        PoolKey memory control = PoolKey(hooked.currency0, hooked.currency1, 12_500, 60, IHooks(address(0)));\n        manager.initialize(hooked, ONE);\n        manager.initialize(control, ONE);\n        MaxOutputProofTrader trader = new MaxOutputProofTrader(manager);\n        token.transfer(address(trader), 1_000_000 ether);\n        MaxOutputProofIMD(IMD).mint(address(trader), 1_000_000 ether);\n        trader.seed(hooked);\n        trader.seed(control);\n        bool zeroForOne = !imdFirst; // Sell TEST, receive IMD.\n        // Leave ample signed headroom for the actual partial-fill fee. This is not the\n        // unavoidable v4 limit where adding even the executed fee to int256.max overflows.\n        SwapParams memory params = SwapParams(\n            zeroForOne, type(int256).max - 1e30, TickMath.getSqrtPriceAtTick(zeroForOne ? int24(-60) : int24(60))\n        );\n        BalanceDelta canonical = trader.swap(control, params);\n        uint256 gross = uint256(int256(imdFirst ? canonical.amount0() : canonical.amount1()));\n        assertGt(gross, 0, \"the real v4 pool must execute a partial fill\");\n        assertLt(gross * 3 / 400, 1e30, \"executed fee must fit the request's signed headroom\");\n        // Expected: same partial execution, less the 0.75% IMD fee, with no anti-sniper on a sell.\n        // Actual: beforeSwap panics at requested + fee, before its partial-fill quote runs.\n        BalanceDelta actual = trader.swap(hooked, params);\n        assertEq(uint256(int256(imdFirst ? actual.amount0() : actual.amount1())), gross - gross * 3 / 400);\n        assertEq(IERC20(IMD).balanceOf(hook.SIMD_VAULT()), gross * 3 / 400);\n        assertEq(IERC20(IMD).balanceOf(hook.COMPENSATION_VAULT()), 0);\n    }\n\n    function deployHook(IPoolManager manager, address token) private returns (TestHook) {\n        bytes32 hash = keccak256(abi.encodePacked(type(TestHook).creationCode, abi.encode(manager, token, address(this))));\n        for (uint256 i; i < 200_000; ++i) {\n            address predicted = vm.computeCreate2Address(bytes32(i), hash, address(this));\n            if (uint160(predicted) & 0x3fff == 0x20cc) return new TestHook{salt: bytes32(i)}(manager, token, address(this));\n        }\n        revert(\"salt search exhausted\");\n    }\n}","reproduction":"Save proof as test/MaxExactOutput.proof.t.sol and run forge test --match-path test/MaxExactOutput.proof.t.sol. The self-contained test deploys canonical v4 and two equally seeded TEST/IMD pools (one hookless), price 2^96, fee 12500, ticks [-600, 600], liquidity 1000000e18. Sell TEST with amountSpecified = type(int256).max - 1e30 and a price limit 60 ticks toward the IMD output. The canonical pool completes a partial fill with gross IMD output 2995354955910780937674. Expected hooked output: 2972889793741450080642 IMD units, SIMD Vault fee 22465162169330857032, compensation fee zero. Actual: TestHook.beforeSwap panics with arithmetic overflow 0x11 at line 132, wrapped by v4 as WrappedError, before quoting. The proof was executed on Forge 1.8.0 / solc 0.8.26 and failed for this reason; it is deliberately excluded from the passing submitted suite.","severity":"low","title":"Large exact-output sell requests overflow before a valid partial fill is quoted"}],"hash":"c95d59375cdfe8d4b6f4ee1f4804dd2eff669842e8971633a99e0402ccce491c","nodeId":"f2cb288f-21ee-4696-8596-ee15cf897e24","outcome":"completed","summary":"Added adversarial tests, accounting invariants, and repaired the Forge 1.8 token fixture.\n\n- Build passed.\n- Offline: **81 passed**, 2 fork skips.\n- Robinhood fork: **50 passed**, using real IMD and locally deployed v4 managers.\n\nReported one low-severity overflow with a reproduced failing proof in `.imd-findings.json`. Implementation and configuration files are unchanged.","treeHash":"7f382bf7be79d7416902bb3f32beaeff268395c2","usage":{"cachedInputTokens":3711360,"inputTokens":120276,"model":"gpt-6-astra","outputTokens":34855,"runtime":"codex","turns":9,"wallClockMs":1471979}}],"verification":[{"checks":[{"durationMs":5722,"exitCode":0,"name":"build","output":"Compiling 93 files with Solc 0.8.26\nSolc 0.8.26 finished in 5.63s\nCompiler run successful!\n","passed":true},{"durationMs":3039,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodToken1ForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 440.14µs (0.00ns CPU time)\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodToken0ForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 480.78µs (0.00ns CPU time)\n\nRan 5 tests for test/TestToken.t.sol:TestTokenTest\n[PASS] testFuzz_transferPreservesSupplyAndCannotOverdraw(uint256) (runs: 1000, μ: 126814, ~: 126886)\nLogs:\n  Bound result 95\n\n[PASS] test_factorySplitsSupplyAndContractsPayWithoutTransferTax() (gas: 672303)\n[PASS] test_fixedSupplyAndMetadata() (gas: 54889)\n[PASS] test_noMintAdminPauseBlacklistOrUpgradeEvenForFactory() (gas: 505016)\n[PASS] test_transferFromRequiresConsentAndConsumesExactAllowance() (gas: 166536)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 9.12ms (9.74ms CPU time)\n\nRan 2 tests for test/TestHook.t.sol:TestHookFreshManagerTest\n[PASS] test_firstBuyOnTokenOnlySeedDefersIMDThenRedeemsAfterSettlement() (gas: 775619)\n[PASS] test_firstExactOutputBuyOnTokenOnlySeedSucceeds() (gas: 554466)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 18.58ms (948.75µs CPU time)\n\nRan 11 tests for test/TestHook.t.sol:TestHookFailureTest\n[PASS] test_allEnabledCallbacksRejectForgedCalls() (gas: 149397)\n[PASS] test_bothRecipientsCanFailAndRecoverIndependently() (gas: 654012)\n[PASS] test_constructorNeedsNoExternalCodeAndHasNoOwner() (gas: 23788772)\n[PASS] test_deploymentTimeDoesNotStartTheAntiSniperClock() (gas: 1399550)\n[PASS] test_failedCompensationTransferDoesNotRevertSwapOrRedirectFunds() (gas: 725821)\n[PASS] test_failedVaultTransferDoesNotRevertSwapAndAnyoneCanRedeem() (gas: 801179)\n[PASS] test_hookAndTokenRuntimeHaveNoUpgradeOrDestructionOpcodes() (gas: 4489252)\n[PASS] test_initializationCannotBeResetOrUsedForAnotherPool() (gas: 137466)\n[PASS] test_invalidPriceLimitStillRevertsWithNoFeeOrStateChange() (gas: 120531)\n[PASS] test_unfundedTraderRollsBackAllFeePayments() (gas: 579499)\n[PASS] test_wrongFactoryCannotInitializeThroughRealManager() (gas: 8494186)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 118.26ms (88.66ms CPU time)\n\nRan 25 tests for test/TestHook.t.sol:TestHookToken1Test\n[PASS] testFuzz_allSwapModesConserveFundsAndPayCorrectFee(bool,bool,uint96,uint32) (runs: 1000, μ: 392488, ~: 401149)\nLogs:\n  Bound result 536780587390468998752\n  Bound result 434\n\n[PASS] testFuzz_hookDataCannotWaiveOrRedirectFees(bytes,bool,bool) (runs: 1000, μ: 348515, ~: 352022)\n[PASS] testFuzz_partialFillsAcrossAllSwapModes(bool,bool,uint16,uint16) (runs: 1000, μ: 471558, ~: 474355)\nLogs:\n  Bound result 8\n  Bound result 45\n\n[PASS] testFuzz_quoteRollbackMatchesCanonicalPool(bool,bool,bool,uint96,uint16) (runs: 1000, μ: 903487, ~: 903426)\nLogs:\n  Bound result 808774463126942613029\n  Bound result 1520\n\n[PASS] testFuzz_smallUnitsChargeOnlyIMD(bool,bool,uint16,uint16) (runs: 1000, μ: 431454, ~: 444089)\nLogs:\n  Bound result 45\n  Bound result 8\n\n[PASS] test_blockCountCannotExpireTheTimestampBasedCurve() (gas: 538518)\n[PASS] test_buyAfter1800SecondsKeepsStandingFee() (gas: 280325)\n[PASS] test_buyAt1800SecondsChargesOnlyStandingFee() (gas: 358395)\n[PASS] test_buyAt900SecondsCharges25Point375Percent() (gas: 389684)\n[PASS] test_buyAtOpeningCharges50PercentAndSplitsIMD() (gas: 424730)\n[PASS] test_emptyPoolTradeDoesNotPayARequestBasedFee() (gas: 1047443)\n[PASS] test_exactOutputBuyAfterLaunchHasOnlyStandingFee() (gas: 317914)\n[PASS] test_exactOutputBuyDeliversRequestedTESTIncludingAtOpening() (gas: 352922)\n[PASS] test_exactOutputSellDeliversRequestedNetIMD() (gas: 366991)\n[PASS] test_factoryCanCollectLPFeesAndRemoveLiquidity() (gas: 847229)\n[PASS] test_failedPoolInitializationDoesNotStartTheClock() (gas: 41232890)\n[PASS] test_initializationAndPermissions() (gas: 64535)\n[PASS] test_lastAntiSniperSecondAndBoundary() (gas: 606454)\n[PASS] test_minimumSignedBuyBudgetChargesOnlyItsPartialFill() (gas: 421274)\n[PASS] test_noOneCanRedirectOrChangeTheFees() (gas: 742001)\n[PASS] test_partialExactInputBuyChargesOnlyExecutedIMD() (gas: 422736)\n[PASS] test_partialExactOutputSellChargesOnlyExecutedIMD() (gas: 357589)\n[PASS] test_sellsAtOpeningNeverPayAntiSniper() (gas: 312995)\n[PASS] test_tinyBuysAndSellsDoNotRevertFromFeeRounding() (gas: 429175)\n[PASS] test_zeroSwapCannotPayOrCreateFees() (gas: 309895)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 331.72ms (1.09s CPU time)\n\nRan 9 tests for test/HookIsolation.t.sol:HookIsolationTest\n[PASS] testFuzz_everyPoolKeyFieldIsBound(uint8) (runs: 1000, μ: 408198, ~: 701570)\n[PASS] testFuzz_secondSwapFailureRollsBackPaymentsAndClaims(bool) (runs: 1000, μ: 1461924, ~: 1493576)\n[PASS] test_buyAndSellNetSettleIMDWithinOneUnlock() (gas: 1458815)\n[PASS] test_constructorRejectsEveryMissingOrAliasedDependency() (gas: 6189)\n[PASS] test_deferredClaimsCannotBeTransferredOrBurnedByAnotherCaller() (gas: 1484800)\n[PASS] test_multipleSwapsShareOneUnlockAndChargeExactlyOnceEach() (gas: 1414810)\n[PASS] test_redemptionDuringAnotherUnlockIsAtomicAndCanBeRetried() (gas: 1430371)\n[PASS] test_swapCallbacksRejectTheRightPoolBeforeInitialization() (gas: 20334731)\n[PASS] test_unsolicitedClaimGiftCannotIncreaseOrRedirectEntitlements() (gas: 1605452)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 331.78ms (710.97ms CPU time)\n\nRan 25 tests for test/TestHook.t.sol:TestHookToken0Test\n[PASS] testFuzz_allSwapModesConserveFundsAndPayCorrectFee(bool,bool,uint96,uint32) (runs: 1000, μ: 392384, ~: 402548)\nLogs:\n  Bound result 421148129906523831451\n  Bound result 216\n\n[PASS] testFuzz_hookDataCannotWaiveOrRedirectFees(bytes,bool,bool) (runs: 1000, μ: 347969, ~: 362537)\n[PASS] testFuzz_partialFillsAcrossAllSwapModes(bool,bool,uint16,uint16) (runs: 1000, μ: 471899, ~: 486483)\nLogs:\n  Bound result 24\n  Bound result 227\n\n[PASS] testFuzz_quoteRollbackMatchesCanonicalPool(bool,bool,bool,uint96,uint16) (runs: 1000, μ: 902184, ~: 893333)\nLogs:\n  Bound result 808774463126942613029\n  Bound result 1520\n\n[PASS] testFuzz_smallUnitsChargeOnlyIMD(bool,bool,uint16,uint16) (runs: 1000, μ: 430905, ~: 439317)\nLogs:\n  Bound result 45\n  Bound result 8\n\n[PASS] test_blockCountCannotExpireTheTimestampBasedCurve() (gas: 524498)\n[PASS] test_buyAfter1800SecondsKeepsStandingFee() (gas: 267283)\n[PASS] test_buyAt1800SecondsChargesOnlyStandingFee() (gas: 345353)\n[PASS] test_buyAt900SecondsCharges25Point375Percent() (gas: 376646)\n[PASS] test_buyAtOpeningCharges50PercentAndSplitsIMD() (gas: 411710)\n[PASS] test_emptyPoolTradeDoesNotPayARequestBasedFee() (gas: 1034364)\n[PASS] test_exactOutputBuyAfterLaunchHasOnlyStandingFee() (gas: 312831)\n[PASS] test_exactOutputBuyDeliversRequestedTESTIncludingAtOpening() (gas: 347839)\n[PASS] test_exactOutputSellDeliversRequestedNetIMD() (gas: 377515)\n[PASS] test_factoryCanCollectLPFeesAndRemoveLiquidity() (gas: 841049)\n[PASS] test_failedPoolInitializationDoesNotStartTheClock() (gas: 20454171)\n[PASS] test_initializationAndPermissions() (gas: 64535)\n[PASS] test_lastAntiSniperSecondAndBoundary() (gas: 592438)\n[PASS] test_minimumSignedBuyBudgetChargesOnlyItsPartialFill() (gas: 408430)\n[PASS] test_noOneCanRedirectOrChangeTheFees() (gas: 728959)\n[PASS] test_partialExactInputBuyChargesOnlyExecutedIMD() (gas: 410481)\n[PASS] test_partialExactOutputSellChargesOnlyExecutedIMD() (gas: 369672)\n[PASS] test_sellsAtOpeningNeverPayAntiSniper() (gas: 319493)\n[PASS] test_tinyBuysAndSellsDoNotRevertFromFeeRounding() (gas: 420287)\n[PASS] test_zeroSwapCannotPayOrCreateFees() (gas: 310110)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 331.80ms (1.59s CPU time)\n\nRan 1 test for test/HookInvariant.t.sol:HookInvariantTest\n[PASS] invariant_allIMDIsConservedAndClaimsAreExactlyBacked() (runs: 128, calls: 4096, reverts: 0)\n\n╭------------+------------------+-------+---------+----------╮\n| Contract   | Selector         | Calls | Reverts | Discards |\n+============================================================+\n| FeeHandler | advance          | 1009  | 0       | 0        |\n|------------+------------------+-------+---------+----------|\n| FeeHandler | redeem           | 1026  | 0       | 0        |\n|------------+------------------+-------+---------+----------|\n| FeeHandler | rejectRecipients | 1013  | 0       | 0        |\n|------------+------------------+-------+---------+----------|\n| FeeHandler | trade            | 1048  | 0       | 0        |\n╰------------+------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 2447209286\n  Bound result 8420329101685780\n  Bound result 124\n  Bound result 28\n  Bound result 3245\n  Bound result 446993278402754200671\n  Bound result 14064\n  Bound result 0\n  Bound result 2023415125\n  Bound result 36\n  Bound result 368548458\n  Bound result 1222\n  Bound result 216\n  Bound result 128\n  Bound result 12224\n  Bound result 31\n  Bound result 1\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 886.40ms (856.73ms CPU time)\n\nRan 1 test for test/TokenInvariant.t.sol:TokenInvariantTest\n[PASS] invariant_fixedSupplyAndConsentedUntaxedTransfers() (runs: 256, calls: 16384, reverts: 0)\n\n╭--------------------+-----------------+-------+---------+----------╮\n| Contract           | Selector        | Calls | Reverts | Discards |\n+===================================================================+\n| TokenLedgerHandler | approve         | 3184  | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| TokenLedgerHandler | exceedAllowance | 3145  | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| TokenLedgerHandler | overdraw        | 3327  | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| TokenLedgerHandler | transfer        | 3327  | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| TokenLedgerHandler | transferFrom    | 3401  | 0       | 0        |\n╰--------------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 5199\n  Bound result 0\n  Bound result 0\n  Bound result 5456\n  Bound result 13500000000000000000\n  Bound result 0\n  Bound result 0\n  Bound result 3544\n  Bound result 0\n  Bound result 0\n  Bound result 932\n  Bound result 826\n  Bound result 363549772893954163054768334\n  Bound result 13\n  Bound result 864\n  Bound result 2538\n  Bound result 57\n  Bound result 0\n  Bound result 128\n  Bound result 810\n  Bound result 213471900756813328215003847\n  Bound result 0\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.48s (2.47s CPU time)\n\nRan 1 test for test/HookEntitlementInvariant.t.sol:HookToken0EntitlementInvariantTest\n[PASS] invariant_eachVaultOwnsExactlyItsEarnedFees() (runs: 256, calls: 16384, reverts: 0)\n\n╭--------------------+--------------------+-------+---------+----------╮\n| Contract           | Selector           | Calls | Reverts | Discards |\n+======================================================================+\n| EntitlementHandler | advance            | 3230  | 0       | 0        |\n|--------------------+--------------------+-------+---------+----------|\n| EntitlementHandler | attemptRedirection | 3312  | 0       | 0        |\n|--------------------+--------------------+-------+---------+----------|\n| EntitlementHandler | redeem             | 3263  | 0       | 0        |\n|--------------------+--------------------+-------+---------+----------|\n| EntitlementHandler | rejectRecipients   | 3264  | 0       | 0        |\n|--------------------+--------------------+-------+---------+----------|\n| EntitlementHandler | trade              | 3315  | 0       | 0        |\n╰--------------------+--------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 15240\n  Bound result 1602\n  Bound result 53883049562554938008\n  Bound result 37040585390836882696\n  Bound result 1\n  Bound result 906101799633237\n  Bound result 1504\n  Bound result 1113\n  Bound result 686\n  Bound result 15623\n  Bound result 1801\n  Bound result 307\n  Bound result 3179186434829807725\n  Bound result 784\n  Bound result 400\n  Bound result 6969\n  Bound result 12327\n  Bound result 2850\n  Bound result 1746338587\n  Bound result 113\n  Bound result 1609\n  Bound result 350\n  Bound result 1409\n  Bound result 978\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.93s (2.91s CPU time)\n\nRan 1 test for test/HookEntitlementInvariant.t.sol:HookToken1EntitlementInvariantTest\n[PASS] invariant_eachVaultOwnsExactlyItsEarnedFees() (runs: 256, calls: 16384, reverts: 0)\n\n╭--------------------+--------------------+-------+---------+----------╮\n| Contract           | Selector           | Calls | Reverts | Discards |\n+======================================================================+\n| EntitlementHandler | advance            | 3230  | 0       | 0        |\n|--------------------+--------------------+-------+---------+----------|\n| EntitlementHandler | attemptRedirection | 3312  | 0       | 0        |\n|--------------------+--------------------+-------+---------+----------|\n| EntitlementHandler | redeem             | 3263  | 0       | 0        |\n|--------------------+--------------------+-------+---------+----------|\n| EntitlementHandler | rejectRecipients   | 3264  | 0       | 0        |\n|--------------------+--------------------+-------+---------+----------|\n| EntitlementHandler | trade              | 3315  | 0       | 0        |\n╰--------------------+--------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 3888\n  Bound result 1325\n  Bound result 2299\n  Bound result 37040585390836882696\n  Bound result 1\n  Bound result 906101799633237\n  Bound result 23\n  Bound result 920\n  Bound result 1246\n  Bound result 16498\n  Bound result 1801\n  Bound result 307\n  Bound result 9975\n  Bound result 1060\n  Bound result 400\n  Bound result 6917\n  Bound result 14334\n  Bound result 13367\n  Bound result 1817\n  Bound result 12607\n  Bound result 425\n  Bound result 350\n  Bound result 940\n  Bound result 978\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.94s (2.86s CPU time)\n\nRan 12 test suites in 2.94s (10.37s CPU time): 81 tests passed, 0 failed, 2 skipped (83 total tests)\n","passed":true},{"durationMs":63,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"TestHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"TestHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"TestHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"TestHook.quoteSwap((address,address,uint24,int24,address),(bool,int256,uint160))\",\"TestHook.redeemFees()\",\"TestHook.unlockCallback(bytes)\",\"TestToken.approve(address,uint256)\",\"TestToken.transfer(address,uint256)\",\"TestToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":5,\"DEPENDENCIES.md\":12,\"LICENSE\":23,\"README.md\":110,\"VERIFICATION.md\":55,\"foundry.toml\":23,\"launch.json\":31,\"remappings.txt\":5,\"src/HookFlags.sol\":31,\"src/TestHook.sol\":253,\"src/TestToken.sol\":12,\"test/HookAdversarial.t.sol\":190,\"test/HookEntitlementInvariant.t.sol\":177,\"test/HookInvariant.t.sol\":99,\"test/HookIsolation.t.sol\":234,\"test/README.md\":43,\"test/RobinhoodFork.t.sol\":36,\"test/TestHook.t.sol\":496,\"test/TestToken.t.sol\":111,\"test/TokenInvariant.t.sol\":107,\"test/helpers/BatchSwapActor.sol\":52,\"test/helpers/HookTestBase.sol\":126,\"test/helpers/PoolActor.sol\":61,\"test/mocks/MockIMD.sol\":24},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"0657847a1323cbc54919e2fa5dea522483bff1b4b14fedd81d2a98d5b556daab","verifiedTreeHash":"79786d7964c9c7736651b45ac18c34ba5efcb3d8","verifierVersion":"0.1.0+560ab6a2"},{"checks":[{"durationMs":3933,"exitCode":0,"name":"build","output":"Compiling 88 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.85s\nCompiler run successful!\n","passed":true},{"durationMs":857,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodToken1ForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 484.96µs (0.00ns CPU time)\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodToken0ForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 489.60µs (0.00ns CPU time)\n\nRan 5 tests for test/TestToken.t.sol:TestTokenTest\n[PASS] testFuzz_transferPreservesSupplyAndCannotOverdraw(uint256) (runs: 1000, μ: 126772, ~: 126898)\nLogs:\n  Bound result 24576\n\n[PASS] test_factorySplitsSupplyAndContractsPayWithoutTransferTax() (gas: 672274)\n[PASS] test_fixedSupplyAndMetadata() (gas: 54889)\n[PASS] test_noMintAdminPauseBlacklistOrUpgradeEvenForFactory() (gas: 505016)\n[PASS] test_transferFromRequiresConsentAndConsumesExactAllowance() (gas: 166536)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 9.66ms (10.05ms CPU time)\n\nRan 2 tests for test/TestHook.t.sol:TestHookFreshManagerTest\n[PASS] test_firstBuyOnTokenOnlySeedDefersIMDThenRedeemsAfterSettlement() (gas: 775619)\n[PASS] test_firstExactOutputBuyOnTokenOnlySeedSucceeds() (gas: 554466)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 18.04ms (889.48µs CPU time)\n\nRan 11 tests for test/TestHook.t.sol:TestHookFailureTest\n[PASS] test_allEnabledCallbacksRejectForgedCalls() (gas: 149397)\n[PASS] test_bothRecipientsCanFailAndRecoverIndependently() (gas: 654012)\n[PASS] test_constructorNeedsNoExternalCodeAndHasNoOwner() (gas: 23788772)\n[PASS] test_deploymentTimeDoesNotStartTheAntiSniperClock() (gas: 1399550)\n[PASS] test_failedCompensationTransferDoesNotRevertSwapOrRedirectFunds() (gas: 725821)\n[PASS] test_failedVaultTransferDoesNotRevertSwapAndAnyoneCanRedeem() (gas: 801179)\n[PASS] test_hookAndTokenRuntimeHaveNoUpgradeOrDestructionOpcodes() (gas: 4489252)\n[PASS] test_initializationCannotBeResetOrUsedForAnotherPool() (gas: 137466)\n[PASS] test_invalidPriceLimitStillRevertsWithNoFeeOrStateChange() (gas: 120531)\n[PASS] test_unfundedTraderRollsBackAllFeePayments() (gas: 579499)\n[PASS] test_wrongFactoryCannotInitializeThroughRealManager() (gas: 8494186)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 150.46ms (131.96ms CPU time)\n\nRan 18 tests for test/TestHook.t.sol:TestHookToken0Test\n[PASS] testFuzz_allSwapModesConserveFundsAndPayCorrectFee(bool,bool,uint96,uint32) (runs: 1000, μ: 392389, ~: 402596)\nLogs:\n  Bound result 536780587390468998752\n  Bound result 434\n\n[PASS] testFuzz_partialFillsAcrossAllSwapModes(bool,bool,uint16,uint16) (runs: 1000, μ: 472159, ~: 486417)\nLogs:\n  Bound result 7\n  Bound result 65\n\n[PASS] test_buyAfter1800SecondsKeepsStandingFee() (gas: 267261)\n[PASS] test_buyAt1800SecondsChargesOnlyStandingFee() (gas: 345287)\n[PASS] test_buyAt900SecondsCharges25Point375Percent() (gas: 376602)\n[PASS] test_buyAtOpeningCharges50PercentAndSplitsIMD() (gas: 411644)\n[PASS] test_emptyPoolTradeDoesNotPayARequestBasedFee() (gas: 1034287)\n[PASS] test_exactOutputBuyAfterLaunchHasOnlyStandingFee() (gas: 312843)\n[PASS] test_exactOutputBuyDeliversRequestedTESTIncludingAtOpening() (gas: 347784)\n[PASS] test_exactOutputSellDeliversRequestedNetIMD() (gas: 377438)\n[PASS] test_factoryCanCollectLPFeesAndRemoveLiquidity() (gas: 841005)\n[PASS] test_initializationAndPermissions() (gas: 64491)\n[PASS] test_lastAntiSniperSecondAndBoundary() (gas: 592461)\n[PASS] test_noOneCanRedirectOrChangeTheFees() (gas: 728959)\n[PASS] test_partialExactInputBuyChargesOnlyExecutedIMD() (gas: 410426)\n[PASS] test_partialExactOutputSellChargesOnlyExecutedIMD() (gas: 369584)\n[PASS] test_sellsAtOpeningNeverPayAntiSniper() (gas: 319460)\n[PASS] test_tinyBuysAndSellsDoNotRevertFromFeeRounding() (gas: 420243)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 151.52ms (208.99ms CPU time)\n\nRan 18 tests for test/TestHook.t.sol:TestHookToken1Test\n[PASS] testFuzz_allSwapModesConserveFundsAndPayCorrectFee(bool,bool,uint96,uint32) (runs: 1000, μ: 392649, ~: 401051)\nLogs:\n  Bound result 999999999999000018106\n  Bound result 1835\n\n[PASS] testFuzz_partialFillsAcrossAllSwapModes(bool,bool,uint16,uint16) (runs: 1000, μ: 471624, ~: 474289)\nLogs:\n  Bound result 1061\n  Bound result 412\n\n[PASS] test_buyAfter1800SecondsKeepsStandingFee() (gas: 280303)\n[PASS] test_buyAt1800SecondsChargesOnlyStandingFee() (gas: 358329)\n[PASS] test_buyAt900SecondsCharges25Point375Percent() (gas: 389640)\n[PASS] test_buyAtOpeningCharges50PercentAndSplitsIMD() (gas: 424664)\n[PASS] test_emptyPoolTradeDoesNotPayARequestBasedFee() (gas: 1047366)\n[PASS] test_exactOutputBuyAfterLaunchHasOnlyStandingFee() (gas: 317926)\n[PASS] test_exactOutputBuyDeliversRequestedTESTIncludingAtOpening() (gas: 352867)\n[PASS] test_exactOutputSellDeliversRequestedNetIMD() (gas: 366914)\n[PASS] test_factoryCanCollectLPFeesAndRemoveLiquidity() (gas: 847185)\n[PASS] test_initializationAndPermissions() (gas: 64491)\n[PASS] test_lastAntiSniperSecondAndBoundary() (gas: 606477)\n[PASS] test_noOneCanRedirectOrChangeTheFees() (gas: 742001)\n[PASS] test_partialExactInputBuyChargesOnlyExecutedIMD() (gas: 422681)\n[PASS] test_partialExactOutputSellChargesOnlyExecutedIMD() (gas: 357501)\n[PASS] test_sellsAtOpeningNeverPayAntiSniper() (gas: 312962)\n[PASS] test_tinyBuysAndSellsDoNotRevertFromFeeRounding() (gas: 429131)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 151.99ms (128.96ms CPU time)\n\nRan 1 test for test/HookInvariant.t.sol:HookInvariantTest\n[PASS] invariant_allIMDIsConservedAndClaimsAreExactlyBacked() (runs: 128, calls: 4096, reverts: 0)\n\n╭------------+------------------+-------+---------+----------╮\n| Contract   | Selector         | Calls | Reverts | Discards |\n+============================================================+\n| FeeHandler | advance          | 1009  | 0       | 0        |\n|------------+------------------+-------+---------+----------|\n| FeeHandler | redeem           | 1026  | 0       | 0        |\n|------------+------------------+-------+---------+----------|\n| FeeHandler | rejectRecipients | 1013  | 0       | 0        |\n|------------+------------------+-------+---------+----------|\n| FeeHandler | trade            | 1048  | 0       | 0        |\n╰------------+------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 2447209286\n  Bound result 8420329101685780\n  Bound result 124\n  Bound result 28\n  Bound result 3245\n  Bound result 446980700528772260151\n  Bound result 14064\n  Bound result 276\n  Bound result 2023415125\n  Bound result 295\n  Bound result 368548458\n  Bound result 1222\n  Bound result 216\n  Bound result 8\n  Bound result 12224\n  Bound result 31\n  Bound result 1\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 762.48ms (733.64ms CPU time)\n\nRan 8 test suites in 764.04ms (1.25s CPU time): 55 tests passed, 0 failed, 2 skipped (57 total tests)\n","passed":true},{"durationMs":51,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"TestHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"TestHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"TestHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"TestHook.quoteSwap((address,address,uint24,int24,address),(bool,int256,uint160))\",\"TestHook.redeemFees()\",\"TestHook.unlockCallback(bytes)\",\"TestToken.approve(address,uint256)\",\"TestToken.transfer(address,uint256)\",\"TestToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":5,\"DEPENDENCIES.md\":12,\"LICENSE\":23,\"README.md\":110,\"VERIFICATION.md\":55,\"foundry.toml\":23,\"remappings.txt\":5,\"src/HookFlags.sol\":31,\"src/TestHook.sol\":253,\"src/TestToken.sol\":12,\"test/HookInvariant.t.sol\":99,\"test/RobinhoodFork.t.sol\":36,\"test/TestHook.t.sol\":495,\"test/TestToken.t.sol\":105,\"test/helpers/HookTestBase.sol\":126,\"test/helpers/PoolActor.sol\":61,\"test/mocks/MockIMD.sol\":24},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1101,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/TestHook.sol:219: TestHook._pay(address,uint256) (src/TestHook.sol#219-229) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/TestHook.sol:180: Reentrancy in TestHook.unlockCallback(bytes) (src/TestHook.sol#180-192):\n[medium/medium] unused-return at src/TestHook.sol:176: TestHook.redeemFees() (src/TestHook.sol#176-178) ignores return value by poolManager.unlock() (src/TestHook.sol#177)\n[low/medium] reentrancy-benign at src/TestHook.sol:211: Reentrancy in TestHook._distribute(bool,uint256,uint256) (src/TestHook.sol#211-217):\n[low/medium] reentrancy-events at src/TestHook.sol:113: Reentrancy in TestHook.beforeSwap(address,PoolKey,SwapParams,bytes) (src/TestHook.sol#113-148):\n[low/medium] reentrancy-events at src/TestHook.sol:219: Reentrancy in TestHook._pay(address,uint256) (src/TestHook.sol#219-229):\n[low/medium] reentrancy-events at src/TestHook.sol:231: Reentrancy in TestHook._redeem(address,uint256) (src/TestHook.sol#231-236):\n[low/medium] reentrancy-events at src/TestHook.sol:180: Reentrancy in TestHook.unlockCallback(bytes) (src/TestHook.sol#180-192):\n[low/medium] reentrancy-events at src/TestHook.sol:219: Reentrancy in TestHook._pay(address,uint256) (src/TestHook.sol#219-229):\n[low/medium] reentrancy-events at src/TestHook.sol:211: Reentrancy in TestHook._distribute(bool,uint256,uint256) (src/TestHook.sol#211-217):\n[low/medium] timestamp at src/TestHook.sol:101: TestHook.feeNumerator(bool) (src/TestHook.sol#101-108) uses timestamp for comparisons\n[low/medium] timestamp at src/TestHook.sol:219: TestHook._pay(address,uint256) (src/TestHook.sol#219-229) uses timestamp for comparisons\n[low/medium] timestamp at src/TestHook.sol:113: TestHook.beforeSwap(address,PoolKey,SwapParams,bytes) (src/TestHook.sol#113-148) uses timestamp for comparisons","passed":true},{"durationMs":359,"exitCode":0,"name":"aderyn","output":"[low] internal-function-used-once at src/HookFlags.sol:24: Internal Function Used Only Once\n[low] large-numeric-literal at src/TestHook.sol:28: Large Numeric Literal (4 places)\n[low] unchecked-return at src/TestHook.sol:177: Unchecked Return","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"58bc4a3fe96c21db434c9c6186d6c2f6a9ae5b882653eede8a67a7ad27a187f0","verifiedTreeHash":"cad8ad14550ae05ad95d8050e5d405f66e757984","verifierVersion":"0.1.0+560ab6a2"},{"checks":[{"durationMs":5924,"exitCode":0,"name":"build","output":"Compiling 93 files with Solc 0.8.26\nSolc 0.8.26 finished in 5.83s\nCompiler run successful!\n","passed":true},{"durationMs":3149,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodToken1ForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 457.45µs (0.00ns CPU time)\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodToken0ForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 451.39µs (0.00ns CPU time)\n\nRan 5 tests for test/TestToken.t.sol:TestTokenTest\n[PASS] testFuzz_transferPreservesSupplyAndCannotOverdraw(uint256) (runs: 1000, μ: 126814, ~: 126886)\nLogs:\n  Bound result 2171\n\n[PASS] test_factorySplitsSupplyAndContractsPayWithoutTransferTax() (gas: 672303)\n[PASS] test_fixedSupplyAndMetadata() (gas: 54889)\n[PASS] test_noMintAdminPauseBlacklistOrUpgradeEvenForFactory() (gas: 505016)\n[PASS] test_transferFromRequiresConsentAndConsumesExactAllowance() (gas: 166536)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 9.15ms (9.92ms CPU time)\n\nRan 2 tests for test/TestHook.t.sol:TestHookFreshManagerTest\n[PASS] test_firstBuyOnTokenOnlySeedDefersIMDThenRedeemsAfterSettlement() (gas: 775619)\n[PASS] test_firstExactOutputBuyOnTokenOnlySeedSucceeds() (gas: 554466)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 20.61ms (1.25ms CPU time)\n\nRan 11 tests for test/TestHook.t.sol:TestHookFailureTest\n[PASS] test_allEnabledCallbacksRejectForgedCalls() (gas: 149397)\n[PASS] test_bothRecipientsCanFailAndRecoverIndependently() (gas: 654012)\n[PASS] test_constructorNeedsNoExternalCodeAndHasNoOwner() (gas: 23788772)\n[PASS] test_deploymentTimeDoesNotStartTheAntiSniperClock() (gas: 1399550)\n[PASS] test_failedCompensationTransferDoesNotRevertSwapOrRedirectFunds() (gas: 725821)\n[PASS] test_failedVaultTransferDoesNotRevertSwapAndAnyoneCanRedeem() (gas: 801179)\n[PASS] test_hookAndTokenRuntimeHaveNoUpgradeOrDestructionOpcodes() (gas: 4489252)\n[PASS] test_initializationCannotBeResetOrUsedForAnotherPool() (gas: 137466)\n[PASS] test_invalidPriceLimitStillRevertsWithNoFeeOrStateChange() (gas: 120531)\n[PASS] test_unfundedTraderRollsBackAllFeePayments() (gas: 579499)\n[PASS] test_wrongFactoryCannotInitializeThroughRealManager() (gas: 8494186)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 312.75ms (100.87ms CPU time)\n\nRan 9 tests for test/HookIsolation.t.sol:HookIsolationTest\n[PASS] testFuzz_everyPoolKeyFieldIsBound(uint8) (runs: 1000, μ: 408198, ~: 701570)\n[PASS] testFuzz_secondSwapFailureRollsBackPaymentsAndClaims(bool) (runs: 1000, μ: 1461924, ~: 1493576)\n[PASS] test_buyAndSellNetSettleIMDWithinOneUnlock() (gas: 1458815)\n[PASS] test_constructorRejectsEveryMissingOrAliasedDependency() (gas: 6189)\n[PASS] test_deferredClaimsCannotBeTransferredOrBurnedByAnotherCaller() (gas: 1484800)\n[PASS] test_multipleSwapsShareOneUnlockAndChargeExactlyOnceEach() (gas: 1414810)\n[PASS] test_redemptionDuringAnotherUnlockIsAtomicAndCanBeRetried() (gas: 1430371)\n[PASS] test_swapCallbacksRejectTheRightPoolBeforeInitialization() (gas: 20334731)\n[PASS] test_unsolicitedClaimGiftCannotIncreaseOrRedirectEntitlements() (gas: 1605452)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 331.77ms (377.77ms CPU time)\n\nRan 25 tests for test/TestHook.t.sol:TestHookToken1Test\n[PASS] testFuzz_allSwapModesConserveFundsAndPayCorrectFee(bool,bool,uint96,uint32) (runs: 1000, μ: 392488, ~: 401149)\nLogs:\n  Bound result 3616630725677\n  Bound result 72\n\n[PASS] testFuzz_hookDataCannotWaiveOrRedirectFees(bytes,bool,bool) (runs: 1000, μ: 348515, ~: 352022)\n[PASS] testFuzz_partialFillsAcrossAllSwapModes(bool,bool,uint16,uint16) (runs: 1000, μ: 471558, ~: 474355)\nLogs:\n  Bound result 18\n  Bound result 488\n\n[PASS] testFuzz_quoteRollbackMatchesCanonicalPool(bool,bool,bool,uint96,uint16) (runs: 1000, μ: 903487, ~: 903426)\nLogs:\n  Bound result 364\n  Bound result 144\n\n[PASS] testFuzz_smallUnitsChargeOnlyIMD(bool,bool,uint16,uint16) (runs: 1000, μ: 431454, ~: 444089)\nLogs:\n  Bound result 72\n  Bound result 18\n\n[PASS] test_blockCountCannotExpireTheTimestampBasedCurve() (gas: 538518)\n[PASS] test_buyAfter1800SecondsKeepsStandingFee() (gas: 280325)\n[PASS] test_buyAt1800SecondsChargesOnlyStandingFee() (gas: 358395)\n[PASS] test_buyAt900SecondsCharges25Point375Percent() (gas: 389684)\n[PASS] test_buyAtOpeningCharges50PercentAndSplitsIMD() (gas: 424730)\n[PASS] test_emptyPoolTradeDoesNotPayARequestBasedFee() (gas: 1047443)\n[PASS] test_exactOutputBuyAfterLaunchHasOnlyStandingFee() (gas: 317914)\n[PASS] test_exactOutputBuyDeliversRequestedTESTIncludingAtOpening() (gas: 352922)\n[PASS] test_exactOutputSellDeliversRequestedNetIMD() (gas: 366991)\n[PASS] test_factoryCanCollectLPFeesAndRemoveLiquidity() (gas: 847229)\n[PASS] test_failedPoolInitializationDoesNotStartTheClock() (gas: 41232890)\n[PASS] test_initializationAndPermissions() (gas: 64535)\n[PASS] test_lastAntiSniperSecondAndBoundary() (gas: 606454)\n[PASS] test_minimumSignedBuyBudgetChargesOnlyItsPartialFill() (gas: 421274)\n[PASS] test_noOneCanRedirectOrChangeTheFees() (gas: 742001)\n[PASS] test_partialExactInputBuyChargesOnlyExecutedIMD() (gas: 422736)\n[PASS] test_partialExactOutputSellChargesOnlyExecutedIMD() (gas: 357589)\n[PASS] test_sellsAtOpeningNeverPayAntiSniper() (gas: 312995)\n[PASS] test_tinyBuysAndSellsDoNotRevertFromFeeRounding() (gas: 429175)\n[PASS] test_zeroSwapCannotPayOrCreateFees() (gas: 309895)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 342.69ms (1.13s CPU time)\n\nRan 25 tests for test/TestHook.t.sol:TestHookToken0Test\n[PASS] testFuzz_allSwapModesConserveFundsAndPayCorrectFee(bool,bool,uint96,uint32) (runs: 1000, μ: 392384, ~: 402548)\nLogs:\n  Bound result 421148129906523831451\n  Bound result 216\n\n[PASS] testFuzz_hookDataCannotWaiveOrRedirectFees(bytes,bool,bool) (runs: 1000, μ: 347969, ~: 362537)\n[PASS] testFuzz_partialFillsAcrossAllSwapModes(bool,bool,uint16,uint16) (runs: 1000, μ: 471899, ~: 486483)\nLogs:\n  Bound result 564\n  Bound result 127\n\n[PASS] testFuzz_quoteRollbackMatchesCanonicalPool(bool,bool,bool,uint96,uint16) (runs: 1000, μ: 902184, ~: 893333)\nLogs:\n  Bound result 999999999999000000207\n  Bound result 253\n\n[PASS] testFuzz_smallUnitsChargeOnlyIMD(bool,bool,uint16,uint16) (runs: 1000, μ: 430905, ~: 439317)\nLogs:\n  Bound result 45\n  Bound result 8\n\n[PASS] test_blockCountCannotExpireTheTimestampBasedCurve() (gas: 524498)\n[PASS] test_buyAfter1800SecondsKeepsStandingFee() (gas: 267283)\n[PASS] test_buyAt1800SecondsChargesOnlyStandingFee() (gas: 345353)\n[PASS] test_buyAt900SecondsCharges25Point375Percent() (gas: 376646)\n[PASS] test_buyAtOpeningCharges50PercentAndSplitsIMD() (gas: 411710)\n[PASS] test_emptyPoolTradeDoesNotPayARequestBasedFee() (gas: 1034364)\n[PASS] test_exactOutputBuyAfterLaunchHasOnlyStandingFee() (gas: 312831)\n[PASS] test_exactOutputBuyDeliversRequestedTESTIncludingAtOpening() (gas: 347839)\n[PASS] test_exactOutputSellDeliversRequestedNetIMD() (gas: 377515)\n[PASS] test_factoryCanCollectLPFeesAndRemoveLiquidity() (gas: 841049)\n[PASS] test_failedPoolInitializationDoesNotStartTheClock() (gas: 20454171)\n[PASS] test_initializationAndPermissions() (gas: 64535)\n[PASS] test_lastAntiSniperSecondAndBoundary() (gas: 592438)\n[PASS] test_minimumSignedBuyBudgetChargesOnlyItsPartialFill() (gas: 408430)\n[PASS] test_noOneCanRedirectOrChangeTheFees() (gas: 728959)\n[PASS] test_partialExactInputBuyChargesOnlyExecutedIMD() (gas: 410481)\n[PASS] test_partialExactOutputSellChargesOnlyExecutedIMD() (gas: 369672)\n[PASS] test_sellsAtOpeningNeverPayAntiSniper() (gas: 319493)\n[PASS] test_tinyBuysAndSellsDoNotRevertFromFeeRounding() (gas: 420287)\n[PASS] test_zeroSwapCannotPayOrCreateFees() (gas: 310110)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 342.72ms (1.67s CPU time)\n\nRan 1 test for test/HookInvariant.t.sol:HookInvariantTest\n[PASS] invariant_allIMDIsConservedAndClaimsAreExactlyBacked() (runs: 128, calls: 4096, reverts: 0)\n\n╭------------+------------------+-------+---------+----------╮\n| Contract   | Selector         | Calls | Reverts | Discards |\n+============================================================+\n| FeeHandler | advance          | 1009  | 0       | 0        |\n|------------+------------------+-------+---------+----------|\n| FeeHandler | redeem           | 1026  | 0       | 0        |\n|------------+------------------+-------+---------+----------|\n| FeeHandler | rejectRecipients | 1013  | 0       | 0        |\n|------------+------------------+-------+---------+----------|\n| FeeHandler | trade            | 1048  | 0       | 0        |\n╰------------+------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 2447209286\n  Bound result 8420329101685780\n  Bound result 124\n  Bound result 28\n  Bound result 3245\n  Bound result 446993278402754200671\n  Bound result 14064\n  Bound result 0\n  Bound result 2023415125\n  Bound result 36\n  Bound result 368548458\n  Bound result 1222\n  Bound result 216\n  Bound result 128\n  Bound result 12224\n  Bound result 31\n  Bound result 1\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 843.27ms (814.88ms CPU time)\n\nRan 1 test for test/TokenInvariant.t.sol:TokenInvariantTest\n[PASS] invariant_fixedSupplyAndConsentedUntaxedTransfers() (runs: 256, calls: 16384, reverts: 0)\n\n╭--------------------+-----------------+-------+---------+----------╮\n| Contract           | Selector        | Calls | Reverts | Discards |\n+===================================================================+\n| TokenLedgerHandler | approve         | 3184  | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| TokenLedgerHandler | exceedAllowance | 3145  | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| TokenLedgerHandler | overdraw        | 3327  | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| TokenLedgerHandler | transfer        | 3327  | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| TokenLedgerHandler | transferFrom    | 3401  | 0       | 0        |\n╰--------------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 5199\n  Bound result 0\n  Bound result 0\n  Bound result 5456\n  Bound result 13500000000000000000\n  Bound result 0\n  Bound result 0\n  Bound result 3544\n  Bound result 0\n  Bound result 0\n  Bound result 932\n  Bound result 826\n  Bound result 363549772893954163054768334\n  Bound result 13\n  Bound result 864\n  Bound result 2538\n  Bound result 57\n  Bound result 0\n  Bound result 128\n  Bound result 810\n  Bound result 213471900756813328215003847\n  Bound result 0\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.31s (2.31s CPU time)\n\nRan 1 test for test/HookEntitlementInvariant.t.sol:HookToken0EntitlementInvariantTest\n[PASS] invariant_eachVaultOwnsExactlyItsEarnedFees() (runs: 256, calls: 16384, reverts: 0)\n\n╭--------------------+--------------------+-------+---------+----------╮\n| Contract           | Selector           | Calls | Reverts | Discards |\n+======================================================================+\n| EntitlementHandler | advance            | 3230  | 0       | 0        |\n|--------------------+--------------------+-------+---------+----------|\n| EntitlementHandler | attemptRedirection | 3312  | 0       | 0        |\n|--------------------+--------------------+-------+---------+----------|\n| EntitlementHandler | redeem             | 3263  | 0       | 0        |\n|--------------------+--------------------+-------+---------+----------|\n| EntitlementHandler | rejectRecipients   | 3264  | 0       | 0        |\n|--------------------+--------------------+-------+---------+----------|\n| EntitlementHandler | trade              | 3315  | 0       | 0        |\n╰--------------------+--------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 15240\n  Bound result 1602\n  Bound result 53883049562554938008\n  Bound result 37040585390836882696\n  Bound result 1\n  Bound result 906101799633237\n  Bound result 1504\n  Bound result 1113\n  Bound result 686\n  Bound result 15623\n  Bound result 1801\n  Bound result 307\n  Bound result 3179186434829807725\n  Bound result 784\n  Bound result 400\n  Bound result 6969\n  Bound result 12327\n  Bound result 2850\n  Bound result 1746338587\n  Bound result 113\n  Bound result 1609\n  Bound result 350\n  Bound result 1409\n  Bound result 978\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.83s (2.81s CPU time)\n\nRan 1 test for test/HookEntitlementInvariant.t.sol:HookToken1EntitlementInvariantTest\n[PASS] invariant_eachVaultOwnsExactlyItsEarnedFees() (runs: 256, calls: 16384, reverts: 0)\n\n╭--------------------+--------------------+-------+---------+----------╮\n| Contract           | Selector           | Calls | Reverts | Discards |\n+======================================================================+\n| EntitlementHandler | advance            | 3230  | 0       | 0        |\n|--------------------+--------------------+-------+---------+----------|\n| EntitlementHandler | attemptRedirection | 3312  | 0       | 0        |\n|--------------------+--------------------+-------+---------+----------|\n| EntitlementHandler | redeem             | 3263  | 0       | 0        |\n|--------------------+--------------------+-------+---------+----------|\n| EntitlementHandler | rejectRecipients   | 3264  | 0       | 0        |\n|--------------------+--------------------+-------+---------+----------|\n| EntitlementHandler | trade              | 3315  | 0       | 0        |\n╰--------------------+--------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 3888\n  Bound result 1325\n  Bound result 2299\n  Bound result 37040585390836882696\n  Bound result 1\n  Bound result 906101799633237\n  Bound result 23\n  Bound result 920\n  Bound result 1246\n  Bound result 16498\n  Bound result 1801\n  Bound result 307\n  Bound result 9975\n  Bound result 1060\n  Bound result 400\n  Bound result 6917\n  Bound result 14334\n  Bound result 13367\n  Bound result 1817\n  Bound result 12607\n  Bound result 425\n  Bound result 350\n  Bound result 940\n  Bound result 978\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.05s (2.93s CPU time)\n\nRan 12 test suites in 3.05s (10.40s CPU time): 81 tests passed, 0 failed, 2 skipped (83 total tests)\n","passed":true},{"durationMs":57,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"TestHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"TestHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"TestHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"TestHook.quoteSwap((address,address,uint24,int24,address),(bool,int256,uint160))\",\"TestHook.redeemFees()\",\"TestHook.unlockCallback(bytes)\",\"TestToken.approve(address,uint256)\",\"TestToken.transfer(address,uint256)\",\"TestToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":5,\"DEPENDENCIES.md\":12,\"LICENSE\":23,\"README.md\":110,\"VERIFICATION.md\":55,\"foundry.toml\":23,\"remappings.txt\":5,\"src/HookFlags.sol\":31,\"src/TestHook.sol\":253,\"src/TestToken.sol\":12,\"test/HookAdversarial.t.sol\":190,\"test/HookEntitlementInvariant.t.sol\":177,\"test/HookInvariant.t.sol\":99,\"test/HookIsolation.t.sol\":234,\"test/README.md\":43,\"test/RobinhoodFork.t.sol\":36,\"test/TestHook.t.sol\":496,\"test/TestToken.t.sol\":111,\"test/TokenInvariant.t.sol\":107,\"test/helpers/BatchSwapActor.sol\":52,\"test/helpers/HookTestBase.sol\":126,\"test/helpers/PoolActor.sol\":61,\"test/mocks/MockIMD.sol\":24},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"c95d59375cdfe8d4b6f4ee1f4804dd2eff669842e8971633a99e0402ccce491c","verifiedTreeHash":"7f382bf7be79d7416902bb3f32beaeff268395c2","verifierVersion":"0.1.0+560ab6a2"}]}