{"assessments":[],"deployments":[{"attestationHash":"d6c09f725e5ec2f98e502d9fd836cc0c7a5e58ada0c5968b5c25db81f53713c6","chainId":4663,"contracts":[{"address":"0x5abcfa0c2265e1172567cfba7c29db1eb6b0a27b","blockNumber":107862350,"name":"TestToken","role":"token","txHash":"0x6a8dc3c92a8c675771da3672e8d0e0e10e25c319fdaee50e7b6757669787e62b"},{"address":"0xaf1164b96d75094f8c722dbf380e1c26aa5ebcc7","blockNumber":107862350,"name":"MerkleDistributor","role":"distributor","txHash":"0x6a8dc3c92a8c675771da3672e8d0e0e10e25c319fdaee50e7b6757669787e62b"},{"address":"0xaf94f3a1a6926018b849a709314b235f0fd1a0cc","blockNumber":107862350,"name":"TestHook","role":"hook","txHash":"0x6a8dc3c92a8c675771da3672e8d0e0e10e25c319fdaee50e7b6757669787e62b"}],"id":"4695c9df-a762-4f18-801f-bf42d52bc4b0","manifestHash":"c3a3cee4780c1623f56fa051f405f23b2d0bddffbec5cb87243f53e54e52f797","status":"live"}],"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":"5092c577-9969-4445-b17c-b8e255776a16","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"ffee105daecc0ad07f46e6ff7abfc905e9b8ca2a1f61be6888b212c418d7ba6c","dependsOn":["build_contract_project","write_foundry_tests","manifest"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"df4e6731854dbdc1c145bcefcb7534535284137f2a3f2581d5d6ce7b7f7b7de4","dependsOn":["build_contract_project","write_foundry_tests","manifest"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"f31e50dffd3ba237bb038533f8c99bcb2f6c76fa0184e8d2b33dcfc5832bac4b","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"b89279670f5a00f5d26286edee00c97dd158b997031350a01cafe6e0329ad227","skillId":"audit-judge","subagents":4,"tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"b89279670f5a00f5d26286edee00c97dd158b997031350a01cafe6e0329ad227","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"a5fa9006a617a41d0f967faede631c85666c63fff5a29b352fd77f54cdb1da30","dependsOn":["build_contract_project","write_foundry_tests","manifest"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"0f88caabc44ebcfd51a9ffdea4d1a5f9548c75259f36c58c5402fc243d29d91f","dependsOn":["build_contract_project","write_foundry_tests","manifest"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"b89a8f532827f673c5af4c3b213eee4d0cd61b6dd28226356328aba348cb3c81","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"15b04fc8f60521936b9e2b37c09a804685ec689fc98cd5b04510d986d3106a15","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"15b04fc8f60521936b9e2b37c09a804685ec689fc98cd5b04510d986d3106a15","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"a5a4f6ee03563f9d599329de7ab9f38ffdddfb5d6c6309faff494cd6204f63be","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":"d67bb08a131b9d62e477b1c2937c60ad465f612f64394e2f0fc50d0c4de2ab69","dependsOn":["build_contract_project"],"execution":{"forkChains":[4663],"network":false,"profile":"foundry","requires":[],"skillHash":"68506fdcd4ed07ed9dcf40cddecfe9a7d7282e399586d1d75a902eca2f5d692b","skillId":"write-foundry-tests","subagents":4,"tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"68506fdcd4ed07ed9dcf40cddecfe9a7d7282e399586d1d75a902eca2f5d692b","skillId":"write-foundry-tests","state":"accepted"}],"objective":"Token name: SIMD TEST - DO NOT BUY\nToken symbol: SIMDTEST\nLogo: https://www.si-md.xyz/logo.jpg\n\nTHIS IS A TEST TOKEN, NOT A REAL COIN. It has no value and must not be bought. It tests the SIMD relaunch on Robinhood Chain: a plain token whose pool hook takes a small fee, with the launch's remainder held by a claim contract.\n\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.\n\nContracts:\n1. TestHook, the Uniswap v4 hook on the SIMDTEST/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 wallet 0xdFA1f1F6FEAA04F7874efF0b7754b7FFaEBd8BC0. Together with the factory's 1.25% trading fee a trade costs 2% in total. There is no anti-sniper tax and no time or block logic of any kind: the rate is always the same.\n   - The hook never reverts a real swap. No other contracts.\n\nTests: fork tests against Robinhood Chain: the 0.75% on buys and sells lands in that wallet in IMD, the rate never changes, and nothing can redirect the fee.\n","parentJobId":null,"planHash":"172c3f5059c8ee215e2a1587b3d0fd9a50c23413d6a5f03b3e0b361810e24c97","previousHash":"d2fe9ab04e2f7c6e2b5be909914ae2081507f6b37d7f1aae6fe4b139b5d3200f","projectId":"5092c577-9969-4445-b17c-b8e255776a16","publication":{"commit":"72969e9e55ca65fe1dbf091ce3cbb28aa5f2a361","deliveredAt":"2026-11-06T20:29:38.036Z","repoUrl":"https://github.com/identity-md-launches/launch-3076-simd-test-do-not-buy"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[],"site":null,"snapshotHash":"f8c07a028fea175bc7cdae8b9f23ffbf62e3c3bd6278d0cb6592b361ce3ef0ce","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"b0001bc8a5c908cd","findings":[],"hash":"0f88caabc44ebcfd51a9ffdea4d1a5f9548c75259f36c58c5402fc243d29d91f","nodeId":"d6d04021-47ee-4470-b00c-78e4e9f8ac24","outcome":"completed","summary":"I found no defects in the permissions area (access control, trust gap and asymmetry), so the findings list in `.imd-findings.json` is empty. The file also has a coverage row for each of the 8 entry points, all marked `holds`, plus three rows for invariants I checked. I changed no project files. The local suite passes: 38 passed, 3 skipped, 0 failed.\n\n**What I checked:**\n- **Nothing can change the fee or redirect it.** The rate (75 basis points), the recipient wallet and the IMD address are fixed constants. The pool manager, token and pool ID are set once at deploy. There is no owner, setter, fallback, delegatecall or upgrade path. The hook never approves anyone to move its held fee claims, so nobody else can take them.\n- **Callbacks only answer the pool manager.** `beforeInitialize`, `beforeSwap`, `afterSwap` and `unlockCallback` reject any other caller. `quoteSwap` only accepts calls from the hook itself, and v4 skips hook callbacks for the hook's own swaps, so it can't be used to dodge the fee.\n- **The hook works on one pool only.** It rejects any pool except the one it computed at deploy (IMD/SIMDTEST, fee 12500, tick spacing 60, this hook).\n- **Anyone can call `redeemFees()`, by design.** It takes no recipient or currency argument and pays only the fixed wallet. It is capped by the IMD the pool manager actually holds. Calling it inside another unlock fails and leaves the claims untouched.\n- **The fee doesn't depend on the trade's shape.** Buys and sells, exact-input and exact-output, all charge 0.75% of the gross IMD amount. The trader's address, hook data, time and block number never enter the calculation.\n- **Who opens the pool.** `beforeInitialize` doesn't check who opens the pool. That's safe only because the factory deploys the hook and opens the pool in a single transaction, leaving no gap for anyone else to open it first. If deploy and opening were ever split into separate transactions, this would need a check.\n- **`TestToken`** is an unmodified OpenZeppelin ERC20 with no admin powers. Its whole supply (1e27 units) is minted once, to the deployer.\n\n**Static analysis leads:** I looked at each and none became a finding. The divide-before-multiply warning is a deliberate split that keeps the remainder, so nothing is lost. The reentrancy warnings concern events emitted after calls to the pool manager, which is set at deploy and can't be swapped out.\n\n**Not covered:** I didn't run the 3 skipped tests, which are probably the Robinhood Chain fork tests. So the claim that real fees land in the wallet on-chain is still untested here. I also didn't review delta-accounting edge cases in depth; I left them to the other specialists.\n\nThe untracked files in `git status` (shell profiles, `.claude/`, `.vscode` and others) were already there and aren't mine.","treeHash":null,"usage":{"cachedInputTokens":154929,"cost":{"source":"runtime","usdMicros":459802},"inputTokens":8,"model":"claude-opus-5-5","outputTokens":6792,"runtime":"claude","turns":4,"wallClockMs":92977}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"4731c18b8b03b584","findings":[],"hash":"2a177dbb9b440a5790de0ef4dfdbecf5e5392703a0b2ef99c106ea8a881a9450","nodeId":"3ea54a4e-549f-4d02-9322-5b8a7970ef7a","outcome":"failed","summary":"This content was flagged for possible cybersecurity risk. If this seems wrong, try rephrasing your request. If you’re doing authorized security work that requires more cyber permissive safeguards, apply for Daybreak access via https://platform.openai.com/… before retrying.","treeHash":null,"usage":{"cachedInputTokens":1872512,"cost":{"source":"estimated","usdMicros":3972572},"inputTokens":91301,"model":"gpt-6-astra","outputTokens":23741,"runtime":"codex","turns":5,"wallClockMs":713418}},{"artifacts":[],"attempt":1,"bundleHash":"2ef1e776aead5c494de27fc17d0dbc94034b080c41b1dfec0a252918e18eafec","device":"75816c4fc9cac7f3","findings":[],"hash":"818198ef7f773086ebaf7d7194be4e096a0c9a18f27fce48d5ff51ce24acfa4f","nodeId":"eb1adfb1-32f4-4ba0-9260-5f6fde22ca19","outcome":"completed","summary":"Created `launch.json` matching the accepted contracts, constructor arguments, five hook permissions, and required pool values. Only `launch.json` changed.\n\nValidation: JSON/source/ABI checks passed; `forge build` succeeded; seeded `forge test` passed 38 tests. Three fork suites skipped because no fork was selected.","treeHash":"b711ac4dd830a15251d5d203870a47a47d5429a6","usage":{"cachedInputTokens":333440,"cost":{"source":"estimated","usdMicros":196646},"inputTokens":53169,"model":"gpt-6.1-sol","outputTokens":2362,"runtime":"codex","turns":3,"wallClockMs":65015}},{"artifacts":[],"attempt":1,"bundleHash":"00f5e0766a2fda99507b139dbd7a0f386bfd3370de1b8cde4bc1d883c1ce7a30","device":"829d84438fb3fdc8","findings":[],"hash":"a5a4f6ee03563f9d599329de7ab9f38ffdddfb5d6c6309faff494cd6204f63be","nodeId":"eb1adfb1-32f4-4ba0-9260-5f6fde22ca19","outcome":"completed","summary":"I left `launch.json` unchanged. The manifest from the previous round already describes the hook and token in this repository correctly, and this round listed no findings that call for an edit. No other file was touched.\n\nWhat I compared it against:\n- **Kind:** the first field is `\"kind\": \"univ4_hook\"`.\n- **Hook:** the contract is `TestHook`. Its constructor is `(IPoolManager poolManager_, address token_)` (`src/TestHook.sol:47`), so the arguments are `[\"$poolManager\", \"$token\"]`, one per parameter and in order. It has no owner, factory or launch-number parameter, so none of those placeholders appear.\n- **Permissions:** the manifest lists `beforeInitialize`, `beforeSwap`, `afterSwap`, `beforeSwapReturnDelta` and `afterSwapReturnDelta`. That is exactly what `getHookPermissions` sets (`src/TestHook.sol:70-76`). `beforeInitialize` only accepts calls from the PoolManager, checks the whole pool key including fee 12500 and tick spacing 60, and returns the right selector.\n- **Token:** the contract is `TestToken`, named \"SIMD TEST - DO NOT BUY\" with symbol \"SIMDTEST\" and 18 decimals, and its constructor takes no arguments.\n- **Pool:** `pairedCurrency` is `0x5f7bb59365ce557c26dbcaa4ee9d39a4b95b7127`, `fee` is 12500, `tickSpacing` is 60 and `initialPrice` is `\"79228162514264337593543950336\"`. These match the hook's own constants and the launch facts.\n- **Notes:** the notes are 1,416 characters, under the 4,000 cap. They name the fee wallet and the logo, say the token has no value, and state that nobody can change anything after deploy.\n\nChecks:\n- The file parses as valid JSON. I checked its fields by reading them; I had no schema validator to run.\n- `forge build` succeeds.\n- `forge test` with the verifier's fuzz seed: 52 tests passed and 0 failed.\n- 3 tests were skipped: the Robinhood Chain fork tests. No fork RPC is configured on this machine, so they have not run against live chain state here.","treeHash":"86a29b00f65c0c4a7b9d5b0b2a1f23dea10bd18c","usage":{"cachedInputTokens":152391,"cost":{"source":"runtime","usdMicros":274837},"inputTokens":10,"model":"claude-opus-5-5","outputTokens":1909,"runtime":"claude","turns":5,"wallClockMs":37250}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"523ef565dd740e25","findings":[{"citation":"resolved","description":"Boundary (external call) x invariant seam. When the PoolManager already holds IMD (any pool seeded with IMD, or the live manager which holds ~50,950 IMD at the pinned block), _pay's take succeeds inside beforeSwap/afterSwap and moves `fee` IMD out of the manager in the middle of the caller's swap. PoolManager.settle credits the payer with balanceOf(manager) - reservesOf(synced). A router that uses the documented sync -> transfer -> settle sequence but places the swap between the transfer and the settle (sync(IMD); IMD.transfer(manager, R); swap; settle()) therefore sees its credit reduced by exactly the hook fee: it is credited R - fee against a debt of R (swap delta -(R-fee) plus hook delta -fee), the unlock ends with a non-zero delta and reverts with CurrencyNotSettled. The same prepaid flow for a sell (SIMDTEST is the synced currency) succeeds, so the asymmetry is specific to the IMD side the hook takes from. The brief states the hook never reverts a real swap; for this router shape every fee-bearing buy reverts whenever the manager has enough IMD for the direct payout, and succeeds only when the take fails and the claim path is used instead. Uniswap's V4Router / Universal Router settle after the swap and are unaffected, which is why this is low rather than medium. A fix that preserves the design: always mint the ERC-6909 claim in the callbacks (never take mid-swap) and let redeemFees deliver, or take only when `imd.balanceOf(poolManager) - transient reserves` covers the fee.","line":167,"path":"src/TestHook.sol","reproduction":"State: HookFixture pool (IMD first), LP range [-600,600] with 10,000,000e18 liquidity, so manager holds IMD. Router R deployed with 1000e18 IMD. Call R.swapPrepaid(key, SwapParams(zeroForOne=true, amountSpecified=-100e18, limit=MIN_SQRT_PRICE+1), IMD, 100e18) where the callback does: manager.sync(IMD); IMD.transfer(manager, 100e18); manager.swap(...); manager.settle(); take SIMDTEST. Expected: trade fills, 0.75e18 IMD to 0xdFA1..8BC0, router pays exactly 100e18. Actual: hook takes 0.75e18 IMD out of the manager during beforeSwap, settle credits only 99.25e18, router delta is -0.75e18 at unlock end, PoolManager reverts CurrencyNotSettled. Same router selling 100e18 SIMDTEST exact-input succeeds. Scratch test test/scratch/SyncBeforeSwap.t.sol (test_prepaidExactInputBuyReverts expects the CurrencyNotSettled revert and passes on this code; test_prepaidExactInputSellSucceeds passes).","severity":"low","snippet":"        try poolManager.take(imd, FEE_RECIPIENT, fee) {","title":"In-swap take of the fee breaks routers that sync and prepay IMD before calling swap (CurrencyNotSettled)"},{"citation":"resolved","description":"The README documents the recorded fork at block 107794459 and says 'No launch.json is provided; the later manifest step supplies it' (README.md lines 56-57), but test/RobinhoodFork.t.sol pins FORK_BLOCK = 107_806_973 and launch.json exists in the tree. Running the README command pins a different block than the suite's own constant (vm.createSelectFork(url, FORK_BLOCK) overrides the CLI block), so a reader following the README cannot reproduce the suite's recorded run. Documentation only; no code impact.","line":163,"path":"README.md","reproduction":"Compare README.md:163 (107794459) with test/RobinhoodFork.t.sol:24 (`uint256 internal constant FORK_BLOCK = 107_806_973;`) and README.md:56-57 with the presence of launch.json. Expected: one pinned block and an accurate statement about the manifest; actual: two blocks and a stale statement.","severity":"info","snippet":"  --fork-block-number 107794459 -vv","title":"README fork block and manifest statements disagree with the tree"}],"hash":"a5fa9006a617a41d0f967faede631c85666c63fff5a29b352fd77f54cdb1da30","nodeId":"7a78a3e5-affd-438e-9ab4-ff863239210b","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds two findings and a full coverage record. Nothing in the tree was changed except the findings file and one scratch test under test/scratch/.\n\n**Math verdict: the fee arithmetic is exact.** I checked the hook against the Math Precision, Boundary and Numerical Gap guides. The remainder-preserving `_fraction` helper equals floor(amount × 75 / d) for every uint256 input, so the Slither divide-before-multiply lead is a false positive. The gross-up bound for the two specified-IMD cases gives a fee of maxFee or maxFee+1 before the cap, so full fills always charge exactly floor(0.75% × gross). A brute force over every amount up to 200,000 in all four swap modes, including partial fills within a few wei of the quoted amount, found no off-by-one. HookDeltaExceedsSwapAmount is unreachable and the int128 casts are bounded by the quote deltas.\n\n**Findings written:**\n\n- **Low.** The in-swap `take` of the fee moves IMD out of the PoolManager mid-swap. A router that syncs and transfers IMD before calling swap, then settles afterwards, is credited only gross minus fee and the unlock reverts with CurrencyNotSettled on every fee-bearing buy whenever the manager holds enough IMD for the direct payout. Uniswap's own routers settle after the swap, so the impact is limited. Reproduced in test/scratch/SyncBeforeSwap.t.sol, where the prepaid buy reverts and the prepaid sell succeeds.\n- **Info.** The README documents fork block 107794459 and says no launch.json exists, while the test pins 107806973 and launch.json is present.\n\n**Fork verification.** Forge refuses loopback through the sandbox proxy, so I ran a small forwarder and pointed the suite at it. All 53 fork tests passed against live Robinhood Chain state and the live PoolManager, including the fuzz and invariant tests. IMD on chain 4663 is not a proxy and exposes no pause or blocklist selectors.\n\n**Coverage.** All eight entry points have rows. beforeSwap and afterSwap reference finding 1; the rest hold. Five invariant rows record the arithmetic proofs and fork results.","treeHash":null,"usage":{"cachedInputTokens":1561329,"chain":[{"calls":278,"chainId":4663,"failed":0,"own":268,"public":0,"refused":10}],"cost":{"source":"runtime","usdMicros":5570295},"inputTokens":418,"model":"claude-fable-5-1","outputTokens":43717,"runtime":"claude","turns":36,"wallClockMs":628716}},{"artifacts":[],"attempt":1,"bundleHash":"80af52de1beba1d00a5301a544630a097b7c4c4121e28317c5798af7f1ec0c8b","device":"7236faf97e42979a","findings":[],"hash":"b89a8f532827f673c5af4c3b213eee4d0cd61b6dd28226356328aba348cb3c81","nodeId":"6b0cefc7-dc10-40c3-9b13-3956c30ca15a","outcome":"completed","summary":"Done. The medium finding reproduced, is fixed, and the reviewer's proof now passes; the low finding is fixed with a named error; the three info findings are answered in `.imd-responses.json`.\n\n**What was wrong.** The hook paid its fee with `take(IMD)` in the middle of the swap. A buyer that calls `sync(IMD)` before the swap has an open measuring window, so `settle()` credited it the fee short. That reverted the buy or charged the fee twice, exactly as the proof showed on the starting tree.\n\n**What changed in `src/TestHook.sol`:**\n- `_pay` now reads the PoolManager's synced currency. If it is IMD, the fee is minted as the existing ERC-6909 claim instead of taken. Every other caller keeps direct wallet delivery, and `redeemFees()` still delivers claims only to the fixed wallet.\n- Exact-output sells above a new public constant, `MAX_EXACT_OUTPUT_SELL`, revert with the named `ExactOutputTooLarge` instead of v4's arithmetic panic. The fee is never clamped, per the reviewer's guidance.\n\n**Tests added** (run in both token orderings and on the fork):\n- A prepaid driver mode that pays before the swap and requires `settle()` to credit exactly the transfer with no debt left.\n- Prepaid exact-input and exact-output buys, prepaid sells still paying the wallet directly, and the exact-output bound at three points.\n- The invariant handler now mixes both settlement orders.\n\n**Results.** Build passes. The full suite passes with the verifier's fuzz seed: 53 tests, fork suite skipped offline. The fork suite passed 21 of 21 against live IMD on Robinhood Chain at block 107846725. The proof passes 3 of 3. `forge fmt --check` is clean on delivered paths.\n\n**Findings answered:**\n- Medium (synced-currency take): fixed.\n- Low (int256 overflow): reproduced on the original hook, fixed.\n- Info (stale fork docs): not reproducible on this tree. The contract name, fork mechanism, local manager and absence of `launch.json` all match the README. The one real staleness found was the pinned block, which the public RPC no longer serves. README now pins the block I ran and warns about the limited history.\n- Info (1.99% vs 2%) and info (dust rounding): disputed as documented, intentional behaviour.\n\nOne note: `.imd-responses.json` is written at the repository root but is listed in `.git/info/exclude`, which I may not touch. The harness collects it from the working tree.","treeHash":"01284ee6dda7fbe8a0e053915af4de7096f50551","usage":{"cachedInputTokens":1637572,"cost":{"source":"runtime","usdMicros":4698127},"inputTokens":610,"model":"claude-fable-5-1","outputTokens":41451,"runtime":"claude","turns":52,"wallClockMs":1058583}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"e67cc6ec4009b226","findings":[{"citation":"resolved","description":"Merged from audit_flow (medium) and audit_math (low): same root cause, kept at medium because I reproduced a loss, not only a revert.\n\n_pay() delivers the fee with poolManager.take(IMD, FEE_RECIPIENT, fee) inside beforeSwap (exact-input buys) or afterSwap (exact-output buys), which lowers the manager's IMD balance while the caller's swap is still open. PoolManager.settle() credits an ERC-20 payer with balanceOf(manager) - reserves recorded by the last sync(). If the caller called sync(IMD) before the swap (sync -> transfer -> swap -> settle, or sync -> swap -> transfer -> settle), the hook's take falls inside that measuring window and the caller is credited `fee` less than it transferred, while still owing the full gross amount (pool input + hook fee). The try/catch fallback never helps here: the take succeeds, funded by the caller's own prepayment or the manager's standing IMD.\n\nThree outcomes, all reproduced on the vendored PoolManager:\n1. A buyer that settles once: the unlock ends with a -fee IMD delta and reverts CurrencyNotSettled. This contradicts the brief ('The hook never reverts a real swap') and README.md ('payout failure cannot veto a valid, funded v4 trade').\n2. A buyer that pays whatever debt the manager still reports: it pays gross + fee. The wallet gets the fee once; the second copy sits in the PoolManager as unaccounted IMD that nobody can recover.\n3. An exact-output buy that prepays a maximum and takes the unused IMD back: no revert at all, the refund is silently short by one hook fee, same stranded IMD.\n\nScope: only buys, and only callers that sync IMD before the swap. Callers that swap first and then sync/transfer/settle (Uniswap's V4Router and Universal Router, including a SETTLE action placed before the swap, which syncs and settles atomically) are not affected, and the same prepaid flow on a hookless pool with the same tokens works. Sells are unaffected (the synced currency is SIMDTEST).\n\nFix that keeps the design (direct delivery to the wallet in the normal case, claim otherwise): in _pay, mint the ERC-6909 claim instead of calling take when TransientStateLibrary.getSyncedCurrency(poolManager) == IMD; redeemFees() already delivers claims to the fixed wallet. Always minting in the callbacks also fixes it. I applied the first variant to a scratch copy of the hook and all three proof tests pass against it.\n\nTest gap behind this: every swap in the suite goes through test/helpers/PoolDriver.sol, which only ever settles after the swap, so no test exercises a payment that brackets the swap.\n\nCorroborated on live state (scratch fork at Robinhood Chain block 107835999, live PoolManager 0x8366...0951, real IMD): case A reverts CurrencyNotSettled(); case B: buyer spent 1007.5 IMD, wallet got 7.5, the manager's IMD balance rose by 1000 while the pool was only credited 992.5.","line":167,"path":"src/TestHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {TransientStateLibrary} from \"v4-core/src/libraries/TransientStateLibrary.sol\";\nimport {TestHook} from \"src/TestHook.sol\";\nimport {TestToken} from \"src/TestToken.sol\";\n\ncontract PlainIMD is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {}\n}\n\n/// @dev A buyer that pays IMD into the PoolManager around the swap instead of after it.\n/// prepay == 0: swap, then sync -> transfer -> settle (the V4Router order, used as the control).\n/// prepay != 0: sync(IMD) -> transfer(prepay) -> swap -> settle(), then take back whatever the\n/// manager says is owed to it. `topUp` additionally pays any debt the manager still reports.\ncontract PrepayBuyer is IUnlockCallback {\n    using TransientStateLibrary for IPoolManager;\n\n    IPoolManager internal immutable pm;\n    address internal immutable imd;\n\n    constructor(IPoolManager pm_, address imd_) {\n        pm = pm_;\n        imd = imd_;\n    }\n\n    function buy(PoolKey memory key, SwapParams memory p, uint256 prepay, bool topUp) external {\n        pm.unlock(abi.encode(key, p, prepay, topUp));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(pm), \"manager only\");\n        (PoolKey memory key, SwapParams memory p, uint256 prepay, bool topUp) =\n            abi.decode(data, (PoolKey, SwapParams, uint256, bool));\n        if (prepay != 0) {\n            pm.sync(Currency.wrap(imd));\n            IERC20(imd).transfer(address(pm), prepay);\n        }\n        pm.swap(key, p, \"\");\n        if (prepay != 0) pm.settle();\n        _close(key.currency0, prepay == 0 || topUp);\n        _close(key.currency1, prepay == 0 || topUp);\n        return \"\";\n    }\n\n    function _close(Currency c, bool payDebt) internal {\n        int256 v = pm.currencyDelta(address(this), c);\n        if (v > 0) {\n            pm.take(c, address(this), uint256(v));\n        } else if (v < 0 && payDebt) {\n            pm.sync(c);\n            IERC20(Currency.unwrap(c)).transfer(address(pm), uint256(-v));\n            pm.settle();\n        }\n    }\n}\n\ncontract LiquidityAdder is IUnlockCallback {\n    IPoolManager internal immutable pm;\n\n    constructor(IPoolManager pm_) {\n        pm = pm_;\n    }\n\n    function add(PoolKey memory key, ModifyLiquidityParams memory p) external {\n        pm.unlock(abi.encode(key, p));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        (PoolKey memory key, ModifyLiquidityParams memory p) = abi.decode(data, (PoolKey, ModifyLiquidityParams));\n        (BalanceDelta d,) = pm.modifyLiquidity(key, p, \"\");\n        _pay(key.currency0, d.amount0());\n        _pay(key.currency1, d.amount1());\n        return \"\";\n    }\n\n    function _pay(Currency c, int128 v) internal {\n        if (v < 0) {\n            pm.sync(c);\n            IERC20(Currency.unwrap(c)).transfer(address(pm), uint256(-int256(v)));\n            pm.settle();\n        }\n    }\n}\n\n/// The hook pays its IMD fee with poolManager.take() in the middle of the swap. A buyer that has\n/// already called sync(IMD) is then credited by settle() with `fee` less than it transferred.\ncontract PrepaidBuyTest is Test {\n    address internal constant IMD = 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127;\n    address internal constant RECIPIENT = 0xdFA1f1F6FEAA04F7874efF0b7754b7FFaEBd8BC0;\n    uint256 internal constant START = 10_000 ether;\n\n    IPoolManager internal pm;\n    TestHook internal hook;\n    TestToken internal token;\n    PoolKey internal key;\n    PrepayBuyer internal buyer;\n    bool internal imd0;\n\n    function setUp() public {\n        vm.etch(IMD, address(new PlainIMD()).code);\n        pm = IPoolManager(address(new PoolManager(address(this))));\n        token = new TestToken();\n        imd0 = IMD < address(token);\n        bytes32 h = keccak256(abi.encodePacked(type(TestHook).creationCode, abi.encode(pm, address(token))));\n        for (uint256 s;; ++s) {\n            address a = address(\n                uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(s), h))))\n            );\n            if (uint160(a) & 0x3fff == 0x20cc) {\n                hook = new TestHook{salt: bytes32(s)}(pm, address(token));\n                break;\n            }\n        }\n        key = PoolKey(\n            Currency.wrap(imd0 ? IMD : address(token)),\n            Currency.wrap(imd0 ? address(token) : IMD),\n            12_500,\n            60,\n            IHooks(address(hook))\n        );\n        pm.initialize(key, TickMath.getSqrtPriceAtTick(0));\n        LiquidityAdder lp = new LiquidityAdder(pm);\n        deal(IMD, address(lp), 1_000_000 ether);\n        token.transfer(address(lp), 1_000_000 ether);\n        lp.add(key, ModifyLiquidityParams(-600, 600, 1_000_000 ether, bytes32(0)));\n        buyer = new PrepayBuyer(pm, IMD);\n        deal(IMD, address(buyer), START);\n    }\n\n    function _buy(int256 amountSpecified) internal view returns (SwapParams memory) {\n        return SwapParams(imd0, amountSpecified, imd0 ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1);\n    }\n\n    function _spent() internal view returns (uint256) {\n        return START - IERC20(IMD).balanceOf(address(buyer));\n    }\n\n    function _feeCollected() internal view returns (uint256) {\n        return IERC20(IMD).balanceOf(RECIPIENT) + pm.balanceOf(address(hook), uint160(IMD));\n    }\n\n    /// Exact-input buy of 1000 IMD, paid in full before the swap. Must fill for exactly 1000 IMD.\n    /// Today settle() credits 992.5 and the unlock reverts CurrencyNotSettled.\n    function test_prepaidExactInputBuyFillsForItsBudget() public {\n        buyer.buy(key, _buy(-1000 ether), 1000 ether, false);\n        assertEq(_spent(), 1000 ether, \"buyer pays exactly its exact-input budget\");\n        assertEq(_feeCollected(), 7.5 ether, \"0.75% of gross IMD is charged once\");\n        assertGt(token.balanceOf(address(buyer)), 0);\n    }\n\n    /// The same buy through a buyer that pays whatever debt the manager still reports.\n    /// Today it pays 1007.5 IMD: the hook fee twice, the second copy stranded in the manager.\n    function test_prepaidExactInputBuyIsNotChargedTheFeeTwice() public {\n        buyer.buy(key, _buy(-1000 ether), 1000 ether, true);\n        assertEq(_spent(), 1000 ether, \"buyer pays exactly its exact-input budget\");\n        assertEq(_feeCollected(), 7.5 ether, \"0.75% of gross IMD is charged once\");\n    }\n\n    /// Exact-output buy of 500 SIMDTEST: prepay 1000 IMD, take the unused IMD back. The cost must\n    /// equal the cost of the same buy settled after the swap. Today it costs one hook fee more.\n    function test_prepaidExactOutputBuyCostsTheSameAsSettlingAfter() public {\n        uint256 snap = vm.snapshotState();\n        buyer.buy(key, _buy(500 ether), 0, false);\n        uint256 controlCost = _spent();\n        uint256 controlFee = _feeCollected();\n        vm.revertToState(snap);\n\n        buyer.buy(key, _buy(500 ether), 1000 ether, false);\n        assertEq(token.balanceOf(address(buyer)), 500 ether);\n        assertEq(_feeCollected(), controlFee, \"same hook fee\");\n        assertEq(_spent(), controlCost, \"prepaying must not cost more than settling after the swap\");\n    }\n}","reproduction":"State: fresh vendored PoolManager, plain ERC-20 etched at IMD 0x5F7B...7127, TestHook CREATE2-mined to address bits 0x20cc, pool (IMD, SIMDTEST, 12500, 60, hook) initialized at tick 0 with liquidity 1,000,000e18 in [-600, 600]; buyer contract holds 10,000 IMD.\n\nA) Buyer unlock callback: pm.sync(IMD); IMD.transfer(pm, 1000e18); pm.swap(key, {zeroForOne: IMD is currency0, amountSpecified: -1000e18, limit: MIN+1 / MAX-1}, ''); pm.settle(); pm.take(SIMDTEST, buyer, out). Expected: fills, buyer spends exactly 1000 IMD, 7.5 IMD charged. Actual: hook takes 7.5 IMD from the manager in beforeSwap, settle() returns 992.5e18, unlock reverts CurrencyNotSettled().\n\nB) Same, but after settle() the buyer pays its remaining reported debt (sync, transfer 7.5e18, settle). Expected spend 1000e18. Actual 1007500000000000000000: wallet received 7.5, a further 7.5 IMD is stranded in the manager.\n\nC) Exact-output buy of 500e18 SIMDTEST: pm.sync(IMD); transfer 1000e18; swap(amountSpecified: +500e18); settle(); take back the positive IMD delta. Expected cost equals the same buy settled after the swap, 510410483754774750510 wei. Actual 514238562382935561138 wei, i.e. 3828078628160810628 wei more = exactly one extra hook fee, with no revert.\n\nRun: forge test --match-path test/scratch/PrepaidBuy.t.sol (the attached proof). On this tree: [FAIL: CurrencyNotSettled()] test_prepaidExactInputBuyFillsForItsBudget, [FAIL: ... 1007500000000000000000 != 1000000000000000000000] test_prepaidExactInputBuyIsNotChargedTheFeeTwice, [FAIL: ... 514238562382935561138 != 510410483754774750510] test_prepaidExactOutputBuyCostsTheSameAsSettlingAfter.","severity":"medium","snippet":"        try poolManager.take(imd, FEE_RECIPIENT, fee) {","title":"Fee is taken out of the PoolManager mid-swap: a buyer that syncs IMD before the swap is under-credited by the fee, so the buy reverts or pays the hook fee twice"},{"citation":"resolved","description":"From audit_economics; reproduced and kept at low.\n\nFor an exact-output sell (SIMDTEST in, IMD out, amountSpecified = N > 0) beforeSwap returns the fee as a positive specified delta. v4-core then does a checked `amountToSwap += hookDeltaSpecified` (lib/v4-core/src/libraries/Hooks.sol:296). When N > type(int256).max - fee the addition overflows and the swap reverts with Panic(0x11). Lines 103-104 saturate only the simulated quote, not the outer order. A hookless pool with the same tokens and liquidity accepts the same order and fills it to the price limit, so this is a swap the hook reverts, against the brief's 'The hook never reverts a real swap'.\n\nImpact is narrow, which is why it is low: no funds move, an IMD fill can never exceed int128, so N = type(int128).max expresses the same 'sell until the price limit' order and works (verified), and Uniswap's V4Router cannot express N above uint128 at all. The mirrored sentinels work: exact-input buy with type(int256).min, exact-input sell with type(int256).min and exact-output buy with type(int256).max all fill and pay the fee. README.md and REVIEW.md mention signed-arithmetic limits in general terms but attribute them to v4 and give no bound; here the overflow is caused by the hook's own delta on an order v4 alone accepts.\n\nFix guidance: do NOT clamp the fee to the remaining room (int256.max - N). That would make N = type(int256).max a fee-free sell for anyone. A hook cannot lower amountToSwap while charging a specified-side fee, so the honest options are to revert early with a named error when N > type(int256).max - uint256(uint128(type(int128).max)) and to state that bound for exact-output sells where the README describes what can still revert.","line":112,"path":"src/TestHook.sol","reproduction":"State: fresh vendored PoolManager, plain ERC-20 etched at IMD (currency0 in this run), TestHook mined to bits 0x20cc, hooked pool and a hookless twin (same currencies, fee 12500, spacing 60) both at tick 0 with liquidity 1,000,000e18 in [-600, 600]. Router swaps then settles.\n\nOrder: swap(zeroForOne = false (SIMDTEST in, IMD out), amountSpecified = type(int256).max, sqrtPriceLimitX96 = getSqrtPriceAtTick(120)).\nHookless twin: succeeds, 5981737760509662598933 wei IMD out for 6093908120322192630387 wei SIMDTEST in.\nHooked pool, expected: same fill, 44863033203822469491 wei IMD (0.75% of gross) to the wallet. Actual: [FAIL: panic: arithmetic underflow or overflow (0x11)].\nSame order with amountSpecified = int256(type(int128).max) on the hooked pool: succeeds, trader nets 5936874727305840129442 wei IMD, wallet receives 44863033203822469491.\nRun as test/scratch/Explore.t.sol: test_plain_exactOutSell_int256max PASS, test_hook_exactOutSell_int256max FAIL panic 0x11, test_hook_exactOutSell_int128max PASS.","severity":"low","snippet":"        return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(fee)), 0), 0);","title":"Exact-output sell with amountSpecified near type(int256).max panics in v4's amountToSwap addition because of the hook's fee delta"},{"citation":"resolved","description":"From audit_math (info); reproduced and extended. Documentation only, no contract impact.\n\n1. README.md:161 filters on `--match-contract RobinhoodForkTest`. The fork contracts are RobinhoodForkIMDFirstTest, RobinhoodForkIMDSecondTest and RobinhoodForkInvariantTest (test/RobinhoodFork.t.sol:270, 276, 349); none matches, so the command runs nothing.\n2. The command passes --fork-url / --fork-block-number, but the suite forks only from the ROBINHOOD_RPC_URL environment variable and skips when it is unset (test/RobinhoodFork.t.sol:31-35), so even with the right filter the documented command skips every fork test.\n3. README.md:163 and :166 give block 107794459; the suite pins `FORK_BLOCK = 107_806_973` (test/RobinhoodFork.t.sol:24).\n4. README.md:170-171 and REVIEW.md:38 say the fork uses a fresh local PoolManager; the suite uses the live manager 0x8366a39CC670B4001A1121B8F6A443A643e40951.\n5. README.md:55-56 says 'No `launch.json` is provided'; launch.json is in the tree.\n\nA reader following the README cannot reproduce the fork evidence the brief asks for. The suite itself is sound: a scratch copy pinned to a recent block (107835618) passed all 53 tests against live IMD and the live manager with ROBINHOOD_RPC_URL set.","line":161,"path":"README.md","reproduction":"Run `forge test --match-contract RobinhoodForkTest`: expected the fork suite, actual 'No tests found in project'. Compare README.md:163 (107794459) with test/RobinhoodFork.t.sol:24 (107_806_973); README.md:55-56 with the presence of launch.json; REVIEW.md:38 with LIVE_MANAGER at test/RobinhoodFork.t.sol:28.","severity":"info","snippet":"  forge test --match-contract RobinhoodForkTest \\\n  --fork-url https://rpc.mainnet.chain.robinhood.com \\\n  --fork-block-number 107794459 -vv","title":"README and REVIEW.md describe a fork run the tree no longer contains: the documented command runs zero tests, and the block, manager and manifest statements are stale"}],"hash":"c7f3efa42510b20d76563d569128d306dd53535d5c0362ac42c33dd1bae5385c","nodeId":"62928621-f733-4644-a8ea-91088b0c0848","outcome":"completed","summary":"The review is complete: `.imd-findings.json` holds three findings and all eight entry points are answered. No project file was changed, and my scratch tests are removed.\n\n## Findings kept\n\n1. **Medium — the fee is taken out of the PoolManager mid-swap** (`src/TestHook.sol:167`). This merges the flow specialist's medium and the math specialist's low, which share one root cause. A buyer that calls `sync(IMD)` before the swap is credited one hook fee less than it paid. I reproduced three outcomes:\n   - A buyer that settles once reverts with `CurrencyNotSettled`.\n   - A buyer that tops up its remaining debt pays 1007.5 IMD for a 1000 IMD buy; the extra 7.5 IMD is stranded in the manager.\n   - An exact-output buy that prepays and takes the remainder back is silently short by one fee, with no revert.\n\n   I kept it at medium because the last two are a loss, which neither specialist reported. Buyers that swap first and settle after, as Uniswap's own routers do, are unaffected. The attached proof (three tests) fails on this code and passes against a scratch copy that mints the claim when IMD is the synced currency. A scratch fork on the live manager with real IMD showed the same revert and the same 1007.5 IMD spend.\n\n2. **Low — exact-output sell with `amountSpecified` near `type(int256).max` panics** (`src/TestHook.sol:112`). A hookless twin pool fills the same order; the hooked pool reverts with panic 0x11. Using `type(int128).max` instead works, and the other three sentinel orders fill. The fix must not clamp the fee, which would make that order a fee-free sell; a named error and a stated bound are the realistic remedy.\n\n3. **Info — the README and REVIEW.md describe a fork run the tree no longer contains** (`README.md:161`). The documented command matches no test contract and does not set the `ROBINHOOD_RPC_URL` variable the suite reads, so it runs nothing. The block number, the \"fresh local manager\" statement and \"no launch.json\" are also stale.\n\n## Dropped\n\n- **\"2% in total\" is really 1.990625%** (economics specialist, info). The arithmetic is right, but it is not a defect: the 0.75% and 1.25% rates are each implemented as specified, and the README and manifest notes already disclose the difference.\n\n## Other checks\n\n- **Static analysis leads:** the divide-before-multiply line is a false positive, since `_fraction` computes the exact floor. The reentrancy-event lines are benign.\n- **My own pass found nothing further.** A 3,000-run differential fuzz against a hookless twin (four swap modes, multi-range liquidity, random price limits, amounts from 1 wei to `int128.max`) never reverted where the twin filled, and the fee stayed within 1 wei of 0.75%.\n- **Deployability and manifest hold:** sizes are well under the limits, constructor arguments and permissions match `launch.json`, and the hook satisfies the supplied floor tests as I read them — I could not run them, since their toolchain helpers are not in the tree.\n- **Fork suite:** the public RPC does not serve the pinned block 107,806,973, so I could not run the suite there. A scratch copy pinned to a recent block passed all 53 tests against live IMD and the live manager.","treeHash":null,"usage":{"cachedInputTokens":3616915,"cost":{"source":"runtime","usdMicros":7720560},"inputTokens":59,"model":"claude-fable-5-1","outputTokens":72138,"runtime":"claude","turns":33,"wallClockMs":851016}},{"artifacts":[],"attempt":1,"bundleHash":"271019173bb058a1f46327329f2638004971851e5f9ff0a8409627ddf14957a8","device":"f6591e7f19d7b066","findings":[],"hash":"d0075102e4390b187d2aa58ce79924b8df39bb454bd89417f6dbf59cf597989d","nodeId":"6b0cefc7-dc10-40c3-9b13-3956c30ca15a","outcome":"completed","summary":"Implemented the fixed-supply SIMDTEST token and immutable 0.75% IMD fee hook, including partial-fill handling and deferred payouts.\n\nVerified with the required fuzz seed:\n\n- `forge build` and `forge fmt --check` passed.\n- 38 offline tests passed.\n- 16 Robinhood fork tests passed.\n\nDeployment parameters, nominal 2% fee accounting, and operational responsibilities are documented in [README.md](/home/seat/.identitymd/work/5092c577-9969-4445-b17c-b8e255776a16/6b0cefc7-dc10-40c3-9b13-3956c30ca15a/README.md).","treeHash":"0db436d84186ce3ec0181c184d3748d8a8fac208","usage":{"cachedInputTokens":3520384,"chain":[{"calls":159,"chainId":4663,"failed":0,"own":159,"public":0,"refused":0}],"cost":{"source":"estimated","usdMicros":6572494},"inputTokens":109876,"model":"gpt-6-astra","outputTokens":39067,"runtime":"codex","turns":9,"wallClockMs":1140584}},{"artifacts":[],"attempt":1,"bundleHash":"e1d24c99f08dd1ae92d5427b3d8474a908110a4c482b7768bfd9a99dbbafdda1","device":"d32559488518f947","findings":[],"hash":"d67bb08a131b9d62e477b1c2937c60ad465f612f64394e2f0fc50d0c4de2ab69","nodeId":"3ea54a4e-549f-4d02-9322-5b8a7970ef7a","outcome":"completed","summary":"","treeHash":"277b9e39942009d1924a1d6a73d0f6755300a3ba","usage":{"cachedInputTokens":2286591,"cost":{"source":"runtime","usdMicros":2728941},"inputTokens":76,"model":"claude-opus-5-5","outputTokens":61562,"runtime":"claude","turns":61,"wallClockMs":642145}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"fbdcf3c80b280e3b","findings":[{"citation":"resolved","description":"Execution-trace / periphery seam. _pay() pays the fee with poolManager.take(IMD, FEE_RECIPIENT, fee), which moves IMD out of the PoolManager's balance in the middle of the swap (beforeSwap for exact-input buys, afterSwap for exact-output buys). v4 settlement for ERC-20s measures payment as balanceNow - syncedReserves (PoolManager._settle: `paid = reservesNow - reservesBefore`). A caller that does the valid v4 order sync(IMD) -> transfer IMD -> swap -> settle() (prepaying input, or syncing early) has its settlement measured after the hook removed `fee` IMD, so settle() credits `paid - fee`, the caller's IMD delta stays at -fee, and unlock reverts CurrencyNotSettled. The trade is valid and fully funded; only the hook's choice of take over mint causes the revert. The try/catch fallback does not help because take succeeds (the manager holds IMD). This contradicts the brief ('The hook never reverts a real swap') and the README ('payout failure cannot veto a valid, funded v4 trade'). No funds are lost (the whole tx reverts), so severity is a broken guarantee for any router or integrator that syncs IMD before the swap, not theft. Fix that keeps direct delivery otherwise: in _pay, if TransientStateLibrary.getSyncedCurrency(poolManager) == IMD, mint the ERC-6909 claim instead of take (verified locally: the same test passes with that one-line change), or always mint and let redeemFees deliver.","line":167,"path":"src/TestHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {TestHook} from \"src/TestHook.sol\";\nimport {TestToken} from \"src/TestToken.sol\";\n\ncontract PlainIMD is ERC20 {\n    constructor() ERC20(\"IMD\", \"IMD\") {}\n}\n\n/// Pays its input first: sync(IMD) -> transfer -> swap -> settle(), a valid v4 settlement order.\ncontract PrepayRouter is IUnlockCallback {\n    IPoolManager immutable pm;\n    constructor(IPoolManager pm_) { pm = pm_; }\n\n    function run(PoolKey memory key, SwapParams memory p, address imd, uint256 pay) external {\n        pm.unlock(abi.encode(key, p, imd, pay));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        (PoolKey memory key, SwapParams memory p, address imd, uint256 pay) =\n            abi.decode(data, (PoolKey, SwapParams, address, uint256));\n        if (pay != 0) {\n            pm.sync(Currency.wrap(imd));\n            IERC20(imd).transfer(address(pm), pay);\n        }\n        BalanceDelta d = pm.swap(key, p, \"\");\n        if (pay != 0) pm.settle();\n        // collect the positive side, settle any negative side normally\n        _close(key.currency0, d.amount0());\n        _close(key.currency1, d.amount1());\n        return \"\";\n    }\n\n    function _close(Currency c, int128 v) internal {\n        if (v > 0) pm.take(c, address(this), uint256(int256(v)));\n    }\n}\n\ncontract LPRouter is IUnlockCallback {\n    IPoolManager immutable pm;\n    constructor(IPoolManager pm_) { pm = pm_; }\n    function add(PoolKey memory key, ModifyLiquidityParams memory p) external {\n        pm.unlock(abi.encode(key, p));\n    }\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        (PoolKey memory key, ModifyLiquidityParams memory p) = abi.decode(data, (PoolKey, ModifyLiquidityParams));\n        (BalanceDelta d,) = pm.modifyLiquidity(key, p, \"\");\n        _pay(key.currency0, d.amount0());\n        _pay(key.currency1, d.amount1());\n        return \"\";\n    }\n    function _pay(Currency c, int128 v) internal {\n        if (v < 0) {\n            pm.sync(c);\n            IERC20(Currency.unwrap(c)).transfer(address(pm), uint256(-int256(v)));\n            pm.settle();\n        }\n    }\n}\n\ncontract SyncFirstRouterTest is Test {\n    address constant IMD = 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127;\n    IPoolManager pm;\n    TestHook hook;\n    TestToken token;\n    PoolKey key;\n    bool imd0;\n\n    function setUp() public {\n        vm.etch(IMD, address(new PlainIMD()).code);\n        pm = IPoolManager(address(new PoolManager(address(this))));\n        token = new TestToken();\n        imd0 = IMD < address(token);\n        bytes32 h = keccak256(abi.encodePacked(type(TestHook).creationCode, abi.encode(pm, address(token))));\n        for (uint256 s;; ++s) {\n            address a = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(s), h)))));\n            if (uint160(a) & 0x3fff == 0x20cc) { hook = new TestHook{salt: bytes32(s)}(pm, address(token)); break; }\n        }\n        key = PoolKey(Currency.wrap(imd0 ? IMD : address(token)), Currency.wrap(imd0 ? address(token) : IMD), 12_500, 60, IHooks(address(hook)));\n        pm.initialize(key, TickMath.getSqrtPriceAtTick(0));\n        LPRouter lp = new LPRouter(pm);\n        deal(IMD, address(lp), 1_000_000 ether);\n        token.transfer(address(lp), 1_000_000 ether);\n        lp.add(key, ModifyLiquidityParams(-600, 600, 1_000_000 ether, bytes32(0)));\n    }\n\n    /// A fully funded exact-input buy of 1000 IMD, paid before the swap, must succeed and the\n    /// manager must not be left short. Today the hook's in-swap take() moves 7.5 IMD out of the\n    /// manager between sync() and settle(), so settle() credits only 992.5 and unlock reverts.\n    function test_prepaidBuySucceeds() public {\n        PrepayRouter r = new PrepayRouter(pm);\n        deal(IMD, address(r), 1000 ether);\n        bool zeroForOne = imd0;\n        SwapParams memory p = SwapParams(zeroForOne, -1000 ether, zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1);\n        r.run(key, p, IMD, 1000 ether);\n        assertEq(IERC20(IMD).balanceOf(address(r)), 0);\n    }\n}","reproduction":"Fresh PoolManager, IMD token at 0x5F7B..7127, TestHook mined at flags 0x20cc, pool (IMD, SIMDTEST, 12500, 60, hook) initialized at tick 0 with 1e24 liquidity in [-600,600]. Router unlockCallback: pm.sync(IMD); IMD.transfer(pm, 1000e18); pm.swap(key, {zeroForOne: imdIsCurrency0, amountSpecified: -1000e18, limit: MIN/MAX+-1}, ''); pm.settle(); pm.take(SIMDTEST, router, out). Expected: the swap completes, trader pays 1000 IMD, 7.5 IMD goes to 0xdFA1..8BC0. Actual: during beforeSwap the hook take()s 7.5 IMD from the manager, settle() returns 992.5e18 (trace: `PoolManager::settle() ... Return 992500000000000000000`), and unlock reverts CurrencyNotSettled(). `forge test --match-path test/scratch/SyncFirstRouter.t.sol` fails with [FAIL: CurrencyNotSettled()]; with _pay minting a claim when the synced currency is IMD it passes.","severity":"medium","snippet":"        try poolManager.take(imd, FEE_RECIPIENT, fee) {\n            emit FeeCharged(fee, false);","title":"In-swap take() of IMD between a router's sync(IMD) and settle() under-credits the payer, so a funded buy reverts (breaks 'the hook never reverts a real swap')"}],"hash":"df4e6731854dbdc1c145bcefcb7534535284137f2a3f2581d5d6ce7b7f7b7de4","nodeId":"6ce458c2-df41-4139-8989-74b76ef62184","outcome":"completed","summary":"I found one medium-severity defect in `TestHook` and wrote it to `.imd-findings.json`, with a coverage row for all 8 entry points and three invariant rows. The existing offline suite passes: 38 passed, 3 skipped. The skips are the fork suites, which need an RPC, so I did not run anything against Robinhood Chain.\n\n**Finding 1 (medium): a funded buy reverts if the router syncs IMD before the swap** (`src/TestHook.sol:167`)\n- **What goes wrong:** the hook pays its fee with `poolManager.take(IMD, FEE_RECIPIENT, fee)` during the swap, which moves IMD out of the PoolManager right away. Uniswap v4 measures what a router paid as the manager's balance at `settle()` minus its balance at `sync()`. So a router that runs `sync(IMD)` → transfer → `swap` → `settle()`, a valid v4 order, is credited the payment minus the fee and the whole transaction reverts with `CurrencyNotSettled`.\n- **Reproduction:** in a scratch Foundry test, a prepaid exact-input buy of 1000 IMD had `settle()` credit 992.5 IMD, and the swap failed.\n- **Why it matters:** no funds are lost, because the whole transaction reverts. It breaks the brief's promise that \"the hook never reverts a real swap\" and the README's \"payout failure cannot veto a valid, funded v4 trade\". The fallback to an IMD claim doesn't kick in, because `take` succeeds. Exact-output buys go through the same `take` and are affected the same way.\n- **Fix:** in `_pay`, if the manager's synced currency is IMD, mint the claim instead of calling `take`. Delivery stays direct in every other case. A patched copy of the hook with that one change passed the same test.\n\nThe failing test is included as the finding's proof. I deleted `test/scratch/` afterwards, so the working tree is unchanged.\n\n**What holds**\n- **Fee math:** the four swap modes, partial fills against a price limit, and the 1-wei cap on exact-input buys all charge 0.75% of the filled IMD, rounded down. Slither's divide-before-multiply warning on `_fraction` is a false positive; the formula is an exact floor.\n- **The reverted quote** (`quoteSwap`): only the hook can call it. It always reverts, so pool state and the manager's transient accounting roll back. v4 skips the hook for the hook's own swap, so there is no recursion.\n- **`redeemFees` and `unlockCallback`:** they always pay the fixed wallet. A nested call can't open a second unlock. A failed transfer leaves the claim intact.\n- **Fixed rate and recipient:** they are constants, with no input from time, blocks, the caller or `hookData`. The hook never transfers or approves its claims, so nothing can redirect the fee.\n- **`TestToken`:** unmodified OpenZeppelin ERC20 with a fixed supply.\n\n**Covered and not reported**\n- **Huge amounts:** an exact-output sell within `fee` of `type(int256).max` overflows, but the README already documents this limit and standard routers can't reach it.\n- **\"2% total\":** sequential fees give 1.990625%. The README explains this, and the hook charges exactly the 0.75% the brief asks for.\n- **Fork block mismatch:** the fork test pins block 107,806,973, while the README records a run at 107,794,459.\n\n**Not reached:** the launch factory, the claim and distributor contracts, and real routers on Robinhood Chain are outside this repository.","treeHash":null,"usage":{"cachedInputTokens":1011932,"cost":{"source":"runtime","usdMicros":1583991},"inputTokens":23,"model":"claude-opus-5-5","outputTokens":29048,"runtime":"claude","turns":14,"wallClockMs":306121}},{"artifacts":[],"attempt":2,"bundleHash":"e6b95f5be938255916f2cc834402c5c869fe0d365e010ba3b294bf0e078bb058","device":"39da99ded7f125c8","findings":[{"description":"The brief says the 0.75% hook fee together with the factory's 1.25% LP fee makes a trade cost 2% in total. The hook takes 0.75% of gross IMD and the pool takes 1.25% of what is left (buy) or of the SIMDTEST input (sell), so the two fees compound on different bases: 1 - 0.9925 * 0.9875 = 1.990625%. The README states this; the hook does not adjust its own rate to reach exactly 2%. Not a fund-loss defect, recorded so the requester can confirm the 0.75%-of-gross interpretation.","line":108,"path":"src/TestHook.sol","reproduction":"Fork test test_fork_exactAmountsOnRoundTrades: an exact-input buy of 1000 IMD pays exactly 7.5 IMD to 0xdFA1f1F6FEAA04F7874efF0b7754b7FFaEBd8BC0 and 992.5 IMD enters the pool, which charges 1.25% of 992.5 = 12.40625 IMD LP fee. Total 19.90625 IMD (1.990625%), expected 20 IMD if the brief's 2% is read literally.","severity":"info","title":"Hook fee plus LP fee totals 1.990625%, not exactly 2%"},{"description":"The fee is floor(gross * 75 / 10000), so a gross IMD amount up to 133 wei gives a zero fee. This is documented rounding and can't be used for profit (each split trade still pays the LP fee and gas), but the 0.75% rate isn't exact for dust amounts.","line":202,"path":"src/TestHook.sol","reproduction":"test_tinyAmountsRoundDownWithoutBlocking: 1-wei buys and sells in all four modes succeed with recipient payment 0 (expected 0.0075 wei, rounded down).","severity":"info","title":"Trades under 134 wei of gross IMD pay no hook fee"}],"hash":"e668cbd376193973265ab8ad094bf21b0618ef7e465963be6f0b3b86e0d0f740","nodeId":"3ea54a4e-549f-4d02-9322-5b8a7970ef7a","outcome":"completed","summary":"I made no changes. All the tests, fork tests included, pass at the pinned block. The local check failed on a single RPC request that forge sends by itself, and nothing in `test/` controls it.\n\n**What I ran:** my shell can't reach the RPC socket, so I passed requests through a small relay in my temp folder to the same chain 4663 endpoint, then ran the full suite at block 107806973 with the verifier's fuzz seed:\n- **Result:** 7 suites, 91 tests passed, 0 failed. That includes the 26 + 26 scenario tests and the invariant test in `test/RobinhoodFork.t.sol`.\n- **Requests:** the relay logged 162 chain-reading requests, the same number the check reported. Only one failed, and it is the one the check named: `eth_getAccountInfo`. The endpoint answers \"not available on the ROBINHOOD_MAINNET\".\n\n**Why that request is there:** forge 1.8.3 loads account data under its default \"Auto\" policy. It sends `eth_getAccountInfo` alongside the separate balance, nonce and code requests and uses whichever works. On this endpoint the probe fails once and forge falls back to the separate requests, which all succeeded. So the failure doesn't mean the endpoint lacks archive state: every storage, code and balance read at the pinned block came back fine. The policy is set in forge's configuration, not in test files, and `foundry.toml` is off-limits. No test change can stop the request, and editing tests wouldn't fix anything.\n\nThis is a failure on the endpoint and tooling side, not something to ask the requester. The endpoint's answer was: \"eth_getAccountInfo is not available on the ROBINHOOD_MAINNET\". The tests can go to the verifier as they are. I found no new defect in the hook, so I did not write `.imd-findings.json`.\n\nSources:\n- [Foundry `AccountFetchPolicy` docs](https://foundry-rs.github.io/foundry/foundry_evm_core/backend/enum.AccountFetchPolicy.html)\n- [Alchemy `eth_getAccountInfo` docs](https://www.alchemy.com/docs/chains/ethereum/ethereum-api-endpoints/eth-get-account-info.md)","treeHash":"c18e8a4ed30942a219db27803fce5c5265280060","usage":{"cachedInputTokens":3684000,"chain":[{"calls":2754,"chainId":4663,"failed":258,"own":2460,"public":0,"refused":36}],"cost":{"source":"runtime","usdMicros":3149348},"inputTokens":88,"model":"claude-opus-5-5","outputTokens":47534,"runtime":"claude","turns":59,"wallClockMs":797044}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"5dd3af20ad7ac0c2","findings":[{"citation":"resolved","description":"Carried over from the previous round (id 0a98aeeb...), merged with audit_math's info finding (id 1192de15...). The author answered 'not reproducible' describing a tree with a single contract RobinhoodForkTest that forks from --fork-url, deploys a fresh PoolManager and has no launch.json. That is not this tree. Here test/RobinhoodFork.t.sol defines RobinhoodForkIMDFirstTest (line 270), RobinhoodForkIMDSecondTest (line 276) and RobinhoodForkInvariantTest (line 349); it forks only from the ROBINHOOD_RPC_URL environment variable and skips when unset (lines 31-35, `vm.envOr(\"ROBINHOOD_RPC_URL\", ...)`, `vm.skip(true)`); it pins `FORK_BLOCK = 107_806_973` (line 24) and uses the live manager `LIVE_MANAGER = 0x8366a39CC670B4001A1121B8F6A443A643e40951` (line 28). README.md:179 filters on RobinhoodForkTest, which matches none of the three; README.md:180-181 pass --fork-url/--fork-block-number 107846725, which the suite ignores; README.md:191 says the fork deploys a fresh canonical PoolManager; README.md:55-56 say no launch.json is provided while launch.json is in the tree; README.md:186 reports 21 fork tests while each scenario contract alone carries 33; README.md:167 reports 48 offline tests while 52 pass. Separately, the public RPC the README names keeps only recent state: both the suite's pinned block 107806973 and the README's 107846725 now answer 'historical state is not available' (chain head 107859740 at review time), so nobody can rerun the committed fork suite against that endpoint without editing the pinned constant. Documentation and reproducibility only; the hook and token code are unaffected. A scratch copy of the committed fork suite with only FORK_BLOCK changed to 107_859_700 passed all 67 tests against live IMD and the live PoolManager with the fixed hook, including the new prepaid-buy and exact-output-sell-bound tests, so the fork evidence the brief asks for exists but is not reachable through the documented command or the committed block.","line":179,"path":"README.md","reproduction":"1. `forge test --match-contract RobinhoodForkTest` on this tree: expected the fork suite (skipped offline), actual 'No tests found in project!'. 2. The README's full command with --fork-url https://rpc.mainnet.chain.robinhood.com --fork-block-number 107846725: actual 'Error: server returned an error response: error code -32000: historical state 00d299c9... is not available' (and even at a served block the filter matches nothing and the suite would skip because ROBINHOOD_RPC_URL is unset). 3. `cast call 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127 'balanceOf(address)(uint256)' 0x8366a39CC670B4001A1121B8F6A443A643e40951 --block 107806973 --rpc-url https://rpc.mainnet.chain.robinhood.com`: 'historical state 49afcbd7... is not available', so the suite's own pinned block cannot be forked either; the same call without --block returns 50874792745464056348917. 4. Compare README.md:191 ('fresh canonical v4 PoolManager') with test/RobinhoodFork.t.sol:28 (LIVE_MANAGER) and README.md:55-56 ('No `launch.json` is provided') with `ls launch.json`. 5. Control: copy test/RobinhoodFork.t.sol to test/scratch/ with imports re-based and FORK_BLOCK = 107_859_700, then `ROBINHOOD_RPC_URL=https://rpc.mainnet.chain.robinhood.com forge test --match-path test/scratch/Fork.t.sol`: 67 passed, 0 failed.","severity":"info","snippet":"  forge test --match-contract RobinhoodForkTest \\","title":"README fork instructions describe a suite that is not in this tree: the documented command runs no tests, forks from the wrong source and block, and the manager/manifest statements are stale"}],"hash":"f31e50dffd3ba237bb038533f8c99bcb2f6c76fa0184e8d2b33dcfc5832bac4b","nodeId":"62928621-f733-4644-a8ea-91088b0c0848","outcome":"completed","summary":"Review complete. The findings file holds one info finding and a ten-row coverage record; the working tree is otherwise unchanged.\n\n**Settled from last round**\n\n- **Medium, fee taken inside the buyer's IMD sync window: fixed.** The hook now checks the manager's synced currency in its payout routine and mints the ERC-6909 claim instead of calling take when that currency is IMD. My attached proof and the flow specialist's proof both pass on this tree. The author's new prepaid regression tests pass in both token orderings. I confirmed in the vendored PoolManager that settle resets the synced currency, so the branch can never be stale, and that a sync placed outside an unlock can only under-credit its own caller.\n- **Low, exact-output sell near the int256 ceiling: fixed.** Orders above the new public bound revert with a named error before the quote, the fee is never clamped, and the test covers the ceiling, the bound itself and the int128 sentinel.\n- **Info, README fork documentation: still reproduces, kept at info.** The author's \"not reproducible\" answer describes a tree with one fork contract, a fresh local manager and no manifest. This tree has three fork contracts, the live manager, an environment-variable fork source, and a launch.json. The documented command finds no tests, and the public RPC no longer serves either the suite's pinned block or the README's block.\n\n**Specialist findings this round**\n\n- The two prepaid-buy reports from audit_math and audit_flow are the fixed medium and were dropped.\n- The economics specialist's exact-output overflow report is the fixed low and was dropped.\n- The audit_math README report was merged into the info finding above.\n- The economics specialist's 1.990625% versus 2% note was dropped. The hook charges exactly the 0.75% of gross IMD the brief specifies, and the README and manifest notes already disclose that the two fees compose sequentially.\n- Slither's divide-before-multiply lead is an exact floor identity and the reentrancy-event leads are benign. Neither is a finding.\n\n**Fresh verification**\n\nA scratch copy of the committed fork suite, re-pinned to block 107859700, passed all 67 tests against live IMD and the live PoolManager with the fixed hook. The manifest's constructor arguments, permissions and pool parameters match the implementation. No new defect reproduced, so the launch has no outstanding blocking finding from me.","treeHash":null,"usage":{"cachedInputTokens":881019,"cost":{"source":"runtime","usdMicros":3380146},"inputTokens":354,"model":"claude-fable-5-1","outputTokens":22555,"runtime":"claude","turns":21,"wallClockMs":381340}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"7eaf1cd0fb3df712","findings":[{"citation":"resolved","description":"For a specified-IMD exact-output sell (SIMDTEST in, IMD out, amountSpecified = N > 0), beforeSwap caps only the quote's gross-up to fit in int256 (lines 103-104). It then returns the fee computed from the actual fill as a positive specified delta (line 112), and v4-core Hooks.beforeSwap does a checked `amountToSwap += hookDeltaSpecified` (lib/v4-core/src/libraries/Hooks.sol:296). For any N > type(int256).max - fee, that addition overflows (panic 0x11), so the whole swap reverts. Fee is floor(0.75% x the price-limited gross IMD fill), which is nonzero for any fill of 134 wei or more. Without the hook, v4 accepts this 'swap until the price limit' order (exact output of int256.max bounded by sqrtPriceLimitX96) and fills it partially. So the brief's 'the hook never reverts a real swap' is broken for routers or bots that use the int256.max sentinel on sells. The saturation on line 104 shows the author meant to support huge exact-output requests but left the outer swap unprotected. REVIEW.md mentions signed limits in general but not this reachable, fundable case. Impact is limited: an IMD fill is at most int128 in size, so N = type(int128).max gives the same result without reverting. That limits it to a denial of one order shape and is why this is low. Fix note: simply clamping the fee to room (int256.max - N) would make this order shape fee-free, letting any seller dodge the 0.75%. A hook cannot shrink the outer specified amount while still adding a fee to it, so the realistic fix is to keep charging the fee and document the limit: exact-output sells must use amountSpecified <= type(int256).max - type(int128).max. Alternatively, revert early with a clear custom error instead of a bare panic. Either way the brief's 'never reverts a real swap' should be qualified.","line":103,"path":"src/TestHook.sol","reproduction":"Local vendored v4 PoolManager, IMD (mock ERC20 etched at 0x5F7B...7127) as currency0, TestToken as currency1. Pool fee 12500, spacing 60, initialized at tick 0, liquidity 1e24 in [-600, 600]. A router calls swap(zeroForOne=false (SIMDTEST in, IMD out), amountSpecified=type(int256).max, sqrtPriceLimitX96=getSqrtPriceAtTick(120)). Without a hook (control) it succeeds: about 6.09e21 IMD out for 5.98e21 SIMDTEST in. With TestHook at an address ending in 0x20cc, the quote fills 6.09e21 IMD and the hook computes fee 44863033203822469491 and pays it. v4 then reverts with panic 0x11 at Hooks.beforeSwap amountToSwap += hookDeltaSpecified, so the whole swap reverts. Expected: the swap fills to the price limit and the hook takes 0.75% IMD. Actual: the swap reverts. I ran this as test/scratch/ExactOutputSellRevert.t.sol: test_controlWithoutHookSucceeds passes, the hooked case fails with 'panic: arithmetic underflow or overflow (0x11)'.","severity":"low","snippet":"            uint256 room = uint256(type(int256).max) - requested;\n            quoted.amountSpecified += int256(maximumFee < room ? maximumFee : room);","title":"Exact-output sell sized to a price limit (amountSpecified near type(int256).max) always reverts: the hook's fee overflows v4's amountToSwap"},{"citation":"resolved","description":"The hook takes 0.75% of gross IMD, and the pool's 1.25% LP fee applies to the rest: on buys, 1.25% of the 99.25% that enters the pool. On sells, the LP fee is taken from the SIMDTEST input and then 0.75% of the IMD output. The total value lost is 1 - 0.9925 x 0.9875 = 1.990625%, not the 2% the brief states. The README and launch.json notes disclose this ('2% nominal'), so it is a documented spec deviation, not a hidden defect. The requester should confirm that this composition is acceptable, or the hook rate needs to become about 75.95 bps on the post-LP base to hit exactly 2%.","line":108,"path":"src/TestHook.sol","reproduction":"Exact-input buy of 10,000e18 IMD at zero price impact. The hook gets floor(10000e18 x 75 / 10000) = 75e18 IMD. The pool receives 9,925e18 and its LP fee is 1.25% of that = 124.0625e18. The total is 199.0625e18 IMD = 1.990625% of the trade. Expected per the brief: 2%, i.e. 200e18. Shortfall: 0.9375e18 IMD per 10,000 IMD.","severity":"info","snippet":"        uint256 fee = buy ? _fraction(imdAmount, BPS - FEE_BPS) : _fraction(imdAmount, BPS);","title":"Combined charge is 1.990625%, not the brief's '2% in total', because the 1.25% LP fee is assessed on the amount left after the hook fee"}],"hash":"ffee105daecc0ad07f46e6ff7abfc905e9b8ca2a1f61be6888b212c418d7ba6c","nodeId":"1a89cf4b-4e9d-4e48-a0b0-8c62de9ecbe9","outcome":"completed","summary":"I found one low-severity defect and one informational note in the economics. Nothing high or critical turned up: the fee rate, recipient and IMD denomination hold. The findings and a coverage row for all 8 listed entry points (plus 4 invariant rows) are in `.imd-findings.json`. No repository files were changed, and the scratch test I wrote for the reproduction has been deleted.\n\n**Findings**\n\n1. **Low – a sell sized \"up to a price limit\" always reverts** (`src/TestHook.sol:103`). In a normal v4 order of this kind, the trader asks for an exact IMD output of `type(int256).max` and lets `sqrtPriceLimitX96` decide the actual fill. The hook keeps its own quote within range (lines 103–104), but the 0.75% fee it adds to that amount overflows the checked `amountToSwap += hookDeltaSpecified` in v4 (`Hooks.sol:296`). The whole swap then fails with panic 0x11.\n   - **Reproduction:** I ran the same swap on a local PoolManager with 1e24 liquidity, a tick-120 price limit, IMD out and SIMDTEST in. Without the hook it fills about 6.09e21 IMD. With TestHook it reverts after charging a fee of 44863033203822469491.\n   - **Why it matters:** this breaks the brief's \"the hook never reverts a real swap\".\n   - **Why only low:** a fill can never exceed int128, so asking for `type(int128).max` instead gives the same trade without reverting.\n   - **Fix:** capping the fee so the addition fits would let anyone sell with no hook fee. The safer fix is to keep charging and either document the limit or revert with a clear error instead of a bare panic.\n\n2. **Info – the combined charge is 1.990625%, not 2%.** The 1.25% pool fee applies to what is left after the hook's 0.75%. For example, a 10,000 IMD buy pays 75 to the hook and 124.0625 to the pool. The README and the launch notes already disclose this as \"2% nominal\", so it is for you to confirm rather than fix.\n\n**What holds**\n- **Fee on every swap:** all four swap modes charge exactly 0.75% of the gross IMD, including when a price limit or thin liquidity stops the swap part-way.\n- **Rounding:** fees round down, and trades under 134 wei pay no fee.\n- **Fixed rate and recipient:** both are constants. Nothing based on time, block, caller or extra swap data feeds into the fee, and no setter, owner or delegatecall exists.\n- **Accounting:** every fee the hook returns is matched by an equal payment or claim, so its balance with the pool manager nets to zero.\n- **No fee-free path:** the internal quote function always reverts and only the hook itself can call it, so no trader can use it to skip the fee.\n- **Deferred fees:** if paying the wallet fails, the fee is held as an IMD claim. Anyone can trigger redemption, but it can only pay `0xdFA1…8BC0`.\n- **Manifest and token:** `launch.json` matches the constructor and the hook permissions, and TestToken is a plain OpenZeppelin ERC-20.\n\nThe project's offline suite passes (38 tests). Its 3 fork tests skip because no Robinhood RPC is set here, so I did not run anything against the live chain.\n\n**Not reached:** the launch factory and its claim/remainder contract are not in this repository.","treeHash":null,"usage":{"cachedInputTokens":556900,"cost":{"source":"runtime","usdMicros":1189548},"inputTokens":20,"model":"claude-opus-5-5","outputTokens":27384,"runtime":"claude","turns":10,"wallClockMs":284477}}],"verification":[{"checks":[{"durationMs":4986,"exitCode":0,"name":"build","output":"Compiling 80 files with Solc 0.8.26\nSolc 0.8.26 finished in 4.85s\nCompiler run successful!\n","passed":true},{"durationMs":1153,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodForkIMDFirstTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 3.08ms (0.00ns CPU time)\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodForkInvariantTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 3.34ms (0.00ns CPU time)\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodForkIMDSecondTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 4.37ms (0.00ns CPU time)\n\nRan 3 tests for test/TestToken.t.sol:TestTokenTest\n[PASS] testFuzz_transferAndAllowanceConserveSupply(uint96) (runs: 1000, μ: 217714, ~: 218532)\nLogs:\n  Bound result 9233\n\n[PASS] test_failuresDoNotMoveOrFreezeTokens() (gas: 314156)\n[PASS] test_launchSupplyMetadataAndAllocation() (gas: 259143)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 110.58ms (107.88ms CPU time)\n\nRan 18 tests for test/TestHook.t.sol:HookIMDFirstTest\n[PASS] testFuzz_feeAndSettlementAllModes(bool,bool,uint96) (runs: 1000, μ: 371551, ~: 350486)\nLogs:\n  Bound result 2955\n\n[PASS] testFuzz_partialFillChargesOnlyFilledIMD(bool,bool,uint96) (runs: 1000, μ: 377777, ~: 351470)\nLogs:\n  Bound result 90000000000000000000702\n\n[PASS] test_buyExactInputPaysIMD() (gas: 411165)\n[PASS] test_buyExactOutputPaysIMD() (gas: 345820)\n[PASS] test_buyWithOnlyLaunchTokenLiquidityDefersUntilInputSettled() (gas: 72618795)\n[PASS] test_callbacksAndQuoteRefuseUnauthorizedCallers() (gas: 153497)\n[PASS] test_cannotInitializeOtherPools() (gas: 117965)\n[PASS] test_coreFailuresRollbackFeeAndPrice() (gas: 442350)\n[PASS] test_deployedCodeHasNoUpgradeOrDestructionOpcodes() (gas: 4152199)\n[PASS] test_exhaustedLiquidityChargesNoPhantomFee() (gas: 1033699)\n[PASS] test_noAdminOrRedirectionEvenForRecipientOrDeployer() (gas: 1061274)\n[PASS] test_permissionsMatchMinedAddressAndPool() (gas: 30965)\n[PASS] test_rateUnaffectedByTimeBlocksCallerAndHookData() (gas: 921526)\n[PASS] test_recipientFailureDefersInIMDAndAnyoneCanRedeem() (gas: 684146)\n[PASS] test_sellExactInputPaysIMD() (gas: 339909)\n[PASS] test_sellExactOutputReceivesTheRequestedNetIMD() (gas: 397781)\n[PASS] test_tinyAmountsRoundDownWithoutBlocking() (gas: 1235380)\n[PASS] test_zeroLiquidityCollectAndExitRemainAvailable() (gas: 958233)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 115.30ms (290.61ms CPU time)\n\nRan 16 tests for test/TestHook.t.sol:HookIMDSecondTest\n[PASS] testFuzz_feeAndSettlementAllModes(bool,bool,uint96) (runs: 1000, μ: 371491, ~: 350593)\nLogs:\n  Bound result 639443528323005322795\n\n[PASS] testFuzz_partialFillChargesOnlyFilledIMD(bool,bool,uint96) (runs: 1000, μ: 377983, ~: 351751)\nLogs:\n  Bound result 90000000000000000004625\n\n[PASS] test_buyExactInputPaysIMD() (gas: 398026)\n[PASS] test_buyExactOutputPaysIMD() (gas: 340592)\n[PASS] test_callbacksAndQuoteRefuseUnauthorizedCallers() (gas: 154036)\n[PASS] test_cannotInitializeOtherPools() (gas: 117965)\n[PASS] test_coreFailuresRollbackFeeAndPrice() (gas: 446436)\n[PASS] test_deployedCodeHasNoUpgradeOrDestructionOpcodes() (gas: 4152199)\n[PASS] test_exhaustedLiquidityChargesNoPhantomFee() (gas: 1033549)\n[PASS] test_noAdminOrRedirectionEvenForRecipientOrDeployer() (gas: 1048003)\n[PASS] test_permissionsMatchMinedAddressAndPool() (gas: 30965)\n[PASS] test_rateUnaffectedByTimeBlocksCallerAndHookData() (gas: 902026)\n[PASS] test_sellExactInputPaysIMD() (gas: 346304)\n[PASS] test_sellExactOutputReceivesTheRequestedNetIMD() (gas: 408268)\n[PASS] test_tinyAmountsRoundDownWithoutBlocking() (gas: 1228142)\n[PASS] test_zeroLiquidityCollectAndExitRemainAvailable() (gas: 951862)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 115.29ms (202.52ms CPU time)\n\nRan 1 test for test/HookInvariant.t.sol:HookInvariantTest\n[PASS] invariant_feeConservationAndNoCustodiedLaunchTokens() (runs: 128, calls: 4096, reverts: 0)\n\n╭------------+--------------+-------+---------+----------╮\n| Contract   | Selector     | Calls | Reverts | Discards |\n+========================================================+\n| FeeHandler | redeem       | 1381  | 0       | 0        |\n|------------+--------------+-------+---------+----------|\n| FeeHandler | rejectPayout | 1371  | 0       | 0        |\n|------------+--------------+-------+---------+----------|\n| FeeHandler | swap         | 1344  | 0       | 0        |\n╰------------+--------------+-------+---------+----------╯\n\nLogs:\n  Bound result 4010\n  Bound result 1117\n  Bound result 1802\n  Bound result 15721\n  Bound result 3303551951\n  Bound result 1250565486419895638\n  Bound result 2379\n  Bound result 73000\n  Bound result 8586\n  Bound result 8374\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.02s (1.01s CPU time)\n\nRan 7 test suites in 1.03s (1.38s CPU time): 38 tests passed, 0 failed, 3 skipped (41 total tests)\n","passed":true},{"durationMs":66,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"TestHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"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\":4,\"LICENSE\":23,\"README.md\":204,\"REVIEW.md\":42,\"dependencies.lock.json\":30,\"foundry.toml\":21,\"launch.json\":30,\"remappings.txt\":4,\"src/TestHook.sol\":209,\"src/TestToken.sol\":12,\"test/HookInvariant.t.sol\":108,\"test/RobinhoodFork.t.sol\":386,\"test/TestHook.t.sol\":278,\"test/TestToken.t.sol\":79,\"test/helpers/HookFixture.sol\":161,\"test/helpers/MockIMD.sol\":22,\"test/helpers/PoolDriver.sol\":59},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"818198ef7f773086ebaf7d7194be4e096a0c9a18f27fce48d5ff51ce24acfa4f","verifiedTreeHash":"b711ac4dd830a15251d5d203870a47a47d5429a6","verifierVersion":"0.1.0+18543036"},{"checks":[{"durationMs":4925,"exitCode":0,"name":"build","output":"Compiling 80 files with Solc 0.8.26\nSolc 0.8.26 finished in 4.84s\nCompiler run successful!\n","passed":true},{"durationMs":1093,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodForkInvariantTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 480.90µs (0.00ns CPU time)\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodForkIMDSecondTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 550.18µs (0.00ns CPU time)\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodForkIMDFirstTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 588.76µs (0.00ns CPU time)\n\nRan 3 tests for test/TestToken.t.sol:TestTokenTest\n[PASS] testFuzz_transferAndAllowanceConserveSupply(uint96) (runs: 1000, μ: 217714, ~: 218532)\nLogs:\n  Bound result 1817311\n\n[PASS] test_failuresDoNotMoveOrFreezeTokens() (gas: 314156)\n[PASS] test_launchSupplyMetadataAndAllocation() (gas: 259143)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 128.73ms (129.16ms CPU time)\n\nRan 23 tests for test/TestHook.t.sol:HookIMDSecondTest\n[PASS] testFuzz_feeAndSettlementAllModes(bool,bool,uint96) (runs: 1000, μ: 390089, ~: 369140)\nLogs:\n  Bound result 15337\n\n[PASS] testFuzz_partialFillChargesOnlyFilledIMD(bool,bool,uint96) (runs: 1000, μ: 396488, ~: 370254)\nLogs:\n  Bound result 90000000000000000010841\n\n[PASS] test_buyExactInputPaysIMD() (gas: 416515)\n[PASS] test_buyExactOutputPaysIMD() (gas: 359105)\n[PASS] test_buySettledBeforeSwapPaysTheWalletDirectly() (gas: 383187)\n[PASS] test_callbacksAndQuoteRefuseUnauthorizedCallers() (gas: 154012)\n[PASS] test_cannotInitializeOtherPools() (gas: 118075)\n[PASS] test_coreFailuresRollbackFeeAndPrice() (gas: 447667)\n[PASS] test_deployedCodeHasNoUpgradeOrDestructionOpcodes() (gas: 4301960)\n[PASS] test_dustFeeBoundaryIsExactlyOneHundredThirtyFourWeiOfGrossIMD() (gas: 1509877)\n[PASS] test_exactOutputSellBoundIsNamedAndEveryOrderBelowItFills() (gas: 930326)\n[PASS] test_exhaustedLiquidityChargesNoPhantomFee() (gas: 1079990)\n[PASS] test_hookAndPoolFeesOnABuyCompoundToTheDocumentedTotal() (gas: 506850)\n[PASS] test_noAdminOrRedirectionEvenForRecipientOrDeployer() (gas: 1066736)\n[PASS] test_permissionsMatchMinedAddressAndPool() (gas: 30998)\n[PASS] test_prepaidExactInputBuyPaysOnlyItsBudget() (gas: 513520)\n[PASS] test_prepaidExactOutputBuyCostsTheSameAsSettlingAfter() (gas: 698386)\n[PASS] test_prepaidSellsStillPayTheWalletDirectly() (gas: 740423)\n[PASS] test_rateUnaffectedByTimeBlocksCallerAndHookData() (gas: 934027)\n[PASS] test_sellExactInputPaysIMD() (gas: 364927)\n[PASS] test_sellExactOutputReceivesTheRequestedNetIMD() (gas: 426846)\n[PASS] test_tinyAmountsRoundDownWithoutBlocking() (gas: 1299023)\n[PASS] test_zeroLiquidityCollectAndExitRemainAvailable() (gas: 988910)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 145.04ms (233.28ms CPU time)\n\nRan 25 tests for test/TestHook.t.sol:HookIMDFirstTest\n[PASS] testFuzz_feeAndSettlementAllModes(bool,bool,uint96) (runs: 1000, μ: 390422, ~: 369231)\nLogs:\n  Bound result 6278\n\n[PASS] testFuzz_partialFillChargesOnlyFilledIMD(bool,bool,uint96) (runs: 1000, μ: 396404, ~: 370094)\nLogs:\n  Bound result 90000000000000000016324\n\n[PASS] test_buyExactInputPaysIMD() (gas: 429775)\n[PASS] test_buyExactOutputPaysIMD() (gas: 364454)\n[PASS] test_buySettledBeforeSwapPaysTheWalletDirectly() (gas: 396417)\n[PASS] test_buyWithOnlyLaunchTokenLiquidityDefersUntilInputSettled() (gas: 12405476)\n[PASS] test_callbacksAndQuoteRefuseUnauthorizedCallers() (gas: 153473)\n[PASS] test_cannotInitializeOtherPools() (gas: 118075)\n[PASS] test_coreFailuresRollbackFeeAndPrice() (gas: 443502)\n[PASS] test_deployedCodeHasNoUpgradeOrDestructionOpcodes() (gas: 4301960)\n[PASS] test_dustFeeBoundaryIsExactlyOneHundredThirtyFourWeiOfGrossIMD() (gas: 1518145)\n[PASS] test_exactOutputSellBoundIsNamedAndEveryOrderBelowItFills() (gas: 906578)\n[PASS] test_exhaustedLiquidityChargesNoPhantomFee() (gas: 1080690)\n[PASS] test_hookAndPoolFeesOnABuyCompoundToTheDocumentedTotal() (gas: 520030)\n[PASS] test_noAdminOrRedirectionEvenForRecipientOrDeployer() (gas: 1080128)\n[PASS] test_permissionsMatchMinedAddressAndPool() (gas: 31020)\n[PASS] test_prepaidExactInputBuyPaysOnlyItsBudget() (gas: 526740)\n[PASS] test_prepaidExactOutputBuyCostsTheSameAsSettlingAfter() (gas: 709000)\n[PASS] test_prepaidSellsStillPayTheWalletDirectly() (gas: 735493)\n[PASS] test_rateUnaffectedByTimeBlocksCallerAndHookData() (gas: 953802)\n[PASS] test_recipientFailureDefersInIMDAndAnyoneCanRedeem() (gas: 705400)\n[PASS] test_sellExactInputPaysIMD() (gas: 358543)\n[PASS] test_sellExactOutputReceivesTheRequestedNetIMD() (gas: 416502)\n[PASS] test_tinyAmountsRoundDownWithoutBlocking() (gas: 1306745)\n[PASS] test_zeroLiquidityCollectAndExitRemainAvailable() (gas: 995567)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 145.09ms (291.51ms CPU time)\n\nRan 1 test for test/HookInvariant.t.sol:HookInvariantTest\n[PASS] invariant_feeConservationAndNoCustodiedLaunchTokens() (runs: 128, calls: 4096, reverts: 0)\n\n╭------------+--------------+-------+---------+----------╮\n| Contract   | Selector     | Calls | Reverts | Discards |\n+========================================================+\n| FeeHandler | redeem       | 1383  | 0       | 0        |\n|------------+--------------+-------+---------+----------|\n| FeeHandler | rejectPayout | 1343  | 0       | 0        |\n|------------+--------------+-------+---------+----------|\n| FeeHandler | swap         | 1370  | 0       | 0        |\n╰------------+--------------+-------+---------+----------╯\n\nLogs:\n  Bound result 2110\n  Bound result 5346\n  Bound result 2124\n  Bound result 984\n  Bound result 8515\n  Bound result 14417\n  Bound result 96757423472219440795\n  Bound result 14129\n  Bound result 19456\n  Bound result 4099\n  Bound result 2789\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 974.35ms (954.03ms CPU time)\n\nRan 7 test suites in 978.09ms (1.39s CPU time): 52 tests passed, 0 failed, 3 skipped (55 total tests)\n","passed":true},{"durationMs":56,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"TestHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"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\":4,\"LICENSE\":23,\"README.md\":231,\"REVIEW.md\":45,\"dependencies.lock.json\":30,\"foundry.toml\":21,\"launch.json\":30,\"remappings.txt\":4,\"src/TestHook.sol\":226,\"src/TestToken.sol\":12,\"test/HookInvariant.t.sol\":117,\"test/RobinhoodFork.t.sol\":386,\"test/TestHook.t.sol\":397,\"test/TestToken.t.sol\":79,\"test/helpers/HookFixture.sol\":186,\"test/helpers/MockIMD.sol\":22,\"test/helpers/PoolDriver.sol\":100},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"a5a4f6ee03563f9d599329de7ab9f38ffdddfb5d6c6309faff494cd6204f63be","verifiedTreeHash":"86a29b00f65c0c4a7b9d5b0b2a1f23dea10bd18c","verifierVersion":"0.1.0+18543036"},{"checks":[{"durationMs":4080,"exitCode":0,"name":"build","output":"Compiling 80 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.96s\nCompiler run successful!\n","passed":true},{"durationMs":1298,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 2.72ms (0.00ns CPU time)\n\nRan 21 tests for test/TestHook.t.sol:HookIMDSecondTest\n[PASS] testFuzz_feeAndSettlementAllModes(bool,bool,uint96) (runs: 1000, μ: 390187, ~: 369140)\nLogs:\n  Bound result 3808\n\n[PASS] testFuzz_partialFillChargesOnlyFilledIMD(bool,bool,uint96) (runs: 1000, μ: 396512, ~: 370276)\nLogs:\n  Bound result 90000000000000000016354\n\n[PASS] test_buyExactInputPaysIMD() (gas: 416515)\n[PASS] test_buyExactOutputPaysIMD() (gas: 359127)\n[PASS] test_buySettledBeforeSwapPaysTheWalletDirectly() (gas: 383187)\n[PASS] test_callbacksAndQuoteRefuseUnauthorizedCallers() (gas: 154034)\n[PASS] test_cannotInitializeOtherPools() (gas: 118075)\n[PASS] test_coreFailuresRollbackFeeAndPrice() (gas: 447667)\n[PASS] test_deployedCodeHasNoUpgradeOrDestructionOpcodes() (gas: 4301982)\n[PASS] test_exactOutputSellBoundIsNamedAndEveryOrderBelowItFills() (gas: 930326)\n[PASS] test_exhaustedLiquidityChargesNoPhantomFee() (gas: 1079990)\n[PASS] test_noAdminOrRedirectionEvenForRecipientOrDeployer() (gas: 1066736)\n[PASS] test_permissionsMatchMinedAddressAndPool() (gas: 31020)\n[PASS] test_prepaidExactInputBuyPaysOnlyItsBudget() (gas: 513432)\n[PASS] test_prepaidExactOutputBuyCostsTheSameAsSettlingAfter() (gas: 698386)\n[PASS] test_prepaidSellsStillPayTheWalletDirectly() (gas: 740423)\n[PASS] test_rateUnaffectedByTimeBlocksCallerAndHookData() (gas: 934027)\n[PASS] test_sellExactInputPaysIMD() (gas: 364839)\n[PASS] test_sellExactOutputReceivesTheRequestedNetIMD() (gas: 426846)\n[PASS] test_tinyAmountsRoundDownWithoutBlocking() (gas: 1299045)\n[PASS] test_zeroLiquidityCollectAndExitRemainAvailable() (gas: 988932)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 138.19ms (278.07ms CPU time)\n\nRan 3 tests for test/TestToken.t.sol:TestTokenTest\n[PASS] testFuzz_transferAndAllowanceConserveSupply(uint96) (runs: 1000, μ: 217714, ~: 218532)\nLogs:\n  Bound result 7841\n\n[PASS] test_failuresDoNotMoveOrFreezeTokens() (gas: 314156)\n[PASS] test_launchSupplyMetadataAndAllocation() (gas: 259143)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 138.82ms (140.65ms CPU time)\n\nRan 23 tests for test/TestHook.t.sol:HookIMDFirstTest\n[PASS] testFuzz_feeAndSettlementAllModes(bool,bool,uint96) (runs: 1000, μ: 390527, ~: 369231)\nLogs:\n  Bound result 920\n\n[PASS] testFuzz_partialFillChargesOnlyFilledIMD(bool,bool,uint96) (runs: 1000, μ: 396426, ~: 370116)\nLogs:\n  Bound result 90000000000000000028169\n\n[PASS] test_buyExactInputPaysIMD() (gas: 429775)\n[PASS] test_buyExactOutputPaysIMD() (gas: 364454)\n[PASS] test_buySettledBeforeSwapPaysTheWalletDirectly() (gas: 396417)\n[PASS] test_buyWithOnlyLaunchTokenLiquidityDefersUntilInputSettled() (gas: 12405476)\n[PASS] test_callbacksAndQuoteRefuseUnauthorizedCallers() (gas: 153495)\n[PASS] test_cannotInitializeOtherPools() (gas: 118075)\n[PASS] test_coreFailuresRollbackFeeAndPrice() (gas: 443502)\n[PASS] test_deployedCodeHasNoUpgradeOrDestructionOpcodes() (gas: 4301982)\n[PASS] test_exactOutputSellBoundIsNamedAndEveryOrderBelowItFills() (gas: 906578)\n[PASS] test_exhaustedLiquidityChargesNoPhantomFee() (gas: 1080690)\n[PASS] test_noAdminOrRedirectionEvenForRecipientOrDeployer() (gas: 1080128)\n[PASS] test_permissionsMatchMinedAddressAndPool() (gas: 31020)\n[PASS] test_prepaidExactInputBuyPaysOnlyItsBudget() (gas: 526740)\n[PASS] test_prepaidExactOutputBuyCostsTheSameAsSettlingAfter() (gas: 709000)\n[PASS] test_prepaidSellsStillPayTheWalletDirectly() (gas: 735493)\n[PASS] test_rateUnaffectedByTimeBlocksCallerAndHookData() (gas: 953802)\n[PASS] test_recipientFailureDefersInIMDAndAnyoneCanRedeem() (gas: 705400)\n[PASS] test_sellExactInputPaysIMD() (gas: 358543)\n[PASS] test_sellExactOutputReceivesTheRequestedNetIMD() (gas: 416502)\n[PASS] test_tinyAmountsRoundDownWithoutBlocking() (gas: 1306745)\n[PASS] test_zeroLiquidityCollectAndExitRemainAvailable() (gas: 995567)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 139.48ms (281.18ms CPU time)\n\nRan 1 test for test/HookInvariant.t.sol:HookInvariantTest\n[PASS] invariant_feeConservationAndNoCustodiedLaunchTokens() (runs: 128, calls: 4096, reverts: 0)\n\n╭------------+--------------+-------+---------+----------╮\n| Contract   | Selector     | Calls | Reverts | Discards |\n+========================================================+\n| FeeHandler | redeem       | 1383  | 0       | 0        |\n|------------+--------------+-------+---------+----------|\n| FeeHandler | rejectPayout | 1343  | 0       | 0        |\n|------------+--------------+-------+---------+----------|\n| FeeHandler | swap         | 1370  | 0       | 0        |\n╰------------+--------------+-------+---------+----------╯\n\nLogs:\n  Bound result 2444\n  Bound result 5515\n  Bound result 2124\n  Bound result 17743\n  Bound result 8535\n  Bound result 14414\n  Bound result 96757423472219440795\n  Bound result 14215\n  Bound result 3570988005\n  Bound result 4131\n  Bound result 2937\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.14s (1.12s CPU time)\n\nRan 5 test suites in 1.14s (1.56s CPU time): 48 tests passed, 0 failed, 1 skipped (49 total tests)\n","passed":true},{"durationMs":59,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"TestHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"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\":4,\"LICENSE\":23,\"README.md\":231,\"REVIEW.md\":45,\"dependencies.lock.json\":30,\"foundry.toml\":21,\"remappings.txt\":4,\"src/TestHook.sol\":226,\"src/TestToken.sol\":12,\"test/HookInvariant.t.sol\":117,\"test/RobinhoodFork.t.sol\":21,\"test/TestHook.t.sol\":354,\"test/TestToken.t.sol\":79,\"test/helpers/HookFixture.sol\":179,\"test/helpers/MockIMD.sol\":22,\"test/helpers/PoolDriver.sol\":100},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1542,"exitCode":0,"name":"slither","output":"[medium/medium] divide-before-multiply at src/TestHook.sol:219: TestHook._fraction(uint256,uint256) (src/TestHook.sol#219-221) performs a multiplication on the result of a division:\n[low/medium] reentrancy-events at src/TestHook.sol:171: Reentrancy in TestHook._pay(uint256) (src/TestHook.sol#171-190):\n[low/medium] reentrancy-events at src/TestHook.sol:171: Reentrancy in TestHook._pay(uint256) (src/TestHook.sol#171-190):\n[low/medium] reentrancy-events at src/TestHook.sol:171: Reentrancy in TestHook._pay(uint256) (src/TestHook.sol#171-190):\n[low/medium] reentrancy-events at src/TestHook.sol:92: Reentrancy in TestHook.beforeSwap(address,PoolKey,SwapParams,bytes) (src/TestHook.sol#92-122):\n[low/medium] reentrancy-events at src/TestHook.sol:156: Reentrancy in TestHook.unlockCallback(bytes) (src/TestHook.sol#156-169):","passed":true},{"durationMs":665,"exitCode":0,"name":"aderyn","output":"[low] large-numeric-literal at src/TestHook.sol:22: Large Numeric Literal (2 places)","passed":true},{"durationMs":3033,"exitCode":0,"name":"proof 5f8c1ab89d90","output":"Compiling 77 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.10s\nCompiler run successful!\n\nRan 3 tests for test/imd-proof-2f00bd6a/Proof_5f8c1ab89d90.t.sol:PrepaidBuyTest\n[PASS] test_prepaidExactInputBuyFillsForItsBudget() (gas: 294565)\n[PASS] test_prepaidExactInputBuyIsNotChargedTheFeeTwice() (gas: 281963)\n[PASS] test_prepaidExactOutputBuyCostsTheSameAsSettlingAfter() (gas: 465340)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 21.03ms (1.93ms CPU time)\n\nRan 1 test suite in 21.59ms (21.03ms CPU time): 3 tests passed, 0 failed, 0 skipped (3 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"b89a8f532827f673c5af4c3b213eee4d0cd61b6dd28226356328aba348cb3c81","verifiedTreeHash":"01284ee6dda7fbe8a0e053915af4de7096f50551","verifierVersion":"0.1.0+18543036"},{"checks":[{"durationMs":3653,"exitCode":0,"name":"build","output":"Compiling 80 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.54s\nCompiler run successful!\n","passed":true},{"durationMs":1186,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 865.37µs (0.00ns CPU time)\n\nRan 16 tests for test/TestHook.t.sol:HookIMDSecondTest\n[PASS] testFuzz_feeAndSettlementAllModes(bool,bool,uint96) (runs: 1000, μ: 371222, ~: 350593)\nLogs:\n  Bound result 3711\n\n[PASS] testFuzz_partialFillChargesOnlyFilledIMD(bool,bool,uint96) (runs: 1000, μ: 377983, ~: 351751)\nLogs:\n  Bound result 90000000000000000016244\n\n[PASS] test_buyExactInputPaysIMD() (gas: 398026)\n[PASS] test_buyExactOutputPaysIMD() (gas: 340592)\n[PASS] test_callbacksAndQuoteRefuseUnauthorizedCallers() (gas: 154036)\n[PASS] test_cannotInitializeOtherPools() (gas: 117965)\n[PASS] test_coreFailuresRollbackFeeAndPrice() (gas: 446436)\n[PASS] test_deployedCodeHasNoUpgradeOrDestructionOpcodes() (gas: 4152199)\n[PASS] test_exhaustedLiquidityChargesNoPhantomFee() (gas: 1033549)\n[PASS] test_noAdminOrRedirectionEvenForRecipientOrDeployer() (gas: 1048003)\n[PASS] test_permissionsMatchMinedAddressAndPool() (gas: 30965)\n[PASS] test_rateUnaffectedByTimeBlocksCallerAndHookData() (gas: 902026)\n[PASS] test_sellExactInputPaysIMD() (gas: 346304)\n[PASS] test_sellExactOutputReceivesTheRequestedNetIMD() (gas: 408268)\n[PASS] test_tinyAmountsRoundDownWithoutBlocking() (gas: 1228142)\n[PASS] test_zeroLiquidityCollectAndExitRemainAvailable() (gas: 951862)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 130.33ms (224.39ms CPU time)\n\nRan 3 tests for test/TestToken.t.sol:TestTokenTest\n[PASS] testFuzz_transferAndAllowanceConserveSupply(uint96) (runs: 1000, μ: 217714, ~: 218532)\nLogs:\n  Bound result 7841\n\n[PASS] test_failuresDoNotMoveOrFreezeTokens() (gas: 314156)\n[PASS] test_launchSupplyMetadataAndAllocation() (gas: 259143)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 138.35ms (140.65ms CPU time)\n\nRan 18 tests for test/TestHook.t.sol:HookIMDFirstTest\n[PASS] testFuzz_feeAndSettlementAllModes(bool,bool,uint96) (runs: 1000, μ: 371689, ~: 350486)\nLogs:\n  Bound result 21178\n\n[PASS] testFuzz_partialFillChargesOnlyFilledIMD(bool,bool,uint96) (runs: 1000, μ: 377778, ~: 351470)\nLogs:\n  Bound result 90000000000002090626703\n\n[PASS] test_buyExactInputPaysIMD() (gas: 411165)\n[PASS] test_buyExactOutputPaysIMD() (gas: 345820)\n[PASS] test_buyWithOnlyLaunchTokenLiquidityDefersUntilInputSettled() (gas: 72618795)\n[PASS] test_callbacksAndQuoteRefuseUnauthorizedCallers() (gas: 153497)\n[PASS] test_cannotInitializeOtherPools() (gas: 117965)\n[PASS] test_coreFailuresRollbackFeeAndPrice() (gas: 442350)\n[PASS] test_deployedCodeHasNoUpgradeOrDestructionOpcodes() (gas: 4152199)\n[PASS] test_exhaustedLiquidityChargesNoPhantomFee() (gas: 1033699)\n[PASS] test_noAdminOrRedirectionEvenForRecipientOrDeployer() (gas: 1061274)\n[PASS] test_permissionsMatchMinedAddressAndPool() (gas: 30965)\n[PASS] test_rateUnaffectedByTimeBlocksCallerAndHookData() (gas: 921526)\n[PASS] test_recipientFailureDefersInIMDAndAnyoneCanRedeem() (gas: 684146)\n[PASS] test_sellExactInputPaysIMD() (gas: 339909)\n[PASS] test_sellExactOutputReceivesTheRequestedNetIMD() (gas: 397781)\n[PASS] test_tinyAmountsRoundDownWithoutBlocking() (gas: 1235380)\n[PASS] test_zeroLiquidityCollectAndExitRemainAvailable() (gas: 958233)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 138.50ms (395.57ms CPU time)\n\nRan 1 test for test/HookInvariant.t.sol:HookInvariantTest\n[PASS] invariant_feeConservationAndNoCustodiedLaunchTokens() (runs: 128, calls: 4096, reverts: 0)\n\n╭------------+--------------+-------+---------+----------╮\n| Contract   | Selector     | Calls | Reverts | Discards |\n+========================================================+\n| FeeHandler | redeem       | 1381  | 0       | 0        |\n|------------+--------------+-------+---------+----------|\n| FeeHandler | rejectPayout | 1371  | 0       | 0        |\n|------------+--------------+-------+---------+----------|\n| FeeHandler | swap         | 1344  | 0       | 0        |\n╰------------+--------------+-------+---------+----------╯\n\nLogs:\n  Bound result 4010\n  Bound result 1117\n  Bound result 1802\n  Bound result 15662\n  Bound result 3303551951\n  Bound result 1250565486419895638\n  Bound result 2162\n  Bound result 73000\n  Bound result 8586\n  Bound result 8374\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.04s (1.04s CPU time)\n\nRan 5 test suites in 1.04s (1.45s CPU time): 38 tests passed, 0 failed, 1 skipped (39 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.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\":4,\"LICENSE\":23,\"README.md\":204,\"REVIEW.md\":42,\"dependencies.lock.json\":30,\"foundry.toml\":21,\"remappings.txt\":4,\"src/TestHook.sol\":209,\"src/TestToken.sol\":12,\"test/HookInvariant.t.sol\":108,\"test/RobinhoodFork.t.sol\":21,\"test/TestHook.t.sol\":278,\"test/TestToken.t.sol\":79,\"test/helpers/HookFixture.sol\":154,\"test/helpers/MockIMD.sol\":22,\"test/helpers/PoolDriver.sol\":59},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1414,"exitCode":0,"name":"slither","output":"[medium/medium] divide-before-multiply at src/TestHook.sol:202: TestHook._fraction(uint256,uint256) (src/TestHook.sol#202-204) performs a multiplication on the result of a division:\n[low/medium] reentrancy-events at src/TestHook.sol:86: Reentrancy in TestHook.beforeSwap(address,PoolKey,SwapParams,bytes) (src/TestHook.sol#86-113):\n[low/medium] reentrancy-events at src/TestHook.sol:162: Reentrancy in TestHook._pay(uint256) (src/TestHook.sol#162-173):\n[low/medium] reentrancy-events at src/TestHook.sol:147: Reentrancy in TestHook.unlockCallback(bytes) (src/TestHook.sol#147-160):\n[low/medium] reentrancy-events at src/TestHook.sol:162: Reentrancy in TestHook._pay(uint256) (src/TestHook.sol#162-173):","passed":true},{"durationMs":451,"exitCode":0,"name":"aderyn","output":"[low] large-numeric-literal at src/TestHook.sol:21: Large Numeric Literal (2 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"d0075102e4390b187d2aa58ce79924b8df39bb454bd89417f6dbf59cf597989d","verifiedTreeHash":"0db436d84186ce3ec0181c184d3748d8a8fac208","verifierVersion":"0.1.0+18543036"},{"checks":[{"durationMs":4993,"exitCode":0,"name":"build","output":"Compiling 80 files with Solc 0.8.26\nSolc 0.8.26 finished in 4.89s\nCompiler run successful!\n","passed":true},{"durationMs":2218,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/TestToken.t.sol:TestTokenTest\n[PASS] testFuzz_transferAndAllowanceConserveSupply(uint96) (runs: 1000, μ: 217714, ~: 218532)\nLogs:\n  Bound result 1439028288591614461976852\n\n[PASS] test_failuresDoNotMoveOrFreezeTokens() (gas: 314156)\n[PASS] test_launchSupplyMetadataAndAllocation() (gas: 259143)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 123.52ms (124.03ms CPU time)\n\nRan 23 tests for test/TestHook.t.sol:HookIMDSecondTest\n[PASS] testFuzz_feeAndSettlementAllModes(bool,bool,uint96) (runs: 1000, μ: 390089, ~: 369140)\nLogs:\n  Bound result 28168\n\n[PASS] testFuzz_partialFillChargesOnlyFilledIMD(bool,bool,uint96) (runs: 1000, μ: 396488, ~: 370254)\nLogs:\n  Bound result 90000000000000000003937\n\n[PASS] test_buyExactInputPaysIMD() (gas: 416515)\n[PASS] test_buyExactOutputPaysIMD() (gas: 359105)\n[PASS] test_buySettledBeforeSwapPaysTheWalletDirectly() (gas: 383187)\n[PASS] test_callbacksAndQuoteRefuseUnauthorizedCallers() (gas: 154012)\n[PASS] test_cannotInitializeOtherPools() (gas: 118075)\n[PASS] test_coreFailuresRollbackFeeAndPrice() (gas: 447667)\n[PASS] test_deployedCodeHasNoUpgradeOrDestructionOpcodes() (gas: 4301960)\n[PASS] test_dustFeeBoundaryIsExactlyOneHundredThirtyFourWeiOfGrossIMD() (gas: 1509877)\n[PASS] test_exactOutputSellBoundIsNamedAndEveryOrderBelowItFills() (gas: 930326)\n[PASS] test_exhaustedLiquidityChargesNoPhantomFee() (gas: 1079990)\n[PASS] test_hookAndPoolFeesOnABuyCompoundToTheDocumentedTotal() (gas: 506850)\n[PASS] test_noAdminOrRedirectionEvenForRecipientOrDeployer() (gas: 1066736)\n[PASS] test_permissionsMatchMinedAddressAndPool() (gas: 30998)\n[PASS] test_prepaidExactInputBuyPaysOnlyItsBudget() (gas: 513520)\n[PASS] test_prepaidExactOutputBuyCostsTheSameAsSettlingAfter() (gas: 698386)\n[PASS] test_prepaidSellsStillPayTheWalletDirectly() (gas: 740423)\n[PASS] test_rateUnaffectedByTimeBlocksCallerAndHookData() (gas: 934027)\n[PASS] test_sellExactInputPaysIMD() (gas: 364927)\n[PASS] test_sellExactOutputReceivesTheRequestedNetIMD() (gas: 426846)\n[PASS] test_tinyAmountsRoundDownWithoutBlocking() (gas: 1299023)\n[PASS] test_zeroLiquidityCollectAndExitRemainAvailable() (gas: 988910)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 133.48ms (271.28ms CPU time)\n\nRan 25 tests for test/TestHook.t.sol:HookIMDFirstTest\n[PASS] testFuzz_feeAndSettlementAllModes(bool,bool,uint96) (runs: 1000, μ: 390422, ~: 369231)\nLogs:\n  Bound result 19731077\n\n[PASS] testFuzz_partialFillChargesOnlyFilledIMD(bool,bool,uint96) (runs: 1000, μ: 396404, ~: 370094)\nLogs:\n  Bound result 90000000000000019731078\n\n[PASS] test_buyExactInputPaysIMD() (gas: 429775)\n[PASS] test_buyExactOutputPaysIMD() (gas: 364454)\n[PASS] test_buySettledBeforeSwapPaysTheWalletDirectly() (gas: 396417)\n[PASS] test_buyWithOnlyLaunchTokenLiquidityDefersUntilInputSettled() (gas: 12405476)\n[PASS] test_callbacksAndQuoteRefuseUnauthorizedCallers() (gas: 153473)\n[PASS] test_cannotInitializeOtherPools() (gas: 118075)\n[PASS] test_coreFailuresRollbackFeeAndPrice() (gas: 443502)\n[PASS] test_deployedCodeHasNoUpgradeOrDestructionOpcodes() (gas: 4301960)\n[PASS] test_dustFeeBoundaryIsExactlyOneHundredThirtyFourWeiOfGrossIMD() (gas: 1518145)\n[PASS] test_exactOutputSellBoundIsNamedAndEveryOrderBelowItFills() (gas: 906578)\n[PASS] test_exhaustedLiquidityChargesNoPhantomFee() (gas: 1080690)\n[PASS] test_hookAndPoolFeesOnABuyCompoundToTheDocumentedTotal() (gas: 520030)\n[PASS] test_noAdminOrRedirectionEvenForRecipientOrDeployer() (gas: 1080128)\n[PASS] test_permissionsMatchMinedAddressAndPool() (gas: 31020)\n[PASS] test_prepaidExactInputBuyPaysOnlyItsBudget() (gas: 526740)\n[PASS] test_prepaidExactOutputBuyCostsTheSameAsSettlingAfter() (gas: 709000)\n[PASS] test_prepaidSellsStillPayTheWalletDirectly() (gas: 735493)\n[PASS] test_rateUnaffectedByTimeBlocksCallerAndHookData() (gas: 953802)\n[PASS] test_recipientFailureDefersInIMDAndAnyoneCanRedeem() (gas: 705400)\n[PASS] test_sellExactInputPaysIMD() (gas: 358543)\n[PASS] test_sellExactOutputReceivesTheRequestedNetIMD() (gas: 416502)\n[PASS] test_tinyAmountsRoundDownWithoutBlocking() (gas: 1306745)\n[PASS] test_zeroLiquidityCollectAndExitRemainAvailable() (gas: 995567)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 138.67ms (214.45ms CPU time)\n\nRan 1 test for test/HookInvariant.t.sol:HookInvariantTest\n[PASS] invariant_feeConservationAndNoCustodiedLaunchTokens() (runs: 128, calls: 4096, reverts: 0)\n\n╭------------+--------------+-------+---------+----------╮\n| Contract   | Selector     | Calls | Reverts | Discards |\n+========================================================+\n| FeeHandler | redeem       | 1383  | 0       | 0        |\n|------------+--------------+-------+---------+----------|\n| FeeHandler | rejectPayout | 1343  | 0       | 0        |\n|------------+--------------+-------+---------+----------|\n| FeeHandler | swap         | 1370  | 0       | 0        |\n╰------------+--------------+-------+---------+----------╯\n\nLogs:\n  Bound result 2110\n  Bound result 5346\n  Bound result 2124\n  Bound result 984\n  Bound result 8515\n  Bound result 14417\n  Bound result 96757423472219440795\n  Bound result 14129\n  Bound result 19456\n  Bound result 4099\n  Bound result 2789\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 990.27ms (965.13ms CPU time)\n\nRan 33 tests for test/RobinhoodFork.t.sol:RobinhoodForkIMDSecondTest\n[PASS] testFuzz_feeAndSettlementAllModes(bool,bool,uint96) (runs: 1000, μ: 380474, ~: 359147)\nLogs:\n  Bound result 13089\n\n[PASS] testFuzz_fork_rateIsConstantAcrossSizeTimeAndBlocks(bool,bool,uint96,uint32,uint32,bytes) (runs: 1000, μ: 388364, ~: 367750)\nLogs:\n  Bound result 59938919875783901795\n\n[PASS] testFuzz_partialFillChargesOnlyFilledIMD(bool,bool,uint96) (runs: 1000, μ: 385851, ~: 360399)\nLogs:\n  Bound result 90000000000000019731078\n\n[PASS] test_buyExactInputPaysIMD() (gas: 405334)\n[PASS] test_buyExactOutputPaysIMD() (gas: 350083)\n[PASS] test_buySettledBeforeSwapPaysTheWalletDirectly() (gas: 371828)\n[PASS] test_callbacksAndQuoteRefuseUnauthorizedCallers() (gas: 153972)\n[PASS] test_cannotInitializeOtherPools() (gas: 116534)\n[PASS] test_coreFailuresRollbackFeeAndPrice() (gas: 435086)\n[PASS] test_deployedCodeHasNoUpgradeOrDestructionOpcodes() (gas: 4302071)\n[PASS] test_dustFeeBoundaryIsExactlyOneHundredThirtyFourWeiOfGrossIMD() (gas: 1509091)\n[PASS] test_exactOutputSellBoundIsNamedAndEveryOrderBelowItFills() (gas: 903627)\n[PASS] test_exhaustedLiquidityChargesNoPhantomFee() (gas: 1078684)\n[PASS] test_fork_blockedPayoutDefersAsIMDClaimAndOnlyPaysTheWallet() (gas: 564990)\n[PASS] test_fork_buysAndSellsPaySeventyFiveBpsOfIMDToTheWallet() (gas: 1556650)\n[PASS] test_fork_exactAmountsOnRoundTrades() (gas: 756355)\n[PASS] test_fork_firstAndLaterTradesUseTheSameRate() (gas: 2648728)\n[PASS] test_fork_invalidSwapsRevertAndMoveNoFee() (gas: 470067)\n[PASS] test_fork_managerOwnerAndProtocolFeeControllerCannotRedirectHookFee() (gas: 859560)\n[PASS] test_fork_privilegedOnChainAccountsCannotCallHookCallbacks() (gas: 495743)\n[PASS] test_fork_redeemInsideAnotherUnlockReverts() (gas: 299560)\n[PASS] test_fork_routingThroughAnotherDriverStillPays() (gas: 1818704)\n[PASS] test_hookAndPoolFeesOnABuyCompoundToTheDocumentedTotal() (gas: 501033)\n[PASS] test_noAdminOrRedirectionEvenForRecipientOrDeployer() (gas: 1055800)\n[PASS] test_permissionsMatchMinedAddressAndPool() (gas: 30976)\n[PASS] test_prepaidExactInputBuyPaysOnlyItsBudget() (gas: 505341)\n[PASS] test_prepaidExactOutputBuyCostsTheSameAsSettlingAfter() (gas: 694893)\n[PASS] test_prepaidSellsStillPayTheWalletDirectly() (gas: 735328)\n[PASS] test_rateUnaffectedByTimeBlocksCallerAndHookData() (gas: 933844)\n[PASS] test_sellExactInputPaysIMD() (gas: 354772)\n[PASS] test_sellExactOutputReceivesTheRequestedNetIMD() (gas: 413412)\n[PASS] test_tinyAmountsRoundDownWithoutBlocking() (gas: 1312552)\n[PASS] test_zeroLiquidityCollectAndExitRemainAvailable() (gas: 992383)\nSuite result: ok. 33 passed; 0 failed; 0 skipped; finished in 1.26s (3.08s CPU time)\n\nRan 33 tests for test/RobinhoodFork.t.sol:RobinhoodForkIMDFirstTest\n[PASS] testFuzz_feeAndSettlementAllModes(bool,bool,uint96) (runs: 1000, μ: 380621, ~: 359035)\nLogs:\n  Bound result 710214397\n\n[PASS] testFuzz_fork_rateIsConstantAcrossSizeTimeAndBlocks(bool,bool,uint96,uint32,uint32,bytes) (runs: 1000, μ: 388877, ~: 378609)\nLogs:\n  Bound result 605526791756\n\n[PASS] testFuzz_partialFillChargesOnlyFilledIMD(bool,bool,uint96) (runs: 1000, μ: 385600, ~: 360053)\nLogs:\n  Bound result 90000000000000019731078\n\n[PASS] test_buyExactInputPaysIMD() (gas: 416435)\n[PASS] test_buyExactOutputPaysIMD() (gas: 354206)\n[PASS] test_buySettledBeforeSwapPaysTheWalletDirectly() (gas: 382953)\n[PASS] test_callbacksAndQuoteRefuseUnauthorizedCallers() (gas: 153433)\n[PASS] test_cannotInitializeOtherPools() (gas: 116534)\n[PASS] test_coreFailuresRollbackFeeAndPrice() (gas: 429247)\n[PASS] test_deployedCodeHasNoUpgradeOrDestructionOpcodes() (gas: 4302071)\n[PASS] test_dustFeeBoundaryIsExactlyOneHundredThirtyFourWeiOfGrossIMD() (gas: 1516147)\n[PASS] test_exactOutputSellBoundIsNamedAndEveryOrderBelowItFills() (gas: 883645)\n[PASS] test_exhaustedLiquidityChargesNoPhantomFee() (gas: 1078655)\n[PASS] test_fork_blockedPayoutDefersAsIMDClaimAndOnlyPaysTheWallet() (gas: 559463)\n[PASS] test_fork_buysAndSellsPaySeventyFiveBpsOfIMDToTheWallet() (gas: 1558117)\n[PASS] test_fork_exactAmountsOnRoundTrades() (gas: 758173)\n[PASS] test_fork_firstAndLaterTradesUseTheSameRate() (gas: 2662601)\n[PASS] test_fork_invalidSwapsRevertAndMoveNoFee() (gas: 464230)\n[PASS] test_fork_managerOwnerAndProtocolFeeControllerCannotRedirectHookFee() (gas: 861522)\n[PASS] test_fork_privilegedOnChainAccountsCannotCallHookCallbacks() (gas: 493584)\n[PASS] test_fork_redeemInsideAnotherUnlockReverts() (gas: 299560)\n[PASS] test_fork_routingThroughAnotherDriverStillPays() (gas: 1829833)\n[PASS] test_hookAndPoolFeesOnABuyCompoundToTheDocumentedTotal() (gas: 512068)\n[PASS] test_noAdminOrRedirectionEvenForRecipientOrDeployer() (gas: 1066913)\n[PASS] test_permissionsMatchMinedAddressAndPool() (gas: 30976)\n[PASS] test_prepaidExactInputBuyPaysOnlyItsBudget() (gas: 516386)\n[PASS] test_prepaidExactOutputBuyCostsTheSameAsSettlingAfter() (gas: 703111)\n[PASS] test_prepaidSellsStillPayTheWalletDirectly() (gas: 731060)\n[PASS] test_rateUnaffectedByTimeBlocksCallerAndHookData() (gas: 950388)\n[PASS] test_sellExactInputPaysIMD() (gas: 349205)\n[PASS] test_sellExactOutputReceivesTheRequestedNetIMD() (gas: 404775)\n[PASS] test_tinyAmountsRoundDownWithoutBlocking() (gas: 1319032)\n[PASS] test_zeroLiquidityCollectAndExitRemainAvailable() (gas: 997646)\nSuite result: ok. 33 passed; 0 failed; 0 skipped; finished in 1.56s (2.99s CPU time)\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodForkInvariantTest\n[PASS] invariant_fork_everyFeeReachesTheWalletOrItsClaim() (runs: 64, calls: 2048, reverts: 0)\n\n╭----------------+----------+-------+---------+----------╮\n| Contract       | Selector | Calls | Reverts | Discards |\n+========================================================+\n| ForkFeeHandler | redeem   | 1028  | 0       | 0        |\n|----------------+----------+-------+---------+----------|\n| ForkFeeHandler | swap     | 1020  | 0       | 0        |\n╰----------------+----------+-------+---------+----------╯\n\nLogs:\n  Bound result 5351629176294879141\n  Bound result 2392748185\n  Bound result 5\n  Bound result 1712184845\n  Bound result 1935\n  Bound result 14118\n  Bound result 15673\n  Bound result 982\n  Bound result 1\n  Bound result 16166\n  Bound result 5211\n  Bound result 621967927\n  Bound result 9606\n  Bound result 210\n  Bound result 1463377\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.09s (770.12ms CPU time)\n\nRan 7 test suites in 2.09s (6.29s CPU time): 119 tests passed, 0 failed, 0 skipped (119 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.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\":4,\"LICENSE\":23,\"README.md\":231,\"REVIEW.md\":45,\"dependencies.lock.json\":30,\"foundry.toml\":21,\"remappings.txt\":4,\"src/TestHook.sol\":226,\"src/TestToken.sol\":12,\"test/HookInvariant.t.sol\":117,\"test/RobinhoodFork.t.sol\":386,\"test/TestHook.t.sol\":397,\"test/TestToken.t.sol\":79,\"test/helpers/HookFixture.sol\":186,\"test/helpers/MockIMD.sol\":22,\"test/helpers/PoolDriver.sol\":100},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"d67bb08a131b9d62e477b1c2937c60ad465f612f64394e2f0fc50d0c4de2ab69","verifiedTreeHash":"277b9e39942009d1924a1d6a73d0f6755300a3ba","verifierVersion":"0.1.0+18543036"},{"checks":[{"durationMs":5568,"exitCode":0,"name":"build","output":"Compiling 80 files with Solc 0.8.26\nSolc 0.8.26 finished in 5.41s\nCompiler run successful!\n","passed":true},{"durationMs":2598,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/TestToken.t.sol:TestTokenTest\n[PASS] testFuzz_transferAndAllowanceConserveSupply(uint96) (runs: 1000, μ: 217714, ~: 218532)\nLogs:\n  Bound result 52885\n\n[PASS] test_failuresDoNotMoveOrFreezeTokens() (gas: 314156)\n[PASS] test_launchSupplyMetadataAndAllocation() (gas: 259143)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 136.15ms (136.80ms CPU time)\n\nRan 16 tests for test/TestHook.t.sol:HookIMDSecondTest\n[PASS] testFuzz_feeAndSettlementAllModes(bool,bool,uint96) (runs: 1000, μ: 371491, ~: 350593)\nLogs:\n  Bound result 639443528323005322795\n\n[PASS] testFuzz_partialFillChargesOnlyFilledIMD(bool,bool,uint96) (runs: 1000, μ: 377983, ~: 351751)\nLogs:\n  Bound result 90000000000000000005087\n\n[PASS] test_buyExactInputPaysIMD() (gas: 398026)\n[PASS] test_buyExactOutputPaysIMD() (gas: 340592)\n[PASS] test_callbacksAndQuoteRefuseUnauthorizedCallers() (gas: 154036)\n[PASS] test_cannotInitializeOtherPools() (gas: 117965)\n[PASS] test_coreFailuresRollbackFeeAndPrice() (gas: 446436)\n[PASS] test_deployedCodeHasNoUpgradeOrDestructionOpcodes() (gas: 4152199)\n[PASS] test_exhaustedLiquidityChargesNoPhantomFee() (gas: 1033549)\n[PASS] test_noAdminOrRedirectionEvenForRecipientOrDeployer() (gas: 1048003)\n[PASS] test_permissionsMatchMinedAddressAndPool() (gas: 30965)\n[PASS] test_rateUnaffectedByTimeBlocksCallerAndHookData() (gas: 902026)\n[PASS] test_sellExactInputPaysIMD() (gas: 346304)\n[PASS] test_sellExactOutputReceivesTheRequestedNetIMD() (gas: 408268)\n[PASS] test_tinyAmountsRoundDownWithoutBlocking() (gas: 1228142)\n[PASS] test_zeroLiquidityCollectAndExitRemainAvailable() (gas: 951862)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 138.73ms (229.65ms CPU time)\n\nRan 18 tests for test/TestHook.t.sol:HookIMDFirstTest\n[PASS] testFuzz_feeAndSettlementAllModes(bool,bool,uint96) (runs: 1000, μ: 371551, ~: 350486)\nLogs:\n  Bound result 2955\n\n[PASS] testFuzz_partialFillChargesOnlyFilledIMD(bool,bool,uint96) (runs: 1000, μ: 377777, ~: 351470)\nLogs:\n  Bound result 90000000000000019731078\n\n[PASS] test_buyExactInputPaysIMD() (gas: 411165)\n[PASS] test_buyExactOutputPaysIMD() (gas: 345820)\n[PASS] test_buyWithOnlyLaunchTokenLiquidityDefersUntilInputSettled() (gas: 72618795)\n[PASS] test_callbacksAndQuoteRefuseUnauthorizedCallers() (gas: 153497)\n[PASS] test_cannotInitializeOtherPools() (gas: 117965)\n[PASS] test_coreFailuresRollbackFeeAndPrice() (gas: 442350)\n[PASS] test_deployedCodeHasNoUpgradeOrDestructionOpcodes() (gas: 4152199)\n[PASS] test_exhaustedLiquidityChargesNoPhantomFee() (gas: 1033699)\n[PASS] test_noAdminOrRedirectionEvenForRecipientOrDeployer() (gas: 1061274)\n[PASS] test_permissionsMatchMinedAddressAndPool() (gas: 30965)\n[PASS] test_rateUnaffectedByTimeBlocksCallerAndHookData() (gas: 921526)\n[PASS] test_recipientFailureDefersInIMDAndAnyoneCanRedeem() (gas: 684146)\n[PASS] test_sellExactInputPaysIMD() (gas: 339909)\n[PASS] test_sellExactOutputReceivesTheRequestedNetIMD() (gas: 397781)\n[PASS] test_tinyAmountsRoundDownWithoutBlocking() (gas: 1235380)\n[PASS] test_zeroLiquidityCollectAndExitRemainAvailable() (gas: 958233)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 139.93ms (379.98ms CPU time)\n\nRan 1 test for test/HookInvariant.t.sol:HookInvariantTest\n[PASS] invariant_feeConservationAndNoCustodiedLaunchTokens() (runs: 128, calls: 4096, reverts: 0)\n\n╭------------+--------------+-------+---------+----------╮\n| Contract   | Selector     | Calls | Reverts | Discards |\n+========================================================+\n| FeeHandler | redeem       | 1381  | 0       | 0        |\n|------------+--------------+-------+---------+----------|\n| FeeHandler | rejectPayout | 1371  | 0       | 0        |\n|------------+--------------+-------+---------+----------|\n| FeeHandler | swap         | 1344  | 0       | 0        |\n╰------------+--------------+-------+---------+----------╯\n\nLogs:\n  Bound result 4010\n  Bound result 1117\n  Bound result 1802\n  Bound result 15721\n  Bound result 3303551951\n  Bound result 1250565486419895638\n  Bound result 2379\n  Bound result 73000\n  Bound result 8586\n  Bound result 8374\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.07s (1.06s CPU time)\n\nRan 26 tests for test/RobinhoodFork.t.sol:RobinhoodForkIMDFirstTest\n[PASS] testFuzz_feeAndSettlementAllModes(bool,bool,uint96) (runs: 1000, μ: 362063, ~: 340180)\nLogs:\n  Bound result 4596\n\n[PASS] testFuzz_fork_rateIsConstantAcrossSizeTimeAndBlocks(bool,bool,uint96,uint32,uint32,bytes) (runs: 1000, μ: 369918, ~: 361343)\nLogs:\n  Bound result 1935485202338955741349\n\n[PASS] testFuzz_partialFillChargesOnlyFilledIMD(bool,bool,uint96) (runs: 1000, μ: 366887, ~: 341341)\nLogs:\n  Bound result 90000000000000000003752\n\n[PASS] test_buyExactInputPaysIMD() (gas: 397737)\n[PASS] test_buyExactOutputPaysIMD() (gas: 335484)\n[PASS] test_callbacksAndQuoteRefuseUnauthorizedCallers() (gas: 153435)\n[PASS] test_cannotInitializeOtherPools() (gas: 116402)\n[PASS] test_coreFailuresRollbackFeeAndPrice() (gas: 427605)\n[PASS] test_deployedCodeHasNoUpgradeOrDestructionOpcodes() (gas: 4152177)\n[PASS] test_exhaustedLiquidityChargesNoPhantomFee() (gas: 1033255)\n[PASS] test_fork_blockedPayoutDefersAsIMDClaimAndOnlyPaysTheWallet() (gas: 538162)\n[PASS] test_fork_buysAndSellsPaySeventyFiveBpsOfIMDToTheWallet() (gas: 1482982)\n[PASS] test_fork_exactAmountsOnRoundTrades() (gas: 722822)\n[PASS] test_fork_firstAndLaterTradesUseTheSameRate() (gas: 2513258)\n[PASS] test_fork_invalidSwapsRevertAndMoveNoFee() (gas: 462325)\n[PASS] test_fork_managerOwnerAndProtocolFeeControllerCannotRedirectHookFee() (gas: 858385)\n[PASS] test_fork_privilegedOnChainAccountsCannotCallHookCallbacks() (gas: 493918)\n[PASS] test_fork_redeemInsideAnotherUnlockReverts() (gas: 299516)\n[PASS] test_fork_routingThroughAnotherDriverStillPays() (gas: 1508830)\n[PASS] test_noAdminOrRedirectionEvenForRecipientOrDeployer() (gas: 1047949)\n[PASS] test_permissionsMatchMinedAddressAndPool() (gas: 30921)\n[PASS] test_rateUnaffectedByTimeBlocksCallerAndHookData() (gas: 917534)\n[PASS] test_sellExactInputPaysIMD() (gas: 330483)\n[PASS] test_sellExactOutputReceivesTheRequestedNetIMD() (gas: 385966)\n[PASS] test_tinyAmountsRoundDownWithoutBlocking() (gas: 1249275)\n[PASS] test_zeroLiquidityCollectAndExitRemainAvailable() (gas: 960208)\nSuite result: ok. 26 passed; 0 failed; 0 skipped; finished in 1.86s (2.84s CPU time)\n\nRan 26 tests for test/RobinhoodFork.t.sol:RobinhoodForkIMDSecondTest\n[PASS] testFuzz_feeAndSettlementAllModes(bool,bool,uint96) (runs: 1000, μ: 361705, ~: 340281)\nLogs:\n  Bound result 28168\n\n[PASS] testFuzz_fork_rateIsConstantAcrossSizeTimeAndBlocks(bool,bool,uint96,uint32,uint32,bytes) (runs: 1000, μ: 369510, ~: 348878)\nLogs:\n  Bound result 605526791756\n\n[PASS] testFuzz_partialFillChargesOnlyFilledIMD(bool,bool,uint96) (runs: 1000, μ: 367136, ~: 341687)\nLogs:\n  Bound result 90000000000000019731078\n\n[PASS] test_buyExactInputPaysIMD() (gas: 386636)\n[PASS] test_buyExactOutputPaysIMD() (gas: 331361)\n[PASS] test_callbacksAndQuoteRefuseUnauthorizedCallers() (gas: 153974)\n[PASS] test_cannotInitializeOtherPools() (gas: 116402)\n[PASS] test_coreFailuresRollbackFeeAndPrice() (gas: 433432)\n[PASS] test_deployedCodeHasNoUpgradeOrDestructionOpcodes() (gas: 4152177)\n[PASS] test_exhaustedLiquidityChargesNoPhantomFee() (gas: 1033284)\n[PASS] test_fork_blockedPayoutDefersAsIMDClaimAndOnlyPaysTheWallet() (gas: 543689)\n[PASS] test_fork_buysAndSellsPaySeventyFiveBpsOfIMDToTheWallet() (gas: 1481515)\n[PASS] test_fork_exactAmountsOnRoundTrades() (gas: 721004)\n[PASS] test_fork_firstAndLaterTradesUseTheSameRate() (gas: 2499385)\n[PASS] test_fork_invalidSwapsRevertAndMoveNoFee() (gas: 468150)\n[PASS] test_fork_managerOwnerAndProtocolFeeControllerCannotRedirectHookFee() (gas: 856423)\n[PASS] test_fork_privilegedOnChainAccountsCannotCallHookCallbacks() (gas: 496077)\n[PASS] test_fork_redeemInsideAnotherUnlockReverts() (gas: 299516)\n[PASS] test_fork_routingThroughAnotherDriverStillPays() (gas: 1497701)\n[PASS] test_noAdminOrRedirectionEvenForRecipientOrDeployer() (gas: 1036836)\n[PASS] test_permissionsMatchMinedAddressAndPool() (gas: 30921)\n[PASS] test_rateUnaffectedByTimeBlocksCallerAndHookData() (gas: 900990)\n[PASS] test_sellExactInputPaysIMD() (gas: 336050)\n[PASS] test_sellExactOutputReceivesTheRequestedNetIMD() (gas: 394603)\n[PASS] test_tinyAmountsRoundDownWithoutBlocking() (gas: 1242795)\n[PASS] test_zeroLiquidityCollectAndExitRemainAvailable() (gas: 954945)\nSuite result: ok. 26 passed; 0 failed; 0 skipped; finished in 1.86s (4.13s CPU time)\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodForkInvariantTest\n[PASS] invariant_fork_everyFeeReachesTheWalletOrItsClaim() (runs: 64, calls: 2048, reverts: 0)\n\n╭----------------+----------+-------+---------+----------╮\n| Contract       | Selector | Calls | Reverts | Discards |\n+========================================================+\n| ForkFeeHandler | redeem   | 1028  | 0       | 0        |\n|----------------+----------+-------+---------+----------|\n| ForkFeeHandler | swap     | 1020  | 0       | 0        |\n╰----------------+----------+-------+---------+----------╯\n\nLogs:\n  Bound result 5351629176294879141\n  Bound result 12606\n  Bound result 5\n  Bound result 1712184845\n  Bound result 9422\n  Bound result 12344\n  Bound result 15621\n  Bound result 982\n  Bound result 2021\n  Bound result 1\n  Bound result 2352\n  Bound result 621967927\n  Bound result 15638\n  Bound result 532\n  Bound result 1463377\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.43s (979.43ms CPU time)\n\nRan 7 test suites in 2.44s (7.64s CPU time): 91 tests passed, 0 failed, 0 skipped (91 total tests)\n","passed":true},{"durationMs":97,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"TestHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"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\":4,\"LICENSE\":23,\"README.md\":204,\"REVIEW.md\":42,\"dependencies.lock.json\":30,\"foundry.toml\":21,\"remappings.txt\":4,\"src/TestHook.sol\":209,\"src/TestToken.sol\":12,\"test/HookInvariant.t.sol\":108,\"test/RobinhoodFork.t.sol\":386,\"test/TestHook.t.sol\":278,\"test/TestToken.t.sol\":79,\"test/helpers/HookFixture.sol\":161,\"test/helpers/MockIMD.sol\":22,\"test/helpers/PoolDriver.sol\":59},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"e668cbd376193973265ab8ad094bf21b0618ef7e465963be6f0b3b86e0d0f740","verifiedTreeHash":"c18e8a4ed30942a219db27803fce5c5265280060","verifierVersion":"0.1.0+18543036"}]}