{"assessments":[],"deployments":[{"attestationHash":"f2c349be853c8dd0171fa4d589618bdc5a51218fba6756c293c0798eec98a86f","chainId":4663,"contracts":[{"address":"0x7434511fbae8b2900c9546d910ba24c81379d229","blockNumber":88067717,"name":"IMD6900RobinhoodLaunch","role":"other","txHash":"0xa361122b96cd79b4889819abfa78d94ed4904a8d365edadd02f759796bc541d8"}],"id":"6852fdde-75ab-4792-be10-0861e97bbb10","manifestHash":"94205af6e5de75b2181ff9fe3671bf24128b27a3db4972327bdd0c7850ee349c","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":"1b9f4da0-fc35-47b3-bb7e-deb4d47758ca","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"68a0236d1543669eac16098c5df33cddff27e0be6ca2e78e8f7900d2b5422ac2","dependsOn":["audit_imported_code"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"2d7d84a1a9972f1497e5bc68a704cbf42294d08c62d2868f102ffe3b909b9b51","skillId":"adapt-contract-project","tools":[]},"key":"adapt_contract_project","kind":"code","role":"implement","skillHash":"2d7d84a1a9972f1497e5bc68a704cbf42294d08c62d2868f102ffe3b909b9b51","skillId":"adapt-contract-project","state":"accepted"},{"acceptedSubmissionHash":"579e01b6dd79f21df7496f586e37ad089fb523b55e40abda8b1288e9fab9a4a8","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"9e6c36f88fb5fd16b8ec97294752c2503ed56dd25eb7a09ec088ba152f4bcd27","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"2feac20e63a79c2e04be77bc193bf07b7f1b2cd598e3c9f44f1f7fb777a7fd6d","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","tools":[]},"key":"audit_imported_code","kind":"code","role":"review","skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","state":"accepted"},{"acceptedSubmissionHash":"f1d21d6855d46dedeb3cb4141eaa00b75342dddb3f0034ee8ecfae632706b8e9","dependsOn":["adapt_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"0a16e119451a2df5161d4b7347ef8d21568c3badd7f77bb7c3a864f4ecf203c0","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"89bbcf53ef5a8a8781aef9640dfd9a6b1d774fd2c5939f29497c6e2d7100e951","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"8600812c32f00968c509bd9248d6aa1a586d09ba082709edc694ea482f0c5632","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"69ecbe517a10da6ffef9b3f01b8e5c0895d7130732019477b56d84803756450b","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","state":"accepted"}],"objective":"Deploy IMD6900RobinhoodLaunch (src/IMD6900RobinhoodLaunch.sol) on Robinhood Chain exactly as it is in the repository: one contract, four constructor arguments in this order: 0x35da9c0303507ddf708e87f2568eddf12c47a059 (owner: the team wallet), 0x8366a39CC670B4001A1121B8F6A443A643e40951 (Uniswap v4's PoolManager on Robinhood Chain), 0x0000198C940D8cD70Cb9ACeC5E3af8216ac57d2F (Robinhood's IMDSTR, the old token that claims the new coin 1:1), 0x16D3f65B708883DF042d98E1C7a49B32A33E2A14 (the Robinhood timelock: owns the hook and recovers ETH). Its constructor creates IMD6900LockedPool and nothing else (no salt mining: about 6.9M gas from any deployer address). After the deploy, createHook(salt) (anyone, once) creates IMD6900Hook with CREATE2 from the launch at a salt mined off-chain for its address (script/launch.sh hook: its address carries exactly the hook's permission flags and doesn't start with 0x91; the hook is owned by the timelock); the team calls it, the deploy does not need to. The pool's fee is static: 4.44% (POOL_FEE 44_400), the same for every trade, so Uniswap's router routes the pool on its own (no dynamic fee, no return deltas); it accrues to the locked position, whose fees anyone can collect. A v4 pool's fee can't change, so IMD6900LockedPool.lowerFee (the timelock only, lower only) moves the whole position in one unlock to a new pool at the lower fee and the same price. It calls nothing that existed before it, so it deploys the same on a fresh chain (test_DeploysOnAFreshChain); it fits one transaction (test_FitsOneTransaction, ~6.9M gas, the same from any deployer); no contract shows CALLCODE, DELEGATECALL or SELFDESTRUCT, creation code included (test_PassesTheAdmissionScan). Later, its owner launches the coin IMD6900 at a 0x6900… address mined for this contract's address (launch(): the coin with CREATE2, the pool opened with the whole 1B supply as coin-only liquidity, locked for good, and the opening buy, in one transaction), through Programmable's custom launch, which binds every component to the exact source: do not change the source, the compiler settings (solc 0.8.26, via-IR, 200 runs, cancun, bytecode_hash none), the constructor, the hook's permissions, the 4.44% static fee and lowerFee's lower-only move (refused while the perps engine has positions open), the fee refund (IMD6900LockedPool.feeCheckpoint/feeRefund: only the hook's perps engine and the launch's sellHeld trade fee-free), the pool's lack of any liquidity removal (only lowerFee's move from one pool into the next), the coin's plain ERC20 (no owner, no mint, no tax) and launch()'s refusal of any coin address not starting with 0x6900, the fee split's defaults (90% the pot bridge, which feeds IMD6900's NFT floor pot on Ethereum; 10% $IMD bought for IMD's canonical Robinhood burn executor 0x4D6537ec564aFf82eABb1Ce5f355E817cBEA1e07, fixed in the code), the perps engine's fees (addPerpFees: all of it as ETH to the pot bridge), the split and every landing address being the timelock's alone to change (setPayees, setPerpFeeTo, setCoinFeeTo, setImdBurn, setImdPool), or the timelock's role (the hook's owner; it alone recovers ETH, releases the excess or sells the coins the launch holds back into the pool for the opening buy's ETH (sellHeld), ends the claims early (endClaims), repoints $IMD's pool and sets the split). The perps are part of this project and of every audit and test step: src/perps/PerpEngine.sol, PerpVault.sol and PerpSwapLib.sol (inlined, internal functions only: no DELEGATECALL anywhere, the engine under EIP-170 at about 24.4KB). They are not deployed with the launch (the engine's constructor binds to the live pool): IMD6900LockedPool.createPerps creates them right after launch() from exactly the code whose keccak256 is ENGINE_CODE_HASH / VAULT_CODE_HASH (test_ThePerpsCodeIsTheAuditedCode): any fix to src/perps must update those two constants, or the test fails; the engine stays owned by the Robinhood timelock and is wired into the hook at once (initPerpEngine), fee-free by refund; audit its no-bad-debt liquidation inside the hook's pre-trade sweep, the refund pair, the vault's accounting and the fee split (20% stakers, 10% insurance, 70% to the launch's addPerpFees). test/Perps.t.sol covers creation, longs, shorts, the sweep's liquidations and fees. test/RobinhoodFork.t.sol runs the whole launch on a Robinhood fork when ROBINHOOD_RPC_URL is set (claims, trades, buys through Uniswap's Universal Router, the fee split, the $IMD burn, the perps created and traded, a fee move to 3.33%) and skips otherwise. Everything builds offline (lib/ is vendored; v4-core's PoolManager is used by the tests only).","parentJobId":null,"planHash":"99396a0b8724ac395c474eed3c20b5dcb90783bc27c618f5800e7b201700b1ed","previousHash":"9d38893b2fc1f1b0a603cf933bd9b559a95f0c92bf1442bcbedda94d5002fb5c","projectId":"1b9f4da0-fc35-47b3-bb7e-deb4d47758ca","publication":{"commit":"0d03b6d4e958cae21070c3aae0dcc612cbb9d703","deliveredAt":"2026-10-14T04:04:44.828Z","repoUrl":"https://github.com/identity-md-launches/launch-1591-imd6900robinhoodlaunch"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"52681","feedbackHash":"28af260a1f9a32678bcbb2542a5f119eb679901c45cd018e8a5a4cce1791fa70","nodeKey":"adapt_contract_project","submissionHash":"92caee231bfda987436f8babc2ab8a7d7cacb8ff95ffddf501fc164db0590b2c","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52222","feedbackHash":"06a3854fb6270bf5780ffcb344b119b7a521014127b21f5e0a7a089a5df80153","nodeKey":"adapt_contract_project","submissionHash":"c10f5463cef5edd3962dc55a245d06c85be2272bc5d520e7bbbd95622eca6690","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51251","feedbackHash":"b452d74290df4426d84a53601c6df38009e0a009df2190f092446f25dd2672c4","nodeKey":"adapt_contract_project","submissionHash":"68a0236d1543669eac16098c5df33cddff27e0be6ca2e78e8f7900d2b5422ac2","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52145","feedbackHash":"4e0e2a561f1e882677e7c6030f2e88dda9cfdfe3e941ce95c63acf7f4e5a84b4","nodeKey":"adapt_contract_project","submissionHash":"077e04fb060101c0b7dab85197ee1baaaa7e88f25f4228ad1d9a8e861eea52f7","tag1":"verification:checks","tag2":"acceptance-v2","value":0},{"agentId":"51445","feedbackHash":"5230513dc89fba389533422b45672f37e7baaeeed2eea08edbce8f3c7297c1c3","nodeKey":"adapt_contract_project","submissionHash":"3c5e5da7344ebace507ded9faf9f6b2d1da8f0abd22dc3f9196be217ca2800f1","tag1":"verification:checks","tag2":"acceptance-v2","value":0},{"agentId":"52128","feedbackHash":"4aaff62e45b6b9c228aef568482491457524c80481f727cd0b8d3cab55f01474","nodeKey":"audit_economics","submissionHash":"579e01b6dd79f21df7496f586e37ad089fb523b55e40abda8b1288e9fab9a4a8","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50955","feedbackHash":"49b49beb9919ca9fe6d515ada70d44250e507466ee880e52e58454f481cf2d8c","nodeKey":"audit_flow","submissionHash":"9e6c36f88fb5fd16b8ec97294752c2503ed56dd25eb7a09ec088ba152f4bcd27","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51130","feedbackHash":"425c1262b87ea57c4659849ae7f37f644dee7f625a0e427437762cc9c2ee74e4","nodeKey":"audit_judge","submissionHash":"d39d9a8b0eaeeb0d171e77adc48d9b0659b138edd036b21c0ea311899c201acd","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"53934","feedbackHash":"42e09e69b157b98e38e1f512f37a586e50a6106a57b23ac1c20bb89975d4da36","nodeKey":"audit_judge","submissionHash":"f1d21d6855d46dedeb3cb4141eaa00b75342dddb3f0034ee8ecfae632706b8e9","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51298","feedbackHash":"3968267b71201d7c58dfc9c439a4c594e8d07f4ab028d65b35626120e15fd02d","nodeKey":"audit_math","submissionHash":"0a16e119451a2df5161d4b7347ef8d21568c3badd7f77bb7c3a864f4ecf203c0","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52552","feedbackHash":"81fd10e405abfd559fce4a752e73248ed9022a0bb4240e58cc52c7ad2c90f952","nodeKey":"audit_permissions","submissionHash":"89bbcf53ef5a8a8781aef9640dfd9a6b1d774fd2c5939f29497c6e2d7100e951","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52262","feedbackHash":"38c08cdd30127fce07b588ce06b296548853c9cae797057ba4e48afb9d727dbe","nodeKey":"manifest","submissionHash":"b595e8ce372ce14c7d7bda6934db4ba5ba61bd44418f1caf49be845998ef5d43","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51039","feedbackHash":"ea75d005e3118e16c47a99905308fb3c73b9b8714424b48890f19a4e5a53d8fb","nodeKey":"manifest","submissionHash":"8600812c32f00968c509bd9248d6aa1a586d09ba082709edc694ea482f0c5632","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51509","feedbackHash":"2bc5102048dd365316b7c2982214f60afe4ed6c3e442e66870449ffbcf93a749","nodeKey":"write_foundry_tests","submissionHash":"69ecbe517a10da6ffef9b3f01b8e5c0895d7130732019477b56d84803756450b","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"50976","feedbackHash":"5b40a5900f74b482b27d29d4954d2d2d93eb52fbeea24bb37f22c29961dcedf9","nodeKey":"write_foundry_tests","submissionHash":"c59209f2bb0760c2052f2da90c25b88d2225993677b8c80ab348614be4d1b697","tag1":"verification:checks","tag2":"acceptance-v2","value":0},{"agentId":"52286","feedbackHash":"d171020d155b616a153795457eaf14140839d2941d0bc077b0c988f1a0cc25f2","nodeKey":"write_foundry_tests","submissionHash":"83114168e52d936ca5d1552a10a78d6802d5241196f45f0320b2b2de80cd3813","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"54302b59fd4d569f90114a9c366fc30e654819ac5c0f9b93a8ef96008ca2dd37","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"bb61bb89c95e9b0ca61cb6f8c085b1edd8311bab0a257696b1f55498a8a8a3d9","device":"e02bec63aa9dcaa5","findings":[],"hash":"077e04fb060101c0b7dab85197ee1baaaa7e88f25f4228ad1d9a8e861eea52f7","nodeId":"6727e47f-f3de-44cd-b2d1-d344d37042c2","outcome":"completed","summary":"All checks pass. Final state of the work:\n\n**What this revision does**\n\nThe reviewer's medium finding reproduced with its proof on the accepted tree (one transaction drained 0.678 ETH of the $IMD bucket by looping `burnImd`). Four of the low findings reproduced too. All five are fixed in `src/IMD6900RobinhoodLaunch.sol` only, each with a test in `test/IMD6900RobinhoodLaunch.t.sol`. The perps code, hook, locked pool, coin, constructor and compiler settings are untouched, so the audited code hashes hold.\n\n- **One $IMD buy a block.** A second `burnImd` in the same block reverts, so a transaction can spend at most 0.1 ETH of the bucket. The reviewer's proof now passes.\n- **The bucket is debited by what the pool took**, not by the cap, so a thin $IMD pool no longer leaves ETH unbooked for the split.\n- **Claims never pass the reserve.** An aggregate bound in `claim()` refuses a root whose leaves sum above the declared total.\n- **The launch's owner cannot renounce**, matching the hook and the engine.\n- **A refused perps fee is booked, not reverted.** `addPerpFees` books what the destination refuses as owed, a new permissionless `payOwed` retries it, and the repoint, payee and recover paths keep every booking cancellable so the split cannot underflow.\n\n**Not changed, answered as disputed** with the reasoning in ADAPTATION.md: the vault's virtual-share seeding (the review's own documentation remedy is adopted in the README, since a vault change would alter the pinned code hash), and the three informational notes, which describe designed behaviour and the timelock trust model.\n\n**Verification on this machine**\n\n| Check | Result |\n|---|---|\n| `forge build` (solc 0.8.26, via-IR) | ok |\n| `forge test`, verifier's fuzz seed, empty env | 67 passed, 0 failed |\n| Reviewer's proof (copy in test/scratch) | passes |\n| Protected admission test, real constructor args, chain 4663 | passes |\n| Launch runtime size | 24,303 bytes, under EIP-170 |\n\nRuntime grew by 454 bytes, so the deploy costs roughly 0.1M more gas than before and still fits one transaction. `.imd-responses.json` answers all 13 finding ids, and ADAPTATION.md gained sections 5 through 13 plus the README notes on the per-block buy, the tolerant perps fee path, PLV funding order and the un-delayed setters. Nothing was committed, and no protected build file was touched.","treeHash":"f145b1abf928c18dad3688aae3a79857197ff78a","usage":{"cachedInputTokens":1794403,"inputTokens":610,"model":"claude-fable-5-1","outputTokens":45906,"runtime":"claude","turns":60,"wallClockMs":978128}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"2d027bc56749d95c","findings":[{"citation":"resolved","description":"The ERC-4626-style offset prices the first deposit against `assetsEth() + 1` with 1e6 virtual shares. That is safe when the vault starts empty, but `engine.totalEth()` (= plv + longOiEth) can be non-zero before anyone has shares: the owner's `PerpEngine.fundPlv` (documented as a permanent donation to the PLV) and the 20% open-fee / penalty share `_routeFee` books into `plv` whenever the vault address is set but nobody has deposited yet. Everything in `plv` at that moment is attributed to the virtual shares, and because yield is distributed pro-rata to (real + virtual) shares, the virtual block keeps taking its proportion of all future long-side yield for the life of the vault. Nothing can ever redeem it (there is no path that pays the virtual shares), so it is not a donation that benefits stakers: it is yield that disappears. Precision x boundary seam: the offset that protects the first depositor from a donation attack becomes a sink once the asset base is non-trivial before the first mint. Measured on this tree (test/scratch, Foundry): fundPlv 1 ETH, then the first staker deposits 1 ETH -> 999,999 shares vs 1,000,000 virtual; a 0.3 ETH 2x long routes 0.00414 ETH to plv; the sole staker's redeemable rises by 0.00207 ETH, exactly half. Suggested fix (keeps the design): seed with virtual assets rather than a share of real ones, e.g. price the first deposit against `assetsEth() - plvSeeded + 1` or mint the seed its own shares to a burn/treasury address, or document that fundPlv must only be called after the first community deposit.","line":274,"path":"src/perps/PerpVault.sol","reproduction":"1. Launch and createPerps as in test/Perps.t.sol. 2. Timelock (engine owner): engine.fundPlv{value: 1 ether}(). 3. STAKER: vault.depositEth{value: 1 ether}() -> returns 999,999 shares; vault.ethPosition(STAKER).redeemable = 0.9999995 ETH while engine.totalEth() = 2 ETH (the seeded 1 ETH belongs to the virtual shares). 4. ALICE: engine.openLong{value: 0.3 ether}(2, 0) -> _routeFee books 0.00414 ETH into plv (20% of the 6.9% fee). 5. vault.ethPosition(STAKER).redeemable = 1.0020695 ETH: the only staker received 0.00207 ETH of the 0.00414 ETH yield. Expected: the stakers (the only holders of vault shares) receive the whole LP yield share. Actual: 50% of every future yield accrues to the virtual 1e6 shares and can never be withdrawn by anyone (ratio = seed / (seed + first deposits)).","severity":"low","snippet":"        shares = FullMath.mulDiv(amount, ethShares + OFFSET, assetsEth() + 1);","title":"PerpVault: ETH already in the PLV before the first deposit is owned by the 1e6 virtual shares, and so is their pro-rata share of every later yield, permanently unclaimable"},{"citation":"resolved","description":"The buy in unlockCallback (what == 1) is an exact-input swap of `spend` with sqrtPriceLimitX96 = MIN_SQRT_PRICE + 1. Uniswap v4 stops consuming input once there is no liquidity left on the path and lands the price on the limit, so `ethIn = -d.amount0()` can be less than `spend`; only `ethIn` is settled (line 552) and the difference stays in the launch's ETH balance. But `imdBucket` was already reduced by the whole `spend`, so the difference is no longer earmarked for $IMD: `split()` computes `amount = balance - imdBucket - owedTotal` and divides it among the payees (90% to the pot bridge by default; 10% back into the bucket). Boundary x invariant seam: the invariant 'the $IMD share is spent only on $IMD' holds for every normal-sized pool and breaks exactly at the input boundary where the swap under-fills. The same pattern exists for the opening buy (`launchEth = spend` while only `ethIn` was settled), but with 1B coins of liquidity it is unreachable there. Fix: debit the bucket by what was actually paid (return ethIn from the callback and do `imdBucket -= ethIn`, or add `spend - ethIn` back).","line":500,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"State: imdBucket = 0.1 ETH (several harvests), and the $IMD pool at imdPoolKey() has liquidity that is exhausted below the current price after 0.06 ETH of input (a thin or mostly out-of-range pool, or one whose LPs withdrew). Anyone calls burnImd(0). Inside the unlock the swap consumes ethIn = 0.06 ETH and returns the $IMD bought; `imdBucket` becomes 0 while 0.04 ETH remains in the contract unbooked. Next split(): amount includes that 0.04 ETH; 0.036 ETH goes to POT_BRIDGE and 0.004 ETH back into imdBucket. Expected: imdBucket = 0.04 ETH after the burn, still reserved for $IMD. Actual: 90% of it leaves to the other payees.","severity":"low","snippet":"        imdBucket -= spend;\n        imd = abi.decode(poolManager.unlock(abi.encode(uint8(1), spend)), (uint256));","title":"burnImd debits the $IMD bucket by the full `spend` even when the $IMD pool absorbs less; the unspent ETH is re-split to the other payees at the next split()"},{"citation":"resolved","description":"`ethDepth(L, sp)` = L*(SQRT_MAX - sp)/SQRT_MAX * Q96/sp is the amount0 a position from the current price up to the max tick would hold, i.e. the constant-product virtual reserve. The pool's only position ends at `upperTick` (the opening price b), so the ETH that longs can actually sell into is L*(b-p)*Q96/(p*b) = depth * (b - p)/b. At the state the launch leaves (2.81 ETH buy at a 19.18 ETH virtual depth: p = 0.877 b) that is 12.3% of `activeEthDepth()`: 2.69 ETH real vs 21.9 ETH virtual. The constant-product projection and `exitInsolvent` remain exact while the price stays inside the range, and the supply identity (reserves + outside coins + burned = SUPPLY, with fee coins burned by `collect`) means the price can never pass b while any perp holds coins, so no bad debt follows from this. It does mean maxNotionalBps (3.5% of depth = 0.77 ETH) is 29% of the pool's real ETH, maxOiBps 5% is 41%, and maxLiqBps 20% exceeds the real ETH, so the per-timestamp liquidation throttle never binds. Reported for the author to decide whether the caps should be set against the real in-range ETH (depth * (b - p)/b, b = IMD6900LockedPool.upperTick) instead; the hook comment 'Full-range liquidity makes the depth constant along the way' describes the virtual reserve, which the current code uses consistently.","line":342,"path":"src/perps/PerpEngine.sol","reproduction":"After launch (BUY = 2.81 ETH, 19.18 ETH virtual depth) call engine.activeEthDepth(): ~21.9e18. Compute the pool's real ETH: PoolManager currency0 balance attributable to the position, or L*(b-p)*2^96/(p*b) with (p,,L) = pool.state() and b = TickMath.getSqrtPriceAtTick(pool.upperTick()): ~2.69e18. Ratio 0.123. A long of maxNotionalBps (0.77 ETH notional) is therefore 29% of the ETH available to all sellers rather than 3.5%.","severity":"info","snippet":"    function activeEthDepth() public view returns (uint256) {\n        uint160 sp = _sqrtP();\n        if (sp == 0) return 0;\n        return PerpSwapLib.ethDepth(_liq(), sp);\n    }","title":"PerpEngine sizes every cap against the virtual full-range ETH reserve, which is ~8x the ETH the [minTick, upperTick] position actually holds at the opening state"}],"hash":"0a16e119451a2df5161d4b7347ef8d21568c3badd7f77bb7c3a864f4ecf203c0","nodeId":"9afeb80e-31c7-4082-825b-0b9def7bb048","outcome":"completed","summary":"Review complete. The findings file is written and validated, the scratch tests are removed, and the tree is otherwise untouched.\n\n**Outcome**\n\nThe arithmetic in my area (Math Precision, Boundary, Numerical Gap) holds up well. I found no critical or high defect, so no proof tests are attached. Three findings are recorded in `.imd-findings.json`:\n\n- **Low, PerpVault share pricing.** If `plv` holds ETH before the first community deposit (the owner's `fundPlv` seed, or fees routed while nobody has shares), the 1e6 virtual shares own that ETH and keep taking their pro-rata slice of every later yield. Measured: a 1 ETH seed plus a 1 ETH first deposit gives the sole staker 999,999 shares against 1,000,000 virtual ones. A 0.3 ETH long routed 0.00414 ETH of yield and the staker received exactly half. The other half is unclaimable forever.\n- **Low, burnImd bookkeeping.** The bucket is debited by the full `spend` while the swap settles only what the $IMD pool absorbed before hitting its price limit. The remainder stays unbooked and the next split pays 90% of it to the pot bridge instead of the $IMD burn. Only reachable when the $IMD pool is thin enough for 0.1 ETH to exhaust it.\n- **Info, PerpEngine depth model.** The caps are sized against the virtual full-range reserve, which is about eight times the ETH the `[minTick, upperTick]` position actually holds at the state the launch leaves. The math stays exact inside the range, and I showed the price cannot leave the range upward while any perp holds coins, so this is a parameter-interpretation note, not a loss path.\n\n**What I verified and ruled out**\n\n- Engine ETH balance equals its books to the wei across long and short round trips and a sweep liquidation, with a 30-wei surplus per gross-up leg as the REFUND_DUST design intends.\n- The lowerFee move: derived that the liquidity rounded down minus one never needs more ETH or coins than the old position freed, for any price that has moved since opening.\n- The refund pair, including nested engine refunds inside the timelock's sale, returns exactly each caller's own fee.\n- The split, owed and recoverEth bookkeeping cannot underflow; funding, TWAP and projection intermediates fit their types.\n\n**Coverage**\n\nAll 100 listed entry points have a row, plus three invariant rows. Static-analysis leads (unsafe casting, encodePacked, reentrancy) were checked and none reproduced as defects.","treeHash":null,"usage":{"cachedInputTokens":2673528,"inputTokens":514,"model":"claude-fable-5-1","outputTokens":76657,"runtime":"claude","turns":26,"wallClockMs":1231667}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"fdbedee33322a6ef","findings":[{"citation":"resolved","description":"When a payee's call fails, its `part` simply stays in the contract's balance. The next split() recomputes `amount = address(this).balance - imdBucket` and divides that whole amount again by the current shares, so the refused payee's share is redistributed pro rata to everyone, not kept for that payee as the NatSpec says ('leaves it here for the next split'). split() and harvest() are permissionless, so anyone can loop them. With the default payees (POT_BRIDGE 90%, imdBurn 10%), each call while POT_BRIDGE reverts moves 10% of the pot's ETH into imdBucket, where burnImd spends it on $IMD: the pot's share is paid to the wrong party. With custom payees (setPayees) the same moves a reverting payee's share to the others.","line":455,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"After launch, contract holds 1 ETH of fees, imdBucket = 0, default payees. POT_BRIDGE's receive reverts (paused/upgraded bridge, or in a test: vm.etch(POT_BRIDGE, hex\"fe\")). Call split() 50 times. Expected: 0.1 ETH in imdBucket and 0.9 ETH kept for the pot bridge. Actual: call 1 imdBucket=0.1, call 2 amount=0.9 -> imdBucket=0.19, ... after n calls imdBucket = 1 - 0.9^n (0.995 ETH after 50 calls); burnImd then spends the pot's ETH buying $IMD. Fix: track per-payee owed amounts (or exclude unpaid shares from the next split's base).","severity":"medium","snippet":"            if (!ok) emit PayFailed(to[i], part);","title":"split() re-splits a refused payee's share among all payees: a reverting pot bridge drains the NFT pot's 90% into the $IMD bucket (or to other payees)"},{"citation":"resolved","description":"PerpSwapLib.swapLeg calls feeCheckpoint on the hook's launcher (the locked pool) before every engine swap, unconditionally whenever the hook exposes launcher(). isRefunded only accepts the engine currently wired in the hook. IMD6900Hook.setPerpEngine is documented as the escape hatch (replace an engine, or zero to turn perps off), but as soon as it points anywhere else, the old engine's close(), liquidate() and every settlement revert NotRefunded, so traders cannot exit and their collateral (and the vault's lent ETH/token in longOiEth/shortOiToken) stays locked in the engine. lowerFee refuses while openCount != 0, and any trader can hold a healthy minCollateral position open indefinitely, so the only route to a lower fee is setPerpEngine(0) and then lowerFee; after that the engine is bound to a pool with no liquidity and the positions can never be closed even if the engine is wired back.","line":252,"path":"src/IMD6900LockedPool.sol","reproduction":"Setup as test/Perps.t.sol setUp (launch, createPerps, staker deposits 10 ETH). ALICE: engine.openLong{value: 1 ether}(2, 0) -> id. Timelock: hook.setPerpEngine(address(0)). ALICE: engine.close(id, 0) -> reverts NotRefunded (from IMD6900LockedPool.feeCheckpoint via PerpSwapLib.swapLeg). Anyone: engine.liquidate(id) also reverts the same way. Then timelock: pool.lowerFee(33_300) succeeds (engine==0); timelock re-wires hook.setPerpEngine(engine): close(id) now reverts in the hook's _beforeSwap (NotOurPool, the engine's PoolKey is the old pool). Expected: positions closable at any time, or lowerFee/unwiring impossible while positions are open. Fix: let swapLeg skip the refund when the engine is not refunded (or let isRefunded accept LockedPool.perpsEngine), and make lowerFee check LockedPool.perpsEngine's openCount, not only the hook's current engine.","severity":"medium","snippet":"        if (!isRefunded(msg.sender)) revert NotRefunded();","title":"Unwiring or replacing the perps engine (the timelock's documented way out, and the only way to lowerFee while any position is open) freezes every open position: their closes and liquidations revert No"},{"citation":"resolved","description":"In unlockCallback(2) the launch arms feeCheckpoint, then swaps; the hook's afterSwap runs inside that window and calls engine.sweepLiquidations. A liquidation there runs PerpSwapLib.swapLeg, whose own feeCheckpoint calls _poke(address(this)) on the locked pool, collecting every fee accrued so far, including the 4.44% the launch's sale just paid, into the locked pool (later burned or sent to coinFeeTo by collect). The launch's feeRefund then pokes only what accrued after the engine's refund (about 0), so back = 0, sold = paid, and the timelock's sale pays the full pool fee despite the documented exemption.","line":501,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"After createPerps, a long open at 5x near its liquidation price; launch holds excessHeld coins. Timelock: sellHeld(amount, 0, to) large enough that the post-swap price trips the long. Expected: the round's coin fee (~4.44% of amount) refunded (back > 0, sold = paid - back). Actual: the long is liquidated in afterSwap, back == 0 for that round, sold == paid, and the fee coins are burned at the next pool.collect(). Fix: have checkpoints nest per caller (book the engine's pre-poke amount to the outer armed caller), or skip the hook's sweeps for the launch's sellHeld swap.","severity":"low","snippet":"                (, uint256 back) = pool.feeRefund(address(this));","title":"sellHeld's 'fee-free' sale loses its refund whenever the hook's post-trade sweep liquidates: the engine's feeCheckpoint books the launch's fee as ordinary fees"},{"citation":"resolved","description":"Read in full: src/IMD6900RobinhoodLaunch.sol, src/IMD6900LockedPool.sol, src/IMD6900Hook.sol, src/IMD6900.sol; src/perps/PerpEngine.sol lines 400-1120 (opens, close/liquidate, the hook sweep, settlement, swaps, fee routing, vault funding/admin); src/perps/PerpSwapLib.sol swapLeg/_grossUp/_launcherOf/_settle (the refund pair); src/perps/PerpVault.sol depositEth/withdrawEth/_haircut. Not reached within the time budget: PerpEngine.sol constructor, funding and TWAP (lines 1-400), PerpSwapLib's projection/exitInsolvent/limit math, PerpVault's queue sync and token-side yield (lines 355-823), the tests and scripts. Nothing was compiled or executed: a via-IR build of this repository was not attempted on this machine (the last commit notes an OOM kill), so the findings' reproductions are traced by reading, not run. Checked without a finding: hook permissions/flags, pool initialization guarded by the hook, the coin (plain ERC20, no owner/mint), vanity check, no DELEGATECALL/CALLCODE/SELFDESTRUCT in src, timelock-only setters.","line":227,"path":"src/IMD6900LockedPool.sol","reproduction":"Not a defect: coverage statement for this review.","severity":"info","snippet":"        if (keccak256(engineCode) != ENGINE_CODE_HASH || keccak256(vaultCode) != VAULT_CODE_HASH) revert NotThePerpsCode();","title":"Review scope: contracts read and not reached"}],"hash":"2feac20e63a79c2e04be77bc193bf07b7f1b2cd598e3c9f44f1f7fb777a7fd6d","nodeId":"5f068e72-5ba1-4789-b60e-ba2f9e5f864f","outcome":"completed","summary":"I found two medium defects and one low one, and saved them to `.imd-findings.json` with a separate note on what I read. I didn't compile or run anything: the last commit says the build machine was killed (exit 137), so I didn't try a via-IR build here. Every reproduction was traced by reading the code, and none comes with a proof test. I changed no files.\n\n**Findings**\n\n1. **Medium – a payee that refuses its ETH has its share handed to the others** (`src/IMD6900RobinhoodLaunch.sol:455`). When a payee's transfer fails, the ETH stays in the contract. The next `split()` divides the whole balance again across all payees, so the refused share is spread out rather than kept for that payee, as the code comment says it should be. Anyone can call `split()` or `harvest()` repeatedly. With the default split and the pot bridge reverting, each call moves 10% of what's left into the $IMD buy fund. After 50 calls, 1 ETH of fees becomes about 0.995 ETH spent buying $IMD instead of going to the NFT pot.\n\n2. **Medium – replacing or removing the perps engine freezes every open position** (`src/IMD6900LockedPool.sol:252`). Every engine trade asks the locked pool for its fee refund first. The pool refuses any engine that isn't the one currently set on the hook. So after the timelock calls `hook.setPerpEngine(0)` (the documented way out), `close()` and `liquidate()` on the old engine both revert, and traders' collateral is stuck.\n   - Any trader can block `lowerFee` by keeping one small, healthy position open. The only way around that is to unwire the engine first.\n   - If the timelock then runs `lowerFee`, the old engine is tied to a pool with no liquidity. Its positions can't be closed even if it is wired back.\n\n3. **Low – the fee-free `sellHeld` can pay the full 4.44% fee anyway** (`src/IMD6900RobinhoodLaunch.sol:501`). If the sale pushes a perps position into liquidation, that liquidation runs after the sale's swap but before the sale claims its refund. The engine's own fee bookkeeping sweeps up the sale's fee, so the refund comes back as about 0 and those coins are burned at the next fee collection.\n\n**Contracts read**\n- **In full:** `IMD6900RobinhoodLaunch.sol`, `IMD6900LockedPool.sol`, `IMD6900Hook.sol` and `IMD6900.sol`.\n- **Perps engine:** `PerpEngine.sol` lines 400–1120, which cover opening, closing, liquidation, the pre-trade sweep, settlement, swaps and fee routing.\n- **Fee refund:** the refund pair in `PerpSwapLib.sol`.\n- **Vault:** the ETH-side deposit and withdraw functions in `PerpVault.sol`.\n\n**Not reached in the time available**\n- `PerpEngine.sol` lines 1–400: the constructor, funding and price averaging.\n- `PerpSwapLib.sol`: the price-projection and insolvency math.\n- `PerpVault.sol`: the exit-queue syncing and the token-side yield.\n- The tests and scripts.\n\n**Checked, no problems found:** the hook's permissions and address flags, pool setup being limited to the hook, the coin being a plain ERC20 with no owner or mint, the 0x6900 address check, no DELEGATECALL, CALLCODE or SELFDESTRUCT in `src`, and the setters being timelock-only.","treeHash":null,"usage":{"cachedInputTokens":992307,"inputTokens":26,"model":"claude-opus-5-5","outputTokens":23655,"runtime":"claude","turns":15,"wallClockMs":262287}},{"artifacts":[],"attempt":3,"bundleHash":"ee13558ca45bf3634f5e397857e230f0bd7b7310d71f6b4960babc9c89d02454","device":"fa8fc4653a9e883d","findings":[],"hash":"3c5e5da7344ebace507ded9faf9f6b2d1da8f0abd22dc3f9196be217ca2800f1","nodeId":"6727e47f-f3de-44cd-b2d1-d344d37042c2","outcome":"completed","summary":"The build is clean (the earlier exit code came from the grep finding no compile output, not from the build) and all 59 tests pass. The work is complete.\n\n**Outcome.** The launch contract already fit the factory as it arrived, so the launch-kind rules required no change to the constructor, compiler settings, hook, fee, coin, split defaults or timelock role. All three audit defects reproduced and are fixed, each with tests. The perps source is untouched, so the two code-hash constants still match the build.\n\n**What changed**\n\n- **Refused payee's share (medium, fixed).** The launch now books a failed payee's part to that payee alone, excludes it from every later split's base, and retries it at the next split or through a new permissionless retry call. The timelock's recovery clamps the owed total the way it already clamped the IMD bucket. Two tests cover the audit's 50-call reproduction, a replaced payee, and the recovery clamp.\n- **Unwired engine freezing positions (medium, fixed).** The locked pool keeps refunding the engine it created, so its closes and liquidations work after the timelock unwires or replaces it. The fee move now refuses while either the hook's engine or the created engine has positions open. Two tests on the real engine cover the audit's sequence and a replacement engine.\n- **Lost refund on the timelock's sale (low, fixed).** Fee checkpoints now nest in transient storage: a checkpoint taken inside another refunded caller's window credits what it collected to that caller, whose refund pays it on. A test runs a sweep that trades inside each of the sale's three rounds and checks both parties get exactly their own fee back.\n- **Pre-existing test failure (test only).** The perps code-hash test failed before any change because the salt miner allocated memory on every try and ran out of gas. The miners now hash in scratch memory.\n\n**Verification**\n\n- The exact protected harness was rehearsed locally with the four real constructor arguments and passed.\n- Launch runtime is 23,576 bytes, under the limit; deploy execution is 7.51M gas against 7.22M before the fixes.\n- The fork test was not run, since it needs a Robinhood RPC and the task's verification has no network.\n\nADAPTATION.md at the repository root lists each change, its file, the finding or rule behind it, and the coverage note for the info-level finding. Nothing was committed.","treeHash":"3e14befc3bc806e6b3a2dde2ad3527f7af6d7c50","usage":{"cachedInputTokens":4445823,"inputTokens":1026,"model":"claude-fable-5-1","outputTokens":55186,"runtime":"claude","turns":67,"wallClockMs":963692}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"9c6767b941fcfedc","findings":[{"citation":"resolved","description":"The only protection of the $IMD share against being bought at a manipulated price is IMD_BUY_MAX (0.1 ETH, 'under what sandwiching it would be worth through two 1% fees', line 63) and a caller-chosen minImd. burnImd is permissionless and nonReentrant only guards nesting, not sequential calls, and nothing throttles it per block or per transaction. An attacker who moves the ETH/$IMD pool price can call burnImd(0) in a loop inside the same transaction until imdBucket is empty, so the amount bought at the pushed price is the entire bucket, not 0.1 ETH; the 10% fee share accumulated between harvests (and the imdBucket the launch keeps across splits, line 473) is therefore exposed to a sandwich whose size the cap was meant to bound. The swap itself (line 549) is exact-input to MIN_SQRT_PRICE+1 with no price bound of its own, so the only slippage check is the attacker's minImd.","line":498,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"State: after a few harvests imdBucket = 1 ETH (10% of 10 ETH of pool fees), the $IMD pool at its default key (fee 10_000, spacing 100). Attacker transaction: (1) swap E ETH into the $IMD pool to raise $IMD's price; (2) call L.burnImd(0) ten times: each spends 0.1 ETH at the inflated price and sends the $IMD to imdBurn; imdBucket reaches 0; (3) sell the $IMD from step 1 back. Expected: at most 0.1 ETH of the bucket can be spent against a manipulated price per opportunity, as the constant's comment states. Actual: the whole 1 ETH is spent at the manipulated price in one transaction; the attacker's profit is the bucket's price impact at the pushed price minus two 1% fees, which is positive whenever the pool's active depth is below roughly 50x the bucket. A per-block (or per-transaction) spend cap, or a maximum price derived from an on-chain reference, is needed for the cap to mean what its comment says.","severity":"medium","snippet":"        uint256 spend = imdBucket < IMD_BUY_MAX ? imdBucket : IMD_BUY_MAX;\n        if (spend == 0) revert NoEth();\n        imdBucket -= spend;\n        imd = abi.decode(poolManager.unlock(abi.encode(uint8(1), spend)), (uint256));\n        if (imd < minImd) revert Short(imd);","title":"burnImd's per-call cap does not bound a transaction: the whole $IMD bucket can be sandwiched in one tx"},{"citation":"resolved","description":"imdBucket is reduced by the full `spend` before the swap, but the swap at line 549 is exact-input with the limit MIN_SQRT_PRICE + 1, so when the $IMD pool's liquidity runs out before `spend` is consumed, v4 charges only ethIn < spend (line 550, settle at 552). The difference stays in the launch's balance without being re-added to imdBucket, so the next split (line 467: balance - imdBucket - owedTotal) divides it 90% to the pot bridge and 10% to the bucket: ETH set aside for $IMD's burn is re-routed to the pot.","line":500,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"Measured with the attached test (test/scratch/BurnImdRemainder.t.sol, forge test --match-path): the $IMD stand-in pool holds liquidity in one narrow band, 1.5 ETH of pool fees are harvested so imdBucket = 0.678 ether, then burnImd(0). The pool took ethIn = 0.0150 ether; imdBucket fell from 0.678 to 0.578 ether (the full 0.1), and 0.0850 ether sits in the launch's balance booked to nobody (balance - imdBucket - owedTotal). The next split() sends 90% of it to POT_BRIDGE. Expected: imdBucket = bucket - ethIn (0.663 ether), since the unspent ETH is still $IMD's share. The test asserts that and fails on this code: `578476400000000000 != 663457743910072555`. Same effect on Robinhood whenever the live $IMD pool's active liquidity cannot absorb 0.1 ETH before its price limit (a thin or repointed pool, line 436). Fix: have the callback return ethIn and do `imdBucket += spend - ethIn` after the unlock.","severity":"low","snippet":"        imdBucket -= spend;\n        imd = abi.decode(poolManager.unlock(abi.encode(uint8(1), spend)), (uint256));","title":"burnImd books the ETH the $IMD pool could not take as general fees: it leaves the $IMD share and goes 90% to the pot"}],"hash":"579e01b6dd79f21df7496f586e37ad089fb523b55e40abda8b1288e9fab9a4a8","nodeId":"7ada8138-151c-4ded-8bbd-8acf552b3e96","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds the result: two findings and a coverage row for every one of the 105 listed entry points plus five invariant rows.\n\n**Findings**\n\n- **Medium, `burnImd` cap is per call, not per transaction** (`src/IMD6900RobinhoodLaunch.sol:498`). The 0.1 ETH cap is the only defense against the $IMD share being bought at a manipulated price, and `nonReentrant` only blocks nesting. An attacker can push the $IMD pool, call `burnImd(0)` in a loop until the bucket is empty, then sell back. The whole accumulated bucket is exposed, so the cap does not bound what its comment claims. Fix: a per-block or per-transaction spend cap, or an on-chain price bound.\n- **Low, the unspent part of a $IMD buy leaves the bucket** (`src/IMD6900RobinhoodLaunch.sol:500`). The bucket is reduced by the full spend before an exact-input swap that may run out of liquidity. Measured with the attached Foundry test: the pool took 0.015 ETH, the bucket fell by 0.1 ETH, and 0.085 ETH was left unbooked, which the next split sends 90% to the pot bridge. The test fails on the current code and is attached as proof.\n\n**What I checked and found sound**\n\n- The launch's split, owed bookkeeping, recoverEth re-sync, claims, endClaims, sellHeld's three fee-free rounds and the balance invariant `balance >= imdBucket + owedTotal`.\n- The refund pair: the per-caller transient slot, the engine's nested checkpoint and refund inside the launch's sale and inside users' swaps, and the afterSwap skip for the launch.\n- The perps' ETH accounting on open, close, liquidation, funding and the bad-debt waterfalls, the vault's queue index and token-yield accumulator, and every owner setter's bounds.\n- One lead I refuted by test: a long being written off at zero proceeds once a sale drains the pool to the floor. Every circulating coin came out of the pool and spot sells pay the 4.44% fee in coins, so the price cannot cross `upperTick` while a long holds coins. A 2x long survived a full sale of the opening coins with stakers and insurance whole.\n\n**Noted as a design tradeoff, not a defect**: a short insolvent at spot but refused by the 10% mark band is treated as backlog and the pending trade proceeds, so the loss lands on insurance and the PLV once the TWAP catches up, which can lag up to roughly 17 seconds given the 15-second observation spacing. This matches the documented intent in `_doSweep`.\n\nSlither did not run on this tree and Aderyn's lines were checked as leads only. None of them yielded a finding beyond the two above.","treeHash":null,"usage":{"cachedInputTokens":2997103,"inputTokens":546,"model":"claude-fable-5-1","outputTokens":81050,"runtime":"claude","turns":27,"wallClockMs":1397077}},{"artifacts":[],"attempt":2,"bundleHash":"17b57d358569070db6c13c102ebc2b8d2711ba50ea074129d23e5a4723625143","device":"723b11f958c65250","findings":[],"hash":"68a0236d1543669eac16098c5df33cddff27e0be6ca2e78e8f7900d2b5422ac2","nodeId":"6727e47f-f3de-44cd-b2d1-d344d37042c2","outcome":"completed","summary":"The work is complete and verified. Nothing further is needed from the tree, so here is the recap.\n\n**Outcome.** The reviewer's proof now passes, and the whole suite passes with the verifier's fuzz seed in an empty environment. The tree holds the previously accepted work plus the fixes below. No file under `src/perps` changed, so the audited code hashes still hold.\n\n**Why the proof had failed on the verifier.** Its build compiles `src/`, the non-test helpers in `test/` and the proof, but not the project's `*.t.sol` files, which were the only importers of v4-core's PoolManager. The proof's artifact lookup therefore found nothing. The new compile-only file `test/PoolManagerArtifact.sol`, also imported by the existing salt helper, keeps that artifact in such a build. I rehearsed the verifier's exact layout on a copy of the tree with every project test removed, and the proof passes there.\n\n**Contract changes in `src/IMD6900RobinhoodLaunch.sol`, each with a test:**\n- `burnImd` allows one buy per block timestamp, so one transaction can never spend more than the 0.1 ETH cap. The proof and a new loop test confirm it.\n- The $IMD bucket is debited by what the pool actually took, not by the amount offered, so a thin pool leaves nothing for the next split to misroute.\n- `claim` refuses to draw past the declared reserve, whatever the Merkle root's leaves sum to.\n- `renounceOwnership` reverts, matching the hook and the engine.\n- `addPerpFees` uses a tolerant send. A refused amount is booked, excluded from the split, retried with the next forwarding and dropped by the timelock's ETH recovery.\n\n**Checks run.** Build, the full suite (67 project tests plus the proof, all passing), the verifier-style proof run, the protected admission test with the four real constructor arguments on chain id 4663, and a deploy on a local anvil node.\n\n| Measure | Value |\n|---|---|\n| Runtime size | 24,022 bytes (cap 24,576) |\n| Deploy gas, real arguments | 8,056,611 |\n\n**Not changed, documented.** The vault's virtual-share seam, the one-shot perps recreation, the engine's cap sizing and the timelock's undelayed powers are recorded in ADAPTATION.md as operating rules and trust assumptions. The responses file answers all twelve findings, including the three from the imported audit that the earlier accepted round fixed.","treeHash":"8afc5443883b56572f5ae423d6502c29c4cf9df3","usage":{"cachedInputTokens":5080124,"inputTokens":1538,"model":"claude-fable-5-1","outputTokens":46519,"runtime":"claude","turns":49,"wallClockMs":1227755}},{"artifacts":[],"attempt":1,"bundleHash":"df0cc64aeb37c30cb0f1fced81848a7e5278bcfee28f38fc55df9791eabb0d92","device":"74a99f640688d37b","findings":[],"hash":"69ecbe517a10da6ffef9b3f01b8e5c0895d7130732019477b56d84803756450b","nodeId":"4814be5a-2afc-4c69-87a6-6b70485a87b4","outcome":"completed","summary":"The revision is done and verified. Only my two files changed, both under test/.\n\n**What happened.** The rejected attempt failed on tree_mismatch, an infrastructure base-commit problem, so none of its content survived. The tree held my last accepted Edges and Invariants suites, written against a launch source that has since changed: the perps-fee forwarding now books what its destination refuses, the owner can no longer renounce, burnImd is gated to one buy per timestamp and debits the bucket by what the pool took, and claims are capped at the reserve. The old suites still compiled and passed, but they did not exercise any of that.\n\n**Invariants.t.sol** now drives the new behaviour and checks it:\n- Handler actions for the timelock repointing the perps' fee destination to a refuser, a payee or the pot, the refuser opening and closing, a stranger paying into addPerpFees, and the engine's flush. Each forwarding is checked to either reach the destination with the whole book on top, or book exactly what came in and move nothing.\n- A renounce action that expects the refusal from both the owner and the timelock, and a new invariant that the owner, the timelock and the engine's fee sink never move.\n- The burnImd action now asserts the bucket is debited by what left the launch, never past the cap, and that a second call in the same timestamp is refused.\n- The ETH-backing invariant now includes the perps' book, and a ghost ledger checks the bucket only ever grows through split.\n\n**Edges.t.sol** gained three unit tests: the perps' book surviving a split and a recovery that leaves it covered, then being cancelled before the bucket by a recovery that does not; a stranger's zero and non-zero payments through addPerpFees; and the buy gate not being set when an empty bucket reverts the call.\n\n**Verification.** The full suite passes with the verifier's seed, 90 tests across 6 suites. A scratch drive of the handler confirmed its actions land: longs and shorts open and close, $IMD buys happen, and perps fees are both forwarded and booked.\n\n**One observation, not filed as a finding.** addPerpFees pays its destination with full gas and clears the book only afterwards, so a destination that reenters it could be paid the booked amount repeatedly from the launch's other ETH. Only the timelock can set that destination, and the timelock can already take every wei through recoverEth, so no privilege is gained. I left no findings file.","treeHash":"c26b4d84092d7f09df83b3c4e57af3dd1347176d","usage":{"cachedInputTokens":2426506,"inputTokens":1090,"model":"claude-fable-5-1","outputTokens":22455,"runtime":"claude","turns":35,"wallClockMs":543017}},{"artifacts":[],"attempt":2,"bundleHash":"e9b0fa5cd77e7c9b4de7feab5d5dcb38448de525158aa45ef1b082b3f9a96f84","device":"fcb71e606c933181","findings":[],"hash":"83114168e52d936ca5d1552a10a78d6802d5241196f45f0320b2b2de80cd3813","nodeId":"4814be5a-2afc-4c69-87a6-6b70485a87b4","outcome":"completed","summary":"Done. The full suite passes under the verifier's fuzz seed, and only two new files under `test/` were added. Nothing outside `test/` was touched, which was the fault behind the earlier rejected attempt.\n\n**What I found**\n\nThe existing 62 unit tests already covered the launch, the hook, the pool, the perps and the audit findings, but there were no invariant tests and no fuzz tests, which the acceptance criteria require for contracts that hold value. I did not find a defect in the implementation, so there is no findings file. Every property I wrote held across the random sequences.\n\n**What I added**\n\n- `test/Invariants.t.sol`: a handler that drives the launched system in random order with bounded inputs. Four traders buy and sell, the three snapshot holders claim in parts and over-claim, anyone collects, splits and buys $IMD for the burn, the timelock sells the held coins back, ends the claims, lowers the fee, resets the payees to lists that include a refusing payee and recovers ETH, the owner releases the unclaimed rest, and the perps open longs and shorts sized to the engine's live caps, close, liquidate, stake and unstake both sides, flush fees and advance time. Ten invariants are checked after every call: the locked position is all of the pool and is never taken out, the fee only goes down, the coin is conserved across every holder, the launch holds the coins and ETH it has booked, the vault and hook hold nothing, the engine's ETH and coins cover its ledgers plus every open short's backing, no bad debt, the engine stays wired and refunded, and the vault's share base never exceeds the engine's ETH.\n- `test/Edges.t.sol`: six fuzz tests over the arithmetic (the opening at any price and buy size, the reserve covered or the launch refused, claims in parts, the split's conservation, a fee move to any lower fee, the refund equal to the swap's fee) and eleven pinned failure-path tests the suite lacked: payee validation at every edge, half-set claims, zero amounts everywhere, a launch without ETH or below the lowest tick, the timelock's setters refusing nobody and self, the locked pool and hook refusing every caller that is not theirs, and the sweep's fail-open hatch.\n\n**Results**\n\n| Suite | Tests | Result |\n|---|---|---|\n| Existing suites | 62 | pass |\n| Edges (fuzz + pinned) | 17 | pass |\n| Invariants, 96 runs of depth 64 | 10 invariants, 6144 calls | pass |\n\n**Still owed**\n\nThe Robinhood fork test skips here as before, since this machine and the verifier have no network. The invariant runner logs only its last run's activity, so the per-run counts in the log understate how often opens and liquidations happened across all 96 runs.","treeHash":"0b5d5e31cb6b5164e3324e5ec353222f81b26302","usage":{"cachedInputTokens":3111535,"inputTokens":738,"model":"claude-fable-5-1","outputTokens":54068,"runtime":"claude","turns":49,"wallClockMs":1037536}},{"artifacts":[],"attempt":1,"bundleHash":"4ea61ab48028599e6665e1c96ecf61fd3f7d608a214f92ea1ddb1bc298f920e1","device":"9ab27edcfd62be02","findings":[],"hash":"8600812c32f00968c509bd9248d6aa1a586d09ba082709edc694ea482f0c5632","nodeId":"3af9b358-5a80-4748-9ba6-eb70d0b28be1","outcome":"completed","summary":"The accepted launch.json already matches the brief exactly, and this round listed no findings to repair, so I left it unchanged.\n\n**What I verified**\n\n- The manifest has only the keys kind, contracts and notes, with one contract, IMD6900RobinhoodLaunch, and four string constructor arguments in the constructor's declared order: owner, PoolManager, IMDSTR, timelock.\n- The owner argument is `$owner`, which this launch resolves to the team wallet 0x35da9c0303507ddf708e87f2568eddf12c47a059. The other three are the static addresses the brief names.\n- The notes run 2,132 characters, under the 4,000 limit, and describe only contracts present in the accepted source.\n- `forge build` succeeds and `forge test` with the verifier's fuzz seed passes all 67 tests, including the fresh-chain, one-transaction and admission-scan tests.\n- I rehearsed the pinned protected harness in test/scratch with the manifest's exact arguments and a stand-in factory. The constructor deployed through CREATE2, the predicted address matched, and the runtime passed the size and forbidden-opcode scan.\n\n**State of the tree**\n\nNo tracked file changed. The only additions are the scratch harness under test/scratch, which is discarded before submission.","treeHash":"627935a5c350386b8fa8fdd71c6428de2de946df","usage":{"cachedInputTokens":216164,"inputTokens":194,"model":"claude-fable-5-1","outputTokens":4010,"runtime":"claude","turns":7,"wallClockMs":191394}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"809ae05b6282abad","findings":[{"citation":"resolved","description":"claim() enforces only the per-caller ceiling from the Merkle leaf (`done > allowance`). The aggregate counter `claimedTotal` is never checked against `claimTotal`, the owner-declared reserve that launch() kept back and that the opening buy was required to cover (`bought < claimTotal` -> Short, line 297). setClaims() validates only `(root == bytes32(0)) == (total == 0)` (line 256), so a root whose leaves sum above `claimTotal` is accepted. When that happens: (1) claims pay out of the whole token balance, so the shortfall is taken from `excessHeld` - the coins launch() kept for the timelock to sell back for the opening buy's ETH (sellHeld/releaseExcess) - which is a different role's money; (2) once `claimedTotal > claimTotal`, the three subtractions `claimTotal - claimedTotal` revert under checked arithmetic: claimReserve() (line 246, read by `script/launch.sh recover`), releaseUnclaimed() (line 314) and endClaims() (line 395). The timelock can then never end the claims and the entire unclaimed reserve path is dead, while releaseExcess reverts too because the balance it would move was already claimed. The defect is a missing pair to the existing per-caller check; it is reachable only through the owner's setClaims() input (there is no attacker path), which is why it is low rather than medium.","line":452,"path":"src/IMD6900RobinhoodLaunch.sol","proof":"// test/scratch/ClaimTotalBound.t.sol - run: forge test --match-path test/scratch/ClaimTotalBound.t.sol\n// Fails on the tree as it is now ('next call did not revert as expected', the over-draw claim succeeds)\n// and passes once claim() refuses claimedTotal + amount > claimTotal.\n// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {Hooks} from \"@uniswap/v4-core/src/libraries/Hooks.sol\";\nimport {FullMath} from \"@uniswap/v4-core/src/libraries/FullMath.sol\";\nimport {IMD6900RobinhoodLaunch} from \"src/IMD6900RobinhoodLaunch.sol\";\n\n/// Proof for finding 1 of .imd-findings.json: claim() must not let a claim draw more than the declared\n/// claimTotal. The owner declares a total below the sum of the Merkle leaves (here: one leaf of 40M coins,\n/// total 20M); today the claim passes, draining the coins launch() kept for the timelock. With the fix\n/// (claim() refusing once claimedTotal + amount > claimTotal) the call reverts and this test passes.\n/// Fails on the tree as it is now with \"call did not revert as expected\".\ninterface IERC20Like {\n    function balanceOf(address) external view returns (uint256);\n    function approve(address, uint256) external returns (bool);\n}\n\n/// The old IMDSTR, as far as claims see it\ncontract MockImdstrScratch {\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n    function mint(address to, uint256 a) external { balanceOf[to] += a; }\n    function approve(address s, uint256 a) external returns (bool) { allowance[msg.sender][s] = a; return true; }\n    function transfer(address to, uint256 a) external returns (bool) { balanceOf[msg.sender] -= a; balanceOf[to] += a; return true; }\n    function transferFrom(address f, address to, uint256 a) external returns (bool) {\n        allowance[f][msg.sender] -= a; balanceOf[f] -= a; balanceOf[to] += a; return true;\n    }\n}\n\ncontract ClaimTotalBoundTest is Test {\n    address constant OWNER = address(0xA11CE);\n    address constant TL = address(0x71E10C);\n    address constant A = address(0xA);\n    uint256 constant AA = 40_000_000 ether;\n    /// One third of A's leaf: the owner's under-declared reserve\n    uint256 constant DECLARED_TOTAL = 20_000_000 ether;\n    uint256 constant BUY = 2.81 ether;\n\n    IMD6900RobinhoodLaunch L;\n    MockImdstrScratch imdstr;\n\n    function setUp() public {\n        address pm = vm.deployCode(\"PoolManager.sol:PoolManager\", abi.encode(address(this)));\n        imdstr = new MockImdstrScratch();\n        L = IMD6900RobinhoodLaunch(\n            payable(vm.deployCode(\"IMD6900RobinhoodLaunch.sol:IMD6900RobinhoodLaunch\", abi.encode(OWNER, pm, address(imdstr), TL)))\n        );\n        L.createHook(_hookSalt());\n    }\n\n    function test_ClaimsCannotExceedTheDeclaredClaimTotal() public {\n        imdstr.mint(A, AA);\n        bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(A, AA))));\n        vm.prank(OWNER);\n        L.setClaims(leaf, DECLARED_TOTAL, block.timestamp + 100 days);\n\n        bytes32 salt = _coinSalt();\n        address expected = L.tokenAddress(salt); // read before the prank: a view call consumes it\n        vm.deal(OWNER, BUY);\n        vm.prank(OWNER);\n        L.launch{value: BUY}(salt, expected, uint160(_sqrt(FullMath.mulDiv(1_000_000_000 ether, 1 << 192, 19.18 ether))), 0, address(L));\n        assertGt(L.launchBought(), DECLARED_TOTAL, \"the opening buy covered the declared total\");\n\n        vm.prank(A);\n        imdstr.approve(address(L), AA);\n        bytes32[] memory proof = new bytes32[](0); // the single leaf is the root\n\n        vm.prank(A);\n        vm.expectRevert(); // no claim may draw past the declared claimTotal\n        L.claim(AA, AA, proof);\n    }\n\n    /*                                HELPERS                                */\n\n    function _hookSalt() internal view returns (bytes32 s) {\n        bytes32 initHash = L.hookInitCodeHash();\n        uint160 flags = L.FLAGS();\n        for (uint256 i;; ++i) {\n            uint160 a = _create2(address(L), i, initHash);\n            if (a & Hooks.ALL_HOOK_MASK == flags && a >> 152 != 0x91) return bytes32(i);\n        }\n    }\n\n    function _coinSalt() internal view returns (bytes32 s) {\n        bytes32 initHash = L.tokenInitCodeHash();\n        uint160 vanity = L.VANITY();\n        for (uint256 i;; ++i) {\n            if (_create2(address(L), i, initHash) >> 144 == vanity) return bytes32(i);\n        }\n    }\n\n    function _create2(address deployer, uint256 salt, bytes32 initHash) internal pure returns (uint160 a) {\n        assembly (\"memory-safe\") {\n            let p := mload(0x40)\n            mstore(p, or(shl(248, 0xff), shl(88, deployer)))\n            mstore(add(p, 0x15), salt)\n            mstore(add(p, 0x35), initHash)\n            a := and(keccak256(p, 0x55), 0xffffffffffffffffffffffffffffffffffffffff)\n        }\n    }\n\n    function _sqrt(uint256 x) internal pure returns (uint256 y) {\n        if (x == 0) return 0;\n        y = x;\n        uint256 z = (x + 1) / 2;\n        while (z < y) (y, z) = (z, (x / z + z) / 2);\n    }\n}","reproduction":"As test/IMD6900RobinhoodLaunch.t.sol's harness does: deploy IMD6900RobinhoodLaunch(OWNER, poolManager, imdstr, TL); createHook(mined salt); leaves = [leaf(A, 40_000_000e18), leaf(B, 20_000_000e18)] with root over them; owner calls setClaims(root, 20_000_000e18, now + 100 days) - one third of the leaves' real sum, 60_000_000e18 (launch() accepts it: the opening buy of 2.81 ETH lands ~122_810_000e18 > 20_000_000e18). launch(..., excessTo = address(L)) keeps the excess (about 22.8M coins) as excessHeld. A approves IMDSTR and calls claim(40_000_000e18, 40_000_000e18, proofA): it succeeds because 40M <= A's leaf allowance, and claimedTotal becomes 40_000_000e18 > claimTotal = 20_000_000e18. 20M of the coins A received came out of the timelock's excessHeld. Expected: the claim should revert (the contract holds only claimTotal + excessHeld coins and claimTotal is the claims' budget). Actual: it pays, and afterwards claimReserve() reverts with a 0.8.x arithmetic underflow, releaseUnclaimed(to) reverts (line 314 underflow) and endClaims() from the timelock reverts (line 395 underflow), so `script/launch.sh recover` (which reads claimReserve()) and the timelock's end-claims flow are bricked.","severity":"low","snippet":"        uint256 done = claimedBy[msg.sender] + amount;\n        if (done > allowance) revert OverClaim();\n        claimedBy[msg.sender] = done;\n        claimedTotal += amount;","title":"claim() never bounds claimedTotal by claimTotal: an under-declared claimTotal lets claims drain the timelock's held coins and permanently reverts claimReserve()/releaseUnclaimed()/endClaims()"},{"citation":"resolved","description":"The launch inherits solady Ownable and does not override renounceOwnership(), so the owner (the team wallet, constructor argument 1) can call it and set the owner slot to zero. After that every onlyOwner function is unreachable forever: pre-launch, setClaims(), withdrawEth() and launch() - the coin can never be launched and the opening-buy ETH, which launch.sh sends to the contract by hand, can only be recovered by the timelock's recoverEth(); post-launch, releaseUnclaimed() and releaseImdstr(). The two sibling contracts in the same tree guard exactly this: IMD6900Hook.renounceOwnership() reverts NoRenounce (src/IMD6900Hook.sol:164, 'Ownership never goes to nobody: the engine and the escape hatch need an owner') and PerpEngine.renounceOwnership() reverts OwnershipCannotBeRenounced (src/perps/PerpEngine.sol:289, 'Ownership cannot be renounced: every risk lever lives behind it'). The launch is the asymmetry: it is the one contract whose ownership is required to bring the token into existence. Impact is bounded (the owner already has the power not to launch; no third-party funds are stranded - the unclaimed reserve stays reachable by the timelock via endClaims()+excessHeld once claims work), hence low, but the asymmetry is one line to close and protects against a fat-fingered or template-driven call.","line":44,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"Deploy IMD6900RobinhoodLaunch(OWNER, PM, imdstr, TL) as script/launch.sh deploy does, then `vm.prank(OWNER); L.renounceOwnership();`. Expected (as in the two sibling contracts): revert, e.g. a launch-specific NoRenounce error, mirroring IMD6900Hook.NoRenounce. Actual: it succeeds; l.owner() == address(0); every later `vm.prank(OWNER); L.launch(...)`/`L.setClaims(...)`/`L.withdrawEth(...)` reverts with solady Ownable.Unauthorized, and after a launch (if the renounce happens later) `L.releaseUnclaimed(...)` and `L.releaseImdstr(...)` are equally dead. On the same tree, `hook.renounceOwnership()` and `engine.renounceOwnership()` both revert.","severity":"low","snippet":"contract IMD6900RobinhoodLaunch is Ownable, ReentrancyGuard {","title":"IMD6900RobinhoodLaunch does not guard renounceOwnership, unlike the hook and the engine: the owner can permanently disable launch()/setClaims()/withdrawEth() and later releaseUnclaimed()/releaseImdstr"},{"citation":"resolved","description":"Documented, intended privileges, recorded here as the area's trust model rather than as a defect. The Robinhood timelock (0x16D3f65B708883DF042d98E1C7a49B32A33E2A14, a contract) holds, with no delay enforced by this code (the 24h delay lives in the external timelock itself): recoverEth (all ETH at any time, including the ETH booked to payees in `owed` and set aside for $IMD in `imdBucket` - it cancels those bookings, so a payee's `owed` is not an on-chain claim); releaseExcess/sellHeld/endClaims (all coins the launch holds back, to any `to`, and the unclaimed reserve early, with claims still open to whoever front-runs the 24h); setImdBurn/setImdPool/setPerpFeeTo (the landing addresses of the $IMD share, the $IMD pool the bucket buys in, the perps' fees, plus PerpEngine.setFeeSink for the engine's own sink - all of which redirect value already accrued but not yet paid: PerpEngine.feesOwed follows the current perpFeeTo at flush time, and the pool's collected-but-unpaid coin fees follow the current setCoinFeeTo); setPayees (the whole split); and, as the hook's owner, setPerpEngine/setPerpRefunded/setSweepFailOpen - note that sweepFailOpen = true lets any trader's swap skip the pre-trade liquidation sweep, suspending the 'no bad debt outranks always-tradeable' guarantee so a swap can push positions under water before they are closed. It also owns the perps engine (fees, risk levers, skimInsurance above the live book's need, setVault while no one has shares). Every item matches the brief and README ('each visible 24h before it runs'); the note is that the guarantee is the timelock's good behaviour, not a contract-enforced one.","line":362,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"Concrete state: after any landing of fees, `vm.prank(TL); L.setPerpFeeTo(attacker)` makes the next perp flushFees pay the new address the already-accrued `feesOwed`; `vm.prank(TL); hook.setSweepFailOpen(true)` makes the next `trader.swap(k, false, -coins)` (a dump big enough to sink an open long) proceed without the engine's pre-trade sweep, leaving the long liquidatable at a worse price and the shortfall on the vault. Expected per the README: the timelock waits out its public 24h delay before either takes effect (an external property). Actual: both take effect in the next transaction with nothing in these contracts marking them pending.","severity":"info","snippet":"    function recoverEth(address to, uint256 amount) external nonReentrant {\n        if (msg.sender != timelock) revert NotTimelock();\n        if (amount == 0) amount = address(this).balance;\n        if (amount == 0) revert NoEth();","title":"Trust note (the timelock's powers are un-delayed in these contracts): it can take all ETH and all held coins at any time and redirect every in-flight fee destination; sweepFailOpen suspends the pre-tr"}],"hash":"89bbcf53ef5a8a8781aef9640dfd9a6b1d774fd2c5939f29497c6e2d7100e951","nodeId":"638849b5-7c37-4942-99ae-1ca3aa10af3d","outcome":"completed","summary":"The review is complete and the working tree is untouched: `git diff` is empty, only the two sanctioned ignored paths exist (`.imd-findings.json`, `test/scratch/`).\n\n**Deliverable:** `.imd-findings.json` with 3 findings and a 104-row coverage record.\n\n**What I checked.** Every state-changing entry point in the verifier's list, traced through its guard, state writes and failure paths under the Access Control, Trust Gap and Asymmetry checklists: who can call each function in the launch, the locked pool, the hook, the engine and the vault; which storage each function writes; paired-function diffs such as withdrawEth/recoverEth, endClaims/releaseUnclaimed, depositEth/withdrawEth, setPerpEngine/initPerpEngine; and the seams where privileges meet economics, including the fee-refund pair, `createPerps`' open entry, and the `owed`/`imdBucket` bookings. The documented permission model matches the code on all 100 entry points: the timelock holds exactly the powers the brief lists, the owner exactly the rest, and the deliberately open functions (`createHook`, `createPerps`, `collect`, `harvest`, `split`, `burnImd`, `poke`, `flushFees`) are all either deterministic or bounded. The tree's 62 tests pass, and `cast code` on Robinhood confirms all six hard-coded addresses are live contracts.\n\n**Findings.**\n- **low** `claim()` never bounds `claimedTotal` by `claimTotal` (`src/IMD6900RobinhoodLaunch.sol:452`). A Merkle root whose leaves sum above the declared total lets claims drain the coins `excessHeld` holds for the timelock, and once `claimedTotal > claimTotal` the subtractions in `claimReserve()`, `releaseUnclaimed()` and `endClaims()` revert, breaking the runbook's `recover` step permanently. Reachable only through the owner's `setClaims` input.\n- **low** `IMD6900RobinhoodLaunch` does not guard `renounceOwnership` (`:44`) while `IMD6900Hook:164` and `PerpEngine:289` both refuse it. Renouncing bricks `launch()`, `setClaims()` and `withdrawEth()` forever, and later `releaseUnclaimed()` and `releaseImdstr()`. No third-party funds are stranded.\n- **info** the trust note for the area: the timelock's powers here carry no in-contract delay, it can take all ETH and held coins at any time, and every landing-address setter (`setPerpFeeTo`, `setFeeSink`, `setCoinFeeTo`, `setPayees`, `setImdPool`, `setImdBurn`) redirects value already accrued; `sweepFailOpen` suspends the pre-trade liquidation guarantee. Intended per the brief and README, recorded so the judge can treat it as an assumption rather than a defect.\n\nThe first finding carries a runnable proof in `test/scratch/ClaimTotalBound.t.sol`, also embedded in the JSON. It fails on this code with \"next call did not revert as expected\" because the 40M-coin over-claim against a 20M declared total succeeds, and it passes once `claim()` refuses the over-draw. Note that this scratch test is meant to fail now; `test/scratch/` is not kept. Nothing was unreached: all 100 entry points and four invariants have rows, 102 holds and 2 pointing at findings 1 and 2.","treeHash":null,"usage":{"cachedInputTokens":4389760,"inputTokens":266656,"model":"claude-fable-5-1","outputTokens":119017,"runtime":"claude","turns":45,"wallClockMs":837681}},{"artifacts":[],"attempt":2,"bundleHash":"6664aa74aa684abe1dfbcf68bcd08735808cb395af1003654436c4a3074c97da","device":"39da99ded7f125c8","findings":[],"hash":"8b10676a12b14f0d0a23a6305a6f8ebdb282227412ec3a1bfa78e104fc60ef77","nodeId":"6727e47f-f3de-44cd-b2d1-d344d37042c2","outcome":"completed","summary":"Done. The project is launch-ready as it stood; the work was the three audit findings that reproduced, plus one test fix.\n\n**Launch shape, verified unchanged.** The single contract `IMD6900RobinhoodLaunch` keeps its four-argument nonpayable constructor, explicit owner, no external calls at construction, and no forbidden opcodes. I rehearsed the protected harness itself with the real arguments and chain id 4663 and it passes. Runtime is 23,465 bytes, under the EIP-170 bound. `src/perps/` is untouched, so both code-hash constants still match the build.\n\n**Fixes, each reproduced on the original source first (the new tests fail there with the audit's numbers) and passing now:**\n- **Split re-splitting a refused share** (medium) in `src/IMD6900RobinhoodLaunch.sol`. A refused payee's share is now booked to that payee alone, excluded from later splits' base, and paid with its next share or via a public `payOwed`. The timelock's `recoverEth` clamps the owed total so recovered ETH is owed to nobody.\n- **Unwiring the engine froze positions** (medium) in `src/IMD6900LockedPool.sol`. The engine the pool created stays refunded whether or not the hook serves it, and `lowerFee` refuses while that engine holds positions, not only the hook's current one. Opens remain blocked while unwired, as before.\n- **The sale's refund lost to the sweep's nested checkpoint** (low) in `src/IMD6900LockedPool.sol`. Checkpoints now nest: what an inner checkpoint takes during the outer caller's swap is handed to the outer caller at its refund.\n- **Test flakiness**: the hash test died with MemoryOOG in the coin-salt mining loop, whose iteration count depends on the launch's address. The loops in `test/HookSalt.sol` now hash in a fixed scratch buffer.\n\n**Checks.** `forge build` and `forge test` pass with the project's own configuration: 58 tests, 0 failures, the fork test skipping without an RPC. `ADAPTATION.md` lists each change, the finding or rule behind it, and that the info-level finding needed no change. The README's three affected sentences were updated. No build configuration, dependencies, scripts, hook, coin or perps source were touched, and nothing was committed.","treeHash":"3a8e813b8245fd73c69a0ed50943f5a61d6d7413","usage":{"cachedInputTokens":2604569,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":47556,"runtime":"claude","turns":54,"wallClockMs":849574}},{"artifacts":[],"attempt":2,"bundleHash":"53faf2eb66e0e714ff3c3df559f133109ba62e50380521c061d3fee66aee1824","device":"1a7606be97f168d4","findings":[],"hash":"92caee231bfda987436f8babc2ab8a7d7cacb8ff95ffddf501fc164db0590b2c","nodeId":"6727e47f-f3de-44cd-b2d1-d344d37042c2","outcome":"completed","summary":"The accepted work is resubmitted unchanged in code, with the verification record added to ADAPTATION.md.\n\n**What I found**\n\n- The working tree is the accepted tree (b841b54). The earlier rejection came from that attempt starting at the parent commit's tree, not from these files. This attempt started from the right one.\n- All three audit findings (the re-split of a refused payee's share, stranding open perps positions on unwiring, and the sale's lost refund during a sweep liquidation) were already fixed and tested in the accepted work. Nothing in them needed another change.\n- Slither 0.11.6 crashes here exactly as the operator described. I traced the assertion to Slither's IR failing on the inline struct literal in the perps engine's position booking. That is an analyzer bug, not a code defect, and rewriting the audited perps code to dodge it would change the pinned code hash for no behavioural reason, so it stays.\n- Aderyn 0.6.8, the stand-in the verifier now uses, reports 6 \"high\" and 17 \"low\" classes. I checked each \"high\" against the source and none reproduces as a defect. The locked pool forwards its whole ETH balance on every collect, the vault has withdrawEth, the CREATE2 init-code concatenations are not hash inputs with collision room, and the uint128 cast is on an ETH amount.\n\n**Checks run on this machine**\n\n| Check | Result |\n| --- | --- |\n| forge build (solc 0.8.26, via-IR) | passes, 31 s |\n| forge test, verifier's fuzz seed, empty environment | 62 passed, 0 failed |\n| Contracts.protected.t.sol with the real creation code, chain id 4663 | passes |\n\n**What changed in this revision**\n\nOnly ADAPTATION.md: a new section recording Slither's crash location, Aderyn's triage, and the re-run checks. No file under src, test or script differs from the accepted tree, and no protected path was touched.","treeHash":"74883b04592cb18efc77e5f29aac8b933a26e6ec","usage":{"cachedInputTokens":963236,"inputTokens":514,"model":"claude-fable-5-1","outputTokens":14783,"runtime":"claude","turns":26,"wallClockMs":417715}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"3c7630b22a73c1fb","findings":[{"citation":"resolved","description":"The pool's ETH fees go through split(), which pays each payee with a tolerant call and books a refused share as owed (lines 479-489), so a paused or upgraded pot bridge never blocks a harvest. The perps engine's fees take a different path: PerpEngine.flushFees sends feesOwed to the launch's addPerpFees, which forwards it with SafeTransferLib.safeTransferETH, i.e. a reverting send. If perpFeeTo (POT_BRIDGE by default, or whatever the timelock set) refuses plain ETH, addPerpFees reverts, flushFees reverts, and feesOwed keeps growing inside the engine. Nothing is lost (the ETH stays in the engine, and setPerpFeeTo / PerpEngine.setFeeSink can reroute it behind the timelock's delay), but the launch's own fee-split rule that a refusing payee must never block fee collection is not applied to the perps' 70% share, which is the only fee path that cannot be drained by an unprivileged caller while the destination refuses.","line":214,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"State: launch() done, createPerps() done, perpFeeTo == POT_BRIDGE. Make the destination refuse ETH (e.g. timelock calls setPerpFeeTo(address(refuser)) where refuser's receive() reverts; the default POT_BRIDGE would behave the same the day it is paused or replaced by a contract without a payable fallback). Then: ALICE calls engine.openLong{value: 0.1 ether}(2, 0) so engine.feesOwed() == 0.1e18*690/10000*70/100 == 4.83e15. Anyone calls engine.flushFees(): expected (per the split's own convention) the share to be booked and retried; actual: addPerpFees -> safeTransferETH reverts with ETHTransferFailed, flushFees reverts, feesOwed stays 4.83e15 and every later flushFees reverts the same way until the timelock changes perpFeeTo or the engine's feeSink. Confirmed locally with test/scratch/PerpFeeRefuse.t.sol (local PoolManager, launch at 19.18 ETH FDV with a 2.81 ETH opening buy, createPerps, timelock fundPlv 1 ETH, setPerpFeeTo(Refuser), ALICE openLong 0.1 ETH 2x, flushFees): the test fails with ETHTransferFailed() on this code.","severity":"low","snippet":"        SafeTransferLib.safeTransferETH(to, msg.value);","title":"addPerpFees hard-pushes ETH to perpFeeTo, so a refusing pot bridge makes every PerpEngine.flushFees revert and strands the perps' fee share until the timelock repoints it"},{"citation":"resolved","description":"PerpEngine binds its poolId to the fee at creation (immutable poolFee). lowerFee moves the liquidity to a new pool at the lower fee and the hook's poolId follows it, so the wired engine's swaps revert NotOurPool from then on: opens fail closed as documented, and stakers can still withdraw since withdrawPlvTo/withdrawPlvTokenTo need no swap. But createPerps refuses a second run (perpsEngine already set), so the guarantee that the live perps come from exactly ENGINE_CODE_HASH / VAULT_CODE_HASH holds only until the first fee move; after it, a new engine must be deployed outside the locked pool and wired by the timelock through IMD6900Hook.setPerpEngine, which checks nothing about its code, and setPerpEngine does not reset perpRefunded, so a non-refunding replacement would be reported fee-free by perpFeeBps. This is consistent with the documented 'fails closed until a new one is wired', so it is recorded as a trust assumption rather than a defect.","line":226,"path":"src/IMD6900LockedPool.sol","reproduction":"launch(); pool.createPerps(engineCode, vaultCode); timelock calls pool.lowerFee(33_300) with openCount()==0 (succeeds). Then ALICE calls engine.openLong{value:0.1 ether}(5,0): _openPrologue passes (hook.perpEngine()==engine) but the swap on the old PoolKey reverts in IMD6900Hook._beforeSwap with NotOurPool. pool.createPerps(engineCode, vaultCode) reverts PerpsExist. The only way to perps again is an externally deployed PerpEngine + timelock setPerpEngine.","severity":"info","snippet":"        if (perpsEngine != address(0)) revert PerpsExist();","title":"After lowerFee the audited-by-hash perps cannot be recreated: createPerps is one-shot, so the only replacement path is a hand-deployed engine wired with setPerpEngine"}],"hash":"9e6c36f88fb5fd16b8ec97294752c2503ed56dd25eb7a09ec088ba152f4bcd27","nodeId":"d2f48d92-c8e1-4e18-bdde-05842f4b80df","outcome":"completed","summary":"Review complete. The findings file is written and validated: it parses as JSON, every one of the 100 listed entry points has a coverage row, and both cited snippets match the tree at the stated lines.\n\n**What I did**\n\n- Built the project and ran the local suite (61 tests pass; the fork suite skips without an RPC).\n- Traced the control flow across the launch, locked pool, hook and perps for my area (Execution Trace, Periphery, First Principles): the fee refund window ordering through nested sweeps, the sale's three-round loop, the split's owed accounting, the lowerFee move's rounding on both legs, the engine's gross-up and settle ordering, and the vault's queue math.\n- Chased and discarded one serious-looking lead: the engine assumes full-range liquidity and this pool has none above `upperTick`, so a drained pool would park the price in an empty region and deadlock the sweep. It is unreachable: after the first coin-fee burn the total supply is below what the position holds at `upperTick`, so the price can never get there.\n\n**Findings recorded**\n\n- **Low, confirmed with a scratch test.** `addPerpFees` forwards with a reverting send, so a refusing `perpFeeTo` makes every `PerpEngine.flushFees` revert and the perps' 70% share accumulates in the engine until the timelock repoints it. The pool's own `split` books a refused share instead. The test in `test/scratch/PerpFeeRefuse.t.sol` fails with `ETHTransferFailed` on the current code.\n- **Info.** `createPerps` is one-shot, so after a `lowerFee` move the audited-by-hash guarantee no longer covers any replacement engine, and `setPerpEngine` does not reset `perpRefunded`. Documented behaviour, recorded as a trust assumption.\n\n**Coverage**\n\nAll entry points are marked `holds` except the three tied to the findings above. No entry point was left unreached. Static-analysis leads (encodePacked hashing, unsafe casts, reentrancy ordering) were checked and none reproduced as a defect.","treeHash":null,"usage":{"cachedInputTokens":3406776,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":73298,"runtime":"claude","turns":33,"wallClockMs":1044458}},{"artifacts":[],"attempt":1,"bundleHash":"81c3ef2191e0e3d1a8b91141c97515b194420ca9cddbd6a2460d8cc100a2a2c4","device":"28e346843ec15530","findings":[],"hash":"b595e8ce372ce14c7d7bda6934db4ba5ba61bd44418f1caf49be845998ef5d43","nodeId":"3af9b358-5a80-4748-9ba6-eb70d0b28be1","outcome":"completed","summary":"launch.json is written and verified. Nothing else in the tree changed.\n\n**What the manifest declares**\n\n- **One contract**, IMD6900RobinhoodLaunch, kind evm_contracts, with four address constructor arguments in declaration order: `$owner` for the team wallet, then the Uniswap v4 PoolManager, the IMDSTR token, and the Robinhood timelock, each as the static address the brief names.\n- **Notes** describe what the constructor creates, what is created later by createHook, launch and createPerps, the fixed fee split, and the timelock's roles. The notes are 2,132 characters, under the limit.\n\n**Checks run**\n\n- The manifest has exactly the keys kind, contracts and notes, string arguments, and a 22-character contract name.\n- The compiled ABI's constructor takes four addresses named owner_, poolManager_, imdstr_ and timelock_, matching the argument order.\n- `forge build` succeeded with only two mutability warnings in the perps code, which predate this task.\n- `forge test` with the verifier's fuzz seed passed all 62 tests, including the fresh-chain deploy, one-transaction fit, admission scan and audited-perps-code tests.\n- A scratch rehearsal of the protected harness deployed the contract by CREATE2 from a factory address on an empty chain with the exact manifest arguments, confirmed the four stored addresses, and found no CALLCODE, DELEGATECALL or SELFDESTRUCT in the runtime.\n\n**One judgment call**: the brief names the owner address explicitly, and the task rules ask for `$owner` on privileged owner arguments. Both resolve to the same team wallet, so the manifest uses `$owner` and the notes state the address it resolves to.","treeHash":"902405580ecd195d55b56432f6dc7c6dc8cee61d","usage":{"cachedInputTokens":308420,"inputTokens":258,"model":"claude-fable-5-1","outputTokens":6000,"runtime":"claude","turns":9,"wallClockMs":122971}},{"artifacts":[],"attempt":1,"bundleHash":"c6e52e723a5b9edcd421e61972b4c74daa33f0c98e9e995d895e3b048014415c","device":"dc176559ff153c37","findings":[],"hash":"bd7d9ae50a2fba0386f6e4e3ac6c349198d8380e9a5f709e8147af82caf676cf","nodeId":"6727e47f-f3de-44cd-b2d1-d344d37042c2","outcome":"completed","summary":"Done. The project was already launchable as it stands, so the launch-side adaptation needed no change. All three reproducible audit findings are fixed with tests, one broken test was repaired, and the build and the full suite pass offline on a clean build.\n\n**What I verified first**\n- The constructor already fits the factory: four static arguments, nonpayable, owner from an argument, no external call, only the locked pool created. The protected admission test passes when rehearsed with the brief's four addresses.\n- No CALLCODE, DELEGATECALL or SELFDESTRUCT anywhere, creation code included. Every runtime is under EIP-170.\n- The code as it arrived built in 30 seconds here and 53 of 54 tests passed. The one failure was in a test's salt miner, not the contracts.\n\n**Audit findings, each reproduced and fixed**\n- **Re-split of a refused share** in `src/IMD6900RobinhoodLaunch.sol`: a refused share is now booked to that payee, excluded from later splits, and paid by a new permissionless `payOwed`. The timelock's recovery shrinks what is set aside, bucket first. Test: fifty splits against a reverting pot bridge leave the pot's 90% owed to the bridge only.\n- **Unwired engine froze positions** in `src/IMD6900LockedPool.sol`: the engine the pool created stays refunded after the timelock unwires or replaces it, and `lowerFee` refuses while either engine has positions open. No change to `src/perps`, so the audited code hashes are unchanged. Test: unwire, close fee-free, no new opens, rewire, replace, close, then the fee move goes through.\n- **Sale lost its refund during the post-trade sweep**: checkpoints now nest. A checkpoint opened inside another refunded caller's books the accrued fee to that caller. Test: a stand-in engine trades in each of the sale's three post-trade sweeps and the sale still gets all three refunds.\n- The scope-only finding needed nothing.\n\n**Test repair**\n- The salt miners in `test/HookSalt.sol` allocated memory per try, so the code-hash test ran out of EVM memory when it mined a second coin salt. They now hash in scratch memory, and the test passes: the hashes match the build.\n\n**Results**\n\n| Check | Result |\n|---|---|\n| forge build | passes, warnings only |\n| forge test | 57 passed, 0 failed |\n| Protected admission rehearsal | passes |\n\n`ADAPTATION.md` lists every change with its reason, and the README's test count and the two changed behaviours are updated. One caution is recorded there: a `forge inspect` or `--sizes` run leaves a perps artifact a few bytes off, which makes `createPerps` reject it until a clean rebuild. The fork test was not run, since no RPC was used. Nothing is committed, and `launch.json` is left to the manifest step.","treeHash":"8f3a5df117af9cd4c886f63b11f990aea395cc02","usage":{"cachedInputTokens":3960144,"inputTokens":1090,"model":"claude-fable-5-1","outputTokens":46475,"runtime":"claude","turns":58,"wallClockMs":960713}},{"artifacts":[],"attempt":1,"bundleHash":"f9fa3e12627f837aae88b8a75387ebf0fa7af06736b5b590ecf9281c91af1470","device":"357c46e3781993d4","findings":[],"hash":"c10f5463cef5edd3962dc55a245d06c85be2272bc5d520e7bbbd95622eca6690","nodeId":"6727e47f-f3de-44cd-b2d1-d344d37042c2","outcome":"completed","summary":"The work is complete and committed as `6a00c3a` on top of the pinned commit. Nothing else was pending.\n\n**Outcome.** The earlier attempt's output was not in the tree, so I redid the work against the pinned commit: all three audit findings reproduce by code trace and are now fixed, each with tests, and ADAPTATION.md records every change. Build and tests pass with the project's own configuration and the verifier's fuzz seed.\n\n| Check | Result |\n|---|---|\n| forge build | passes |\n| forge test (62 tests, verifier seed) | 62 passed, 0 failed |\n| Protected admission test, real constructor args | passes |\n| Launch runtime size | 23,849 bytes (limit 24,576) |\n| Deploy gas (anvil) | 8.02M, was 7.79M on the unmodified commit |\n\n**Fixes, each the smallest change that closes the finding:**\n- **split re-split** in `src/IMD6900RobinhoodLaunch.sol`: a refused share is booked to its payee and offered again with its next share, never divided among the others. The timelock's setPayees cancels bookings of dropped payees, and recoverEth drops them all. Four tests cover it, including the audit's 50-call reproduction.\n- **Engine unwiring strands positions** in `src/IMD6900Hook.sol`: setPerpEngine refuses while the wired engine reports open positions. An engine that cannot answer stays replaceable. Tested with the real engine and a stand-in.\n- **sellHeld loses its refund** in `src/IMD6900Hook.sol`: the post-trade sweep skips the launch's own swaps. The pre-trade sweep still runs, so the no-bad-debt liquidation is unchanged. Tested with a sweeping stand-in engine and with the real engine sinking a 5x long.\n\n**Not changed, and why:** src/perps is untouched, so the audited code hashes still match. The info-level scope finding needed nothing. A payee that burns all forwarded gas can still exhaust split, which I noted rather than fixed since payees are the timelock's to drop.\n\n**Test infrastructure:** the salt miners in `test/HookSalt.sol` now hash in fixed scratch memory. The pre-existing MemoryOOG failure in the perps code-hash test on the unmodified commit came from per-try memory growth, not from the contracts.\n\nOne thing worth knowing: the brief's \"about 6.9M gas\" figure was already stale on arrival. The unmodified commit deploys at 7.79M, and these fixes add about 3%. It still fits one transaction with room to spare.","treeHash":"b841b54ca7e370374ff7a839185a8f3316f65537","usage":{"cachedInputTokens":3241328,"inputTokens":802,"model":"claude-fable-5-1","outputTokens":51253,"runtime":"claude","turns":60,"wallClockMs":1245009}},{"artifacts":[],"attempt":1,"bundleHash":"815a314737e192ce8859156ae233e190e20f67c06c4e3756a521b3e99a7a23d3","device":"9b06782c7559b54c","findings":[],"hash":"c59209f2bb0760c2052f2da90c25b88d2225993677b8c80ab348614be4d1b697","nodeId":"4814be5a-2afc-4c69-87a6-6b70485a87b4","outcome":"completed","summary":"Done. `forge test` passes: 96 tests across 5 suites, run with the verifier's fuzz seed and once more with a different seed. No defects were found, so there is no findings file.\n\n**What I added** (two files under `test/`, committed as ee03d24):\n\n- `test/Invariants.t.sol`: a handler with three actor contracts that trade spot through the PoolManager, open and close longs and shorts, liquidate, stake and unstake both sides of the vault, claim queued exits, harvest, collect, flush perp fees, warp time, and act as the timelock for `sellHeld`, `lowerFee` and `endClaims`. It runs 128 sequences of 64 calls. Invariants checked after every call:\n  - The engine's ETH covers its whole ledger: free PLV, insurance, fees owed, the short side's yield pot, unpushed payouts, and every open short's backing.\n  - The engine's coins cover the free token inventory plus every open long's holding.\n  - The open set, `openCount`, `longOiEth`, `longSizeToken` and `shortOiToken` all agree with the positions themselves.\n  - No unabsorbed bad debt ever appears under bounded trading, so the hook's pre-trade sweep does its job.\n  - Vault shares equal the sum of the holders' shares, the vault itself holds nothing, and the token side is conserved (coins out never exceed coins in; staked coins never go missing).\n  - All of the pool's liquidity is the locked position, it is never emptied, the hook serves exactly that pool, the fee only falls, and the engine stays wired.\n  - The coin's supply only ever falls.\n  - The launch's ETH covers its bookings, it holds the claims' reserve and the held excess in full, every harvest splits 90/10, and the team wallet receives nothing.\n- `test/FailurePaths.t.sol`: 33 tests on the inputs the happy path never sends. Zero amounts, one wei, overdraws, the wrong caller on every privileged path of the launch, the locked pool, the hook, the engine and the vault, calling twice, bad payee lists (sum off by one, zero address, one past the cap, mismatched lengths), bad opening prices, leverage under and over the bounds, dust collateral, the notional and OI caps, the utilisation and insurance gates, missing ETH or token inventory, a trader that refuses ETH (owed, not stuck), queued vault exits that pay once the long closes, and two fuzz tests (a vault round trip never profits; a long round trip on a still market never profits and never costs the stakers).\n\n**What is still owed:** the fork tests in `test/RobinhoodFork.t.sol` skip here because this machine has no network, so the live Universal Router, the real $IMD pool and the real IMDSTR remain unexercised in this run.","treeHash":"33df0cfbb8b8189b525db2055b958cf6aa8cad33","usage":{"cachedInputTokens":2567107,"inputTokens":610,"model":"claude-fable-5-1","outputTokens":49316,"runtime":"claude","turns":35,"wallClockMs":1087421}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"232b62e021f6f394","findings":[{"citation":"resolved","description":"The only protection of the $IMD share against being bought at a pushed price is IMD_BUY_MAX (0.1 ETH, line 64: 'under what sandwiching it would be worth through two 1% fees') plus a caller-chosen minImd. burnImd is permissionless, nonReentrant only blocks nesting, and nothing throttles it per block or per transaction, so one transaction calls it until imdBucket is empty. The swap in unlockCallback (what == 1, line 549) is exact-input to MIN_SQRT_PRICE + 1 with no price bound of its own, so an attacker who first moves the ETH/$IMD pool's price, then loops burnImd(0), then sells back, exposes the entire accrued bucket (10% of every harvest, kept across splits at line 473) to the sandwich the cap was meant to make unprofitable. The cap bounds nothing in practice; the author's own comment states the intended guarantee. Reported by audit_economics; reproduced here with a local PoolManager and a stand-in $IMD pool. Fix: a per-block (or per-transaction) spend cap on burnImd, or a maximum price derived from an on-chain reference (e.g. a TWAP of the $IMD pool).","line":498,"path":"src/IMD6900RobinhoodLaunch.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {Hooks} from \"@uniswap/v4-core/src/libraries/Hooks.sol\";\nimport {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"@uniswap/v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"@uniswap/v4-core/src/types/BalanceDelta.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"@uniswap/v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"@uniswap/v4-core/src/libraries/TickMath.sol\";\nimport {FullMath} from \"@uniswap/v4-core/src/libraries/FullMath.sol\";\nimport {IMD6900RobinhoodLaunch} from \"src/IMD6900RobinhoodLaunch.sol\";\nimport {IMD6900LockedPool} from \"src/IMD6900LockedPool.sol\";\n\ninterface IERC20 {\n    function balanceOf(address) external view returns (uint256);\n    function transfer(address, uint256) external returns (bool);\n}\n\ncontract MockErc20 {\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n    function mint(address to, uint256 a) external { balanceOf[to] += a; }\n    function approve(address s, uint256 a) external returns (bool) { allowance[msg.sender][s] = a; return true; }\n    function transfer(address to, uint256 a) external returns (bool) { balanceOf[msg.sender] -= a; balanceOf[to] += a; return true; }\n    function transferFrom(address f, address to, uint256 a) external returns (bool) {\n        allowance[f][msg.sender] -= a; balanceOf[f] -= a; balanceOf[to] += a; return true;\n    }\n}\n\ncontract Trader {\n    IPoolManager immutable pm;\n    PoolKey key;\n    constructor(IPoolManager pm_) { pm = pm_; }\n    function swap(PoolKey calldata k, bool buy, int256 amountSpecified) external payable returns (BalanceDelta d) {\n        key = k;\n        d = abi.decode(pm.unlock(abi.encode(buy, amountSpecified)), (BalanceDelta));\n    }\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        (bool buy, int256 amt) = abi.decode(data, (bool, int256));\n        BalanceDelta d = pm.swap(key, SwapParams(buy, amt, buy ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1), \"\");\n        _settle(key.currency0, d.amount0());\n        _settle(key.currency1, d.amount1());\n        return abi.encode(d);\n    }\n    function _settle(Currency c, int128 v) internal {\n        if (v < 0) {\n            if (c.isAddressZero()) pm.settle{value: uint128(-v)}();\n            else { pm.sync(c); IERC20(Currency.unwrap(c)).transfer(address(pm), uint128(-v)); pm.settle(); }\n        } else if (v > 0) pm.take(c, address(this), uint128(v));\n    }\n    receive() external payable {}\n}\n\n/// Puts liquidity in a chosen band of a plain pool\ncontract BandAdder {\n    IPoolManager immutable pm; PoolKey key; int24 lo; int24 hi; uint256 liq;\n    constructor(IPoolManager pm_) { pm = pm_; }\n    function add(PoolKey calldata k, int24 lo_, int24 hi_, uint256 l) external { (key, lo, hi, liq) = (k, lo_, hi_, l); pm.unlock(\"\"); }\n    function unlockCallback(bytes calldata) external returns (bytes memory) {\n        (BalanceDelta d,) = pm.modifyLiquidity(key, ModifyLiquidityParams(lo, hi, int256(liq), 0), \"\");\n        if (d.amount0() < 0) pm.settle{value: uint128(-d.amount0())}();\n        if (d.amount1() < 0) {\n            pm.sync(key.currency1);\n            IERC20(Currency.unwrap(key.currency1)).transfer(address(pm), uint128(-d.amount1()));\n            pm.settle();\n        }\n        return \"\";\n    }\n    receive() external payable {}\n}\n\ncontract BurnImdLoopTest is Test {\n    address constant OWNER = address(0xA11CE);\n    address constant TL = address(0x71E10C);\n    uint256 constant SUPPLY = 1_000_000_000 ether;\n    uint256 constant BUY = 2.81 ether;\n\n    IPoolManager PM;\n    IMD6900RobinhoodLaunch L;\n    IMD6900LockedPool pool;\n    MockErc20 imdstr;\n    Trader trader;\n    address imd;\n\n    function setUp() public {\n        PM = IPoolManager(vm.deployCode(\"PoolManager.sol:PoolManager\", abi.encode(address(this))));\n        imdstr = new MockErc20();\n        L = IMD6900RobinhoodLaunch(payable(vm.deployCode(\"IMD6900RobinhoodLaunch.sol:IMD6900RobinhoodLaunch\", abi.encode(OWNER, PM, address(imdstr), TL))));\n        L.createHook(_hookSalt());\n        pool = L.pool();\n        trader = new Trader(PM);\n        vm.deal(address(trader), 300 ether);\n        imd = L.IMD();\n        vm.etch(imd, address(new MockErc20()).code);\n    }\n\n    function _launch() internal {\n        bytes32 salt = _coinSalt();\n        address expected = L.tokenAddress(salt);\n        vm.deal(OWNER, BUY);\n        vm.prank(OWNER);\n        L.launch{value: BUY}(salt, expected, uint160(_sqrt(FullMath.mulDiv(SUPPLY, 1 << 192, 19.18 ether))), 0, address(L));\n    }\n\n    function _imdPool(int24 lo, int24 hi, uint256 liq) internal returns (PoolKey memory ik) {\n        BandAdder adder = new BandAdder(PM);\n        vm.deal(address(adder), 100 ether);\n        MockErc20(imd).mint(address(adder), 1e30);\n        ik = L.imdPoolKey();\n        PM.initialize(ik, uint160(_sqrt(261 << 192))); // ~261 $IMD per ETH, tick ~55690\n        adder.add(ik, lo, hi, liq);\n    }\n\n    /// Finding: IMD_BUY_MAX bounds one call, not one transaction: the bucket drains in a loop\n    function test_WholeBucketSpentInOneTransaction() public {\n        _imdPool(TickMath.minUsableTick(100), TickMath.maxUsableTick(100), 1e21);\n        _launch();\n        trader.swap(pool.poolKey(), true, -150 ether); // 1.5 ETH of fees -> bucket ~0.15 ETH... make it bigger\n        trader.swap(pool.poolKey(), false, -int256(IERC20(L.token()).balanceOf(address(trader))));\n        L.harvest();\n        uint256 bucket = L.imdBucket();\n        emit log_named_uint(\"bucket\", bucket);\n        assertGt(bucket, 0.2 ether);\n        // the same transaction calls burnImd again and again (a sandwicher's transaction would, around its own\n        // price move on the $IMD pool); a fix may refuse or no-op the repeats, so they are tolerated here\n        L.burnImd(0);\n        for (uint256 i; i < 10; ++i) {\n            try L.burnImd(0) {} catch {}\n        }\n        emit log_named_uint(\"spent in one tx\", bucket - L.imdBucket());\n        // expected: one transaction spends at most IMD_BUY_MAX of the bucket, as the constant's comment promises\n        assertLe(bucket - L.imdBucket(), L.IMD_BUY_MAX(), \"more than IMD_BUY_MAX of the bucket spent in one transaction\");\n    }\n\n    function _hookSalt() internal view returns (bytes32 s) {\n        bytes32 initHash = L.hookInitCodeHash();\n        uint160 flags = L.FLAGS();\n        for (uint256 i;; ++i) {\n            uint160 a = _create2(address(L), i, initHash);\n            if (a & Hooks.ALL_HOOK_MASK == flags && a >> 152 != 0x91) return bytes32(i);\n        }\n    }\n    function _coinSalt() internal view returns (bytes32 s) {\n        bytes32 initHash = L.tokenInitCodeHash();\n        uint160 vanity = L.VANITY();\n        for (uint256 i;; ++i) {\n            if (_create2(address(L), i, initHash) >> 144 == vanity) return bytes32(i);\n        }\n    }\n    function _create2(address deployer, uint256 salt, bytes32 initHash) internal pure returns (uint160 a) {\n        assembly (\"memory-safe\") {\n            let p := mload(0x40)\n            mstore(p, or(shl(248, 0xff), shl(88, deployer)))\n            mstore(add(p, 0x15), salt)\n            mstore(add(p, 0x35), initHash)\n            a := and(keccak256(p, 0x55), 0xffffffffffffffffffffffffffffffffffffffff)\n        }\n    }\n    function _sqrt(uint256 x) internal pure returns (uint256 y) {\n        if (x == 0) return 0;\n        y = x;\n        uint256 z = (x + 1) / 2;\n        while (z < y) (y, z) = (z, (x / z + z) / 2);\n    }\n}","reproduction":"test/scratch/BurnImdLoop.t.sol (forge test --match-path test/scratch/BurnImdLoop.t.sol): local PoolManager, launch at 19.18 ETH with a 2.81 ETH opening buy, a full-range stand-in $IMD pool at the default key (fee 10_000, spacing 100), 150 ETH of trading harvested so imdBucket = 0.678476 ETH. One transaction calls burnImd(0) eleven times (repeats wrapped in try/catch so a refusing fix passes). Expected: at most IMD_BUY_MAX = 0.1 ETH of the bucket spent in one transaction. Actual: 0.678476 ETH spent, imdBucket == 0 (assertion '678476400000000000 > 100000000000000000'). With the attacker's price push around it, all of it is bought at the pushed price; profit is positive whenever the $IMD pool's active depth is below roughly 50x the bucket.","severity":"medium","snippet":"        uint256 spend = imdBucket < IMD_BUY_MAX ? imdBucket : IMD_BUY_MAX;","title":"burnImd's IMD_BUY_MAX bounds one call, not one transaction: the whole $IMD bucket can be bought at a manipulated price in a single sandwiched transaction"},{"citation":"resolved","description":"The $IMD buy (unlockCallback, what == 1, line 549) is an exact-input swap of `spend` with sqrtPriceLimitX96 = MIN_SQRT_PRICE + 1. When the $IMD pool's liquidity runs out below the price (a thin, banded or repointed pool, setImdPool line 436), v4 stops consuming input and charges only ethIn < spend (line 550; only ethIn is settled at line 552). imdBucket was already reduced by the whole spend, so the difference stays in the launch's balance booked to nobody, and the next split() (line 467: balance - imdBucket - owedTotal) divides it among the payees: with the defaults 90% to POT_BRIDGE and 10% back into the bucket. ETH set aside for $IMD's burn is re-routed to the pot. Reported by audit_economics and audit_math (same root cause, merged). The same pattern exists for the opening buy (launchEth = spend at line 305) but is unreachable there with 1B coins of liquidity. Fix: have the callback return ethIn as well and do `imdBucket += spend - ethIn` (or debit by ethIn) after the unlock.","line":500,"path":"src/IMD6900RobinhoodLaunch.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {Hooks} from \"@uniswap/v4-core/src/libraries/Hooks.sol\";\nimport {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"@uniswap/v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"@uniswap/v4-core/src/types/BalanceDelta.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"@uniswap/v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"@uniswap/v4-core/src/libraries/TickMath.sol\";\nimport {FullMath} from \"@uniswap/v4-core/src/libraries/FullMath.sol\";\nimport {IMD6900RobinhoodLaunch} from \"src/IMD6900RobinhoodLaunch.sol\";\nimport {IMD6900LockedPool} from \"src/IMD6900LockedPool.sol\";\n\ninterface IERC20 {\n    function balanceOf(address) external view returns (uint256);\n    function transfer(address, uint256) external returns (bool);\n}\n\ncontract MockErc20 {\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n    function mint(address to, uint256 a) external { balanceOf[to] += a; }\n    function approve(address s, uint256 a) external returns (bool) { allowance[msg.sender][s] = a; return true; }\n    function transfer(address to, uint256 a) external returns (bool) { balanceOf[msg.sender] -= a; balanceOf[to] += a; return true; }\n    function transferFrom(address f, address to, uint256 a) external returns (bool) {\n        allowance[f][msg.sender] -= a; balanceOf[f] -= a; balanceOf[to] += a; return true;\n    }\n}\n\ncontract Trader {\n    IPoolManager immutable pm;\n    PoolKey key;\n    constructor(IPoolManager pm_) { pm = pm_; }\n    function swap(PoolKey calldata k, bool buy, int256 amountSpecified) external payable returns (BalanceDelta d) {\n        key = k;\n        d = abi.decode(pm.unlock(abi.encode(buy, amountSpecified)), (BalanceDelta));\n    }\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        (bool buy, int256 amt) = abi.decode(data, (bool, int256));\n        BalanceDelta d = pm.swap(key, SwapParams(buy, amt, buy ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1), \"\");\n        _settle(key.currency0, d.amount0());\n        _settle(key.currency1, d.amount1());\n        return abi.encode(d);\n    }\n    function _settle(Currency c, int128 v) internal {\n        if (v < 0) {\n            if (c.isAddressZero()) pm.settle{value: uint128(-v)}();\n            else { pm.sync(c); IERC20(Currency.unwrap(c)).transfer(address(pm), uint128(-v)); pm.settle(); }\n        } else if (v > 0) pm.take(c, address(this), uint128(v));\n    }\n    receive() external payable {}\n}\n\n/// Puts liquidity in a chosen band of a plain pool\ncontract BandAdder {\n    IPoolManager immutable pm; PoolKey key; int24 lo; int24 hi; uint256 liq;\n    constructor(IPoolManager pm_) { pm = pm_; }\n    function add(PoolKey calldata k, int24 lo_, int24 hi_, uint256 l) external { (key, lo, hi, liq) = (k, lo_, hi_, l); pm.unlock(\"\"); }\n    function unlockCallback(bytes calldata) external returns (bytes memory) {\n        (BalanceDelta d,) = pm.modifyLiquidity(key, ModifyLiquidityParams(lo, hi, int256(liq), 0), \"\");\n        if (d.amount0() < 0) pm.settle{value: uint128(-d.amount0())}();\n        if (d.amount1() < 0) {\n            pm.sync(key.currency1);\n            IERC20(Currency.unwrap(key.currency1)).transfer(address(pm), uint128(-d.amount1()));\n            pm.settle();\n        }\n        return \"\";\n    }\n    receive() external payable {}\n}\n\ncontract BurnImdUnderfillTest is Test {\n    address constant OWNER = address(0xA11CE);\n    address constant TL = address(0x71E10C);\n    uint256 constant SUPPLY = 1_000_000_000 ether;\n    uint256 constant BUY = 2.81 ether;\n\n    IPoolManager PM;\n    IMD6900RobinhoodLaunch L;\n    IMD6900LockedPool pool;\n    MockErc20 imdstr;\n    Trader trader;\n    address imd;\n\n    function setUp() public {\n        PM = IPoolManager(vm.deployCode(\"PoolManager.sol:PoolManager\", abi.encode(address(this))));\n        imdstr = new MockErc20();\n        L = IMD6900RobinhoodLaunch(payable(vm.deployCode(\"IMD6900RobinhoodLaunch.sol:IMD6900RobinhoodLaunch\", abi.encode(OWNER, PM, address(imdstr), TL))));\n        L.createHook(_hookSalt());\n        pool = L.pool();\n        trader = new Trader(PM);\n        vm.deal(address(trader), 300 ether);\n        imd = L.IMD();\n        vm.etch(imd, address(new MockErc20()).code);\n    }\n\n    function _launch() internal {\n        bytes32 salt = _coinSalt();\n        address expected = L.tokenAddress(salt);\n        vm.deal(OWNER, BUY);\n        vm.prank(OWNER);\n        L.launch{value: BUY}(salt, expected, uint160(_sqrt(FullMath.mulDiv(SUPPLY, 1 << 192, 19.18 ether))), 0, address(L));\n    }\n\n    function _imdPool(int24 lo, int24 hi, uint256 liq) internal returns (PoolKey memory ik) {\n        BandAdder adder = new BandAdder(PM);\n        vm.deal(address(adder), 100 ether);\n        MockErc20(imd).mint(address(adder), 1e30);\n        ik = L.imdPoolKey();\n        PM.initialize(ik, uint160(_sqrt(261 << 192))); // ~261 $IMD per ETH, tick ~55690\n        adder.add(ik, lo, hi, liq);\n    }\n\n    /// Finding: the ETH the $IMD pool could not take is debited from imdBucket anyway and re-split next time\n    function test_UnderfilledBuyLeavesTheBucketShort() public {\n        _imdPool(55600, 55800, 5e19); // liquidity only just below the price: exhausted after a few hundredths of an ETH\n        _launch();\n        trader.swap(pool.poolKey(), true, -150 ether);\n        L.harvest();\n        uint256 bucket = L.imdBucket();\n        assertGt(bucket, 0.1 ether);\n        uint256 balBefore = address(L).balance;\n        L.burnImd(0);\n        uint256 ethIn = balBefore - address(L).balance;\n        emit log_named_uint(\"ethIn actually paid\", ethIn);\n        emit log_named_uint(\"bucket debited by\", bucket - L.imdBucket());\n        assertLt(ethIn, 0.1 ether, \"the pool took less than the cap\");\n        // the unspent remainder is no longer booked to anyone: the next split divides it (90% to the pot bridge)\n        uint256 unbooked = address(L).balance - L.imdBucket() - L.owedTotal();\n        emit log_named_uint(\"unbooked ETH\", unbooked);\n        // expected: imdBucket == bucket - ethIn (the unspent ETH is still $IMD's share)\n        assertEq(L.imdBucket(), bucket - ethIn, \"the bucket should only lose what was actually spent\");\n    }\n\n    function _hookSalt() internal view returns (bytes32 s) {\n        bytes32 initHash = L.hookInitCodeHash();\n        uint160 flags = L.FLAGS();\n        for (uint256 i;; ++i) {\n            uint160 a = _create2(address(L), i, initHash);\n            if (a & Hooks.ALL_HOOK_MASK == flags && a >> 152 != 0x91) return bytes32(i);\n        }\n    }\n    function _coinSalt() internal view returns (bytes32 s) {\n        bytes32 initHash = L.tokenInitCodeHash();\n        uint160 vanity = L.VANITY();\n        for (uint256 i;; ++i) {\n            if (_create2(address(L), i, initHash) >> 144 == vanity) return bytes32(i);\n        }\n    }\n    function _create2(address deployer, uint256 salt, bytes32 initHash) internal pure returns (uint160 a) {\n        assembly (\"memory-safe\") {\n            let p := mload(0x40)\n            mstore(p, or(shl(248, 0xff), shl(88, deployer)))\n            mstore(add(p, 0x15), salt)\n            mstore(add(p, 0x35), initHash)\n            a := and(keccak256(p, 0x55), 0xffffffffffffffffffffffffffffffffffffffff)\n        }\n    }\n    function _sqrt(uint256 x) internal pure returns (uint256 y) {\n        if (x == 0) return 0;\n        y = x;\n        uint256 z = (x + 1) / 2;\n        while (z < y) (y, z) = (z, (x / z + z) / 2);\n    }\n}","reproduction":"test/scratch/BurnImdUnderfill.t.sol: the stand-in $IMD pool holds liquidity only in the band [55600, 55800) around ~261 $IMD/ETH; launch, 150 ETH of trading harvested so imdBucket = 0.678476 ETH; burnImd(0). The pool took ethIn = 0.007509 ETH (measured 7509328044963723 wei); imdBucket fell by the full 0.1 ETH to 0.578476 ETH; 0.092491 ETH sits in the launch's balance unbooked (balance - imdBucket - owedTotal) and the next split() sends 90% of it to POT_BRIDGE. Expected: imdBucket == bucket - ethIn == 0.670967 ETH. Actual: 0.578476 ETH (assertion '578476400000000000 != 670967071955036277').","severity":"low","snippet":"        imdBucket -= spend;","title":"burnImd debits imdBucket by the full spend even when the $IMD pool absorbs less: the unspent ETH is re-split to the other payees (90% to the pot) at the next split"},{"citation":"resolved","description":"claim() enforces only the per-caller ceiling from the leaf (`done > allowance`). The aggregate `claimedTotal` is never checked against `claimTotal`, the reserve launch() kept back and the opening buy had to cover (line 297). setClaims() (line 256) validates only `(root == 0) == (total == 0)`, so a root whose leaves sum above claimTotal is accepted. Then (1) claims pay from the whole coin balance, i.e. out of excessHeld, the coins the timelock alone may sell back (sellHeld) or release (releaseExcess); (2) once claimedTotal > claimTotal, `claimTotal - claimedTotal` reverts under checked arithmetic in claimReserve() (line 246, read by script/launch.sh recover), releaseUnclaimed() (line 314) and endClaims() (line 395): the timelock can never end the claims and releaseExcess reverts for what was already claimed. Reachable only through the owner's setClaims input (no attacker path), hence low. Reported by audit_permissions; its proof fails on this tree as stated. Fix: `if (claimedTotal + amount > claimTotal) revert OverClaim();` in claim().","line":453,"path":"src/IMD6900RobinhoodLaunch.sol","proof":"// test/scratch/ClaimTotalBound.t.sol - run: forge test --match-path test/scratch/ClaimTotalBound.t.sol\n// Fails on the tree as it is now ('next call did not revert as expected', the over-draw claim succeeds)\n// and passes once claim() refuses claimedTotal + amount > claimTotal.\n// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {Hooks} from \"@uniswap/v4-core/src/libraries/Hooks.sol\";\nimport {FullMath} from \"@uniswap/v4-core/src/libraries/FullMath.sol\";\nimport {IMD6900RobinhoodLaunch} from \"src/IMD6900RobinhoodLaunch.sol\";\n\n/// Proof for finding 3 of .imd-findings.json: claim() must not let a claim draw more than the declared\n/// claimTotal. The owner declares a total below the sum of the Merkle leaves (here: one leaf of 40M coins,\n/// total 20M); today the claim passes, draining the coins launch() kept for the timelock. With the fix\n/// (claim() refusing once claimedTotal + amount > claimTotal) the call reverts and this test passes.\n/// Fails on the tree as it is now with \"call did not revert as expected\".\ninterface IERC20Like {\n    function balanceOf(address) external view returns (uint256);\n    function approve(address, uint256) external returns (bool);\n}\n\n/// The old IMDSTR, as far as claims see it\ncontract MockImdstrScratch {\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n    function mint(address to, uint256 a) external { balanceOf[to] += a; }\n    function approve(address s, uint256 a) external returns (bool) { allowance[msg.sender][s] = a; return true; }\n    function transfer(address to, uint256 a) external returns (bool) { balanceOf[msg.sender] -= a; balanceOf[to] += a; return true; }\n    function transferFrom(address f, address to, uint256 a) external returns (bool) {\n        allowance[f][msg.sender] -= a; balanceOf[f] -= a; balanceOf[to] += a; return true;\n    }\n}\n\ncontract ClaimTotalBoundTest is Test {\n    address constant OWNER = address(0xA11CE);\n    address constant TL = address(0x71E10C);\n    address constant A = address(0xA);\n    uint256 constant AA = 40_000_000 ether;\n    /// One third of A's leaf: the owner's under-declared reserve\n    uint256 constant DECLARED_TOTAL = 20_000_000 ether;\n    uint256 constant BUY = 2.81 ether;\n\n    IMD6900RobinhoodLaunch L;\n    MockImdstrScratch imdstr;\n\n    function setUp() public {\n        address pm = vm.deployCode(\"PoolManager.sol:PoolManager\", abi.encode(address(this)));\n        imdstr = new MockImdstrScratch();\n        L = IMD6900RobinhoodLaunch(\n            payable(vm.deployCode(\"IMD6900RobinhoodLaunch.sol:IMD6900RobinhoodLaunch\", abi.encode(OWNER, pm, address(imdstr), TL)))\n        );\n        L.createHook(_hookSalt());\n    }\n\n    function test_ClaimsCannotExceedTheDeclaredClaimTotal() public {\n        imdstr.mint(A, AA);\n        bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(A, AA))));\n        vm.prank(OWNER);\n        L.setClaims(leaf, DECLARED_TOTAL, block.timestamp + 100 days);\n\n        bytes32 salt = _coinSalt();\n        address expected = L.tokenAddress(salt); // read before the prank: a view call consumes it\n        vm.deal(OWNER, BUY);\n        vm.prank(OWNER);\n        L.launch{value: BUY}(salt, expected, uint160(_sqrt(FullMath.mulDiv(1_000_000_000 ether, 1 << 192, 19.18 ether))), 0, address(L));\n        assertGt(L.launchBought(), DECLARED_TOTAL, \"the opening buy covered the declared total\");\n\n        vm.prank(A);\n        imdstr.approve(address(L), AA);\n        bytes32[] memory proof = new bytes32[](0); // the single leaf is the root\n\n        vm.prank(A);\n        vm.expectRevert(); // no claim may draw past the declared claimTotal\n        L.claim(AA, AA, proof);\n    }\n\n    /*                                HELPERS                                */\n\n    function _hookSalt() internal view returns (bytes32 s) {\n        bytes32 initHash = L.hookInitCodeHash();\n        uint160 flags = L.FLAGS();\n        for (uint256 i;; ++i) {\n            uint160 a = _create2(address(L), i, initHash);\n            if (a & Hooks.ALL_HOOK_MASK == flags && a >> 152 != 0x91) return bytes32(i);\n        }\n    }\n\n    function _coinSalt() internal view returns (bytes32 s) {\n        bytes32 initHash = L.tokenInitCodeHash();\n        uint160 vanity = L.VANITY();\n        for (uint256 i;; ++i) {\n            if (_create2(address(L), i, initHash) >> 144 == vanity) return bytes32(i);\n        }\n    }\n\n    function _create2(address deployer, uint256 salt, bytes32 initHash) internal pure returns (uint160 a) {\n        assembly (\"memory-safe\") {\n            let p := mload(0x40)\n            mstore(p, or(shl(248, 0xff), shl(88, deployer)))\n            mstore(add(p, 0x15), salt)\n            mstore(add(p, 0x35), initHash)\n            a := and(keccak256(p, 0x55), 0xffffffffffffffffffffffffffffffffffffffff)\n        }\n    }\n\n    function _sqrt(uint256 x) internal pure returns (uint256 y) {\n        if (x == 0) return 0;\n        y = x;\n        uint256 z = (x + 1) / 2;\n        while (z < y) (y, z) = (z, (x / z + z) / 2);\n    }\n}","reproduction":"test/scratch/ClaimTotalBound.t.sol (the specialist's proof, run here: FAIL 'next call did not revert as expected'): deploy IMD6900RobinhoodLaunch(OWNER, PoolManager, imdstr, TL), createHook; one leaf (A, 40_000_000e18) as the root; owner setClaims(root, 20_000_000e18, now + 100 days) (half the leaf); launch with 2.81 ETH at 19.18 ETH, excessTo = the launch (bought ~122.8M > 20M, so launch() accepts). A approves IMDSTR and calls claim(40_000_000e18, 40_000_000e18, []). Expected: revert (claims may not draw past claimTotal). Actual: it pays 40M coins, 20M of them from excessHeld; claimedTotal = 40M > claimTotal = 20M, and claimReserve(), releaseUnclaimed(to) and endClaims() (from TL) all revert with an arithmetic underflow from then on.","severity":"low","snippet":"        if (done > allowance) revert OverClaim();","title":"claim() never bounds claimedTotal by claimTotal: a Merkle root whose leaves sum above the declared reserve pays claims out of the timelock's held coins and then bricks claimReserve()/releaseUnclaimed("},{"citation":"resolved","description":"The launch inherits solady Ownable and never overrides renounceOwnership(), so the team wallet can set the owner to zero. Before the launch every onlyOwner path dies: setClaims(), withdrawEth() and launch() itself, so the coin can never be launched and the opening-buy ETH sent by hand can only leave through the timelock's recoverEth(). After the launch releaseUnclaimed() and releaseImdstr() die (the unclaimed reserve stays reachable through the timelock's endClaims()+excessHeld). The two sibling contracts guard exactly this: IMD6900Hook.renounceOwnership() reverts NoRenounce (src/IMD6900Hook.sol:164) and PerpEngine.renounceOwnership() reverts OwnershipCannotBeRenounced (src/perps/PerpEngine.sol:289). Impact is bounded (the owner already holds the power not to launch) hence low; one override closes it. Reported by audit_permissions; reproduced by reading solady's Ownable (renounceOwnership is public payable virtual, onlyOwner) and the absence of an override here.","line":44,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"Deploy IMD6900RobinhoodLaunch(OWNER, PM, imdstr, TL); `vm.prank(OWNER); L.renounceOwnership();`. Expected (as the hook and engine do): revert. Actual: succeeds, L.owner() == address(0); every later `vm.prank(OWNER); L.launch(...)` / `L.setClaims(...)` / `L.withdrawEth(...)` reverts with Ownable.Unauthorized. On the same tree `hook.renounceOwnership()` reverts NoRenounce and `engine.renounceOwnership()` reverts OwnershipCannotBeRenounced.","severity":"low","snippet":"contract IMD6900RobinhoodLaunch is Ownable, ReentrancyGuard {","title":"IMD6900RobinhoodLaunch does not guard renounceOwnership (the hook and the engine do): the owner can permanently disable launch()/setClaims()/withdrawEth() and, after the launch, releaseUnclaimed()/rel"},{"citation":"resolved","description":"The pool's ETH fees go through split(), which pays each payee with a tolerant call and books a refused share as owed (lines 479-489), so a paused or upgraded pot bridge never blocks a harvest. The perps' fee share takes another path: PerpEngine.flushFees (src/perps/PerpEngine.sol:967) sends feesOwed to addPerpFees, which forwards with SafeTransferLib.safeTransferETH, a reverting send. If perpFeeTo (POT_BRIDGE by default) refuses plain ETH, addPerpFees reverts, flushFees reverts, feesOwed keeps growing inside the engine and nobody can drain it until the timelock changes perpFeeTo (setPerpFeeTo) or the engine's feeSink. Nothing is lost, hence low, but the launch's own rule that a refusing payee must never block fee collection is not applied to the only fee path an unprivileged caller cannot otherwise clear. Reported by audit_flow; reproduced by tracing flushFees -> addPerpFees -> safeTransferETH (reverts ETHTransferFailed on a failed call). Fix: book a refused amount as owed to perpFeeTo (as split does) or let the engine keep feesOwed on failure instead of reverting.","line":214,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"State: launch() and createPerps() done, perpFeeTo == POT_BRIDGE. Make the destination refuse ETH: `vm.prank(TL); L.setPerpFeeTo(address(refuser))` where refuser's receive() reverts (the default POT_BRIDGE behaves the same the day it is paused or replaced by a contract without a payable fallback). ALICE calls engine.openLong{value: 0.1 ether}(2, 0): engine.feesOwed() == 0.1e18 * 690/10000 * 70/100 == 4.83e15. Anyone calls engine.flushFees(). Expected (per split's convention): the share booked and retried later. Actual: addPerpFees -> safeTransferETH reverts ETHTransferFailed(), flushFees reverts, feesOwed stays 4.83e15 and every later flushFees reverts the same way.","severity":"low","snippet":"        SafeTransferLib.safeTransferETH(to, msg.value);","title":"addPerpFees hard-pushes ETH to perpFeeTo with a reverting send: a refusing pot bridge makes every PerpEngine.flushFees revert and strands the perps' 70% fee share until the timelock repoints it"},{"citation":"resolved","description":"The ERC-4626-style offset prices the first deposit against `assetsEth() + 1` with OFFSET = 1e6 virtual shares. That is safe when the vault starts empty, but `engine.totalEth()` (plv + longOiEth) can be non-zero before anyone holds shares: the owner's PerpEngine.fundPlv (src/perps/PerpEngine.sol:1003, documented as a permanent donation to the PLV) and the 20% open-fee/penalty share `_routeFee` books into plv (line 923) as soon as the vault address is set. Everything in plv at that moment is attributed to the virtual shares, and since yield is distributed pro-rata to (real + virtual) shares the virtual block keeps taking its proportion of all future long-side yield for the life of the vault; no path pays the virtual shares, so that yield simply disappears. The token side has the same seam (depositToken line 625 with fundPlvToken, PerpEngine.sol:1009). Reported by audit_math; reproduced by the share arithmetic below. Fix: price the first deposit against the assets the vault itself funded (track a seeded base), mint the seed its own shares to a burn/treasury address, or document that fundPlv must only follow the first community deposit.","line":274,"path":"src/perps/PerpVault.sol","reproduction":"After launch and createPerps (as test/Perps.t.sol): 1. TL (engine owner): engine.fundPlv{value: 1 ether}() so plv = 1e18, ethShares = 0. 2. STAKER: vault.depositEth{value: 1 ether}(): shares = 1e18 * (0 + 1e6) / (1e18 + 1) = 999_999; redeemable = 999_999 * (2e18 + 1) / 1_999_999 = 0.9999995 ETH while engine.totalEth() = 2 ETH: the seeded 1 ETH is the virtual shares'. 3. ALICE: engine.openLong{value: 0.3 ether}(2, 0): _routeFee books 0.3e18 * 690/10000 * 20% = 4.14e15 into plv. 4. STAKER's redeemable rises to 1.0020695 ETH: 2.07e15 of the 4.14e15 yield; the other half accrued to the 1e6 virtual shares. Expected: the only share holder receives the whole LP yield share. Actual: 50% (seed / (seed + first deposits)) of every future yield is attributed to virtual shares nobody can redeem.","severity":"low","snippet":"        shares = FullMath.mulDiv(amount, ethShares + OFFSET, assetsEth() + 1);","title":"PerpVault: ETH (or token) already in the PLV before the first deposit belongs to the 1e6 virtual shares, and so does their pro-rata share of all later yield, which nobody can ever redeem"},{"citation":"resolved","description":"PerpEngine binds its poolId to the fee at creation (immutable poolFee, src/perps/PerpEngine.sol:275). lowerFee moves the liquidity to a new pool and the hook's poolId follows it (src/IMD6900Hook.sol:180), so the wired engine's swaps revert NotOurPool from then on: opens fail closed as documented, stakers can still withdraw (withdrawPlvTo/withdrawPlvTokenTo need no swap). But createPerps refuses a second run, so the guarantee that the live perps come from exactly ENGINE_CODE_HASH / VAULT_CODE_HASH holds only until the first fee move; afterwards a replacement engine must be deployed outside the locked pool and wired by the timelock through setPerpEngine (src/IMD6900Hook.sol:130), which verifies no code hash and leaves perpRefunded = true, so a non-refunding replacement would be reported fee-free by perpFeeBps. Consistent with the documented 'fails closed until a new one is wired'; recorded as a trust assumption, not a defect. Reported by audit_flow; reproduced by tracing.","line":226,"path":"src/IMD6900LockedPool.sol","reproduction":"launch(); pool.createPerps(engineCode, vaultCode); TL calls pool.lowerFee(33_300) with openCount() == 0 (succeeds). ALICE calls engine.openLong{value: 0.1 ether}(5, 0): _openPrologue passes (hook.perpEngine() == engine) but the swap on the old PoolKey reverts in IMD6900Hook._beforeSwap with NotOurPool. pool.createPerps(engineCode, vaultCode) reverts PerpsExist. The only way to perps again is an externally deployed PerpEngine plus the timelock's setPerpEngine.","severity":"info","snippet":"        if (perpsEngine != address(0)) revert PerpsExist();","title":"After lowerFee the audited-by-hash perps cannot be recreated: createPerps is one-shot, so the only replacement is a hand-deployed engine wired by the timelock through IMD6900Hook.setPerpEngine, which "},{"citation":"resolved","description":"ethDepth(L, sp) = L * (SQRT_MAX - sp) / SQRT_MAX * Q96 / sp is the amount0 a position from the current price up to the max tick would hold (the constant-product virtual reserve). The pool's only position ends at upperTick (the opening price b), so the ETH longs can actually sell into is L * (b - p) * Q96 / (p * b) = depth * (b - p) / b. At the state the launch leaves (2.81 ETH buy at 19.18 ETH FDV, p ~ 0.877 b) that is ~12.3% of activeEthDepth(): ~2.69 ETH real against ~21.9 ETH virtual. The projection and exitInsolvent stay exact while the price is inside the range, and the supply identity (reserves + coins outside + burned = SUPPLY) means the price cannot pass b while any coin is outside the pool, so no bad debt follows. But maxNotionalBps (3.5% of depth = 0.77 ETH) is ~29% of the pool's real ETH, maxOiBps 5% is ~41%, and maxLiqBps 20% exceeds the real ETH, so the per-timestamp liquidation throttle never binds. For the author to decide whether the caps should use the real in-range ETH. Reported by audit_math; reproduced by the formulas.","line":345,"path":"src/perps/PerpEngine.sol","reproduction":"After launch (BUY = 2.81 ETH, 19.18 ETH virtual depth) call engine.activeEthDepth(): ~21.9e18. Compute the real ETH as L * (b - p) * 2^96 / (p * b) with (p,,L) = pool.state() and b = TickMath.getSqrtPriceAtTick(pool.upperTick()): ~2.69e18, ratio ~0.123. A long at maxNotionalBps (0.77 ETH notional) is therefore ~29% of the ETH available to all sellers, not 3.5%.","severity":"info","snippet":"        return PerpSwapLib.ethDepth(_liq(), sp);","title":"PerpEngine sizes every cap against the virtual full-range ETH reserve (activeEthDepth), about 8x the ETH the [minTick, upperTick] position actually holds at the opening state"},{"citation":"resolved","description":"Documented, intended privileges, recorded as the trust model rather than defects. The Robinhood timelock (constructor argument 4, a contract) holds with no delay enforced here: recoverEth (all ETH, including what is booked to payees in `owed` and set aside in `imdBucket`: it cancels those bookings, so a payee's owed is not an on-chain claim); releaseExcess/sellHeld/endClaims (all coins held back, to any address, and the unclaimed reserve early while claims are open); setImdBurn/setImdPool/setPerpFeeTo/setCoinFeeTo and PerpEngine.setFeeSink (landing addresses of value already accrued but not yet paid: feesOwed follows perpFeeTo at flush time, collected coin fees follow coinFeeTo); setPayees (the whole split); as the hook's owner setPerpEngine/setPerpRefunded/setSweepFailOpen, where sweepFailOpen = true lets any swap skip the pre-trade sweep, suspending 'no bad debt outranks always-tradeable'; and as the engine's owner every risk lever, skimInsurance above the live need and setVault while nobody has shares. All matches the brief and README; the guarantee is the timelock's conduct, not contract-enforced. Reported by audit_permissions; verified by reading each guard.","line":158,"path":"src/IMD6900Hook.sol","reproduction":"`vm.prank(TL); L.setPerpFeeTo(attacker)` makes the next engine.flushFees pay the already-accrued feesOwed to the new address; `vm.prank(TL); hook.setSweepFailOpen(true)` makes the next trader.swap(k, false, -coins) proceed when the engine's sweep cannot run, leaving positions liquidatable at a worse price with the shortfall on the vault. Expected per the README: a public 24h delay before either takes effect (external). Actual: both take effect in the next transaction; nothing in these contracts marks them pending.","severity":"info","snippet":"    function setSweepFailOpen(bool on) external onlyOwner {","title":"Trust note: the timelock's powers are un-delayed in these contracts (the 24h lives in the external timelock): it takes all ETH and all held coins at any time, redirects every in-flight fee destination"}],"hash":"d39d9a8b0eaeeb0d171e77adc48d9b0659b138edd036b21c0ea311899c201acd","nodeId":"d197a7e6-a4d0-4062-b0c7-5113a3c8a362","outcome":"completed","summary":"The review is complete and written to `.imd-findings.json` in the repository root. No source files were changed; only the findings file and my scratch tests under `test/scratch/` exist.\n\n**Outcome.** Nine findings kept, none critical or high. Every specialist finding reproduced against the code, so none were dropped. The two burnImd under-fill reports from the economics and math areas were the same root cause and are merged into one. The full existing suite passes (80 tests, fork test skipped as designed), and the build is clean.\n\n**Findings, by severity:**\n1. **Medium.** burnImd's 0.1 ETH cap bounds one call, not one transaction. My proof drains a 0.678 ETH bucket in a single transaction with eleven calls, so a sandwicher exposes the whole $IMD share, not the capped amount the code comment promises.\n2. **Low.** burnImd debits the bucket by the full spend even when the $IMD pool takes less. Measured on a banded stand-in pool: the pool took 0.0075 ETH, the bucket lost 0.1 ETH, and the next split sends 90% of the difference to the pot bridge.\n3. **Low.** claim() has no aggregate bound against claimTotal. The specialist's proof fails on this tree as stated: an under-declared total lets claims draw from the timelock's held coins and then bricks claimReserve, releaseUnclaimed and endClaims by underflow.\n4. **Low.** The launch does not override renounceOwnership, unlike the hook and the engine.\n5. **Low.** addPerpFees forwards with a reverting send, so a refusing destination makes every flushFees revert.\n6. **Low.** PerpVault attributes any PLV seeded before the first deposit to the 1e6 virtual shares, and with it a permanent share of all future yield.\n7. **Info.** Perps cannot be recreated from the pinned code after lowerFee.\n8. **Info.** Engine caps are sized against the virtual full-range depth, about 8x the real in-range ETH.\n9. **Info.** Trust note on the timelock's un-delayed powers, including sweepFailOpen.\n\n**Proofs.** Three findings carry a Foundry proof that fails on this code for the stated reason: the burnImd loop, the burnImd under-fill, and the claim bound.\n\n**Coverage.** All 100 listed entry points have a row. Seventeen point at a finding, the rest hold after tracing their guards, state changes and failure paths. My own pass beyond the specialists confirmed the fee refund pair, the sell-back loop, the lowerFee move arithmetic, the engine's ETH accounting identity, and the supply identity that keeps the price inside the position's range, and found nothing new to report.","treeHash":null,"usage":{"cachedInputTokens":2552991,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":55829,"runtime":"claude","turns":27,"wallClockMs":907779}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"5dd3af20ad7ac0c2","findings":[{"citation":"resolved","description":"Introduced by this round's fix of addPerpFees (the tolerant forward that books a refused amount as perpFeesOwed). addPerpFees is payable and callable by anyone at any time, including before the launch, and perpFeeTo may already refuse ETH then (the timelock's setPerpFeeTo works before the launch; the pot bridge itself would behave the same the day it refuses plain ETH). split() (line 509) and recoverEth (line 405) were taught about perpFeesOwed, but launch() spends `address(this).balance` whole (line 331) and withdrawEth(to, 0) takes `address(this).balance` whole (line 302): neither subtracts perpFeesOwed. So the ETH booked to perpFeeTo is spent in the opening buy (launchEth includes it, excess coins land in excessHeld for the timelock), while perpFeesOwed stays booked against an empty balance. From then on `amount = balance - imdBucket - owedTotal - perpFeesOwed` in split() underflows (Panic 0x11) and so does harvest(), until the pool's collected fees alone exceed perpFeesOwed; once they do, the next addPerpFees pays the perps' owed out of the pool's fees, i.e. out of the pot bridge's and $IMD's share, a second time. Nothing is lost outside the contract (the timelock's recoverEth clears perpFeesOwed), and the preconditions need a refusing perpFeeTo before the launch, hence low. Fix: in launch() `uint256 spend = address(this).balance - perpFeesOwed;` and the same cap in withdrawEth (or have addPerpFees revert NotLaunched while token == 0, since the engine cannot exist before launch()).","line":331,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"test/scratch/PrelaunchPerpOwed.t.sol (forge test --match-path test/scratch/PrelaunchPerpOwed.t.sol -vv): local PoolManager, IMD6900RobinhoodLaunch(OWNER, PM, imdstr, TL), createHook. TL: setPerpFeeTo(refuser) where refuser's receive() reverts. STRANGER: addPerpFees{value: 0.5 ether}() -> perpFeesOwed == 0.5e18 (booked, as intended). OWNER: launch{value: 2.81 ether}(salt, expected, sqrtPrice(19.18 ETH), 0, address(L)). Expected: launchEth == 2.81e18 and the 0.5 ETH still held for perpFeeTo. Actual: launchEth == 3.31e18 (assertion '3310000000000000000 != 2810000000000000000'), address(L).balance == 0, perpFeesOwed == 0.5e18; afterwards L.split() reverts (0 - 0 - 0 - 0.5e18 underflow) and L.harvest() reverts the same way (both measured: 'split ok: 0', 'harvest ok: 0').","severity":"low","snippet":"        uint256 spend = address(this).balance;","title":"launch() (and withdrawEth) spend ETH already booked as perpFeesOwed: a perps fee refused before the launch is spent in the opening buy, and split()/harvest() then revert until the pool's fees exceed i"},{"citation":"resolved","description":"Raised last round as low; the author disputes a code change and records in ADAPTATION.md (line 174) the operating rule 'fundPlv / fundPlvToken only after the first community deposit'. Re-checked: the arithmetic still holds exactly as reported (fundPlv 1 ETH then a 1 ETH first deposit -> 999,999 shares against 1,000,000 virtual, half of every later long-side yield unredeemable), but the only way to reach it is the engine owner's own fundPlv before anyone holds shares (the 20% fee share needs an open, which needs shares to lend against), it costs the funder (the timelock) and never a depositor, and src/perps is pinned by ENGINE_CODE_HASH / VAULT_CODE_HASH. The dispute holds: downgraded to an info trust note, no revision needed. The rule should stay in the timelock's runbook.","line":274,"path":"src/perps/PerpVault.sol","reproduction":"After launch and createPerps (as test/Perps.t.sol): TL engine.fundPlv{value: 1 ether}(); STAKER vault.depositEth{value: 1 ether}() returns 999_999 shares; ALICE engine.openLong{value: 0.3 ether}(2, 0) routes 4.14e15 to plv; STAKER's redeemable rises by 2.07e15 only. Expected under the operating rule (fundPlv after the first deposit): the whole 4.14e15 to the sole staker; actual when the rule is broken: half to the virtual shares. Not a defect while the rule is followed.","severity":"info","snippet":"        shares = FullMath.mulDiv(amount, ethShares + OFFSET, assetsEth() + 1);","title":"Settled (accepted as a documented operating rule): PerpVault prices ETH already in the PLV before the first deposit into the 1e6 virtual shares, whose share of later yield nobody can redeem"},{"citation":"resolved","description":"Three info notes from last round that the author answered as the documented trust model: (1) the Robinhood timelock takes effect in the next transaction here (recoverEth of all ETH including owed/imdBucket/perpFeesOwed, releaseExcess/sellHeld/endClaims, setPayees, setImdBurn/setImdPool/setPerpFeeTo/setCoinFeeTo, hook setPerpEngine/setPerpRefunded/setSweepFailOpen, every engine lever); the 24h delay is the external timelock's. (2) createPerps is one-shot, so after lowerFee the only replacement engine is one the timelock deploys by hand and wires with setPerpEngine, which checks no code hash and leaves perpRefunded as it was. (3) PerpEngine's maxNotionalBps/maxOiBps/maxLiqBps are fractions of activeEthDepth(), about 8x the ETH the [minTick, upperTick] position really holds at the opening state; no bad debt follows. Re-verified against this tree: each guard and path reads as before; perpFeesOwed now also follows setPerpFeeTo and is dropped by recoverEth, as the author notes. All match the brief ('the timelock's role'). Accepted as trust assumptions; nothing to revise.","line":158,"path":"src/IMD6900Hook.sol","reproduction":"`vm.prank(TL); L.setPerpFeeTo(x)` repoints feesOwed already accrued in the engine and perpFeesOwed to x at the next flush/forwarding; `vm.prank(TL); hook.setSweepFailOpen(true)` lets the next swap proceed when the engine's sweep fails; after `pool.lowerFee(33_300)` with openCount()==0, engine.openLong reverts NotOurPool and pool.createPerps reverts PerpsExist; engine.activeEthDepth() ~21.9e18 against ~2.69e18 real in-range ETH after a 2.81 ETH opening buy at 19.18 ETH. All as documented, not defects.","severity":"info","snippet":"    function setSweepFailOpen(bool on) external onlyOwner {","title":"Settled trust notes (no code change, recorded in ADAPTATION.md): the timelock's powers are un-delayed in these contracts; after lowerFee the hash-pinned perps cannot be recreated; the engine's caps ar"}],"hash":"f1d21d6855d46dedeb3cb4141eaa00b75342dddb3f0034ee8ecfae632706b8e9","nodeId":"d197a7e6-a4d0-4062-b0c7-5113a3c8a362","outcome":"completed","summary":"The review is done and written to `.imd-findings.json`. The tree is otherwise unchanged.\n\n**Settlement of last round's findings**\n\n- **burnImd per-transaction cap (medium): fixed.** The attached proof passes on this code. One buy per block timestamp, so a looping transaction spends at most 0.1 ETH.\n- **burnImd debited by the full spend (low): fixed.** The callback returns the ETH the pool took and the bucket is debited by that amount.\n- **claim() unbounded by claimTotal (low): fixed.** The specialist's proof passes. A claim past the reserve reverts OverClaim.\n- **renounceOwnership unguarded (low): fixed.** It now reverts NoRenounce like the hook and the engine.\n- **addPerpFees reverting send (low): fixed**, with one side effect reported below.\n- **PerpVault virtual shares, perps not recreatable after lowerFee, cap sizing, un-delayed timelock: disputes accepted.** The arithmetic and tracing still hold, but each is a documented trust assumption or operating rule rather than a defect. Recorded as two info entries so the record stays complete.\n\n**One new defect, introduced by the addPerpFees fix (low)**\n\nThe launch and withdrawEth still spend the whole balance, including ETH booked as perpFeesOwed. If perpFeeTo refuses ETH before the launch and anyone sends perps fees then, the opening buy spends the booked ETH, and afterwards split() and harvest() revert on an underflow until the pool's fees alone exceed the owed amount. Reproduced with a scratch test:\n\n```\nlaunchEth 3.31 ETH instead of 2.81 ETH; balance 0; perpFeesOwed 0.5 ETH\nsplit() reverts, harvest() reverts\n```\n\nThe preconditions need a refusing perpFeeTo before the launch, so this is low. The fix is to subtract perpFeesOwed from the spend in launch() and withdrawEth, or to refuse addPerpFees before the launch.\n\n**Checks run**\n\n- Both attached proofs and the project's 88 tests pass on this tree.\n- The coverage record answers all 100 entry points, none unreached.\n- Scratch tests were removed after running, so no file other than the findings file changed.","treeHash":null,"usage":{"cachedInputTokens":1880078,"inputTokens":642,"model":"claude-fable-5-1","outputTokens":23907,"runtime":"claude","turns":21,"wallClockMs":616662}}],"verification":[{"checks":[{"durationMs":29855,"exitCode":0,"name":"build","output":"Compiling 91 files with Solc 0.8.26\nSolc 0.8.26 finished in 29.76s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpEngine.sol:349:5:\n    |\n349 |     function _quoteAt(uint256 size, uint256 sp) internal view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpVault.sol:364:5:\n    |\n364 |     function _toYieldUnit(uint256 v) private view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n","passed":true},{"durationMs":282,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodForkTest\n[PASS] test_TheWholeLaunchOnRobinhood() (gas: 2548)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 379.67µs (31.66µs CPU time)\n\nRan 11 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1047565)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AShortRoundTrip() (gas: 1842840)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1882445)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 650076)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120171)\n[PASS] test_PerpFeesReachThePot() (gas: 695057)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 1010484)\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145028)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 64551783)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14696281)\n[PASS] test_TheTimelocksSaleSweepsFirstAndKeepsItsRefund() (gas: 1537182)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 169.83ms (155.57ms CPU time)\n\nRan 55 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_ARefusedPerpFeeIsOwedNotRefused() (gas: 2290735)\n[PASS] test_ARefusedShareIsOwedNotReSplit() (gas: 4268548)\n[PASS] test_AnOwedShareIsPaidOnceThePayeeAccepts() (gas: 1823819)\n[PASS] test_BurnImdDebitsWhatThePoolTook() (gas: 3296119)\nLogs:\n  the pool took: 0.004505596826978234\n\n[PASS] test_BurnsImdOnceABlock() (gas: 3484041)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 3046018)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1258294)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1460305)\n[PASS] test_ClaimsNeverPassTheReserve() (gas: 1658420)\n[PASS] test_ClaimsOneForOne() (gas: 1582842)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208805)\n[PASS] test_DeploysOnAFreshChain() (gas: 2483949)\n[PASS] test_DroppingAPayeeCancelsWhatItWasOwed() (gas: 1813541)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 4228838)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2338064)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599550457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1299542)\n[PASS] test_FitsOneTransaction() (gas: 4207550)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 7825\n  createHook gas: 1317877\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1851868)\nLogs:\n  pot bridge: 0.511887600000000000\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1429708)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1307689)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1299174)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2206314)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1969404)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288388119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2079778)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 1998403)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 178966)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1973157)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1299520)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 68143)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 94101)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 117983)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1812123)\n[PASS] test_PassesTheAdmissionScan() (gas: 15745011)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1455712)\n[PASS] test_RecoverEthDropsTheBookings() (gas: 1500764)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702582)\n[PASS] test_RefusesTheWrongAddress() (gas: 710029)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1274136)\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1215732)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1807712)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1232755)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1296203)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 1850715)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2666976)\n[PASS] test_TheFeeIsStatic() (gas: 1585603)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 28588515)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1344408)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21922)\n[PASS] test_TheLaunchsOwnerCantRenounce() (gas: 106860)\n[PASS] test_TheLiquidityIsLocked() (gas: 2157830)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2318595)\n[PASS] test_TheSalesRefundSurvivesALiquidationInTheSweep() (gas: 3389642)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2112018)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1650319)\n[PASS] test_TradesBothWays() (gas: 1561110)\nSuite result: ok. 55 passed; 0 failed; 0 skipped; finished in 169.99ms (177.65ms CPU time)\n\nRan 3 test suites in 171.21ms (340.21ms CPU time): 67 tests passed, 0 failed, 0 skipped (67 total tests)\n","passed":true},{"durationMs":60,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMD6900.approve(address,uint256)\",\"IMD6900.burn(uint256)\",\"IMD6900.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"IMD6900.transfer(address,uint256)\",\"IMD6900.transferFrom(address,address,uint256)\",\"IMD6900Hook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"IMD6900Hook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"IMD6900Hook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"IMD6900Hook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"IMD6900Hook.cancelOwnershipHandover()\",\"IMD6900Hook.completeOwnershipHandover(address)\",\"IMD6900Hook.initPerpEngine(address)\",\"IMD6900Hook.renounceOwnership()\",\"IMD6900Hook.requestOwnershipHandover()\",\"IMD6900Hook.setPerpEngine(address)\",\"IMD6900Hook.setPerpRefunded(bool)\",\"IMD6900Hook.setSweepFailOpen(bool)\",\"IMD6900Hook.transferOwnership(address)\",\"IMD6900LockedPool.collect()\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(address)\",\"IMD6900LockedPool.setHook(address)\",\"IMD6900LockedPool.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.addFees()\",\"IMD6900RobinhoodLaunch.addPerpFees()\",\"IMD6900RobinhoodLaunch.burnImd(uint256)\",\"IMD6900RobinhoodLaunch.cancelOwnershipHandover()\",\"IMD6900RobinhoodLaunch.claim(uint256,uint256,bytes32[])\",\"IMD6900RobinhoodLaunch.completeOwnershipHandover(address)\",\"IMD6900RobinhoodLaunch.createHook(bytes32)\",\"IMD6900RobinhoodLaunch.endClaims()\",\"IMD6900RobinhoodLaunch.harvest()\",\"IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address)\",\"IMD6900RobinhoodLaunch.payOwed(address)\",\"IMD6900RobinhoodLaunch.receive()\",\"IMD6900RobinhoodLaunch.recoverEth(address,uint256)\",\"IMD6900RobinhoodLaunch.releaseExcess(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseImdstr(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseUnclaimed(address)\",\"IMD6900RobinhoodLaunch.renounceOwnership()\",\"IMD6900RobinhoodLaunch.requestOwnershipHandover()\",\"IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address)\",\"IMD6900RobinhoodLaunch.setClaims(bytes32,uint256,uint256)\",\"IMD6900RobinhoodLaunch.setImdBurn(address)\",\"IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address)\",\"IMD6900RobinhoodLaunch.setPayees(address[],uint16[])\",\"IMD6900RobinhoodLaunch.setPerpFeeTo(address)\",\"IMD6900RobinhoodLaunch.split()\",\"IMD6900RobinhoodLaunch.transferOwnership(address)\",\"IMD6900RobinhoodLaunch.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.withdrawEth(address,uint256)\",\"PerpEngine.claimPayout()\",\"PerpEngine.close(uint256,uint256)\",\"PerpEngine.flushFees()\",\"PerpEngine.fundFromVault()\",\"PerpEngine.fundInsurance()\",\"PerpEngine.fundPlv()\",\"PerpEngine.fundPlvToken(uint256)\",\"PerpEngine.fundTokenFromVault(uint256)\",\"PerpEngine.liquidate(uint256)\",\"PerpEngine.openLong(uint8,uint256)\",\"PerpEngine.openShort(uint8,uint256)\",\"PerpEngine.poke()\",\"PerpEngine.receive()\",\"PerpEngine.selfSweep(address)\",\"PerpEngine.setFeeSink(address)\",\"PerpEngine.setFees(uint256,uint256,uint256)\",\"PerpEngine.setGuards(uint32,uint256,uint256)\",\"PerpEngine.setMinCollateral(uint256)\",\"PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256)\",\"PerpEngine.setShortCap(uint256)\",\"PerpEngine.setVault(address)\",\"PerpEngine.setVaultLimits(uint256,uint256)\",\"PerpEngine.setVaultSplit(uint256,uint256)\",\"PerpEngine.skimInsurance(uint256,address)\",\"PerpEngine.sweepLiquidations(address,int256,bool,uint160)\",\"PerpEngine.transferOwnership(address)\",\"PerpEngine.unlockCallback(bytes)\",\"PerpEngine.withdrawPlvTo(uint256,address)\",\"PerpEngine.withdrawPlvTokenTo(uint256,address)\",\"PerpEngine.withdrawTokYieldTo(uint256,address)\",\"PerpVault.claimPendingEth()\",\"PerpVault.claimPendingToken()\",\"PerpVault.claimTokYield()\",\"PerpVault.depositEth()\",\"PerpVault.depositToken(uint256)\",\"PerpVault.settlePendingEth(address)\",\"PerpVault.settlePendingToken(address)\",\"PerpVault.withdrawEth(uint256)\",\"PerpVault.withdrawToken(uint256)\"],\"files\":{\".gitignore\":15,\"ADAPTATION.md\":271,\"README.md\":155,\"foundry.toml\":30,\"jobs/swarm-launch.json\":27,\"script/claims.py\":88,\"script/launch.sh\":269,\"script/mine.sh\":12,\"script/params.py\":64,\"script/programmable_plan.py\":205,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":277,\"src/IMD6900LockedPool.sol\":392,\"src/IMD6900RobinhoodLaunch.sol\":643,\"src/perps/PerpEngine.sol\":1158,\"src/perps/PerpSwapLib.sol\":728,\"src/perps/PerpVault.sol\":823,\"test/HookSalt.sol\":49,\"test/IMD6900RobinhoodLaunch.t.sol\":1400,\"test/Perps.t.sol\":269,\"test/RobinhoodFork.t.sol\":255},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":12348,"exitCode":0,"name":"slither","output":"Slither crashed on this tree, an analyser bug and not a finding, so its rejecting detectors did not run; Aderyn's report below stands in. slither produced no report: ib/python3.11/site-packages/slither/slithir/convert.py\", line 1981, in convert_constant_types\n    assert len(types) == len(ir.arguments)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nAssertionError\n'forge clean' running (wd: /tmp/imd-verify-cz5oUX/repo)\n'forge config --json' running\n'forge build --build-info --deny never --skip ./test/** ./script/** --force' running (wd: /tmp/imd-verify-cz5oUX/repo)\n","passed":true},{"durationMs":842,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/IMD6900LockedPool.sol:230: `abi.encodePacked()` Hash Collision (5 places)\n[high] contract-locks-ether at src/IMD6900Hook.sol:42: Contract locks Ether without a withdraw function (3 places)\n[high] eth-send-unchecked-address at src/IMD6900RobinhoodLaunch.sol:227: ETH transferred without address checks (3 places)\n[high] reentrancy-state-change at src/IMD6900LockedPool.sol:160: Reentrancy: State change after external call (21 places)\n[high] storage-array-memory-edit at src/perps/PerpEngine.sol:682: Storage Array Edited with Memory\n[high] unsafe-casting at src/perps/PerpEngine.sol:863: Unsafe Casting of integers\n[low] centralization-risk at src/IMD6900Hook.sol:42: Centralization Risk (25 places)\n[low] costly-loop at src/IMD6900RobinhoodLaunch.sol:373: Costly operations inside loop (7 places)\n[low] large-numeric-literal at src/IMD6900.sol:11: Large Numeric Literal (22 places)\n[low] literal-instead-of-constant at src/IMD6900Hook.sol:111: Literal Instead of Constant (32 places)\n[low] local-variable-shadowing at src/perps/PerpEngine.sol:267: Local Variable Shadows State Variable\n[low] non-reentrant-not-first at src/IMD6900RobinhoodLaunch.sol:294: `nonReentrant` is Not the First Modifier (7 places)\n[low] push-zero-opcode at src/IMD6900.sol:2: PUSH0 Opcode (7 places)\n[low] require-revert-in-loop at src/IMD6900RobinhoodLaunch.sol:373: Loop Contains `require`/`revert` (4 places)\n[low] state-change-without-event at src/IMD6900RobinhoodLaunch.sol:551: State Change Without Event (14 places)\n[low] state-no-address-check at src/IMD6900Hook.sol:146: Address State Variable Set Without Checks (8 places)\n[low] unchecked-return at src/IMD6900LockedPool.sol:160: Unchecked Return (11 places)\n[low] uninitialized-local-variable at src/IMD6900RobinhoodLaunch.sol:382: Uninitialized Local Variable (9 places)\n[low] unsafe-erc20-operation at src/perps/PerpSwapLib.sol:435: Unsafe ERC20 Operation (3 places)\n[low] unspecific-solidity-pragma at src/IMD6900.sol:2: Unspecific Solidity Pragma (6 places)\n[low] unused-error at src/perps/PerpVault.sol:190: Unused Error (2 places)\n[low] unused-import at src/perps/PerpVault.sol:5: Unused Import\n[low] unused-public-function at src/IMD6900Hook.sol:110: Public Function Not Used Internally (3 places)","passed":true},{"durationMs":19980,"exitCode":1,"name":"proof 644c65575bf3","output":"Compiling 68 files with Solc 0.8.26\nSolc 0.8.26 finished in 11.74s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpEngine.sol:349:5:\n    |\n349 |     function _quoteAt(uint256 size, uint256 sp) internal view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpVault.sol:364:5:\n    |\n364 |     function _toYieldUnit(uint256 v) private view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n\nRan 1 test for test/imd-proof-4ad035ff/Proof_644c65575bf3.t.sol:BurnImdLoopTest\n[FAIL: vm.deployCode: no matching artifact found] setUp() (gas: 0)\nSuite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 350.86µs (0.00ns CPU time)\n\nRan 1 test suite in 3.52ms (350.86µs CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)\n\nFailing tests:\nEncountered 1 failing test in test/imd-proof-4ad035ff/Proof_644c65575bf3.t.sol:BurnImdLoopTest\n[FAIL: vm.deployCode: no matching artifact found] setUp() (gas: 0)\n\nEncountered a total of 1 failing tests, 0 tests succeeded\n\nTip: Run `forge test --rerun` to retry only the 1 failed test\nTip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger\n","passed":false}],"detail":"the proof for \"burnImd's IMD_BUY_MAX bounds one call, not one transaction: the whole $IMD bucket can be bought at a manipulated price in a single sandwiched transaction\" failed on the code it was reported against and still fails on this revision. Make it pass, or answer the finding \"disputed\" if the test itself is wrong.","evaluation":"checks","profile":"foundry","status":"rejected","submissionHash":"077e04fb060101c0b7dab85197ee1baaaa7e88f25f4228ad1d9a8e861eea52f7","verifiedTreeHash":"f145b1abf928c18dad3688aae3a79857197ff78a","verifierVersion":"0.1.0+67c588fa"},{"checks":[],"detail":"start commit produces tree 3e2bf0e99221 but its accepted dependencies produce b841b54ca7e3","evaluation":"checks","profile":"foundry","status":"rejected","submissionHash":"3c5e5da7344ebace507ded9faf9f6b2d1da8f0abd22dc3f9196be217ca2800f1","verifiedTreeHash":"3e14befc3bc806e6b3a2dde2ad3527f7af6d7c50","verifierVersion":"0.1.0+67c588fa"},{"checks":[{"durationMs":28097,"exitCode":0,"name":"build","output":"Compiling 92 files with Solc 0.8.26\nSolc 0.8.26 finished in 28.00s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpEngine.sol:349:5:\n    |\n349 |     function _quoteAt(uint256 size, uint256 sp) internal view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpVault.sol:364:5:\n    |\n364 |     function _toYieldUnit(uint256 v) private view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n","passed":true},{"durationMs":266,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodForkTest\n[PASS] test_TheWholeLaunchOnRobinhood() (gas: 2548)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 303.42µs (25.05µs CPU time)\n\nRan 11 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1047565)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AShortRoundTrip() (gas: 1842840)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1882445)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 650076)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120171)\n[PASS] test_PerpFeesReachThePot() (gas: 697375)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 1010484)\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145028)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 64551497)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14696281)\n[PASS] test_TheTimelocksSaleSweepsFirstAndKeepsItsRefund() (gas: 1537131)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 169.59ms (157.12ms CPU time)\n\nRan 55 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_ARefusedShareIsOwedNotReSplit() (gas: 4380922)\n[PASS] test_AnOwedShareIsPaidOnceThePayeeAccepts() (gas: 1826793)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 3047909)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1258052)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1460063)\n[PASS] test_ClaimsNeverPassTheReserve() (gas: 1640029)\n[PASS] test_ClaimsOneForOne() (gas: 1584754)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208651)\n[PASS] test_DeploysOnAFreshChain() (gas: 2483531)\n[PASS] test_DroppingAPayeeCancelsWhatItWasOwed() (gas: 1808575)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 4228508)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2337079)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599550457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1299344)\n[PASS] test_FitsOneTransaction() (gas: 4207418)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 7825\n  createHook gas: 1317833\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1853812)\nLogs:\n  pot bridge: 0.511887600000000000\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1429356)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1307447)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1298910)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2206157)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1968599)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288388119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2079624)\n[PASS] test_OneImdBuyATimestamp() (gas: 3488785)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 1998272)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 178856)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1972959)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1301486)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 68099)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 94057)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 118005)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1810492)\n[PASS] test_PassesTheAdmissionScan() (gas: 15630080)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1454954)\n[PASS] test_RecoverEthDropsTheBookings() (gas: 1504820)\n[PASS] test_RefusedPerpFeesWaitForTheNextForwarding() (gas: 2058474)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702494)\n[PASS] test_RefusesTheWrongAddress() (gas: 709853)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1273982)\n[PASS] test_TheBucketIsDebitedByWhatThePoolTook() (gas: 3324027)\nLogs:\n  the pool took: 0.007509328044963723\n\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1215534)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1807558)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1232579)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1296049)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 1850517)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2666822)\n[PASS] test_TheFeeIsStatic() (gas: 1585405)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 28588163)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1344188)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21856)\n[PASS] test_TheLaunchCantBeRenounced() (gas: 1287242)\n[PASS] test_TheLiquidityIsLocked() (gas: 2157676)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2318397)\n[PASS] test_TheSalesRefundSurvivesALiquidationInTheSweep() (gas: 3389393)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2111806)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1650051)\n[PASS] test_TradesBothWays() (gas: 1560978)\nSuite result: ok. 55 passed; 0 failed; 0 skipped; finished in 169.78ms (168.58ms CPU time)\n\nRan 3 test suites in 170.94ms (339.67ms CPU time): 67 tests passed, 0 failed, 0 skipped (67 total tests)\n","passed":true},{"durationMs":71,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMD6900.approve(address,uint256)\",\"IMD6900.burn(uint256)\",\"IMD6900.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"IMD6900.transfer(address,uint256)\",\"IMD6900.transferFrom(address,address,uint256)\",\"IMD6900Hook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"IMD6900Hook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"IMD6900Hook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"IMD6900Hook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"IMD6900Hook.cancelOwnershipHandover()\",\"IMD6900Hook.completeOwnershipHandover(address)\",\"IMD6900Hook.initPerpEngine(address)\",\"IMD6900Hook.renounceOwnership()\",\"IMD6900Hook.requestOwnershipHandover()\",\"IMD6900Hook.setPerpEngine(address)\",\"IMD6900Hook.setPerpRefunded(bool)\",\"IMD6900Hook.setSweepFailOpen(bool)\",\"IMD6900Hook.transferOwnership(address)\",\"IMD6900LockedPool.collect()\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(address)\",\"IMD6900LockedPool.setHook(address)\",\"IMD6900LockedPool.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.addFees()\",\"IMD6900RobinhoodLaunch.addPerpFees()\",\"IMD6900RobinhoodLaunch.burnImd(uint256)\",\"IMD6900RobinhoodLaunch.cancelOwnershipHandover()\",\"IMD6900RobinhoodLaunch.claim(uint256,uint256,bytes32[])\",\"IMD6900RobinhoodLaunch.completeOwnershipHandover(address)\",\"IMD6900RobinhoodLaunch.createHook(bytes32)\",\"IMD6900RobinhoodLaunch.endClaims()\",\"IMD6900RobinhoodLaunch.harvest()\",\"IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address)\",\"IMD6900RobinhoodLaunch.receive()\",\"IMD6900RobinhoodLaunch.recoverEth(address,uint256)\",\"IMD6900RobinhoodLaunch.releaseExcess(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseImdstr(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseUnclaimed(address)\",\"IMD6900RobinhoodLaunch.renounceOwnership()\",\"IMD6900RobinhoodLaunch.requestOwnershipHandover()\",\"IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address)\",\"IMD6900RobinhoodLaunch.setClaims(bytes32,uint256,uint256)\",\"IMD6900RobinhoodLaunch.setImdBurn(address)\",\"IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address)\",\"IMD6900RobinhoodLaunch.setPayees(address[],uint16[])\",\"IMD6900RobinhoodLaunch.setPerpFeeTo(address)\",\"IMD6900RobinhoodLaunch.split()\",\"IMD6900RobinhoodLaunch.transferOwnership(address)\",\"IMD6900RobinhoodLaunch.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.withdrawEth(address,uint256)\",\"PerpEngine.claimPayout()\",\"PerpEngine.close(uint256,uint256)\",\"PerpEngine.flushFees()\",\"PerpEngine.fundFromVault()\",\"PerpEngine.fundInsurance()\",\"PerpEngine.fundPlv()\",\"PerpEngine.fundPlvToken(uint256)\",\"PerpEngine.fundTokenFromVault(uint256)\",\"PerpEngine.liquidate(uint256)\",\"PerpEngine.openLong(uint8,uint256)\",\"PerpEngine.openShort(uint8,uint256)\",\"PerpEngine.poke()\",\"PerpEngine.receive()\",\"PerpEngine.selfSweep(address)\",\"PerpEngine.setFeeSink(address)\",\"PerpEngine.setFees(uint256,uint256,uint256)\",\"PerpEngine.setGuards(uint32,uint256,uint256)\",\"PerpEngine.setMinCollateral(uint256)\",\"PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256)\",\"PerpEngine.setShortCap(uint256)\",\"PerpEngine.setVault(address)\",\"PerpEngine.setVaultLimits(uint256,uint256)\",\"PerpEngine.setVaultSplit(uint256,uint256)\",\"PerpEngine.skimInsurance(uint256,address)\",\"PerpEngine.sweepLiquidations(address,int256,bool,uint160)\",\"PerpEngine.transferOwnership(address)\",\"PerpEngine.unlockCallback(bytes)\",\"PerpEngine.withdrawPlvTo(uint256,address)\",\"PerpEngine.withdrawPlvTokenTo(uint256,address)\",\"PerpEngine.withdrawTokYieldTo(uint256,address)\",\"PerpVault.claimPendingEth()\",\"PerpVault.claimPendingToken()\",\"PerpVault.claimTokYield()\",\"PerpVault.depositEth()\",\"PerpVault.depositToken(uint256)\",\"PerpVault.settlePendingEth(address)\",\"PerpVault.settlePendingToken(address)\",\"PerpVault.withdrawEth(uint256)\",\"PerpVault.withdrawToken(uint256)\"],\"files\":{\".gitignore\":15,\"ADAPTATION.md\":238,\"README.md\":147,\"foundry.toml\":30,\"jobs/swarm-launch.json\":27,\"script/claims.py\":88,\"script/launch.sh\":269,\"script/mine.sh\":12,\"script/params.py\":64,\"script/programmable_plan.py\":205,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":277,\"src/IMD6900LockedPool.sol\":392,\"src/IMD6900RobinhoodLaunch.sol\":629,\"src/perps/PerpEngine.sol\":1158,\"src/perps/PerpSwapLib.sol\":728,\"src/perps/PerpVault.sol\":823,\"test/HookSalt.sol\":50,\"test/IMD6900RobinhoodLaunch.t.sol\":1388,\"test/Perps.t.sol\":269,\"test/PoolManagerArtifact.sol\":8,\"test/RobinhoodFork.t.sol\":255},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":12647,"exitCode":0,"name":"slither","output":"Slither crashed on this tree, an analyser bug and not a finding, so its rejecting detectors did not run; Aderyn's report below stands in. slither produced no report: ib/python3.11/site-packages/slither/slithir/convert.py\", line 1981, in convert_constant_types\n    assert len(types) == len(ir.arguments)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nAssertionError\n'forge clean' running (wd: /tmp/imd-verify-b1iswj/repo)\n'forge config --json' running\n'forge build --build-info --deny never --skip ./test/** ./script/** --force' running (wd: /tmp/imd-verify-b1iswj/repo)\n","passed":true},{"durationMs":871,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/IMD6900LockedPool.sol:230: `abi.encodePacked()` Hash Collision (5 places)\n[high] contract-locks-ether at src/IMD6900Hook.sol:42: Contract locks Ether without a withdraw function (3 places)\n[high] eth-send-unchecked-address at src/IMD6900RobinhoodLaunch.sol:231: ETH transferred without address checks (2 places)\n[high] reentrancy-state-change at src/IMD6900LockedPool.sol:160: Reentrancy: State change after external call (20 places)\n[high] storage-array-memory-edit at src/perps/PerpEngine.sol:682: Storage Array Edited with Memory\n[high] unsafe-casting at src/perps/PerpEngine.sol:863: Unsafe Casting of integers\n[low] centralization-risk at src/IMD6900Hook.sol:42: Centralization Risk (24 places)\n[low] costly-loop at src/IMD6900RobinhoodLaunch.sol:379: Costly operations inside loop (7 places)\n[low] large-numeric-literal at src/IMD6900.sol:11: Large Numeric Literal (22 places)\n[low] literal-instead-of-constant at src/IMD6900Hook.sol:111: Literal Instead of Constant (32 places)\n[low] local-variable-shadowing at src/perps/PerpEngine.sol:267: Local Variable Shadows State Variable\n[low] non-reentrant-not-first at src/IMD6900RobinhoodLaunch.sol:300: `nonReentrant` is Not the First Modifier (7 places)\n[low] push-zero-opcode at src/IMD6900.sol:2: PUSH0 Opcode (7 places)\n[low] require-revert-in-loop at src/IMD6900RobinhoodLaunch.sol:379: Loop Contains `require`/`revert` (4 places)\n[low] state-change-without-event at src/perps/PerpEngine.sol:320: State Change Without Event (13 places)\n[low] state-no-address-check at src/IMD6900Hook.sol:146: Address State Variable Set Without Checks (8 places)\n[low] unchecked-return at src/IMD6900LockedPool.sol:160: Unchecked Return (11 places)\n[low] uninitialized-local-variable at src/IMD6900RobinhoodLaunch.sol:387: Uninitialized Local Variable (9 places)\n[low] unsafe-erc20-operation at src/perps/PerpSwapLib.sol:435: Unsafe ERC20 Operation (3 places)\n[low] unspecific-solidity-pragma at src/IMD6900.sol:2: Unspecific Solidity Pragma (6 places)\n[low] unused-error at src/perps/PerpVault.sol:190: Unused Error (2 places)\n[low] unused-import at src/perps/PerpVault.sol:5: Unused Import\n[low] unused-public-function at src/IMD6900Hook.sol:110: Public Function Not Used Internally (3 places)","passed":true},{"durationMs":13837,"exitCode":0,"name":"proof 644c65575bf3","output":"Compiling 90 files with Solc 0.8.26\nSolc 0.8.26 finished in 13.19s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpEngine.sol:349:5:\n    |\n349 |     function _quoteAt(uint256 size, uint256 sp) internal view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpVault.sol:364:5:\n    |\n364 |     function _toYieldUnit(uint256 v) private view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n\nRan 1 test for test/imd-proof-25b372a8/Proof_644c65575bf3.t.sol:BurnImdLoopTest\n[PASS] test_WholeBucketSpentInOneTransaction() (gas: 19475636)\nLogs:\n  bucket: 678476400000000000\n  spent in one tx: 100000000000000000\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 40.18ms (36.96ms CPU time)\n\nRan 1 test suite in 41.00ms (40.18ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"68a0236d1543669eac16098c5df33cddff27e0be6ca2e78e8f7900d2b5422ac2","verifiedTreeHash":"8afc5443883b56572f5ae423d6502c29c4cf9df3","verifierVersion":"0.1.0+67c588fa"},{"checks":[{"durationMs":66995,"exitCode":0,"name":"build","output":"Compiling 94 files with Solc 0.8.26\nSolc 0.8.26 finished in 66.85s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpEngine.sol:349:5:\n    |\n349 |     function _quoteAt(uint256 size, uint256 sp) internal view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpVault.sol:364:5:\n    |\n364 |     function _toYieldUnit(uint256 v) private view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n","passed":true},{"durationMs":4737,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodForkTest\n[PASS] test_TheWholeLaunchOnRobinhood() (gas: 2548)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 564.31µs (44.37µs CPU time)\n\nRan 11 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1047565)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AShortRoundTrip() (gas: 1842840)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1882445)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 650076)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120171)\n[PASS] test_PerpFeesReachThePot() (gas: 697375)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 1010484)\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145028)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 64551497)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14696281)\n[PASS] test_TheTimelocksSaleSweepsFirstAndKeepsItsRefund() (gas: 1537131)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 319.39ms (310.53ms CPU time)\n\nRan 20 tests for test/Edges.t.sol:EdgesTest\n[PASS] testFuzz_ClaimsInParts(uint256,uint256) (runs: 200, μ: 1669639, ~: 1673738)\nLogs:\n  Bound result 21584100220074961381483221\n  Bound result 14490220729675408626076276\n\n[PASS] testFuzz_LowerFeeToAnyLowerFee(uint24,uint256) (runs: 100, μ: 2101501, ~: 2106179)\nLogs:\n  Bound result 2\n  Bound result 19999000000128828253\n\n[PASS] testFuzz_TheOpeningAtAnyPriceAndSize(uint256,uint256) (runs: 200, μ: 1245061, ~: 1242387)\nLogs:\n  Bound result 69922976793821993616418\n  Bound result 144527458961801894358\n\n[PASS] testFuzz_TheRefundIsExactlyTheSwapsFee(uint256) (runs: 100, μ: 2232139, ~: 2236160)\nLogs:\n  Bound result 100000000000003\n\n[PASS] testFuzz_TheReserveIsCoveredOrTheLaunchRefuses(uint256,uint256) (runs: 100, μ: 1270197, ~: 1276169)\nLogs:\n  Bound result 49999999999000473611\n  Bound result 128828252\n\n[PASS] testFuzz_TheSplitConservesEth(uint256,uint16) (runs: 200, μ: 1378785, ~: 1384220)\nLogs:\n  Bound result 5796156876359272223\n  Bound result 14\n\n[PASS] test_AddPerpFeesByAStranger() (gas: 1581523)\n[PASS] test_ClaimZeroAndBeforeTheLaunch() (gas: 1486901)\n[PASS] test_ClaimsCantBeHalfSet() (gas: 1345117)\n[PASS] test_OnlyTheLauncherRunsTheLockedPool() (gas: 1482181)\n[PASS] test_PayeesAreValidated() (gas: 1965314)\n[PASS] test_RecoverEthAllOfItAndMoreThanHeld() (gas: 1308960)\n[PASS] test_SweepFailOpenIsTheTimelocksOnly() (gas: 1497227)\n[PASS] test_TheHookServesOnlyItsPool() (gas: 1364807)\n[PASS] test_TheImdBuyGateIsSetOnlyByABuy() (gas: 1159893)\n[PASS] test_TheLaunchNeedsEth() (gas: 3392703)\n[PASS] test_TheRefusedPerpsBookAndRecoverEth() (gas: 1714165)\n[PASS] test_TheTimelocksSettersRefuseNobody() (gas: 78956)\n[PASS] test_TheWholeExcessSoldInOneGo() (gas: 1616830)\n[PASS] test_ZeroAmountsAreRefused() (gas: 657550)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 319.47ms (631.29ms CPU time)\n\nRan 55 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_ARefusedShareIsOwedNotReSplit() (gas: 4380922)\n[PASS] test_AnOwedShareIsPaidOnceThePayeeAccepts() (gas: 1826793)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 3047909)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1258052)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1460063)\n[PASS] test_ClaimsNeverPassTheReserve() (gas: 1640029)\n[PASS] test_ClaimsOneForOne() (gas: 1584754)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208651)\n[PASS] test_DeploysOnAFreshChain() (gas: 2483531)\n[PASS] test_DroppingAPayeeCancelsWhatItWasOwed() (gas: 1808575)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 4228508)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2337079)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599550457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1299344)\n[PASS] test_FitsOneTransaction() (gas: 4207418)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 7825\n  createHook gas: 1317833\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1853812)\nLogs:\n  pot bridge: 0.511887600000000000\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1429356)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1307447)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1298910)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2206157)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1968599)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288388119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2079624)\n[PASS] test_OneImdBuyATimestamp() (gas: 3488785)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 1998272)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 178856)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1972959)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1301486)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 68099)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 94057)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 118005)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1810492)\n[PASS] test_PassesTheAdmissionScan() (gas: 15630080)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1454954)\n[PASS] test_RecoverEthDropsTheBookings() (gas: 1504820)\n[PASS] test_RefusedPerpFeesWaitForTheNextForwarding() (gas: 2058474)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702494)\n[PASS] test_RefusesTheWrongAddress() (gas: 709853)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1273982)\n[PASS] test_TheBucketIsDebitedByWhatThePoolTook() (gas: 3324027)\nLogs:\n  the pool took: 0.007509328044963723\n\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1215534)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1807558)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1232579)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1296049)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 1850517)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2666822)\n[PASS] test_TheFeeIsStatic() (gas: 1585405)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 28588163)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1344188)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21856)\n[PASS] test_TheLaunchCantBeRenounced() (gas: 1287242)\n[PASS] test_TheLiquidityIsLocked() (gas: 2157676)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2318397)\n[PASS] test_TheSalesRefundSurvivesALiquidationInTheSweep() (gas: 3389393)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2111806)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1650051)\n[PASS] test_TradesBothWays() (gas: 1560978)\nSuite result: ok. 55 passed; 0 failed; 0 skipped; finished in 319.62ms (328.49ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:LaunchInvariantTest\n[PASS]\nLaunchInvariantTest invariants:\n[PASS] invariant_NoBadDebt\n[PASS] invariant_NothingPoolsOutsideTheLedgers\n[PASS] invariant_TheCoinIsConserved\n[PASS] invariant_TheEngineHoldsWhatItsLedgersSay\n[PASS] invariant_TheEngineStaysWired\n[PASS] invariant_TheFeeOnlyGoesDown\n[PASS] invariant_TheLaunchHoldsTheEthItBooked\n[PASS] invariant_TheLaunchHoldsWhatItOwes\n[PASS] invariant_TheLockedPositionIsAllOfThePool\n[PASS] invariant_TheRolesStay\n[PASS] invariant_TheVaultIsBacked\n LaunchInvariantTest invariants (runs: 96, calls: 6144, reverts: 0)\n\n╭---------------+------------------+-------+---------+----------╮\n| Contract      | Selector         | Calls | Reverts | Discards |\n+===============================================================+\n| LaunchHandler | addPerpFees      | 191   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | burnImd          | 199   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | buy              | 176   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | claim            | 196   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | claimPayout      | 204   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | claimPendingEth  | 209   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | close            | 175   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | collect          | 203   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | endClaims        | 185   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | flushFees        | 212   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | harvest          | 193   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | liquidate        | 230   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | lowerFee         | 196   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | openLong         | 185   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | openShort        | 177   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | overClaim        | 183   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | raiseFeeRefused  | 215   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | recoverEth       | 185   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | refuserOpens     | 172   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | releaseExcess    | 159   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | releaseUnclaimed | 180   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | renounce         | 227   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | sell             | 200   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | sellHeld         | 199   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | setPayees        | 200   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | setPerpFeeTo     | 173   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | split            | 179   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | stakeEth         | 181   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | stakeToken       | 187   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | unstakeEth       | 183   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | unstakeToken     | 188   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | warp             | 202   | 0       | 0        |\n╰---------------+------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 2\n  Bound result 255\n  Bound result 2379889120\n  Bound result 88\n  Bound result 0\n  Bound result 10000000000000000\n  Bound result 0\n  Bound result 2\n  Bound result 0\n  Bound result 1\n  Bound result 0\n  Bound result 6878\n  Bound result 2\n  Bound result 99000000000000000\n  Bound result 2\n  Bound result 1\n  Bound result 3\n  Bound result 11000000000000000\n  Bound result 59\n  Bound result 3\n  Bound result 150960\n  Bound result 0\n  Bound result 93308\n  Bound result 0\n  Bound result 75038516255581405762775946\n  Bound result 0\n  Bound result 40\n  Bound result 2\n  Bound result 3\n  Bound result 0\n  Bound result 2\n  Bound result 11000000000000000\n  Bound result 0\n  Bound result 2\n  Bound result 3\n  Bound result 25\n  Bound result 2848\n  Bound result 9187\n  Bound result 2\n  Bound result 1\n  Bound result 500000000000000000000\n  Bound result 0\n  Bound result 7999900000000002922\n  Bound result 2\n  Bound result 25\n  Bound result 1\n  Bound result 37339331724830570657257698\n  Bound result 1\n  Bound result 7999900000000007552\n  Bound result 3\n  Bound result 2\n  Bound result 11000000000000000\n  Bound result 2941\n  Bound result 8000\n  Bound result 6283052608433587614754877\n  opens / refused / closes 0 3 0\n  liquidations 0\n  fee moves / sweep refusals 4 0\n  claimed / sold back / recovered 0 112378347980395984029421703 99000000000002941\n  eth to payees / refused / open now 17888300680499998 0 0\n  imd buys / perp fees forwarded / booked 0 0 0\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.58s (4.51s CPU time)\n\nRan 5 test suites in 4.58s (5.54s CPU time): 88 tests passed, 0 failed, 0 skipped (88 total tests)\n","passed":true},{"durationMs":124,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMD6900.approve(address,uint256)\",\"IMD6900.burn(uint256)\",\"IMD6900.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"IMD6900.transfer(address,uint256)\",\"IMD6900.transferFrom(address,address,uint256)\",\"IMD6900Hook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"IMD6900Hook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"IMD6900Hook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"IMD6900Hook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"IMD6900Hook.cancelOwnershipHandover()\",\"IMD6900Hook.completeOwnershipHandover(address)\",\"IMD6900Hook.initPerpEngine(address)\",\"IMD6900Hook.renounceOwnership()\",\"IMD6900Hook.requestOwnershipHandover()\",\"IMD6900Hook.setPerpEngine(address)\",\"IMD6900Hook.setPerpRefunded(bool)\",\"IMD6900Hook.setSweepFailOpen(bool)\",\"IMD6900Hook.transferOwnership(address)\",\"IMD6900LockedPool.collect()\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(address)\",\"IMD6900LockedPool.setHook(address)\",\"IMD6900LockedPool.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.addFees()\",\"IMD6900RobinhoodLaunch.addPerpFees()\",\"IMD6900RobinhoodLaunch.burnImd(uint256)\",\"IMD6900RobinhoodLaunch.cancelOwnershipHandover()\",\"IMD6900RobinhoodLaunch.claim(uint256,uint256,bytes32[])\",\"IMD6900RobinhoodLaunch.completeOwnershipHandover(address)\",\"IMD6900RobinhoodLaunch.createHook(bytes32)\",\"IMD6900RobinhoodLaunch.endClaims()\",\"IMD6900RobinhoodLaunch.harvest()\",\"IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address)\",\"IMD6900RobinhoodLaunch.receive()\",\"IMD6900RobinhoodLaunch.recoverEth(address,uint256)\",\"IMD6900RobinhoodLaunch.releaseExcess(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseImdstr(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseUnclaimed(address)\",\"IMD6900RobinhoodLaunch.renounceOwnership()\",\"IMD6900RobinhoodLaunch.requestOwnershipHandover()\",\"IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address)\",\"IMD6900RobinhoodLaunch.setClaims(bytes32,uint256,uint256)\",\"IMD6900RobinhoodLaunch.setImdBurn(address)\",\"IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address)\",\"IMD6900RobinhoodLaunch.setPayees(address[],uint16[])\",\"IMD6900RobinhoodLaunch.setPerpFeeTo(address)\",\"IMD6900RobinhoodLaunch.split()\",\"IMD6900RobinhoodLaunch.transferOwnership(address)\",\"IMD6900RobinhoodLaunch.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.withdrawEth(address,uint256)\",\"PerpEngine.claimPayout()\",\"PerpEngine.close(uint256,uint256)\",\"PerpEngine.flushFees()\",\"PerpEngine.fundFromVault()\",\"PerpEngine.fundInsurance()\",\"PerpEngine.fundPlv()\",\"PerpEngine.fundPlvToken(uint256)\",\"PerpEngine.fundTokenFromVault(uint256)\",\"PerpEngine.liquidate(uint256)\",\"PerpEngine.openLong(uint8,uint256)\",\"PerpEngine.openShort(uint8,uint256)\",\"PerpEngine.poke()\",\"PerpEngine.receive()\",\"PerpEngine.selfSweep(address)\",\"PerpEngine.setFeeSink(address)\",\"PerpEngine.setFees(uint256,uint256,uint256)\",\"PerpEngine.setGuards(uint32,uint256,uint256)\",\"PerpEngine.setMinCollateral(uint256)\",\"PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256)\",\"PerpEngine.setShortCap(uint256)\",\"PerpEngine.setVault(address)\",\"PerpEngine.setVaultLimits(uint256,uint256)\",\"PerpEngine.setVaultSplit(uint256,uint256)\",\"PerpEngine.skimInsurance(uint256,address)\",\"PerpEngine.sweepLiquidations(address,int256,bool,uint160)\",\"PerpEngine.transferOwnership(address)\",\"PerpEngine.unlockCallback(bytes)\",\"PerpEngine.withdrawPlvTo(uint256,address)\",\"PerpEngine.withdrawPlvTokenTo(uint256,address)\",\"PerpEngine.withdrawTokYieldTo(uint256,address)\",\"PerpVault.claimPendingEth()\",\"PerpVault.claimPendingToken()\",\"PerpVault.claimTokYield()\",\"PerpVault.depositEth()\",\"PerpVault.depositToken(uint256)\",\"PerpVault.settlePendingEth(address)\",\"PerpVault.settlePendingToken(address)\",\"PerpVault.withdrawEth(uint256)\",\"PerpVault.withdrawToken(uint256)\"],\"files\":{\".gitignore\":15,\"ADAPTATION.md\":238,\"README.md\":147,\"foundry.toml\":30,\"jobs/swarm-launch.json\":27,\"script/claims.py\":88,\"script/launch.sh\":269,\"script/mine.sh\":12,\"script/params.py\":64,\"script/programmable_plan.py\":205,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":277,\"src/IMD6900LockedPool.sol\":392,\"src/IMD6900RobinhoodLaunch.sol\":629,\"src/perps/PerpEngine.sol\":1158,\"src/perps/PerpSwapLib.sol\":728,\"src/perps/PerpVault.sol\":823,\"test/Edges.t.sol\":599,\"test/HookSalt.sol\":50,\"test/IMD6900RobinhoodLaunch.t.sol\":1388,\"test/Invariants.t.sol\":799,\"test/Perps.t.sol\":269,\"test/PoolManagerArtifact.sol\":8,\"test/RobinhoodFork.t.sol\":255},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"69ecbe517a10da6ffef9b3f01b8e5c0895d7130732019477b56d84803756450b","verifiedTreeHash":"c26b4d84092d7f09df83b3c4e57af3dd1347176d","verifierVersion":"0.1.0+67c588fa"},{"checks":[{"durationMs":62683,"exitCode":0,"name":"build","output":"Compiling 93 files with Solc 0.8.26\nSolc 0.8.26 finished in 62.55s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpEngine.sol:349:5:\n    |\n349 |     function _quoteAt(uint256 size, uint256 sp) internal view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpVault.sol:364:5:\n    |\n364 |     function _toYieldUnit(uint256 v) private view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n","passed":true},{"durationMs":4334,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodForkTest\n[PASS] test_TheWholeLaunchOnRobinhood() (gas: 2548)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 377.61µs (32.29µs CPU time)\n\nRan 50 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_ARefusedShareIsOwedNotReSplit() (gas: 4264189)\n[PASS] test_AnOwedShareIsPaidOnceThePayeeAccepts() (gas: 1819830)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 2954525)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1257943)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1459866)\n[PASS] test_ClaimsOneForOne() (gas: 1578197)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208629)\n[PASS] test_DeploysOnAFreshChain() (gas: 2483554)\n[PASS] test_DroppingAPayeeCancelsWhatItWasOwed() (gas: 1803760)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 4228486)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2332540)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599550457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1299235)\n[PASS] test_FitsOneTransaction() (gas: 4207308)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 7825\n  createHook gas: 1317833\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1851387)\nLogs:\n  pot bridge: 0.511887600000000000\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1429159)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1307250)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1298835)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2206073)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1966203)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288388119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2079427)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 1998185)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 178856)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1972872)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1299147)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 68033)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 93881)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 117917)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1808177)\n[PASS] test_PassesTheAdmissionScan() (gas: 15553891)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1450049)\n[PASS] test_RecoverEthDropsTheBookings() (gas: 1497799)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702393)\n[PASS] test_RefusesTheWrongAddress() (gas: 709821)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1273848)\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1215425)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1807361)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1232448)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1295852)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 1850408)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2666735)\n[PASS] test_TheFeeIsStatic() (gas: 1585230)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 28588053)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1344101)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21856)\n[PASS] test_TheLiquidityIsLocked() (gas: 2157589)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2318288)\n[PASS] test_TheSalesRefundSurvivesALiquidationInTheSweep() (gas: 3389269)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2111689)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1647711)\n[PASS] test_TradesBothWays() (gas: 1560803)\nSuite result: ok. 50 passed; 0 failed; 0 skipped; finished in 188.13ms (276.07ms CPU time)\n\nRan 17 tests for test/Edges.t.sol:EdgesTest\n[PASS] testFuzz_ClaimsInParts(uint256,uint256) (runs: 200, μ: 1660630, ~: 1665412)\nLogs:\n  Bound result 5796156876359272223\n  Bound result 12310607858561222398\n\n[PASS] testFuzz_LowerFeeToAnyLowerFee(uint24,uint256) (runs: 100, μ: 2101239, ~: 2105998)\nLogs:\n  Bound result 2\n  Bound result 19999000000128828253\n\n[PASS] testFuzz_TheOpeningAtAnyPriceAndSize(uint256,uint256) (runs: 200, μ: 1245709, ~: 1242264)\nLogs:\n  Bound result 69922976793821993616418\n  Bound result 144527458961801894358\n\n[PASS] testFuzz_TheRefundIsExactlyTheSwapsFee(uint256) (runs: 100, μ: 2232328, ~: 2236158)\nLogs:\n  Bound result 100000000000003\n\n[PASS] testFuzz_TheReserveIsCoveredOrTheLaunchRefuses(uint256,uint256) (runs: 100, μ: 1267888, ~: 1275955)\nLogs:\n  Bound result 49999999999000473611\n  Bound result 128828252\n\n[PASS] testFuzz_TheSplitConservesEth(uint256,uint16) (runs: 200, μ: 1376693, ~: 1381751)\nLogs:\n  Bound result 986096317387843396825\n  Bound result 1\n\n[PASS] test_ClaimZeroAndBeforeTheLaunch() (gas: 1484561)\n[PASS] test_ClaimsCantBeHalfSet() (gas: 1344876)\n[PASS] test_OnlyTheLauncherRunsTheLockedPool() (gas: 1482072)\n[PASS] test_PayeesAreValidated() (gas: 1962837)\n[PASS] test_RecoverEthAllOfItAndMoreThanHeld() (gas: 1304155)\n[PASS] test_SweepFailOpenIsTheTimelocksOnly() (gas: 1497118)\n[PASS] test_TheHookServesOnlyItsPool() (gas: 1364698)\n[PASS] test_TheLaunchNeedsEth() (gas: 3392506)\n[PASS] test_TheTimelocksSettersRefuseNobody() (gas: 78934)\n[PASS] test_TheWholeExcessSoldInOneGo() (gas: 1616684)\n[PASS] test_ZeroAmountsAreRefused() (gas: 635173)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 249.26ms (619.81ms CPU time)\n\nRan 11 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1047565)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AShortRoundTrip() (gas: 1842840)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1882445)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 650076)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120171)\n[PASS] test_PerpFeesReachThePot() (gas: 695010)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 1010484)\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145028)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 64551366)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14696281)\n[PASS] test_TheTimelocksSaleSweepsFirstAndKeepsItsRefund() (gas: 1537116)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 322.89ms (325.61ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:LaunchInvariantTest\n[PASS]\nLaunchInvariantTest invariants:\n[PASS] invariant_NoBadDebt\n[PASS] invariant_NothingPoolsOutsideTheLedgers\n[PASS] invariant_TheCoinIsConserved\n[PASS] invariant_TheEngineHoldsWhatItsLedgersSay\n[PASS] invariant_TheEngineStaysWired\n[PASS] invariant_TheFeeOnlyGoesDown\n[PASS] invariant_TheLaunchHoldsTheEthItBooked\n[PASS] invariant_TheLaunchHoldsWhatItOwes\n[PASS] invariant_TheLockedPositionIsAllOfThePool\n[PASS] invariant_TheVaultIsBacked\n LaunchInvariantTest invariants (runs: 96, calls: 6144, reverts: 0)\n\n╭---------------+------------------+-------+---------+----------╮\n| Contract      | Selector         | Calls | Reverts | Discards |\n+===============================================================+\n| LaunchHandler | burnImd          | 213   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | buy              | 226   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | claim            | 211   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | claimPayout      | 253   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | claimPendingEth  | 218   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | close            | 215   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | collect          | 219   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | endClaims        | 220   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | flushFees        | 212   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | harvest          | 222   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | liquidate        | 251   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | lowerFee         | 195   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | openLong         | 197   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | openShort        | 226   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | overClaim        | 228   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | raiseFeeRefused  | 239   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | recoverEth       | 186   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | releaseExcess    | 231   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | releaseUnclaimed | 219   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | sell             | 219   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | sellHeld         | 237   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | setPayees        | 227   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | split            | 206   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | stakeEth         | 221   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | stakeToken       | 216   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | unstakeEth       | 209   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | unstakeToken     | 223   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | warp             | 205   | 0       | 0        |\n╰---------------+------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 2\n  Bound result 1\n  Bound result 130\n  Bound result 0\n  Bound result 71282\n  Bound result 0\n  Bound result 15662853795544596613\n  Bound result 0\n  Bound result 3\n  Bound result 291536088514727706\n  Bound result 3\n  Bound result 671497\n  Bound result 0\n  Bound result 1\n  Bound result 2\n  Bound result 452683456080336207\n  Bound result 3847\n  Bound result 1\n  Bound result 1\n  Bound result 5446804387469040\n  Bound result 1\n  Bound result 0\n  Bound result 7999900000000014135\n  Bound result 2\n  Bound result 2\n  Bound result 19990000000000002497\n  Bound result 997597\n  Bound result 17789\n  Bound result 997663\n  Bound result 70400\n  Bound result 1\n  Bound result 19999999999999999999\n  Bound result 3\n  Bound result 19990000001377830821\n  Bound result 32\n  Bound result 0\n  Bound result 998413\n  Bound result 1\n  Bound result 6400\n  Bound result 0\n  Bound result 0\n  Bound result 3\n  Bound result 0\n  Bound result 2768469393196493367\n  Bound result 9136\n  Bound result 0\n  Bound result 19990000000000004047\n  Bound result 0\n  Bound result 5\n  Bound result 11000000000000000\n  Bound result 5030\n  Bound result 1\n  Bound result 2\n  Bound result 0\n  Bound result 4289416153\n  Bound result 0\n  Bound result 3\n  Bound result 11000000000000000\n  Bound result 45640918350564305404146987\n  Bound result 0\n  Bound result 4126\n  Bound result 0\n  Bound result 1810077929002903730738859\n  Bound result 2\n  Bound result 7999900000000005080\n  Bound result 0\n  Bound result 20000000000000000000\n  Bound result 2\n  Bound result 19990000000008640001\n  Bound result 1\n  Bound result 19990000000943158538\n  opens / refused / closes 0 4 0\n  liquidations 0\n  fee moves / sweep refusals 1 0\n  claimed / sold back / recovered 0 32 5446804387469040\n  eth to payees / refused / open now 88386211998128410 2239806541862903 0\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.18s (4.10s CPU time)\n\nRan 5 test suites in 4.18s (4.94s CPU time): 80 tests passed, 0 failed, 0 skipped (80 total tests)\n","passed":true},{"durationMs":115,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMD6900.approve(address,uint256)\",\"IMD6900.burn(uint256)\",\"IMD6900.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"IMD6900.transfer(address,uint256)\",\"IMD6900.transferFrom(address,address,uint256)\",\"IMD6900Hook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"IMD6900Hook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"IMD6900Hook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"IMD6900Hook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"IMD6900Hook.cancelOwnershipHandover()\",\"IMD6900Hook.completeOwnershipHandover(address)\",\"IMD6900Hook.initPerpEngine(address)\",\"IMD6900Hook.renounceOwnership()\",\"IMD6900Hook.requestOwnershipHandover()\",\"IMD6900Hook.setPerpEngine(address)\",\"IMD6900Hook.setPerpRefunded(bool)\",\"IMD6900Hook.setSweepFailOpen(bool)\",\"IMD6900Hook.transferOwnership(address)\",\"IMD6900LockedPool.collect()\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(address)\",\"IMD6900LockedPool.setHook(address)\",\"IMD6900LockedPool.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.addFees()\",\"IMD6900RobinhoodLaunch.addPerpFees()\",\"IMD6900RobinhoodLaunch.burnImd(uint256)\",\"IMD6900RobinhoodLaunch.cancelOwnershipHandover()\",\"IMD6900RobinhoodLaunch.claim(uint256,uint256,bytes32[])\",\"IMD6900RobinhoodLaunch.completeOwnershipHandover(address)\",\"IMD6900RobinhoodLaunch.createHook(bytes32)\",\"IMD6900RobinhoodLaunch.endClaims()\",\"IMD6900RobinhoodLaunch.harvest()\",\"IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address)\",\"IMD6900RobinhoodLaunch.receive()\",\"IMD6900RobinhoodLaunch.recoverEth(address,uint256)\",\"IMD6900RobinhoodLaunch.releaseExcess(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseImdstr(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseUnclaimed(address)\",\"IMD6900RobinhoodLaunch.renounceOwnership()\",\"IMD6900RobinhoodLaunch.requestOwnershipHandover()\",\"IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address)\",\"IMD6900RobinhoodLaunch.setClaims(bytes32,uint256,uint256)\",\"IMD6900RobinhoodLaunch.setImdBurn(address)\",\"IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address)\",\"IMD6900RobinhoodLaunch.setPayees(address[],uint16[])\",\"IMD6900RobinhoodLaunch.setPerpFeeTo(address)\",\"IMD6900RobinhoodLaunch.split()\",\"IMD6900RobinhoodLaunch.transferOwnership(address)\",\"IMD6900RobinhoodLaunch.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.withdrawEth(address,uint256)\",\"PerpEngine.claimPayout()\",\"PerpEngine.close(uint256,uint256)\",\"PerpEngine.flushFees()\",\"PerpEngine.fundFromVault()\",\"PerpEngine.fundInsurance()\",\"PerpEngine.fundPlv()\",\"PerpEngine.fundPlvToken(uint256)\",\"PerpEngine.fundTokenFromVault(uint256)\",\"PerpEngine.liquidate(uint256)\",\"PerpEngine.openLong(uint8,uint256)\",\"PerpEngine.openShort(uint8,uint256)\",\"PerpEngine.poke()\",\"PerpEngine.receive()\",\"PerpEngine.selfSweep(address)\",\"PerpEngine.setFeeSink(address)\",\"PerpEngine.setFees(uint256,uint256,uint256)\",\"PerpEngine.setGuards(uint32,uint256,uint256)\",\"PerpEngine.setMinCollateral(uint256)\",\"PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256)\",\"PerpEngine.setShortCap(uint256)\",\"PerpEngine.setVault(address)\",\"PerpEngine.setVaultLimits(uint256,uint256)\",\"PerpEngine.setVaultSplit(uint256,uint256)\",\"PerpEngine.skimInsurance(uint256,address)\",\"PerpEngine.sweepLiquidations(address,int256,bool,uint160)\",\"PerpEngine.transferOwnership(address)\",\"PerpEngine.unlockCallback(bytes)\",\"PerpEngine.withdrawPlvTo(uint256,address)\",\"PerpEngine.withdrawPlvTokenTo(uint256,address)\",\"PerpEngine.withdrawTokYieldTo(uint256,address)\",\"PerpVault.claimPendingEth()\",\"PerpVault.claimPendingToken()\",\"PerpVault.claimTokYield()\",\"PerpVault.depositEth()\",\"PerpVault.depositToken(uint256)\",\"PerpVault.settlePendingEth(address)\",\"PerpVault.settlePendingToken(address)\",\"PerpVault.withdrawEth(uint256)\",\"PerpVault.withdrawToken(uint256)\"],\"files\":{\".gitignore\":15,\"ADAPTATION.md\":165,\"README.md\":145,\"foundry.toml\":30,\"jobs/swarm-launch.json\":27,\"script/claims.py\":88,\"script/launch.sh\":269,\"script/mine.sh\":12,\"script/params.py\":64,\"script/programmable_plan.py\":205,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":277,\"src/IMD6900LockedPool.sol\":392,\"src/IMD6900RobinhoodLaunch.sol\":581,\"src/perps/PerpEngine.sol\":1158,\"src/perps/PerpSwapLib.sol\":728,\"src/perps/PerpVault.sol\":823,\"test/Edges.t.sol\":525,\"test/HookSalt.sol\":49,\"test/IMD6900RobinhoodLaunch.t.sol\":1113,\"test/Invariants.t.sol\":693,\"test/Perps.t.sol\":269,\"test/RobinhoodFork.t.sol\":255},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"83114168e52d936ca5d1552a10a78d6802d5241196f45f0320b2b2de80cd3813","verifiedTreeHash":"0b5d5e31cb6b5164e3324e5ec353222f81b26302","verifierVersion":"0.1.0+67c588fa"},{"checks":[{"durationMs":53897,"exitCode":0,"name":"build","output":"Compiling 92 files with Solc 0.8.26\nSolc 0.8.26 finished in 53.71s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpEngine.sol:349:5:\n    |\n349 |     function _quoteAt(uint256 size, uint256 sp) internal view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpVault.sol:364:5:\n    |\n364 |     function _toYieldUnit(uint256 v) private view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n","passed":true},{"durationMs":520,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodForkTest\n[PASS] test_TheWholeLaunchOnRobinhood() (gas: 2548)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 14.42ms (50.69µs CPU time)\n\nRan 11 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1047565)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AShortRoundTrip() (gas: 1842840)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1882445)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 650076)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120171)\n[PASS] test_PerpFeesReachThePot() (gas: 697375)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 1010484)\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145028)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 64551497)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14696281)\n[PASS] test_TheTimelocksSaleSweepsFirstAndKeepsItsRefund() (gas: 1537131)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 324.31ms (282.65ms CPU time)\n\nRan 55 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_ARefusedShareIsOwedNotReSplit() (gas: 4380922)\n[PASS] test_AnOwedShareIsPaidOnceThePayeeAccepts() (gas: 1826793)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 3047909)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1258052)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1460063)\n[PASS] test_ClaimsNeverPassTheReserve() (gas: 1640029)\n[PASS] test_ClaimsOneForOne() (gas: 1584754)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208651)\n[PASS] test_DeploysOnAFreshChain() (gas: 2483531)\n[PASS] test_DroppingAPayeeCancelsWhatItWasOwed() (gas: 1808575)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 4228508)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2337079)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599550457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1299344)\n[PASS] test_FitsOneTransaction() (gas: 4207418)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 7825\n  createHook gas: 1317833\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1853812)\nLogs:\n  pot bridge: 0.511887600000000000\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1429356)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1307447)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1298910)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2206157)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1968599)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288388119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2079624)\n[PASS] test_OneImdBuyATimestamp() (gas: 3488785)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 1998272)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 178856)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1972959)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1301486)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 68099)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 94057)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 118005)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1810492)\n[PASS] test_PassesTheAdmissionScan() (gas: 15630080)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1454954)\n[PASS] test_RecoverEthDropsTheBookings() (gas: 1504820)\n[PASS] test_RefusedPerpFeesWaitForTheNextForwarding() (gas: 2058474)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702494)\n[PASS] test_RefusesTheWrongAddress() (gas: 709853)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1273982)\n[PASS] test_TheBucketIsDebitedByWhatThePoolTook() (gas: 3324027)\nLogs:\n  the pool took: 0.007509328044963723\n\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1215534)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1807558)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1232579)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1296049)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 1850517)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2666822)\n[PASS] test_TheFeeIsStatic() (gas: 1585405)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 28588163)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1344188)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21856)\n[PASS] test_TheLaunchCantBeRenounced() (gas: 1287242)\n[PASS] test_TheLiquidityIsLocked() (gas: 2157676)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2318397)\n[PASS] test_TheSalesRefundSurvivesALiquidationInTheSweep() (gas: 3389393)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2111806)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1650051)\n[PASS] test_TradesBothWays() (gas: 1560978)\nSuite result: ok. 55 passed; 0 failed; 0 skipped; finished in 324.52ms (365.96ms CPU time)\n\nRan 3 test suites in 339.94ms (663.25ms CPU time): 67 tests passed, 0 failed, 0 skipped (67 total tests)\n","passed":true},{"durationMs":105,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMD6900.approve(address,uint256)\",\"IMD6900.burn(uint256)\",\"IMD6900.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"IMD6900.transfer(address,uint256)\",\"IMD6900.transferFrom(address,address,uint256)\",\"IMD6900Hook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"IMD6900Hook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"IMD6900Hook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"IMD6900Hook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"IMD6900Hook.cancelOwnershipHandover()\",\"IMD6900Hook.completeOwnershipHandover(address)\",\"IMD6900Hook.initPerpEngine(address)\",\"IMD6900Hook.renounceOwnership()\",\"IMD6900Hook.requestOwnershipHandover()\",\"IMD6900Hook.setPerpEngine(address)\",\"IMD6900Hook.setPerpRefunded(bool)\",\"IMD6900Hook.setSweepFailOpen(bool)\",\"IMD6900Hook.transferOwnership(address)\",\"IMD6900LockedPool.collect()\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(address)\",\"IMD6900LockedPool.setHook(address)\",\"IMD6900LockedPool.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.addFees()\",\"IMD6900RobinhoodLaunch.addPerpFees()\",\"IMD6900RobinhoodLaunch.burnImd(uint256)\",\"IMD6900RobinhoodLaunch.cancelOwnershipHandover()\",\"IMD6900RobinhoodLaunch.claim(uint256,uint256,bytes32[])\",\"IMD6900RobinhoodLaunch.completeOwnershipHandover(address)\",\"IMD6900RobinhoodLaunch.createHook(bytes32)\",\"IMD6900RobinhoodLaunch.endClaims()\",\"IMD6900RobinhoodLaunch.harvest()\",\"IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address)\",\"IMD6900RobinhoodLaunch.receive()\",\"IMD6900RobinhoodLaunch.recoverEth(address,uint256)\",\"IMD6900RobinhoodLaunch.releaseExcess(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseImdstr(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseUnclaimed(address)\",\"IMD6900RobinhoodLaunch.renounceOwnership()\",\"IMD6900RobinhoodLaunch.requestOwnershipHandover()\",\"IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address)\",\"IMD6900RobinhoodLaunch.setClaims(bytes32,uint256,uint256)\",\"IMD6900RobinhoodLaunch.setImdBurn(address)\",\"IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address)\",\"IMD6900RobinhoodLaunch.setPayees(address[],uint16[])\",\"IMD6900RobinhoodLaunch.setPerpFeeTo(address)\",\"IMD6900RobinhoodLaunch.split()\",\"IMD6900RobinhoodLaunch.transferOwnership(address)\",\"IMD6900RobinhoodLaunch.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.withdrawEth(address,uint256)\",\"PerpEngine.claimPayout()\",\"PerpEngine.close(uint256,uint256)\",\"PerpEngine.flushFees()\",\"PerpEngine.fundFromVault()\",\"PerpEngine.fundInsurance()\",\"PerpEngine.fundPlv()\",\"PerpEngine.fundPlvToken(uint256)\",\"PerpEngine.fundTokenFromVault(uint256)\",\"PerpEngine.liquidate(uint256)\",\"PerpEngine.openLong(uint8,uint256)\",\"PerpEngine.openShort(uint8,uint256)\",\"PerpEngine.poke()\",\"PerpEngine.receive()\",\"PerpEngine.selfSweep(address)\",\"PerpEngine.setFeeSink(address)\",\"PerpEngine.setFees(uint256,uint256,uint256)\",\"PerpEngine.setGuards(uint32,uint256,uint256)\",\"PerpEngine.setMinCollateral(uint256)\",\"PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256)\",\"PerpEngine.setShortCap(uint256)\",\"PerpEngine.setVault(address)\",\"PerpEngine.setVaultLimits(uint256,uint256)\",\"PerpEngine.setVaultSplit(uint256,uint256)\",\"PerpEngine.skimInsurance(uint256,address)\",\"PerpEngine.sweepLiquidations(address,int256,bool,uint160)\",\"PerpEngine.transferOwnership(address)\",\"PerpEngine.unlockCallback(bytes)\",\"PerpEngine.withdrawPlvTo(uint256,address)\",\"PerpEngine.withdrawPlvTokenTo(uint256,address)\",\"PerpEngine.withdrawTokYieldTo(uint256,address)\",\"PerpVault.claimPendingEth()\",\"PerpVault.claimPendingToken()\",\"PerpVault.claimTokYield()\",\"PerpVault.depositEth()\",\"PerpVault.depositToken(uint256)\",\"PerpVault.settlePendingEth(address)\",\"PerpVault.settlePendingToken(address)\",\"PerpVault.withdrawEth(uint256)\",\"PerpVault.withdrawToken(uint256)\"],\"files\":{\".gitignore\":15,\"ADAPTATION.md\":238,\"README.md\":147,\"foundry.toml\":30,\"jobs/swarm-launch.json\":27,\"launch.json\":15,\"script/claims.py\":88,\"script/launch.sh\":269,\"script/mine.sh\":12,\"script/params.py\":64,\"script/programmable_plan.py\":205,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":277,\"src/IMD6900LockedPool.sol\":392,\"src/IMD6900RobinhoodLaunch.sol\":629,\"src/perps/PerpEngine.sol\":1158,\"src/perps/PerpSwapLib.sol\":728,\"src/perps/PerpVault.sol\":823,\"test/HookSalt.sol\":50,\"test/IMD6900RobinhoodLaunch.t.sol\":1388,\"test/Perps.t.sol\":269,\"test/PoolManagerArtifact.sol\":8,\"test/RobinhoodFork.t.sol\":255},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"8600812c32f00968c509bd9248d6aa1a586d09ba082709edc694ea482f0c5632","verifiedTreeHash":"627935a5c350386b8fa8fdd71c6428de2de946df","verifierVersion":"0.1.0+67c588fa"},{"checks":[{"durationMs":27480,"exitCode":0,"name":"build","output":"Compiling 91 files with Solc 0.8.26\nSolc 0.8.26 finished in 27.38s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpEngine.sol:349:5:\n    |\n349 |     function _quoteAt(uint256 size, uint256 sp) internal view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpVault.sol:364:5:\n    |\n364 |     function _toYieldUnit(uint256 v) private view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n","passed":true},{"durationMs":295,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodForkTest\n[PASS] test_TheWholeLaunchOnRobinhood() (gas: 2548)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 296.18µs (31.45µs CPU time)\n\nRan 47 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_ARefusedShareStaysThePayeesOwn() (gas: 3519447)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 2951478)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1257507)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1459408)\n[PASS] test_ClaimsOneForOne() (gas: 1577827)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208651)\n[PASS] test_DeploysOnAFreshChain() (gas: 8061875)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 5407078)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2326729)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599550457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1298777)\n[PASS] test_FitsOneTransaction() (gas: 5385790)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 7825\n  createHook gas: 1266599\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1845133)\nLogs:\n  pot bridge: 0.511887600000000000\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1428767)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1306836)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1298387)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2201493)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1960326)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288388119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2081091)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 2004141)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 178900)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1971459)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1298864)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 68033)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 91605)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 117797)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1800616)\n[PASS] test_PassesTheAdmissionScan() (gas: 15580853)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1449657)\n[PASS] test_RecoveredEthIsOwedToNobody() (gas: 1639438)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702362)\n[PASS] test_RefusesTheWrongAddress() (gas: 709853)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1273437)\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1214945)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1800481)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1231990)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1295350)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2668800)\n[PASS] test_TheFeeIsStatic() (gas: 1581583)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 6981734)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1343643)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21856)\n[PASS] test_TheLiquidityIsLocked() (gas: 2153964)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2311392)\n[PASS] test_TheSaleStaysFeeFreeWhenTheSweepLiquidates() (gas: 2674110)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2117147)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1647319)\n[PASS] test_TradesBothWays() (gas: 1553946)\nSuite result: ok. 47 passed; 0 failed; 0 skipped; finished in 86.32ms (143.36ms CPU time)\n\nRan 10 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1051209)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AShortRoundTrip() (gas: 1843291)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1882206)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 651975)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120149)\n[PASS] test_PerpFeesReachThePot() (gas: 696841)\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145006)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 67539994)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14696259)\n[PASS] test_UnwiringTheEngineLeavesItsPositionsClosable() (gas: 2266370)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 202.32ms (174.48ms CPU time)\n\nRan 3 test suites in 203.09ms (288.93ms CPU time): 58 tests passed, 0 failed, 0 skipped (58 total tests)\n","passed":true},{"durationMs":45,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMD6900.approve(address,uint256)\",\"IMD6900.burn(uint256)\",\"IMD6900.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"IMD6900.transfer(address,uint256)\",\"IMD6900.transferFrom(address,address,uint256)\",\"IMD6900Hook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"IMD6900Hook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"IMD6900Hook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"IMD6900Hook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"IMD6900Hook.cancelOwnershipHandover()\",\"IMD6900Hook.completeOwnershipHandover(address)\",\"IMD6900Hook.initPerpEngine(address)\",\"IMD6900Hook.renounceOwnership()\",\"IMD6900Hook.requestOwnershipHandover()\",\"IMD6900Hook.setPerpEngine(address)\",\"IMD6900Hook.setPerpRefunded(bool)\",\"IMD6900Hook.setSweepFailOpen(bool)\",\"IMD6900Hook.transferOwnership(address)\",\"IMD6900LockedPool.collect()\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(address)\",\"IMD6900LockedPool.setHook(address)\",\"IMD6900LockedPool.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.addFees()\",\"IMD6900RobinhoodLaunch.addPerpFees()\",\"IMD6900RobinhoodLaunch.burnImd(uint256)\",\"IMD6900RobinhoodLaunch.cancelOwnershipHandover()\",\"IMD6900RobinhoodLaunch.claim(uint256,uint256,bytes32[])\",\"IMD6900RobinhoodLaunch.completeOwnershipHandover(address)\",\"IMD6900RobinhoodLaunch.createHook(bytes32)\",\"IMD6900RobinhoodLaunch.endClaims()\",\"IMD6900RobinhoodLaunch.harvest()\",\"IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address)\",\"IMD6900RobinhoodLaunch.payOwed(address)\",\"IMD6900RobinhoodLaunch.receive()\",\"IMD6900RobinhoodLaunch.recoverEth(address,uint256)\",\"IMD6900RobinhoodLaunch.releaseExcess(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseImdstr(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseUnclaimed(address)\",\"IMD6900RobinhoodLaunch.renounceOwnership()\",\"IMD6900RobinhoodLaunch.requestOwnershipHandover()\",\"IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address)\",\"IMD6900RobinhoodLaunch.setClaims(bytes32,uint256,uint256)\",\"IMD6900RobinhoodLaunch.setImdBurn(address)\",\"IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address)\",\"IMD6900RobinhoodLaunch.setPayees(address[],uint16[])\",\"IMD6900RobinhoodLaunch.setPerpFeeTo(address)\",\"IMD6900RobinhoodLaunch.split()\",\"IMD6900RobinhoodLaunch.transferOwnership(address)\",\"IMD6900RobinhoodLaunch.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.withdrawEth(address,uint256)\",\"PerpEngine.claimPayout()\",\"PerpEngine.close(uint256,uint256)\",\"PerpEngine.flushFees()\",\"PerpEngine.fundFromVault()\",\"PerpEngine.fundInsurance()\",\"PerpEngine.fundPlv()\",\"PerpEngine.fundPlvToken(uint256)\",\"PerpEngine.fundTokenFromVault(uint256)\",\"PerpEngine.liquidate(uint256)\",\"PerpEngine.openLong(uint8,uint256)\",\"PerpEngine.openShort(uint8,uint256)\",\"PerpEngine.poke()\",\"PerpEngine.receive()\",\"PerpEngine.selfSweep(address)\",\"PerpEngine.setFeeSink(address)\",\"PerpEngine.setFees(uint256,uint256,uint256)\",\"PerpEngine.setGuards(uint32,uint256,uint256)\",\"PerpEngine.setMinCollateral(uint256)\",\"PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256)\",\"PerpEngine.setShortCap(uint256)\",\"PerpEngine.setVault(address)\",\"PerpEngine.setVaultLimits(uint256,uint256)\",\"PerpEngine.setVaultSplit(uint256,uint256)\",\"PerpEngine.skimInsurance(uint256,address)\",\"PerpEngine.sweepLiquidations(address,int256,bool,uint160)\",\"PerpEngine.transferOwnership(address)\",\"PerpEngine.unlockCallback(bytes)\",\"PerpEngine.withdrawPlvTo(uint256,address)\",\"PerpEngine.withdrawPlvTokenTo(uint256,address)\",\"PerpEngine.withdrawTokYieldTo(uint256,address)\",\"PerpVault.claimPendingEth()\",\"PerpVault.claimPendingToken()\",\"PerpVault.claimTokYield()\",\"PerpVault.depositEth()\",\"PerpVault.depositToken(uint256)\",\"PerpVault.settlePendingEth(address)\",\"PerpVault.settlePendingToken(address)\",\"PerpVault.withdrawEth(uint256)\",\"PerpVault.withdrawToken(uint256)\"],\"files\":{\".gitignore\":15,\"ADAPTATION.md\":90,\"README.md\":148,\"foundry.toml\":30,\"jobs/swarm-launch.json\":27,\"script/claims.py\":88,\"script/launch.sh\":269,\"script/mine.sh\":12,\"script/params.py\":64,\"script/programmable_plan.py\":205,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":255,\"src/IMD6900LockedPool.sol\":456,\"src/IMD6900RobinhoodLaunch.sol\":585,\"src/perps/PerpEngine.sol\":1158,\"src/perps/PerpSwapLib.sol\":728,\"src/perps/PerpVault.sol\":823,\"test/HookSalt.sol\":51,\"test/IMD6900RobinhoodLaunch.t.sol\":1166,\"test/Perps.t.sol\":274,\"test/RobinhoodFork.t.sol\":255},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"static analysis could not run: slither produced no report: ib/python3.11/site-packages/slither/slithir/convert.py\", line 1981, in convert_constant_types\n    assert len(types) == len(ir.arguments)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nAssertionError\n'forge clean' running (wd: /tmp/imd-verify-wjpm4A/repo)\n'forge config --json' running\n'forge build --build-info --deny never --skip ./test/** ./script/** --force' running (wd: /tmp/imd-verify-wjpm4A/repo)\n","evaluation":"checks","profile":"foundry","status":"error","submissionHash":"8b10676a12b14f0d0a23a6305a6f8ebdb282227412ec3a1bfa78e104fc60ef77","verifiedTreeHash":"3a8e813b8245fd73c69a0ed50943f5a61d6d7413","verifierVersion":"0.1.0+7129bf9d"},{"checks":[{"durationMs":41722,"exitCode":0,"name":"build","output":"Compiling 91 files with Solc 0.8.26\nSolc 0.8.26 finished in 41.55s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpEngine.sol:349:5:\n    |\n349 |     function _quoteAt(uint256 size, uint256 sp) internal view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpVault.sol:364:5:\n    |\n364 |     function _toYieldUnit(uint256 v) private view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n","passed":true},{"durationMs":431,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodForkTest\n[PASS] test_TheWholeLaunchOnRobinhood() (gas: 2548)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 588.20µs (38.12µs CPU time)\n\nRan 50 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_ARefusedShareIsOwedNotReSplit() (gas: 4264189)\n[PASS] test_AnOwedShareIsPaidOnceThePayeeAccepts() (gas: 1819830)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 2954525)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1257943)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1459866)\n[PASS] test_ClaimsOneForOne() (gas: 1578197)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208629)\n[PASS] test_DeploysOnAFreshChain() (gas: 2483554)\n[PASS] test_DroppingAPayeeCancelsWhatItWasOwed() (gas: 1803760)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 4228486)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2332540)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599550457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1299235)\n[PASS] test_FitsOneTransaction() (gas: 4207308)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 7825\n  createHook gas: 1317833\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1851387)\nLogs:\n  pot bridge: 0.511887600000000000\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1429159)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1307250)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1298835)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2206073)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1966203)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288388119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2079427)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 1998185)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 178856)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1972872)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1299147)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 68033)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 93881)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 117917)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1808177)\n[PASS] test_PassesTheAdmissionScan() (gas: 15553891)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1450049)\n[PASS] test_RecoverEthDropsTheBookings() (gas: 1497799)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702393)\n[PASS] test_RefusesTheWrongAddress() (gas: 709821)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1273848)\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1215425)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1807361)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1232448)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1295852)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 1850408)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2666735)\n[PASS] test_TheFeeIsStatic() (gas: 1585230)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 28588053)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1344101)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21856)\n[PASS] test_TheLiquidityIsLocked() (gas: 2157589)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2318288)\n[PASS] test_TheSalesRefundSurvivesALiquidationInTheSweep() (gas: 3389269)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2111689)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1647711)\n[PASS] test_TradesBothWays() (gas: 1560803)\nSuite result: ok. 50 passed; 0 failed; 0 skipped; finished in 180.40ms (263.87ms CPU time)\n\nRan 11 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1047565)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AShortRoundTrip() (gas: 1842840)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1882445)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 650076)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120171)\n[PASS] test_PerpFeesReachThePot() (gas: 695010)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 1010484)\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145028)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 64551366)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14696281)\n[PASS] test_TheTimelocksSaleSweepsFirstAndKeepsItsRefund() (gas: 1537116)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 277.38ms (267.67ms CPU time)\n\nRan 3 test suites in 278.37ms (458.36ms CPU time): 62 tests passed, 0 failed, 0 skipped (62 total tests)\n","passed":true},{"durationMs":97,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMD6900.approve(address,uint256)\",\"IMD6900.burn(uint256)\",\"IMD6900.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"IMD6900.transfer(address,uint256)\",\"IMD6900.transferFrom(address,address,uint256)\",\"IMD6900Hook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"IMD6900Hook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"IMD6900Hook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"IMD6900Hook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"IMD6900Hook.cancelOwnershipHandover()\",\"IMD6900Hook.completeOwnershipHandover(address)\",\"IMD6900Hook.initPerpEngine(address)\",\"IMD6900Hook.renounceOwnership()\",\"IMD6900Hook.requestOwnershipHandover()\",\"IMD6900Hook.setPerpEngine(address)\",\"IMD6900Hook.setPerpRefunded(bool)\",\"IMD6900Hook.setSweepFailOpen(bool)\",\"IMD6900Hook.transferOwnership(address)\",\"IMD6900LockedPool.collect()\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(address)\",\"IMD6900LockedPool.setHook(address)\",\"IMD6900LockedPool.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.addFees()\",\"IMD6900RobinhoodLaunch.addPerpFees()\",\"IMD6900RobinhoodLaunch.burnImd(uint256)\",\"IMD6900RobinhoodLaunch.cancelOwnershipHandover()\",\"IMD6900RobinhoodLaunch.claim(uint256,uint256,bytes32[])\",\"IMD6900RobinhoodLaunch.completeOwnershipHandover(address)\",\"IMD6900RobinhoodLaunch.createHook(bytes32)\",\"IMD6900RobinhoodLaunch.endClaims()\",\"IMD6900RobinhoodLaunch.harvest()\",\"IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address)\",\"IMD6900RobinhoodLaunch.receive()\",\"IMD6900RobinhoodLaunch.recoverEth(address,uint256)\",\"IMD6900RobinhoodLaunch.releaseExcess(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseImdstr(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseUnclaimed(address)\",\"IMD6900RobinhoodLaunch.renounceOwnership()\",\"IMD6900RobinhoodLaunch.requestOwnershipHandover()\",\"IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address)\",\"IMD6900RobinhoodLaunch.setClaims(bytes32,uint256,uint256)\",\"IMD6900RobinhoodLaunch.setImdBurn(address)\",\"IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address)\",\"IMD6900RobinhoodLaunch.setPayees(address[],uint16[])\",\"IMD6900RobinhoodLaunch.setPerpFeeTo(address)\",\"IMD6900RobinhoodLaunch.split()\",\"IMD6900RobinhoodLaunch.transferOwnership(address)\",\"IMD6900RobinhoodLaunch.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.withdrawEth(address,uint256)\",\"PerpEngine.claimPayout()\",\"PerpEngine.close(uint256,uint256)\",\"PerpEngine.flushFees()\",\"PerpEngine.fundFromVault()\",\"PerpEngine.fundInsurance()\",\"PerpEngine.fundPlv()\",\"PerpEngine.fundPlvToken(uint256)\",\"PerpEngine.fundTokenFromVault(uint256)\",\"PerpEngine.liquidate(uint256)\",\"PerpEngine.openLong(uint8,uint256)\",\"PerpEngine.openShort(uint8,uint256)\",\"PerpEngine.poke()\",\"PerpEngine.receive()\",\"PerpEngine.selfSweep(address)\",\"PerpEngine.setFeeSink(address)\",\"PerpEngine.setFees(uint256,uint256,uint256)\",\"PerpEngine.setGuards(uint32,uint256,uint256)\",\"PerpEngine.setMinCollateral(uint256)\",\"PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256)\",\"PerpEngine.setShortCap(uint256)\",\"PerpEngine.setVault(address)\",\"PerpEngine.setVaultLimits(uint256,uint256)\",\"PerpEngine.setVaultSplit(uint256,uint256)\",\"PerpEngine.skimInsurance(uint256,address)\",\"PerpEngine.sweepLiquidations(address,int256,bool,uint160)\",\"PerpEngine.transferOwnership(address)\",\"PerpEngine.unlockCallback(bytes)\",\"PerpEngine.withdrawPlvTo(uint256,address)\",\"PerpEngine.withdrawPlvTokenTo(uint256,address)\",\"PerpEngine.withdrawTokYieldTo(uint256,address)\",\"PerpVault.claimPendingEth()\",\"PerpVault.claimPendingToken()\",\"PerpVault.claimTokYield()\",\"PerpVault.depositEth()\",\"PerpVault.depositToken(uint256)\",\"PerpVault.settlePendingEth(address)\",\"PerpVault.settlePendingToken(address)\",\"PerpVault.withdrawEth(uint256)\",\"PerpVault.withdrawToken(uint256)\"],\"files\":{\".gitignore\":15,\"ADAPTATION.md\":165,\"README.md\":145,\"foundry.toml\":30,\"jobs/swarm-launch.json\":27,\"script/claims.py\":88,\"script/launch.sh\":269,\"script/mine.sh\":12,\"script/params.py\":64,\"script/programmable_plan.py\":205,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":277,\"src/IMD6900LockedPool.sol\":392,\"src/IMD6900RobinhoodLaunch.sol\":581,\"src/perps/PerpEngine.sol\":1158,\"src/perps/PerpSwapLib.sol\":728,\"src/perps/PerpVault.sol\":823,\"test/HookSalt.sol\":49,\"test/IMD6900RobinhoodLaunch.t.sol\":1113,\"test/Perps.t.sol\":269,\"test/RobinhoodFork.t.sol\":255},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"92caee231bfda987436f8babc2ab8a7d7cacb8ff95ffddf501fc164db0590b2c","verifiedTreeHash":"74883b04592cb18efc77e5f29aac8b933a26e6ec","verifierVersion":"0.1.0+67c588fa"},{"checks":[{"durationMs":42481,"exitCode":0,"name":"build","output":"Compiling 91 files with Solc 0.8.26\nSolc 0.8.26 finished in 42.34s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpEngine.sol:349:5:\n    |\n349 |     function _quoteAt(uint256 size, uint256 sp) internal view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpVault.sol:364:5:\n    |\n364 |     function _toYieldUnit(uint256 v) private view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n","passed":true},{"durationMs":476,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodForkTest\n[PASS] test_TheWholeLaunchOnRobinhood() (gas: 2548)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 561.51µs (48.08µs CPU time)\n\nRan 50 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_ARefusedShareIsOwedNotReSplit() (gas: 4264189)\n[PASS] test_AnOwedShareIsPaidOnceThePayeeAccepts() (gas: 1819830)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 2954525)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1257943)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1459866)\n[PASS] test_ClaimsOneForOne() (gas: 1578197)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208629)\n[PASS] test_DeploysOnAFreshChain() (gas: 2483554)\n[PASS] test_DroppingAPayeeCancelsWhatItWasOwed() (gas: 1803760)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 4228486)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2332540)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599550457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1299235)\n[PASS] test_FitsOneTransaction() (gas: 4207308)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 7825\n  createHook gas: 1317833\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1851387)\nLogs:\n  pot bridge: 0.511887600000000000\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1429159)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1307250)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1298835)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2206073)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1966203)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288388119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2079427)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 1998185)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 178856)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1972872)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1299147)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 68033)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 93881)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 117917)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1808177)\n[PASS] test_PassesTheAdmissionScan() (gas: 15553891)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1450049)\n[PASS] test_RecoverEthDropsTheBookings() (gas: 1497799)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702393)\n[PASS] test_RefusesTheWrongAddress() (gas: 709821)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1273848)\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1215425)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1807361)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1232448)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1295852)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 1850408)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2666735)\n[PASS] test_TheFeeIsStatic() (gas: 1585230)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 28588053)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1344101)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21856)\n[PASS] test_TheLiquidityIsLocked() (gas: 2157589)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2318288)\n[PASS] test_TheSalesRefundSurvivesALiquidationInTheSweep() (gas: 3389269)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2111689)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1647711)\n[PASS] test_TradesBothWays() (gas: 1560803)\nSuite result: ok. 50 passed; 0 failed; 0 skipped; finished in 199.29ms (342.57ms CPU time)\n\nRan 11 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1047565)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AShortRoundTrip() (gas: 1842840)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1882445)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 650076)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120171)\n[PASS] test_PerpFeesReachThePot() (gas: 695010)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 1010484)\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145028)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 64551366)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14696281)\n[PASS] test_TheTimelocksSaleSweepsFirstAndKeepsItsRefund() (gas: 1537116)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 305.31ms (294.29ms CPU time)\n\nRan 3 test suites in 306.22ms (505.16ms CPU time): 62 tests passed, 0 failed, 0 skipped (62 total tests)\n","passed":true},{"durationMs":112,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMD6900.approve(address,uint256)\",\"IMD6900.burn(uint256)\",\"IMD6900.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"IMD6900.transfer(address,uint256)\",\"IMD6900.transferFrom(address,address,uint256)\",\"IMD6900Hook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"IMD6900Hook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"IMD6900Hook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"IMD6900Hook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"IMD6900Hook.cancelOwnershipHandover()\",\"IMD6900Hook.completeOwnershipHandover(address)\",\"IMD6900Hook.initPerpEngine(address)\",\"IMD6900Hook.renounceOwnership()\",\"IMD6900Hook.requestOwnershipHandover()\",\"IMD6900Hook.setPerpEngine(address)\",\"IMD6900Hook.setPerpRefunded(bool)\",\"IMD6900Hook.setSweepFailOpen(bool)\",\"IMD6900Hook.transferOwnership(address)\",\"IMD6900LockedPool.collect()\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(address)\",\"IMD6900LockedPool.setHook(address)\",\"IMD6900LockedPool.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.addFees()\",\"IMD6900RobinhoodLaunch.addPerpFees()\",\"IMD6900RobinhoodLaunch.burnImd(uint256)\",\"IMD6900RobinhoodLaunch.cancelOwnershipHandover()\",\"IMD6900RobinhoodLaunch.claim(uint256,uint256,bytes32[])\",\"IMD6900RobinhoodLaunch.completeOwnershipHandover(address)\",\"IMD6900RobinhoodLaunch.createHook(bytes32)\",\"IMD6900RobinhoodLaunch.endClaims()\",\"IMD6900RobinhoodLaunch.harvest()\",\"IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address)\",\"IMD6900RobinhoodLaunch.receive()\",\"IMD6900RobinhoodLaunch.recoverEth(address,uint256)\",\"IMD6900RobinhoodLaunch.releaseExcess(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseImdstr(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseUnclaimed(address)\",\"IMD6900RobinhoodLaunch.renounceOwnership()\",\"IMD6900RobinhoodLaunch.requestOwnershipHandover()\",\"IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address)\",\"IMD6900RobinhoodLaunch.setClaims(bytes32,uint256,uint256)\",\"IMD6900RobinhoodLaunch.setImdBurn(address)\",\"IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address)\",\"IMD6900RobinhoodLaunch.setPayees(address[],uint16[])\",\"IMD6900RobinhoodLaunch.setPerpFeeTo(address)\",\"IMD6900RobinhoodLaunch.split()\",\"IMD6900RobinhoodLaunch.transferOwnership(address)\",\"IMD6900RobinhoodLaunch.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.withdrawEth(address,uint256)\",\"PerpEngine.claimPayout()\",\"PerpEngine.close(uint256,uint256)\",\"PerpEngine.flushFees()\",\"PerpEngine.fundFromVault()\",\"PerpEngine.fundInsurance()\",\"PerpEngine.fundPlv()\",\"PerpEngine.fundPlvToken(uint256)\",\"PerpEngine.fundTokenFromVault(uint256)\",\"PerpEngine.liquidate(uint256)\",\"PerpEngine.openLong(uint8,uint256)\",\"PerpEngine.openShort(uint8,uint256)\",\"PerpEngine.poke()\",\"PerpEngine.receive()\",\"PerpEngine.selfSweep(address)\",\"PerpEngine.setFeeSink(address)\",\"PerpEngine.setFees(uint256,uint256,uint256)\",\"PerpEngine.setGuards(uint32,uint256,uint256)\",\"PerpEngine.setMinCollateral(uint256)\",\"PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256)\",\"PerpEngine.setShortCap(uint256)\",\"PerpEngine.setVault(address)\",\"PerpEngine.setVaultLimits(uint256,uint256)\",\"PerpEngine.setVaultSplit(uint256,uint256)\",\"PerpEngine.skimInsurance(uint256,address)\",\"PerpEngine.sweepLiquidations(address,int256,bool,uint160)\",\"PerpEngine.transferOwnership(address)\",\"PerpEngine.unlockCallback(bytes)\",\"PerpEngine.withdrawPlvTo(uint256,address)\",\"PerpEngine.withdrawPlvTokenTo(uint256,address)\",\"PerpEngine.withdrawTokYieldTo(uint256,address)\",\"PerpVault.claimPendingEth()\",\"PerpVault.claimPendingToken()\",\"PerpVault.claimTokYield()\",\"PerpVault.depositEth()\",\"PerpVault.depositToken(uint256)\",\"PerpVault.settlePendingEth(address)\",\"PerpVault.settlePendingToken(address)\",\"PerpVault.withdrawEth(uint256)\",\"PerpVault.withdrawToken(uint256)\"],\"files\":{\".gitignore\":15,\"ADAPTATION.md\":124,\"README.md\":145,\"foundry.toml\":30,\"jobs/swarm-launch.json\":27,\"launch.json\":15,\"script/claims.py\":88,\"script/launch.sh\":269,\"script/mine.sh\":12,\"script/params.py\":64,\"script/programmable_plan.py\":205,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":277,\"src/IMD6900LockedPool.sol\":392,\"src/IMD6900RobinhoodLaunch.sol\":581,\"src/perps/PerpEngine.sol\":1158,\"src/perps/PerpSwapLib.sol\":728,\"src/perps/PerpVault.sol\":823,\"test/HookSalt.sol\":49,\"test/IMD6900RobinhoodLaunch.t.sol\":1113,\"test/Perps.t.sol\":269,\"test/RobinhoodFork.t.sol\":255},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"b595e8ce372ce14c7d7bda6934db4ba5ba61bd44418f1caf49be845998ef5d43","verifiedTreeHash":"902405580ecd195d55b56432f6dc7c6dc8cee61d","verifierVersion":"0.1.0+67c588fa"},{"checks":[{"durationMs":28000,"exitCode":0,"name":"build","output":"Compiling 91 files with Solc 0.8.26\nSolc 0.8.26 finished in 27.91s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpEngine.sol:349:5:\n    |\n349 |     function _quoteAt(uint256 size, uint256 sp) internal view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpVault.sol:364:5:\n    |\n364 |     function _toYieldUnit(uint256 v) private view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n","passed":true},{"durationMs":342,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodForkTest\n[PASS] test_TheWholeLaunchOnRobinhood() (gas: 2548)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 358.86µs (30.19µs CPU time)\n\nRan 46 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_ARefusedShareIsOwedToItsPayeeAlone() (gas: 3337969)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 2948639)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1257485)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1459386)\n[PASS] test_ClaimsOneForOne() (gas: 1577827)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208629)\n[PASS] test_DeploysOnAFreshChain() (gas: 8061875)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 5407056)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2324391)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599550457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1298755)\n[PASS] test_FitsOneTransaction() (gas: 5385768)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 7825\n  createHook gas: 1266599\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1842269)\nLogs:\n  pot bridge: 0.511887600000000000\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1428723)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1306858)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1298387)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2201436)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1957988)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288388119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2080970)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 2004141)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 178900)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1971402)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1298864)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 68011)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 91561)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 117753)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1797774)\n[PASS] test_PassesTheAdmissionScan() (gas: 15511600)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1449613)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702318)\n[PASS] test_RefusesTheWrongAddress() (gas: 709809)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1273437)\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1214967)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1800459)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1231946)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1295306)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2669176)\n[PASS] test_TheFeeIsStatic() (gas: 1581539)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 6981712)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1343665)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21856)\n[PASS] test_TheLiquidityIsLocked() (gas: 2153964)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2311370)\n[PASS] test_TheSaleKeepsItsRefundWhenTheSweepTrades() (gas: 2624190)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2118275)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1647297)\n[PASS] test_TradesBothWays() (gas: 1553902)\nSuite result: ok. 46 passed; 0 failed; 0 skipped; finished in 236.22ms (138.53ms CPU time)\n\nRan 10 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1051591)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AShortRoundTrip() (gas: 1843644)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1882582)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 652145)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120127)\n[PASS] test_PerpFeesReachThePot() (gas: 697029)\n[PASS] test_PositionsStayClosableOnceTheEngineIsUnwired() (gas: 2829139)\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 144972)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 67539972)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14696259)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 237.58ms (184.00ms CPU time)\n\nRan 3 test suites in 238.99ms (474.16ms CPU time): 57 tests passed, 0 failed, 0 skipped (57 total tests)\n","passed":true},{"durationMs":50,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMD6900.approve(address,uint256)\",\"IMD6900.burn(uint256)\",\"IMD6900.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"IMD6900.transfer(address,uint256)\",\"IMD6900.transferFrom(address,address,uint256)\",\"IMD6900Hook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"IMD6900Hook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"IMD6900Hook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"IMD6900Hook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"IMD6900Hook.cancelOwnershipHandover()\",\"IMD6900Hook.completeOwnershipHandover(address)\",\"IMD6900Hook.initPerpEngine(address)\",\"IMD6900Hook.renounceOwnership()\",\"IMD6900Hook.requestOwnershipHandover()\",\"IMD6900Hook.setPerpEngine(address)\",\"IMD6900Hook.setPerpRefunded(bool)\",\"IMD6900Hook.setSweepFailOpen(bool)\",\"IMD6900Hook.transferOwnership(address)\",\"IMD6900LockedPool.collect()\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(address)\",\"IMD6900LockedPool.setHook(address)\",\"IMD6900LockedPool.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.addFees()\",\"IMD6900RobinhoodLaunch.addPerpFees()\",\"IMD6900RobinhoodLaunch.burnImd(uint256)\",\"IMD6900RobinhoodLaunch.cancelOwnershipHandover()\",\"IMD6900RobinhoodLaunch.claim(uint256,uint256,bytes32[])\",\"IMD6900RobinhoodLaunch.completeOwnershipHandover(address)\",\"IMD6900RobinhoodLaunch.createHook(bytes32)\",\"IMD6900RobinhoodLaunch.endClaims()\",\"IMD6900RobinhoodLaunch.harvest()\",\"IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address)\",\"IMD6900RobinhoodLaunch.payOwed(address)\",\"IMD6900RobinhoodLaunch.receive()\",\"IMD6900RobinhoodLaunch.recoverEth(address,uint256)\",\"IMD6900RobinhoodLaunch.releaseExcess(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseImdstr(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseUnclaimed(address)\",\"IMD6900RobinhoodLaunch.renounceOwnership()\",\"IMD6900RobinhoodLaunch.requestOwnershipHandover()\",\"IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address)\",\"IMD6900RobinhoodLaunch.setClaims(bytes32,uint256,uint256)\",\"IMD6900RobinhoodLaunch.setImdBurn(address)\",\"IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address)\",\"IMD6900RobinhoodLaunch.setPayees(address[],uint16[])\",\"IMD6900RobinhoodLaunch.setPerpFeeTo(address)\",\"IMD6900RobinhoodLaunch.split()\",\"IMD6900RobinhoodLaunch.transferOwnership(address)\",\"IMD6900RobinhoodLaunch.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.withdrawEth(address,uint256)\",\"PerpEngine.claimPayout()\",\"PerpEngine.close(uint256,uint256)\",\"PerpEngine.flushFees()\",\"PerpEngine.fundFromVault()\",\"PerpEngine.fundInsurance()\",\"PerpEngine.fundPlv()\",\"PerpEngine.fundPlvToken(uint256)\",\"PerpEngine.fundTokenFromVault(uint256)\",\"PerpEngine.liquidate(uint256)\",\"PerpEngine.openLong(uint8,uint256)\",\"PerpEngine.openShort(uint8,uint256)\",\"PerpEngine.poke()\",\"PerpEngine.receive()\",\"PerpEngine.selfSweep(address)\",\"PerpEngine.setFeeSink(address)\",\"PerpEngine.setFees(uint256,uint256,uint256)\",\"PerpEngine.setGuards(uint32,uint256,uint256)\",\"PerpEngine.setMinCollateral(uint256)\",\"PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256)\",\"PerpEngine.setShortCap(uint256)\",\"PerpEngine.setVault(address)\",\"PerpEngine.setVaultLimits(uint256,uint256)\",\"PerpEngine.setVaultSplit(uint256,uint256)\",\"PerpEngine.skimInsurance(uint256,address)\",\"PerpEngine.sweepLiquidations(address,int256,bool,uint160)\",\"PerpEngine.transferOwnership(address)\",\"PerpEngine.unlockCallback(bytes)\",\"PerpEngine.withdrawPlvTo(uint256,address)\",\"PerpEngine.withdrawPlvTokenTo(uint256,address)\",\"PerpEngine.withdrawTokYieldTo(uint256,address)\",\"PerpVault.claimPendingEth()\",\"PerpVault.claimPendingToken()\",\"PerpVault.claimTokYield()\",\"PerpVault.depositEth()\",\"PerpVault.depositToken(uint256)\",\"PerpVault.settlePendingEth(address)\",\"PerpVault.settlePendingToken(address)\",\"PerpVault.withdrawEth(uint256)\",\"PerpVault.withdrawToken(uint256)\"],\"files\":{\".gitignore\":15,\"ADAPTATION.md\":92,\"README.md\":147,\"foundry.toml\":30,\"jobs/swarm-launch.json\":27,\"script/claims.py\":88,\"script/launch.sh\":269,\"script/mine.sh\":12,\"script/params.py\":64,\"script/programmable_plan.py\":205,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":255,\"src/IMD6900LockedPool.sol\":459,\"src/IMD6900RobinhoodLaunch.sol\":581,\"src/perps/PerpEngine.sol\":1158,\"src/perps/PerpSwapLib.sol\":728,\"src/perps/PerpVault.sol\":823,\"test/HookSalt.sol\":50,\"test/IMD6900RobinhoodLaunch.t.sol\":1130,\"test/Perps.t.sol\":270,\"test/RobinhoodFork.t.sol\":255},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"static analysis could not run: slither produced no report: ib/python3.11/site-packages/slither/slithir/convert.py\", line 1981, in convert_constant_types\n    assert len(types) == len(ir.arguments)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nAssertionError\n'forge clean' running (wd: /tmp/imd-verify-QRh3GR/repo)\n'forge config --json' running\n'forge build --build-info --deny never --skip ./test/** ./script/** --force' running (wd: /tmp/imd-verify-QRh3GR/repo)\n","evaluation":"checks","profile":"foundry","status":"error","submissionHash":"bd7d9ae50a2fba0386f6e4e3ac6c349198d8380e9a5f709e8147af82caf676cf","verifiedTreeHash":"8f3a5df117af9cd4c886f63b11f990aea395cc02","verifierVersion":"0.1.0+7129bf9d"},{"checks":[{"durationMs":42052,"exitCode":0,"name":"build","output":"Compiling 91 files with Solc 0.8.26\nSolc 0.8.26 finished in 41.89s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpEngine.sol:349:5:\n    |\n349 |     function _quoteAt(uint256 size, uint256 sp) internal view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpVault.sol:364:5:\n    |\n364 |     function _toYieldUnit(uint256 v) private view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n","passed":true},{"durationMs":479,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodForkTest\n[PASS] test_TheWholeLaunchOnRobinhood() (gas: 2548)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 652.85µs (46.90µs CPU time)\n\nRan 11 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1047565)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AShortRoundTrip() (gas: 1842840)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1882445)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 650076)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120171)\n[PASS] test_PerpFeesReachThePot() (gas: 695010)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 1010484)\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145028)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 64551366)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14696281)\n[PASS] test_TheTimelocksSaleSweepsFirstAndKeepsItsRefund() (gas: 1537116)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 318.96ms (295.35ms CPU time)\n\nRan 50 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_ARefusedShareIsOwedNotReSplit() (gas: 4264189)\n[PASS] test_AnOwedShareIsPaidOnceThePayeeAccepts() (gas: 1819830)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 2954525)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1257943)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1459866)\n[PASS] test_ClaimsOneForOne() (gas: 1578197)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208629)\n[PASS] test_DeploysOnAFreshChain() (gas: 2483554)\n[PASS] test_DroppingAPayeeCancelsWhatItWasOwed() (gas: 1803760)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 4228486)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2332540)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599550457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1299235)\n[PASS] test_FitsOneTransaction() (gas: 4207308)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 7825\n  createHook gas: 1317833\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1851387)\nLogs:\n  pot bridge: 0.511887600000000000\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1429159)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1307250)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1298835)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2206073)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1966203)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288388119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2079427)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 1998185)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 178856)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1972872)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1299147)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 68033)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 93881)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 117917)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1808177)\n[PASS] test_PassesTheAdmissionScan() (gas: 15553891)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1450049)\n[PASS] test_RecoverEthDropsTheBookings() (gas: 1497799)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702393)\n[PASS] test_RefusesTheWrongAddress() (gas: 709821)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1273848)\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1215425)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1807361)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1232448)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1295852)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 1850408)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2666735)\n[PASS] test_TheFeeIsStatic() (gas: 1585230)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 28588053)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1344101)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21856)\n[PASS] test_TheLiquidityIsLocked() (gas: 2157589)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2318288)\n[PASS] test_TheSalesRefundSurvivesALiquidationInTheSweep() (gas: 3389269)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2111689)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1647711)\n[PASS] test_TradesBothWays() (gas: 1560803)\nSuite result: ok. 50 passed; 0 failed; 0 skipped; finished in 319.13ms (267.32ms CPU time)\n\nRan 3 test suites in 320.26ms (638.75ms CPU time): 62 tests passed, 0 failed, 0 skipped (62 total tests)\n","passed":true},{"durationMs":114,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMD6900.approve(address,uint256)\",\"IMD6900.burn(uint256)\",\"IMD6900.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"IMD6900.transfer(address,uint256)\",\"IMD6900.transferFrom(address,address,uint256)\",\"IMD6900Hook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"IMD6900Hook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"IMD6900Hook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"IMD6900Hook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"IMD6900Hook.cancelOwnershipHandover()\",\"IMD6900Hook.completeOwnershipHandover(address)\",\"IMD6900Hook.initPerpEngine(address)\",\"IMD6900Hook.renounceOwnership()\",\"IMD6900Hook.requestOwnershipHandover()\",\"IMD6900Hook.setPerpEngine(address)\",\"IMD6900Hook.setPerpRefunded(bool)\",\"IMD6900Hook.setSweepFailOpen(bool)\",\"IMD6900Hook.transferOwnership(address)\",\"IMD6900LockedPool.collect()\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(address)\",\"IMD6900LockedPool.setHook(address)\",\"IMD6900LockedPool.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.addFees()\",\"IMD6900RobinhoodLaunch.addPerpFees()\",\"IMD6900RobinhoodLaunch.burnImd(uint256)\",\"IMD6900RobinhoodLaunch.cancelOwnershipHandover()\",\"IMD6900RobinhoodLaunch.claim(uint256,uint256,bytes32[])\",\"IMD6900RobinhoodLaunch.completeOwnershipHandover(address)\",\"IMD6900RobinhoodLaunch.createHook(bytes32)\",\"IMD6900RobinhoodLaunch.endClaims()\",\"IMD6900RobinhoodLaunch.harvest()\",\"IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address)\",\"IMD6900RobinhoodLaunch.receive()\",\"IMD6900RobinhoodLaunch.recoverEth(address,uint256)\",\"IMD6900RobinhoodLaunch.releaseExcess(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseImdstr(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseUnclaimed(address)\",\"IMD6900RobinhoodLaunch.renounceOwnership()\",\"IMD6900RobinhoodLaunch.requestOwnershipHandover()\",\"IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address)\",\"IMD6900RobinhoodLaunch.setClaims(bytes32,uint256,uint256)\",\"IMD6900RobinhoodLaunch.setImdBurn(address)\",\"IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address)\",\"IMD6900RobinhoodLaunch.setPayees(address[],uint16[])\",\"IMD6900RobinhoodLaunch.setPerpFeeTo(address)\",\"IMD6900RobinhoodLaunch.split()\",\"IMD6900RobinhoodLaunch.transferOwnership(address)\",\"IMD6900RobinhoodLaunch.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.withdrawEth(address,uint256)\",\"PerpEngine.claimPayout()\",\"PerpEngine.close(uint256,uint256)\",\"PerpEngine.flushFees()\",\"PerpEngine.fundFromVault()\",\"PerpEngine.fundInsurance()\",\"PerpEngine.fundPlv()\",\"PerpEngine.fundPlvToken(uint256)\",\"PerpEngine.fundTokenFromVault(uint256)\",\"PerpEngine.liquidate(uint256)\",\"PerpEngine.openLong(uint8,uint256)\",\"PerpEngine.openShort(uint8,uint256)\",\"PerpEngine.poke()\",\"PerpEngine.receive()\",\"PerpEngine.selfSweep(address)\",\"PerpEngine.setFeeSink(address)\",\"PerpEngine.setFees(uint256,uint256,uint256)\",\"PerpEngine.setGuards(uint32,uint256,uint256)\",\"PerpEngine.setMinCollateral(uint256)\",\"PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256)\",\"PerpEngine.setShortCap(uint256)\",\"PerpEngine.setVault(address)\",\"PerpEngine.setVaultLimits(uint256,uint256)\",\"PerpEngine.setVaultSplit(uint256,uint256)\",\"PerpEngine.skimInsurance(uint256,address)\",\"PerpEngine.sweepLiquidations(address,int256,bool,uint160)\",\"PerpEngine.transferOwnership(address)\",\"PerpEngine.unlockCallback(bytes)\",\"PerpEngine.withdrawPlvTo(uint256,address)\",\"PerpEngine.withdrawPlvTokenTo(uint256,address)\",\"PerpEngine.withdrawTokYieldTo(uint256,address)\",\"PerpVault.claimPendingEth()\",\"PerpVault.claimPendingToken()\",\"PerpVault.claimTokYield()\",\"PerpVault.depositEth()\",\"PerpVault.depositToken(uint256)\",\"PerpVault.settlePendingEth(address)\",\"PerpVault.settlePendingToken(address)\",\"PerpVault.withdrawEth(uint256)\",\"PerpVault.withdrawToken(uint256)\"],\"files\":{\".gitignore\":15,\"ADAPTATION.md\":124,\"README.md\":145,\"foundry.toml\":30,\"jobs/swarm-launch.json\":27,\"script/claims.py\":88,\"script/launch.sh\":269,\"script/mine.sh\":12,\"script/params.py\":64,\"script/programmable_plan.py\":205,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":277,\"src/IMD6900LockedPool.sol\":392,\"src/IMD6900RobinhoodLaunch.sol\":581,\"src/perps/PerpEngine.sol\":1158,\"src/perps/PerpSwapLib.sol\":728,\"src/perps/PerpVault.sol\":823,\"test/HookSalt.sol\":49,\"test/IMD6900RobinhoodLaunch.t.sol\":1113,\"test/Perps.t.sol\":269,\"test/RobinhoodFork.t.sol\":255},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":17923,"exitCode":0,"name":"slither","output":"Slither crashed on this tree, an analyser bug and not a finding, so its rejecting detectors did not run; Aderyn's report below stands in. slither produced no report: ib/python3.11/site-packages/slither/slithir/convert.py\", line 1981, in convert_constant_types\n    assert len(types) == len(ir.arguments)\n           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\nAssertionError\n'forge clean' running (wd: /tmp/imd-verify-7VuD2j/repo)\n'forge config --json' running\n'forge build --build-info --deny never --skip ./test/** ./script/** --force' running (wd: /tmp/imd-verify-7VuD2j/repo)\n","passed":true},{"durationMs":1374,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/IMD6900LockedPool.sol:230: `abi.encodePacked()` Hash Collision (5 places)\n[high] contract-locks-ether at src/IMD6900Hook.sol:42: Contract locks Ether without a withdraw function (3 places)\n[high] eth-send-unchecked-address at src/perps/PerpEngine.sol:953: ETH transferred without address checks\n[high] reentrancy-state-change at src/IMD6900LockedPool.sol:160: Reentrancy: State change after external call (19 places)\n[high] storage-array-memory-edit at src/perps/PerpEngine.sol:682: Storage Array Edited with Memory\n[high] unsafe-casting at src/perps/PerpEngine.sol:863: Unsafe Casting of integers\n[low] centralization-risk at src/IMD6900Hook.sol:42: Centralization Risk (24 places)\n[low] costly-loop at src/IMD6900RobinhoodLaunch.sol:342: Costly operations inside loop (7 places)\n[low] large-numeric-literal at src/IMD6900.sol:11: Large Numeric Literal (22 places)\n[low] literal-instead-of-constant at src/IMD6900Hook.sol:111: Literal Instead of Constant (32 places)\n[low] local-variable-shadowing at src/perps/PerpEngine.sol:267: Local Variable Shadows State Variable\n[low] non-reentrant-not-first at src/IMD6900RobinhoodLaunch.sol:263: `nonReentrant` is Not the First Modifier (7 places)\n[low] push-zero-opcode at src/IMD6900.sol:2: PUSH0 Opcode (7 places)\n[low] require-revert-in-loop at src/IMD6900RobinhoodLaunch.sol:342: Loop Contains `require`/`revert` (4 places)\n[low] state-change-without-event at src/perps/PerpEngine.sol:320: State Change Without Event (13 places)\n[low] state-no-address-check at src/IMD6900Hook.sol:146: Address State Variable Set Without Checks (8 places)\n[low] unchecked-return at src/IMD6900LockedPool.sol:160: Unchecked Return (11 places)\n[low] uninitialized-local-variable at src/IMD6900RobinhoodLaunch.sol:350: Uninitialized Local Variable (9 places)\n[low] unsafe-erc20-operation at src/perps/PerpSwapLib.sol:435: Unsafe ERC20 Operation (3 places)\n[low] unspecific-solidity-pragma at src/IMD6900.sol:2: Unspecific Solidity Pragma (6 places)\n[low] unused-error at src/perps/PerpVault.sol:190: Unused Error (2 places)\n[low] unused-import at src/perps/PerpVault.sol:5: Unused Import\n[low] unused-public-function at src/IMD6900Hook.sol:110: Public Function Not Used Internally (3 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"c10f5463cef5edd3962dc55a245d06c85be2272bc5d520e7bbbd95622eca6690","verifiedTreeHash":"b841b54ca7e370374ff7a839185a8f3316f65537","verifierVersion":"0.1.0+67c588fa"},{"checks":[],"detail":"start commit produces tree b841b54ca7e3 but its accepted dependencies produce 74883b04592c","evaluation":"checks","profile":"foundry","status":"rejected","submissionHash":"c59209f2bb0760c2052f2da90c25b88d2225993677b8c80ab348614be4d1b697","verifiedTreeHash":"33df0cfbb8b8189b525db2055b958cf6aa8cad33","verifierVersion":"0.1.0+67c588fa"}]}