{"assessments":[],"deployments":[],"fuzz":[],"identity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"interpretation":"Records acceptance and evidence. Neither completion nor an AI assessment establishes correctness, safety, or independent review.","jobId":"fa037221-1d75-4778-8c52-2b1bd0cbfe49","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"1e983eae6e1fc2f2332a4aa5a6af19c6d1ff332b21fcb9c52796cb51d659d1fd","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":"29c8b18fca08ac83a835d865763968122aeefc8bc38a061f578aad5d27187c1b","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":"0fae2e1df0d91beb47123e055effca1f97f86ab686de0283ca5a399d1e6191b9","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":"0d8e4f7407f6276472abebb7e6c4119a5d4ed64d539f7f7f9e0a448dcb699a89","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":"59cc14df8d9a60295608aa4bda1753ab7d4667bf4562d775ef55c472b9ccb2f9","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":"c967a6e0ef680b5fd7d26242bdf802cb91167e56abb9fd88178520d52fbfbc9f","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":"e8a9e06c0dc88c1d363fee8ed6d559cc10bfa4181472cdd089e1dc1933a65a63","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":"e502beb23af302c7dd849bc06874ebc586676def73db2701a40c1f49eb047955","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"3a9b60d7857471032d269522e2f521eb33bfc16e5fcbfe2744af07e60d15dc83","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"4c76766b00591dfe982fe85d3de589fdb2f810e5137c301155d1c5e36a1d3b23","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"4c76766b00591dfe982fe85d3de589fdb2f810e5137c301155d1c5e36a1d3b23","skillId":"write-foundry-tests","state":"accepted"}],"objective":"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) has IMD6900LockedPool create IMD6900Hook with CREATE2 from the pool (IMD6900LockedPool.createHook) at a salt mined off-chain for the pool's 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). Proof tests: deploy Uniswap's PoolManager with `new PoolManager(address(this))` (importing it) and the project's contracts by importing them, never vm.deployCode / vm.getCode on a contract the proof doesn't import: a proof is compiled with its own imports only (a proof that loaded the PoolManager by name failed setUp with 'vm.deployCode: no matching artifact found' on job 4b952282).","parentJobId":null,"planHash":"769a0dbd9a8729897635568a953612ad826a763c7518b9c752c11efd09f586d6","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"fa037221-1d75-4778-8c52-2b1bd0cbfe49","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1639-imd6900robinhoodlaunch"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51367","feedbackHash":"61d80d95e262d5097006cbf4cdeddcb3e5885757d1b52d6d47767b0002bc6627","nodeKey":"adapt_contract_project","submissionHash":"01aa1e6c1adb781be8b60203eaded70a2dc64660f85735bd43d922fcd55bff47","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51465","feedbackHash":"0951c49f2b0b0fd4183837ebb2dd5f730df178092ca5f4d8ca37c6fd18f34ca7","nodeKey":"adapt_contract_project","submissionHash":"1e983eae6e1fc2f2332a4aa5a6af19c6d1ff332b21fcb9c52796cb51d659d1fd","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52230","feedbackHash":"679b15d74d3d0847a510c9ce1afb9ee14d0f63998a08f9bf287fbbf2d82a066c","nodeKey":"audit_economics","submissionHash":"29c8b18fca08ac83a835d865763968122aeefc8bc38a061f578aad5d27187c1b","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52597","feedbackHash":"524f010e808b2555d34ff2b51a08df580ffad76b59a857a4ace5337caf6c9395","nodeKey":"audit_flow","submissionHash":"0fae2e1df0d91beb47123e055effca1f97f86ab686de0283ca5a399d1e6191b9","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52207","feedbackHash":"48f7d4126d5d86237608771e34648149d518283e4ba0d236a62bdf989ee83cea","nodeKey":"audit_imported_code","submissionHash":"0d8e4f7407f6276472abebb7e6c4119a5d4ed64d539f7f7f9e0a448dcb699a89","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51027","feedbackHash":"215892f889111c1ab37b7b866191bb5cb912e398732e4de6a62ffc43e3d7a520","nodeKey":"audit_judge","submissionHash":"59cc14df8d9a60295608aa4bda1753ab7d4667bf4562d775ef55c472b9ccb2f9","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51891","feedbackHash":"2d5983d04a9c20b56cc2a586b0cfcd3e97a3c1217496a8163f7137654bd33783","nodeKey":"audit_judge","submissionHash":"095d95311da4bf09be4a3a700338cff6978a0584eed99471bc7d6d4645f50269","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52264","feedbackHash":"817c7b5c343fe2bcaa82f488406a2e7d6e07f030634e4e18ab0e1c7da4e57e71","nodeKey":"audit_math","submissionHash":"c967a6e0ef680b5fd7d26242bdf802cb91167e56abb9fd88178520d52fbfbc9f","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51345","feedbackHash":"f35bc05bd2d645a00ce8b666fdff1175a9737f4606eb9599d4cef3b4db8a0b50","nodeKey":"audit_permissions","submissionHash":"e8a9e06c0dc88c1d363fee8ed6d559cc10bfa4181472cdd089e1dc1933a65a63","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51504","feedbackHash":"f59c03d9f64222ea48f9ca8b92ffdc6efc992c4a29ccb3bb1a711888bfe94f57","nodeKey":"manifest","submissionHash":"e502beb23af302c7dd849bc06874ebc586676def73db2701a40c1f49eb047955","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52680","feedbackHash":"19636ab3c61d954029860893541da6e88c0419f628e29eaa9b2bb41402272d20","nodeKey":"manifest","submissionHash":"48006eca754947846db29a1c869786ded64f733b11ed9bc4498009b2e1b52e96","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"53929","feedbackHash":"626288453c2398d581d075c26b6021e83b8d5bdff881237e46183f49ad19fe50","nodeKey":"write_foundry_tests","submissionHash":"3a9b60d7857471032d269522e2f521eb33bfc16e5fcbfe2744af07e60d15dc83","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51315","feedbackHash":"95712efa1de98264c9d1bb56dda24746a0083b580ac34a0d84e5194d6bb3c270","nodeKey":"write_foundry_tests","submissionHash":"01e5d6b52f2f9f6004c25c2270e9e699a9114403f79bea0241905cd8a1894a37","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"a5189d3a06d42d4b282f46913c91453772c651f5533450afdb39cf457803dbec","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"2e0d2e4ff713f499d10ea5499e1fb520fcc81e8f8769a3718feeb34a966b1254","device":"ddfb1efa72fe9a94","findings":[],"hash":"01aa1e6c1adb781be8b60203eaded70a2dc64660f85735bd43d922fcd55bff47","nodeId":"c37a5641-0327-4199-9a4d-e51266189c02","outcome":"completed","summary":"Done. The build passes with the project's own configuration and all 64 tests pass (48 launch, 15 perps, the fork test skipping without an RPC). The perps hashes are unchanged by my edits.\n\n**What reproduced and was fixed**\n\n- **High, the engine hash.** `ENGINE_CODE_HASH` in the locked pool did not match the PerpEngine this tree builds, so `createPerps` could never run and the whole perps suite failed in setUp. I set it to the built code's keccak256, which is the value the audit computed. Nothing in src/perps changed, so the vault hash and the engine bytecode are as audited.\n- **Medium, the sale skipped the pre-trade sweep.** The hook's `_sweep` now skips the launcher only for the post-trade sweep. A new test shows the old code leaves a long open through the sale and the new code closes it inside the sale at the pre-sale price, with no bad debt and the sale still fee-free.\n- **Low, perps stranded after a fee move.** `createPerps` now refuses only while the pool's engine is bound to the current pool. After `lowerFee` it creates a fresh pair from the same audited code, and the hook rewires only its own pool-made engine when it has no open positions, never one the timelock chose. Two new tests cover it, and the fork test re-creates the perps on the 3.33% pool.\n- **Low, owed ETH stranded by setImdBurn.** Repointing the executor cancels what it was owed, as `setPayees` does for a dropped payee. Tested.\n- **Low, refund flag left on.** `setPerpEngine` resets `perpRefunded` on any change of engine. Tested in both suites.\n\n**Left alone:** the block.number throttle on burnImd is real but conservative on an Orbit chain, so I only documented it in the NatSpec and ADAPTATION.md. The coverage note is not a defect.\n\n**Other notes:** the test salt helper now hashes in place because the hook's new code hash made one salt search run out of memory. ADAPTATION.md lists every change with its finding and test. No launch.json or blocked file was written, and foundry.toml, lib and the compiler settings are untouched.","treeHash":"e3f8f8428155218b2162a91d149b215becb87090","usage":{"cachedInputTokens":2872611,"inputTokens":770,"model":"claude-fable-5-1","outputTokens":40166,"runtime":"claude","turns":65,"wallClockMs":1157828}},{"artifacts":[],"attempt":1,"bundleHash":"961a70732b18534673880861d472c25cb14c6591201a7d589a820112b835e70f","device":"e3a598aae0640402","findings":[],"hash":"01e5d6b52f2f9f6004c25c2270e9e699a9114403f79bea0241905cd8a1894a37","nodeId":"0c95d427-6b61-4b97-b449-4006c1a42ea0","outcome":"completed","summary":"Done. The suite grew from 64 to 86 tests and all pass with `forge test`. Two new files are committed under `test/`.\n\n**What I added**\n\n- `test/Invariants.t.sol` holds two handler-driven invariant suites over random call sequences, with run counts set by inline forge-config.\n  - The launch suite drives claimants, traders, harvests and splits with a refusing payee, plus the timelock's sales, releases, recoveries and fee moves. Invariants: the launch's coins equal the claims reserve plus the held excess, claims are 1:1 and bounded, ended claims never reopen, the ETH ledger matches the balance, the owed book is covered, the coin's supply only falls by burns seen and every coin sits with a named holder, the fee only falls and the liquidity stays whole.\n  - The perps suite drives longs, shorts, closes, keeper and in-swap liquidations, spot dumps, both vault sides, time and fee flushes. Invariants: no unabsorbed bad debt, the engine holds the ETH and token it books, the open book is consistent and bounded, the vault's exit queue is backed, the pool stays whole, perp fees never stay in the launch. Non-vacuity tests confirm each handler's calls actually go through.\n- `test/Edges.t.sol` covers failure paths the existing suite skipped: bad payee lists, landing addresses refusing zero or self, perp fees never kept when the pot refuses, recovery clamping the bookings, claim edge cases, double or early releases, sales over the held amount or below the floor, the pool's caller checks, the fee to zero and no lower. Fuzz tests cover fee moves at any lower fee, refused higher fees, claims never exceeding the snapshot, the coin's CREATE2 address and the split's pro-rata.\n\n**Findings**\n\nNo defect found. Three failures during development were harness errors, not contract bugs: a wei of split rounding carried to the next split, the fee checkpoint collecting the opening buy's fee, and forge's `bound` wrapping rather than clamping. No `.imd-findings.json` was written.\n\n**Still owed**\n\nThe fork test against live Robinhood state did not run here, as there is no network. The perps invariant ran at 32 runs by 40 calls; a longer run on the verifier would strengthen the no-bad-debt claim further.","treeHash":"d6d51c319607639423eafab4c8b90bd48465365b","usage":{"cachedInputTokens":3258515,"inputTokens":898,"model":"claude-fable-5-1","outputTokens":46169,"runtime":"claude","turns":39,"wallClockMs":1745439}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"934ef01306a3cc89","findings":[{"citation":"resolved","description":"Short-attributed ETH yield (20% of each short's open fee and liquidation penalty, PerpEngine._routeFee) is folded into accEthPerTokShare as floor(yield * 1e18 / tokShares). Token shares are minted at OFFSET = 1e6 times the 18-decimal token amount (depositToken, line 625), so with S wei of IMD6900 staked the index resolves yield only in steps of S * 1e6 / 1e18 wei. With 300M coins staked (3e26 wei, 3e32 shares) one step is 3e14 wei; the LP share of a minimum short (0.01 ETH collateral: 1.48e14 wei) floors to zero. lastTokYieldCum is advanced to cum unconditionally (line 537), so the unfolded wei are never credited to anyone: they stay in engine.tokYieldEth with no claimant (withdrawTokYieldTo only pays what the vault settled). Every depositToken / withdrawToken / claimTokYield / settle folds, and depositToken(1 wei) mints 1e6 shares, so anyone can fold after each short and strip the stakers of up to one step per fold. Reported by the math specialist; reproduced with its proof (it fails on this tree: 'yield the only staker can claim (wei): 0'). Fix: scale the accumulator by ACC * OFFSET (1e36; mulDiv handles the 512-bit intermediate) or carry the unfolded remainder of (cum - last) instead of advancing lastTokYieldCum past it; mirror the change in pendingTokYield.","line":566,"path":"src/perps/PerpVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PerpVault} from \"src/perps/PerpVault.sol\";\nimport {IMD6900} from \"src/IMD6900.sol\";\n\n/// A stand-in for PerpEngine's vault-facing surface: the token side's inventory and the short-attributed ETH yield\n/// pot, credited the way PerpEngine._routeFee credits it (tokYieldEth and tokYieldCumulative up by the same amount).\ncontract EngineStub {\n    address public token;\n    uint256 public plvToken;\n    uint256 public tokYieldEth;\n    uint256 public tokYieldCumulative;\n\n    constructor(address t) {\n        token = t;\n    }\n\n    function credit() external payable {\n        tokYieldEth += msg.value;\n        tokYieldCumulative += msg.value;\n    }\n\n    function fundTokenFromVault(uint256 amount) external {\n        IMD6900(token).transferFrom(msg.sender, address(this), amount);\n        plvToken += amount;\n    }\n\n    function withdrawPlvTokenTo(uint256 amount, address to) external {\n        plvToken -= amount;\n        IMD6900(token).transfer(to, amount);\n    }\n\n    function withdrawTokYieldTo(uint256 amount, address to) external {\n        tokYieldEth -= amount;\n        (bool ok,) = to.call{value: amount}(\"\");\n        require(ok);\n    }\n\n    function totalTokenAssets() external view returns (uint256) {\n        return plvToken;\n    }\n\n    function freeToken() external view returns (uint256) {\n        return plvToken;\n    }\n\n    function totalEth() external pure returns (uint256) {\n        return 0;\n    }\n\n    function freeEth() external pure returns (uint256) {\n        return 0;\n    }\n\n    function fundFromVault() external payable {}\n    function withdrawPlvTo(uint256, address) external {}\n}\n\n/// PerpVault folds short-side ETH yield into `accEthPerTokShare` as yield * 1e18 / tokShares. Token shares are minted\n/// at 1e6 x the token amount (OFFSET), so with 300M IMD6900 staked (3e26 wei, 3e32 shares) one fold can only resolve\n/// yield in steps of 3e32 / 1e18 = 3e14 wei: the 20% LP share of a minimum-size short's open fee (0.01 ETH collateral,\n/// 1.48e14 wei to the stakers) floors to zero and stays in the engine's pot with no claimant, for good.\ncontract VaultYieldRoundingTest is Test {\n    uint256 constant STAKE = 300_000_000 ether; // 30% of the supply on the token side\n    uint256 constant YIELD = 1.48e14; // PerpEngine: 0.01074 ETH sent, 6.9% open fee, 20% of it to the stakers\n\n    function test_SmallShortYieldIsLostToTheAccumulatorsRounding() public {\n        IMD6900 coin = new IMD6900(address(this));\n        EngineStub engine = new EngineStub(address(coin));\n        PerpVault vault = new PerpVault(address(engine));\n        address staker = address(0x57A4E);\n        coin.transfer(staker, STAKE);\n        vm.prank(staker);\n        coin.approve(address(vault), STAKE);\n        vm.prank(staker);\n        vault.depositToken(STAKE); // the only staker: every wei of yield from here on is theirs\n\n        engine.credit{value: YIELD}(); // a short opens: its fee's LP share lands in the pot\n        uint256 pending = vault.pendingTokYield(staker);\n        emit log_named_uint(\"yield credited (wei)\", YIELD);\n        emit log_named_uint(\"yield the only staker can claim (wei)\", pending);\n        // the pot holds the yield and the only staker should be able to claim (nearly) all of it\n        assertGe(pending, YIELD * 9 / 10, \"the only staker's yield vanishes in accEthPerTokShare's rounding\");\n        vm.prank(staker);\n        uint256 paid = vault.claimTokYield();\n        assertGe(paid, YIELD * 9 / 10);\n    }\n}","reproduction":"Deploy IMD6900, an engine stub exposing token/plvToken/tokYieldEth/tokYieldCumulative/fundTokenFromVault/withdrawPlvTokenTo/withdrawTokYieldTo, and PerpVault(engine). Staker deposits 300_000_000e18 coins: tokShares = 3e32. Credit the pot the way _routeFee does: tokYieldEth += 1.48e14, tokYieldCumulative += 1.48e14. Expected: vault.pendingTokYield(staker) ~ 1.48e14 and claimTokYield pays it. Actual: pendingTokYield == 0 (floor(1.48e14 * 1e18 / 3e32) = 0), claimTokYield reverts ZeroAmount, lastTokYieldCum == cum so the 1.48e14 wei are never folded again. Run: forge test --match-path test/scratch/VaultYieldRounding.t.sol (fails: '0 < 133200000000000').","severity":"medium","snippet":"                    accEthPerTokShare += FullMath.mulDiv(_toYieldUnit(cum - last), ACC, sh);","title":"PerpVault's token-side yield accumulator (ACC = 1e18 over 1e6x-offset shares) floors small short-side yield to zero and orphans the remainder in the engine's pot"},{"citation":"resolved","description":"releaseExcess refuses to == address(this) with BadTo and launch() books excessTo == address(this) into excessHeld, but releaseUnclaimed(to) has neither guard nor booking: with to == address(this) it does a self-transfer (no-op), sets unclaimedReleased = true (claimReserve() becomes 0, the call can never repeat) and leaves excessHeld unchanged. Afterwards no function can move those coins: releaseExcess and sellHeld are bounded by excessHeld, releaseUnclaimed reverts AlreadyReleased, claim reverts AlreadyReleased, and the contract has no other token transfer. The owner (team wallet) is trusted, but the slip is plausible because launch() takes the same address to mean 'keep it here for the timelock'. Reported by the permissions specialist; reproduced with its proof (fails on this tree at the excessHeld assertion). Fix: revert BadTo on to == address(this), or book excessHeld += amount on that branch as endClaims does.","line":343,"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 {PoolManager} from \"@uniswap/v4-core/src/PoolManager.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\";\nimport {IMD6900LockedPool} from \"src/IMD6900LockedPool.sol\";\nimport {IMD6900} from \"src/IMD6900.sol\";\n\ncontract MockImdstr {\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\n/// releaseUnclaimed(address(launch)) strands the unclaimed reserve: it leaves claimReserve() but never enters\n/// excessHeld, and no function moves coins held outside excessHeld.\ncontract ReleaseUnclaimedToSelfTest is Test {\n    address constant OWNER = address(0xA11CE);\n    address constant TL = address(0x71E10C);\n    address constant A = address(0xA1);\n    uint256 constant AA = 1_000 ether;\n    uint256 constant SUPPLY = 1_000_000_000 ether;\n\n    IPoolManager PM;\n    IMD6900RobinhoodLaunch L;\n    IMD6900LockedPool pool;\n    MockImdstr imdstr;\n    address t;\n\n    function setUp() public {\n        PM = IPoolManager(address(new PoolManager(address(this))));\n        imdstr = new MockImdstr();\n        L = new IMD6900RobinhoodLaunch(OWNER, PM, address(imdstr), TL);\n        pool = L.pool();\n        L.createHook(_mineHook());\n        bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(A, AA))));\n        vm.prank(OWNER);\n        L.setClaims(leaf, AA, block.timestamp + 100 days);\n        bytes32 salt = _mineCoin();\n        address expected = L.tokenAddress(salt);\n        uint160 sp = uint160(_sqrt(FullMath.mulDiv(SUPPLY, 1 << 192, 19.18 ether)));\n        vm.deal(OWNER, 2.81 ether);\n        vm.prank(OWNER);\n        (t,) = L.launch{value: 2.81 ether}(salt, expected, sp, 0, address(L));\n    }\n\n    function test_ReleaseUnclaimedToSelfStrandsTheReserve() public {\n        vm.warp(block.timestamp + 101 days);\n        uint256 heldBefore = L.excessHeld();\n        uint256 balBefore = IMD6900(t).balanceOf(address(L));\n        vm.prank(OWNER);\n        L.releaseUnclaimed(address(L)); // accepted, unlike releaseExcess(_, address(L)) which reverts BadTo\n        assertEq(IMD6900(t).balanceOf(address(L)), balBefore, \"the coins never left\");\n        assertEq(L.claimReserve(), 0, \"no longer reserved for claims\");\n        // expected: the reserve joins excessHeld, as endClaims does; actual: excessHeld is unchanged\n        assertEq(L.excessHeld(), heldBefore + AA, \"the unclaimed reserve should be held for the timelock\");\n    }\n\n    function test_NothingCanMoveTheStrandedCoins() public {\n        vm.warp(block.timestamp + 101 days);\n        vm.prank(OWNER);\n        L.releaseUnclaimed(address(L));\n        uint256 held = L.excessHeld();\n        vm.prank(TL);\n        vm.expectRevert(IMD6900RobinhoodLaunch.ZeroAmount.selector);\n        L.releaseExcess(held + 1, TL);\n        vm.prank(TL);\n        vm.expectRevert(IMD6900RobinhoodLaunch.ZeroAmount.selector);\n        L.sellHeld(held + 1, 0, TL);\n        vm.prank(OWNER);\n        vm.expectRevert(IMD6900RobinhoodLaunch.AlreadyReleased.selector);\n        L.releaseUnclaimed(TL);\n    }\n\n    function _mineHook() internal view returns (bytes32) {\n        bytes32 initHash = L.hookInitCodeHash();\n        for (uint256 i;; ++i) {\n            address a = _create2(address(pool), bytes32(i), initHash);\n            if (uint160(a) & Hooks.ALL_HOOK_MASK == L.FLAGS() && uint160(a) >> 152 != 0x91) return bytes32(i);\n        }\n    }\n\n    function _mineCoin() internal view returns (bytes32) {\n        bytes32 initHash = L.tokenInitCodeHash();\n        for (uint256 i;; ++i) {\n            if (uint160(_create2(address(L), bytes32(i), initHash)) >> 144 == 0x6900) return bytes32(i);\n        }\n    }\n\n    function _create2(address d, bytes32 s, bytes32 h) internal pure returns (address a) {\n        assembly (\"memory-safe\") {\n            let p := mload(0x40)\n            mstore8(p, 0xff)\n            mstore(add(p, 0x01), shl(96, d))\n            mstore(add(p, 0x15), s)\n            mstore(add(p, 0x35), h)\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":"State: launched with claims (claimTotal = 1000e18, nothing claimed), block.timestamp >= claimDeadline. Owner calls releaseUnclaimed(address(launch)). Expected: BadTo (as releaseExcess) or excessHeld += 1000e18 (as endClaims). Actual: success, excessHeld unchanged (122346490873046793208752165 instead of 122347490873046793208752165 in the proof), claimReserve() == 0, token balance unchanged; then releaseExcess(excessHeld+1, tl) and sellHeld(excessHeld+1, 0, tl) revert ZeroAmount and releaseUnclaimed(tl) reverts AlreadyReleased: 1000e18 coins are frozen. Run: forge test --match-path test/scratch/ReleaseUnclaimedToSelf.t.sol.","severity":"low","snippet":"            else SafeTransferLib.safeTransfer(token, to, amount);","title":"releaseUnclaimed accepts the launch contract itself as `to`, which strands the unclaimed reserve outside excessHeld for good"},{"citation":"resolved","description":"lowerFee (lines 215-220), IMD6900Hook.setPerpEngine (lines 134-137) and IMD6900Hook.initPerpEngine (lines 158-159) all refuse while the wired engine reports openCount != 0, as the brief asks. What makes the refusal a standing veto: PerpEngine._liqTest (lines 647-660) tests only the TWAP mark value and the close-out price; the funding index (_fundingDelta) is applied only inside _settle and never makes a position liquidatable, and close() checks p.trader == msg.sender. So one trader holding a 2x long at a flat price (0.02 ETH in, 0.01862 ETH collateral after the 6.9% fee, above minCollateral) blocks the timelock's fee move and the hook's engine replacement for as long as they like, and can reopen the moment an old one closes. The timelock has no override (setSweepFailOpen does not touch openCount; the engine owner cannot force-close a healthy position). Reported by the flow specialist; reproduced by tracing and by the existing tests test_NoFeeMoveWhilePerpsAreOpen / test_NoFeeMoveWithAPositionOpen. Trust/griefing note on an intended guard; a fix would be a timelock-only forced close at the mark (with the penalty) or a time bound on the veto.","line":219,"path":"src/IMD6900LockedPool.sol","reproduction":"State: launch() and createPerps() done, hook.perpEngine == engine, vault funded (vault.depositEth 10 ETH). (1) trader: engine.openLong{value: 0.02 ether}(2, 0) -> openCount == 1. (2) timelock: pool.lowerFee(33_300) -> revert PerpsOpen (line 219). (3) timelock: hook.setPerpEngine(address(0)) -> revert PerpsOpen (IMD6900Hook.sol:136). (4) vm.warp(+365 days), engine.poke(), price unchanged: engine.isLiquidatable(id) stays false (funding is not in _liqTest); (2) and (3) keep reverting. Expected per the brief: refused while positions are open. Actual: identical, but permanent at a third party's discretion for ~0.02 ETH with no timelock override.","severity":"low","snippet":"            if (!ok || ret.length < 32 || abi.decode(ret, (uint256)) != 0) revert PerpsOpen();","title":"One minimum-size perp position vetoes lowerFee, setPerpEngine and initPerpEngine indefinitely: funding never enters the liquidation test, so a flat-price position is never clearable by anyone but its "},{"citation":"resolved","description":"_syncEthQueue scales ethQueueIndex by backing/ethBackingMark whenever engine.totalEth() has fallen below the mark. _markEth (line 361) refreshes the mark on every vault action to the then-current totalEth, which includes yield (open fees, funding, penalties routed to plv) that by design is never distributed to the queue. So yield accrues -> any vault action re-marks at the peak -> a realised PLV loss writes the queue down by the loss's share of the peak although the queue was fully backed and the lost yield belonged to live shares only. Needs a realised PLV loss (long bad debt beyond insurance, or a funding credit beyond insurance), which the engine keeps rare, so the impact is bounded and conditional. Reported by the economics specialist; reproduced by tracing lines 426-455 with the numbers below. Fix direction: mark net of yield, or only scale the index when backing < claims.","line":433,"path":"src/perps/PerpVault.sol","reproduction":"State: live ETH shares backed by 10 ETH, a queued exit of 10 ETH (ethQueueUnits * index = 10e18), totalEth = 20, ethBackingMark = 20. Engine routes 10 ETH of yield to plv (totalEth = 30; assetsEth = 20, all live shares'). A third party calls depositEth{value: 1 wei}: _markEth sets ethBackingMark = 30. The engine then absorbs a 10 ETH PLV loss (totalEth = 20, which still fully backs the 10 ETH queue). Next claimPendingEth / withdrawEth / settlePendingEth: backing 20 < mark 30 -> newIdx = idx * 20 / 30 -> pendingEth() = 6.67 ETH (QueueWrittenDown for 3.33 ETH); live shares now hold assetsEth = 13.33 ETH on a 10 ETH basis. Expected: with totalEth (20) >= claims (10) the queued claimant keeps 10 ETH and live shares, which took all the yield, bear the loss. Actual: 3.33 ETH moves from the queued claimant to the live stakers.","severity":"low","snippet":"        uint256 newIdx = (mark != 0 && backing < mark)\n            ? FullMath.mulDiv(idx, backing, mark)\n            : idx;","title":"PerpVault writes the ETH exit queue down against a mark that includes yield the queue never received, moving value from queued claimants to live stakers on a later loss"},{"citation":"resolved","description":"burnImd is callable by imdKeeper (the timelock's setImdKeeper) or by the owner while none is set (line 549). It spends min(imdBucket, 0.1 ether) on the $IMD pool at MIN_SQRT_PRICE + 1 and the only floor is the caller's minImd (line 556); nothing in the contract bounds the fill against a TWAP or a maximum slippage. The keeper is designed as a low-trust operational role ('it quotes the buy and passes the floor'), yet it controls the execution price of ETH that belongs to the $IMD burn (10% of every split, fixed in the code). A keeper, or a compromised keeper key, pumps $IMD, calls burnImd(0), and sells back; the per-L1-block throttle (nextImdBurnBlock = block.number + 1) bounds the rate, not the total. Reported by the permissions specialist; reproduced by tracing lines 547-560 (burnImd(0) always passes the floor). Documented as a trust assumption with a concrete loss path, not a bypass; the IMD_BUY_MAX comment shows the author weighed sandwiching by outsiders but not by the keeper. Fix keeping the design: a timelock-set maximum slippage in bps enforced in addition to minImd, or a floor from a short TWAP of the $IMD pool.","line":556,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"State: launched, several splits done, imdBucket = 1 ether, imdKeeper = K. One transaction from K through a helper: (1) swap 5 ETH -> $IMD on the ETH/$IMD 1% pool (price up); (2) launch.burnImd(0) (succeeds: the floor is 0); (3) swap the $IMD back. Expected: the 0.1 ETH buys about 0.1 ETH worth of $IMD for the burn executor. Actual: the executor receives $IMD worth a fraction of 0.1 ETH at the fair price and K keeps the difference minus two 1% fees; repeat every L1 block until imdBucket is empty.","severity":"low","snippet":"        if (imd < minImd) revert Short(imd);","title":"burnImd's only price protection is the caller's own minImd, so the keeper key can self-sandwich the $IMD burn bucket at 0.1 ETH per L1 block"},{"citation":"resolved","description":"The brief fixes the first constructor argument: 0x35da9c0303507ddf708e87f2568eddf12c47a059 (owner: the team wallet). The manifest convention for this launch mode says an owner address the brief itself states is written as that static address, with $owner reserved for the wallet that paid or an owner the request's owner field names. The manifest writes \"$owner\" and relies on the notes ('$owner is the team wallet 0x35da...') to say what it should resolve to; notes are explanatory text, not deployment authority. If $owner resolves to anything but 0x35da...a059 the deployed contract's owner (who alone launches, sets the claim list, releases the unclaimed reserve and runs burnImd by default) is wrong from the first block, and the brief's owner cannot take it back. The other three arguments match the brief in value and order (poolManager, imdstr, timelock), the name is 22 characters, notes are 1954 characters and the file has only kind/contracts/notes. Fix: write the static address \"0x35da9c0303507ddf708e87f2568eddf12c47a059\" as the first constructorArgs entry.","line":7,"path":"launch.json","reproduction":"Read launch.json: contracts[0].constructorArgs[0] == \"$owner\" (line 7). The brief's first constructor argument is the literal 0x35da9c0303507ddf708e87f2568eddf12c47a059. Expected per the manifest rules: the static address. Actual: the symbolic $owner, so the owner the factory passes to IMD6900RobinhoodLaunch(owner_, ...) depends on how the service resolves $owner for this launch rather than on the brief.","severity":"low","snippet":"        \"$owner\",","title":"launch.json writes the owner as $owner although the brief states the owner address itself (0x35da9c03...a059)"},{"citation":"resolved","description":"PerpEngine.liquidate(id) pays keeperBps of the penalty to msg.sender; the hook's sweep, where most liquidations happen, pays it to tx.origin. For a trade sent through a smart-contract wallet, an ERC-4337 bundler, a relayer or a batching contract, tx.origin neither traded nor chose to liquidate. The amount is small (145 bps of a 6.9% penalty, about 0.1% of the liquidated collateral) and stays inside the engine's accounting, so this is informational: an unintended party is paid, nothing is lost to the protocol. Reported by the permissions specialist; reproduced by tracing _sweep -> sweepLiquidations(liquidator = tx.origin) -> _settle(..., MODE_LIQUIDATION, keeper) -> _payOut(keeper, toKeeper). If the intent is 'the EOA that sent the transaction', document it; if it is 'whoever traded', pass sender instead.","line":266,"path":"src/IMD6900Hook.sol","reproduction":"State: engine wired, one long liquidatable at the price a pending sell would leave. Wallet W (a contract) sells IMD6900 through the router inside a UserOperation relayed by bundler EOA B. Expected: the keeper cut reaches W or its router, as liquidate(id) would pay msg.sender. Actual: _sweep passes tx.origin == B; _settle(..., MODE_LIQUIDATION, B) calls _payOut(B, toKeeper): B receives the cut, W nothing.","severity":"info","snippet":"                abi.encodeCall(IPerpSweep.sweepLiquidations, (tx.origin, spec, isBuy, limit))","title":"The hook's pre-trade sweep names tx.origin as the liquidator, so the keeper cut of in-swap liquidations goes to the relayer or bundler rather than the account that traded"},{"citation":"resolved","description":"The brief says only the hook's perps engine and the launch's sellHeld trade fee-free. isRefunded defines the refunded engine as whatever hook.perpEngine() returns, and IMD6900Hook.setPerpEngine (onlyOwner: the Robinhood timelock, behind its delay) accepts any address with no code-hash check while the current engine is zero or reports no positions. So the timelock can name a router R; every swap R routes then gets its whole 4.44% fee back through feeCheckpoint/feeRefund while the pool's fee stays nominally static, removing the split's income for those trades. The existing test test_TheEngineTradesFeeFree exercises exactly this with a hand-written RefundingEngine, so it is an accepted design choice; listed because it widens the timelock's documented role to 'can make any party trade fee-free'. Reported by the permissions specialist; reproduced by tracing. No change proposed; if the refund should bind to pool-created engines only, compare against IMD6900LockedPool.perpsEngine / the hook's poolEngine instead.","line":290,"path":"src/IMD6900LockedPool.sol","reproduction":"State: launched, hook.perpEngine() == 0. Timelock calls hook.setPerpEngine(R) where R calls pool.feeCheckpoint(), swaps, then pool.feeRefund(R) inside its own unlock (RefundingEngine in test/IMD6900RobinhoodLaunch.t.sol). Expected per the brief: only the perps engine and sellHeld are fee-free. Actual: pool.isRefunded(R) is true and R's 1 ETH buy gets 0.0444 ETH back (asserted by test_TheEngineTradesFeeFree).","severity":"info","snippet":"        return a == launcher || (a != address(0) && a == IPerpsWiring(hook).perpEngine());","title":"Trust assumption: the hook owner's setPerpEngine(x) makes any address x a fee-refunded trader in the locked pool, not only an engine the pool created"},{"citation":"resolved","description":"After IMD6900LockedPool.lowerFee moves the liquidity and createPerps wires a fresh engine, the previous engine is bound for good to the drained pool (poolId immutable) and its _openPrologue refuses every open with PerpsOff (PerpEngine.sol:843). Its PerpVault has no such gate: depositEth (lines 253-281) and depositToken (lines 611-635) only check queue solvency and keep minting shares and pushing capital into the dead engine. Funds are not lost: withdrawEth / withdrawToken pay instantly because nothing is lent, so the impact is a dead-end deposit only. Reported by the flow specialist; reproduced by tracing. A frontend check or a vault gate on hook.perpEngine() == engine would close it.","line":272,"path":"src/perps/PerpVault.sol","reproduction":"State: launch() and createPerps() -> (engine1, vault1), no positions. (1) timelock: pool.lowerFee(33_300). (2) anyone: pool.createPerps(engineCode, vaultCode) -> (engine2, vault2), hook.perpEngine = engine2. (3) staker: vault1.depositEth{value: 1 ether}() succeeds, engine1.plv += 1 ether. (4) staker: engine1.openLong{value: 0.05 ether}(2, 0) reverts PerpsOff. Expected: a vault whose engine is unwired refuses new stakes. Actual: the deposit sits idle until vault1.withdrawEth(shares) returns it in full.","severity":"info","snippet":"        if (pendingEth() > engine.totalEth()) revert QueueInsolvent();","title":"PerpVault keeps minting shares into an engine the hook no longer serves: after lowerFee + createPerps the old vault still accepts deposits that can never earn"}],"hash":"095d95311da4bf09be4a3a700338cff6978a0584eed99471bc7d6d4645f50269","nodeId":"54aaeeda-03fa-4321-9d1b-fba4614f4d39","outcome":"completed","summary":"The review is complete. The findings file is written at `.imd-findings.json` and nothing else in the tree changed.\n\n**Outcome.** No critical or high defects. I kept all eight specialist findings after reproducing each one, and added one of my own on the manifest. Both attached proofs fail on this tree for the stated reasons, and the project's own suite passes, including the code-hash binding, the one-transaction gas test and the admission scan.\n\n**Findings reported, by severity**\n\n- **Medium.** PerpVault's token-side yield accumulator uses a 1e18 scale over shares minted at 1e6 times an 18-decimal token. Small short-side yield floors to zero and the remainder is orphaned in the engine's pot, and anyone can force a fold after every short. Proof attached, fails on this tree.\n- **Low.** `releaseUnclaimed(address(launch))` strands the reserve outside `excessHeld` for good. Proof attached, fails on this tree.\n- **Low.** One minimum perp position vetoes `lowerFee`, `setPerpEngine` and `initPerpEngine` indefinitely, since funding never enters the liquidation test and the timelock has no override.\n- **Low.** The vault's ETH exit queue is written down against a mark that includes yield the queue never received, so a later loss transfers value from queued claimants to live stakers.\n- **Low.** `burnImd`'s only floor is the keeper's own `minImd`, so a keeper key can self-sandwich the burn bucket one buy per L1 block.\n- **Low.** launch.json writes `$owner` where the brief states the owner address itself. The other three arguments, the name, the keys and the notes length are in order.\n- **Info.** The sweep names `tx.origin` as liquidator. The timelock's `setPerpEngine` makes any address fee-refunded. The old vault still accepts deposits after a fee move.\n\n**Coverage.** All 101 entry points answered, with 84 holding and 17 pointing at a finding. In my own pass I traced the refund pair under nested sweeps inside `sellHeld`, the fee move, the hook callbacks, the launch and split accounting and the vault's queue, and found nothing beyond what is listed. I did not independently prove the engine's no-bad-debt claim across every cascade order within the time budget; the existing tests and the conservative close-out test support it, and that is noted as a limit rather than a finding.","treeHash":null,"usage":{"cachedInputTokens":1529054,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":45282,"runtime":"claude","turns":17,"wallClockMs":652475}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"f009de0ce52c7b3a","findings":[{"citation":"resolved","description":"createPerps (src/IMD6900LockedPool.sol:259) accepts only engine bytes whose keccak256 equals ENGINE_CODE_HASH (0x3ce566f9...). A clean `forge build` of this tree at the pinned settings (solc 0.8.26, via-IR, 200 runs, cancun, bytecode_hash none) gives PerpEngine creation code with keccak256 0x136b10b3b461616f618358a9d4ad7fff26b80ac920c5d8092be808bdb60c0651. VAULT_CODE_HASH does match the built PerpVault. Commit 827c1d0 changed PerpEngine._book and the constant together, but the constant it pinned is not what the committed source compiles to. Since no other preimage of the constant can be produced, createPerps reverts NotThePerpsCode for the real engine and for everything else: the perps engine and vault, which the launch describes as part of the project and live right after launch(), are unreachable for good, and the hook never gets an engine (initPerpEngine is only called from createPerps). The repository's own test/Perps.t.sol fails in setUp with NotThePerpsCode() for this reason, so none of its perps coverage currently runs. Fix: set ENGINE_CODE_HASH to the keccak256 of the built creation code (or restore the engine source the constant was computed from) and keep test_ThePerpsCodeIsTheAuditedCode green.","line":79,"path":"src/IMD6900LockedPool.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"@uniswap/v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.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\";\nimport {IMD6900LockedPool} from \"src/IMD6900LockedPool.sol\";\nimport {PerpEngine} from \"src/perps/PerpEngine.sol\";\nimport {PerpVault} from \"src/perps/PerpVault.sol\";\n\n/// @dev A stand-in for IMDSTR: the launch only stores its address before the claims.\ncontract DummyImdstr {}\n\n/// Finding: IMD6900LockedPool.ENGINE_CODE_HASH is not the keccak256 of src/perps/PerpEngine.sol's creation code as\n/// this repository compiles it (VAULT_CODE_HASH is). createPerps accepts only a preimage of that constant, so the\n/// perps can never be created from the audited source: the whole perps system is unreachable after the launch.\ncontract PerpsCodeHashProof is Test {\n    address constant OWNER = address(0xA11CE);\n    address constant TL = address(0x71E10C);\n    uint256 constant SUPPLY = 1_000_000_000 ether;\n\n    /// The pinned engine hash must be the hash of the engine this repository builds.\n    function test_TheEngineHashIsTheBuildsHash() public {\n        IMD6900LockedPool p = new IMD6900LockedPool(IPoolManager(address(0)), 44_400, address(this), TL);\n        assertEq(keccak256(type(PerpVault).creationCode), p.VAULT_CODE_HASH(), \"vault hash\");\n        assertEq(keccak256(type(PerpEngine).creationCode), p.ENGINE_CODE_HASH(), \"engine hash\");\n    }\n\n    /// createPerps with the real engine and vault code, right after the launch, must create them.\n    function test_CreatePerpsAcceptsTheRealEngineCode() public {\n        IPoolManager pm = IPoolManager(address(new PoolManager(address(this))));\n        IMD6900RobinhoodLaunch l = new IMD6900RobinhoodLaunch(OWNER, pm, address(new DummyImdstr()), TL);\n        IMD6900LockedPool pool = l.pool();\n        l.createHook(_mineHook(l, pool));\n        bytes32 salt = _mineCoin(l);\n        address expected = l.tokenAddress(salt);\n        uint160 price = uint160(_sqrt(FullMath.mulDiv(SUPPLY, 1 << 192, 19.18 ether)));\n        vm.deal(OWNER, 1 ether);\n        vm.prank(OWNER);\n        l.launch{value: 1 ether}(salt, expected, price, 0, address(l));\n        (address engine, address vault) = pool.createPerps(type(PerpEngine).creationCode, type(PerpVault).creationCode);\n        assertEq(pool.perpsEngine(), engine);\n        assertEq(pool.perpsVault(), vault);\n        assertEq(PerpEngine(payable(engine)).owner(), TL);\n    }\n\n    function _mineHook(IMD6900RobinhoodLaunch l, IMD6900LockedPool pool) internal view returns (bytes32) {\n        bytes32 h = l.hookInitCodeHash();\n        uint160 flags = l.FLAGS();\n        for (uint256 i; i < 2_000_000; ++i) {\n            bytes32 salt = bytes32(i);\n            address a = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(pool), salt, h)))));\n            if (uint160(a) & Hooks.ALL_HOOK_MASK == flags && uint160(a) >> 152 != 0x91) return salt;\n        }\n        revert(\"no hook salt\");\n    }\n\n    function _mineCoin(IMD6900RobinhoodLaunch l) internal view returns (bytes32) {\n        bytes32 h = l.tokenInitCodeHash();\n        for (uint256 i; i < 20_000_000; ++i) {\n            bytes32 salt = bytes32(i);\n            address a = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(l), salt, h)))));\n            if (uint160(a) >> 144 == 0x6900) return salt;\n        }\n        revert(\"no coin salt\");\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":"State: fresh chain, PoolManager deployed, IMD6900RobinhoodLaunch deployed, createHook(salt) done, launch() done. Call pool.createPerps(type(PerpEngine).creationCode, type(PerpVault).creationCode). Expected: returns (engine, vault), pool.perpsEngine() != 0, hook.perpEngine() == engine. Actual: reverts NotThePerpsCode(). Directly: keccak256(type(PerpEngine).creationCode) == 0x136b10b3...0651 while IMD6900LockedPool.ENGINE_CODE_HASH == 0x3ce566f9...de3f. `forge test --match-path test/Perps.t.sol` reproduces it today ([FAIL: NotThePerpsCode()] setUp()).","severity":"high","snippet":"    bytes32 public constant ENGINE_CODE_HASH = 0x3ce566f987e94e7d6e37f1e6eae05488d1ec6893765f381bd37b12579e9ade3f;","title":"ENGINE_CODE_HASH is not the hash of the PerpEngine this repository builds: createPerps can never create the perps"},{"citation":"resolved","description":"PerpEngine's no-bad-debt argument (PerpEngine.sol:54-59) rests on every price move being a swap whose beforeSwap sweep closes what the trade would sink at the price before it moves. _sweep returns early for sender == launcher for BOTH callbacks, so IMD6900RobinhoodLaunch.sellHeld (a fee-free sale of up to the whole excess the opening buy left, which can be most of the ETH ever paid into the pool) moves the price with no pre-trade projection at all. Longs that the sale pushes below their principal are only closed by the next ordinary trade's sweep, at the price the sale left: proceeds < principal, and _replenishPlv/_absorbPlvLoss draw insurance then the community vault's plv. The comment's reason for the skip (a nested liquidation inside the sale's refund window would take the sale's refund) only applies to the afterSwap sweep: at beforeSwap the sale's swap has not happened yet, so nothing of its fee can be taken. Suggested fix: skip only the post-trade sweep for the launcher (`spec == 0`), or run the pre-trade sweep with the launcher's own refund re-armed after it.","line":242,"path":"src/IMD6900Hook.sol","reproduction":"State: launched with a 2.81 ETH opening buy and excessTo = the launch (excessHeld ~ most of the bought coins), perps created, vault.depositEth 10 ETH, Alice openLong{value: 0.1 ether}(5, 0) (notional 0.5 ETH, principal 0.4 ETH). Then timelock: launch.sellHeld(0, 0, TL) selling all held coins (fee-free). The price falls well past the long's liquidation level, with no sweep run (hook returned at line 242). Any next swap (e.g. 0.001 ETH buy) runs the sweep: the long is liquidated at the post-sale price, PerpEngine emits BadDebt(shortfall > 0, ...) and insuranceEth/plv fall. Expected (per the engine's invariant): the long is closed BEFORE the sale at the pre-sale price, proceeds >= principal, no BadDebt.","severity":"medium","snippet":"        if (engine == address(0) || sender == engine || sender == ILockedPoolView(pool).launcher()) return;","title":"The launch's sellHeld skips the hook's pre-trade liquidation sweep, so the timelock's sale can sink longs past their debt and charge PLV stakers"},{"citation":"resolved","description":"PerpEngine.poolId/poolFee are immutable and the hook's _beforeSwap serves only the newest poolId. lowerFee (allowed as soon as openCount == 0) initializes a new pool and moves all liquidity, after which every engine swap reverts NotOurPool and activeEthDepth() reads the empty old pool. createPerps refuses a second creation (PerpsExist), so the only way back is the timelock deploying an engine by hand with the new fee and wiring it with setPerpEngine; isRefunded keys off hook.perpEngine() so that works, but nothing in the repository (scripts, tests) covers it, and vault stakers' ETH sits idle meanwhile (withdrawable, since withdrawPlvTo does not touch the pool). Consider letting createPerps run again once the wired engine reports no positions and is bound to a pool other than the current one, or document the manual path.","line":258,"path":"src/IMD6900LockedPool.sol","reproduction":"launch(); createPerps(engineCode, vaultCode); no positions; timelock: pool.lowerFee(33_300). Then engine.openLong{value: 0.1 ether}(5, 0) reverts (hook NotOurPool, the engine's poolKey() still names fee 44_400); pool.createPerps(engineCode, vaultCode) reverts PerpsExist(). Expected: perps usable on the moved pool or re-creatable.","severity":"low","snippet":"        if (perpsEngine != address(0)) revert PerpsExist();","title":"After lowerFee the perps are stranded for good: createPerps is one-shot and the engine is bound to the drained pool"},{"citation":"resolved","description":"split() treats the payee equal to imdBurn by identity and `continue`s before the owed() bookkeeping. If a payee A refused a share (owed[A] = x, owedTotal += x) and the timelock later calls setImdBurn(A) (or sets A as a payee after it became the burn executor), A's owed balance is never paid, never cancelled by setPayees (A is kept), and owedTotal keeps x reserved: `amount = balance - imdBucket - owedTotal` excludes it from every split until recoverEth cancels it. Not a theft, but ETH stuck by an ordinary timelock action. setImdBurn should cancel (or re-split) the owed balance of the new executor, or split() should pay/clear owed before the imdBurn branch.","line":496,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"After launch: timelock setPayees([A, POT_BRIDGE], [5000, 5000]) where A is a contract whose receive() reverts; send 1 ETH of fees; split() -> PayFailed(A, 0.5 ether), owedTotal == 0.5 ether. Timelock setImdBurn(A). Send 1 ETH; split(): A's part goes to imdBucket, owed(A) stays 0.5 ether, owedTotal stays 0.5 ether, and that 0.5 ether is never offered to anyone again. Expected: the refused share is re-split or paid; actual: it is reserved forever (until recoverEth).","severity":"low","snippet":"            if (to[i] == imdBurn) {","title":"Repointing imdBurn to a payee that holds an owed share strands that ETH out of every future split"},{"citation":"resolved","description":"initPerpEngine sets perpRefunded = true for the engine createPerps makes. setPerpEngine (the timelock's replacement path, and the only path after a fee move, see the stranded-perps finding) does not reset it. A replacement engine that does not call feeCheckpoint/feeRefund then reads perpFeeBps() == 0 and prices its closes and liquidations fee-free while actually paying the pool's 4.44%, so _exitInsolvent fires ~4.4% too late and liquidations can repay less than principal. The NatSpec says to set it with the engine, but nothing enforces it; resetting perpRefunded to false in setPerpEngine (the safe default: the engine then assumes the fee) removes the footgun.","line":135,"path":"src/IMD6900Hook.sol","reproduction":"hook.perpRefunded() == true after createPerps. Timelock: hook.setPerpEngine(E2) where E2 is a PerpEngine deployed by hand against a hook whose launcher() returns 0 (or any engine that does not refund). hook.perpFeeBps() still returns 0 while E2's swaps pay 44_400 pips. Expected: perpFeeBps() == 444 until the timelock says otherwise.","severity":"low","snippet":"        perpEngine = engine;","title":"setPerpEngine leaves perpRefunded on for a replacement engine, so the hook can report a 0 bps fee for an engine that pays 4.44%"},{"citation":"resolved","description":"PerpEngine.sol:172 notes that on Arbitrum Orbit chains block.number is the L1 number and keys its throttle on timestamp. burnImd uses block.number, so the burn is limited to one 0.1 ETH buy per Ethereum block (~12 s), not per Robinhood block. No loss; the $IMD bucket just drains more slowly than the comment implies (and burnImd calls in consecutive Robinhood blocks within one L1 block revert BurnRateLimited).","line":529,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"On a Robinhood fork with imdBucket >= 0.2 ether: keeper burnImd(0) in one Robinhood block, then burnImd(0) in the next Robinhood block inside the same L1 block -> BurnRateLimited(). Expected per the NatSpec: one buy per block.","severity":"info","snippet":"        nextImdBurnBlock = block.number + 1;","title":"burnImd's one-per-block throttle keys on block.number, which is the L1 block number on Robinhood Chain (Arbitrum Orbit)"},{"citation":"resolved","description":"Read in full against the Pashov checklist (every state-changing external/public function, its caller, what it moves, what it trusts): src/IMD6900RobinhoodLaunch.sol, src/IMD6900LockedPool.sol, src/IMD6900Hook.sol, src/IMD6900.sol, src/perps/PerpEngine.sol, src/perps/PerpVault.sol, src/perps/PerpSwapLib.sol; test/Perps.t.sol and the protected checks. Built clean (solc 0.8.26, via-IR) and ran the suite: 46/46 in test/IMD6900RobinhoodLaunch.t.sol pass; test/Perps.t.sol fails in setUp (the high finding); RobinhoodFork skips (no RPC). Checked and found no issue: launch()'s 0x6900 and expectedToken checks, the coin (plain solady ERC20, no owner/mint/tax), the pool's lack of any liquidity removal except lowerFee's same-unlock move, lowerFee lower-only and refused while openCount != 0, the fee refund pair (checkpoint/refund limited to launcher and the wired engine, transient-armed, once per checkpoint), split()'s owed bookkeeping (refused shares are owed, never re-split), claim's Merkle leaf (double-hashed, 32-byte leaf vs 64-byte nodes: no second-preimage), reentrancy guards on every ETH path, the engine's fee split (20/10/70 via _routeFee, all 70% forwarded by addPerpFees), the vault's unit-indexed exit queues, and the engine's constant-product projection (exact for the one-sided range since sells cannot carry the price past upperTick). Not reached in depth within the budget: the vendored lib/ code (v4-core, solady, OpenZeppelin) was trusted as published; script/ (launch.sh, programmable_plan.py, params.py) was not reviewed; the fork test was not run.","line":44,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"Not a defect; coverage statement required by the assignment.","severity":"info","snippet":"contract IMD6900RobinhoodLaunch is Ownable, ReentrancyGuard {","title":"Review coverage: what was read and what was not reached"}],"hash":"0d8e4f7407f6276472abebb7e6c4119a5d4ed64d539f7f7f9e0a448dcb699a89","nodeId":"1ec77a4a-8d53-4b9d-8e20-bf41553914d3","outcome":"completed","summary":"All seven snippets match their lines, the JSON is valid, and the proof file compiles and fails as required. The review is complete.\n\n**Outcome:** one high finding with a proof, one medium, three low, two info, written to `.imd-findings.json`.\n\n**High: the perps can never be created from this source.** `IMD6900LockedPool.ENGINE_CODE_HASH` is `0x3ce566f9…`, but a clean build of `src/perps/PerpEngine.sol` at the pinned settings hashes to `0x136b10b3…` (the vault hash does match). `createPerps` accepts only a preimage of the constant, so it reverts `NotThePerpsCode` for the real engine and for anything else. The repository's own `test/Perps.t.sol` already fails in `setUp` for this reason, so none of its perps coverage runs. Commit 827c1d0 changed the engine and the constant together, but pinned a hash the committed source does not produce. The proof under `test/scratch/PerpsCodeHash.t.sol` fails on this tree for exactly that reason and passes once the constant is updated.\n\n**Medium:** the hook skips both sweeps for the launcher, so the timelock's fee-free `sellHeld` can sink longs past their debt with no pre-trade liquidation; the next trade closes them at the post-sale price and the loss lands on insurance and the community vault. The skip is only needed for the post-trade sweep.\n\n**Low and info:** after `lowerFee` the perps are stranded and `createPerps` is one-shot; `setImdBurn` pointing at a payee with an owed share strands that ETH out of every split; `setPerpEngine` leaves `perpRefunded` on for a replacement engine; `burnImd` throttles on the L1 block number on Robinhood Chain; and a coverage entry listing what was read.\n\n**Coverage:** all seven contracts under `src/` were read in full and checked against the Pashov checklist, the suite was built and run (46 of 46 launch tests pass, the perps suite fails in setup, the fork test skipped without an RPC). Not reached in depth: the vendored `lib/` code, the `script/` directory, and the fork test.\n\nNo repository files were changed. The only writes are `.imd-findings.json` and the scratch proof.","treeHash":null,"usage":{"cachedInputTokens":1522892,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":56271,"runtime":"claude","turns":25,"wallClockMs":1065114}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"9f3c23750c16c22c","findings":[{"citation":"resolved","description":"lowerFee (IMD6900LockedPool.sol:215-220), IMD6900Hook.setPerpEngine (IMD6900Hook.sol:134-137) and IMD6900Hook.initPerpEngine (IMD6900Hook.sol:158-159) all refuse while the wired engine reports openCount != 0. The brief asks for exactly that refusal, so this is reported as a trust/griefing note, not a spec violation. What makes it a standing veto rather than a transient one: PerpEngine._liqTest (PerpEngine.sol:647-660) tests only the TWAP mark value and the close-out price; the funding index (_fundingDelta, applied only inside _settle) never enters the test, so a 2x long at a flat price is never liquidatable, and nobody but its owner can close it (close() checks p.trader == msg.sender). A single trader therefore holds the timelock's fee-lowering power and the hook's engine-replacement power hostage for 0.01 ETH of collateral plus the 6.9% open fee, for as long as they like, and can re-open a new position the moment an old one is closed. The timelock has no override: setSweepFailOpen does not touch openCount, and the engine owner cannot force-close a healthy position.","line":219,"path":"src/IMD6900LockedPool.sol","reproduction":"State: launch() done, createPerps() done, engine wired (perpEngine == engine, perpRefunded == true), PLV funded (fundPlv or PerpVault.depositEth) so a 2x long can borrow. Steps: (1) trader calls engine.openLong{value: 0.02 ether}(2, 0): fee 6.9% leaves 0.01862 ETH collateral >= minCollateral (0.01 ETH), borrow 0.01862 ETH, position booked, openCount == 1. (2) timelock calls pool.lowerFee(33_300): engine.openCount() returns 1 -> revert PerpsOpen (line 219). (3) timelock calls hook.setPerpEngine(address(0)) or any other address: _openPositions(cur) returns open == true -> revert PerpsOpen (IMD6900Hook.sol:136). (4) Warp any amount of time with the pool price unchanged and call engine.poke(): fundingIndex moves but engine.isLiquidatable(id) stays false (funding is not in _liqTest), so no third party can clear the position; steps (2) and (3) keep reverting. Expected per the brief: the move is refused while positions are open. Actual: identical, but the refusal is permanent at a third party's discretion for ~0.02 ETH, with no timelock override. The existing tests test_NoFeeMoveWhilePerpsAreOpen / test_NoFeeMoveWithAPositionOpen demonstrate steps (1)-(2).","severity":"low","snippet":"            if (!ok || ret.length < 32 || abi.decode(ret, (uint256)) != 0) revert PerpsOpen();","title":"One minimum perp position vetoes lowerFee, setPerpEngine and initPerpEngine indefinitely (funding never makes a position liquidatable)"},{"citation":"resolved","description":"After IMD6900LockedPool.lowerFee moves the liquidity and createPerps wires a fresh engine, the previous engine is bound for good to the drained pool (poolId immutable) and its _openPrologue refuses every open with PerpsOff (PerpEngine.sol:843). Its PerpVault has no such gate: depositEth (PerpVault.sol:253-281) and depositToken (PerpVault.sol:611-635) only check queue solvency and keep minting shares and pushing capital into the dead engine via fundFromVault / fundTokenFromVault. No yield can ever accrue there, and a frontend or script still pointed at the old vault address keeps accepting stakes. Funds are not lost: withdrawEth / withdrawToken pay instantly because nothing is lent (plv == totalEth), so impact is a dead-end deposit only, reported for completeness of the fee-move flow. The brief's design (one vault per engine, a fresh pair per pool) is unchanged by this note.","line":272,"path":"src/perps/PerpVault.sol","reproduction":"State: launch() done, createPerps() done -> (engine1, vault1); no positions open. Steps: (1) timelock calls pool.lowerFee(33_300): liquidity moves, hook.poolId is the new pool, engine1 stays hook.perpEngine until replaced. (2) anyone calls pool.createPerps(engineCode, vaultCode) -> (engine2, vault2); hook.initPerpEngine(engine2) sets perpEngine = engine2. (3) staker calls vault1.depositEth{value: 1 ether}(): passes (pendingEth() == 0 <= totalEth()), mints shares, engine1.plv += 1 ether. (4) staker calls engine1.openLong{value: 0.05 ether}(2, 0): reverts PerpsOff, so the 1 ETH can never be lent or earn. Expected: a vault whose engine is unwired refuses new stakes (or the UI is told so). Actual: the deposit succeeds and sits idle until the staker notices and calls vault1.withdrawEth(shares), which returns it in full.","severity":"info","snippet":"        if (pendingEth() > engine.totalEth()) revert QueueInsolvent();","title":"PerpVault keeps minting shares into an engine the hook no longer serves (after lowerFee + createPerps the old pair is dead for trading but still accepts deposits)"}],"hash":"0fae2e1df0d91beb47123e055effca1f97f86ab686de0283ca5a399d1e6191b9","nodeId":"623fad75-aeac-4719-a22f-29b747dc8ee2","outcome":"completed","summary":"Review complete. The only file written is `.imd-findings.json` (git status is otherwise clean, and the findings file is ignored by git).\n\n**Outcome**\n\nThe control flow of the launch, the locked pool, the hook and the perps holds up under the Execution Trace, Periphery and First Principles passes. I found no loss-of-funds or permanent-breakage defect. Two notes are recorded, both with concrete reproductions, neither critical or high, so no proof test is attached.\n\n1. **Low, by design but worth a trust note.** One minimum 2x position, about 0.02 ETH all in, vetoes `lowerFee`, `setPerpEngine` and `initPerpEngine` for as long as the trader wants. The liquidation test ignores funding, so a flat-price position never becomes liquidatable, and nobody but the trader can close it. The brief asks for the refusal while positions are open, so this is reported as a standing griefing lever on the timelock, not a spec violation.\n2. **Info.** After a fee move plus a fresh `createPerps`, the old vault still mints shares into an engine that refuses every open. Deposits are not lost since nothing is lent and withdrawal pays in full, but a stale frontend pointer accepts dead stakes. Confirmed with a scratch test, then removed.\n\n**What I verified along the way**\n\n- The per-caller transient checkpoint slots make the launch's fee-free sale and nested engine liquidations inside its `beforeSwap` each recover exactly their own swap's fee.\n- The move in `lowerFee` re-points the hook before initialising, removes and re-adds inside one unlock, and pays out only old fees. No path takes liquidity out otherwise.\n- The pool price cannot pass the upper tick while coins exist outside the pool, so the engine's constant-product close-out math is exact within range.\n- Static-analysis leads on `split`, `recoverEth`, `_payOutHeld`, `_sendEth` and the engine's reentrancy all resolve to guarded or intended paths.\n- The existing suite is green, and the Robinhood fork test passes on live chain state through the contributor RPC, including the perps fee flush to the real pot bridge. That killed my lead about `flushFees` liveness.\n\n**Coverage**\n\nAll 103 listed entry points have a row, plus invariant rows. Verdicts are `holds` for everything traced, `finding` for the six entry points touched by the two notes. Nothing is marked unreached.","treeHash":null,"usage":{"cachedInputTokens":2147472,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":64858,"runtime":"claude","turns":26,"wallClockMs":983561}},{"artifacts":[],"attempt":1,"bundleHash":"d74e2d79659d093d328f6450ba47c43346d538e028634897b7e9fafe7098d6f6","device":"f0c4ae5b8ada215c","findings":[],"hash":"1e983eae6e1fc2f2332a4aa5a6af19c6d1ff332b21fcb9c52796cb51d659d1fd","nodeId":"c37a5641-0327-4199-9a4d-e51266189c02","outcome":"completed","summary":"The responses file is excluded from git by the harness's own exclude list, which is expected: it is read from the working tree, as the brief describes. The work is complete.\n\n## Recap\n\n**The reopened medium finding reproduces and is fixed.** The reviewer's proof failed on the starting tree (0 of 1.48e14 wei claimable) and passes now. PerpVault's token-side yield index was 1e18-scaled over shares minted at 1e6 times the token amount, so small short-side yield floored to zero and stayed in the engine's pot with no claimant, strippable one step per 1-wei deposit. The scale constant is now 1e36 (one constant, used by every fold, debt, settle and the view), so a fold's residue is below a wei for any stake the supply allows. The vault's creation code changed, so `VAULT_CODE_HASH` in the locked pool is re-pinned to the clean build's value; the engine is untouched and its hash unchanged.\n\n**The releaseUnclaimed self-transfer is fixed.** Releasing to the launch itself now books the unclaimed reserve into the held coins, the same meaning launch() and endClaims give that address, so the timelock can sell or release it instead of it being frozen.\n\n**A build-path hazard found while re-pinning.** The engine's via-IR bytecode depends on which files share its compile job: an incremental build after a partial one reproduces the author's original hash, a clean full build gives the pinned one (the audit's and the verifier's). The launch script's perps step now runs a clean build before hashing, and the README says so.\n\n**Six advisory findings left alone, each answered as disputed with reasons** in `.imd-responses.json` and documented as trust notes in the README and ADAPTATION.md: the one-position veto on lowerFee is the brief's own rule and the remedies are new timelock powers over traders; the ETH queue's mark is the vault's documented pari-passu rule; the keeper's burn price has no on-chain reference to bound it by; the tx.origin liquidator, the setPerpEngine refund and the unwired vault's deposits are intended or lossless.\n\n**Verification.** Clean build, then the full suite with the verifier's fuzz seed: 70 tests pass (69 of the project's, including 6 new ones, plus the reviewer's proof). Both perps hashes match the pinned constants on the clean build. Only allowed paths changed: three source files, two tests plus one new test file, the launch script, README.md and ADAPTATION.md.","treeHash":"dc5b7a3b89c35425118cfd7a22a778367e001b6d","usage":{"cachedInputTokens":2628770,"inputTokens":802,"model":"claude-fable-5-1","outputTokens":43111,"runtime":"claude","turns":55,"wallClockMs":917404}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"04f946173ab09bec","findings":[{"citation":"resolved","description":"_syncEthQueue scales the queue index by backing/ethBackingMark whenever engine.totalEth() has fallen below the mark. The mark is refreshed by every vault action (_markEth) to the then-current totalEth, which includes yield (open fees, funding, penalties routed to plv) that by design is never distributed to the queue ('a RISE in backing is not distributed to the queue'). So the sequence yield-accrues -> any vault action re-marks -> loss realised writes the queue down by the loss's share of the PEAK, although the queue's claim was fully backed and the yield that was lost belonged entirely to live shares. The asymmetry is a transfer from queued claimants to live stakers; it needs a realised PLV loss (long bad debt beyond insurance, or a funding credit beyond insurance), which the engine otherwise keeps rare, so the impact is bounded and conditional.","line":433,"path":"src/perps/PerpVault.sol","reproduction":"State: live ETH shares backed by A = 10 ETH, a queued exit Q = 10 ETH units at index 1 (totalEth = 20, ethBackingMark = 20). Yield of 10 ETH accrues to plv (totalEth = 30; assetsEth = 20, all of it live shares' since pendingEth stays 10). Any vault action, e.g. a third party's depositEth of 1 wei, runs _markEth: ethBackingMark = 30. The engine then realises a 10 ETH PLV loss (totalEth back to 20, which still fully backs the 10 ETH queue). Next claimPendingEth/withdrawEth/settlePendingEth: _syncEthQueue sees backing 20 < mark 30 and sets newIdx = idx*20/30: the queued claimant is now owed 6.67 ETH (QueueWrittenDown emitted for 3.33 ETH) while live shares hold assetsEth = 20 - 6.67 = 13.33 ETH, a 33% gain over their 10 ETH. Expected: with totalEth (20) >= claims (10) a fully backed, non-yield-bearing claim keeps 10 ETH and the live shares, which took all 10 ETH of yield, bear the 10 ETH loss. Actual: 3.33 ETH moves from the queued claimant to the live stakers. Fix direction: record the mark net of yield (e.g. mark = backing - max(0, backing - lastMark) only for vault-caused moves, or mark the queue's own share of backing), or only scale the index when backing < claims.","severity":"low","snippet":"        uint256 newIdx = (mark != 0 && backing < mark)\n            ? FullMath.mulDiv(idx, backing, mark)\n            : idx;","title":"PerpVault ETH exit queue is marked at the ETH side's peak: a loss after yield is charged to queued claims that never received the yield"}],"hash":"29c8b18fca08ac83a835d865763968122aeefc8bc38a061f578aad5d27187c1b","nodeId":"c3d70844-3a94-4a51-bf24-5bb77877171c","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds the result: one low finding and 104 coverage rows (every listed entry point plus three invariants). The working tree shows no changes to source or configuration; only the findings file and my scratch test were created.\n\n**What I found**\n\n- **Low, PerpVault exit queue marked at the peak.** The ETH exit queue is written down by the ratio of current backing to a mark that every vault action refreshes to the full ETH side, yield included. Since yield is never distributed to the queue, a loss that follows yield charges queued claimants for gains they never received. With 10 ETH live, 10 ETH queued, 10 ETH of yield, a re-mark and then a 10 ETH loss, the queued claim drops to 6.67 ETH while live shares end 33% up. It needs a realised PLV loss, which the engine otherwise keeps rare, so the impact is bounded and I rated it low without a proof file.\n\n**What I checked and rejected**\n\n- **Long close-out test versus real pool ETH.** I suspected the long trigger's virtual reserve could overstate proceeds near the opening tick. By conservation the only tokens that could push a sale past that tick are the engine's own, so the constant-product price is exact for every reachable sale. Not a defect.\n- **Engine swaps skipping the pre-trade sweep.** A short's opening sale moves the price without the hook's sweep. A scratch test opened four 5x longs at the long cap and three shorts to the short cap in one second. Every long was closed in the post-open sweep with the ETH side and insurance both up and no bad debt, so the long-side cascade reserve covers it. The test passes on current code and is therefore not attached as a proof.\n- **Fee refund pair, split and owed accounting, sellHeld's three rounds, burnImd, recoverEth, lowerFee's move, short buy-back budgets, engine ETH solvency.** All traced and hold. Two trust notes are recorded in the coverage rows: a refusing perp fee destination makes flushFees revert until the timelock repoints it, and anyone can create the perps first with zero insurance, which fundInsurance then tops up.\n\n**Coverage limits.** Admin setters are rated on their parameter bounds rather than on economic tuning scenarios. I did not reach a funding-rate farming analysis beyond confirming the crowded side always pays net, and I did not run the Robinhood fork test.","treeHash":null,"usage":{"cachedInputTokens":2366061,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":81817,"runtime":"claude","turns":23,"wallClockMs":1331352}},{"artifacts":[],"attempt":1,"bundleHash":"c318b1bc4facf5648540e38194cf33703e9fdeab4015489fe9f1240201cbe216","device":"23617eab0c5242d6","findings":[],"hash":"3a9b60d7857471032d269522e2f521eb33bfc16e5fcbfe2744af07e60d15dc83","nodeId":"0c95d427-6b61-4b97-b449-4006c1a42ea0","outcome":"completed","summary":"I added tests in `test/Invariants.t.sol` for the two source changes another contributor made since my last round. I changed nothing else. `forge build` succeeds and `forge test` passes with the verifier's fuzz seed: 93 tests across 7 suites, none failing.\n\n**The PerpVault yield fix** (the token side's yield index moved from 1e18 to 1e36):\n- The perps handler now has a `claimTokYield` action. Each claim must pay exactly what `pendingTokYield` promised, in ETH, taken from the engine's token-side yield pot (`tokYieldEth`). Afterwards nothing may still be pending. If a staker was promised yield, the claim must not be refused.\n- **New invariant `invariant_TheTokenYieldIsBacked`:** what the token stakers can still claim never exceeds the pot. Also, what has been claimed plus what is left in the pot never exceeds the total yield the engine has credited.\n- **New unit test `test_TheHandlerClaimsTheTokenYield`:** after one short, the only token staker is owed the whole credited yield, short by one wei at most. The claim goes through, and a staker with nothing owed is refused.\n\n**The `releaseUnclaimed(address(this))` fix** (the unclaimed reserve can now be kept in the launch as coins the timelock can move):\n- The launch handler now has a `releaseUnclaimed` action, called by the owner with one of three destinations: burn, an outside address, or the launch itself. It asserts that the release is refused before the claim deadline. It also checks that a reserve kept in the launch is booked as held coins (`excessHeld`) and that a burn removes exactly the reserve.\n- With this action the existing invariants over coin conservation and \"reserve plus held coins\" now cover the fix.\n- **New unit test `test_TheHandlerReleasesTheUnclaimedReserve`:** the release is refused while claims are open. After the deadline the kept reserve becomes held coins, the timelock can release it, and no coins are left stuck in the launch. A second release is refused.\n\nNone of the new properties failed in the fuzz runs, so I found no new defects and did not write `.imd-findings.json`. The fork test against Robinhood Chain still needs `ROBINHOOD_RPC_URL` and has not been run here.","treeHash":"dc1e6f491a91efff411ffcc57b0c72e3ceb32575","usage":{"cachedInputTokens":772138,"inputTokens":30,"model":"claude-opus-5-5","outputTokens":7985,"runtime":"claude","turns":16,"wallClockMs":271019}},{"artifacts":[],"attempt":1,"bundleHash":"6999a35fc6ececca2615e9bce879fb7fc046936459b89a7f5eb9f8d17deabf99","device":"d7e6877fd9e388bb","findings":[],"hash":"48006eca754947846db29a1c869786ded64f733b11ed9bc4498009b2e1b52e96","nodeId":"d15b04fb-54fc-47a4-af41-d850645b4daf","outcome":"completed","summary":"Done. launch.json is written and is the only change in the tree.\n\n**What it contains.** One contract, IMD6900RobinhoodLaunch, with four constructor arguments in the contract's declared order: owner, PoolManager, IMDSTR, timelock. The owner argument is `$owner`, which resolves to the team wallet the brief names. The other three are the static addresses from the brief. The notes explain each argument, that the constructor creates only IMD6900LockedPool, that createHook and createPerps happen later by the team and not the deployment, and that no ETH or initialization call is needed.\n\n**Checks run.**\n- The manifest parses as JSON with exactly the keys kind, contracts and notes, and the notes are under the 4,000 character limit.\n- `forge build` compiles cleanly.\n- `forge test` passes all 64 tests across 3 suites, none skipped.\n\n**What the protected checks require.** The Contracts probe deploys each manifest contract with CREATE2 from the factory on an empty chain and scans the runtime for CALLCODE, DELEGATECALL and SELFDESTRUCT. The constructor calls no external contract, so it survives the fresh chain. The Owners check refuses a contract owned by the factory or IMD, which `$owner` satisfies.","treeHash":"442ae38b58155ecbbbb90f2fb810c20c55155a4c","usage":{"cachedInputTokens":226123,"inputTokens":194,"model":"claude-fable-5-1","outputTokens":3170,"runtime":"claude","turns":10,"wallClockMs":77291}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"61b4050710026370","findings":[],"hash":"59cc14df8d9a60295608aa4bda1753ab7d4667bf4562d775ef55c472b9ccb2f9","nodeId":"54aaeeda-03fa-4321-9d1b-fba4614f4d39","outcome":"completed","summary":"The review is settled with no outstanding defects. The findings file parses, lists zero findings, and answers all 101 entry points plus one settlement row per prior finding. The working tree is clean apart from the findings file, and the scratch copies of the proofs were removed.\n\n**Fixed, confirmed against the revised code**\n\n- **Vault yield rounding (medium):** the token-side accumulator constant is now 1e36 and is the one constant every fold, reward debt, settle and the pending view use. Both attached proofs that failed last round now pass, with the minimum short's yield claimable to within one wei. The vault's creation-code hash was re-pinned and the audited-code test ties it to the imported contract. The engine hash is unchanged.\n- **releaseUnclaimed to the launch itself (low):** the self branch now books the reserve into excessHeld instead of a no-op transfer. The attached proof, the author's new test and the reserve-and-excess invariant all pass.\n- **launch.json owner (low):** the first constructor argument is the brief's static team wallet. The file has only the three schema keys, four arguments in constructor order, and a 2007-character note.\n\n**Disputed by the author, settled as documented trust assumptions**\n\nThe perp-position veto on fee moves, the pari-passu queue write-down, the keeper-priced $IMD burn, tx.origin as the sweep liquidator, the any-address refund via setPerpEngine, and deposits into a retired vault all still reproduce by tracing. Each is an intended design or a power the brief grants, and each is now documented in the README. None is a loss of funds or a permission bypass, so none reopens the work. They are recorded as settled rows in the coverage record rather than as findings.\n\n**Checks run**\n\n- Full suite: 93 tests passed, 0 failed, including the invariants and the audited-code hash test.\n- The three attached proofs: all pass on this tree.\n- No new defect was found in the lines the fix touched.","treeHash":null,"usage":{"cachedInputTokens":742106,"inputTokens":290,"model":"claude-fable-5-1","outputTokens":21391,"runtime":"claude","turns":18,"wallClockMs":313732}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"c97fd6a73e3737dc","findings":[{"citation":"resolved","description":"Token-side ETH yield (20% of every short's open fee and liquidation penalty, PerpEngine._routeFee) is folded into a MasterChef-style index as floor(yield * ACC / tokShares) with ACC = 1e18 (line 113). Token shares are minted at OFFSET = 1e6 times the token amount (line 625, line 62), so with S tokens staked (18 decimals) the index can only resolve yield in steps of S * 1e6 / 1e18 wei. IMD6900's supply is 1e27 wei: with 300M coins staked (3e26 wei, 3e32 shares) one step is 3e14 wei, and every fold loses up to that much. The remainder is not carried: lastTokYieldCum is advanced to cum unconditionally, so the un-folded wei are never credited to anyone and sit in the engine's tokYieldEth pot with no claimant (withdrawTokYieldTo pays only what the vault settled). The LP share of a minimum-size short (minCollateral 0.01 ETH: 0.01074 ETH sent, 6.9% fee = 7.41e14, 20% = 1.48e14 wei) floors to zero entirely: floor(1.48e14 * 1e18 / 3e32) = 0. Because every depositToken / withdrawToken / claimTokYield / settle call folds, and depositToken(1 wei) still mints 1e6 shares, anyone can fold after each short and strip stakers of up to 3e14 wei per fold; a 1 ETH short's yield (1.38e16 wei) loses up to 2.2% per fold even without griefing. Seam: boundary (large stake, small yield) x precision (fixed 1e18 scale) x invariant (the pot equals the sum of claims; it does not, the difference is orphaned). Fix: scale the accumulator by ACC * OFFSET (e.g. 1e36 in a uint256, mulDiv already handles the 512-bit intermediate) or carry the unfolded remainder of (cum - last) forward instead of advancing lastTokYieldCum past it.","line":566,"path":"src/perps/PerpVault.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PerpVault} from \"src/perps/PerpVault.sol\";\nimport {IMD6900} from \"src/IMD6900.sol\";\n\n/// A stand-in for PerpEngine's vault-facing surface: the token side's inventory and the short-attributed ETH yield\n/// pot, credited the way PerpEngine._routeFee credits it (tokYieldEth and tokYieldCumulative up by the same amount).\ncontract EngineStub {\n    address public token;\n    uint256 public plvToken;\n    uint256 public tokYieldEth;\n    uint256 public tokYieldCumulative;\n\n    constructor(address t) {\n        token = t;\n    }\n\n    function credit() external payable {\n        tokYieldEth += msg.value;\n        tokYieldCumulative += msg.value;\n    }\n\n    function fundTokenFromVault(uint256 amount) external {\n        IMD6900(token).transferFrom(msg.sender, address(this), amount);\n        plvToken += amount;\n    }\n\n    function withdrawPlvTokenTo(uint256 amount, address to) external {\n        plvToken -= amount;\n        IMD6900(token).transfer(to, amount);\n    }\n\n    function withdrawTokYieldTo(uint256 amount, address to) external {\n        tokYieldEth -= amount;\n        (bool ok,) = to.call{value: amount}(\"\");\n        require(ok);\n    }\n\n    function totalTokenAssets() external view returns (uint256) {\n        return plvToken;\n    }\n\n    function freeToken() external view returns (uint256) {\n        return plvToken;\n    }\n\n    function totalEth() external pure returns (uint256) {\n        return 0;\n    }\n\n    function freeEth() external pure returns (uint256) {\n        return 0;\n    }\n\n    function fundFromVault() external payable {}\n    function withdrawPlvTo(uint256, address) external {}\n}\n\n/// PerpVault folds short-side ETH yield into `accEthPerTokShare` as yield * 1e18 / tokShares. Token shares are minted\n/// at 1e6 x the token amount (OFFSET), so with 300M IMD6900 staked (3e26 wei, 3e32 shares) one fold can only resolve\n/// yield in steps of 3e32 / 1e18 = 3e14 wei: the 20% LP share of a minimum-size short's open fee (0.01 ETH collateral,\n/// 1.48e14 wei to the stakers) floors to zero and stays in the engine's pot with no claimant, for good.\ncontract VaultYieldRoundingTest is Test {\n    uint256 constant STAKE = 300_000_000 ether; // 30% of the supply on the token side\n    uint256 constant YIELD = 1.48e14; // PerpEngine: 0.01074 ETH sent, 6.9% open fee, 20% of it to the stakers\n\n    function test_SmallShortYieldIsLostToTheAccumulatorsRounding() public {\n        IMD6900 coin = new IMD6900(address(this));\n        EngineStub engine = new EngineStub(address(coin));\n        PerpVault vault = new PerpVault(address(engine));\n        address staker = address(0x57A4E);\n        coin.transfer(staker, STAKE);\n        vm.prank(staker);\n        coin.approve(address(vault), STAKE);\n        vm.prank(staker);\n        vault.depositToken(STAKE); // the only staker: every wei of yield from here on is theirs\n\n        engine.credit{value: YIELD}(); // a short opens: its fee's LP share lands in the pot\n        uint256 pending = vault.pendingTokYield(staker);\n        emit log_named_uint(\"yield credited (wei)\", YIELD);\n        emit log_named_uint(\"yield the only staker can claim (wei)\", pending);\n        // the pot holds the yield and the only staker should be able to claim (nearly) all of it\n        assertGe(pending, YIELD * 9 / 10, \"the only staker's yield vanishes in accEthPerTokShare's rounding\");\n        vm.prank(staker);\n        uint256 paid = vault.claimTokYield();\n        assertGe(paid, YIELD * 9 / 10);\n    }\n}","reproduction":"1. Deploy PerpVault on the engine (or the stub in the proof). A staker deposits 300_000_000e18 IMD6900: tokShares = 3e26 * 1e6 = 3e32 (the only staker, so 100% of yield is theirs).\n2. A short opens with the minimum collateral: engine._routeFee credits tokYieldEth += 1.48e14 and tokYieldCumulative += 1.48e14 (the proof credits the stub directly).\n3. vault.pendingTokYield(staker): expected ~1.48e14 wei; actual 0 (accEthPerTokShare += floor(1.48e14 * 1e18 / 3e32) = 0). vault.claimTokYield() reverts ZeroAmount. lastTokYieldCum now equals cum, so the 1.48e14 wei are never folded again and stay in the engine's pot with no claimant.\nRun: forge test --match-path test/scratch/VaultYieldRounding.t.sol -vv (fails: 'yield the only staker can claim (wei): 0').","severity":"medium","snippet":"                    accEthPerTokShare += FullMath.mulDiv(_toYieldUnit(cum - last), ACC, sh);","title":"PerpVault token-side yield accumulator floors small short-side yield to zero: ACC=1e18 is too coarse for shares minted at 1e6x an 18-decimal, 1e27-supply token"}],"hash":"c967a6e0ef680b5fd7d26242bdf802cb91167e56abb9fd88178520d52fbfbc9f","nodeId":"67ca28fd-44fb-4905-bdfd-3b68a5bc2d32","outcome":"completed","summary":"The findings file is complete and validated: one medium finding with an embedded, failing Foundry proof, plus 104 coverage rows covering all 101 listed entry points and three invariants. The snippet matches the tree at `src/perps/PerpVault.sol:566`.\n\n## Recap\n\n**Area covered**: Math Precision, Boundary and Numerical Gap passes over all seven contracts, with the perps (engine, vault, swap library) traced in depth.\n\n**Finding (medium)**: the vault's token-side yield accumulator loses yield to rounding. `accEthPerTokShare` folds yield as `yield * 1e18 / tokShares`, but token shares are minted at a million times the token amount for a token with a 1e27 supply. With 300M coins staked, the index resolves yield only in steps of about 3e14 wei. The 20% LP share of a minimum-size short's open fee is 1.48e14 wei and floors to zero. The remainder is never carried forward, so it sits in the engine's yield pot with no claimant. Anyone can trigger a fold with a 1 wei deposit, so stakers can be stripped of small-short yield repeatedly. The proof in `test/scratch/VaultYieldRounding.t.sol` fails on the current code (the only staker can claim 0 wei) and would pass with a finer accumulator scale or a carried remainder.\n\n**What holds, and why it matters for the author**:\n- The refund pair is exact to the wei. v4 rounds the fee up and the position's fees down, and the engine's 32 wei of kept-back input absorbs the loss, so a grossed-up sell can never underflow the position's size.\n- The engine's full-range depth model is exact for every reachable price. Coins outside the pool can never exceed what it takes to reach the position's upper tick, so no seller can push the price past it while a long still holds its coins. I traced this because the engine's comment assumes a full-range book while the real position ends at the opening price.\n- The locked pool's opening liquidity, the fee move's re-add and its fallback liquidity formula round in the safe direction and the kept margin covers the wei of difference.\n- The launch's split, claims and held-coin sale arithmetic cannot underflow or leak beyond dust.\n\n**Not reached**: nothing in the listed entry points; the fork test and the IMD pool buy path were reasoned about, not executed against a Robinhood fork.","treeHash":null,"usage":{"cachedInputTokens":1935399,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":61486,"runtime":"claude","turns":19,"wallClockMs":907471}},{"artifacts":[],"attempt":1,"bundleHash":"778b9ee391ca8a89e47e83e4d357c9875188b8e455f68897be26d8555c336bf0","device":"0238a59bba722237","findings":[],"hash":"e502beb23af302c7dd849bc06874ebc586676def73db2701a40c1f49eb047955","nodeId":"d15b04fb-54fc-47a4-af41-d850645b4daf","outcome":"completed","summary":"The finding held and is fixed. The only changed file is launch.json.\n\n**What changed**\n\n- The first constructor argument of IMD6900RobinhoodLaunch is now the static address 0x35da9c0303507ddf708e87f2568eddf12c47a059 instead of \"$owner\". The brief states that owner itself, and the manifest convention writes a brief-stated owner as the literal address.\n- The notes were reworded to say the owner is written as the static address. The other three arguments (PoolManager, IMDSTR, timelock), the contract name and the kind are unchanged.\n\n**Verification**\n\n- launch.json parses with only kind, contracts and notes. Name length is 22 characters and notes are 2007 characters.\n- forge build succeeds and forge test with the verifier's fuzz seed passes 69 of 69 tests. No Solidity changed.\n- The protected owner check only forbids factory or IMD ownership, so it is unaffected either way.\n\nThe answer to the finding is recorded as \"fixed\" in .imd-responses.json at the repository root.","treeHash":"b3cd033f9de2f604603a213db75c4644d0ecd2b0","usage":{"cachedInputTokens":221894,"inputTokens":194,"model":"claude-fable-5-1","outputTokens":2694,"runtime":"claude","turns":7,"wallClockMs":150458}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"fba19b641cdd6f27","findings":[{"citation":"resolved","description":"Asymmetry between the two functions that end the claims. The timelock's endClaims() folds the unclaimed reserve into excessHeld, and the timelock's releaseExcess() refuses `to == address(this)` with BadTo because 'it is here already'. The owner's releaseUnclaimed(to) has no such guard: with `to == address(this)` it transfers the reserve to itself (a no-op transfer), sets unclaimedReleased = true (so claimReserve() drops to 0 and the call can never be repeated), and does not add the amount to excessHeld. After that no function can move those coins: releaseExcess and sellHeld are bounded by excessHeld (ZeroAmount above it), releaseUnclaimed reverts AlreadyReleased, claim reverts AlreadyReleased, and the contract has no other token transfer. The coins are permanently frozen in the launch contract. Preconditions: the owner (team wallet) passes the launch's own address, a plausible slip given that launch() itself takes `excessTo == address(this)` to mean 'keep it here for the timelock' and books it into excessHeld. Minimal fix preserving the design: in releaseUnclaimed, either revert BadTo on `to == address(this)` (mirroring releaseExcess) or book `excessHeld += amount` on that branch (mirroring launch() and endClaims()).","line":343,"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 {PoolManager} from \"@uniswap/v4-core/src/PoolManager.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\";\nimport {IMD6900LockedPool} from \"src/IMD6900LockedPool.sol\";\nimport {IMD6900} from \"src/IMD6900.sol\";\n\ncontract MockImdstr {\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\n/// releaseUnclaimed(address(launch)) strands the unclaimed reserve: it leaves claimReserve() but never enters\n/// excessHeld, and no function moves coins held outside excessHeld.\ncontract ReleaseUnclaimedToSelfTest is Test {\n    address constant OWNER = address(0xA11CE);\n    address constant TL = address(0x71E10C);\n    address constant A = address(0xA1);\n    uint256 constant AA = 1_000 ether;\n    uint256 constant SUPPLY = 1_000_000_000 ether;\n\n    IPoolManager PM;\n    IMD6900RobinhoodLaunch L;\n    IMD6900LockedPool pool;\n    MockImdstr imdstr;\n    address t;\n\n    function setUp() public {\n        PM = IPoolManager(address(new PoolManager(address(this))));\n        imdstr = new MockImdstr();\n        L = new IMD6900RobinhoodLaunch(OWNER, PM, address(imdstr), TL);\n        pool = L.pool();\n        L.createHook(_mineHook());\n        bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(A, AA))));\n        vm.prank(OWNER);\n        L.setClaims(leaf, AA, block.timestamp + 100 days);\n        bytes32 salt = _mineCoin();\n        address expected = L.tokenAddress(salt);\n        uint160 sp = uint160(_sqrt(FullMath.mulDiv(SUPPLY, 1 << 192, 19.18 ether)));\n        vm.deal(OWNER, 2.81 ether);\n        vm.prank(OWNER);\n        (t,) = L.launch{value: 2.81 ether}(salt, expected, sp, 0, address(L));\n    }\n\n    function test_ReleaseUnclaimedToSelfStrandsTheReserve() public {\n        vm.warp(block.timestamp + 101 days);\n        uint256 heldBefore = L.excessHeld();\n        uint256 balBefore = IMD6900(t).balanceOf(address(L));\n        vm.prank(OWNER);\n        L.releaseUnclaimed(address(L)); // accepted, unlike releaseExcess(_, address(L)) which reverts BadTo\n        assertEq(IMD6900(t).balanceOf(address(L)), balBefore, \"the coins never left\");\n        assertEq(L.claimReserve(), 0, \"no longer reserved for claims\");\n        // expected: the reserve joins excessHeld, as endClaims does; actual: excessHeld is unchanged\n        assertEq(L.excessHeld(), heldBefore + AA, \"the unclaimed reserve should be held for the timelock\");\n    }\n\n    function test_NothingCanMoveTheStrandedCoins() public {\n        vm.warp(block.timestamp + 101 days);\n        vm.prank(OWNER);\n        L.releaseUnclaimed(address(L));\n        uint256 held = L.excessHeld();\n        vm.prank(TL);\n        vm.expectRevert(IMD6900RobinhoodLaunch.ZeroAmount.selector);\n        L.releaseExcess(held + 1, TL);\n        vm.prank(TL);\n        vm.expectRevert(IMD6900RobinhoodLaunch.ZeroAmount.selector);\n        L.sellHeld(held + 1, 0, TL);\n        vm.prank(OWNER);\n        vm.expectRevert(IMD6900RobinhoodLaunch.AlreadyReleased.selector);\n        L.releaseUnclaimed(TL);\n    }\n\n    function _mineHook() internal view returns (bytes32) {\n        bytes32 initHash = L.hookInitCodeHash();\n        for (uint256 i;; ++i) {\n            address a = _create2(address(pool), bytes32(i), initHash);\n            if (uint160(a) & Hooks.ALL_HOOK_MASK == L.FLAGS() && uint160(a) >> 152 != 0x91) return bytes32(i);\n        }\n    }\n\n    function _mineCoin() internal view returns (bytes32) {\n        bytes32 initHash = L.tokenInitCodeHash();\n        for (uint256 i;; ++i) {\n            if (uint160(_create2(address(L), bytes32(i), initHash)) >> 144 == 0x6900) return bytes32(i);\n        }\n    }\n\n    function _create2(address d, bytes32 s, bytes32 h) internal pure returns (address a) {\n        assembly (\"memory-safe\") {\n            let p := mload(0x40)\n            mstore8(p, 0xff)\n            mstore(add(p, 0x01), shl(96, d))\n            mstore(add(p, 0x15), s)\n            mstore(add(p, 0x35), h)\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":"State: launched with claims (claimTotal = 1000e18, nothing claimed), block.timestamp >= claimDeadline. Input: owner calls releaseUnclaimed(address(launch)). Expected: the reserve is either refused (BadTo, as releaseExcess does) or held for the timelock (excessHeld += 1000e18, as endClaims does). Actual: the call succeeds, excessHeld is unchanged, claimReserve() == 0, the launch's IMD6900 balance is unchanged, and afterwards releaseExcess(excessHeld+1, tl) and sellHeld(excessHeld+1, 0, tl) revert ZeroAmount while releaseUnclaimed(tl) reverts AlreadyReleased: 1000e18 coins are frozen. The attached test fails on this code at the excessHeld assertion (122346490873046793208752165 != 122347490873046793208752165) and its second test demonstrates that nothing can move the coins.","severity":"low","snippet":"        if (amount != 0) {\n            if (to == address(0)) IMD6900(token).burn(amount);\n            else SafeTransferLib.safeTransfer(token, to, amount);\n        }","title":"releaseUnclaimed accepts the launch contract itself as `to`, stranding the unclaimed reserve outside excessHeld for good"},{"citation":"resolved","description":"Trust gap, access x economics (Pashov Seam 1: a role-restricted function whose swap floor is supplied by the role itself). burnImd(minImd) is callable by imdKeeper, or by the owner while none is set. It spends min(imdBucket, 0.1 ether) on the $IMD pool at MIN_SQRT_PRICE+1 and the only floor is the caller's minImd. The keeper is designed as a low-trust operational role ('it quotes the buy and passes the floor'), yet it fully controls the execution price of ETH that belongs to the $IMD burn (10% of every fee split, 'fixed in the code'). A keeper, or anyone holding a compromised keeper key, can in one bundle: (1) buy $IMD on the ETH/$IMD pool to push the price up, (2) call burnImd(0), which buys at the inflated price and sends very little $IMD to the executor, (3) sell back, pocketing most of the 0.1 ETH less two pool fees. The per-L1-block throttle bounds the rate (0.1 ETH / ~12 s), not the total: the whole bucket drains over time. Everyone else is unaffected, and the owner/timelock are trusted in the brief, so this is reported as a documented trust assumption with a concrete loss path rather than a bypass. Minimal fix keeping the keeper design: bound the fill independently of the caller, e.g. require `imd >= quote(spend) * (1 - X%)` from a TWAP of the $IMD pool, or let the timelock set a maximum slippage in bps that burnImd enforces in addition to minImd.","line":556,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"State: after launch and a few splits, imdBucket = 1 ether; imdKeeper = K (set by the timelock via setImdKeeper, or K = owner). Input, one transaction from K's EOA via a helper contract: swap 5 ETH -> $IMD on the ETH/$IMD 1% pool (price up), call launch.burnImd(0) (succeeds: imd < 0 is never true), swap the $IMD back. Expected: the 0.1 ETH buys roughly 0.1 ETH worth of $IMD for the burn executor. Actual: the burn executor receives $IMD worth a fraction of 0.1 ETH at the fair price and K keeps the difference minus two 1% fees; repeat every L1 block (nextImdBurnBlock = block.number + 1) until imdBucket is empty. Nothing in the contract bounds the fill besides K's minImd.","severity":"low","snippet":"        if (imd < minImd) revert Short(imd);","title":"burnImd's only price protection is the keeper's own minImd: a keeper key (a bot the timelock appoints, the team wallet by default) can self-sandwich the $IMD burn bucket at up to 0.1 ETH per L1 block"},{"citation":"resolved","description":"Asymmetry between the two liquidation entry points. PerpEngine.liquidate(id) pays the keeper cut (keeperBps of the penalty) to msg.sender, the account that did the work. The hook's sweep, which is where almost every liquidation happens, pays it to tx.origin. For a trade sent through a smart-contract wallet, an ERC-4337 bundler, a relayer or a batching contract, tx.origin is a third party that neither traded nor chose to liquidate. The payment itself is small (145 bps of a 6.9% penalty, about 0.1% of the liquidated collateral) and never leaves the engine's accounting, so this is informational: funds go to an unintended party, not an attacker, and nothing is lost to the protocol. If the intent is 'the EOA that sent the transaction', document it; if it is 'whoever traded', pass `sender` (the swap's router or wallet) instead.","line":266,"path":"src/IMD6900Hook.sol","reproduction":"State: an engine is wired and one long is liquidatable at the price a pending sell would leave. Input: account W (a smart-contract wallet) sells IMD6900 through the Universal Router inside a UserOperation relayed by bundler EOA B. Expected: the keeper cut reaches W (or its router), as PerpEngine.liquidate(id) would pay msg.sender. Actual: _sweep passes tx.origin == B, _settle(..., MODE_LIQUIDATION, B) calls _payOut(B, toKeeper): B receives the cut, W receives nothing.","severity":"info","snippet":"            (bool ok, bytes memory out) = engine.call{gas: g - reserve}(\n                abi.encodeCall(IPerpSweep.sweepLiquidations, (tx.origin, spec, isBuy, limit))\n            );","title":"The pre-trade sweep names tx.origin as the liquidator, so the keeper cut of in-swap liquidations goes to the relayer/bundler rather than the account that traded"},{"citation":"resolved","description":"Documented for the judge, not a bypass: the brief says only the hook's perps engine and the launch's sellHeld trade fee-free, and that the timelock owns the hook. isRefunded() defines the refunded engine as whatever hook.perpEngine() returns, and IMD6900Hook.setPerpEngine (onlyOwner, i.e. the Robinhood timelock, behind its delay) accepts any address with no code check, no code-hash check and no openCount() requirement when the current engine is zero or has no positions. So the timelock can name a router contract R; from then on every swap R routes is refunded its whole 4.44% fee (feeCheckpoint/feeRefund around the swap), which removes the fee split's income (pot bridge, $IMD burn) for all trades through R while the pool's fee stays nominally static. The existing test test_TheEngineTradesFeeFree exercises exactly this with a hand-written RefundingEngine, so it is an accepted design choice; it is listed here because it widens the timelock's documented role (hook owner, recover ETH, split, landing addresses) to 'can make any party trade fee-free', which readers of the brief may not expect. No change is proposed; if the requester wants the refund bound to pool-created engines only, isRefunded could compare against IMD6900LockedPool.perpsEngine / the hook's poolEngine instead of perpEngine.","line":290,"path":"src/IMD6900LockedPool.sol","reproduction":"State: launched, no engine wired (hook.perpEngine() == 0). Input: timelock calls hook.setPerpEngine(R) where R is any contract that calls pool.feeCheckpoint(), swaps, then pool.feeRefund(R) inside its own unlock (the RefundingEngine in test/IMD6900RobinhoodLaunch.t.sol). Expected per the brief: only the perps engine and sellHeld are fee-free. Actual: pool.isRefunded(R) is true and R's 1 ETH buy gets 0.0444 ETH back (asserted by test_TheEngineTradesFeeFree); every user routing through R trades at 0% effective fee and the split collects nothing from them.","severity":"info","snippet":"        return a == launcher || (a != address(0) && a == IPerpsWiring(hook).perpEngine());","title":"Trust assumption: the hook owner's setPerpEngine(x) makes any address x a fee-refunded trader in the locked pool, not only an engine the pool created"}],"hash":"e8a9e06c0dc88c1d363fee8ed6d559cc10bfa4181472cdd089e1dc1933a65a63","nodeId":"b3fab657-ee5d-4802-bd01-435642f66f72","outcome":"completed","summary":"Review complete. The findings file is at `.imd-findings.json`; git shows no tracked file modified (the findings file and `test/scratch/` are untracked and within my allowed paths).\n\n**Outcome.** The permission model holds up under the Access Control, Trust Gap and Asymmetry guides. Every privileged function is guarded by the role the brief names (team owner, Robinhood timelock, pool-only, hook-only, vault-only, PoolManager-only), the fee-refund pair is keyed per caller and traced correctly even with engine liquidations nested inside the timelock's sale, and no unguarded writer exists for any variable a guarded function also writes. I found no critical, high or medium defect. The full offline suite passes (63 tests), the perps code hashes match this build, and the fork test asserts the pot bridge accepts ETH.\n\n**Findings reported**\n\n- **Low, `releaseUnclaimed` to self strands the reserve** (`src/IMD6900RobinhoodLaunch.sol:343`). The owner's release path lacks the `to == address(this)` guard that `releaseExcess` has and does not book into `excessHeld` the way `endClaims` and `launch` do. After such a call the unclaimed coins are frozen forever. Verified with a scratch test that fails on the current code and shows no function can move the coins; it is attached as the proof.\n- **Low, keeper-controlled slippage on the $IMD burn** (`src/IMD6900RobinhoodLaunch.sol:556`). The keeper's own `minImd` is the only price bound, so a keeper key can self-sandwich the burn bucket, bounded to 0.1 ETH per L1 block but not in total. Reported as a trust gap with the actor and loss path stated.\n- **Info, keeper cut to tx.origin** in the hook's sweep versus msg.sender in `PerpEngine.liquidate`. Relayed or bundled trades pay the cut to the relayer.\n- **Info, trust assumption**: `setPerpEngine` makes any address the timelock names a fee-refunded trader, which the existing tests show is intentional but widens the timelock's role beyond the brief's wording.\n\n**Coverage.** All 102 verifier-listed entry points have a row, 98 marked holds with a one-line reason, 4 pointing at the findings above. Economics of the liquidation math and vault share pricing were checked only through the asymmetry lens, and the rows say so.","treeHash":null,"usage":{"cachedInputTokens":2879780,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":82143,"runtime":"claude","turns":32,"wallClockMs":1200615}}],"verification":[{"checks":[{"durationMs":34694,"exitCode":0,"name":"build","output":"Compiling 91 files with Solc 0.8.26\nSolc 0.8.26 finished in 34.60s\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":340,"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 360.42µs (27.58µs CPU time)\n\nRan 15 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_AFeeMoveKeepsTheWholePosition() (gas: 364466)\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1048931)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AReplacementEngineIsNotReportedFeeFree() (gas: 234661)\n[PASS] test_AShortRoundTrip() (gas: 1843942)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1885625)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 651630)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120357)\n[PASS] test_PerpFeesReachThePot() (gas: 696631)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 818744)\n[PASS] test_TheHookReplacesOnlyThePoolsOwnEngine() (gas: 17572622)\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145526)\n[PASS] test_ThePerpsAreRecreatedAfterAFeeMove() (gas: 10984195)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 76070587)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14696649)\n[PASS] test_TheTimelocksSaleLiquidatesWhatItSinksFirst() (gas: 2186875)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 232.03ms (210.28ms CPU time)\n\nRan 48 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_ARefusedShareIsOwedNotReSplit() (gas: 2441280)\n[PASS] test_AReplacementEngineStartsNotRefunded() (gas: 2047347)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 3069883)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1277867)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1479856)\n[PASS] test_ClaimsOneForOne() (gas: 1602975)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208761)\n[PASS] test_DeploysOnAFreshChain() (gas: 2918585)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 12904078)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2357732)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599549457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1319181)\n[PASS] test_FitsOneTransaction() (gas: 12882707)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 7825\n  createHook gas: 1389288\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1867611)\nLogs:\n  pot bridge: 0.511887599999999100\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1449419)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1327372)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1318813)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2214950)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1988758)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288387119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2096384)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 2020511)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 179032)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1984654)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1319163)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 70409)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 96233)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 120122)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1833233)\n[PASS] test_PassesTheAdmissionScan() (gas: 16384985)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1470237)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702516)\n[PASS] test_RefusesTheWrongAddress() (gas: 710007)\n[PASS] test_RepointingTheBurnExecutorCancelsWhatItWasOwed() (gas: 1827839)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1293819)\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1215427)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1821242)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1252306)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1315710)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2687832)\n[PASS] test_TheFeeIsStatic() (gas: 1602374)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 21299123)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1364138)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21917)\n[PASS] test_TheLiquidityIsLocked() (gas: 2174622)\n[PASS] test_TheOwnerCantBeRenounced() (gas: 40233)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2337423)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2129503)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1670140)\n[PASS] test_TradesBothWays() (gas: 1574330)\nSuite result: ok. 48 passed; 0 failed; 0 skipped; finished in 232.12ms (178.98ms CPU time)\n\nRan 3 test suites in 233.42ms (464.51ms CPU time): 64 tests passed, 0 failed, 0 skipped (64 total tests)\n","passed":true},{"durationMs":51,"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.createHook(bytes32)\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(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.setImdKeeper(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\":48,\"README.md\":149,\"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,\"script/verify_delivery.py\":68,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":294,\"src/IMD6900LockedPool.sol\":461,\"src/IMD6900RobinhoodLaunch.sol\":624,\"src/perps/PerpEngine.sol\":1160,\"src/perps/PerpSwapLib.sol\":728,\"src/perps/PerpVault.sol\":823,\"test/HookSalt.sol\":51,\"test/IMD6900RobinhoodLaunch.t.sol\":1147,\"test/Perps.t.sol\":371,\"test/RobinhoodFork.t.sol\":297},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":16512,"exitCode":0,"name":"slither","output":"[high/medium] arbitrary-send-eth at src/perps/PerpEngine.sol:948: PerpEngine._sendEth(address,uint256) (src/perps/PerpEngine.sol#948-952) sends eth to arbitrary user\n[high/medium] arbitrary-send-eth at src/IMD6900RobinhoodLaunch.sol:511: IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#511-538) sends eth to arbitrary user\n[high/medium] arbitrary-send-eth at src/IMD6900LockedPool.sol:423: IMD6900LockedPool._payOutHeld() (src/IMD6900LockedPool.sol#423-435) sends eth to arbitrary user\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:712: Reentrancy in PerpEngine._settle(uint256,Position,uint256,uint8,address) (src/perps/PerpEngine.sol#712-787):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:520: Reentrancy in PerpEngine._doSweep(address,bool,int256,bool,uint160) (src/perps/PerpEngine.sol#520-606):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:520: Reentrancy in PerpEngine._doSweep(address,bool,int256,bool,uint160) (src/perps/PerpEngine.sol#520-606):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:520: Reentrancy in PerpEngine._doSweep(address,bool,int256,bool,uint160) (src/perps/PerpEngine.sol#520-606):\n[high/medium] reentrancy-eth at src/IMD6900RobinhoodLaunch.sol:511: Reentrancy in IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#511-538):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:712: Reentrancy in PerpEngine._settle(uint256,Position,uint256,uint8,address) (src/perps/PerpEngine.sol#712-787):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:712: Reentrancy in PerpEngine._settle(uint256,Position,uint256,uint8,address) (src/perps/PerpEngine.sol#712-787):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:520: Reentrancy in PerpEngine._doSweep(address,bool,int256,bool,uint160) (src/perps/PerpEngine.sol#520-606):\n[high/medium] reentrancy-eth at src/perps/PerpVault.sol:253: Reentrancy in PerpVault.depositEth() (src/perps/PerpVault.sol#253-281):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:406: Reentrancy in PerpEngine.openLong(uint8,uint256) (src/perps/PerpEngine.sol#406-429):\n[medium/medium] divide-before-multiply at src/perps/PerpEngine.sol:712: PerpEngine._settle(uint256,Position,uint256,uint8,address) (src/perps/PerpEngine.sol#712-787) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:392: IMD6900RobinhoodLaunch.recoverEth(address,uint256) (src/IMD6900RobinhoodLaunch.sol#392-406) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpSwapLib.sol:246: PerpSwapLib.twapTick(PerpSwapLib.Observation[32],PerpSwapLib.Ring,uint32) (src/perps/PerpSwapLib.sol#246-291) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:511: IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#511-538) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpEngine.sol:362: PerpEngine._pokeFunding() (src/perps/PerpEngine.sol#362-375) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpEngine.sol:917: PerpEngine._routeFee(uint256,bool) (src/perps/PerpEngine.sol#917-935) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpEngine.sol:939: PerpEngine._payOut(address,uint256) (src/perps/PerpEngine.sol#939-946) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:511: IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#511-538) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:288: IMD6900RobinhoodLaunch.withdrawEth(address,uint256) (src/IMD6900RobinhoodLaunch.sol#288-294) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:302: IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address) (src/IMD6900RobinhoodLaunch.sol#302-332) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpEngine.sol:362: PerpEngine._pokeFunding() (src/perps/PerpEngine.sol#362-375) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/IMD6900RobinhoodLaunch.sol:198: Reentrancy in IMD6900RobinhoodLaunch.createHook(bytes32) (src/IMD6900RobinhoodLaunch.sol#198-205):\n[medium/medium] reentrancy-no-eth at src/IMD6900RobinhoodLaunch.sol:438: Reentrancy in IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address) (src/IMD6900RobinhoodLaunch.sol#438-448):\n[medium/medium] reentrancy-no-eth at src/perps/PerpVault.sol:299: Reentrancy in PerpVault.withdrawEth(uint256) (src/perps/PerpVault.sol#299-319):\n[medium/medium] reentrancy-no-eth at src/perps/PerpVault.sol:486: Reentrancy in PerpVault.claimPendingEth() (src/perps/PerpVault.sol#486-506):\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:531: PerpEngine._doSweep(address,bool,int256,bool,uint160).pendingIds (src/perps/PerpEngine.sol#531) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:526: PerpSwapLib.swapLeg(IPoolManager,PoolKey,PerpSwapLib.Req,bool,bytes).refundQuote (src/perps/PerpSwapLib.sol#526) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:527: PerpSwapLib.swapLeg(IPoolManager,PoolKey,PerpSwapLib.Req,bool,bytes).refundTok (src/perps/PerpSwapLib.sol#527) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:159: PerpSwapLib.projectedSqrtPriceX96(uint160,uint256,uint256,int256,bool,uint160).amountIn (src/perps/PerpSwapLib.sol#159) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:273: PerpSwapLib.twapTick(PerpSwapLib.Observation[32],PerpSwapLib.Ring,uint32).lo (src/perps/PerpSwapLib.sol#273) is a local variable never initialized\n[medium/medium] uninitialized-local at src/IMD6900RobinhoodLaunch.sol:366: IMD6900RobinhoodLaunch.setPayees(address[],uint16[]).sum (src/IMD6900RobinhoodLaunch.sol#366) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:571: PerpEngine._doSweep(address,bool,int256,bool,uint160).condemnedRemains (src/perps/PerpEngine.sol#571) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:570: PerpEngine._doSweep(address,bool,int256,bool,uint160).killedAny (src/perps/PerpEngine.sol#570) is a local variable never initialized\n[medium/medium] uninitialized-local at src/IMD6900RobinhoodLaunch.sol:586: IMD6900RobinhoodLaunch.unlockCallback(bytes).sold (src/IMD6900RobinhoodLaunch.sol#586) is a local variable never initialized\n[medium/medium] uninitialized-local at src/IMD6900RobinhoodLaunch.sol:378: IMD6900RobinhoodLaunch.setPayees(address[],uint16[]).kept (src/IMD6900RobinhoodLaunch.sol#378) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:919: PerpEngine._routeFee(uint256,bool).toVault (src/perps/PerpEngine.sol#919) is a local variable never initialized\n[medium/medium] uninitialized-local at src/IMD6900RobinhoodLaunch.sol:585: IMD6900RobinhoodLaunch.unlockCallback(bytes).ethOut (src/IMD6900RobinhoodLaunch.sol#585) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:920: PerpEngine._routeFee(uint256,bool).toIns (src/perps/PerpEngine.sol#920) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpVault.sol:816: PerpVault.pendingTokYield(address).earned (src/perps/PerpVault.sol#816) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:536: PerpEngine._doSweep(address,bool,int256,bool,uint160).scanned (src/perps/PerpEngine.sol#536) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:568: PerpEngine._doSweep(address,bool,int256,bool,uint160).clean (src/perps/PerpEngine.sol#568) is a local variable never initialized\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:577: IMD6900RobinhoodLaunch.unlockCallback(bytes) (src/IMD6900RobinhoodLaunch.sol#577-611) ignores return value by poolManager.settle() (src/IMD6900RobinhoodLaunch.sol#595)\n[medium/medium] unused-return at src/perps/PerpEngine.sol:314: PerpEngine._slot0() (src/perps/PerpEngine.sol#314) ignores return value by (s,t,None,None) = poolManager.getSlot0(poolId) (src/perps/PerpEngine.sol#314)\n[medium/medium] unused-return at src/perps/PerpSwapLib.sol:581: PerpSwapLib._settle(IPoolManager,Currency,uint256) (src/perps/PerpSwapLib.sol#581-597) ignores return value by poolManager.settle() (src/perps/PerpSwapLib.sol#595)\n[medium/medium] unused-return at src/perps/PerpSwapLib.sol:581: PerpSwapLib._settle(IPoolManager,Currency,uint256) (src/perps/PerpSwapLib.sol#581-597) ignores return value by poolManager.settle{value: amount}() (src/perps/PerpSwapLib.sol#584)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:211: IMD6900LockedPool.lowerFee(uint24) (src/IMD6900LockedPool.sol#211-230) ignores return value by (sqrtPriceX96,None,None,None) = poolManager.getSlot0(old.toId()) (src/IMD6900LockedPool.sol#222)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:389: IMD6900LockedPool._settle(int128,int128) (src/IMD6900LockedPool.sol#389-400) ignores return value by poolManager.settle{value: uint256(uint128(- a0))}() (src/IMD6900LockedPool.sol#391)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:568: IMD6900RobinhoodLaunch.harvest() (src/IMD6900RobinhoodLaunch.sol#568-571) ignores return value by pool.collect() (src/IMD6900RobinhoodLaunch.sol#569)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:577: IMD6900RobinhoodLaunch.unlockCallback(bytes) (src/IMD6900RobinhoodLaunch.sol#577-611) ignores return value by poolManager.settle{value: ethIn}() (src/IMD6900RobinhoodLaunch.sol#608)\n[medium/medium] unused-return at src/perps/PerpSwapLib.sol:556: PerpSwapLib._grossUp(IPoolManager,PoolKey,bool,uint256) (src/perps/PerpSwapLib.sol#556-566) ignores return value by (None,None,protocolFee,None) = StateLibrary.getSlot0(poolManager,PoolIdLibrary.toId(key)) (src/perps/PerpSwapLib.sol#561)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:320: IMD6900LockedPool.state() (src/IMD6900LockedPool.sol#320-324) ignores return value by (sqrtPriceX96,tick,None,None) = poolManager.getSlot0(id) (src/IMD6900LockedPool.sol#322)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:370: IMD6900LockedPool._amountsFor(uint128) (src/IMD6900LockedPool.sol#370-382) ignores return value by (p,tick,None,None) = poolManager.getSlot0(poolKey().toId()) (src/IMD6900LockedPool.sol#371)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:389: IMD6900LockedPool._settle(int128,int128) (src/IMD6900LockedPool.sol#389-400) ignores return value by poolManager.settle() (src/IMD6900LockedPool.sol#396)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:302: IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address) (src/IMD6900RobinhoodLaunch.sol#302-332) ignores return value by pool.open(t,sqrtPriceX96,SUPPLY) (src/IMD6900RobinhoodLaunch.sol#317)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:242: IMD6900LockedPool.collect() (src/IMD6900LockedPool.sol#242-246) ignores return value by poolManager.unlock(abi.encode(COLLECT,uint128(0))) (src/IMD6900LockedPool.sol#244)\n[medium/medium] unused-return at src/IMD6900Hook.sol:111: IMD6900Hook.perpFeeBps() (src/IMD6900Hook.sol#111-117) ignores return value by (None,None,protocolFee,None) = StateLibrary.getSlot0(poolManager,poolId) (src/IMD6900Hook.sol#113)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:211: IMD6900LockedPool.lowerFee(uint24) (src/IMD6900LockedPool.sol#211-230) ignores return value by poolManager.initialize(poolKey(),sqrtPriceX96) (src/IMD6900LockedPool.sol#225)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:181: IMD6900LockedPool.open(address,uint160,uint256) (src/IMD6900LockedPool.sol#181-204) ignores return value by poolManager.initialize(poolKey(),start) (src/IMD6900LockedPool.sol#193)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:577: IMD6900RobinhoodLaunch.unlockCallback(bytes) (src/IMD6900RobinhoodLaunch.sol#577-611) ignores return value by (None,back) = pool.feeRefund(address(this)) (src/IMD6900RobinhoodLaunch.sol#590)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:403: IMD6900LockedPool._liquidityFor(uint256,uint256) (src/IMD6900LockedPool.sol#403-418) ignores return value by (p,None,None,None) = poolManager.getSlot0(poolKey().toId()) (src/IMD6900LockedPool.sol#404)\n[low/medium] events-maths at src/perps/PerpEngine.sol:1078: PerpEngine.setShortCap(uint256) (src/perps/PerpEngine.sol#1078-1081) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1105: PerpEngine.setVaultLimits(uint256,uint256) (src/perps/PerpEngine.sol#1105-1108) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1088: PerpEngine.setVaultSplit(uint256,uint256) (src/perps/PerpEngine.sol#1088-1091) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1116: PerpEngine.skimInsurance(uint256,address) (src/perps/PerpEngine.sol#1116-1121) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1061: PerpEngine.setFees(uint256,uint256,uint256) (src/perps/PerpEngine.sol#1061-1064) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1094: PerpEngine.setGuards(uint32,uint256,uint256) (src/perps/PerpEngine.sol#1094-1097) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1067: PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256) (src/perps/PerpEngine.sol#1067-1075) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:406: PerpEngine.openLong(uint8,uint256) (src/perps/PerpEngine.sol#406-429) should emit an event for: \n[low/medium] missing-zero-check at src/IMD6900LockedPool.sol:181: IMD6900LockedPool.open(address,uint160,uint256).token_ (src/IMD6900LockedPool.sol#181) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900RobinhoodLaunch.sol:185: IMD6900RobinhoodLaunch.constructor(address,IPoolManager,address,address).timelock_ (src/IMD6900RobinhoodLaunch.sol#185) lacks a zero-check on :\n[low/medium] missing-zero-check at src/perps/PerpVault.sol:198: PerpVault.constructor(address)._engine (src/perps/PerpVault.sol#198) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900Hook.sol:84: IMD6900Hook.constructor(IPoolManager,address,address).pool_ (src/IMD6900Hook.sol#84) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900Hook.sol:153: IMD6900Hook.initPerpEngine(address).engine (src/IMD6900Hook.sol#153) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900LockedPool.sol:133: IMD6900LockedPool.constructor(IPoolManager,uint24,address,address).launcher_ (src/IMD6900LockedPool.sol#133) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900LockedPool.sol:233: IMD6900LockedPool.setCoinFeeTo(address).to (src/IMD6900LockedPool.sol#233) lacks a zero-check on :\n[low/medium] missing-zero-check at src/perps/PerpEngine.sol:1083: PerpEngine.setFeeSink(address)._feeSink (src/perps/PerpEngine.sol#1083) lacks a zero-check on :\n[low/medium] missing-zero-check at src/perps/PerpEngine.sol:1100: PerpEngine.setVault(address)._vault (src/perps/PerpEngine.sol#1100) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900RobinhoodLaunch.sol:483: IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address).hooks (src/IMD6900RobinhoodLaunch.sol#483) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900RobinhoodLaunch.sol:468: IMD6900RobinhoodLaunch.setImdKeeper(address).keeper (src/IMD6900RobinhoodLaunch.sol#468) lacks a zero-check","passed":true},{"durationMs":991,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/IMD6900RobinhoodLaunch.sol:235: `abi.encodePacked()` Hash Collision (2 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:955: ETH transferred without address checks\n[high] reentrancy-state-change at src/IMD6900LockedPool.sol:193: 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:864: 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:367: Costly operations inside loop (6 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:112: 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:288: `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:367: 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:161: Address State Variable Set Without Checks (8 places)\n[low] unchecked-return at src/IMD6900LockedPool.sol:193: Unchecked Return (11 places)\n[low] uninitialized-local-variable at src/IMD6900RobinhoodLaunch.sol:379: Uninitialized Local Variable (7 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/IMD6900RobinhoodLaunch.sol:159: Unused Error (3 places)\n[low] unused-import at src/perps/PerpVault.sol:5: Unused Import\n[low] unused-public-function at src/IMD6900Hook.sol:111: Public Function Not Used Internally (3 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"01aa1e6c1adb781be8b60203eaded70a2dc64660f85735bd43d922fcd55bff47","verifiedTreeHash":"e3f8f8428155218b2162a91d149b215becb87090","verifierVersion":"0.1.0+7129bf9d"},{"checks":[{"durationMs":55213,"exitCode":0,"name":"build","output":"Compiling 93 files with Solc 0.8.26\nSolc 0.8.26 finished in 55.10s\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":759,"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 370.26µs (34.04µs CPU time)\n\nRan 18 tests for test/Edges.t.sol:EdgesTest\n[PASS] testFuzz_ASplitPaysProRataAndKeepsTheBurnShare(uint96) (runs: 100, μ: 204303, ~: 206626)\nLogs:\n  Bound result 326076000\n\n[PASS] testFuzz_ClaimsNeverExceedTheSnapshot(uint256,uint256) (runs: 200, μ: 336696, ~: 345098)\nLogs:\n  Bound result 33300\n  Bound result 300000000000000000\n\n[PASS] testFuzz_LowerFeeKeepsThePositionWholeAtAnyLowerFee(uint24) (runs: 200, μ: 443914, ~: 443705)\nLogs:\n  Bound result 32498\n\n[PASS] testFuzz_NeverAHigherFee(uint24) (runs: 200, μ: 43625, ~: 43596)\nLogs:\n  Bound result 16777215\n\n[PASS] testFuzz_TheCoinsAddressIsTheCreate2Address(bytes32) (runs: 200, μ: 17971, ~: 17971)\n[PASS] test_ASplitWithNothingToPayIsANoOp() (gas: 47273)\n[PASS] test_BurnImdWithNothingSetAside() (gas: 44366)\n[PASS] test_ClaimFailurePaths() (gas: 614090)\n[PASS] test_LandingAddressesRefuseZeroAndSelf() (gas: 154811)\n[PASS] test_PerpFeesAreNeverKeptWhenThePotRefuses() (gas: 299283)\n[PASS] test_RecoveringEthClampsTheBookings() (gas: 655165)\n[PASS] test_ReleaseExcessRefusesSelfOverHeldAndStrangers() (gas: 314154)\n[PASS] test_ReleaseImdstrRefusesOverHeld() (gas: 373664)\n[PASS] test_ReleasingTheUnclaimedTwiceOrEarlyIsRefused() (gas: 261842)\n[PASS] test_SellHeldRefusesOverHeldAndBelowTheFloor() (gas: 823549)\n[PASS] test_SetPayeesRefusesBadLists() (gas: 810575)\n[PASS] test_TheFeeGoesToZeroAndNoLower() (gas: 1206069)\n[PASS] test_ThePoolsEntryPointsAreTheLaunchersAndTheManagers() (gas: 230567)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 98.52ms (65.87ms CPU time)\n\nRan 15 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_AFeeMoveKeepsTheWholePosition() (gas: 364466)\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1048931)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AReplacementEngineIsNotReportedFeeFree() (gas: 234661)\n[PASS] test_AShortRoundTrip() (gas: 1843942)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1885625)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 651630)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120357)\n[PASS] test_PerpFeesReachThePot() (gas: 696631)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 818744)\n[PASS] test_TheHookReplacesOnlyThePoolsOwnEngine() (gas: 17572622)\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145526)\n[PASS] test_ThePerpsAreRecreatedAfterAFeeMove() (gas: 10984195)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 76070587)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14696649)\n[PASS] test_TheTimelocksSaleLiquidatesWhatItSinksFirst() (gas: 2186875)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 232.90ms (219.49ms CPU time)\n\nRan 48 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_ARefusedShareIsOwedNotReSplit() (gas: 2441280)\n[PASS] test_AReplacementEngineStartsNotRefunded() (gas: 2047347)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 3069883)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1277867)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1479856)\n[PASS] test_ClaimsOneForOne() (gas: 1602975)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208761)\n[PASS] test_DeploysOnAFreshChain() (gas: 2918585)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 12904078)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2357732)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599549457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1319181)\n[PASS] test_FitsOneTransaction() (gas: 12882707)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 7825\n  createHook gas: 1389288\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1867611)\nLogs:\n  pot bridge: 0.511887599999999100\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1449419)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1327372)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1318813)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2214950)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1988758)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288387119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2096384)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 2020511)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 179032)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1984654)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1319163)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 70409)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 96233)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 120122)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1833233)\n[PASS] test_PassesTheAdmissionScan() (gas: 16384985)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1470237)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702516)\n[PASS] test_RefusesTheWrongAddress() (gas: 710007)\n[PASS] test_RepointingTheBurnExecutorCancelsWhatItWasOwed() (gas: 1827839)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1293819)\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1215427)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1821242)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1252306)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1315710)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2687832)\n[PASS] test_TheFeeIsStatic() (gas: 1602374)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 21299123)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1364138)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21917)\n[PASS] test_TheLiquidityIsLocked() (gas: 2174622)\n[PASS] test_TheOwnerCantBeRenounced() (gas: 40233)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2337423)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2129503)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1670140)\n[PASS] test_TradesBothWays() (gas: 1574330)\nSuite result: ok. 48 passed; 0 failed; 0 skipped; finished in 232.92ms (191.04ms CPU time)\n\nRan 2 tests for test/Invariants.t.sol:PerpsInvariantsTest\n[PASS]\nPerpsInvariantsTest invariants:\n[PASS] invariant_NoBadDebt\n[PASS] invariant_PerpFeesNeverStayInTheLaunch\n[PASS] invariant_TheEngineHoldsItsToken\n[PASS] invariant_TheEngineHoldsWhatItBooks\n[PASS] invariant_TheOpenBookIsBounded\n[PASS] invariant_ThePoolStaysWholeUnderThePerps\n[PASS] invariant_TheVaultIsBackedByTheEngine\n PerpsInvariantsTest invariants (runs: 32, calls: 1280, reverts: 0)\n\n╭--------------+-----------------+-------+---------+----------╮\n| Contract     | Selector        | Calls | Reverts | Discards |\n+=============================================================+\n| PerpsHandler | claimPendingEth | 90    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | close           | 82    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | closeAsStranger | 82    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | depositEth      | 93    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | depositToken    | 93    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | flushFees       | 92    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | liquidate       | 93    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | lowerFee        | 71    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | openLong        | 94    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | openShort       | 88    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | poke            | 88    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | spotBuy         | 76    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | spotSell        | 78    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | withdrawEth     | 74    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | withdrawToken   | 86    | 0       | 0        |\n╰--------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 1\n  Bound result 1\n  Bound result 0\n  Bound result 1\n  Bound result 1\n  Bound result 295000000000000460\n  Bound result 1\n  Bound result 16298\n  Bound result 0\n  Bound result 4999000000007463181\n  Bound result 0\n  Bound result 3990000003627087449\n  Bound result 1\n  Bound result 0\n  Bound result 295000000185441290\n  Bound result 5\n  Bound result 1\n  Bound result 0\n  Bound result 278399387930545734618\n  Bound result 17568\n  Bound result 0\n  Bound result 814\n  Bound result 0\n  Bound result 17511\n  Bound result 1\n  Bound result 0\n  Bound result 4943\n  Bound result 1\n  Bound result 0\n  Bound result 0\n  Bound result 1\n  Bound result 1\n  Bound result 24999999999999995\n  Bound result 6\n  Bound result 0\n  Bound result 157198258\n  Bound result 1\n  Bound result 20950\n  Bound result 1\n  Bound result 1\n  Bound result 0\n  Bound result 0\n  Bound result 3990000000000007201\n  Bound result 0\n  Bound result 295000000000004362\n  Bound result 2\n  Bound result 0\n  Bound result 3995000000000000001\n\n[PASS] test_TheHandlerDrivesThePerps() (gas: 3843323)\nLogs:\n  Bound result 0\n  Bound result 5000000000000000000\n  Bound result 0\n  Bound result 100000000000000000\n  Bound result 5\n  Bound result 1\n  Bound result 100000000000000000\n  Bound result 6\n  Bound result 0\n  Bound result 1\n  Bound result 0\n  Bound result 0\n  Bound result 2000000000000000000\n  Bound result 0\n  Bound result 70303963565447621158283250\n  Bound result 2\n  Bound result 100000000000000000\n  Bound result 2\n  Bound result 0\n  Bound result 100000000000000000\n  Bound result 5\n  Bound result 1\n  Bound result 3000000000000000000\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 328.34ms (277.29ms CPU time)\n\nRan 2 tests for test/Invariants.t.sol:LaunchInvariantsTest\n[PASS]\nLaunchInvariantsTest invariants:\n[PASS] invariant_ClaimsAreOneForOneAndBounded\n[PASS] invariant_EndedClaimsStayEnded\n[PASS] invariant_EthIsAccounted\n[PASS] invariant_TheCoinIsConserved\n[PASS] invariant_TheFeeOnlyFallsAndTheLiquidityStays\n[PASS] invariant_TheLaunchCoversItsBookings\n[PASS] invariant_TheLaunchHoldsTheReserveAndTheExcess\n LaunchInvariantsTest invariants (runs: 48, calls: 1920, reverts: 0)\n\n╭---------------+---------------+-------+---------+----------╮\n| Contract      | Selector      | Calls | Reverts | Discards |\n+============================================================+\n| LaunchHandler | buy           | 122   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LaunchHandler | claim         | 111   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LaunchHandler | collect       | 117   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LaunchHandler | endClaims     | 136   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LaunchHandler | gift          | 136   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LaunchHandler | harvest       | 140   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LaunchHandler | lowerFee      | 124   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LaunchHandler | recoverEth    | 133   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LaunchHandler | releaseExcess | 126   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LaunchHandler | releaseImdstr | 129   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LaunchHandler | sell          | 141   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LaunchHandler | sellHeld      | 128   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LaunchHandler | setPayees     | 117   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LaunchHandler | split         | 133   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| LaunchHandler | warp          | 127   | 0       | 0        |\n╰---------------+---------------+-------+---------+----------╯\n\nLogs:\n  Bound result 2\n  Bound result 2999000000000014499\n  Bound result 1702492\n  Bound result 0\n  Bound result 2\n  Bound result 15820954073522735\n  Bound result 1\n  Bound result 11827\n  Bound result 2\n  Bound result 2999001075551553323\n  Bound result 1\n  Bound result 2\n  Bound result 99000000000000000\n  Bound result 1000000000000000000000000\n  Bound result 1\n  Bound result 6466828649612046066590533\n  Bound result 0\n  Bound result 2999000000007463181\n  Bound result 2\n  Bound result 46000000000000000\n  Bound result 2\n  Bound result 2999000000000250001\n  Bound result 6738\n  Bound result 2\n  Bound result 303765475116089709310415\n  Bound result 17311\n  Bound result 0\n  Bound result 4524\n  Bound result 4446\n  Bound result 1500000000000000000\n  Bound result 113592943857777681910326532\n  Bound result 60\n  Bound result 163164619598071921\n  Bound result 4202047187\n  Bound result 5720\n  Bound result 0\n  Bound result 1000000000000001\n  Bound result 10623\n  Bound result 500000000000000000\n  Bound result 22433\n  Bound result 2\n  Bound result 2999000000000002354\n\n[PASS] test_TheHandlerDrivesTheLaunch() (gas: 2248351)\nLogs:\n  Bound result 0\n  Bound result 40000000000000000000000000\n  Bound result 0\n  Bound result 1\n  Bound result 0\n  Bound result 1000000000000000000\n  Bound result 0\n  Bound result 36618641782508094585807338\n  Bound result 0\n  Bound result 58411743873046793208752165\n  Bound result 33300\n  Bound result 84581999999999500\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 646.03ms (586.85ms CPU time)\n\nRan 6 test suites in 654.74ms (1.54s CPU time): 86 tests passed, 0 failed, 0 skipped (86 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.createHook(bytes32)\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(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.setImdKeeper(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\":48,\"README.md\":149,\"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,\"script/verify_delivery.py\":68,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":294,\"src/IMD6900LockedPool.sol\":461,\"src/IMD6900RobinhoodLaunch.sol\":624,\"src/perps/PerpEngine.sol\":1160,\"src/perps/PerpSwapLib.sol\":728,\"src/perps/PerpVault.sol\":823,\"test/Edges.t.sol\":388,\"test/HookSalt.sol\":51,\"test/IMD6900RobinhoodLaunch.t.sol\":1147,\"test/Invariants.t.sol\":847,\"test/Perps.t.sol\":371,\"test/RobinhoodFork.t.sol\":297},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"01e5d6b52f2f9f6004c25c2270e9e699a9114403f79bea0241905cd8a1894a37","verifiedTreeHash":"d6d51c319607639423eafab4c8b90bd48465365b","verifierVersion":"0.1.0+7129bf9d"},{"checks":[{"durationMs":39843,"exitCode":0,"name":"build","output":"Compiling 92 files with Solc 0.8.26\nSolc 0.8.26 finished in 39.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:368:5:\n    |\n368 |     function _toYieldUnit(uint256 v) private view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n","passed":true},{"durationMs":390,"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 378.26µs (30.03µs CPU time)\n\nRan 3 tests for test/PerpVaultYield.t.sol:PerpVaultYieldTest\n[PASS] test_AMinimumShortsYieldReachesTheOnlyStaker() (gas: 285441)\n[PASS] test_FoldingAfterEveryShortStripsNothing() (gas: 8512749)\n[PASS] test_TheIndexSurvivesASingleShareAndAWholeSupplyOfYield() (gas: 911409)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 4.17ms (3.38ms CPU time)\n\nRan 16 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_AFeeMoveKeepsTheWholePosition() (gas: 364488)\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1048931)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AMinimumShortsFeeReachesTheTokenStakers() (gas: 1848873)\nLogs:\n  a minimum short's yield (wei): 149040000000000\n  what the stakers can claim of it (wei): 149039999999999\n\n[PASS] test_AReplacementEngineIsNotReportedFeeFree() (gas: 234683)\n[PASS] test_AShortRoundTrip() (gas: 1843966)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1885625)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 651630)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120379)\n[PASS] test_PerpFeesReachThePot() (gas: 696653)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 818766)\n[PASS] test_TheHookReplacesOnlyThePoolsOwnEngine() (gas: 17590650)\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145548)\n[PASS] test_ThePerpsAreRecreatedAfterAFeeMove() (gas: 10993793)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 76072540)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14696730)\n[PASS] test_TheTimelocksSaleLiquidatesWhatItSinksFirst() (gas: 2186897)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 259.47ms (228.56ms CPU time)\n\nRan 49 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_ARefusedShareIsOwedNotReSplit() (gas: 2441302)\n[PASS] test_AReplacementEngineStartsNotRefunded() (gas: 2047347)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 3069883)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1277867)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1479878)\n[PASS] test_ClaimsOneForOne() (gas: 1602975)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208761)\n[PASS] test_DeploysOnAFreshChain() (gas: 2918585)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 12904078)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2357754)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599549457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1319181)\n[PASS] test_FitsOneTransaction() (gas: 12882729)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 7825\n  createHook gas: 1389288\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1867611)\nLogs:\n  pot bridge: 0.511887599999999100\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1449441)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1327372)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1318813)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2214950)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1988780)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288387119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2096406)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 2020511)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 179032)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1984654)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1319163)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 70409)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 96255)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 120144)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1833233)\n[PASS] test_PassesTheAdmissionScan() (gas: 16400400)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1470259)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702538)\n[PASS] test_RefusesTheWrongAddress() (gas: 710029)\n[PASS] test_RepointingTheBurnExecutorCancelsWhatItWasOwed() (gas: 1827839)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1293819)\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1215427)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1821264)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1252328)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1315732)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2687832)\n[PASS] test_TheFeeIsStatic() (gas: 1602396)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 21299145)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1364138)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21917)\n[PASS] test_TheLiquidityIsLocked() (gas: 2174622)\n[PASS] test_TheOwnerCantBeRenounced() (gas: 40255)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2337423)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2129503)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1670167)\n[PASS] test_TheUnclaimedRestKeptHereIsTheTimelocksToSell() (gas: 2040892)\n[PASS] test_TradesBothWays() (gas: 1574352)\nSuite result: ok. 49 passed; 0 failed; 0 skipped; finished in 259.54ms (287.99ms CPU time)\n\nRan 4 test suites in 260.74ms (523.55ms CPU time): 69 tests passed, 0 failed, 0 skipped (69 total tests)\n","passed":true},{"durationMs":90,"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.createHook(bytes32)\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(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.setImdKeeper(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\":88,\"README.md\":161,\"foundry.toml\":30,\"jobs/swarm-launch.json\":27,\"script/claims.py\":88,\"script/launch.sh\":271,\"script/mine.sh\":12,\"script/params.py\":64,\"script/programmable_plan.py\":205,\"script/verify_delivery.py\":68,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":294,\"src/IMD6900LockedPool.sol\":461,\"src/IMD6900RobinhoodLaunch.sol\":627,\"src/perps/PerpEngine.sol\":1160,\"src/perps/PerpSwapLib.sol\":728,\"src/perps/PerpVault.sol\":871,\"test/HookSalt.sol\":51,\"test/IMD6900RobinhoodLaunch.t.sol\":1173,\"test/PerpVaultYield.t.sol\":145,\"test/Perps.t.sol\":396,\"test/RobinhoodFork.t.sol\":297},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":19644,"exitCode":0,"name":"slither","output":"[high/medium] arbitrary-send-eth at src/perps/PerpEngine.sol:948: PerpEngine._sendEth(address,uint256) (src/perps/PerpEngine.sol#948-952) sends eth to arbitrary user\n[high/medium] arbitrary-send-eth at src/IMD6900RobinhoodLaunch.sol:514: IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#514-541) sends eth to arbitrary user\n[high/medium] arbitrary-send-eth at src/IMD6900LockedPool.sol:423: IMD6900LockedPool._payOutHeld() (src/IMD6900LockedPool.sol#423-435) sends eth to arbitrary user\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:712: Reentrancy in PerpEngine._settle(uint256,Position,uint256,uint8,address) (src/perps/PerpEngine.sol#712-787):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:520: Reentrancy in PerpEngine._doSweep(address,bool,int256,bool,uint160) (src/perps/PerpEngine.sol#520-606):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:712: Reentrancy in PerpEngine._settle(uint256,Position,uint256,uint8,address) (src/perps/PerpEngine.sol#712-787):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:520: Reentrancy in PerpEngine._doSweep(address,bool,int256,bool,uint160) (src/perps/PerpEngine.sol#520-606):\n[high/medium] reentrancy-eth at src/IMD6900RobinhoodLaunch.sol:514: Reentrancy in IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#514-541):\n[high/medium] reentrancy-eth at src/perps/PerpVault.sol:259: Reentrancy in PerpVault.depositEth() (src/perps/PerpVault.sol#259-287):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:712: Reentrancy in PerpEngine._settle(uint256,Position,uint256,uint8,address) (src/perps/PerpEngine.sol#712-787):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:406: Reentrancy in PerpEngine.openLong(uint8,uint256) (src/perps/PerpEngine.sol#406-429):\n[medium/medium] divide-before-multiply at src/perps/PerpEngine.sol:712: PerpEngine._settle(uint256,Position,uint256,uint8,address) (src/perps/PerpEngine.sol#712-787) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/perps/PerpSwapLib.sol:246: PerpSwapLib.twapTick(PerpSwapLib.Observation[32],PerpSwapLib.Ring,uint32) (src/perps/PerpSwapLib.sol#246-291) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:514: IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#514-541) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:514: IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#514-541) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpEngine.sol:362: PerpEngine._pokeFunding() (src/perps/PerpEngine.sol#362-375) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:395: IMD6900RobinhoodLaunch.recoverEth(address,uint256) (src/IMD6900RobinhoodLaunch.sol#395-409) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpEngine.sol:917: PerpEngine._routeFee(uint256,bool) (src/perps/PerpEngine.sol#917-935) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpEngine.sol:939: PerpEngine._payOut(address,uint256) (src/perps/PerpEngine.sol#939-946) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:288: IMD6900RobinhoodLaunch.withdrawEth(address,uint256) (src/IMD6900RobinhoodLaunch.sol#288-294) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:302: IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address) (src/IMD6900RobinhoodLaunch.sol#302-332) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpEngine.sol:362: PerpEngine._pokeFunding() (src/perps/PerpEngine.sol#362-375) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/IMD6900RobinhoodLaunch.sol:198: Reentrancy in IMD6900RobinhoodLaunch.createHook(bytes32) (src/IMD6900RobinhoodLaunch.sol#198-205):\n[medium/medium] reentrancy-no-eth at src/perps/PerpVault.sol:304: Reentrancy in PerpVault.withdrawEth(uint256) (src/perps/PerpVault.sol#304-324):\n[medium/medium] reentrancy-no-eth at src/IMD6900RobinhoodLaunch.sol:441: Reentrancy in IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address) (src/IMD6900RobinhoodLaunch.sol#441-451):\n[medium/medium] reentrancy-no-eth at src/perps/PerpVault.sol:496: Reentrancy in PerpVault.claimPendingEth() (src/perps/PerpVault.sol#496-523):\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:531: PerpEngine._doSweep(address,bool,int256,bool,uint160).pendingIds (src/perps/PerpEngine.sol#531) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:526: PerpSwapLib.swapLeg(IPoolManager,PoolKey,PerpSwapLib.Req,bool,bytes).refundQuote (src/perps/PerpSwapLib.sol#526) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:527: PerpSwapLib.swapLeg(IPoolManager,PoolKey,PerpSwapLib.Req,bool,bytes).refundTok (src/perps/PerpSwapLib.sol#527) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:159: PerpSwapLib.projectedSqrtPriceX96(uint160,uint256,uint256,int256,bool,uint160).amountIn (src/perps/PerpSwapLib.sol#159) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:273: PerpSwapLib.twapTick(PerpSwapLib.Observation[32],PerpSwapLib.Ring,uint32).lo (src/perps/PerpSwapLib.sol#273) is a local variable never initialized\n[medium/medium] uninitialized-local at src/IMD6900RobinhoodLaunch.sol:369: IMD6900RobinhoodLaunch.setPayees(address[],uint16[]).sum (src/IMD6900RobinhoodLaunch.sol#369) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:571: PerpEngine._doSweep(address,bool,int256,bool,uint160).condemnedRemains (src/perps/PerpEngine.sol#571) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:570: PerpEngine._doSweep(address,bool,int256,bool,uint160).killedAny (src/perps/PerpEngine.sol#570) is a local variable never initialized\n[medium/medium] uninitialized-local at src/IMD6900RobinhoodLaunch.sol:589: IMD6900RobinhoodLaunch.unlockCallback(bytes).sold (src/IMD6900RobinhoodLaunch.sol#589) is a local variable never initialized\n[medium/medium] uninitialized-local at src/IMD6900RobinhoodLaunch.sol:381: IMD6900RobinhoodLaunch.setPayees(address[],uint16[]).kept (src/IMD6900RobinhoodLaunch.sol#381) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:919: PerpEngine._routeFee(uint256,bool).toVault (src/perps/PerpEngine.sol#919) is a local variable never initialized\n[medium/medium] uninitialized-local at src/IMD6900RobinhoodLaunch.sol:588: IMD6900RobinhoodLaunch.unlockCallback(bytes).ethOut (src/IMD6900RobinhoodLaunch.sol#588) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:920: PerpEngine._routeFee(uint256,bool).toIns (src/perps/PerpEngine.sol#920) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpVault.sol:861: PerpVault.pendingTokYield(address).earned (src/perps/PerpVault.sol#861) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:536: PerpEngine._doSweep(address,bool,int256,bool,uint160).scanned (src/perps/PerpEngine.sol#536) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:568: PerpEngine._doSweep(address,bool,int256,bool,uint160).clean (src/perps/PerpEngine.sol#568) is a local variable never initialized\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:580: IMD6900RobinhoodLaunch.unlockCallback(bytes) (src/IMD6900RobinhoodLaunch.sol#580-614) ignores return value by poolManager.settle() (src/IMD6900RobinhoodLaunch.sol#598)\n[medium/medium] unused-return at src/perps/PerpEngine.sol:314: PerpEngine._slot0() (src/perps/PerpEngine.sol#314) ignores return value by (s,t,None,None) = poolManager.getSlot0(poolId) (src/perps/PerpEngine.sol#314)\n[medium/medium] unused-return at src/perps/PerpSwapLib.sol:581: PerpSwapLib._settle(IPoolManager,Currency,uint256) (src/perps/PerpSwapLib.sol#581-597) ignores return value by poolManager.settle() (src/perps/PerpSwapLib.sol#595)\n[medium/medium] unused-return at src/perps/PerpSwapLib.sol:581: PerpSwapLib._settle(IPoolManager,Currency,uint256) (src/perps/PerpSwapLib.sol#581-597) ignores return value by poolManager.settle{value: amount}() (src/perps/PerpSwapLib.sol#584)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:211: IMD6900LockedPool.lowerFee(uint24) (src/IMD6900LockedPool.sol#211-230) ignores return value by (sqrtPriceX96,None,None,None) = poolManager.getSlot0(old.toId()) (src/IMD6900LockedPool.sol#222)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:389: IMD6900LockedPool._settle(int128,int128) (src/IMD6900LockedPool.sol#389-400) ignores return value by poolManager.settle{value: uint256(uint128(- a0))}() (src/IMD6900LockedPool.sol#391)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:580: IMD6900RobinhoodLaunch.unlockCallback(bytes) (src/IMD6900RobinhoodLaunch.sol#580-614) ignores return value by (None,back) = pool.feeRefund(address(this)) (src/IMD6900RobinhoodLaunch.sol#593)\n[medium/medium] unused-return at src/perps/PerpSwapLib.sol:556: PerpSwapLib._grossUp(IPoolManager,PoolKey,bool,uint256) (src/perps/PerpSwapLib.sol#556-566) ignores return value by (None,None,protocolFee,None) = StateLibrary.getSlot0(poolManager,PoolIdLibrary.toId(key)) (src/perps/PerpSwapLib.sol#561)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:320: IMD6900LockedPool.state() (src/IMD6900LockedPool.sol#320-324) ignores return value by (sqrtPriceX96,tick,None,None) = poolManager.getSlot0(id) (src/IMD6900LockedPool.sol#322)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:571: IMD6900RobinhoodLaunch.harvest() (src/IMD6900RobinhoodLaunch.sol#571-574) ignores return value by pool.collect() (src/IMD6900RobinhoodLaunch.sol#572)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:370: IMD6900LockedPool._amountsFor(uint128) (src/IMD6900LockedPool.sol#370-382) ignores return value by (p,tick,None,None) = poolManager.getSlot0(poolKey().toId()) (src/IMD6900LockedPool.sol#371)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:389: IMD6900LockedPool._settle(int128,int128) (src/IMD6900LockedPool.sol#389-400) ignores return value by poolManager.settle() (src/IMD6900LockedPool.sol#396)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:302: IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address) (src/IMD6900RobinhoodLaunch.sol#302-332) ignores return value by pool.open(t,sqrtPriceX96,SUPPLY) (src/IMD6900RobinhoodLaunch.sol#317)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:242: IMD6900LockedPool.collect() (src/IMD6900LockedPool.sol#242-246) ignores return value by poolManager.unlock(abi.encode(COLLECT,uint128(0))) (src/IMD6900LockedPool.sol#244)\n[medium/medium] unused-return at src/IMD6900Hook.sol:111: IMD6900Hook.perpFeeBps() (src/IMD6900Hook.sol#111-117) ignores return value by (None,None,protocolFee,None) = StateLibrary.getSlot0(poolManager,poolId) (src/IMD6900Hook.sol#113)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:580: IMD6900RobinhoodLaunch.unlockCallback(bytes) (src/IMD6900RobinhoodLaunch.sol#580-614) ignores return value by poolManager.settle{value: ethIn}() (src/IMD6900RobinhoodLaunch.sol#611)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:211: IMD6900LockedPool.lowerFee(uint24) (src/IMD6900LockedPool.sol#211-230) ignores return value by poolManager.initialize(poolKey(),sqrtPriceX96) (src/IMD6900LockedPool.sol#225)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:181: IMD6900LockedPool.open(address,uint160,uint256) (src/IMD6900LockedPool.sol#181-204) ignores return value by poolManager.initialize(poolKey(),start) (src/IMD6900LockedPool.sol#193)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:403: IMD6900LockedPool._liquidityFor(uint256,uint256) (src/IMD6900LockedPool.sol#403-418) ignores return value by (p,None,None,None) = poolManager.getSlot0(poolKey().toId()) (src/IMD6900LockedPool.sol#404)\n[low/medium] events-maths at src/perps/PerpEngine.sol:1078: PerpEngine.setShortCap(uint256) (src/perps/PerpEngine.sol#1078-1081) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1105: PerpEngine.setVaultLimits(uint256,uint256) (src/perps/PerpEngine.sol#1105-1108) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1088: PerpEngine.setVaultSplit(uint256,uint256) (src/perps/PerpEngine.sol#1088-1091) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1116: PerpEngine.skimInsurance(uint256,address) (src/perps/PerpEngine.sol#1116-1121) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1061: PerpEngine.setFees(uint256,uint256,uint256) (src/perps/PerpEngine.sol#1061-1064) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1094: PerpEngine.setGuards(uint32,uint256,uint256) (src/perps/PerpEngine.sol#1094-1097) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1067: PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256) (src/perps/PerpEngine.sol#1067-1075) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:406: PerpEngine.openLong(uint8,uint256) (src/perps/PerpEngine.sol#406-429) should emit an event for: \n[low/medium] missing-zero-check at src/IMD6900LockedPool.sol:181: IMD6900LockedPool.open(address,uint160,uint256).token_ (src/IMD6900LockedPool.sol#181) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900RobinhoodLaunch.sol:185: IMD6900RobinhoodLaunch.constructor(address,IPoolManager,address,address).timelock_ (src/IMD6900RobinhoodLaunch.sol#185) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900RobinhoodLaunch.sol:486: IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address).hooks (src/IMD6900RobinhoodLaunch.sol#486) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900Hook.sol:84: IMD6900Hook.constructor(IPoolManager,address,address).pool_ (src/IMD6900Hook.sol#84) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900Hook.sol:153: IMD6900Hook.initPerpEngine(address).engine (src/IMD6900Hook.sol#153) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900LockedPool.sol:133: IMD6900LockedPool.constructor(IPoolManager,uint24,address,address).launcher_ (src/IMD6900LockedPool.sol#133) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900LockedPool.sol:233: IMD6900LockedPool.setCoinFeeTo(address).to (src/IMD6900LockedPool.sol#233) lacks a zero-check on :\n[low/medium] missing-zero-check at src/perps/PerpEngine.sol:1083: PerpEngine.setFeeSink(address)._feeSink (src/perps/PerpEngine.sol#1083) lacks a zero-check on :\n[low/medium] missing-zero-check at src/perps/PerpEngine.sol:1100: PerpEngine.setVault(address)._vault (src/perps/PerpEngine.sol#1100) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900RobinhoodLaunch.sol:471: IMD6900RobinhoodLaunch.setImdKeeper(address).keeper (src/IMD6900RobinhoodLaunch.sol#471) lacks a zero-check on :\n[low/medium] missing-zero-check at src/perps/PerpVault.sol:204: PerpVault.constructor(address)._engine (src/perps/PerpVault.sol#204) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900RobinhoodLaunch.sol:185: IMD6900RobinhoodLaunch.constructor(address,IPoolManager,address,address).imdstr_ (src/IMD6900RobinhoodLaunch.sol#185) lacks a zero-check on :\n[low/medium] missing-zero-check at src/perps/PerpEngine.sol:266: PerpEngine.constructor(IPoolManager,address,addr","passed":true},{"durationMs":1034,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/IMD6900RobinhoodLaunch.sol:235: `abi.encodePacked()` Hash Collision (2 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:955: ETH transferred without address checks\n[high] reentrancy-state-change at src/IMD6900LockedPool.sol:193: 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:864: 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:370: Costly operations inside loop (6 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:112: 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:288: `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:370: 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:161: Address State Variable Set Without Checks (8 places)\n[low] unchecked-return at src/IMD6900LockedPool.sol:193: Unchecked Return (11 places)\n[low] uninitialized-local-variable at src/IMD6900RobinhoodLaunch.sol:382: Uninitialized Local Variable (7 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/IMD6900RobinhoodLaunch.sol:159: Unused Error (3 places)\n[low] unused-import at src/perps/PerpVault.sol:5: Unused Import\n[low] unused-public-function at src/IMD6900Hook.sol:111: Public Function Not Used Internally (3 places)","passed":true},{"durationMs":14801,"exitCode":0,"name":"proof 2496ca65fc7a","output":"Compiling 71 files with Solc 0.8.26\nSolc 0.8.26 finished in 13.86s\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:368:5:\n    |\n368 |     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-1a617dad/Proof_2496ca65fc7a.t.sol:VaultYieldRoundingTest\n[PASS] test_SmallShortYieldIsLostToTheAccumulatorsRounding() (gas: 864540)\nLogs:\n  yield credited (wei): 148000000000000\n  yield the only staker can claim (wei): 147999999999999\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 566.09µs (438.62µs CPU time)\n\nRan 1 test suite in 4.00ms (566.09µs 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":"1e983eae6e1fc2f2332a4aa5a6af19c6d1ff332b21fcb9c52796cb51d659d1fd","verifiedTreeHash":"dc5b7a3b89c35425118cfd7a22a778367e001b6d","verifierVersion":"0.1.0+7129bf9d"},{"checks":[{"durationMs":54393,"exitCode":0,"name":"build","output":"Compiling 94 files with Solc 0.8.26\nSolc 0.8.26 finished in 54.29s\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:368:5:\n    |\n368 |     function _toYieldUnit(uint256 v) private view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n","passed":true},{"durationMs":771,"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 266.50µs (25.86µs CPU time)\n\nRan 3 tests for test/PerpVaultYield.t.sol:PerpVaultYieldTest\n[PASS] test_AMinimumShortsYieldReachesTheOnlyStaker() (gas: 285441)\n[PASS] test_FoldingAfterEveryShortStripsNothing() (gas: 8512749)\n[PASS] test_TheIndexSurvivesASingleShareAndAWholeSupplyOfYield() (gas: 911409)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 2.62ms (2.58ms CPU time)\n\nRan 16 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_AFeeMoveKeepsTheWholePosition() (gas: 364488)\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1048931)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AMinimumShortsFeeReachesTheTokenStakers() (gas: 1848873)\nLogs:\n  a minimum short's yield (wei): 149040000000000\n  what the stakers can claim of it (wei): 149039999999999\n\n[PASS] test_AReplacementEngineIsNotReportedFeeFree() (gas: 234683)\n[PASS] test_AShortRoundTrip() (gas: 1843966)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1885625)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 651630)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120379)\n[PASS] test_PerpFeesReachThePot() (gas: 696653)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 818766)\n[PASS] test_TheHookReplacesOnlyThePoolsOwnEngine() (gas: 17590650)\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145548)\n[PASS] test_ThePerpsAreRecreatedAfterAFeeMove() (gas: 10993793)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 76072540)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14696730)\n[PASS] test_TheTimelocksSaleLiquidatesWhatItSinksFirst() (gas: 2186897)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 199.53ms (177.20ms CPU time)\n\nRan 18 tests for test/Edges.t.sol:EdgesTest\n[PASS] testFuzz_ASplitPaysProRataAndKeepsTheBurnShare(uint96) (runs: 100, μ: 204191, ~: 206626)\nLogs:\n  Bound result 326076000\n\n[PASS] testFuzz_ClaimsNeverExceedTheSnapshot(uint256,uint256) (runs: 200, μ: 336668, ~: 345110)\nLogs:\n  Bound result 1000000000000\n  Bound result 16777215\n\n[PASS] testFuzz_LowerFeeKeepsThePositionWholeAtAnyLowerFee(uint24) (runs: 200, μ: 443914, ~: 443705)\nLogs:\n  Bound result 32498\n\n[PASS] testFuzz_NeverAHigherFee(uint24) (runs: 200, μ: 43625, ~: 43596)\nLogs:\n  Bound result 16777215\n\n[PASS] testFuzz_TheCoinsAddressIsTheCreate2Address(bytes32) (runs: 200, μ: 17971, ~: 17971)\n[PASS] test_ASplitWithNothingToPayIsANoOp() (gas: 47273)\n[PASS] test_BurnImdWithNothingSetAside() (gas: 44366)\n[PASS] test_ClaimFailurePaths() (gas: 614090)\n[PASS] test_LandingAddressesRefuseZeroAndSelf() (gas: 154811)\n[PASS] test_PerpFeesAreNeverKeptWhenThePotRefuses() (gas: 299283)\n[PASS] test_RecoveringEthClampsTheBookings() (gas: 655165)\n[PASS] test_ReleaseExcessRefusesSelfOverHeldAndStrangers() (gas: 314154)\n[PASS] test_ReleaseImdstrRefusesOverHeld() (gas: 373664)\n[PASS] test_ReleasingTheUnclaimedTwiceOrEarlyIsRefused() (gas: 261847)\n[PASS] test_SellHeldRefusesOverHeldAndBelowTheFloor() (gas: 823549)\n[PASS] test_SetPayeesRefusesBadLists() (gas: 810575)\n[PASS] test_TheFeeGoesToZeroAndNoLower() (gas: 1206069)\n[PASS] test_ThePoolsEntryPointsAreTheLaunchersAndTheManagers() (gas: 230567)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 199.60ms (207.65ms CPU time)\n\nRan 49 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_ARefusedShareIsOwedNotReSplit() (gas: 2441302)\n[PASS] test_AReplacementEngineStartsNotRefunded() (gas: 2047347)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 3069883)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1277867)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1479878)\n[PASS] test_ClaimsOneForOne() (gas: 1602975)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208761)\n[PASS] test_DeploysOnAFreshChain() (gas: 2918585)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 12904078)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2357754)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599549457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1319181)\n[PASS] test_FitsOneTransaction() (gas: 12882729)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 7825\n  createHook gas: 1389288\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1867611)\nLogs:\n  pot bridge: 0.511887599999999100\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1449441)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1327372)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1318813)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2214950)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1988780)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288387119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2096406)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 2020511)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 179032)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1984654)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1319163)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 70409)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 96255)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 120144)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1833233)\n[PASS] test_PassesTheAdmissionScan() (gas: 16400400)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1470259)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702538)\n[PASS] test_RefusesTheWrongAddress() (gas: 710029)\n[PASS] test_RepointingTheBurnExecutorCancelsWhatItWasOwed() (gas: 1827839)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1293819)\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1215427)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1821264)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1252328)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1315732)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2687832)\n[PASS] test_TheFeeIsStatic() (gas: 1602396)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 21299145)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1364138)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21917)\n[PASS] test_TheLiquidityIsLocked() (gas: 2174622)\n[PASS] test_TheOwnerCantBeRenounced() (gas: 40255)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2337423)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2129503)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1670167)\n[PASS] test_TheUnclaimedRestKeptHereIsTheTimelocksToSell() (gas: 2040892)\n[PASS] test_TradesBothWays() (gas: 1574352)\nSuite result: ok. 49 passed; 0 failed; 0 skipped; finished in 199.59ms (183.57ms CPU time)\n\nRan 3 tests for test/Invariants.t.sol:PerpsInvariantsTest\n[PASS]\nPerpsInvariantsTest invariants:\n[PASS] invariant_NoBadDebt\n[PASS] invariant_PerpFeesNeverStayInTheLaunch\n[PASS] invariant_TheEngineHoldsItsToken\n[PASS] invariant_TheEngineHoldsWhatItBooks\n[PASS] invariant_TheOpenBookIsBounded\n[PASS] invariant_ThePoolStaysWholeUnderThePerps\n[PASS] invariant_TheTokenYieldIsBacked\n[PASS] invariant_TheVaultIsBackedByTheEngine\n PerpsInvariantsTest invariants (runs: 32, calls: 1280, reverts: 0)\n\n╭--------------+-----------------+-------+---------+----------╮\n| Contract     | Selector        | Calls | Reverts | Discards |\n+=============================================================+\n| PerpsHandler | claimPendingEth | 77    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | claimTokYield   | 80    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | close           | 92    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | closeAsStranger | 78    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | depositEth      | 75    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | depositToken    | 75    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | flushFees       | 77    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | liquidate       | 90    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | lowerFee        | 69    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | openLong        | 90    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | openShort       | 85    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | poke            | 69    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | spotBuy         | 76    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | spotSell        | 96    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | withdrawEth     | 64    | 0       | 0        |\n|--------------+-----------------+-------+---------+----------|\n| PerpsHandler | withdrawToken   | 87    | 0       | 0        |\n╰--------------+-----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 2\n  Bound result 115514328924776862\n  Bound result 2\n  Bound result 38038\n  Bound result 0\n  Bound result 1\n  Bound result 2016\n  Bound result 2\n  Bound result 295000000000000245\n  Bound result 1\n  Bound result 0\n  Bound result 295149040000000001\n  Bound result 2\n  Bound result 5700\n  Bound result 1\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result 295000000000000262\n  Bound result 3\n  Bound result 1826\n  Bound result 0\n  Bound result 4999000000000013055\n  Bound result 1\n  Bound result 1\n  Bound result 4999000000000019966\n  Bound result 1\n  Bound result 1\n  Bound result 180000\n  Bound result 1\n  Bound result 21600\n  Bound result 0\n  Bound result 351822826450922414325823\n  Bound result 108\n  Bound result 0\n  Bound result 1\n  Bound result 0\n  Bound result 0\n  Bound result 4999000001264592693\n  Bound result 1\n  Bound result 280726978977310210\n  Bound result 3\n  Bound result 4709\n  Bound result 3826\n  Bound result 1\n  Bound result 224999999999999935\n  Bound result 2\n  Bound result 1\n  Bound result 4999000000000358991\n  Bound result 1\n  Bound result 0\n  Bound result 39999999999799960\n\n[PASS] test_TheHandlerClaimsTheTokenYield() (gas: 1843570)\nLogs:\n  Bound result 0\n  Bound result 5000000000000000000\n  Bound result 0\n  Bound result 2000000000000000000\n  Bound result 0\n  Bound result 70303963565447621158283254\n  Bound result 2\n  Bound result 100000000000000000\n  Bound result 2\n  Bound result 0\n  Bound result 1\n\n[PASS] test_TheHandlerDrivesThePerps() (gas: 3843826)\nLogs:\n  Bound result 0\n  Bound result 5000000000000000000\n  Bound result 0\n  Bound result 100000000000000000\n  Bound result 5\n  Bound result 1\n  Bound result 100000000000000000\n  Bound result 6\n  Bound result 0\n  Bound result 1\n  Bound result 0\n  Bound result 0\n  Bound result 2000000000000000000\n  Bound result 0\n  Bound result 70303963565447621158283250\n  Bound result 2\n  Bound result 100000000000000000\n  Bound result 2\n  Bound result 0\n  Bound result 100000000000000000\n  Bound result 5\n  Bound result 1\n  Bound result 3000000000000000000\n\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 387.80ms (340.20ms CPU time)\n\nRan 3 tests for test/Invariants.t.sol:LaunchInvariantsTest\n[PASS]\nLaunchInvariantsTest invariants:\n[PASS] invariant_ClaimsAreOneForOneAndBounded\n[PASS] invariant_EndedClaimsStayEnded\n[PASS] invariant_EthIsAccounted\n[PASS] invariant_TheCoinIsConserved\n[PASS] invariant_TheFeeOnlyFallsAndTheLiquidityStays\n[PASS] invariant_TheLaunchCoversItsBookings\n[PASS] invariant_TheLaunchHoldsTheReserveAndTheExcess\n LaunchInvariantsTest invariants (runs: 48, calls: 1920, reverts: 0)\n\n╭---------------+------------------+-------+---------+----------╮\n| Contract      | Selector         | Calls | Reverts | Discards |\n+===============================================================+\n| LaunchHandler | buy              | 115   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | claim            | 130   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | collect          | 136   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | endClaims        | 128   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | gift             | 105   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | harvest          | 139   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | lowerFee         | 124   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | recoverEth       | 134   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | releaseExcess    | 111   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | releaseImdstr    | 128   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | releaseUnclaimed | 102   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | sell             | 110   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | sellHeld         | 122   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | setPayees        | 113   | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | split            | 99    | 0       | 0        |\n|---------------+------------------+-------+---------+----------|\n| LaunchHandler | warp             | 124   | 0       | 0        |\n╰---------------+------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 73483359817819253350858389\n  Bound result 20700000000000000\n  Bound result 11516\n  Bound result 207000000000000000000\n  Bound result 3\n  Bound result 7373\n  Bound result 1\n  Bound result 14503279674562888118190433\n  Bound result 29085217811922633025500937\n  Bound result 0\n  Bound result 2999000003037033922\n  Bound result 0\n  Bound result 1\n  Bound result 1\n  Bound result 8463\n  Bound result 1000000000000000000\n  Bound result 2\n  Bound result 487846201174499662\n  Bound result 50000000000000000\n  Bound result 1567\n  Bound result 0\n  Bound result 328431792560068610\n  Bound result 2254\n  Bound result 1\n  Bound result 11438470843205\n  Bound result 13041\n  Bound result 1183\n  Bound result 2\n  Bound result 1119\n  Bound result 17748247369374419787185295\n  Bound result 404\n  Bound result 1\n  Bound result 2999000000000000024\n\n[PASS] test_TheHandlerDrivesTheLaunch() (gas: 2248523)\nLogs:\n  Bound result 0\n  Bound result 40000000000000000000000000\n  Bound result 0\n  Bound result 1\n  Bound result 0\n  Bound result 1000000000000000000\n  Bound result 0\n  Bound result 36618641782508094585807338\n  Bound result 0\n  Bound result 58411743873046793208752165\n  Bound result 33300\n  Bound result 84581999999999500\n\n[PASS] test_TheHandlerReleasesTheUnclaimedReserve() (gas: 641945)\nLogs:\n  Bound result 0\n  Bound result 40000000000000000000000000\n  Bound result 2\n  Bound result 2\n  Bound result 82347490873046793208752165\n  Bound result 0\n\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 661.27ms (607.16ms CPU time)\n\nRan 7 test suites in 667.68ms (1.65s CPU time): 93 tests passed, 0 failed, 0 skipped (93 total tests)\n","passed":true},{"durationMs":61,"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.createHook(bytes32)\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(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.setImdKeeper(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\":88,\"README.md\":161,\"foundry.toml\":30,\"jobs/swarm-launch.json\":27,\"script/claims.py\":88,\"script/launch.sh\":271,\"script/mine.sh\":12,\"script/params.py\":64,\"script/programmable_plan.py\":205,\"script/verify_delivery.py\":68,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":294,\"src/IMD6900LockedPool.sol\":461,\"src/IMD6900RobinhoodLaunch.sol\":627,\"src/perps/PerpEngine.sol\":1160,\"src/perps/PerpSwapLib.sol\":728,\"src/perps/PerpVault.sol\":871,\"test/Edges.t.sol\":388,\"test/HookSalt.sol\":51,\"test/IMD6900RobinhoodLaunch.t.sol\":1173,\"test/Invariants.t.sol\":941,\"test/PerpVaultYield.t.sol\":145,\"test/Perps.t.sol\":396,\"test/RobinhoodFork.t.sol\":297},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"3a9b60d7857471032d269522e2f521eb33bfc16e5fcbfe2744af07e60d15dc83","verifiedTreeHash":"dc1e6f491a91efff411ffcc57b0c72e3ceb32575","verifierVersion":"0.1.0+7129bf9d"},{"checks":[{"durationMs":33851,"exitCode":0,"name":"build","output":"Compiling 91 files with Solc 0.8.26\nSolc 0.8.26 finished in 33.75s\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":318,"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 384.58µs (26.18µs CPU time)\n\nRan 15 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_AFeeMoveKeepsTheWholePosition() (gas: 364466)\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1048931)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AReplacementEngineIsNotReportedFeeFree() (gas: 234661)\n[PASS] test_AShortRoundTrip() (gas: 1843942)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1885625)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 651630)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120357)\n[PASS] test_PerpFeesReachThePot() (gas: 696631)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 818744)\n[PASS] test_TheHookReplacesOnlyThePoolsOwnEngine() (gas: 17572622)\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145526)\n[PASS] test_ThePerpsAreRecreatedAfterAFeeMove() (gas: 10984195)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 76070587)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14696649)\n[PASS] test_TheTimelocksSaleLiquidatesWhatItSinksFirst() (gas: 2186875)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 209.15ms (185.10ms CPU time)\n\nRan 48 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_ARefusedShareIsOwedNotReSplit() (gas: 2441280)\n[PASS] test_AReplacementEngineStartsNotRefunded() (gas: 2047347)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 3069883)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1277867)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1479856)\n[PASS] test_ClaimsOneForOne() (gas: 1602975)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208761)\n[PASS] test_DeploysOnAFreshChain() (gas: 2918585)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 12904078)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2357732)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599549457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1319181)\n[PASS] test_FitsOneTransaction() (gas: 12882707)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 7825\n  createHook gas: 1389288\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1867611)\nLogs:\n  pot bridge: 0.511887599999999100\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1449419)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1327372)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1318813)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2214950)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1988758)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288387119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2096384)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 2020511)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 179032)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1984654)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1319163)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 70409)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 96233)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 120122)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1833233)\n[PASS] test_PassesTheAdmissionScan() (gas: 16384985)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1470237)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702516)\n[PASS] test_RefusesTheWrongAddress() (gas: 710007)\n[PASS] test_RepointingTheBurnExecutorCancelsWhatItWasOwed() (gas: 1827839)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1293819)\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1215427)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1821242)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1252306)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1315710)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2687832)\n[PASS] test_TheFeeIsStatic() (gas: 1602374)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 21299123)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1364138)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21917)\n[PASS] test_TheLiquidityIsLocked() (gas: 2174622)\n[PASS] test_TheOwnerCantBeRenounced() (gas: 40233)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2337423)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2129503)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1670140)\n[PASS] test_TradesBothWays() (gas: 1574330)\nSuite result: ok. 48 passed; 0 failed; 0 skipped; finished in 209.20ms (187.62ms CPU time)\n\nRan 3 test suites in 210.24ms (418.73ms CPU time): 64 tests passed, 0 failed, 0 skipped (64 total tests)\n","passed":true},{"durationMs":57,"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.createHook(bytes32)\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(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.setImdKeeper(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\":48,\"README.md\":149,\"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,\"script/verify_delivery.py\":68,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":294,\"src/IMD6900LockedPool.sol\":461,\"src/IMD6900RobinhoodLaunch.sol\":624,\"src/perps/PerpEngine.sol\":1160,\"src/perps/PerpSwapLib.sol\":728,\"src/perps/PerpVault.sol\":823,\"test/HookSalt.sol\":51,\"test/IMD6900RobinhoodLaunch.t.sol\":1147,\"test/Perps.t.sol\":371,\"test/RobinhoodFork.t.sol\":297},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"48006eca754947846db29a1c869786ded64f733b11ed9bc4498009b2e1b52e96","verifiedTreeHash":"442ae38b58155ecbbbb90f2fb810c20c55155a4c","verifierVersion":"0.1.0+7129bf9d"},{"checks":[{"durationMs":33056,"exitCode":0,"name":"build","output":"Compiling 92 files with Solc 0.8.26\nSolc 0.8.26 finished in 32.95s\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:368:5:\n    |\n368 |     function _toYieldUnit(uint256 v) private view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n","passed":true},{"durationMs":318,"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 282.24µs (23.39µs CPU time)\n\nRan 3 tests for test/PerpVaultYield.t.sol:PerpVaultYieldTest\n[PASS] test_AMinimumShortsYieldReachesTheOnlyStaker() (gas: 285441)\n[PASS] test_FoldingAfterEveryShortStripsNothing() (gas: 8512749)\n[PASS] test_TheIndexSurvivesASingleShareAndAWholeSupplyOfYield() (gas: 911409)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 3.47ms (3.30ms CPU time)\n\nRan 49 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_ARefusedShareIsOwedNotReSplit() (gas: 2441302)\n[PASS] test_AReplacementEngineStartsNotRefunded() (gas: 2047347)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 3069883)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1277867)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1479878)\n[PASS] test_ClaimsOneForOne() (gas: 1602975)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208761)\n[PASS] test_DeploysOnAFreshChain() (gas: 2918585)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 12904078)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2357754)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599549457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1319181)\n[PASS] test_FitsOneTransaction() (gas: 12882729)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 7825\n  createHook gas: 1389288\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1867611)\nLogs:\n  pot bridge: 0.511887599999999100\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1449441)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1327372)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1318813)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2214950)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1988780)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288387119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2096406)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 2020511)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 179032)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1984654)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1319163)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 70409)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 96255)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 120144)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1833233)\n[PASS] test_PassesTheAdmissionScan() (gas: 16400400)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1470259)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702538)\n[PASS] test_RefusesTheWrongAddress() (gas: 710029)\n[PASS] test_RepointingTheBurnExecutorCancelsWhatItWasOwed() (gas: 1827839)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1293819)\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1215427)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1821264)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1252328)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1315732)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2687832)\n[PASS] test_TheFeeIsStatic() (gas: 1602396)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 21299145)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1364138)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21917)\n[PASS] test_TheLiquidityIsLocked() (gas: 2174622)\n[PASS] test_TheOwnerCantBeRenounced() (gas: 40255)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2337423)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2129503)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1670167)\n[PASS] test_TheUnclaimedRestKeptHereIsTheTimelocksToSell() (gas: 2040892)\n[PASS] test_TradesBothWays() (gas: 1574352)\nSuite result: ok. 49 passed; 0 failed; 0 skipped; finished in 89.62ms (167.97ms CPU time)\n\nRan 16 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_AFeeMoveKeepsTheWholePosition() (gas: 364488)\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1048931)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AMinimumShortsFeeReachesTheTokenStakers() (gas: 1848873)\nLogs:\n  a minimum short's yield (wei): 149040000000000\n  what the stakers can claim of it (wei): 149039999999999\n\n[PASS] test_AReplacementEngineIsNotReportedFeeFree() (gas: 234683)\n[PASS] test_AShortRoundTrip() (gas: 1843966)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1885625)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 651630)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120379)\n[PASS] test_PerpFeesReachThePot() (gas: 696653)\n[PASS] test_TheEngineCantBeUnwiredWithPositionsOpen() (gas: 818766)\n[PASS] test_TheHookReplacesOnlyThePoolsOwnEngine() (gas: 17590650)\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145548)\n[PASS] test_ThePerpsAreRecreatedAfterAFeeMove() (gas: 10993793)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 76072540)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14696730)\n[PASS] test_TheTimelocksSaleLiquidatesWhatItSinksFirst() (gas: 2186897)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 216.52ms (208.41ms CPU time)\n\nRan 4 test suites in 217.33ms (309.88ms CPU time): 69 tests passed, 0 failed, 0 skipped (69 total tests)\n","passed":true},{"durationMs":64,"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.createHook(bytes32)\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(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.setImdKeeper(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\":88,\"README.md\":161,\"foundry.toml\":30,\"jobs/swarm-launch.json\":27,\"launch.json\":15,\"script/claims.py\":88,\"script/launch.sh\":271,\"script/mine.sh\":12,\"script/params.py\":64,\"script/programmable_plan.py\":205,\"script/verify_delivery.py\":68,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":294,\"src/IMD6900LockedPool.sol\":461,\"src/IMD6900RobinhoodLaunch.sol\":627,\"src/perps/PerpEngine.sol\":1160,\"src/perps/PerpSwapLib.sol\":728,\"src/perps/PerpVault.sol\":871,\"test/HookSalt.sol\":51,\"test/IMD6900RobinhoodLaunch.t.sol\":1173,\"test/PerpVaultYield.t.sol\":145,\"test/Perps.t.sol\":396,\"test/RobinhoodFork.t.sol\":297},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"e502beb23af302c7dd849bc06874ebc586676def73db2701a40c1f49eb047955","verifiedTreeHash":"b3cd033f9de2f604603a213db75c4644d0ecd2b0","verifierVersion":"0.1.0+7129bf9d"}]}