{"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":"e63e94be-2e3d-4e5a-bf03-30d4c4c3cf03","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"acfb568db736f564418c573648792a11f30d493ceaa99fbf7a17227794deb9eb","dependsOn":["audit_imported_code"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"2d7d84a1a9972f1497e5bc68a704cbf42294d08c62d2868f102ffe3b909b9b51","skillId":"adapt-contract-project","tools":[]},"key":"adapt_contract_project","kind":"code","role":"implement","skillHash":"2d7d84a1a9972f1497e5bc68a704cbf42294d08c62d2868f102ffe3b909b9b51","skillId":"adapt-contract-project","state":"accepted"},{"acceptedSubmissionHash":"1d005e7730856f45a68ef3dc8c8333ed6d86613daefa92949b5d266270dad696","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":"518ef7f9b9cfc23cbe8c24b20f761155975cd0f08bcaa4ef001361d609aebeb1","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":"3afd7b37f7da46fb46306a2756843fc222c00475118c8e2053f96c0dcf5db106","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":"3b46e199d89431f287cabee29ddb57f9112b603bf2828bdeb22576eb61535594","dependsOn":["adapt_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"25a6caa389c6fac30e732d610d442a2d339c8abaea7da293fedbaf733ea71652","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":"a29a531f04d85eff52401d4ac433645845d9d005c7babc8a89bf116cbe5f3078","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":"f984620e9a02a6d3f2c2081e24e692bb2388ef94eb7d38ea56b1cff8e1dfcf2b","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"c4bdf2375ac44b1bde35a733757e940e80600349630ad4a08d320e8f93f90aa5","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 swarm's earlier runs of this launch (jobs ea1c84c3, d07ab5c6) audited this code; every finding 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. Audit coverage: each audit must answer all 41 entry points, including the ten ownership functions IMD6900PerpHook and IMD6900PerpPool inherit from solady's Ownable (transferOwnership, renounceOwnership, requestOwnershipHandover, cancelOwnershipHandover, completeOwnershipHandover on each; renounceOwnership reverts NoRenounce on both). Three runs of this launch were blocked because one audit left one of these unanswered: a coverage ratio below 1 is not a whole number, IMD's worker refuses to verify a task carrying one, and the judge's task could not be delivered.","parentJobId":null,"planHash":"7944a1d3934a8399d393413e244484487d8fbe5e1287636a5a050644e27ee2cf","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"e63e94be-2e3d-4e5a-bf03-30d4c4c3cf03","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1271-imd6900perpslaunch"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"50994","feedbackHash":"663ff8b5898010490322295325267351f1d5b797535560804cc36d4d74e9f3cd","nodeKey":"adapt_contract_project","submissionHash":"2ab7e1ac5c291aa5cb9c28b3d847c4381803df7dc98b89269fc953cd0ca1d59d","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51094","feedbackHash":"185d328b784552a248471a716cb6eb1339d883c220ef5d016ac9b59dc0a8d25c","nodeKey":"adapt_contract_project","submissionHash":"acfb568db736f564418c573648792a11f30d493ceaa99fbf7a17227794deb9eb","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51367","feedbackHash":"c061f2bcbfd6af1c59b48004db7c078da92bbd5308b3e588278fb12cd03f0f8d","nodeKey":"audit_economics","submissionHash":"1d005e7730856f45a68ef3dc8c8333ed6d86613daefa92949b5d266270dad696","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51082","feedbackHash":"511dca515e39cdb37db5a0bfed90fbc845abb707946a410ea28f8804061b07aa","nodeKey":"audit_flow","submissionHash":"518ef7f9b9cfc23cbe8c24b20f761155975cd0f08bcaa4ef001361d609aebeb1","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51083","feedbackHash":"6c1299aa42365f8aea145dcbe11ed46608697f7de7d5c236dfb5a240bb27797a","nodeKey":"audit_imported_code","submissionHash":"3afd7b37f7da46fb46306a2756843fc222c00475118c8e2053f96c0dcf5db106","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52261","feedbackHash":"f5ac3feb7d179e7a026c9c2a97cf161d7cbe297dd16a97d53c104b70b99121c9","nodeKey":"audit_judge","submissionHash":"3b46e199d89431f287cabee29ddb57f9112b603bf2828bdeb22576eb61535594","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52479","feedbackHash":"b78a78ce6c20263f49519b91537a69b796caae7339076008e9f5e96757972ea6","nodeKey":"audit_judge","submissionHash":"b9d99efa8b2cb4a086da4d165376772e43f57007abc48bac33a9bd0fa8f43f62","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51214","feedbackHash":"29b112893b80c0dbfaf8b3334c6a153790f75113de6806842f4f0bb0d6fcea70","nodeKey":"audit_math","submissionHash":"25a6caa389c6fac30e732d610d442a2d339c8abaea7da293fedbaf733ea71652","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51140","feedbackHash":"355ee30b69fde6714b3abad40a6253160c50fd94a846d9e8d8ef681f8f0ab526","nodeKey":"audit_permissions","submissionHash":"a29a531f04d85eff52401d4ac433645845d9d005c7babc8a89bf116cbe5f3078","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52421","feedbackHash":"51a908ef4f2c3e049f5e073da6385f8ec44523a5e9e3e9e8e2431fde0c90e89a","nodeKey":"manifest","submissionHash":"f984620e9a02a6d3f2c2081e24e692bb2388ef94eb7d38ea56b1cff8e1dfcf2b","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51087","feedbackHash":"a67cb52a5b1c24de53a42f17db25c7264475c5c29315c59c1ca45717e59c1f3a","nodeKey":"manifest","submissionHash":"90ce743fecde13594d2e736e05c8e026cf054efa053377947de5417d2a65ad69","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51353","feedbackHash":"005fd18441e6a21640182b2d1859415be4ae6d8ee642c688d508e74513419138","nodeKey":"write_foundry_tests","submissionHash":"330ffe3d5d1330a1874f1e600ede83713d4344962446758f12331c6907c68506","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51085","feedbackHash":"68e1b15c5bfd15e71abd78ca3a4bf0e87188a66cd372a9d2ca28046859e4367a","nodeKey":"write_foundry_tests","submissionHash":"c4bdf2375ac44b1bde35a733757e940e80600349630ad4a08d320e8f93f90aa5","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"9ba9cc9877154e11b6b8a1c476da23fba185a91169abbb071e2bcd316e74d650","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ddfb1efa72fe9a94","findings":[{"citation":"resolved","description":"Economic Security / Invariant (interface guarantee). The band measures deviation as |pool - ref| / ref where both prices are COIN PER ETH (poolPriceX96, ponsPriceX96). A 30% move in coin-per-ETH is not a 30% move in the coin's own price (ETH per coin, which is what Pons quotes, what the README states as 'further than 30% from the coin's Pons price', and what a perps mark is). Downward in coin-per-ETH (a buy of coin, zeroForOne) the band stops at pool = 0.7*ref, i.e. the coin costs 1/0.7 = 1.4286x its Pons price (+42.9%). Upward it stops at 1.3*ref, i.e. the coin costs 1/1.3 = 0.769x (-23.1%). The one economic guard this launch ships therefore allows the mark to be pumped almost twice as far as it can be dumped. For the future perps engine that reads this pool as its mark (the hook's stated purpose, 'turns the band into real protection for the perps mark'), longs can be marked up 43% while shorts only 23%; the pool's own position also sits with asymmetric impermanent-loss exposure. Within this launch there is no fund loss (no engine), so severity is low; the fix is to measure deviation symmetrically (e.g. compare max(pool,ref)/min(pool,ref) - 1, or compare in ticks / sqrt-price ratio), which keeps FLAGS, fees and arguments unchanged.","line":347,"path":"src/IMD6900PerpHook.sol","reproduction":"State: launch (PM, 10000, OWNER, OWNER); owner opens the pool with 2 ETH + 2e27 coin at sqrtPrice = sqrt(1e9)<<96 with a curve at (q=1 ether, t=1e27) so pool == Pons == 1e9 coin/ETH, maxDeviationBps = 3000. Input A (buy coin): a trader swaps zeroForOne, amountSpecified = -394514418328981261 wei (0.3945 ETH, found by bisection as the largest accepted buy). Result: swap accepted, hook.deviationBps() == 3000, ponsPriceX96/poolPriceX96 = 14287 bps, i.e. the coin is now priced 42.87% ABOVE Pons. Input B (sell coin) from the same fresh state: largest accepted oneForZero sell gives deviationBps == 3000 with ponsPriceX96/poolPriceX96 = 7691 bps, i.e. the coin is 23.09% BELOW Pons. Expected per README/comments: the same 30% distance in the coin's price on both sides. Actual: +42.9% / -23.1%. (test/scratch/Econ.t.sol test_BandAsymmetry logs these numbers.)","severity":"low","snippet":"        uint256 d = pool_ > ref ? pool_ - ref : ref - pool_;\n        return (d * BPS) / ref;","title":"The 30% band is asymmetric in the coin's price: it lets the coin trade 42.9% above Pons but only 23.1% below"},{"citation":"resolved","description":"Economic Security ('sandwich every price-dependent operation missing deadline/bound protection') and Flow Gap seam execution x first-principles. compound() and addLiquidity() refuse to pair funds unless the pool is within COMPOUND_MAX_DEV_BPS (1%) of a readable Pons price, because pairing at an off-Pons price gives the gap away to whoever trades the pool back. open() pairs the largest amounts of the whole lifetime (the launch refund, ~0.83 ETH, plus the matching coin) at a price the owner computed off-chain (script/Owner.s.sol reads getReserves() at simulation time) and checks nothing on-chain: after initialize the hook's poolId and the pool's curve are both set, so _requireOnPons() (or a caller-supplied max deviation) could be applied, but is not. Any Pons trade between the script's read and inclusion, or a front-runner's Pons buy, leaves the pool opened off-Pons; the band then explicitly allows any trade that moves the pool toward Pons, so an arbitrageur sells into (or buys from) the pool up to the Pons price and keeps the difference, paid out of the owner's position. Bounded (the IL of the opening amounts for the gap) and only the owner's funds, so low; the fix is a deviation bound in open() after initialize (e.g. revert OffPons if deviationBps() > a parameter or > COMPOUND_MAX_DEV_BPS when a reference is readable), which does not change the launch's constructor, flags or fees.","line":167,"path":"src/IMD6900PerpPool.sol","reproduction":"State: launch (PM, 10000, OWNER, OWNER); Pons curve at (q=1 ether, t=1.25e27) = 1.25e9 coin/ETH, but the owner's transaction (signed when Pons read 1e9) calls pool.open{value: 2 ether}(coin, curve, sqrt(1e9)<<96, 2e27). Actual: open succeeds; hook.deviationBps() == 2000 right after opening. An arbitrageur then sells 238496258048144227359443901 coin (2.385e26) into the pool in one swap (oneForZero; allowed because it moves the pool toward Pons), receiving 211189549225169335 wei (0.2112 ETH); rebuying that coin at the Pons price costs 190797006438515381 wei, net profit 20392542786653954 wei (0.0204 ETH) before Pons' own fee. The owner's position valued at the Pons price falls from 3599921414400002748 to 3579528871613348795 wei: a loss of 20392542786653953 wei (0.57% of the position) in one transaction. Expected: open() refuses (OffPons) when the chosen price is further from a readable Pons price than the same 1% the other pairing functions enforce, or the owner passes an explicit tolerance. (test/scratch/Open.t.sol test_OpenAtStalePonsPrice logs these numbers.)","severity":"low","snippet":"        poolManager.initialize(poolKey(), sqrtPriceX96);\n        uint128 liq = _fullRangeLiquidity(sqrtPriceX96, msg.value, tokenAmount);","title":"open() takes any price with no on-Pons check, so a stale or front-run opening price is arbitraged out of the owner's position"},{"citation":"resolved","description":"Invariant (view/write divergence, 'interface guarantees'). The hook's own rule (lines 90-95) is that it charges nothing itself, 'one rate applies to everyone — the engine and our own arbitrage vault included — so there are no per-caller exemptions', and that feeBps and perpFeeBps 'must be kept equal to the pool's real fee' because the engine reads them to model its solvency. The code keeps three ways to break that equality: feeFor() returns 0 for any feeExempt caller, setPerpFeeBps() accepts any value in [0, 1000] for the engine's rate, and lowerFee() lowers feeBps while the pool's fee is the immutable poolFeePips (so lowerFee can never lower the trading fee at all; at this launch's 1% it is dead code since feeBps == MIN_FEE_BPS). The pool charges poolFeePips to every swap regardless. A solvency model that reads feeFor(engine) == 0 or feeFor(vault) == 0 under-counts the engine's liquidation and arbitrage costs by exactly the 1% the pool takes on each leg, which is how a liquidation that the model says is solvent ends in bad debt. The engine is not in this launch, so impact today is nil and severity is low; the fix is to derive feeFor from poolFeePips alone (drop the exemption from feeFor, bound setPerpFeeBps to poolFeePips/100 or remove it, and either remove lowerFee or make it clear it is model-only), keeping the pool's fee, flags and arguments unchanged.","line":358,"path":"src/IMD6900PerpHook.sol","reproduction":"State: launch (PM, 10000, OWNER, OWNER), pool opened at 1e9 with 2 ETH + 2e27 coin. Owner calls hook.setFeeExempt(trader, true). hook.feeFor(trader) returns 0. trader swaps zeroForOne amountSpecified = -0.1 ether. Actual: the position's feeGrowthGlobal0 rises by exactly 999999999999999 wei * 2^128 / liquidity, i.e. the pool took 0.001 ETH (1%) from the 'exempt' trade. Then owner calls hook.setPerpEngine(trader) and hook.setPerpFeeBps(0); hook.feeFor(trader) returns 0; the same 0.1 ETH swap again pays 999999999999999 wei of fee. Also owner calls hook.lowerFee(99): reverts InvalidFee, and lowerFee(100) reverts InvalidFee, so with a 1% pool nothing can be lowered. Expected: feeFor(x) == poolFeePips/100 == 100 for every x, matching what the pool charges. (test/scratch/Econ.t.sol test_FeeForDiverges.)","severity":"low","snippet":"    function feeFor(address sender) public view returns (uint256) {\n        if (feeExempt[sender]) return 0;\n        if (sender == perpEngine && sender != address(0)) return perpFeeBps;\n        return feeBps;\n    }","title":"feeFor / feeBps / perpFeeBps / feeExempt promise rates the pool does not charge: an 'exempt' caller and an engine at perpFeeBps 0 still pay the full 1%"},{"citation":"resolved","description":"Flow Gap seam execution x first-principles. The fee flow the README describes sends 10% of the coin's creator fees to this contract as ETH only ('its compound turns them into depth'). compound() pairs min(fromEth, fromTokens) and reverts ZeroLiquidity when the coin side is zero; the comment's other coin source ('the coin the hook skimmed from arbitrageurs') no longer exists in this hook, so coin only arrives when the owner sends it (release() from the launcher, finite). No function sends the contract's idle balance out: removeLiquidity() transfers only the modifyLiquidity delta, addFees() forwards only msg.value, and there is no sweep. Idle ETH therefore accumulates one-sided and can only be recovered by sending coin worth the same at spot, compounding (which also requires ponsPriceX96() != 0 and deviation <= 1%, so it is blocked whenever the reference is unreadable, e.g. Pons graduated with no pool recorded), and then removing liquidity. Recoverable by the owner with extra steps, no loss, hence info; a documented owner-only sweep of idle balances (or letting compound pair one-sidedly via a swap) would close the gap.","line":206,"path":"src/IMD6900PerpPool.sol","reproduction":"State: pool opened (2 ETH + 2e27 coin at 1e9), curve at the pool's price. 1 ETH of fee share arrives at the pool (addFees with feeSink == 0, or receive()). pool.pending() returns (1e18, 0). Anyone calls pool.compound(0, 0): reverts ZeroLiquidity (fromTokens == 0). Owner calls pool.removeLiquidity(all, OWNER, OWNER): receives the position's ETH and coin, but address(pool).balance stays 1e18. No entry point in the ABI moves that ETH. Expected: an owner path that returns idle balances, or compound able to use one-sided funds.","severity":"info","snippet":"        liq = _fullRangeLiquidity(sqrtPriceX96, ethHave, tokensHave);\n        if (liq == 0) revert ZeroLiquidity();","title":"The pool's idle ETH (its 10% creator-fee share) has no exit except compound(), which needs matching coin and a readable Pons price"}],"hash":"1d005e7730856f45a68ef3dc8c8333ed6d86613daefa92949b5d266270dad696","nodeId":"ba0f41de-8170-4c31-84cf-c6624820e854","outcome":"completed","summary":"The review is complete. The findings file is written at the repository root, no source file was changed, and the scratch tests were removed.\n\n**Outcome.** The economics of this launch hold up: the earlier fixes do what they claim, and I found no path by which an unprivileged actor takes funds from the pool's position. I report three low findings and one informational one, each with measured numbers.\n\n- **Band asymmetry** (low, `src/IMD6900PerpHook.sol:347`). Deviation is measured in coin per ETH, so the 30% band lets the coin's own price sit 42.9% above Pons but only 23.1% below. A perps mark pumped nearly twice as far as it can be dumped. No fund loss in this launch since the engine is absent.\n- **No on-Pons check in open()** (low, `src/IMD6900PerpPool.sol:167`). The two other pairing functions refuse to pair more than 1% off Pons, but open() accepts any price. A 20% stale or front-run opening cost the owner 0.0204 ETH on a 3.6 ETH position to one arbitrage swap the band explicitly allows.\n- **Fee views that lie** (low, `src/IMD6900PerpHook.sol:358`). An exempt caller and an engine at perpFeeBps 0 both still pay the pool's 1%; lowerFee is dead at this launch's fee. The hook's own rule says these rates must equal the real fee because the engine models solvency from them.\n- **One-sided idle ETH** (info, `src/IMD6900PerpPool.sol:206`). The fee share arrives as ETH only, compound needs coin, and nothing else moves idle balances out.\n\n**What held.** The compound and addLiquidity sandwich inside the 1% guard loses money for the pusher. A 256-run fuzz showed addLiquidity never draws on the pool's idle funds. The unlockCallback accounting reconciles for additions and removals. The owner's full liquidity return holds under a broken curve, dead engine and 1 bps band.\n\n**Coverage.** All 41 entry points have a row: 37 hold, 6 carry a finding, and one lead stays unreached. That lead is whether the live Pons curve's reserves give its marginal price and whether the factory struct matches, which needs the Pons source or a fork. A mismatch would only switch the band off and block compounding, not take funds.\n\nNo finding reaches high, so no proof test was attached.","treeHash":null,"usage":{"cachedInputTokens":1913886,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":56141,"runtime":"claude","turns":30,"wallClockMs":1023944}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"dd2ee4882a1be950","findings":[{"citation":"resolved","description":"Seam: boundary x invariant. compound() sizes the liquidity it adds from the contract's current balances (ethHave, tokensHave) and reverts ZeroLiquidity when the smaller side yields no liquidity. The fees the position has earned are only collected inside _modify (PoolManager.modifyLiquidity nets feesAccrued into callerDelta, src/IMD6900PerpPool.sol:232-254), which runs after this check. So in the state the launch's own flow produces (the creator-fee split from wire() sends the pool ETH only; this hook skims no coin any more, so the pool's only coin source is the position's own coin fees) the function can never run: tokensHave == 0 makes fromTokens == 0, _fullRangeLiquidity returns 0, and the coin fees that would pair the ETH stay uncollected in the position. Nothing is lost, but the documented purpose ('turns what collects here back into depth', README: the 10% fee split 'its compound turns into depth') is unreachable by compound alone: it needs someone else to run a modify first (anyone's dust addLiquidity, or the owner's removeLiquidity(1, pool, pool)). The NatSpec at line 194-195 still describes 'the coin the hook skimmed from arbitrageurs' as a coin source, which this hook no longer has. Minimal fix: when either balance is 0 and the position has liquidity, collect first (a zero-delta _modify(0) is accepted by v4 for a non-empty position and unlockCallback already handles it: both deltas positive, both taken) and size the addition from the balances after collection; or add the position's owed fees (feeGrowthInside - position.feeGrowthInsideLast) * liquidity >> 128 to ethHave / tokensHave before sizing.","line":206,"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 {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} from \"src/IMD6900PerpHook.sol\";\nimport {IMD6900PerpPool} from \"src/IMD6900PerpPool.sol\";\n\ncontract PCoin {\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n\n    function mint(address to, uint256 a) external {\n        balanceOf[to] += a;\n    }\n\n    function approve(address s, uint256 a) external returns (bool) {\n        allowance[msg.sender][s] = a;\n        return true;\n    }\n\n    function transfer(address to, uint256 a) external returns (bool) {\n        balanceOf[msg.sender] -= a;\n        balanceOf[to] += a;\n        return true;\n    }\n\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;\n        balanceOf[to] += a;\n        return true;\n    }\n}\n\n/// A Pons curve pinned wherever the test puts it\ncontract PCurve {\n    uint256 public q;\n    uint256 public t;\n    address public token;\n\n    constructor(address token_, uint256 q_, uint256 t_) {\n        token = token_;\n        q = q_;\n        t = t_;\n    }\n\n    function set(uint256 q_, uint256 t_) external {\n        q = q_;\n        t = t_;\n    }\n\n    function getReserves() external view returns (uint256, uint256) {\n        return (q, t);\n    }\n\n    function graduated() external pure returns (bool) {\n        return false;\n    }\n\n    function pairToken() external pure returns (address) {\n        return address(0);\n    }\n\n    function factory() external pure returns (address) {\n        return address(0);\n    }\n}\n\ncontract PTrader {\n    IPoolManager immutable pm;\n    PoolKey key;\n\n    constructor(IPoolManager pm_) {\n        pm = pm_;\n    }\n\n    function swap(PoolKey calldata k, bool buy, int256 amountSpecified) external {\n        key = k;\n        pm.unlock(abi.encode(buy, amountSpecified));\n    }\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(\n            key, SwapParams(buy, amt, buy ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1), \"\"\n        );\n        _settle(key.currency0, d.amount0());\n        _settle(key.currency1, d.amount1());\n        return \"\";\n    }\n\n    function _settle(Currency c, int128 v) internal {\n        if (v < 0) {\n            if (c.isAddressZero()) {\n                pm.settle{value: uint128(-v)}();\n            } else {\n                pm.sync(c);\n                PCoin(Currency.unwrap(c)).transfer(address(pm), uint128(-v));\n                pm.settle();\n            }\n        } else if (v > 0) {\n            pm.take(c, address(this), uint128(v));\n        }\n    }\n\n    receive() external payable {}\n}\n\n/// compound() with idle ETH, no idle coin, and coin fees the position has earned but not collected: it should pair\n/// the ETH with those fees (collecting them is what its own modify does); instead it reverts ZeroLiquidity because the\n/// zero-coin fast path runs before the collection.\ncontract ProofCompoundIdleEthTest is Test {\n    address constant OWNER = address(0xA11CE);\n\n    function test_CompoundPairsIdleEthWithTheFeesThePositionEarned() public {\n        IPoolManager PM = IPoolManager(address(new PoolManager(address(this))));\n        IMD6900PerpsLaunch l = new IMD6900PerpsLaunch(PM, 10_000, OWNER, OWNER);\n        IMD6900PerpHook hook = l.hook();\n        IMD6900PerpPool pool = l.pool();\n        PCoin coin = new PCoin();\n        PCurve curve = new PCurve(address(coin), 1 ether, 1e27); // 1e9 coin per ETH\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(_sqrt(1e9)) << 96, 2e27);\n        vm.stopPrank();\n        PoolKey memory key = pool.poolKey();\n        PTrader trader = new PTrader(PM);\n        vm.deal(address(trader), 100 ether);\n        coin.mint(address(trader), 1e28);\n        // trades both ways: the position earns its 1% in ETH and in coin\n        for (uint256 i; i < 6; ++i) {\n            trader.swap(key, true, -0.1 ether);\n            trader.swap(key, false, -9e25);\n        }\n        curve.set(1 ether, (hook.poolPriceX96() * 1e18) >> 96); // Pons at the pool's price\n        assertLe(hook.deviationBps(), 1);\n        vm.deal(address(pool), 1 ether); // the creator-fee split arrives as ETH only\n        assertEq(coin.balanceOf(address(pool)), 0);\n        uint128 before = pool.liquidity();\n        uint128 added = pool.compound(0, 0); // reverts ZeroLiquidity on the current code\n        assertGt(added, 0, \"the earned coin fees were paired with the idle ETH\");\n        assertGt(pool.liquidity(), before);\n    }\n\n    function _sqrt(uint256 x) internal pure returns (uint256 z) {\n        uint256 r = x;\n        uint256 y = (x + 1) / 2;\n        while (y < r) {\n            r = y;\n            y = (x / y + y) / 2;\n        }\n        z = r;\n    }\n\n    receive() external payable {}\n}","reproduction":"Against a real PoolManager (test/IMD6900Perps.t.sol fixtures): launch with (PM, 10000, OWNER, OWNER); OWNER opens the pool at 1e9 coin/ETH with 2 ETH and 2e27 coin; a trader runs 6 round trips (buy 0.1 ETH, sell 9e25 coin) so the position accrues fees of 5999999999999999 wei and 5399999999999999999999999 coin (uncollected, read from getFeeGrowthInside - position.feeGrowthInsideLast); park the Pons curve at the pool's price (deviationBps <= 1); vm.deal(pool, 1 ether) to stand in for the fee split; coin.balanceOf(pool) == 0. Call pool.compound(0, 0). Expected: the idle ETH is paired with the position's earned coin fees (5.4e24 coin at 1e9/ETH is worth 0.0054 ETH) and liquidity grows. Actual: revert ZeroLiquidity() at line 207, nothing collected. Then anyone's dust addLiquidity{value: 1}(1e9, to) collects the fees into the pool's balances (coin.balanceOf(pool) becomes 5.4e24) and the same compound(0, 0) succeeds (liquidity grows; 1000462227526103652 wei stay idle for want of coin).","severity":"low","snippet":"        liq = _fullRangeLiquidity(sqrtPriceX96, ethHave, tokensHave);\n        if (liq == 0) revert ZeroLiquidity();","title":"compound() cannot start while the pool holds idle ETH but no idle coin: its zero-coin fast path reverts before the modify that would collect the coin fees the position has earned"},{"citation":"resolved","description":"Boundary (sole-occupant comparator). With the launch's fixed poolFee of 10000 pips, the constructor sets feeBps = 10000 / 100 = 100 = MIN_FEE_BPS (line 186-187). lowerFee then needs newFeeBps < 100 and newFeeBps >= 100 at once, so the entry point is dead for this deployment (and for any pool fee <= 1%, where feeBps <= MIN_FEE_BPS from the start). No funds are at risk and the dead function matches the stated rule 'never below 100, never raised'; it is reported so the owner does not plan on it and so the ABI entry point has an answer. Related, not reachable with the fixed arguments: `uint256(poolFeePips_) / 100` truncates sub-basis-point pool fees (150 pips -> 1 bps, 50 pips -> 0 bps, which feeFor() would then report as a free pool to the engine). If lowerFee is meant to stay usable, either make MIN_FEE_BPS a floor below the derived fee or drop the function; if feeBps must track the immutable pool fee, lowerFee contradicts the rule at lines 94-95 for any pool fee above 1%.","line":216,"path":"src/IMD6900PerpHook.sol","reproduction":"Deploy IMD6900PerpsLaunch(PM, 10000, owner, owner). hook.feeBps() == 100, hook.MIN_FEE_BPS() == 100. As owner: hook.lowerFee(100) reverts InvalidFee (100 >= 100); hook.lowerFee(99) reverts InvalidFee (99 < 100); hook.lowerFee(101) reverts InvalidFee. There is no uint256 that passes. Expected: a usable only-lower path or no such function; actual: every call reverts (test/AuditFixes.t.sol:342-345 asserts the two boundary reverts).","severity":"info","snippet":"        if (newFeeBps >= feeBps || newFeeBps < MIN_FEE_BPS) revert InvalidFee();","title":"lowerFee() has an empty input domain for this launch: feeBps starts at MIN_FEE_BPS (10000 pips / 100 = 100), so `newFeeBps >= feeBps || newFeeBps < MIN_FEE_BPS` rejects every value"}],"hash":"25a6caa389c6fac30e732d610d442a2d339c8abaea7da293fedbaf733ea71652","nodeId":"a35b4425-ce9f-417f-9546-d4fc3ee391b4","outcome":"completed","summary":"The review is complete. No tracked file changed; the findings file is at the repository root and the proof test sits under test/scratch/.\n\n**Outcome.** The math in this launch holds. Two findings, neither a loss of funds, and all 41 entry points answered.\n\n**Findings written to .imd-findings.json**\n- **Low, compound() in the pool.** When the pool holds idle ETH but no idle coin, the zero-coin fast path reverts with ZeroLiquidity before the modify that would collect the coin fees the position has already earned. That is exactly the state the owner's wire() step produces, since the fee split sends ETH only and this hook no longer skims coin. Reproduced against a real PoolManager with the position holding 5.4e24 uncollected coin fees and 1 ETH idle. A dust addition by anyone unblocks it, so nothing is lost. Carries a proof test that fails on the current code with ZeroLiquidity.\n- **Info, lowerFee() on the hook.** With the launch's 1% pool fee, feeBps starts at the 100 bps floor, so the `>=` and `<` pair rejects every input. The entry point is dead for this deployment. The sub-basis-point truncation in the constructor is noted alongside it but is unreachable with the fixed arguments.\n\n**What I verified and found sound**\n- Full-range liquidity sizing never asks for more ETH or coin than given, fuzzed 3000 runs against Uniswap's round-up amount formulas. The only revert is a mulDiv overflow within a few hundred thousand units of the maximum usable sqrt price, which is unreachable.\n- Fee netting in the unlock callback: additions cost exactly the principal, earned fees land in the pool contract, idle balances never shrink, and a full removal pays principal plus fees out while leaving idle balances untouched.\n- The band's deviation arithmetic cannot overflow or collide with the side bit. A gas scan over every amount from 60k to 92k never lets a band-breaking swap through, so the try/catch around the Pons read cannot be starved into switching the band off.\n- The CREATE2 mining preimage and flag mask are correct, and the owner's script computes the opening sqrt price consistently with how the hook reads Pons.\n\n**Not reachable offline.** Whether the IMD6900 coin taxes plain ERC20 transfers. If it did, open() would simply revert, while addLiquidity would draw the shortfall from the pool's idle coin. The fork test suggests plain transfers, but I could not confirm it without an RPC.","treeHash":null,"usage":{"cachedInputTokens":3588915,"inputTokens":770,"model":"claude-fable-5-1","outputTokens":72690,"runtime":"claude","turns":44,"wallClockMs":1133587}},{"artifacts":[],"attempt":1,"bundleHash":"9036de2f3e46d6eca3ed00d45c0c9d2146ac7a6dd99cd70eb9bc37a4ccfdbdb7","device":"60ffae3cc413a0c6","findings":[],"hash":"2ab7e1ac5c291aa5cb9c28b3d847c4381803df7dc98b89269fc953cd0ca1d59d","nodeId":"a47cdc2a-7e44-4e26-95b1-03af81da24fa","outcome":"completed","summary":"All four reproduced audit findings are fixed and tested, the launch contract and its arguments are unchanged, and the suite passes offline.\n\n**What reproduced and what I changed**\n\n- **compound() with no reference** (medium): the proof failed on the arriving code with the attacker up 0.032 ETH. The pool now reads the hook's `ponsPriceX96()` first and reverts `NoPonsReference` when it is 0, then applies the existing 1% check. The proof passes.\n- **addLiquidity() sandwich** (medium): the proof failed with the attacker up 0.013 ETH. addLiquidity now runs under the same shared guard as compound, in `src/IMD6900PerpPool.sol`. The proof passes.\n- **Zero fee address at construction** (low): reproduced by reading, the constructor stored it unchecked. The hook's constructor now reverts `ZeroAddress`, so the launch reverts `BadHook` on that argument. This launch's arguments pass the team wallet, so the deployed instance was never affected.\n- **feeBps at 10% against a 1% pool** (low): reproduced. Both `feeBps` and `perpFeeBps` are derived from the pool fee at construction, 100 bps for this launch. lowerFee keeps its only-lower rule and floor.\n- **Coverage** (info): not a defect. ADAPTATION.md lists all 41 entry points by contract, and a new test exercises the ten solady Ownable functions on both contracts.\n\n**Verification**\n\n- forge build and forge test with the project's configuration: 26 tests pass, the fork suite skips without an RPC.\n- Launch init code is 19,682 bytes, under the harness limit. Runtimes stay under the EIP-170 cap and the opcode scan is clean.\n- Fresh-chain deployment, one-transaction fit and the admission scan tests pass unchanged.\n- One existing test needed an adjustment: the earned-fees test now parks the Pons reference at the pool's price before its addition, since its trades left the pool 2.5% off and additions now require 1%.\n- A new test holds the brief's property that the owner removes all liquidity with an unreadable reference, a dead engine on the hook and a 1 bps band.\n\n**Files touched**: `src/IMD6900PerpHook.sol`, `src/IMD6900PerpPool.sol`, `test/AuditFixes.t.sol`, `README.md`, and the new `ADAPTATION.md`. No change to the launch contract, the owner script, the fork test, or any protected config. No launch.json or blocked file was written, and the working tree is left uncommitted for the harness to pick up.","treeHash":"e9cddb18b78752c197ff7b31be62c23611a64a2b","usage":{"cachedInputTokens":1165752,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":31343,"runtime":"claude","turns":41,"wallClockMs":626362}},{"artifacts":[],"attempt":1,"bundleHash":"b4c23f829af86b4ac4e6651dc26ec1507022a956f5e61c51d25abe675cf024eb","device":"60f85cbc35c28a7e","findings":[{"description":"The constructor searches up to 1,048,576 hook salts without a gas bound. Some valid launch/deployer addresses require more mining work than the stated 2^24 transaction limit permits. With the exact approved constructor arguments, the concrete factory salt below requires 132,475 iterations and 21,218,454 execution gas before transaction intrinsic gas. That deployment cannot fit the required single transaction. This is a remaining deployment-liveness limitation; it does not duplicate the application fixes in AuditFixes.t.sol. The original gas test also used new, which this Foundry version dynamically linked to unmetered vm.deployCode; its measured 3,760 gas hid constructor cost. The submitted tests repair that measurement and retain a passing example, but do not assert a universal gas bound.","line":65,"path":"src/IMD6900PerpsLaunch.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\nimport {Test} from \"forge-std/Test.sol\";\nimport {IMD6900PerpsLaunch} from \"src/IMD6900PerpsLaunch.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\ncontract GasProbeFactory {\n    function deploy(bytes memory init, bytes32 salt) external returns (IMD6900PerpsLaunch) {\n        address result;\n        assembly (\"memory-safe\") { result := create2(0, add(init, 32), mload(init), salt) }\n        require(result != address(0), \"deployment failed\");\n        return IMD6900PerpsLaunch(result);\n    }\n}\ncontract MiningGasProbeTest is Test {\n    function test_LaunchFitsTransactionAtFactorySalt50149() public {\n        uint16 seed = 50149;\n        GasProbeFactory code = new GasProbeFactory();\n        vm.etch(address(0xBEEF), address(code).code);\n        address owner = 0x35dA9C0303507ddf708E87F2568EdDf12c47a059;\n        bytes memory init = abi.encodePacked(type(IMD6900PerpsLaunch).creationCode,\n            abi.encode(IPoolManager(0x8366a39CC670B4001A1121B8F6A443A643e40951), uint24(10_000), owner, owner));\n        uint256 before = gasleft();\n        IMD6900PerpsLaunch launch = GasProbeFactory(address(0xBEEF)).deploy(init, bytes32(uint256(seed)));\n        uint256 used = before - gasleft();\n        emit log_named_uint(\"salt seed\", seed);\n        emit log_named_uint(\"mining iterations\", uint256(launch.salt()) + 1);\n        emit log_named_uint(\"gas\", used);\n        assertLt(used, 1 << 24, \"launch exceeds transaction budget before intrinsic gas\");\n    }\n}","reproduction":"Place the proof at test/scratch/MiningGasProbe.t.sol and run forge test --offline --match-path test/scratch/MiningGasProbe.t.sol -vv. The test installs a plain CREATE2 factory at 0x000000000000000000000000000000000000BEEF, uses factory salt bytes32(uint256(50149)), and deploys IMD6900PerpsLaunch with PoolManager 0x8366a39CC670B4001A1121B8F6A443A643e40951, fee 10000, owner and fee recipient 0x35dA9C0303507ddf708E87F2568EdDf12c47a059. Expected: metered deployment gas below 16,777,216 (even before adding transaction intrinsic gas). Actual: hook salt 132474, 21,218,454 gas, assertion fails. Reproduced as a fixed-input test locally.","severity":"medium","title":"Constructor salt mining can exceed the launch transaction gas ceiling"}],"hash":"330ffe3d5d1330a1874f1e600ede83713d4344962446758f12331c6907c68506","nodeId":"e9199b71-c874-4529-b8d4-db6ec1bdac16","outcome":"completed","summary":"Added failure-path, fuzz, and invariant tests, with coverage mapped across all 41 entry points.\n\n- Offline build passed.\n- Tests: 60 passed, 1 fork skip.\n- Invariants: 16,384 calls, no failures.\n\nReported one medium defect in `.imd-findings.json`, with a reproduced failing proof: a valid launch requires 21,218,454 gas, exceeding the stated 16,777,216 limit. Corrected the existing gas test to meter actual deployment.","treeHash":"18924d2cc15f217b3e9009b873f0a42dc3459d1e","usage":{"cachedInputTokens":3938304,"inputTokens":150577,"model":"gpt-6-astra","outputTokens":33644,"runtime":"codex","turns":8,"wallClockMs":818938}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"dc7103e773ea194a","findings":[{"citation":"resolved","description":"The ea1c84c3 fix guards compound() with the pool's deviation from Pons (COMPOUND_MAX_DEV_BPS = 1%). But IMD6900PerpHook._deviation returns 0 when the reference price is 0 (src/IMD6900PerpHook.sol:336, `if (ref == 0) return 0;`), and ponsPriceX96() returns 0 in every state the hook cannot price: the curve reports graduated() while the factory's record is not at phase 2 (no pool recorded yet, or a key/memeHook the hook cannot find, sq == 0), a curve whose reserves read 0, a curve that reverts, or a reference the owner mispointed. In exactly those states the band is off as well (_checkBand returns when ref == 0), so an unprivileged caller can push the pool's price as far as they like, call compound() to pair the pool's idle ETH and coin (its fee share, coin the owner released to it) at that price, and sell back into the deeper pool. The profit comes out of the position the pool owns. The guard should refuse (or compound should skip) when the deviation cannot be judged, i.e. when ponsPriceX96() == 0, instead of reading 'no reference' as 'on price'.","line":196,"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} from \"src/IMD6900PerpHook.sol\";\nimport {IMD6900PerpPool} from \"src/IMD6900PerpPool.sol\";\n\ncontract PCoin {\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, with the launch graduated but its pool not yet created (phase 1)\ncontract PFactory {\n    struct L { address token; address curve; address deployer; address creatorFeeRecipient; address pairToken; uint256 graduationThreshold; uint24 poolFee; int24 tickSpacing; uint16 creatorTaxBps; bool buybackEnabled; uint8 phase; uint256 sweptQuote; uint256 sweptTokens; uint256 sweptAt; bool exists; }\n    function memeHook() external pure returns (address) { return address(0); }\n    function getLaunchedToken(address token) external pure returns (L memory l) { l.token = token; l.exists = true; l.phase = 1; }\n}\n\n/// A Pons curve at 1e9 coin per ETH that can graduate (after which the hook reads the reference as 0)\ncontract PCurve {\n    uint256 public q = 1 ether; uint256 public t = 1e27; address public token; bool public graduated; address public factory;\n    constructor(address token_, address factory_) { token = token_; factory = factory_; }\n    function graduate() external { graduated = true; }\n    function getReserves() external view returns (uint256, uint256) { return (q, t); }\n    function pairToken() external pure returns (address) { return address(0); }\n}\n\ncontract PTrader {\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); PCoin(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() is guarded by the pool's deviation from Pons, but the hook reports a deviation of 0 whenever the Pons\n/// reference reads as 0 (graduated and no pool recorded, zero reserves, an unreadable curve). In that state the band\n/// is off too, so anyone can push the pool's price, compound the pool's idle ETH and coin at that price, and trade\n/// back for a profit taken from the pool's own position.\ncontract CompoundUnguardedWhenReferenceReadsZeroTest is Test {\n    IPoolManager PM;\n    address constant OWNER = address(0xA11CE);\n    IMD6900PerpHook hook; IMD6900PerpPool pool; PCoin coin; PCurve curve; PTrader 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 PCoin();\n        curve = new PCurve(address(coin), address(new PFactory()));\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(_sqrt(1e9)) << 96, 2e27);\n        vm.stopPrank();\n        key = pool.poolKey();\n        attacker = new PTrader(PM);\n        vm.deal(address(attacker), 10 ether);\n    }\n\n    function test_SandwichingCompoundMustNotPayWhenTheReferenceReadsZero() public {\n        // the pool holds idle funds for compound: its fee share in ETH, coin the owner released to it\n        vm.deal(address(pool), 0.5 ether);\n        coin.mint(address(pool), 5e26);\n        // the coin graduates on Pons; the factory has no pool for it yet: the hook's reference reads 0\n        curve.graduate();\n        assertEq(hook.ponsPriceX96(), 0, \"no reference\");\n        assertEq(hook.deviationBps(), 0, \"the deviation guard reads 0 whatever the pool's price\");\n\n        uint256 before = address(attacker).balance;\n        attacker.swap(key, true, -1 ether); // push: buy coin with 1 ETH into 2 ETH of depth (the band is off)\n        try pool.compound(0, 0) {} catch {} // pairs the pool's idle ETH and coin at the pushed price\n        attacker.swap(key, false, -int256(coin.balanceOf(address(attacker)))); // sell it all back into the deeper pool\n        assertLe(address(attacker).balance, before, \"sandwiching compound paid the attacker from the pool's position\");\n    }\n\n    function _sqrt(uint256 x) internal pure returns (uint256 z) {\n        uint256 r = x; uint256 y = (x + 1) / 2;\n        while (y < r) { r = y; y = (x / y + y) / 2; }\n        z = r;\n    }\n\n    receive() external payable {}\n}","reproduction":"Pool opened at 1e9 coin/ETH with 2 ETH + 2e27 coin; pool holds 0.5 ETH and 5e26 coin idle; the curve reports graduated() == true and the factory returns a record with phase == 1 (hook.ponsPriceX96() == 0, hook.deviationBps() == 0). Attacker with 10 ETH: (1) swap 1 ETH exact-in for coin (allowed, band off); (2) pool.compound(0, 0) succeeds and adds 1.06e22 liquidity at the pushed price; (3) sell all coin back. Expected: compound refuses while the reference cannot be read, the round trip loses the 1% fee twice. Actual: attacker ends with 10.03198 ETH, +0.03198 ETH taken from the pool's position (0.49 ETH of the idle 0.5 ETH was paired at the pushed price). Proof: test/scratch/CompoundUnguardedWhenReferenceReadsZero.t.sol fails on this code with that number.","severity":"medium","snippet":"        uint256 dev = IBandView(hook).deviationBps();\n        if (dev > COMPOUND_MAX_DEV_BPS) revert OffPons(dev);","title":"compound()'s Pons guard is void whenever the hook cannot read the reference: the sandwich the audit fix removed returns"},{"citation":"resolved","description":"addLiquidity(uint256 tokenAmount, address refundTo) is public and pairs the caller's ETH and coin at the pool's current price with no deviation check against Pons and no minimum liquidity or price bound. The band allows a tracked swap to leave the pool up to maxDeviationBps (30%) from Pons, so an unprivileged trader can push the price inside the band, let (or wait for) a deepening transaction land, and sell back into the deeper pool; the depth added at the pushed price is what pays them. The liquidity belongs to the pool contract, so the victim is the protocol, not the depositor. compound() received a 1% deviation guard for the same mechanism in the ea1c84c3 fix; addLiquidity() did not. The owner's own re-deposits after a removal go through this function too. Fix: apply the same OffPons guard (COMPOUND_MAX_DEV_BPS or a parameter) to addLiquidity(), or take minimum amounts/price bounds from the caller.","line":179,"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} from \"src/IMD6900PerpHook.sol\";\nimport {IMD6900PerpPool} from \"src/IMD6900PerpPool.sol\";\n\ncontract ACoin {\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 coin's Pons curve, readable and steady at 1e9 coin per ETH: the band is on the whole time\ncontract ACurve {\n    address public token;\n    constructor(address token_) { token = token_; }\n    function getReserves() external pure returns (uint256, uint256) { return (1 ether, 1e27); }\n    function graduated() external pure returns (bool) { return false; }\n    function pairToken() external pure returns (address) { return address(0); }\n    function factory() external pure returns (address) { return address(0); }\n}\n\ncontract ATrader {\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); ACoin(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/// addLiquidity() pairs the caller's ETH and coin at whatever price the pool shows, with no bound against Pons and\n/// no minimum: a 30% band leaves room for a push, the addition, and a sell-back that takes its profit from the\n/// position the pool owns. compound() got exactly this guard (COMPOUND_MAX_DEV_BPS); addLiquidity() did not.\ncontract AddLiquiditySandwichTest is Test {\n    IPoolManager PM;\n    address constant OWNER = address(0xA11CE);\n    address constant DONOR = address(0xD0E);\n    IMD6900PerpHook hook; IMD6900PerpPool pool; ACoin coin; ATrader 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 ACoin();\n        ACurve curve = new ACurve(address(coin));\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(_sqrt(1e9)) << 96, 2e27);\n        vm.stopPrank();\n        key = pool.poolKey();\n        attacker = new ATrader(PM);\n        vm.deal(address(attacker), 10 ether);\n        vm.deal(DONOR, 2 ether);\n        coin.mint(DONOR, 2e27);\n        vm.prank(DONOR);\n        coin.approve(address(pool), type(uint256).max);\n    }\n\n    function test_SandwichingAddLiquidityMustNotPay() public {\n        uint256 before = address(attacker).balance;\n        attacker.swap(key, true, -0.3 ether); // push: 0.3 ETH into 2 ETH of depth leaves the pool ~24% off Pons, inside the band\n        assertLe(hook.deviationBps(), hook.maxDeviationBps(), \"the push stays inside the band\");\n        // a stranger deepens the pool with 2 ETH and the coin for it, paired at the pushed price\n        vm.prank(DONOR);\n        (bool ok,) = address(pool).call{value: 2 ether}(abi.encodeWithSignature(\"addLiquidity(uint256,address)\", 2e27, DONOR));\n        ok; // refused or not, the attacker's round trip below must not profit\n        attacker.swap(key, false, -int256(coin.balanceOf(address(attacker)))); // sell it all back into the deeper pool\n        assertLe(address(attacker).balance, before, \"sandwiching addLiquidity paid the attacker from the pool's position\");\n    }\n\n    function _sqrt(uint256 x) internal pure returns (uint256 z) {\n        uint256 r = x; uint256 y = (x + 1) / 2;\n        while (y < r) { r = y; y = (x / y + y) / 2; }\n        z = r;\n    }\n\n    receive() external payable {}\n}","reproduction":"Pool opened at 1e9 coin/ETH with 2 ETH + 2e27 coin; the curve stays at 1e9 (reference readable, band on). Attacker with 10 ETH: (1) swap 0.3 ETH exact-in for coin: hook.deviationBps() == 2,419, inside the 3,000 band; (2) a stranger calls pool.addLiquidity{value: 2 ether}(2e27, donor), paired at the pushed price; (3) attacker sells all coin back. Expected: the round trip loses the 1% fee twice. Actual: attacker ends with 10.01309 ETH, +0.01309 ETH out of the pool's position. The same sweep with pushes of 0.1, 0.15, 0.2, 0.25 ETH pays 0.00034, 0.00214, 0.00491, 0.00858 ETH. Proof: test/scratch/AddLiquiditySandwich.t.sol fails on this code with that number.","severity":"medium","snippet":"        (uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolKey().toId());","title":"addLiquidity() pairs at any price inside the 30% band with no bound or minimum: a push, the addition and a sell-back take profit from the pool's position"},{"citation":"resolved","description":"The ea1c84c3 fix added a zero check to updateFeeAddress() ('no zero fee address') but the constructor still stores feeAddress_ unchecked, and IMD6900PerpsLaunch passes its feeAddress argument straight through. With feeAddress == address(0) and feeAddressRenounced == false, flush() computes toFeeAddress = 3% and SafeTransferLib.safeTransferETH(address(0), toFeeAddress) succeeds, burning it; updateFeeAddress(address(0)) would have refused the same state. This launch passes the team wallet, so the deployed instance is not affected; the gap matters for any relaunch or for a launch.json typo, which nothing in the build checks. Fix: revert ZeroAddress() in the constructor (or treat a zero fee address as renounced).","line":181,"path":"src/IMD6900PerpHook.sol","reproduction":"new IMD6900PerpsLaunch(PM, 10_000, OWNER, address(0)); hook.feeAddress() == address(0); vm.deal(address(hook), 1 ether); hook.flush(). Expected: the launch refuses the argument (or sends 100% to the pool). Actual: address(0) receives 0.03 ETH, the pool 0.97 ETH (test/scratch/Experiments.t.sol::test_ZeroFeeAddressBurns, logs 'sent to address(0): 30000000000000000').","severity":"low","snippet":"        feeAddress = feeAddress_;","title":"The hook's constructor accepts a zero fee address, so flush() sends 3% of forwarded ETH to address(0)"},{"citation":"resolved","description":"The hook states (lines 90-91) that 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'. The pool's fee is the constructor's poolFeePips (10,000 pips = 1% = 100 bps), but feeBps is hard-coded to 1,000 bps (10%) and is not derived from poolFeePips, so feeFor(anyone) reports a 10% fee from deployment until the owner remembers to call lowerFee(100). lowerFee() also floors at MIN_FEE_BPS = 100, so for any poolFee below 1% the model can never match the pool. The engine is not in this launch, so the impact here is a stated invariant that is false from construction and a post-deployment step the launch recipe says to avoid ('derive what follows from other arguments'). Fix: set feeBps = poolFeePips_ / 100 in the constructor (and drop or align MIN_FEE_BPS), keeping lowerFee's only-lower rule.","line":111,"path":"src/IMD6900PerpHook.sol","reproduction":"new IMD6900PerpsLaunch(PM, 10_000, OWNER, OWNER) exactly as the brief's arguments; then hook.poolFeePips() == 10000 (1%), hook.feeBps() == 1000 (10%), hook.feeFor(0xBEEF) == 1000 while the pool charges that trader 100 bps. Expected after the documented rule: feeBps == 100. Actual: 1000 (test/scratch/Experiments.t.sol::test_FeeBpsDefault logs). With poolFee = 3000 the equality is unreachable: lowerFee(30) reverts InvalidFee because 30 < MIN_FEE_BPS.","severity":"low","snippet":"    uint256 public feeBps = 1_000;","title":"feeBps starts at 1,000 bps while the pool's real fee is 100 bps, breaking the hook's own 'must be kept equal' rule the engine model relies on"},{"citation":"resolved","description":"CONTRACTS READ (all of src/, every line): src/IMD6900PerpHook.sol (488 lines), src/IMD6900PerpPool.sol (277), src/IMD6900PerpsLaunch.sol (76). Also read: script/Owner.s.sol, test/AuditFixes.t.sol, test/IMD6900Perps.t.sol, test/IMD6900PerpsLaunch.t.sol, test/RobinhoodFlow.fork.t.sol, the vendored lib/v4-periphery BaseHook, lib/solady Ownable (transferOwnership rejects address(0); handover is request/complete with a 48h expiry), v4-core Hooks.validateHookPermissions/isValidHookAddress, and the protected harness .imd/reads/protected/evm_contracts/Contracts.protected.t.sol. forge test (offline) passes 18 tests; the fork suite skips (no ROBINHOOD_RPC_URL). Measured: launch init code 19,508 bytes (harness limit 49,152), launch runtime 308 bytes, hook runtime 8,920, pool runtime 8,181 (limit 24,576); forbidden-opcode scan clean; mining fits one transaction on average (test_FitsOneTransaction).\n\nCOULD NOT BE REACHED (not in the tree, no network in the verifier): the Pons bonding curve and PonsV2LaunchFactory the hook reads (getReserves semantics, graduated(), the PonsLaunchedToken struct layout, memeHook), the Pons launcher 0xE0aBb21F15766BE162429f46F494617eB5EF6Ba0, the coin 0x69005D86d2c1bb1dFE70Df48e27D6aa02D044149 (whether its transfers are taxed), the live PoolManager 0x8366a39CC670B4001A1121B8F6A443A643e40951 (whether its hook ABI matches the vendored v4-core), and the perps engine (PerpEngine/PerpVault, not part of this launch). Leads that depend on them, NOT reported as findings: (a) ponsPriceUnchecked prices the curve as tokenReserve/quoteReserve; if Pons getReserves returns real rather than virtual reserves the pool opens off the real Pons price and is arbitraged at once (the fork test only checks self-consistency of the same formula); (b) a PonsLaunchedToken layout that differs from the real factory decodes to garbage rather than reverting; (c) ~0.3% of launch addresses need more than ~95k salts, which exceeds the 2^24 gas cap: deterministic per factory salt, the deployer's simulation catches it and a new salt resolves it; (d) tx.origin as the liquidator pays a 4337 bundler, not the user.\n\nTHE 41 ENTRY POINTS, each answered. IMD6900PerpHook (25): lowerFee [owner; only lowers, floor 100 bps; see low finding on the 1,000 default] OK. setPerpFeeBps [owner; <=1,000] OK. setPerpEngine [owner; zero = perps off; documented replacement path] OK, trust. setSweepFailOpen [owner; documented escape hatch] OK, trust. setBand [owner; nonzero] OK, trust. renounceOwnership [reverts NoRenounce, verified in AuditFixes] OK. setFeeExempt [owner; exempts a caller from the band] trust. updateFeeAddress [owner; nonzero; refused after renounce] OK. renounceFeeAddress [feeAddress or owner; one-way to the pool] OK. flush [anyone; 97% to pool.addFees, 3% to feeAddress; burns the 3% when the constructor's feeAddress is zero, see low finding] OK otherwise. beforeInitialize [PoolManager only; sender must be the pool; once; ETH/pool.token(); static fee == poolFeePips; spacing 60] OK. afterInitialize [PoolManager only; HookNotImplemented; flag off so never called] OK. beforeAddLiquidity [PoolManager only; sender == pool; full range only] OK. afterAddLiquidity [not implemented, flag off] OK. beforeRemoveLiquidity [PoolManager only; sender == pool; full range; no engine, band or reference dependency: verified offline that the owner removes all liquidity with a reverting engine on the hook, a reverting curve and a 1 bps band] OK, property kept. afterRemoveLiquidity [not implemented, flag off] OK. beforeSwap [PoolManager only; pool-id check; pre-trade sweep fails closed at any gas unless fail-open; band start recorded after the sweep so engine nested swaps cannot hide a push; UNTRACKED cannot collide with a real deviation] OK, but the band is off whenever the reference reads 0 (root of the compound finding). afterSwap [PoolManager only; band judged before the best-effort sweep; crossing rule verified by AuditFixes] OK. beforeDonate/afterDonate [not implemente","line":17,"path":"src/IMD6900PerpsLaunch.sol","reproduction":"Not a defect. forge test offline: 18 passed, fork suite skipped (no RPC). test/scratch/OwnerAlwaysRemoves.t.sol passes: the owner removes all liquidity with a reverting engine set on the hook, a reverting curve and a 1 bps band, which is the property the brief asks to keep.","severity":"info","snippet":"contract IMD6900PerpsLaunch {","title":"Coverage: contracts read, contracts that could not be reached, and all 41 entry points answered"}],"hash":"3afd7b37f7da46fb46306a2756843fc222c00475118c8e2053f96c0dcf5db106","nodeId":"8c1f0a91-37b1-4119-8dbe-d822ef47ba94","outcome":"completed","summary":"Review complete. The findings are in `.imd-findings.json` at the repository root; no source, config or library file was changed (only `test/scratch/`, which is mine).\n\n**What I found**\n\n- **Medium: `compound()`'s Pons guard is void when the reference reads zero** (`src/IMD6900PerpPool.sol:196`). The hook reports a deviation of 0 whenever it cannot price Pons (graduated with no pool recorded, zero reserves, an unreadable curve), and the band is off in the same states. Anyone can push the price, compound the pool's idle ETH and coin at that price, and sell back. The proof shows the attacker gaining 0.032 ETH from the pool's position on a 2 ETH pool. Proof attached and failing on the current code.\n- **Medium: public `addLiquidity()` has no price bound inside the 30% band** (`src/IMD6900PerpPool.sol:179`). A 0.3 ETH push, a stranger's 2 ETH addition, and a sell-back pay the attacker 0.013 ETH out of the pool's position, with the reference fully readable. The audit fix gave this guard to `compound()` only. Proof attached and failing on the current code.\n- **Low: the hook constructor accepts a zero fee address**, so `flush()` burns 3% to address(0). The audit fix covered `updateFeeAddress` but not construction. This launch passes the team wallet, so the deployed instance is unaffected.\n- **Low: `feeBps` starts at 1,000 bps while the pool charges 100 bps**, breaking the hook's own \"must be kept equal\" rule, and unreachable for any pool fee below 1% because of the floor in `lowerFee`.\n- **Info: coverage record.** All three source contracts were read in full. All 41 entry points are answered one by one, including the ten solady ownership functions on both contracts. The owner's always-remove property was verified offline past a reverting engine, a reverting curve and a 1 bps band.\n\n**What could not be reached** (recorded in the coverage finding, with the leads that depend on them): the Pons curve and factory, the launcher, the coin, the live Robinhood PoolManager, and the perps engine. The fork suite skipped for lack of an RPC.\n\n**Verified clean:** the admission scan, fresh-chain deployment, one-transaction fit, init code of 19,508 bytes against the harness's 49,152 limit, flags matching `FLAGS` exactly, no dynamic fee, and the existing 18 offline tests passing.","treeHash":null,"usage":{"cachedInputTokens":1741449,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":65967,"runtime":"claude","turns":33,"wallClockMs":1018880}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"dedc96e38388cb45","findings":[{"citation":"resolved","description":"Second-round settlement of 0b0abb0c. The author kept _deviation as abs(pool - ref) / ref in coin per ETH and documented the unit in the NatSpec (line 338: 'Distance in coin per ETH: abs(pool - Pons) / Pons, in basis points (not ETH per coin)') and the README ('spans roughly -23.08% to +42.86% in the reciprocal ETH-per-coin price'). The reciprocal asymmetry still reproduces exactly as described, but the brief does not fix the band's unit, the choice is now explicit for the future engine, and no fund loss exists inside this launch, so it is recorded as information and not reopened. The author's correction is accepted: at curve.set(1.3 ether, 1e27) the reference is 7.69e8 coin/ETH against a pool at 1e9, which is 3000 bps (my earlier 2307 divided by the pool price instead of the reference). Nothing to fix unless the engine's mark needs a symmetric ETH-per-coin bound, which is a design decision for the owner.","line":347,"path":"src/IMD6900PerpHook.sol","reproduction":"forge test --offline --match-test test_BandIsDefinedInCoinPerEth (test/RevisionFixes.t.sol, passes): pool at 1e9 coin/ETH. curve.set(0.7 ether, 1e27): deviationBps == 3000, inside the band, while the coin trades at 1/0.7 = 1.4286x its Pons price in ETH per coin. curve.set(1.3 ether, 1e27): deviationBps == 3000, inside, coin at 1/1.3 = 0.769x. curve.set(1.4286 ether, 1e27): deviationBps == 4286, refused, coin at exactly 0.7x. Expected and actual agree with the documented unit; the asymmetry is in the reciprocal price only.","severity":"info","snippet":"        return (d * BPS) / ref;","title":"Settled as a documented design choice: the 30% band is measured in coin per ETH, so in the coin's own price it spans about -23.1% to +42.9% around Pons (author disputed; reproduces; my earlier 2307 bp"}],"hash":"3b46e199d89431f287cabee29ddb57f9112b603bf2828bdeb22576eb61535594","nodeId":"a6bd2778-d5a7-4d32-b9af-d8fddadb0ea3","outcome":"completed","summary":"The review is complete and the findings file is written. The working tree is clean apart from `.imd-findings.json`.\n\n**Outcome: every earlier finding is settled, nothing blocking remains.** The revised tree builds offline and all 96 offline tests pass, with the two fork-dependent tests skipping as designed. Both attached proofs now pass against the revised code.\n\nSettlement of the two medium findings:\n\n- **Salt mining gas bound (b3778ef6): fixed.** The miner now checks eight salts per batch and stops after 134,144 tries with a NoSalt revert instead of running out of gas. The original 132,475-try vector is replayed at its original address and costs 11.87M gas; exhaustion costs 12.02M. Worst-case search plus deployment plus intrinsic and a 100K factory headroom comes to 16,697,989 gas, under the 2^24 cap. A fuzz test confirms the batched miner returns the same first salt as a plain search. Residuals that are not defects: roughly one launch address in 3,600 will revert NoSalt and need a new launch number, which the README now states, and the real factory's wrapper overhead is not in the tree, so the final admission simulation still has to run.\n- **Manifest owner argument (f7dadb5f): fixed.** launch.json now writes the static owner address as the third argument.\n\nSettlement of the advisory findings:\n\n- **Idle ETH in compound (411b0e14): fixed.** When either idle balance is zero and the position is non-empty, compound pokes the position to collect earned fees, then pairs. The attached proof passes.\n- **Fee model divergence (f44ad048): fixed.** feeFor returns the pool's immutable rate for every caller, setPerpFeeBps accepts only that rate, and lowerFee always refuses.\n- **open() with no on-Pons bound (af62a280): fixed.** open now runs the same 1% readable-reference guard after initialize, and a stale price rolls everything back.\n- **Band asymmetry (0b0abb0c): author disputed, kept as info.** The reciprocal asymmetry reproduces, but the author documented the coin-per-ETH unit in NatSpec and README as a design choice. The author's correction of my earlier number is right: the deviation in that example is 3000 bps, not 2307.\n- **Dead-address transfer, owner powers, drained-pool price move, bad fee sink: documented** in the README and covered by regression tests. The real Pons launcher's addFees compatibility is now exercised in the fork flow but stays unverified offline.\n\nThe audit_flow finding that the one-transaction test measured a cheatcode is also fixed: the test now uses a raw CREATE and meters 4.58M gas.\n\nI re-read every changed line and found nothing new that reproduces. The coverage record answers all 41 entry points as holds, with three extra rows for the launch constructor, the manifest and the gas test.","treeHash":null,"usage":{"cachedInputTokens":894393,"inputTokens":258,"model":"claude-fable-5-1","outputTokens":22637,"runtime":"claude","turns":17,"wallClockMs":402585}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"f47f953f8e35921e","findings":[{"citation":"resolved","description":"The task names test_FitsOneTransaction as the check that the whole launch, salt mining included, fits one transaction under EIP-7825's 2^24 gas. The test brackets `new IMD6900PerpsLaunch(...)` with gasleft(). Under the forge this repository is built and checked with (1.8.3; `forge config` shows dynamic_test_linking = true by default, nothing in foundry.toml sets it), every `new` in a test compilation unit is rewritten into a VM::deployCode cheatcode call, whose gas is not charged to the calling frame. The trace shows exactly this: `VM::deployCode(\"src/IMD6900PerpsLaunch.sol:IMD6900PerpsLaunch\", ...)` wrapping a real CREATE that cost 4,217,013 gas, while the test logs `launch gas (the mining included): 3760` and asserts 3760 < 16,777,216. The assertion is vacuous: it would pass if the mining loop cost 100M gas. Measured with a raw CREATE in assembly (which the rewrite does not touch) over 24 deployer addresses, the real launch costs 3,863,137 (best) to 13,203,253 (worst) gas, mean 6,128,371, against the 16,777,216 cap; the README's own 40-address sample reports a 10.5M worst case and one deployer in ~350 over the cap. None of that is exercised by the test as it stands, so a deployer address whose salt search exceeds the cap (or the deployer service's gas ceiling) would not be caught before the launch transaction fails. This is a defect of the acceptance test, not of the contracts; the launch's init code (19,810 bytes) and runtimes (hook 8,920, pool 8,307, launch 308 bytes) are within the harness limits. Fix: measure through a frame the rewrite cannot touch, e.g. a helper contract that runs `create(0, add(init, 0x20), mload(init))` on `abi.encodePacked(type(IMD6900PerpsLaunch).creationCode, abi.encode(PM, uint24(10_000), OWNER, FEE))` and returns `g - gasleft()`, and assert on that; optionally also assert the cost at the factory's actual CREATE2 address once the salt is known, since the mining cost is a function of the launch's own address.","line":34,"path":"test/IMD6900PerpsLaunch.t.sol","reproduction":"1) `forge test --offline --match-test test_FitsOneTransaction -vvvv`: Logs show `launch gas (the mining included): 3760`; the trace shows `VM::deployCode(...)` -> `[4217013] new IMD6900PerpsLaunch@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f`. Expected: the logged value equals the CREATE's cost (~4.2M); actual: 3,760, so `assertLt(used, 1 << 24)` tests nothing. 2) `forge config | grep dynamic` prints `dynamic_test_linking = true` with no such key in foundry.toml. 3) Raw-CREATE measurement (test/scratch/RawGas.t.sol, a RawDeployer helper calling `create` in assembly on the launch's init code, 24 fresh helper addresses): init code 19,810 bytes; mean 6,128,371 gas; best 3,863,137; worst 13,203,253; cap 16,777,216. The same test's repo-style gasleft delta around `new` is 3,541.","severity":"medium","snippet":"        uint256 g = gasleft();\n        new IMD6900PerpsLaunch(PM, 10_000, OWNER, FEE);\n        uint256 used = g - gasleft();","title":"test_FitsOneTransaction measures a cheatcode, not the launch: the one-transaction admission property is unverified"},{"citation":"resolved","description":"The README's migration path is: take all the liquidity out with removeLiquidity, open a new pool with it. removeLiquidity asks the engine whether any open position is liquidatable after the removal, but the engine answers at the pool's current price, and the removal leaves the pool with zero liquidity at that price. In Uniswap v4 a swap against zero liquidity moves sqrtPriceX96 to the caller's price limit with zero amounts exchanged, so once the pool is empty, any caller moves its price for nothing; the hook's band (_checkBand) only requires the end price to sit within maxDeviationBps (30%) of Pons or closer than before, so the free move reaches 30% either side. The pool's price is the mark the perps engine (deployed after this launch) trades and liquidates against. Nothing in this launch is harmed (there is no engine yet), and the state requires the owner's own removal, so this is recorded as information for the owner's runbook rather than a defect: unwire the engine (hook.setPerpEngine(0), pool.setPerpEngine(0)) and close or migrate positions before taking the last liquidity out, or have removeLiquidity refuse to empty the pool while the engine reports open positions (openIds().length != 0 and the removal would leave liquidity() == 0).","line":144,"path":"src/IMD6900PerpPool.sol","reproduction":"Offline against the vendored PoolManager (test/scratch/Probe.t.sol test_ZeroLiquidityPriceMove): launch at 1% fee, owner opens at 1e9 coin/ETH with 2 ETH + 2e27 coin (Pons curve at the same price); owner calls `pool.removeLiquidity(pool.liquidity(), OWNER, OWNER)` -> pool.liquidity() == 0. A trader then swaps zeroForOne with amountSpecified = -1 and sqrtPriceLimitX96 = sqrt(0.75 * ponsPriceX96 << 96): BalanceDelta is (0, 0), trader ETH spent 0, poolPriceX96 goes from 79224271143834286986338705831335297024 to 59421121885698253195157962751999977299 and hook.deviationBps() from 0 to 2500; the band accepts it. Expected (per README's 'held near the Pons price'): a price move should cost liquidity-proportional value; actual: free, bounded only by the 30% band.","severity":"info","snippet":"                if (IPerpBookView(engine).isLiquidatable(ids[i])) revert WouldLiquidate(ids[i]);","title":"After the owner removes all liquidity, anyone can set the pool price for free up to the band's edge; the engine check in removeLiquidity is evaluated at the pre-removal price"},{"citation":"resolved","description":"flush() forwards 97% of the hook's balance into IMD6900PerpPool.addFees(), which forwards all of msg.value into IFeeSink(feeSink).addFees(). setFeeSink only checks that the sink has code, not that it exposes addFees(); with a sink that lacks it (or whose addFees reverts), pool.addFees reverts, so hook.flush reverts and any ETH in the hook (its receive() accepts anything) stays there until the owner calls setFeeSink(0) or another sink. The owner can always recover it, so this is not a loss, but script/Owner.s.sol wire() sets the sink to the Pons launcher (0xE0aB...6Ba0), and neither test/RobinhoodFlow.fork.t.sol (which sets that sink but never calls flush or addFees with it set) nor any offline test exercises flush -> pool.addFees -> launcher.addFees; the IPonsLaunch interfaces in the script and the fork test list no addFees() function. Verify the launcher accepts addFees() on the fork (one flush in test_feeSplitFeedsThePool after setFeeSink) or have setFeeSink probe it (a zero-value staticcall is not possible for a payable; a try addFees{value:0}() on set would do).","line":96,"path":"src/IMD6900PerpPool.sol","reproduction":"Offline (test/scratch/Probe.t.sol test_FlushStuckOnBadSink): vm.deal(hook, 1 ether); owner calls pool.setFeeSink(<any contract without addFees(), e.g. the Curve mock>) -> accepted (code.length != 0); hook.flush() -> reverts (the sink's fallback is missing); owner calls pool.setFeeSink(address(0)); hook.flush() -> succeeds, pool balance 0.97 ether, 0.03 ether to the fee address. Expected: a sink that cannot take fees is refused when set, or flush fails open into the pool; actual: flush is blocked until the owner notices.","severity":"info","snippet":"        if (feeSink_ != address(0) && feeSink_.code.length == 0) revert NotAContract(); // a sink must take addFees()","title":"setFeeSink accepts any contract; a sink without addFees() makes flush() revert and strands the hook's ETH until the owner re-points it, and the planned sink path is never exercised"},{"citation":"resolved","description":"After the second audit's fix, feeBps and perpFeeBps are derived from poolFeePips at construction (100 bps for this launch's 10,000 pips), and the hook's own rule (lines 94-95) is that both 'must be kept equal to the pool's real fee', which is immutable. With feeBps == MIN_FEE_BPS == 100, lowerFee(x) reverts InvalidFee for every x (x < 100 fails the floor, x >= 100 fails the only-lower rule), so the function is dead code for this deployment. setPerpFeeBps(bps) still accepts any value up to 1,000 and so lets the owner desynchronise perpFeeBps from poolFeePips. Owner-only and the engine that reads these values is not part of this launch, so no harm is reachable here; recorded so the author can decide whether to remove lowerFee/setPerpFeeBps or bound setPerpFeeBps to poolFeePips / 100.","line":216,"path":"src/IMD6900PerpHook.sol","reproduction":"Offline (test/scratch/Fee.t.sol): launch with poolFee 10_000; hook.feeBps() == 100; as owner, hook.lowerFee(f) reverts InvalidFee for every f in 0..200 (and by the condition for every uint256); hook.setPerpFeeBps(1000) succeeds and leaves perpFeeBps == 1000 while feeBps == 100 and poolFeePips == 10_000. Expected per lines 94-95: both rates equal the pool's fee; actual: perpFeeBps 10x the pool's fee.","severity":"info","snippet":"        if (newFeeBps >= feeBps || newFeeBps < MIN_FEE_BPS) revert InvalidFee();","title":"lowerFee can never succeed for this launch's fee, and setPerpFeeBps can move the engine-facing rate away from the pool's real fee the hook says it must equal"}],"hash":"518ef7f9b9cfc23cbe8c24b20f761155975cd0f08bcaa4ef001361d609aebeb1","nodeId":"798a264a-f6c1-44e3-9ea7-20bf0fbe6dde","outcome":"completed","summary":"Review complete. The findings file is at the repository root with 4 findings and a 43-row coverage record answering all 41 entry points; the scratch tests and the gas snapshot they produced are removed, so nothing else in the tree changed.\n\n**What I found**\n\n- **Medium, test defect:** `test_FitsOneTransaction` does not measure the launch. This forge version enables dynamic test linking by default, so the test's `new` is rewritten into a `deployCode` cheatcode whose gas is not charged to the frame. The test logs 3,760 gas and asserts it is under 2^24, while the trace shows the real CREATE cost 4,217,013. Measured with a raw CREATE over 24 deployer addresses, the launch costs between 3.86M and 13.2M gas, mean 6.13M, against a 16.78M cap. The contracts themselves are within limits (init code 19,810 bytes, runtimes under 24,576), but the one-transaction property the task names is unverified by the suite.\n- **Info:** after the owner empties the pool with `removeLiquidity`, a 1 wei swap moves the price up to the 30% band edge for free (reproduced: zero delta, 25% move). Harmless in this launch, relevant to the migration runbook once the perps engine reads the pool as its mark.\n- **Info:** `setFeeSink` only checks for code, so a sink without `addFees()` makes `flush()` revert until the owner re-points it. The planned sink path through the Pons launcher is never exercised by any test, including the fork flow.\n- **Info:** for this launch's 1% fee, every `lowerFee` call reverts, while `setPerpFeeBps` can move the engine-facing rate away from the pool fee the hook's own comment says it must equal.\n\n**Coverage:** 36 entry points hold after tracing their guards, state changes and failure paths from the Execution Trace, Periphery and First Principles guides; 7 rows point at the info findings above. The earlier audits' fixes in `test/AuditFixes.t.sol` were re-run and hold. Nothing was left unreached. What I could not check offline is the live Pons launcher's `addFees()` and the coin's transfer behaviour, both covered only by the fork test.","treeHash":null,"usage":{"cachedInputTokens":1716306,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":56991,"runtime":"claude","turns":40,"wallClockMs":924432}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"e35a3abed04af1ac","findings":[],"hash":"6d0d945be6dc96b5a4c6d76f5b3b22e3d3a0b2f0051f20f95fbb673dc3859a95","nodeId":"e9199b71-c874-4529-b8d4-db6ec1bdac16","outcome":"failed","summary":"could not restore accepted work: accepted work conflicts with a dependency that changed the same files after it was accepted: ","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":null,"outputTokens":0,"runtime":"codex","turns":0,"wallClockMs":1518}},{"artifacts":[],"attempt":1,"bundleHash":"9d42d1237bdb1cea84aa5c678354a9736a85f62866142b03e4f39523ac10ab85","device":"87645bda72145989","findings":[],"hash":"90ce743fecde13594d2e736e05c8e026cf054efa053377947de5417d2a65ad69","nodeId":"4d09efb7-dd04-4939-915f-4eed773f1126","outcome":"completed","summary":"Created [launch.json](/home/imd/.identitymd/work/e63e94be-2e3d-4e5a-bf03-30d4c4c3cf03/4d09efb7-dd04-4939-915f-4eed773f1126/launch.json) with the approved constructor arguments and `$owner` for ownership.\n\nValidated manifest structure and compiled ABI. Offline build succeeded; tests: 24 passed, 0 failed, 1 fork suite skipped because `ROBINHOOD_RPC_URL` was unset. Only `launch.json` changed.","treeHash":"3dadd2b0f103bff990212c5a5ef136e3821cd391","usage":{"cachedInputTokens":180864,"inputTokens":47282,"model":"gpt-6-astra","outputTokens":2517,"runtime":"codex","turns":3,"wallClockMs":53290}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ddc5542bddcad225","findings":[{"citation":"resolved","description":"Access-control / asymmetry (guard inconsistency). The pool and the hook both override solady's renounceOwnership to revert NoRenounce, and the README states the reason: only the owner can take the liquidity out, so renouncing would strand it. The equivalent paths are left open: the inherited one-step transferOwnership(newOwner) accepts any non-zero address (0x000000000000000000000000000000000000dEaD, a typo, a contract with no way to call removeLiquidity), and completeOwnershipHandover(pendingOwner) hands ownership to any address that once called requestOwnershipHandover. After either, nobody can call removeLiquidity, setPerpEngine, setCurve or any hook setter, which is exactly the state NoRenounce exists to prevent. The same asymmetry exists on IMD6900PerpHook (src/IMD6900PerpHook.sol:247-249). This is a residual of the earlier 'ownership can't be renounced' fix: the guard covers one of three equivalent paths. It is an owner action, so impact needs an operator mistake; the brief plans a transferOwnership to the Robinhood timelock, which is where a wrong address would be fatal. A minimal fix that keeps the design: route transfers through the two-step handover only (override transferOwnership to revert, or require newOwner.code.length != 0 / a handover request from newOwner), so a wrong address cannot be made owner without having acted first.","line":131,"path":"src/IMD6900PerpPool.sol","reproduction":"Open the pool as in test/IMD6900Perps.t.sol setUp (owner 0xA11CE, 2 ETH + 2e27 coin). 1) vm.prank(OWNER); pool.renounceOwnership() -> reverts NoRenounce (the guard). 2) vm.prank(OWNER); pool.transferOwnership(0x000000000000000000000000000000000000dEaD) -> succeeds, pool.owner() == 0xdEaD. 3) vm.prank(OWNER); pool.removeLiquidity(pool.liquidity(), OWNER, OWNER) -> reverts Ownable.Unauthorized; no address can ever call it. Expected: the stated invariant 'the liquidity always comes back' is protected on every path that gives ownership away. Actual: it is protected on renounceOwnership only. Same sequence on hook.transferOwnership(0xdEaD) succeeds and hook.owner() == 0xdEaD. Verified with a scratch Foundry test against a real v4 PoolManager.","severity":"low","snippet":"    function renounceOwnership() public payable override onlyOwner {\n        revert NoRenounce();\n    }","title":"NoRenounce guards renounceOwnership only; transferOwnership/completeOwnershipHandover to a dead address strands the liquidity the same way on both contracts"},{"citation":"resolved","description":"Asymmetry between writers and the documented invariant. The hook's own rule (src/IMD6900PerpHook.sol:94-95) is that feeBps and perpFeeBps are 'what the engine reads to model its solvency, and must be kept equal to the pool's real fee: both are derived from poolFeePips at construction'. The earlier fix (93cc8561) derived the initial values, but the two setters that write the same variables were left as they were: setPerpFeeBps accepts any value in [0, 1000] in either direction, and lowerFee lowers feeBps to anything >= MIN_FEE_BPS, while the pool's real fee (poolFeePips, immutable, enforced in _beforeInitialize) never changes. So the owner can make feeFor(engine) read 0 or 1000 bps while every engine swap actually pays 100 bps to the position, and the engine's solvency model (not in this launch, wired later with setPerpEngine) is fed a wrong fee. For this launch's 1% fee, lowerFee can never succeed at all: feeBps == MIN_FEE_BPS == 100, so every argument is refused (>= feeBps or < MIN_FEE_BPS), which makes it a dead owner power rather than a risk. Trust assumption: only the owner can do this. Minimal fix preserving the design: either remove setPerpFeeBps/lowerFee (the values are immutable in substance) or bound them to poolFeePips / 100.","line":221,"path":"src/IMD6900PerpHook.sol","reproduction":"Deploy IMD6900PerpsLaunch(PM, 10000, OWNER, OWNER). hook.poolFeePips() == 10000, hook.feeBps() == 100, hook.perpFeeBps() == 100. 1) vm.prank(OWNER); hook.lowerFee(x) for every x in 0..299 -> reverts InvalidFee (x >= 100 fails newFeeBps >= feeBps, x < 100 fails newFeeBps < MIN_FEE_BPS): no input succeeds. 2) vm.prank(OWNER); hook.setPerpFeeBps(0) -> perpFeeBps == 0; hook.setPerpFeeBps(1000) -> perpFeeBps == 1000, while hook.poolFeePips() is still 10000 (1%) and the pool charges 1% on the engine's swaps. 3) With a 2% pool, new IMD6900PerpsLaunch(PM, 20000, OWNER, OWNER): feeBps == 200; vm.prank(OWNER); hook.lowerFee(100) -> feeBps == 100 while poolFeePips == 20000. Expected per the hook's own doc: feeBps == perpFeeBps == poolFeePips / 100 always. Actual: any owner call to either setter breaks it. Verified with a scratch Foundry test.","severity":"low","snippet":"    function setPerpFeeBps(uint256 bps) external onlyOwner {\n        if (bps > MAX_PERP_FEE_BPS) revert InvalidFee();\n        perpFeeBps = bps;\n        emit PerpFeeSet(bps);\n    }","title":"setPerpFeeBps and lowerFee can move the engine-model rates away from the pool's immutable fee, which the hook says must stay equal; lowerFee is unreachable for this launch"},{"citation":"resolved","description":"Not a bypass: every one of these is behind onlyOwner and is the intended role. Recorded separately as the Trust Gap guide asks (seam access x asymmetry: an admin write that changes where in-flight value goes, or which caller class a guard applies to). (1) updateFeeAddress and renounceFeeAddress (callable by the owner, not only by the fee address) redirect the 3% of whatever ETH already sits in the hook: flush() reads feeAddress at call time, nothing is checkpointed for the old address. For this launch owner == feeAddress, so there is no second party to lose anything; it only matters if feeAddress is ever a different wallet. (2) setFeeExempt(router, true) removes the Pons band for every trade through that router (tracksPons), and feeFor() reports it fee-free to the engine's model although the pool still charges its static fee. (3) setCurve repoints the reference the band, addLiquidity and compound are judged against, to any contract. (4) setSweepFailOpen(true) lets trades run with no pre-trade liquidation sweep (the documented escape hatch). (5) pool.setPerpEngine(address(0)) switches off the only guard on removeLiquidity while hook.perpEngine may still have open positions; the brief asks for this (the liquidity must always come back). (6) The hook and the pool are two separate Ownables; the plan transfers both to the timelock, and nothing on-chain keeps them together. Preconditions for all: the owner key (then the timelock).","line":256,"path":"src/IMD6900PerpHook.sol","reproduction":"For (1): vm.deal(address(hook), 1 ether); vm.prank(OWNER); hook.updateFeeAddress(0xB0B); hook.flush() -> 0.03 ETH goes to 0xB0B and 0.97 ETH to the pool, the previous fee address gets nothing (flush: toFeeAddress = amount * 3 / 100 sent to the current feeAddress). For (2): vm.prank(OWNER); hook.setFeeExempt(address(trader), true); a 1 ETH buy into 2 ETH of depth by trader then succeeds although test_RefusesToDriftFarFromPons shows the same trade reverting TooFarFromPons without the exemption (tracksPons returns false, start == UNTRACKED, _checkBand returns). Expected and actual agree: these are designed owner powers; the point is to have them in the record.","severity":"info","snippet":"    function updateFeeAddress(address feeAddress_) external onlyOwner {\n        if (feeAddressRenounced) revert FeeAddressIsRenounced();\n        if (feeAddress_ == address(0)) revert ZeroAddress();\n        feeAddress = feeAddress_;\n        emit FeeAddressUpdated(feeAddress_);\n    }","title":"Owner powers to document as trust assumptions: fee-address reassignment before flush, band exemptions, curve repointing, fail-open sweep, and the pool-side engine switch-off"}],"hash":"a29a531f04d85eff52401d4ac433645845d9d005c7babc8a89bf116cbe5f3078","nodeId":"68612fef-2eb8-47f4-bdba-25a45d2b08e7","outcome":"completed","summary":"The review is complete. The findings file is in place at the repository root, the scratch tests are removed, and the working tree has no stray files (`out/` and `cache/` are the gitignored build artifacts).\n\n**Outcome.** The permission model of the launch holds up. Every state writer on the hook and the pool is either the owner, the PoolManager with the pool as sender, or a public function that only moves protocol-owned value into the protocol-owned position. I found no permission bypass, no initialization front-run, and no escalation path. Three items are reported, two low and one informational.\n\n**Findings written to `.imd-findings.json`:**\n\n1. **Low.** NoRenounce guards only one of three equivalent paths. On both contracts the owner can still call `transferOwnership` or `completeOwnershipHandover` to a dead address, which strands the liquidity exactly as renouncing would. Reproduced against a real v4 PoolManager: after a transfer to 0xdEaD nobody can call removeLiquidity.\n2. **Low.** The fee-model setters contradict the hook's own rule that feeBps and perpFeeBps stay equal to the immutable pool fee. `setPerpFeeBps` moves the engine-model rate to 0 or 1000 bps while the pool still charges 1%. For this launch `lowerFee` can never succeed, since feeBps already sits at its floor.\n3. **Info.** Owner powers recorded as trust assumptions, as the Trust Gap guide asks: redirecting un-flushed ETH by changing the fee address, exempting a router from the band, repointing the Pons curve, fail-open sweeps, and switching off the pool-side engine guard. Each is intended and reproduced, not a bypass.\n\n**Coverage.** All 41 entry points have a row, plus the launch constructor and the liquidity-ownership invariant. Guards I verified with scratch tests before deleting them: the pool's key cannot be initialized by anyone but the pool, a second pool cannot attach to the hook, every hook callback and the pool's unlockCallback refuse callers other than the PoolManager, and every owner setter refuses non-owners. The existing 24 offline tests pass unchanged.\n\n**Not reached.** Nothing in the assigned area. The perps engine is outside this launch, so the tx.origin liquidator and the fee-model consequences of finding 2 could only be traced to the hook's boundary.","treeHash":null,"usage":{"cachedInputTokens":2236222,"inputTokens":546,"model":"claude-fable-5-1","outputTokens":55016,"runtime":"claude","turns":37,"wallClockMs":818960}},{"artifacts":[],"attempt":1,"bundleHash":"3f503c3a91fd214cadf4bd30784ec9f627c94ba594c7cebbc3dead31386d776d","device":"ea30e435c4dd5aff","findings":[],"hash":"acfb568db736f564418c573648792a11f30d493ceaa99fbf7a17227794deb9eb","nodeId":"a47cdc2a-7e44-4e26-95b1-03af81da24fa","outcome":"completed","summary":"Implemented bounded salt mining and fixes for compounding, fee-model consistency, and stale-price opening.\n\nRecorded all 14 finding responses in `.imd-responses.json`; `ADAPTATION.md` documents changes and reviews all 41 entry points.\n\n`forge build` passed. `forge test`: **58 passed, 0 failed, 2 skipped**. Robinhood fork and production-factory checks await their configuration.","treeHash":"7bc90047a296058200bf13291ea03942844079c9","usage":{"cachedInputTokens":3423616,"inputTokens":147393,"model":"gpt-6-astra","outputTokens":41958,"runtime":"codex","turns":8,"wallClockMs":960942}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"dfa08759ca0424de","findings":[{"citation":"resolved","description":"The constructor mines the hook's CREATE2 salt from its own address until the low 14 bits match FLAGS, up to MAX_TRIES = 2^20 (1,048,576) tries. Measured with a raw CREATE2 (test/scratch/Judge.t.sol test_MiningGasFit, 24 factory salts, exact Robinhood arguments): 129 gas per try on a 3,789,724 gas baseline, so the 16,777,216 cap (EIP-7825, the bound test_FitsOneTransaction and the deployer's gas ceiling enforce) leaves room for about 100,000 tries before intrinsic and calldata gas. A geometric search with p = 1/16384 exceeds 100,000 tries with probability e^-6.1 (about 1 in 450; about 1 in 400 after the transaction's intrinsic gas), and 2^20 tries would cost about 139M gas, so NoSalt is unreachable: the transaction simply runs out of gas and the whole launch reverts. In the 24-salt sample one address (salt 12: 109,951 tries, 17,973,403 gas) is already over the cap; the specialist's probe at factory salt 50149 needs 132,475 tries and 21,218,454 gas. The launch's address is set by IMD's factory, the launch number and this init code (see the reference's deployment model), so for a given launch it is either deterministically under or deterministically over; neither the factory address nor the launch number is in the tree, so this review cannot settle which. The deployer's simulation would catch an over-cap launch before broadcast (no funds lost), but the launch would be parked and could only be retried under a new launch number. The tree's own one-transaction tests (test/IMD6900PerpsLaunch.t.sol test_FitsOneTransaction now uses a raw CREATE and asserts a metered >3M; test/LaunchAdmission.t.sol test_FitsOneTransactionWithMeteredCreate2) each measure one deployer address, so they prove the property for that address only; audit_flow's finding that the earlier version measured an unmetered vm.deployCode is already fixed in this commit and is dropped. Minimal mitigations that keep FLAGS and the arguments: cheapen the loop (the keccak of 85 bytes is ~48 gas, the rest is memory traffic that could be trimmed), or have the verifier compute the launch's actual address and mining cost before admission and state the retry path (new launch number) in the README.","line":65,"path":"src/IMD6900PerpsLaunch.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\nimport {Test} from \"forge-std/Test.sol\";\nimport {IMD6900PerpsLaunch} from \"src/IMD6900PerpsLaunch.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\ncontract GasProbeFactory {\n    function deploy(bytes memory init, bytes32 salt) external returns (IMD6900PerpsLaunch) {\n        address result;\n        assembly (\"memory-safe\") { result := create2(0, add(init, 32), mload(init), salt) }\n        require(result != address(0), \"deployment failed\");\n        return IMD6900PerpsLaunch(result);\n    }\n}\ncontract MiningGasProbeTest is Test {\n    function test_LaunchFitsTransactionAtFactorySalt50149() public {\n        uint16 seed = 50149;\n        GasProbeFactory code = new GasProbeFactory();\n        vm.etch(address(0xBEEF), address(code).code);\n        address owner = 0x35dA9C0303507ddf708E87F2568EdDf12c47a059;\n        bytes memory init = abi.encodePacked(type(IMD6900PerpsLaunch).creationCode,\n            abi.encode(IPoolManager(0x8366a39CC670B4001A1121B8F6A443A643e40951), uint24(10_000), owner, owner));\n        uint256 before = gasleft();\n        IMD6900PerpsLaunch launch = GasProbeFactory(address(0xBEEF)).deploy(init, bytes32(uint256(seed)));\n        uint256 used = before - gasleft();\n        emit log_named_uint(\"salt seed\", seed);\n        emit log_named_uint(\"mining iterations\", uint256(launch.salt()) + 1);\n        emit log_named_uint(\"gas\", used);\n        assertLt(used, 1 << 24, \"launch exceeds transaction budget before intrinsic gas\");\n    }\n}","reproduction":"forge test --offline --match-path test/scratch/Proof_d8eac96ce765.t.sol -vv (the attached proof): a CREATE2 factory etched at 0xBEEF deploys IMD6900PerpsLaunch(0x8366a39CC670B4001A1121B8F6A443A643e40951, 10000, 0x35dA9C0303507ddf708E87F2568EdDf12c47a059, 0x35dA9C0303507ddf708E87F2568EdDf12c47a059) at factory salt 50149. Expected: metered gas < 16,777,216. Actual: salt 132474 (132,475 tries), 21,218,454 gas, assertion fails. test/scratch/Judge.t.sol test_MiningGasFit over factory salts 0..23: best 1,507 tries / 3,984,127 gas, worst 109,951 tries / 17,973,403 gas (over the cap), 129 gas per try, baseline 3,789,724.","severity":"medium","snippet":"            for { let i := 0 } lt(i, tries) { i := add(i, 1) } {","title":"Hook salt mining has no gas bound: about one launch address in 400 needs more than the 2^24 one-transaction cap, and the launch address is fixed by the factory"},{"citation":"resolved","description":"The brief fixes the third constructor argument: 0x35da9c0303507ddf708e87f2568eddf12c47a059 (owner of the pool and the hook). The recipe for this launch mode says an owner address the brief itself states is written as that static address instead of $owner, since $owner resolves to the wallet that paid for the launch or the request's owner field, which the manifest cannot see. The manifest writes \"$owner\" for the owner and the static 0x35da... for the fee address (line 10), and relies on the notes to say that $owner resolves to 0x35da..., but notes have no deployment authority. If the paying wallet differs from 0x35da..., IMD6900PerpPool and IMD6900PerpHook are born owned by that wallet: open(), removeLiquidity, setPerpEngine, setCurve, setFeeSink and every hook setter are onlyOwner, so the team could not open the pool and the launch would have to be redone (no funds enter the contracts until open, so nothing is lost, but the deployed launch is useless). If they are the same wallet the outcome is identical to the static address and the finding has no effect. Fix: write \"0x35da9c0303507ddf708e87f2568eddf12c47a059\" as the third constructorArgs entry.","line":9,"path":"launch.json","reproduction":"Read launch.json: contracts[0].constructorArgs == [\"0x8366a39CC670B4001A1121B8F6A443A643e40951\", \"10000\", \"$owner\", \"0x35da9c0303507ddf708e87f2568eddf12c47a059\"]. Brief: argument 3 is 0x35da9c0303507ddf708e87f2568eddf12c47a059. Expected per the recipe: the static address. Actual: $owner. With any resolver value R != 0x35da..., IMD6900PerpsLaunch's constructor runs p.transferOwnership(R) and the hook's _initializeOwner(R) (src/IMD6900PerpsLaunch.sol:39,48), so pool.owner() == hook.owner() == R and the team wallet's pool.open(...) reverts Ownable.Unauthorized.","severity":"medium","snippet":"        \"$owner\",","title":"launch.json writes $owner for the owner argument although the brief states the owner address; the fee address in the same file is static"},{"citation":"resolved","description":"Merged from audit_math (compound's zero-coin fast path) and audit_economics (no exit for idle ETH). The fee flow script/Owner.s.sol wire() sets up sends the pool ETH only (10% of the coin's creator fees); the hook skims no coin any more, so the NatSpec's second coin source (lines 194-195, 'the coin the hook skimmed from arbitrageurs') does not exist. compound() sizes liquidity from the contract's current balances; with tokensHave == 0, fromTokens == 0, _fullRangeLiquidity returns 0 and it reverts ZeroLiquidity before the _modify that would have collected the coin fees the position has already earned (PoolManager nets feesAccrued into the caller delta only inside modifyLiquidity). removeLiquidity transfers only the modify delta, addFees forwards only msg.value, and there is no sweep, so the ETH sits idle until someone runs any modify (dust addLiquidity, or the owner sending coin to the pool) first. Nothing is lost and the owner can always recover it with extra steps, hence low. Fix: in compound(), when one balance is 0 and the position has liquidity, collect first (a _modify(0) on a non-empty position is accepted by v4 and unlockCallback already takes both positive deltas) and size from the balances after collection; or add an owner-only sweep of idle balances.","line":206,"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 {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} from \"src/IMD6900PerpHook.sol\";\nimport {IMD6900PerpPool} from \"src/IMD6900PerpPool.sol\";\n\ncontract PCoin {\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n\n    function mint(address to, uint256 a) external {\n        balanceOf[to] += a;\n    }\n\n    function approve(address s, uint256 a) external returns (bool) {\n        allowance[msg.sender][s] = a;\n        return true;\n    }\n\n    function transfer(address to, uint256 a) external returns (bool) {\n        balanceOf[msg.sender] -= a;\n        balanceOf[to] += a;\n        return true;\n    }\n\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;\n        balanceOf[to] += a;\n        return true;\n    }\n}\n\n/// A Pons curve pinned wherever the test puts it\ncontract PCurve {\n    uint256 public q;\n    uint256 public t;\n    address public token;\n\n    constructor(address token_, uint256 q_, uint256 t_) {\n        token = token_;\n        q = q_;\n        t = t_;\n    }\n\n    function set(uint256 q_, uint256 t_) external {\n        q = q_;\n        t = t_;\n    }\n\n    function getReserves() external view returns (uint256, uint256) {\n        return (q, t);\n    }\n\n    function graduated() external pure returns (bool) {\n        return false;\n    }\n\n    function pairToken() external pure returns (address) {\n        return address(0);\n    }\n\n    function factory() external pure returns (address) {\n        return address(0);\n    }\n}\n\ncontract PTrader {\n    IPoolManager immutable pm;\n    PoolKey key;\n\n    constructor(IPoolManager pm_) {\n        pm = pm_;\n    }\n\n    function swap(PoolKey calldata k, bool buy, int256 amountSpecified) external {\n        key = k;\n        pm.unlock(abi.encode(buy, amountSpecified));\n    }\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(\n            key, SwapParams(buy, amt, buy ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1), \"\"\n        );\n        _settle(key.currency0, d.amount0());\n        _settle(key.currency1, d.amount1());\n        return \"\";\n    }\n\n    function _settle(Currency c, int128 v) internal {\n        if (v < 0) {\n            if (c.isAddressZero()) {\n                pm.settle{value: uint128(-v)}();\n            } else {\n                pm.sync(c);\n                PCoin(Currency.unwrap(c)).transfer(address(pm), uint128(-v));\n                pm.settle();\n            }\n        } else if (v > 0) {\n            pm.take(c, address(this), uint128(v));\n        }\n    }\n\n    receive() external payable {}\n}\n\n/// compound() with idle ETH, no idle coin, and coin fees the position has earned but not collected: it should pair\n/// the ETH with those fees (collecting them is what its own modify does); instead it reverts ZeroLiquidity because the\n/// zero-coin fast path runs before the collection.\ncontract ProofCompoundIdleEthTest is Test {\n    address constant OWNER = address(0xA11CE);\n\n    function test_CompoundPairsIdleEthWithTheFeesThePositionEarned() public {\n        IPoolManager PM = IPoolManager(address(new PoolManager(address(this))));\n        IMD6900PerpsLaunch l = new IMD6900PerpsLaunch(PM, 10_000, OWNER, OWNER);\n        IMD6900PerpHook hook = l.hook();\n        IMD6900PerpPool pool = l.pool();\n        PCoin coin = new PCoin();\n        PCurve curve = new PCurve(address(coin), 1 ether, 1e27); // 1e9 coin per ETH\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(_sqrt(1e9)) << 96, 2e27);\n        vm.stopPrank();\n        PoolKey memory key = pool.poolKey();\n        PTrader trader = new PTrader(PM);\n        vm.deal(address(trader), 100 ether);\n        coin.mint(address(trader), 1e28);\n        // trades both ways: the position earns its 1% in ETH and in coin\n        for (uint256 i; i < 6; ++i) {\n            trader.swap(key, true, -0.1 ether);\n            trader.swap(key, false, -9e25);\n        }\n        curve.set(1 ether, (hook.poolPriceX96() * 1e18) >> 96); // Pons at the pool's price\n        assertLe(hook.deviationBps(), 1);\n        vm.deal(address(pool), 1 ether); // the creator-fee split arrives as ETH only\n        assertEq(coin.balanceOf(address(pool)), 0);\n        uint128 before = pool.liquidity();\n        uint128 added = pool.compound(0, 0); // reverts ZeroLiquidity on the current code\n        assertGt(added, 0, \"the earned coin fees were paired with the idle ETH\");\n        assertGt(pool.liquidity(), before);\n    }\n\n    function _sqrt(uint256 x) internal pure returns (uint256 z) {\n        uint256 r = x;\n        uint256 y = (x + 1) / 2;\n        while (y < r) {\n            r = y;\n            y = (x / y + y) / 2;\n        }\n        z = r;\n    }\n\n    receive() external payable {}\n}","reproduction":"forge test --offline --match-path test/scratch/Proof_1ecb3406f302.t.sol (attached proof, fails ZeroLiquidity): launch (PM, 10000, OWNER, OWNER); OWNER opens at 1e9 coin/ETH with 2 ETH + 2e27 coin; 6 round trips (buy 0.1 ETH, sell 9e25 coin) accrue uncollected fees in both currencies; curve parked at the pool's price (deviation <= 1); vm.deal(pool, 1 ether); coin.balanceOf(pool) == 0; pool.compound(0, 0). Expected: idle ETH paired with the earned coin fees, liquidity grows. Actual: revert ZeroLiquidity() at line 207. test/scratch/Judge.t.sol test_IdleEthStranded: vm.deal(pool, 1 ether); compound(0,0) reverts ZeroLiquidity; owner removeLiquidity(all) leaves address(pool).balance == 1 ether with no entry point that moves it.","severity":"low","snippet":"        liq = _fullRangeLiquidity(sqrtPriceX96, ethHave, tokensHave);\n        if (liq == 0) revert ZeroLiquidity();","title":"The pool's idle ETH (its 10% creator-fee share) has no one-sided exit: compound() reverts ZeroLiquidity without idle coin, even when the position holds uncollected coin fees, and nothing else sends th"},{"citation":"resolved","description":"Merged from audit_math (info), audit_economics (low), audit_permissions (low) and audit_flow (info): one root cause. The hook's own rule (lines 90-95) is that it charges nothing itself, one rate applies to everyone with no per-caller exemptions, and feeBps / perpFeeBps 'must be kept equal to the pool's real fee'. The pool's fee is the immutable poolFeePips, charged on every swap. Three writers break the equality: feeFor() reports 0 for any feeExempt caller while the pool still takes 1%; setPerpFeeBps(bps) accepts any value in [0, 1000] (line 222), so feeFor(engine) can read 0 or 10x the real fee; lowerFee() (line 216: newFeeBps >= feeBps || newFeeBps < MIN_FEE_BPS) can lower feeBps for pool fees above 1% without changing what the pool charges, and at this launch's 10,000 pips feeBps == MIN_FEE_BPS == 100 so every argument is refused: the entry point is dead. The engine (not in this launch) reads these to model solvency; a model fed 0 under-counts the 1% each liquidation leg pays, which is how a 'solvent' liquidation ends in bad debt. Owner-only and no engine yet, so low. Fix without touching FLAGS, fees or arguments: derive feeFor from poolFeePips alone (drop the exemption from feeFor; keep feeExempt for tracksPons only), remove lowerFee and bound or remove setPerpFeeBps.","line":359,"path":"src/IMD6900PerpHook.sol","reproduction":"test/scratch/Judge.t.sol test_FeeModelDiverges (passes, i.e. the divergence is real): launch (PM, 10000, OWNER, OWNER), pool opened at 1e9 with 2 ETH + 2e27 coin. (1) as OWNER, hook.lowerFee(x) reverts InvalidFee for every x in 0..299 (the condition makes it every uint256). (2) OWNER: hook.setFeeExempt(trader, true); hook.feeFor(trader) == 0; trader swaps zeroForOne -0.1 ether; feeGrowthGlobal0 rises by 0.001 ether * 2^128 / liquidity (within 10 wei): the pool took its full 1%. (3) OWNER: setPerpEngine(0xE); setPerpFeeBps(0) -> feeFor(0xE) == 0; setPerpFeeBps(1000) -> feeFor(0xE) == 1000; hook.poolFeePips() still 10000. Expected per lines 94-95: feeFor(x) == 100 for every x.","severity":"low","snippet":"        if (feeExempt[sender]) return 0;","title":"The engine-facing fee model can diverge from the pool's immutable fee: feeFor() returns 0 for exempt callers, setPerpFeeBps accepts 0..1000, and lowerFee() is dead at this launch's 1%"},{"citation":"resolved","description":"_deviation computes |pool - ref| / ref with both prices in coin per ETH (poolPriceX96, ponsPriceX96). The README ('further than 30% from the coin's Pons price') and a perps mark are in the coin's own price (ETH per coin), the reciprocal. A pool at 0.7 * ref (deviation 3000, allowed) prices the coin at 1/0.7 = 1.4286x Pons; a pool at 1.3 * ref (deviation 3000, allowed) prices it at 1/1.3 = 0.769x. So the one economic guard this launch ships allows the mark to be pumped +42.9% but dumped only -23.1%, and a coin 30% below its Pons price (pool = 1.4286 * ref, deviation 4286) is already refused. No fund loss inside this launch (no engine yet), so low. Fix keeping FLAGS, fees and arguments: a symmetric measure, e.g. max(pool, ref) * BPS / min(pool, ref) - BPS, or compare in sqrt-price / tick space.","line":347,"path":"src/IMD6900PerpHook.sol","reproduction":"test/scratch/Judge.t.sol test_BandAsymmetry (passes): pool at 1e9 coin/ETH. curve.set(0.7 ether, 1e27) (ref = 1.4286e9, coin 42.86% above Pons in its own price): hook.deviationBps() == 3000, inside the band. curve.set(1.3 ether, 1e27) (coin 23.08% below Pons): deviationBps == 2307, inside. curve.set(1.4286 ether, 1e27) (coin exactly 30% below Pons in its own price): deviationBps == 4286 > 3000, refused. Expected: the same 30% distance in the coin's price on both sides.","severity":"low","snippet":"        uint256 d = pool_ > ref ? pool_ - ref : ref - pool_;\n        return (d * BPS) / ref;","title":"The 30% band is measured in coin-per-ETH, so it lets the coin trade 42.9% above its Pons price but only 23.1% below"},{"citation":"resolved","description":"compound() and addLiquidity() refuse to pair funds further than COMPOUND_MAX_DEV_BPS (1%) from a readable Pons price. open() pairs the largest amounts of the pool's life (the launch refund, ~0.83 ETH, plus the matching coin) at a price script/Owner.s.sol computed from getReserves() at simulation time and checks nothing on-chain, although after initialize the hook's poolId and the pool's curve are both set and _requireOnPons() could run. Any Pons trade between the script's read and inclusion leaves the pool open off-Pons; the band allows any trade toward Pons, so an arbitrageur trades the pool to the Pons price and keeps the gap, paid by the owner's position. Owner's own funds, bounded by the opening amounts, hence low. Fix: after initialize, revert OffPons when a readable reference is further than COMPOUND_MAX_DEV_BPS (or a caller-supplied tolerance) from sqrtPriceX96; no change to the constructor, FLAGS or fees.","line":167,"path":"src/IMD6900PerpPool.sol","reproduction":"test/scratch/Judge.t.sol test_OpenAtStalePrice (passes): Pons curve at (1 ether, 1.25e27) = 1.25e9 coin/ETH; OWNER calls open{value: 2 ether}(coin, curve, sqrt(1e9)<<96, 2e27) (the price Pons showed when the script was simulated). Actual: open succeeds, hook.deviationBps() == 2000. A trader sells 238496258048144227359443901 coin oneForZero (allowed: toward Pons), receives 211189549225169335 wei; rebuying that coin at Pons costs 190797006438515381 wei; profit 20392542786653954 wei (0.0204 ETH) out of the owner's position; deviation afterwards 0. Expected: open reverts OffPons at 20% from a readable reference, as compound/addLiquidity would.","severity":"low","snippet":"        poolManager.initialize(poolKey(), sqrtPriceX96);","title":"open() initializes and pairs the opening funds at any price with no on-Pons bound, so a stale or front-run opening price is arbitraged out of the owner's position"},{"citation":"resolved","description":"Both contracts override renounceOwnership to revert NoRenounce because only the owner can take the liquidity out. The inherited one-step transferOwnership(newOwner) accepts any non-zero address and completeOwnershipHandover accepts any address that once requested; after either to a dead or wrong address nobody can call removeLiquidity, setPerpEngine, setCurve or any hook setter. Same on IMD6900PerpHook (src/IMD6900PerpHook.sol:247-249). It takes an owner mistake (the brief plans a transfer to the Robinhood timelock), so it is recorded as information for the runbook rather than a defect: prefer the two-step handover (the timelock calls requestOwnershipHandover, the owner completes it) for the planned transfer, or override transferOwnership to require the handover.","line":131,"path":"src/IMD6900PerpPool.sol","reproduction":"test/scratch/Judge.t.sol test_TransferToDeadStrands (passes): pool opened with 2 ETH + 2e27 coin. OWNER: pool.renounceOwnership() reverts NoRenounce. OWNER: pool.transferOwnership(0x000000000000000000000000000000000000dEaD) succeeds, pool.owner() == 0xdEaD. OWNER: pool.removeLiquidity(liquidity, OWNER, OWNER) reverts Ownable.Unauthorized; no key can ever call it.","severity":"info","snippet":"    function renounceOwnership() public payable override onlyOwner {","title":"NoRenounce guards one of three equivalent paths: transferOwnership / completeOwnershipHandover to a dead address strands the liquidity the same way on both contracts"},{"citation":"resolved","description":"None of these is a bypass; each is behind onlyOwner and is the intended role (owner, then the Robinhood timelock). (1) updateFeeAddress / renounceFeeAddress (the owner, not only the fee address) redirect the 3% of ETH already in the hook: flush() reads feeAddress at call time. For this launch owner == feeAddress. (2) setFeeExempt(x, true) removes the Pons band for x (tracksPons false) and feeFor(x) reads 0 while the pool still charges 1% (finding 4). (3) setCurve repoints the reference the band, addLiquidity and compound are judged against, to any contract. (4) setSweepFailOpen(true) lets trades run with no pre-trade liquidation sweep. (5) pool.setPerpEngine(0) switches off the only guard on removeLiquidity while hook.perpEngine may still have open positions (the brief asks for this). (6) hook.setPerpEngine to an address without code makes every non-engine swap revert SweepUnavailable (engine.call returns ok with empty data, out.length < 32) until the owner fixes it or sets fail-open. (7) The hook and the pool are two separate Ownables; nothing on-chain keeps them under one owner.","line":256,"path":"src/IMD6900PerpHook.sol","reproduction":"(1) vm.deal(hook, 1 ether); OWNER: hook.updateFeeAddress(0xB0B); hook.flush() sends 0.03 ETH to 0xB0B and 0.97 ETH to the pool (test/AuditFixes.t.sol test_Fix_NoZeroFeeAddressAtConstruction shows the split). (2) test/scratch/Judge.t.sol test_FeeModelDiverges: after setFeeExempt(trader, true) a 0.1 ETH swap runs untracked and still pays 0.001 ETH to the position. (6) OWNER: hook.setPerpEngine(0xE) (no code); any trader swap reverts SweepUnavailable (test/PerpsFailurePaths.t.sol covers the unavailable-engine path). Expected and actual agree: designed powers.","severity":"info","snippet":"    function updateFeeAddress(address feeAddress_) external onlyOwner {","title":"Owner powers to record as trust assumptions: fee-address redirection before flush, band and model exemptions, curve repointing, fail-open sweep, engine switch-off, an engine without code blocks tradin"},{"citation":"resolved","description":"The README's migration path is to take all the liquidity out and open a new pool. removeLiquidity asks the engine whether any position is liquidatable after the removal at the current price, but a v4 swap against zero liquidity moves sqrtPriceX96 to the caller's price limit for zero amounts, and the band only requires the end price within maxDeviationBps (30%) of Pons. The pool's price is the mark a future engine reads. No engine in this launch and the owner's own action is the precondition, so information for the runbook: unwire the engine and close positions before emptying the pool, or refuse to empty the pool while the engine reports open positions.","line":144,"path":"src/IMD6900PerpPool.sol","reproduction":"test/scratch/Judge.t.sol test_ZeroLiquidityFreeMove (passes): pool at 1e9 with Pons at the same price; OWNER removeLiquidity(all) -> liquidity 0; a trader swaps zeroForOne amountSpecified -1 with sqrtPriceLimitX96 = sqrt(0.75 * ponsPriceX96 << 96): ETH spent 0, poolPriceX96 79224271143834286986338705831335297024 -> 59421121885698253195157962751999977299, hook.deviationBps() 0 -> 2500, accepted. The same swap with the limit at MIN_SQRT_PRICE+1 reverts TooFarFromPons (0x84364199): the band bounds the free move at 30%.","severity":"info","snippet":"                if (IPerpBookView(engine).isLiquidatable(ids[i])) revert WouldLiquidate(ids[i]);","title":"After the owner removes all liquidity, anyone moves the pool price for free up to the band's edge; the engine check in removeLiquidity is answered at the pre-move price"},{"citation":"resolved","description":"flush() forwards 97% of the hook's balance into pool.addFees(), which forwards msg.value into IFeeSink(feeSink).addFees(). setFeeSink only checks for code. With a sink that lacks addFees() (or reverts) the hook's ETH stays in the hook until the owner calls setFeeSink(0) or another sink; recoverable, no loss. script/Owner.s.sol wire() sets the sink to the Pons launcher 0xE0aBb21F15766BE162429f46F494617eB5EF6Ba0, whose IPonsLaunch interface in the script and the fork test lists no addFees(); no offline test and no fork test calls flush with that sink set, so whether the launcher accepts addFees() is unverified by this review (no Robinhood RPC offline). Suggest one flush in the fork flow after setFeeSink, or a try addFees{value: 0}() probe in setFeeSink.","line":96,"path":"src/IMD6900PerpPool.sol","reproduction":"test/scratch/Judge.t.sol test_FlushStuckOnBadSink (passes): vm.deal(hook, 1 ether); OWNER: pool.setFeeSink(address(curve)) (has code, no addFees) accepted; hook.flush() reverts; OWNER: pool.setFeeSink(0); hook.flush() succeeds, pool balance 0.97 ether, 0.03 ether to the fee address.","severity":"info","snippet":"        if (feeSink_ != address(0) && feeSink_.code.length == 0) revert NotAContract(); // a sink must take addFees()","title":"setFeeSink accepts any contract; a sink without addFees() makes flush() revert until the owner re-points it, and the planned Pons launcher sink is never exercised"}],"hash":"b9d99efa8b2cb4a086da4d165376772e43f57007abc48bac33a9bd0fa8f43f62","nodeId":"a6bd2778-d5a7-4d32-b9af-d8fddadb0ea3","outcome":"completed","summary":"The review is complete. The findings file is at `.imd-findings.json` in the repository root, no tracked file was changed, and the scratch tests that back each reproduction live under `test/scratch/`.\n\n**Outcome.** Ten findings survived reproduction: two medium, four low, four info. No critical or high finding, so no loss-of-funds path was found in the hook, the pool or the launch. The existing suite passes (60 tests, the fork test skipped offline).\n\n**What I kept, merged and recalibrated:**\n\n- **Medium, salt mining can exceed the one-transaction cap.** The specialist's proof fails as stated (132,475 tries, 21.2M gas at factory salt 50149). My own 24-address fit gives 129 gas per try on a 3.79M baseline, so the 2^24 cap allows about 100k tries and roughly one launch address in 400 overruns it. The launch address is fixed by IMD's factory and launch number, neither of which is in the tree, so I could not settle whether this launch's address is one of them. The proof is attached. The audit_flow claim that `test_FitsOneTransaction` measured a cheatcode is dropped: this commit already uses a raw CREATE and asserts a metered cost.\n- **Medium, launch.json writes `$owner`** where the brief states the owner address, while the fee address in the same file is static. If the paying wallet differs, both contracts are born owned by the wrong wallet and the launch must be redone.\n- **Low, idle ETH has no one-sided exit** (audit_math and audit_economics merged, proof attached and failing).\n- **Low, engine-facing fee model diverges from the immutable pool fee** (four specialists' reports merged into one root cause: exempt callers read 0, `setPerpFeeBps` accepts 0 to 1000, `lowerFee` is dead at 1%).\n- **Low, band asymmetry** (+42.9% / -23.1% in the coin's own price) and **low, `open()` has no on-Pons bound** (stale price arbitraged for 0.0204 ETH in the reproduction).\n- **Info**: transfer to a dead address strands liquidity like renounce would, the owner-power trust assumptions, free price moves on an emptied pool bounded by the band, and a fee sink without `addFees()` blocking `flush()` until re-pointed. The Pons launcher's `addFees()` remains unverified offline.\n\n**Coverage.** All 41 entry points are answered, 30 as holds and 11 as findings, plus a row for the launch constructor. Static-analysis lines were checked and none added a finding beyond these.","treeHash":null,"usage":{"cachedInputTokens":1510622,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":40799,"runtime":"claude","turns":27,"wallClockMs":796868}},{"artifacts":[],"attempt":1,"bundleHash":"fde6e8784583012d4031aed793b94c6fb6ff48f867d78cc2dc326f6295b230d9","device":"e8f1969b1b8ff9d4","findings":[],"hash":"c4bdf2375ac44b1bde35a733757e940e80600349630ad4a08d320e8f93f90aa5","nodeId":"e9199b71-c874-4529-b8d4-db6ec1bdac16","outcome":"completed","summary":"Added failure-path, fuzz, and stateful invariant tests, with a [41-entry-point coverage inventory](/home/imd/.identitymd/work/e63e94be-2e3d-4e5a-bf03-30d4c4c3cf03/e9199b71-c874-4529-b8d4-db6ec1bdac16/test/TEST_COVERAGE.md).\n\n- `forge build` passed.\n- `forge test`: **96 passed, 0 failed, 2 skipped**.\n- Skips require Robinhood RPC and actual-factory inputs.\n- Only `test/` changed; accepted files remain unchanged.\n- No new implementation defects reproduced.","treeHash":"7fb88790c4efbe23f8cffa06edef6e4c73b05e06","usage":{"cachedInputTokens":2906752,"inputTokens":138849,"model":"gpt-6-astra","outputTokens":36816,"runtime":"codex","turns":8,"wallClockMs":751065}},{"artifacts":[],"attempt":1,"bundleHash":"8c3392eefbaa03f5b1b96bf0d31174685b884330b4068d70a153cbf49da67242","device":"6ea221d017084baf","findings":[],"hash":"f984620e9a02a6d3f2c2081e24e692bb2388ef94eb7d38ea56b1cff8e1dfcf2b","nodeId":"4d09efb7-dd04-4939-915f-4eed773f1126","outcome":"completed","summary":"Fixed `launch.json` to use the specified static owner address and updated its notes. Recorded the finding as fixed in `.imd-responses.json`.\n\nValidation passed: schema and constructor ABI checks, offline `forge build`, and `forge test`—55 passed, 0 failed, 2 skipped for missing RPC/factory inputs. Accepted source and configuration remain unchanged.","treeHash":"f1553db02fb32112e09f72801d97f4f08b4ae8ff","usage":{"cachedInputTokens":424832,"inputTokens":48342,"model":"gpt-6-astra","outputTokens":4505,"runtime":"codex","turns":4,"wallClockMs":130886}}],"verification":[{"checks":[{"durationMs":18894,"exitCode":0,"name":"build","output":"Compiling 84 files with Solc 0.8.26\nSolc 0.8.26 finished in 18.75s\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:268:9\n    │\n268 │         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:95:25\n   │\n95 │     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: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:98:9\n   │\n98 │         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:112:22\n    │\n112 │     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:115:9\n    │\n115 │         emit HookSet(hook_);\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:218:9\n    │\n218 │         emit FeeLowered(newFeeBps);\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:118:28\n    │\n118 │     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:120:9\n    │\n120 │         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:224:9\n    │\n224 │         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:127:9\n    │\n127 │         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:229:28\n    │\n229 │     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:231:9\n    │\n231 │         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:147:9\n    │\n147 │         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:236:9\n    │\n236 │         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:243:9\n    │\n243 │         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:253:9\n    │\n253 │         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:260:9\n    │\n260 │         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:137:95\n    │\n137 │     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:144:21\n    │\n144 │                 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:149:9\n    │\n149 │         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:144:67\n    │\n144 │                 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[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/IMD6900PerpPool.sol:159:9\n    │\n159 │         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:173:9\n    │\n173 │         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:167:9\n    │\n167 │         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:189:32\n    │\n189 │         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:191:9\n    │\n191 │         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:209:9\n    │\n209 │         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:222:9\n    │\n222 │         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:223:9\n    │\n223 │         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:269:9\n    │\n269 │         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:280:32\n    │\n280 │         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:281:9\n    │\n281 │         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:242:21\n    │\n242 │         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:242:47\n    │\n242 │         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:242:55\n    │\n242 │         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:243:73\n    │\n243 │         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:243:81\n    │\n243 │         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:246:71\n    │\n246 │             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:246:79\n    │\n246 │             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:247:13\n    │\n247 │             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:249:60\n    │\n249 │             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:249:68\n    │\n249 │             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:473:43\n    │\n473 │               (bool ok, bytes memory out) = engine.call{gas: g - reserve}(\n    │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n474 │ ┃                 abi.encodeCall(IPerpSweep.sweepLiquidations, (tx.origin, spec, isBuy, limit))\n475 │ ┃             );\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:285:60\n    │\n285 │         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:289:24\n    │\n289 │         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:289:32\n    │\n289 │         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:289:47\n    │\n289 │         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:289:55\n    │\n289 │         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":121,"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 456.25µs (0.00ns CPU time)\n\nRan 7 tests for test/IMD6900Perps.t.sol:IMD6900PerpsTest\n[PASS] test_AnyoneDeepensIt() (gas: 295313)\n[PASS] test_OnlyTheOwnerOpensAndRemoves() (gas: 231709)\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 6.07ms (1.29ms CPU time)\n\nRan 12 tests for test/AuditFixes.t.sol:AuditFixesTest\n[PASS] test_Fix_AddLiquidityOnlyAtThePonsPrice() (gas: 1172804)\n[PASS] test_Fix_AdminGuards() (gas: 213648)\n[PASS] test_Fix_AnAdditionLeavesTheEarnedFeesInThePool() (gas: 2107876)\n[PASS] test_Fix_CompoundOnlyAtThePonsPrice() (gas: 400464)\n[PASS] test_Fix_CompoundRefusesWithoutAReference() (gas: 1674319)\n[PASS] test_Fix_FeeBpsFollowsThePoolFee() (gas: 124367)\n[PASS] test_Fix_LowGasSweepFailsClosed() (gas: 580066)\n[PASS] test_Fix_NoCrossingThroughPons() (gas: 469108)\n[PASS] test_Fix_NoZeroFeeAddressAtConstruction() (gas: 6331)\n[PASS] test_Fix_UnreadablePonsLeavesTradingOpen() (gas: 449016)\n[PASS] test_OwnableEntryPointsOnBoth() (gas: 842089)\n[PASS] test_OwnerAlwaysTakesTheLiquidityBack() (gas: 508483)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 9.23ms (18.78ms CPU time)\n\nRan 5 tests for test/IMD6900PerpsLaunch.t.sol:IMD6900PerpsLaunchTest\n[PASS] test_DeploysOnAFreshChain() (gas: 74730)\n[PASS] test_EachDeployerMinesItsOwnSalt() (gas: 21426)\n[PASS] test_FitsOneTransaction() (gas: 6932)\nLogs:\n  launch gas (the mining included): 3760\n\n[PASS] test_PassesTheAdmissionScan() (gas: 7089506)\n[PASS] test_RefusesADynamicFee() (gas: 4460)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 15.84ms (34.79ms CPU time)\n\nRan 4 test suites in 16.47ms (31.60ms CPU time): 24 tests passed, 0 failed, 1 skipped (25 total tests)\n","passed":true},{"durationMs":64,"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.lowerFee(uint256)\",\"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.transferOwnership(address)\",\"IMD6900PerpPool.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"ADAPTATION.md\":104,\"README.md\":120,\"foundry.toml\":25,\"script/Owner.s.sol\":94,\"src/IMD6900PerpHook.sol\":498,\"src/IMD6900PerpPool.sol\":291,\"src/IMD6900PerpsLaunch.sol\":76,\"test/AuditFixes.t.sol\":431,\"test/IMD6900Perps.t.sol\":158,\"test/IMD6900PerpsLaunch.t.sol\":71,\"test/RobinhoodFlow.fork.t.sol\":303},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":3814,"exitCode":0,"name":"slither","output":"[high/medium] arbitrary-send-eth at src/IMD6900PerpHook.sol:275: IMD6900PerpHook.flush() (src/IMD6900PerpHook.sol#275-282) sends eth to arbitrary user\n[medium/high] incorrect-equality at src/IMD6900PerpPool.sol:276: IMD6900PerpPool._fullRangeLiquidity(uint160,uint256,uint256) (src/IMD6900PerpPool.sol#276-286) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900PerpPool.sol:199: IMD6900PerpPool.compound(uint256,uint256) (src/IMD6900PerpPool.sol#199-210) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900PerpPool.sol:155: IMD6900PerpPool.open(address,address,uint160,uint256) (src/IMD6900PerpPool.sol#155-174) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900PerpHook.sol:275: IMD6900PerpHook.flush() (src/IMD6900PerpHook.sol#275-282) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900PerpPool.sol:181: IMD6900PerpPool.addLiquidity(uint256,address) (src/IMD6900PerpPool.sol#181-192) uses a dangerous strict equality:\n[medium/medium] unused-return at src/IMD6900PerpHook.sol:333: IMD6900PerpHook.poolPriceX96() (src/IMD6900PerpHook.sol#333-337) ignores return value by (sq,None,None,None) = poolManager.getSlot0(poolId) (src/IMD6900PerpHook.sol#334)\n[medium/medium] unused-return at src/IMD6900PerpPool.sol:199: IMD6900PerpPool.compound(uint256,uint256) (src/IMD6900PerpPool.sol#199-210) ignores return value by (sqrtPriceX96,None,None,None) = poolManager.getSlot0(poolKey().toId()) (src/IMD6900PerpPool.sol#205)\n[medium/medium] unused-return at src/IMD6900PerpPool.sol:181: IMD6900PerpPool.addLiquidity(uint256,address) (src/IMD6900PerpPool.sol#181-192) ignores return value by (sqrtPriceX96,None,None,None) = poolManager.getSlot0(poolKey().toId()) (src/IMD6900PerpPool.sol#185)\n[medium/medium] unused-return at src/IMD6900PerpPool.sol:155: IMD6900PerpPool.open(address,address,uint160,uint256) (src/IMD6900PerpPool.sol#155-174) ignores return value by poolManager.initialize(poolKey(),sqrtPriceX96) (src/IMD6900PerpPool.sol#167)\n[medium/medium] unused-return at src/IMD6900PerpPool.sol:228: IMD6900PerpPool.unlockCallback(bytes) (src/IMD6900PerpPool.sol#228-255) ignores return value by poolManager.settle() (src/IMD6900PerpPool.sol#247)\n[medium/medium] unused-return at src/IMD6900PerpHook.sol:312: IMD6900PerpHook._graduatedPriceX96(address) (src/IMD6900PerpHook.sol#312-330) ignores return value by (sq,None,None,None) = poolManager.getSlot0(key.toId()) (src/IMD6900PerpHook.sol#322)\n[medium/medium] unused-return at src/IMD6900PerpPool.sol:228: IMD6900PerpPool.unlockCallback(bytes) (src/IMD6900PerpPool.sol#228-255) ignores return value by poolManager.settle{value: uint256(uint128(- a0))}() (src/IMD6900PerpPool.sol#242)\n[low/medium] missing-zero-check at src/IMD6900PerpHook.sol:229: IMD6900PerpHook.setPerpEngine(address).engine (src/IMD6900PerpHook.sol#229) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900PerpPool.sol:118: IMD6900PerpPool.setPerpEngine(address).engine (src/IMD6900PerpPool.sol#118) lacks a zero-check on :\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:112: IMD6900PerpPool.setHook(address).hook_ (src/IMD6900PerpPool.sol#112) lacks a zero-check on :\n[low/medium] calls-loop at src/IMD6900PerpPool.sol:137: IMD6900PerpPool.removeLiquidity(uint128,address,address) (src/IMD6900PerpPool.sol#137-150) has external calls inside a loop: IPerpBookView(engine).isLiquidatable(ids[i]) (src/IMD6900PerpPool.sol#144)\n[low/medium] reentrancy-events at src/IMD6900PerpPool.sol:219: Reentrancy in IMD6900PerpPool.addFees() (src/IMD6900PerpPool.sol#219-224):\n[low/medium] reentrancy-events at src/IMD6900PerpHook.sol:275: Reentrancy in IMD6900PerpHook.flush() (src/IMD6900PerpHook.sol#275-282):\n[low/medium] return-bomb at src/IMD6900PerpHook.sol:467: IMD6900PerpHook._sweep(address,int256,bool,uint160) (src/IMD6900PerpHook.sol#467-494) tries to limit the gas of an external call that controls implicit decoding","passed":true},{"durationMs":538,"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 (17 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:186: Literal Instead of Constant (6 places)\n[low] non-reentrant-not-first at src/IMD6900PerpPool.sol:137: `nonReentrant` is Not the First Modifier (2 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:143: Loop Contains `require`/`revert`\n[low] state-no-address-check at src/IMD6900PerpHook.sol:230: Address State Variable Set Without Checks (3 places)\n[low] unchecked-return at src/IMD6900PerpPool.sol:167: 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:340: 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":"2ab7e1ac5c291aa5cb9c28b3d847c4381803df7dc98b89269fc953cd0ca1d59d","verifiedTreeHash":"e9cddb18b78752c197ff7b31be62c23611a64a2b","verifierVersion":"0.1.0+3f1208aa"},{"checks":[{"durationMs":40792,"exitCode":0,"name":"build","output":"Compiling 89 files with Solc 0.8.26\nSolc 0.8.26 finished in 40.64s\nCompiler run successful!\nwarning[missing-events-access-control]: `feeAddress` is changed without an event but is used for access control\n    ╭▸ src/IMD6900PerpHook.sol:268:9\n    │\n268 │         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:95:25\n   │\n95 │     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:98:9\n   │\n98 │         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:112:22\n    │\n112 │     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:115:9\n    │\n115 │         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:118:28\n    │\n118 │     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:120:9\n    │\n120 │         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:218:9\n    │\n218 │         emit FeeLowered(newFeeBps);\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:127:9\n    │\n127 │         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:224:9\n    │\n224 │         emit PerpFeeSet(bps);\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:229:28\n    │\n229 │     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:231:9\n    │\n231 │         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:236:9\n    │\n236 │         emit SweepFailOpenSet(on);\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:147:9\n    │\n147 │         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:243:9\n    │\n243 │         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:253:9\n    │\n253 │         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:260:9\n    │\n260 │         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:137:95\n    │\n137 │     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:144:21\n    │\n144 │                 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:149:9\n    │\n149 │         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:144:67\n    │\n144 │                 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[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/IMD6900PerpPool.sol:159:9\n    │\n159 │         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:173:9\n    │\n173 │         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:167:9\n    │\n167 │         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:189:32\n    │\n189 │         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:191:9\n    │\n191 │         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:269:9\n    │\n269 │         emit FeeAddressRenounced(msg.sender);\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:209:9\n    │\n209 │         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/IMD6900PerpHook.sol:280:32\n    │\n280 │         if (toFeeAddress != 0) SafeTransferLib.safeTransferETH(feeAddress, toFeeAddress);\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/IMD6900PerpPool.sol:222:9\n    │\n222 │         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:223:9\n    │\n223 │         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:281:9\n    │\n281 │         emit FeesFlushed(amount - toFeeAddress, toFeeAddress);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\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[unused-return]: return value of an external call is not used\n    ╭▸ src/IMD6900PerpPool.sol:242:21\n    │\n242 │         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:242:47\n    │\n242 │         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:242:55\n    │\n242 │         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:243:73\n    │\n243 │         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:243:81\n    │\n243 │         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:246:71\n    │\n246 │             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:246:79\n    │\n246 │             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:247:13\n    │\n247 │             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:249:60\n    │\n249 │             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:249:68\n    │\n249 │             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:473:43\n    │\n473 │               (bool ok, bytes memory out) = engine.call{gas: g - reserve}(\n    │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n474 │ ┃                 abi.encodeCall(IPerpSweep.sweepLiquidations, (tx.origin, spec, isBuy, limit))\n475 │ ┃             );\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:285:60\n    │\n285 │         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:289:24\n    │\n289 │         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:289:32\n    │\n289 │         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:289:47\n    │\n289 │         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:289:55\n    │\n289 │         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":4207,"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 429.41µs (0.00ns CPU time)\n\nRan 7 tests for test/IMD6900Perps.t.sol:IMD6900PerpsTest\n[PASS] test_AnyoneDeepensIt() (gas: 295313)\n[PASS] test_OnlyTheOwnerOpensAndRemoves() (gas: 231709)\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 6.31ms (1.31ms CPU time)\n\nRan 12 tests for test/AuditFixes.t.sol:AuditFixesTest\n[PASS] test_Fix_AddLiquidityOnlyAtThePonsPrice() (gas: 1172804)\n[PASS] test_Fix_AdminGuards() (gas: 213648)\n[PASS] test_Fix_AnAdditionLeavesTheEarnedFeesInThePool() (gas: 2107876)\n[PASS] test_Fix_CompoundOnlyAtThePonsPrice() (gas: 400464)\n[PASS] test_Fix_CompoundRefusesWithoutAReference() (gas: 1674319)\n[PASS] test_Fix_FeeBpsFollowsThePoolFee() (gas: 124367)\n[PASS] test_Fix_LowGasSweepFailsClosed() (gas: 580066)\n[PASS] test_Fix_NoCrossingThroughPons() (gas: 469108)\n[PASS] test_Fix_NoZeroFeeAddressAtConstruction() (gas: 6331)\n[PASS] test_Fix_UnreadablePonsLeavesTradingOpen() (gas: 449016)\n[PASS] test_OwnableEntryPointsOnBoth() (gas: 842089)\n[PASS] test_OwnerAlwaysTakesTheLiquidityBack() (gas: 508483)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 26.10ms (23.38ms CPU time)\n\nRan 5 tests for test/PerpsFailurePaths.t.sol:PerpsFeeForwardingTest\n[PASS] testFuzz_FlushConservesFundsAndCannotPayTwice(uint128) (runs: 1000, μ: 107905, ~: 110946)\n[PASS] test_FeeAddressCanRenouncePermanentlyWithoutRenouncingOwnership() (gas: 170190)\n[PASS] test_FeeSinkCanBeEnabledAndDisabledForSubsequentPayments() (gas: 375984)\n[PASS] test_FlushRoundingEdges() (gas: 363474)\n[PASS] test_RejectingSinkOrFeeRecipientRollsBackBothPayments() (gas: 711483)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 29.63ms (26.29ms CPU time)\n\nRan 8 tests for test/PerpsLifecycle.t.sol:PerpsLifecycleTest\n[PASS] testFuzz_ReferencePriceScalesAndZeroReservesDisableIt(uint96,uint96) (runs: 1000, μ: 83356, ~: 83553)\n[PASS] test_BandExemptionDoesNotChangeTheStaticPoolFee() (gas: 454107)\n[PASS] test_ExactOutputBothWaysPaysTheStaticFeeWithoutHookDeltas() (gas: 373601)\n[PASS] test_GraduatedReferenceUsesRealPoolPriceAndCurrencyOrientation() (gas: 1147370)\n[PASS] test_HandoverDeadlineIsolationAndReplayOnBothContracts() (gas: 661177)\n[PASS] test_OpeningFailuresLeaveNoTokenCurveOrPoolId() (gas: 915710)\n[PASS] test_PricesAtOrOutsideFullRangeCannotOpen() (gas: 954479)\n[PASS] test_UnadvertisedCallbacksAreNotImplementedEvenForTheManager() (gas: 151128)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 41.61ms (58.45ms CPU time)\n\nRan 5 tests for test/IMD6900PerpsLaunch.t.sol:IMD6900PerpsLaunchTest\n[PASS] test_DeploysOnAFreshChain() (gas: 74730)\n[PASS] test_EachDeployerMinesItsOwnSalt() (gas: 21426)\n[PASS] test_FitsOneTransaction() (gas: 4633834)\nLogs:\n  launch gas (the mining included): 4607358\n\n[PASS] test_PassesTheAdmissionScan() (gas: 7089538)\n[PASS] test_RefusesADynamicFee() (gas: 4460)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 45.50ms (41.20ms CPU time)\n\nRan 16 tests for test/PerpsFailurePaths.t.sol:PerpsFailurePathsTest\n[PASS] testFuzz_AddRemoveRoundTripCannotMintValue(uint256,uint256) (runs: 1000, μ: 408193, ~: 409676)\nLogs:\n  Bound result 9318790136673839791\n  Bound result 7325947010911097396\n\n[PASS] test_AllAdministrativeEntrypointsRejectStrangers() (gas: 598430)\n[PASS] test_BandAndModeledFeeBounds() (gas: 304337)\n[PASS] test_ClosedAndAlreadyOpenPoolGuards() (gas: 225757)\n[PASS] test_CompoundMinimumsAndRefundRecipientAreEnforced() (gas: 605001)\n[PASS] test_LiquidityCallbacksCheckSenderAndBothRangeEnds() (gas: 231296)\n[PASS] test_LowGasCannotSkipAnOpenBookEvenWithFailOpen() (gas: 726204)\n[PASS] test_OnlyManagerMayInvokeEveryHookCallback() (gas: 322029)\n[PASS] test_OverWithdrawalAndMaximumUint128CannotChangeThePosition() (gas: 164601)\n[PASS] test_ReinitializingAndSwappingAnotherPoolAreRejected() (gas: 101234)\n[PASS] test_RejectingWithdrawalRecipientRollsBackPositionAndCanRetry() (gas: 478566)\n[PASS] test_SweepArgumentsAndBestEffortPostSweepAgainstRealManager() (gas: 1023270)\n[PASS] test_SweepStatusAndFailOpenHaveDistinctMeanings() (gas: 1435388)\n[PASS] test_TwoWeiRoundTripsDoNotAccumulateProfit() (gas: 7603822)\nLogs:\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 2\n\n[PASS] test_WithdrawalHealthUsesTheFinalDepthAndRollsBack() (gas: 969423)\n[PASS] test_ZeroAndOneWeiLiquidityCannotConsumeFunds() (gas: 277416)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 50.86ms (68.15ms CPU time)\n\nRan 5 tests for test/LaunchAdmission.t.sol:LaunchAdmissionTest\n[PASS] testFuzz_Create2PredictionsUseTheActualLaunchAddress(bytes32) (runs: 64, μ: 7868376, ~: 7024910)\n[PASS] test_DeploysOnAFreshChainWithExactArguments() (gas: 8690526)\n[PASS] test_FitsOneTransactionWithMeteredCreate2() (gas: 8638063)\nLogs:\n  metered constructor and factory gas: 7125397\n\n[PASS] test_InvalidConstructorArgumentsAreAtomic() (gas: 30261463)\n[PASS] test_PassesAdmissionSizeLimitsAndScanWithExactArguments() (gas: 20973902)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 447.27ms (579.15ms CPU time)\n\nRan 2 tests for test/PerpsInvariants.t.sol:PerpsInvariantsTest\n[PASS]\nPerpsInvariantsTest invariants:\n[PASS] invariant_AssetsMatchIndependentGhostAccounting\n[PASS] invariant_CurrentOwnerCanAlwaysExitAllLiquidity\n[PASS] invariant_OwnershipAndFullRangePositionStayConsistent\n PerpsInvariantsTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭--------------+------------------------+-------+---------+----------╮\n| Contract     | Selector               | Calls | Reverts | Discards |\n+====================================================================+\n| PerpsHandler | add                    | 1447  | 0       | 0        |\n|--------------+------------------------+-------+---------+----------|\n| PerpsHandler | addFees                | 1519  | 0       | 0        |\n|--------------+------------------------+-------+---------+----------|\n| PerpsHandler | changeOwner            | 1518  | 0       | 0        |\n|--------------+------------------------+-------+---------+----------|\n| PerpsHandler | compound               | 1491  | 0       | 0        |\n|--------------+------------------------+-------+---------+----------|\n| PerpsHandler | donate                 | 1476  | 0       | 0        |\n|--------------+------------------------+-------+---------+----------|\n| PerpsHandler | flush                  | 1484  | 0       | 0        |\n|--------------+------------------------+-------+---------+----------|\n| PerpsHandler | moveReference          | 1477  | 0       | 0        |\n|--------------+------------------------+-------+---------+----------|\n| PerpsHandler | remove                 | 1530  | 0       | 0        |\n|--------------+------------------------+-------+---------+----------|\n| PerpsHandler | routeFees              | 1481  | 0       | 0        |\n|--------------+------------------------+-------+---------+----------|\n| PerpsHandler | swap                   | 1485  | 0       | 0        |\n|--------------+------------------------+-------+---------+----------|\n| PerpsHandler | unauthorizedWithdrawal | 1476  | 0       | 0        |\n╰--------------+------------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 5902\n  Bound result 3784\n  Bound result 20697442978000111\n  Bound result 514782465507\n  Bound result 7400\n  Bound result 4065\n  Bound result 18\n  Bound result 999999999999999011\n  Bound result 1386\n  Bound result 5498\n  Bound result 4562\n  Bound result 1000000000000000\n  Bound result 12083\n  Bound result 708286521556175862\n  Bound result 860617574716407193\n  Bound result 244\n  Bound result 999999999999999256\n  Bound result 10842\n  Bound result 11210\n  Bound result 99900\n  Bound result 221786128688432899\n  Bound result 14999\n  Bound result 3964052489\n  Bound result 3000\n  Bound result 13397\n  Bound result 12316\n  Bound result 6428\n  Bound result 406092224601213896\n  Bound result 999999999999999997\n  Bound result 2322\n  Bound result 3552062333\n  Bound result 9771\n  Bound result 83813203721682184\n  Bound result 7972\n  Bound result 4562\n  Bound result 18014398509481985\n  Bound result 158\n  Bound result 1678\n  Bound result 999999999999999010\n  Bound result 400000\n  Bound result 887220\n  Bound result 24576\n  Bound result 11327\n  Bound result 10091\n\n[PASS] test_HandlerExercisesValueFlowsAndCanReenterAfterFullExit() (gas: 2358425)\nLogs:\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 10000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 0\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 4.09s (4.08s CPU time)\n\nRan 9 test suites in 4.09s (4.74s CPU time): 60 tests passed, 0 failed, 1 skipped (61 total tests)\n","passed":true},{"durationMs":54,"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.lowerFee(uint256)\",\"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.transferOwnership(address)\",\"IMD6900PerpPool.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"ADAPTATION.md\":104,\"README.md\":120,\"foundry.toml\":25,\"script/Owner.s.sol\":94,\"src/IMD6900PerpHook.sol\":498,\"src/IMD6900PerpPool.sol\":291,\"src/IMD6900PerpsLaunch.sol\":76,\"test/AuditFixes.t.sol\":431,\"test/ENTRYPOINT_COVERAGE.md\":57,\"test/IMD6900Perps.t.sol\":158,\"test/IMD6900PerpsLaunch.t.sol\":79,\"test/LaunchAdmission.t.sol\":140,\"test/PerpsFailurePaths.t.sol\":425,\"test/PerpsInvariants.t.sol\":376,\"test/PerpsLifecycle.t.sol\":205,\"test/PerpsTestSupport.sol\":129,\"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":"330ffe3d5d1330a1874f1e600ede83713d4344962446758f12331c6907c68506","verifiedTreeHash":"18924d2cc15f217b3e9009b873f0a42dc3459d1e","verifierVersion":"0.1.0+3f1208aa"},{"checks":[{"durationMs":17925,"exitCode":0,"name":"build","output":"Compiling 84 files with Solc 0.8.26\nSolc 0.8.26 finished in 17.79s\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:95:25\n   │\n95 │     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:98:9\n   │\n98 │         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:112:22\n    │\n112 │     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:115:9\n    │\n115 │         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:118:28\n    │\n118 │     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:120:9\n    │\n120 │         emit PerpEngineSet(engine);\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:268:9\n    │\n268 │         feeAddress = launcher;\n    │         ━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\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:127:9\n    │\n127 │         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:147:9\n    │\n147 │         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: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[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/IMD6900PerpPool.sol:137:95\n    │\n137 │     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:144:21\n    │\n144 │                 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:149:9\n    │\n149 │         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:144:67\n    │\n144 │                 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[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:218:9\n    │\n218 │         emit FeeLowered(newFeeBps);\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:224:9\n    │\n224 │         emit PerpFeeSet(bps);\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:229:28\n    │\n229 │     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:231:9\n    │\n231 │         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:236:9\n    │\n236 │         emit SweepFailOpenSet(on);\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:159:9\n    │\n159 │         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/IMD6900PerpHook.sol:243:9\n    │\n243 │         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/IMD6900PerpPool.sol:173:9\n    │\n173 │         emit Opened(sqrtPriceX96, ethIn, tokensIn, liq);\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:253:9\n    │\n253 │         emit FeeExemptSet(caller, exempt);\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:167:9\n    │\n167 │         poolManager.initialize(poolKey(), sqrtPriceX96);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\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:260:9\n    │\n260 │         emit FeeAddressUpdated(feeAddress_);\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:189:32\n    │\n189 │         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:191:9\n    │\n191 │         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:209:9\n    │\n209 │         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:222:9\n    │\n222 │         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:223:9\n    │\n223 │         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:242:21\n    │\n242 │         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:242:47\n    │\n242 │         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:242:55\n    │\n242 │         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:243:73\n    │\n243 │         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:243:81\n    │\n243 │         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:246:71\n    │\n246 │             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:246:79\n    │\n246 │             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[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:269:9\n    │\n269 │         emit FeeAddressRenounced(msg.sender);\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:247:13\n    │\n247 │             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:249:60\n    │\n249 │             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:249:68\n    │\n249 │             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[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/IMD6900PerpHook.sol:280:32\n    │\n280 │         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:281:9\n    │\n281 │         emit FeesFlushed(amount - toFeeAddress, toFeeAddress);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:285:60\n    │\n285 │         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:289:24\n    │\n289 │         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:289:32\n    │\n289 │         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:289:47\n    │\n289 │         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:289:55\n    │\n289 │         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[return-bomb]: external call with a gas limit may copy unbounded return data\n    ╭▸ src/IMD6900PerpHook.sol:473:43\n    │\n473 │               (bool ok, bytes memory out) = engine.call{gas: g - reserve}(\n    │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n474 │ ┃                 abi.encodeCall(IPerpSweep.sweepLiquidations, (tx.origin, spec, isBuy, limit))\n475 │ ┃             );\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\n","passed":true},{"durationMs":112,"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 299.46µs (0.00ns CPU time)\n\nRan 7 tests for test/IMD6900Perps.t.sol:IMD6900PerpsTest\n[PASS] test_AnyoneDeepensIt() (gas: 295313)\n[PASS] test_OnlyTheOwnerOpensAndRemoves() (gas: 231709)\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 8.57ms (1.45ms CPU time)\n\nRan 12 tests for test/AuditFixes.t.sol:AuditFixesTest\n[PASS] test_Fix_AddLiquidityOnlyAtThePonsPrice() (gas: 1172804)\n[PASS] test_Fix_AdminGuards() (gas: 213648)\n[PASS] test_Fix_AnAdditionLeavesTheEarnedFeesInThePool() (gas: 2107876)\n[PASS] test_Fix_CompoundOnlyAtThePonsPrice() (gas: 400464)\n[PASS] test_Fix_CompoundRefusesWithoutAReference() (gas: 1674319)\n[PASS] test_Fix_FeeBpsFollowsThePoolFee() (gas: 124367)\n[PASS] test_Fix_LowGasSweepFailsClosed() (gas: 580066)\n[PASS] test_Fix_NoCrossingThroughPons() (gas: 469108)\n[PASS] test_Fix_NoZeroFeeAddressAtConstruction() (gas: 6331)\n[PASS] test_Fix_UnreadablePonsLeavesTradingOpen() (gas: 449016)\n[PASS] test_OwnableEntryPointsOnBoth() (gas: 842089)\n[PASS] test_OwnerAlwaysTakesTheLiquidityBack() (gas: 508483)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 8.61ms (15.95ms CPU time)\n\nRan 5 tests for test/IMD6900PerpsLaunch.t.sol:IMD6900PerpsLaunchTest\n[PASS] test_DeploysOnAFreshChain() (gas: 74730)\n[PASS] test_EachDeployerMinesItsOwnSalt() (gas: 21426)\n[PASS] test_FitsOneTransaction() (gas: 6932)\nLogs:\n  launch gas (the mining included): 3760\n\n[PASS] test_PassesTheAdmissionScan() (gas: 7089506)\n[PASS] test_RefusesADynamicFee() (gas: 4460)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 15.25ms (30.49ms CPU time)\n\nRan 4 test suites in 15.76ms (32.74ms CPU time): 24 tests passed, 0 failed, 1 skipped (25 total tests)\n","passed":true},{"durationMs":63,"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.lowerFee(uint256)\",\"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.transferOwnership(address)\",\"IMD6900PerpPool.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"ADAPTATION.md\":104,\"README.md\":120,\"foundry.toml\":25,\"launch.json\":15,\"script/Owner.s.sol\":94,\"src/IMD6900PerpHook.sol\":498,\"src/IMD6900PerpPool.sol\":291,\"src/IMD6900PerpsLaunch.sol\":76,\"test/AuditFixes.t.sol\":431,\"test/IMD6900Perps.t.sol\":158,\"test/IMD6900PerpsLaunch.t.sol\":71,\"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":"90ce743fecde13594d2e736e05c8e026cf054efa053377947de5417d2a65ad69","verifiedTreeHash":"3dadd2b0f103bff990212c5a5ef136e3821cd391","verifierVersion":"0.1.0+3f1208aa"},{"checks":[{"durationMs":31965,"exitCode":0,"name":"build","output":"Compiling 86 files with Solc 0.8.26\nSolc 0.8.26 finished in 31.83s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to view\n   --> src/IMD6900PerpHook.sol:216:5:\n    |\n216 |     function lowerFee(uint256) external onlyOwner {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n   ╭▸ src/IMD6900PerpsLaunch.sol:39:29\n   │\n39 │           bytes memory init = abi.encodePacked(\n   │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n40 │ ┃             type(IMD6900PerpHook).creationCode, abi.encode(poolManager, address(p), feeAddress, owner, poolFee)\n41 │ ┃         );\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:96:14\n   │\n96 │         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:267:9\n    │\n267 │         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:95:25\n   │\n95 │     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:98:9\n   │\n98 │         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:112:22\n    │\n112 │     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:115:9\n    │\n115 │         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:118:28\n    │\n118 │     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:120:9\n    │\n120 │         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:223:9\n    │\n223 │         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:127:9\n    │\n127 │         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:228:28\n    │\n228 │     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:230:9\n    │\n230 │         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:147:9\n    │\n147 │         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:235:9\n    │\n235 │         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:242:9\n    │\n242 │         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:252:9\n    │\n252 │         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:259:9\n    │\n259 │         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:137:95\n    │\n137 │     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:144:21\n    │\n144 │                 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:149:9\n    │\n149 │         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:144:67\n    │\n144 │                 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[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/IMD6900PerpPool.sol:159:9\n    │\n159 │         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:174:9\n    │\n174 │         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:167:9\n    │\n167 │         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:190:32\n    │\n190 │         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:192:9\n    │\n192 │         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:214:9\n    │\n214 │         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:227:9\n    │\n227 │         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:228:9\n    │\n228 │         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:268:9\n    │\n268 │         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:279:32\n    │\n279 │         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:280:9\n    │\n280 │         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:247:21\n    │\n247 │         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:247:47\n    │\n247 │         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:247:55\n    │\n247 │         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:248:73\n    │\n248 │         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:248:81\n    │\n248 │         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:251:71\n    │\n251 │             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:251:79\n    │\n251 │             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:252:13\n    │\n252 │             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:254:60\n    │\n254 │             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:254:68\n    │\n254 │             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:470:43\n    │\n470 │               (bool ok, bytes memory out) = engine.call{gas: g - reserve}(\n    │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n471 │ ┃                 abi.encodeCall(IPerpSweep.sweepLiquidations, (tx.origin, spec, isBuy, limit))\n472 │ ┃             );\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:290:60\n    │\n290 │         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:294:24\n    │\n294 │         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:294:32\n    │\n294 │         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:294:47\n    │\n294 │         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:294:55\n    │\n294 │         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":1078,"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 333.24µs (0.00ns CPU time)\n\nRan 12 tests for test/AuditFixes.t.sol:AuditFixesTest\n[PASS] test_Fix_AddLiquidityOnlyAtThePonsPrice() (gas: 1172981)\n[PASS] test_Fix_AdminGuards() (gas: 213648)\n[PASS] test_Fix_AnAdditionLeavesTheEarnedFeesInThePool() (gas: 2107876)\n[PASS] test_Fix_CompoundOnlyAtThePonsPrice() (gas: 400659)\n[PASS] test_Fix_CompoundRefusesWithoutAReference() (gas: 1674573)\n[PASS] test_Fix_FeeBpsFollowsThePoolFee() (gas: 113562)\n[PASS] test_Fix_LowGasSweepFailsClosed() (gas: 580066)\n[PASS] test_Fix_NoCrossingThroughPons() (gas: 469108)\n[PASS] test_Fix_NoZeroFeeAddressAtConstruction() (gas: 6331)\n[PASS] test_Fix_UnreadablePonsLeavesTradingOpen() (gas: 449016)\n[PASS] test_OwnableEntryPointsOnBoth() (gas: 842089)\n[PASS] test_OwnerAlwaysTakesTheLiquidityBack() (gas: 508601)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 23.58ms (21.77ms CPU time)\n\nRan 5 tests for test/IMD6900PerpsLaunch.t.sol:IMD6900PerpsLaunchTest\n[PASS] test_DeploysOnAFreshChain() (gas: 74730)\n[PASS] test_EachDeployerMinesItsOwnSalt() (gas: 21426)\n[PASS] test_FitsOneTransaction() (gas: 4602229)\nLogs:\n  launch gas (the mining included): 4575023\n\n[PASS] test_PassesTheAdmissionScan() (gas: 7210705)\n[PASS] test_RefusesADynamicFee() (gas: 4460)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 23.55ms (41.32ms CPU time)\n\nRan 7 tests for test/IMD6900Perps.t.sol:IMD6900PerpsTest\n[PASS] test_AnyoneDeepensIt() (gas: 295431)\n[PASS] test_OnlyTheOwnerOpensAndRemoves() (gas: 231827)\n[PASS] test_OnlyThePoolAddsLiquidity() (gas: 405772)\n[PASS] test_OpensAtThePrice() (gas: 53491)\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 36.89ms (1.72ms CPU time)\n\nRan 25 tests for test/RevisionFixes.t.sol:RevisionFixesTest\n[PASS] test_BadFeeSinkIsRecoverable() (gas: 185811)\n[PASS] test_BandIsDefinedInCoinPerEth() (gas: 195998)\n[PASS] test_CompoundCollectionStillHonorsMinimumsAndPonsGuard() (gas: 2321451)\n[PASS] test_CompoundCollectsEarnedCoinFees() (gas: 2091654)\n[PASS] test_CompoundCollectsWhenBothIdleBalancesAreEmpty() (gas: 2070365)\n[PASS] test_EmptyPoolPriceCanMoveForFreeInsideBand() (gas: 702844)\n[PASS] test_FeeModelAlwaysMatchesStaticFee() (gas: 520502)\n[PASS] test_FeeModelCannotDivergeAtOtherStaticFees() (gas: 174228)\n[PASS] test_Fix_AddLiquidityOnlyAtThePonsPrice() (gas: 1173269)\n[PASS] test_Fix_AdminGuards() (gas: 213824)\n[PASS] test_Fix_AnAdditionLeavesTheEarnedFeesInThePool() (gas: 2107978)\n[PASS] test_Fix_CompoundOnlyAtThePonsPrice() (gas: 400907)\n[PASS] test_Fix_CompoundRefusesWithoutAReference() (gas: 1674708)\n[PASS] test_Fix_FeeBpsFollowsThePoolFee() (gas: 113740)\n[PASS] test_Fix_LowGasSweepFailsClosed() (gas: 580308)\n[PASS] test_Fix_NoCrossingThroughPons() (gas: 469202)\n[PASS] test_Fix_NoZeroFeeAddressAtConstruction() (gas: 6419)\n[PASS] test_Fix_UnreadablePonsLeavesTradingOpen() (gas: 449302)\n[PASS] test_FlushUsesCurrentFeeAddressAndBadEngineCanBeReplaced() (gas: 493602)\n[PASS] test_OpenRejectsStalePriceAndCanRetry() (gas: 871571)\n[PASS] test_OpenRequiresReadableReference() (gas: 404709)\n[PASS] test_OwnableEntryPointsOnBoth() (gas: 842375)\n[PASS] test_OwnerAlwaysTakesTheLiquidityBack() (gas: 508792)\n[PASS] test_OwnerCanTransferControlToAnUnusableAddress() (gas: 122203)\n[PASS] test_UnmatchedIdleEthNeedsCoinAndRemainsRecoverable() (gas: 814318)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 36.95ms (91.40ms CPU time)\n\nRan 7 tests for test/LaunchAdmission.t.sol:LaunchAdmissionTest\n[PASS] testFuzz_MinerReturnsFirstMatchingCreate2Salt(bytes32) (runs: 256, μ: 9230968, ~: 7903163)\n[SKIP] test_ActualFactorySaltWhenProvided() (gas: 0)\n[PASS] test_ExhaustedSearchRevertsWithinMiningBudget() (gas: 16965475)\nLogs:\n  exhausted search gas: 12023472\n\n[PASS] test_FitsOneTransactionWithMeteredCreate2() (gas: 15132079)\nLogs:\n  metered launch execution gas: 7089271\n  mining attempts: 32417\n\n[PASS] test_MeteredFactorySaltSample() (gas: 322047576)\nLogs:\n  worst execution gas in 24 salts: 8528499\n\n[PASS] test_OriginalExpensiveSearchStillFitsMiningBudget() (gas: 16815946)\nLogs:\n  original 132475-attempt search gas: 11874512\n\n[PASS] test_WorstSearchBudgetLeavesRoomForDeployment() (gas: 21460737)\nLogs:\n  worst search plus deployment budget: 16697989\n\nSuite result: ok. 6 passed; 0 failed; 1 skipped; finished in 989.69ms (1.53s CPU time)\n\nRan 6 test suites in 990.40ms (1.11s CPU time): 55 tests passed, 0 failed, 2 skipped (57 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.lowerFee(uint256)\",\"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.transferOwnership(address)\",\"IMD6900PerpPool.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"ADAPTATION.md\":273,\"README.md\":168,\"foundry.toml\":25,\"script/Owner.s.sol\":94,\"src/IMD6900PerpHook.sol\":495,\"src/IMD6900PerpPool.sol\":296,\"src/IMD6900PerpsLaunch.sol\":98,\"test/AuditFixes.t.sol\":431,\"test/IMD6900Perps.t.sol\":158,\"test/IMD6900PerpsLaunch.t.sol\":76,\"test/LaunchAdmission.t.sol\":195,\"test/RevisionFixes.t.sol\":261,\"test/RobinhoodFlow.fork.t.sol\":316},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":3230,"exitCode":0,"name":"slither","output":"[high/medium] arbitrary-send-eth at src/IMD6900PerpHook.sol:274: IMD6900PerpHook.flush() (src/IMD6900PerpHook.sol#274-281) sends eth to arbitrary user\n[medium/high] incorrect-equality at src/IMD6900PerpPool.sol:199: IMD6900PerpPool.compound(uint256,uint256) (src/IMD6900PerpPool.sol#199-215) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900PerpPool.sol:155: IMD6900PerpPool.open(address,address,uint160,uint256) (src/IMD6900PerpPool.sol#155-175) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900PerpPool.sol:281: IMD6900PerpPool._fullRangeLiquidity(uint160,uint256,uint256) (src/IMD6900PerpPool.sol#281-291) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900PerpHook.sol:274: IMD6900PerpHook.flush() (src/IMD6900PerpHook.sol#274-281) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900PerpPool.sol:199: IMD6900PerpPool.compound(uint256,uint256) (src/IMD6900PerpPool.sol#199-215) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/IMD6900PerpPool.sol:182: IMD6900PerpPool.addLiquidity(uint256,address) (src/IMD6900PerpPool.sol#182-193) uses a dangerous strict equality:\n[medium/medium] unused-return at src/IMD6900PerpPool.sol:199: IMD6900PerpPool.compound(uint256,uint256) (src/IMD6900PerpPool.sol#199-215) ignores return value by (sqrtPriceX96,None,None,None) = poolManager.getSlot0(poolKey().toId()) (src/IMD6900PerpPool.sol#210)\n[medium/medium] unused-return at src/IMD6900PerpHook.sol:311: IMD6900PerpHook._graduatedPriceX96(address) (src/IMD6900PerpHook.sol#311-329) ignores return value by (sq,None,None,None) = poolManager.getSlot0(key.toId()) (src/IMD6900PerpHook.sol#321)\n[medium/medium] unused-return at src/IMD6900PerpPool.sol:155: IMD6900PerpPool.open(address,address,uint160,uint256) (src/IMD6900PerpPool.sol#155-175) ignores return value by poolManager.initialize(poolKey(),sqrtPriceX96) (src/IMD6900PerpPool.sol#167)\n[medium/medium] unused-return at src/IMD6900PerpPool.sol:233: IMD6900PerpPool.unlockCallback(bytes) (src/IMD6900PerpPool.sol#233-260) ignores return value by poolManager.settle{value: uint256(uint128(- a0))}() (src/IMD6900PerpPool.sol#247)\n[medium/medium] unused-return at src/IMD6900PerpPool.sol:182: IMD6900PerpPool.addLiquidity(uint256,address) (src/IMD6900PerpPool.sol#182-193) ignores return value by (sqrtPriceX96,None,None,None) = poolManager.getSlot0(poolKey().toId()) (src/IMD6900PerpPool.sol#186)\n[medium/medium] unused-return at src/IMD6900PerpHook.sol:332: IMD6900PerpHook.poolPriceX96() (src/IMD6900PerpHook.sol#332-336) ignores return value by (sq,None,None,None) = poolManager.getSlot0(poolId) (src/IMD6900PerpHook.sol#333)\n[medium/medium] unused-return at src/IMD6900PerpPool.sol:233: IMD6900PerpPool.unlockCallback(bytes) (src/IMD6900PerpPool.sol#233-260) ignores return value by poolManager.settle() (src/IMD6900PerpPool.sol#252)\n[low/medium] missing-zero-check at src/IMD6900PerpPool.sol:118: IMD6900PerpPool.setPerpEngine(address).engine (src/IMD6900PerpPool.sol#118) lacks a zero-check on :\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/IMD6900PerpHook.sol:228: IMD6900PerpHook.setPerpEngine(address).engine (src/IMD6900PerpHook.sol#228) lacks a zero-check on :\n[low/medium] missing-zero-check at src/IMD6900PerpPool.sol:112: IMD6900PerpPool.setHook(address).hook_ (src/IMD6900PerpPool.sol#112) lacks a zero-check on :\n[low/medium] calls-loop at src/IMD6900PerpPool.sol:137: IMD6900PerpPool.removeLiquidity(uint128,address,address) (src/IMD6900PerpPool.sol#137-150) has external calls inside a loop: IPerpBookView(engine).isLiquidatable(ids[i]) (src/IMD6900PerpPool.sol#144)\n[low/medium] reentrancy-events at src/IMD6900PerpHook.sol:274: Reentrancy in IMD6900PerpHook.flush() (src/IMD6900PerpHook.sol#274-281):\n[low/medium] reentrancy-events at src/IMD6900PerpPool.sol:224: Reentrancy in IMD6900PerpPool.addFees() (src/IMD6900PerpPool.sol#224-229):\n[low/medium] return-bomb at src/IMD6900PerpHook.sol:464: IMD6900PerpHook._sweep(address,int256,bool,uint160) (src/IMD6900PerpHook.sol#464-491) tries to limit the gas of an external call that controls implicit decoding","passed":true},{"durationMs":401,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/IMD6900PerpsLaunch.sol:39: `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:48: 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 (17 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:186: Literal Instead of Constant (8 places)\n[low] non-reentrant-not-first at src/IMD6900PerpPool.sol:137: `nonReentrant` is Not the First Modifier (2 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:143: Loop Contains `require`/`revert`\n[low] state-no-address-check at src/IMD6900PerpHook.sol:229: Address State Variable Set Without Checks (3 places)\n[low] state-variable-could-be-immutable at src/IMD6900PerpHook.sol:115: State Variable Could Be Immutable\n[low] unchecked-return at src/IMD6900PerpPool.sol:167: Unchecked Return (3 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:339: Public Function Not Used Internally (2 places)\n[low] unused-state-variable at src/IMD6900PerpHook.sol:140: Unused State Variable (2 places)","passed":true},{"durationMs":3083,"exitCode":0,"name":"proof b3778ef613a8","output":"Compiling 58 files with Solc 0.8.30\nSolc 0.8.30 finished in 2.58s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to view\n   --> src/IMD6900PerpHook.sol:216:5:\n    |\n216 |     function lowerFee(uint256) external onlyOwner {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n\nRan 1 test for test/imd-proof-d69e9843/Proof_b3778ef613a8.t.sol:MiningGasProbeTest\n[PASS] test_LaunchFitsTransactionAtFactorySalt50149() (gas: 5267182)\nLogs:\n  salt seed: 50149\n  mining iterations: 9928\n  gas: 5083332\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.83ms (3.68ms CPU time)\n\nRan 1 test suite in 4.63ms (3.83ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"acfb568db736f564418c573648792a11f30d493ceaa99fbf7a17227794deb9eb","verifiedTreeHash":"7bc90047a296058200bf13291ea03942844079c9","verifierVersion":"0.1.0+0e08a464"},{"checks":[{"durationMs":57788,"exitCode":0,"name":"build","output":"Compiling 94 files with Solc 0.8.26\nSolc 0.8.26 finished in 57.63s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to view\n   --> src/IMD6900PerpHook.sol:216:5:\n    |\n216 |     function lowerFee(uint256) external onlyOwner {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n   ╭▸ src/IMD6900PerpsLaunch.sol:39:29\n   │\n39 │           bytes memory init = abi.encodePacked(\n   │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n40 │ ┃             type(IMD6900PerpHook).creationCode, abi.encode(poolManager, address(p), feeAddress, owner, poolFee)\n41 │ ┃         );\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:96:14\n   │\n96 │         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:267:9\n    │\n267 │         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:95:25\n   │\n95 │     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:98:9\n   │\n98 │         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:112:22\n    │\n112 │     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:115:9\n    │\n115 │         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:118:28\n    │\n118 │     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:120:9\n    │\n120 │         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:223:9\n    │\n223 │         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:127:9\n    │\n127 │         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:228:28\n    │\n228 │     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:230:9\n    │\n230 │         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:147:9\n    │\n147 │         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:235:9\n    │\n235 │         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:242:9\n    │\n242 │         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:252:9\n    │\n252 │         emit FeeExemptSet(caller, exempt);\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:137:95\n    │\n137 │     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:144:21\n    │\n144 │                 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/IMD6900PerpHook.sol:259:9\n    │\n259 │         emit FeeAddressUpdated(feeAddress_);\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:149:9\n    │\n149 │         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:144:67\n    │\n144 │                 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[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/IMD6900PerpPool.sol:159:9\n    │\n159 │         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:174:9\n    │\n174 │         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:167:9\n    │\n167 │         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:190:32\n    │\n190 │         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:192:9\n    │\n192 │         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:214:9\n    │\n214 │         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:227:9\n    │\n227 │         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:228:9\n    │\n228 │         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:268:9\n    │\n268 │         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:279:32\n    │\n279 │         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:280:9\n    │\n280 │         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:247:21\n    │\n247 │         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:247:47\n    │\n247 │         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:247:55\n    │\n247 │         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:248:73\n    │\n248 │         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:248:81\n    │\n248 │         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:251:71\n    │\n251 │             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:251:79\n    │\n251 │             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:252:13\n    │\n252 │             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:254:60\n    │\n254 │             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:254:68\n    │\n254 │             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:470:43\n    │\n470 │               (bool ok, bytes memory out) = engine.call{gas: g - reserve}(\n    │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n471 │ ┃                 abi.encodeCall(IPerpSweep.sweepLiquidations, (tx.origin, spec, isBuy, limit))\n472 │ ┃             );\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:290:60\n    │\n290 │         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:294:24\n    │\n294 │         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:294:32\n    │\n294 │         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:294:47\n    │\n294 │         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:294:55\n    │\n294 │         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":5564,"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 374.31µs (0.00ns CPU time)\n\nRan 5 tests for test/PerpsBandIntegration.t.sol:PerpsBandIntegrationTest\n[PASS] test_BandRejectionRollsBackBothCurrenciesAndPrice() (gas: 443064)\n[PASS] test_ExemptionChangesBandOnlyAndCanBeRevoked() (gas: 672699)\n[PASS] test_LiquidationAfterAValidTradeIsNotRolledBackByTheBand() (gas: 933341)\n[PASS] test_PostSweepCannotHideAnOutOfBandTrade() (gas: 1140816)\n[PASS] test_SellCanCorrectBelowPonsButCannotCrossAndFinishOutOfBand() (gas: 456084)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 8.73ms (2.85ms CPU time)\n\nRan 7 tests for test/IMD6900Perps.t.sol:IMD6900PerpsTest\n[PASS] test_AnyoneDeepensIt() (gas: 295431)\n[PASS] test_OnlyTheOwnerOpensAndRemoves() (gas: 231827)\n[PASS] test_OnlyThePoolAddsLiquidity() (gas: 405772)\n[PASS] test_OpensAtThePrice() (gas: 53491)\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 10.44ms (946.53µs CPU time)\n\nRan 12 tests for test/AuditFixes.t.sol:AuditFixesTest\n[PASS] test_Fix_AddLiquidityOnlyAtThePonsPrice() (gas: 1172981)\n[PASS] test_Fix_AdminGuards() (gas: 213648)\n[PASS] test_Fix_AnAdditionLeavesTheEarnedFeesInThePool() (gas: 2107876)\n[PASS] test_Fix_CompoundOnlyAtThePonsPrice() (gas: 400659)\n[PASS] test_Fix_CompoundRefusesWithoutAReference() (gas: 1674573)\n[PASS] test_Fix_FeeBpsFollowsThePoolFee() (gas: 113562)\n[PASS] test_Fix_LowGasSweepFailsClosed() (gas: 580066)\n[PASS] test_Fix_NoCrossingThroughPons() (gas: 469108)\n[PASS] test_Fix_NoZeroFeeAddressAtConstruction() (gas: 6331)\n[PASS] test_Fix_UnreadablePonsLeavesTradingOpen() (gas: 449016)\n[PASS] test_OwnableEntryPointsOnBoth() (gas: 842089)\n[PASS] test_OwnerAlwaysTakesTheLiquidityBack() (gas: 508601)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 24.67ms (20.18ms CPU time)\n\nRan 25 tests for test/RevisionFixes.t.sol:RevisionFixesTest\n[PASS] test_BadFeeSinkIsRecoverable() (gas: 185811)\n[PASS] test_BandIsDefinedInCoinPerEth() (gas: 195998)\n[PASS] test_CompoundCollectionStillHonorsMinimumsAndPonsGuard() (gas: 2321451)\n[PASS] test_CompoundCollectsEarnedCoinFees() (gas: 2091654)\n[PASS] test_CompoundCollectsWhenBothIdleBalancesAreEmpty() (gas: 2070365)\n[PASS] test_EmptyPoolPriceCanMoveForFreeInsideBand() (gas: 702844)\n[PASS] test_FeeModelAlwaysMatchesStaticFee() (gas: 520502)\n[PASS] test_FeeModelCannotDivergeAtOtherStaticFees() (gas: 174228)\n[PASS] test_Fix_AddLiquidityOnlyAtThePonsPrice() (gas: 1173269)\n[PASS] test_Fix_AdminGuards() (gas: 213824)\n[PASS] test_Fix_AnAdditionLeavesTheEarnedFeesInThePool() (gas: 2107978)\n[PASS] test_Fix_CompoundOnlyAtThePonsPrice() (gas: 400907)\n[PASS] test_Fix_CompoundRefusesWithoutAReference() (gas: 1674708)\n[PASS] test_Fix_FeeBpsFollowsThePoolFee() (gas: 113740)\n[PASS] test_Fix_LowGasSweepFailsClosed() (gas: 580308)\n[PASS] test_Fix_NoCrossingThroughPons() (gas: 469202)\n[PASS] test_Fix_NoZeroFeeAddressAtConstruction() (gas: 6419)\n[PASS] test_Fix_UnreadablePonsLeavesTradingOpen() (gas: 449302)\n[PASS] test_FlushUsesCurrentFeeAddressAndBadEngineCanBeReplaced() (gas: 493602)\n[PASS] test_OpenRejectsStalePriceAndCanRetry() (gas: 871571)\n[PASS] test_OpenRequiresReadableReference() (gas: 404709)\n[PASS] test_OwnableEntryPointsOnBoth() (gas: 842375)\n[PASS] test_OwnerAlwaysTakesTheLiquidityBack() (gas: 508792)\n[PASS] test_OwnerCanTransferControlToAnUnusableAddress() (gas: 122203)\n[PASS] test_UnmatchedIdleEthNeedsCoinAndRemainsRecoverable() (gas: 814318)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 67.46ms (94.73ms CPU time)\n\nRan 5 tests for test/IMD6900PerpsLaunch.t.sol:IMD6900PerpsLaunchTest\n[PASS] test_DeploysOnAFreshChain() (gas: 74730)\n[PASS] test_EachDeployerMinesItsOwnSalt() (gas: 21426)\n[PASS] test_FitsOneTransaction() (gas: 4602229)\nLogs:\n  launch gas (the mining included): 4575023\n\n[PASS] test_PassesTheAdmissionScan() (gas: 7210705)\n[PASS] test_RefusesADynamicFee() (gas: 4460)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 67.51ms (44.51ms CPU time)\n\nRan 7 tests for test/PerpsReferenceAndOwnership.t.sol:PerpsReferenceAndOwnershipTest\n[PASS] testFuzz_ReservePriceIsMonotoneAndScaleInvariant(uint96,uint96) (runs: 1000, μ: 240944, ~: 240982)\nLogs:\n  Bound result 79228162514264337593543950335\n  Bound result 79228162514264337593543950335\n\n[PASS] test_EmptyAndOverflowingReferenceReturnsNoPrice() (gas: 217293)\n[PASS] test_GraduatedCoinSortingFirstInvertsTheReference() (gas: 880381)\n[PASS] test_GraduationSwitchesFromFrozenCurveToActualV4ReferencePool() (gas: 978677)\n[PASS] test_HandoverExpiryBoundaryAndRequestIsolationOnBothContracts() (gas: 638356)\n[PASS] test_IncompleteOrBrokenGraduationDisablesBandButPreventsPairing() (gas: 2049475)\n[PASS] test_TransferringOneOwnerDoesNotChangeTheOtherOrRedirectExistingFees() (gas: 257890)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 67.62ms (63.81ms CPU time)\n\nRan 2 tests for test/PerpsLiquidityMath.t.sol:PerpsLiquidityMathTest\n[PASS] testFuzz_QuotedLiquidityNeverExceedsEitherFundingBudget(int24,uint128,uint128) (runs: 1000, μ: 45218, ~: 45160)\nLogs:\n  Bound result -522677\n\n[PASS] test_FullRangeEndpointsAndEmptyBudgets() (gas: 105385)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 67.67ms (67.52ms CPU time)\n\nRan 18 tests for test/PerpsFailurePaths.t.sol:PerpsFailurePathsTest\n[PASS] testFuzz_ImmediateAddRemoveCannotCreateValue(uint256,uint256) (runs: 1000, μ: 408816, ~: 410240)\nLogs:\n  Bound result 5663\n  Bound result 2750\n\n[PASS] test_AllAdministrativeMutationsRejectNonOwners() (gas: 568855)\n[PASS] test_AllHookCallbacksAuthenticateTheManager() (gas: 730223)\n[PASS] test_ClosedPoolAndZeroInputsHaveSpecificFailures() (gas: 389014)\n[PASS] test_FailedFeeRecipientRollsBackTheEarlierPoolPayment() (gas: 275532)\n[PASS] test_FailedOpeningRollsBackInitializationAndCanRetry() (gas: 1003326)\n[PASS] test_FalseReturningTokenCannotPartiallyAddOrWithdraw() (gas: 490893)\n[PASS] test_InitializationRejectsForeignSenderAndWrongKeyThenIsOneShot() (gas: 844756)\n[PASS] test_LiquidityCallbacksRequirePoolAndBothFullRangeBounds() (gas: 251926)\n[PASS] test_MalformedSweepAndOpenCountFailClosedAtBothGasBudgets() (gas: 374937)\n[PASS] test_RefundCallbackCannotReenterCompounding() (gas: 577426)\n[PASS] test_RoundTripMinimumAndUnbalancedAmounts() (gas: 1254342)\nLogs:\n  Bound result 2\n  Bound result 2\n  Bound result 2\n  Bound result 10000000000000000000\n  Bound result 10000000000000000000\n  Bound result 2\n\n[PASS] test_SwapRejectsForeignPoolBeforeCallingTheEngine() (gas: 447962)\n[PASS] test_SweepAvailabilityLowGasAndEngineSelfSwap() (gas: 776738)\n[PASS] test_SweepParametersZeroDeltasAndBestEffortPostSweep() (gas: 855067)\n[PASS] test_SweepStatusCannotBeBypassedByFailOpen() (gas: 1462652)\n[PASS] test_WithdrawalSafetyCheckIsAtomicAndOwnerCanRecoverFromBrokenBook() (gas: 1171580)\n[PASS] test_WithdrawalWithHealthyPositionsAndRejectingRecipient() (gas: 929012)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 67.67ms (82.03ms CPU time)\n\nRan 3 tests for test/PerpsExactLaunch.t.sol:PerpsExactLaunchTest\n[PASS] test_DeploysOnAFreshChainWithExactArgumentsAndNoPreexistingCalls() (gas: 306612)\n[PASS] test_InvalidConstructorArgumentsRevertTheWholeLaunch() (gas: 4385)\n[PASS] test_PassesTheAdmissionScanWithExactArguments() (gas: 35231008)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 77.23ms (90.05ms CPU time)\n\nRan 7 tests for test/LaunchAdmission.t.sol:LaunchAdmissionTest\n[PASS] testFuzz_MinerReturnsFirstMatchingCreate2Salt(bytes32) (runs: 256, μ: 9960528, ~: 8178742)\n[SKIP] test_ActualFactorySaltWhenProvided() (gas: 0)\n[PASS] test_ExhaustedSearchRevertsWithinMiningBudget() (gas: 16965475)\nLogs:\n  exhausted search gas: 12023472\n\n[PASS] test_FitsOneTransactionWithMeteredCreate2() (gas: 15132079)\nLogs:\n  metered launch execution gas: 7089271\n  mining attempts: 32417\n\n[PASS] test_MeteredFactorySaltSample() (gas: 322047576)\nLogs:\n  worst execution gas in 24 salts: 8528499\n\n[PASS] test_OriginalExpensiveSearchStillFitsMiningBudget() (gas: 16815946)\nLogs:\n  original 132475-attempt search gas: 11874512\n\n[PASS] test_WorstSearchBudgetLeavesRoomForDeployment() (gas: 21460737)\nLogs:\n  worst search plus deployment budget: 16697989\n\nSuite result: ok. 6 passed; 0 failed; 1 skipped; finished in 1.29s (1.87s CPU time)\n\nRan 4 tests for test/PerpsFees.invariant.t.sol:PerpsFeesInvariantTest\n[PASS] invariant_feeFlowsConserveEveryWeiAndReachOnlyTheCurrentRecipients() (runs: 256, calls: 16384, reverts: 0)\n\n╭--------------------+----------------------+-------+---------+----------╮\n| Contract           | Selector             | Calls | Reverts | Discards |\n+========================================================================+\n| FeeSequenceHandler | deposit              | 2346  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| FeeSequenceHandler | flush                | 2313  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| FeeSequenceHandler | payPool              | 2370  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| FeeSequenceHandler | renounce             | 2330  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| FeeSequenceHandler | routeSink            | 2336  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| FeeSequenceHandler | unauthorizedRenounce | 2292  | 0       | 0        |\n|--------------------+----------------------+-------+---------+----------|\n| FeeSequenceHandler | updateRecipient      | 2397  | 0       | 0        |\n╰--------------------+----------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 999999999999999997\n  Bound result 12060000\n  Bound result 427289232708016284\n  Bound result 16777216\n  Bound result 11532125\n  Bound result 1000000000000\n  Bound result 7301\n  Bound result 25\n  Bound result 4521\n  Bound result 7565\n  Bound result 15369\n  Bound result 127\n  Bound result 6069\n  Bound result 3605\n  Bound result 571023057078641383\n  Bound result 768842346534930544\n  Bound result 716312263031323966\n\n[PASS] testFuzz_FlushRoundingAndIdempotence(uint128) (runs: 1000, μ: 116939, ~: 118345)\n[PASS] test_FlushRoundingEdges() (gas: 585704)\n[PASS] test_HandlerReachesFailureRecoveryAndPermanentRenunciation() (gas: 1450581)\nLogs:\n  Bound result 101\n  Bound result 100\n  Bound result 50\n  Bound result 25\n\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.64s (1.67s CPU time)\n\nRan 2 tests for test/PerpsLiquidity.invariant.t.sol:PerpsLiquidityInvariantTest\n[PASS]\nPerpsLiquidityInvariantTest invariants:\n[PASS] invariant_assetsMatchExternalCashFlows\n[PASS] invariant_ownerCanWithdrawTheEntirePosition\n PerpsLiquidityInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭--------------------------+----------------------+-------+---------+----------╮\n| Contract                 | Selector             | Calls | Reverts | Discards |\n+==============================================================================+\n| LiquiditySequenceHandler | add                  | 2370  | 0       | 0        |\n|--------------------------+----------------------+-------+---------+----------|\n| LiquiditySequenceHandler | compound             | 2367  | 0       | 0        |\n|--------------------------+----------------------+-------+---------+----------|\n| LiquiditySequenceHandler | donate               | 2315  | 0       | 0        |\n|--------------------------+----------------------+-------+---------+----------|\n| LiquiditySequenceHandler | referenceUnavailable | 2260  | 0       | 0        |\n|--------------------------+----------------------+-------+---------+----------|\n| LiquiditySequenceHandler | remove               | 2324  | 0       | 0        |\n|--------------------------+----------------------+-------+---------+----------|\n| LiquiditySequenceHandler | swap                 | 2395  | 0       | 0        |\n|--------------------------+----------------------+-------+---------+----------|\n| LiquiditySequenceHandler | unauthorizedRemoval  | 2353  | 0       | 0        |\n╰--------------------------+----------------------+-------+---------+----------╯\n\nLogs:\n  Bound result 560164610191578929\n  Bound result 537986421456849087\n  Bound result 4562\n  Bound result 6\n  Bound result 1000000000\n  Bound result 999999999999004537\n  Bound result 435505406139754699\n  Bound result 1000000000000\n  Bound result 4503599627370496\n  Bound result 1000000000000000\n  Bound result 1680\n  Bound result 15348\n  Bound result 1000001\n  Bound result 999999999999057006\n  Bound result 50149\n  Bound result 100\n  Bound result 60000000000999999\n  Bound result 999999999999011770\n  Bound result 78904329998820271740\n  Bound result 246\n  Bound result 42237973901218860\n  Bound result 999999999999000051\n  Bound result 999999999999001779\n  Bound result 2890239246191567270\n  Bound result 52918690108469459\n  Bound result 3304\n  Bound result 5274\n  Bound result 101\n  Bound result 24\n  Bound result 841\n  Bound result 2831283526473075\n  Bound result 955\n  Bound result 999999999999003440\n  Bound result 10000000000000000\n  Bound result 1259352355\n  Bound result 999999999999012693\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 4557286882433808\n  Bound result 237754832295200\n  Bound result 92240510829748332\n  Bound result 8193\n  Bound result 4537560724202861\n  Bound result 1000000000000001\n  Bound result 184481021659496664\n  Bound result 4353318602848624\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 438057344023872820\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 868728762331603\n  Bound result 679000000000000000\n  Bound result 999999999999049153\n\n[PASS] test_HandlerExercisesAnEntireLifecycle() (gas: 2275348)\nLogs:\n  Bound result 10000000000000000\n  Bound result 10000000000000000\n  Bound result 1000000000000000000\n  Bound result 500000000000000000\n  Bound result 500000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 5.45s (5.44s CPU time)\n\nRan 13 test suites in 5.45s (8.84s CPU time): 96 tests passed, 0 failed, 2 skipped (98 total tests)\n","passed":true},{"durationMs":70,"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.lowerFee(uint256)\",\"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.transferOwnership(address)\",\"IMD6900PerpPool.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"ADAPTATION.md\":273,\"README.md\":168,\"foundry.toml\":25,\"script/Owner.s.sol\":94,\"src/IMD6900PerpHook.sol\":495,\"src/IMD6900PerpPool.sol\":296,\"src/IMD6900PerpsLaunch.sol\":98,\"test/AuditFixes.t.sol\":431,\"test/IMD6900Perps.t.sol\":158,\"test/IMD6900PerpsLaunch.t.sol\":76,\"test/LaunchAdmission.t.sol\":195,\"test/PerpsBandIntegration.t.sol\":134,\"test/PerpsExactLaunch.t.sol\":116,\"test/PerpsFailurePaths.t.sol\":486,\"test/PerpsFees.invariant.t.sol\":222,\"test/PerpsLiquidity.invariant.t.sol\":284,\"test/PerpsLiquidityMath.t.sol\":67,\"test/PerpsReferenceAndOwnership.t.sol\":210,\"test/RevisionFixes.t.sol\":261,\"test/RobinhoodFlow.fork.t.sol\":316,\"test/TEST_COVERAGE.md\":77,\"test/helpers/PerpsTestFixture.sol\":119},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"c4bdf2375ac44b1bde35a733757e940e80600349630ad4a08d320e8f93f90aa5","verifiedTreeHash":"7fb88790c4efbe23f8cffa06edef6e4c73b05e06","verifierVersion":"0.1.0+0e08a464"},{"checks":[{"durationMs":30771,"exitCode":0,"name":"build","output":"Compiling 86 files with Solc 0.8.26\nSolc 0.8.26 finished in 30.64s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to view\n   --> src/IMD6900PerpHook.sol:216:5:\n    |\n216 |     function lowerFee(uint256) external onlyOwner {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n   ╭▸ src/IMD6900PerpsLaunch.sol:39:29\n   │\n39 │           bytes memory init = abi.encodePacked(\n   │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n40 │ ┃             type(IMD6900PerpHook).creationCode, abi.encode(poolManager, address(p), feeAddress, owner, poolFee)\n41 │ ┃         );\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:96:14\n   │\n96 │         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:95:25\n   │\n95 │     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:98:9\n   │\n98 │         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:112:22\n    │\n112 │     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:115:9\n    │\n115 │         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:118:28\n    │\n118 │     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:120:9\n    │\n120 │         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:127:9\n    │\n127 │         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:147:9\n    │\n147 │         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:267:9\n    │\n267 │         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:137:95\n    │\n137 │     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:144:21\n    │\n144 │                 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:149:9\n    │\n149 │         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:144:67\n    │\n144 │                 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: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[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/IMD6900PerpPool.sol:159:9\n    │\n159 │         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/IMD6900PerpHook.sol:223:9\n    │\n223 │         emit PerpFeeSet(bps);\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:228:28\n    │\n228 │     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:230:9\n    │\n230 │         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:174:9\n    │\n174 │         emit Opened(sqrtPriceX96, ethIn, tokensIn, liq);\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:235:9\n    │\n235 │         emit SweepFailOpenSet(on);\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:167:9\n    │\n167 │         poolManager.initialize(poolKey(), sqrtPriceX96);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\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:242:9\n    │\n242 │         emit BandSet(maxDeviationBps_);\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:190:32\n    │\n190 │         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/IMD6900PerpHook.sol:252:9\n    │\n252 │         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:259:9\n    │\n259 │         emit FeeAddressUpdated(feeAddress_);\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:192:9\n    │\n192 │         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:214:9\n    │\n214 │         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:227:9\n    │\n227 │         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:228:9\n    │\n228 │         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:247:21\n    │\n247 │         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:247:47\n    │\n247 │         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[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:268:9\n    │\n268 │         emit FeeAddressRenounced(msg.sender);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:247:55\n    │\n247 │         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[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/IMD6900PerpHook.sol:279:32\n    │\n279 │         if (toFeeAddress != 0) SafeTransferLib.safeTransferETH(feeAddress, toFeeAddress);\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:248:73\n    │\n248 │         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:248:81\n    │\n248 │         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:251:71\n    │\n251 │             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:251:79\n    │\n251 │             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:252:13\n    │\n252 │             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:254:60\n    │\n254 │             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:254:68\n    │\n254 │             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[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/IMD6900PerpHook.sol:280:9\n    │\n280 │         emit FeesFlushed(amount - toFeeAddress, toFeeAddress);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/IMD6900PerpPool.sol:290:60\n    │\n290 │         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:294:24\n    │\n294 │         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:294:32\n    │\n294 │         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:294:47\n    │\n294 │         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:294:55\n    │\n294 │         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[return-bomb]: external call with a gas limit may copy unbounded return data\n    ╭▸ src/IMD6900PerpHook.sol:470:43\n    │\n470 │               (bool ok, bytes memory out) = engine.call{gas: g - reserve}(\n    │ ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛\n471 │ ┃                 abi.encodeCall(IPerpSweep.sweepLiquidations, (tx.origin, spec, isBuy, limit))\n472 │ ┃             );\n    │ ┗━━━━━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\n","passed":true},{"durationMs":1003,"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 346.44µs (0.00ns CPU time)\n\nRan 7 tests for test/IMD6900Perps.t.sol:IMD6900PerpsTest\n[PASS] test_AnyoneDeepensIt() (gas: 295431)\n[PASS] test_OnlyTheOwnerOpensAndRemoves() (gas: 231827)\n[PASS] test_OnlyThePoolAddsLiquidity() (gas: 405772)\n[PASS] test_OpensAtThePrice() (gas: 53491)\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 9.67ms (1.02ms CPU time)\n\nRan 12 tests for test/AuditFixes.t.sol:AuditFixesTest\n[PASS] test_Fix_AddLiquidityOnlyAtThePonsPrice() (gas: 1172981)\n[PASS] test_Fix_AdminGuards() (gas: 213648)\n[PASS] test_Fix_AnAdditionLeavesTheEarnedFeesInThePool() (gas: 2107876)\n[PASS] test_Fix_CompoundOnlyAtThePonsPrice() (gas: 400659)\n[PASS] test_Fix_CompoundRefusesWithoutAReference() (gas: 1674573)\n[PASS] test_Fix_FeeBpsFollowsThePoolFee() (gas: 113562)\n[PASS] test_Fix_LowGasSweepFailsClosed() (gas: 580066)\n[PASS] test_Fix_NoCrossingThroughPons() (gas: 469108)\n[PASS] test_Fix_NoZeroFeeAddressAtConstruction() (gas: 6331)\n[PASS] test_Fix_UnreadablePonsLeavesTradingOpen() (gas: 449016)\n[PASS] test_OwnableEntryPointsOnBoth() (gas: 842089)\n[PASS] test_OwnerAlwaysTakesTheLiquidityBack() (gas: 508601)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 26.63ms (21.31ms CPU time)\n\nRan 5 tests for test/IMD6900PerpsLaunch.t.sol:IMD6900PerpsLaunchTest\n[PASS] test_DeploysOnAFreshChain() (gas: 74730)\n[PASS] test_EachDeployerMinesItsOwnSalt() (gas: 21426)\n[PASS] test_FitsOneTransaction() (gas: 4602229)\nLogs:\n  launch gas (the mining included): 4575023\n\n[PASS] test_PassesTheAdmissionScan() (gas: 7210705)\n[PASS] test_RefusesADynamicFee() (gas: 4460)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 27.23ms (54.32ms CPU time)\n\nRan 25 tests for test/RevisionFixes.t.sol:RevisionFixesTest\n[PASS] test_BadFeeSinkIsRecoverable() (gas: 185811)\n[PASS] test_BandIsDefinedInCoinPerEth() (gas: 195998)\n[PASS] test_CompoundCollectionStillHonorsMinimumsAndPonsGuard() (gas: 2321451)\n[PASS] test_CompoundCollectsEarnedCoinFees() (gas: 2091654)\n[PASS] test_CompoundCollectsWhenBothIdleBalancesAreEmpty() (gas: 2070365)\n[PASS] test_EmptyPoolPriceCanMoveForFreeInsideBand() (gas: 702844)\n[PASS] test_FeeModelAlwaysMatchesStaticFee() (gas: 520502)\n[PASS] test_FeeModelCannotDivergeAtOtherStaticFees() (gas: 174228)\n[PASS] test_Fix_AddLiquidityOnlyAtThePonsPrice() (gas: 1173269)\n[PASS] test_Fix_AdminGuards() (gas: 213824)\n[PASS] test_Fix_AnAdditionLeavesTheEarnedFeesInThePool() (gas: 2107978)\n[PASS] test_Fix_CompoundOnlyAtThePonsPrice() (gas: 400907)\n[PASS] test_Fix_CompoundRefusesWithoutAReference() (gas: 1674708)\n[PASS] test_Fix_FeeBpsFollowsThePoolFee() (gas: 113740)\n[PASS] test_Fix_LowGasSweepFailsClosed() (gas: 580308)\n[PASS] test_Fix_NoCrossingThroughPons() (gas: 469202)\n[PASS] test_Fix_NoZeroFeeAddressAtConstruction() (gas: 6419)\n[PASS] test_Fix_UnreadablePonsLeavesTradingOpen() (gas: 449302)\n[PASS] test_FlushUsesCurrentFeeAddressAndBadEngineCanBeReplaced() (gas: 493602)\n[PASS] test_OpenRejectsStalePriceAndCanRetry() (gas: 871571)\n[PASS] test_OpenRequiresReadableReference() (gas: 404709)\n[PASS] test_OwnableEntryPointsOnBoth() (gas: 842375)\n[PASS] test_OwnerAlwaysTakesTheLiquidityBack() (gas: 508792)\n[PASS] test_OwnerCanTransferControlToAnUnusableAddress() (gas: 122203)\n[PASS] test_UnmatchedIdleEthNeedsCoinAndRemainsRecoverable() (gas: 814318)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 27.28ms (80.35ms CPU time)\n\nRan 7 tests for test/LaunchAdmission.t.sol:LaunchAdmissionTest\n[PASS] testFuzz_MinerReturnsFirstMatchingCreate2Salt(bytes32) (runs: 256, μ: 8759371, ~: 7629768)\n[SKIP] test_ActualFactorySaltWhenProvided() (gas: 0)\n[PASS] test_ExhaustedSearchRevertsWithinMiningBudget() (gas: 16965475)\nLogs:\n  exhausted search gas: 12023472\n\n[PASS] test_FitsOneTransactionWithMeteredCreate2() (gas: 15132079)\nLogs:\n  metered launch execution gas: 7089271\n  mining attempts: 32417\n\n[PASS] test_MeteredFactorySaltSample() (gas: 322047576)\nLogs:\n  worst execution gas in 24 salts: 8528499\n\n[PASS] test_OriginalExpensiveSearchStillFitsMiningBudget() (gas: 16815946)\nLogs:\n  original 132475-attempt search gas: 11874512\n\n[PASS] test_WorstSearchBudgetLeavesRoomForDeployment() (gas: 21460737)\nLogs:\n  worst search plus deployment budget: 16697989\n\nSuite result: ok. 6 passed; 0 failed; 1 skipped; finished in 916.02ms (1.47s CPU time)\n\nRan 6 test suites in 916.81ms (1.01s CPU time): 55 tests passed, 0 failed, 2 skipped (57 total tests)\n","passed":true},{"durationMs":58,"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.lowerFee(uint256)\",\"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.transferOwnership(address)\",\"IMD6900PerpPool.unlockCallback(bytes)\"],\"files\":{\".gitignore\":4,\"ADAPTATION.md\":273,\"README.md\":168,\"foundry.toml\":25,\"launch.json\":15,\"script/Owner.s.sol\":94,\"src/IMD6900PerpHook.sol\":495,\"src/IMD6900PerpPool.sol\":296,\"src/IMD6900PerpsLaunch.sol\":98,\"test/AuditFixes.t.sol\":431,\"test/IMD6900Perps.t.sol\":158,\"test/IMD6900PerpsLaunch.t.sol\":76,\"test/LaunchAdmission.t.sol\":195,\"test/RevisionFixes.t.sol\":261,\"test/RobinhoodFlow.fork.t.sol\":316},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"f984620e9a02a6d3f2c2081e24e692bb2388ef94eb7d38ea56b1cff8e1dfcf2b","verifiedTreeHash":"f1553db02fb32112e09f72801d97f4f08b4ae8ff","verifierVersion":"0.1.0+0e08a464"}]}