{"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":"e497ebcf-33bb-4548-bfd7-fa95843dc898","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"acde13e2d83688f167a5ccb838f51fc0489013ded31876489589c6e074f74216","dependsOn":["build_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":"687f0c66c08b4fa689e0d33a4f7c589be05c9cb40d78d6257810b6c275805490","dependsOn":["build_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":"15b14dc90e6c7c88cdf4145c80bb9cd427576eb4a527ffd00f12e33103f0ea43","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"84edd2a8d0b8e9474d2ef9260182c985d5db5c6a25f357d3f77199cc84cb5386","dependsOn":["build_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":"9d97c650305d25a510139a6add82bde20f37d53a7039a2da9e6a117bcdae6749","dependsOn":["build_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":"6e6e4fa7150d4ba1f6e78a08391dbec1d641cc5515d2243bda2291fc0c402fac","dependsOn":[],"execution":{"mustProduce":["src/LaunchToken.sol"],"network":false,"profile":"foundry","requires":[],"skillHash":"b6503de65ad02f845827887c23da4c3b56ccc5df7a459db263bae6e549d92f7f","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"b6503de65ad02f845827887c23da4c3b56ccc5df7a459db263bae6e549d92f7f","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"e7f58dc73d9860d08e56e29288a9b7481beaffdf2c44c538b50bd29c14702f56","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"773defab701f3a5c0ed179a7f0276fab44bca6446e452f13e0277df9de3d21dc","dependsOn":["build_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":"Task (single job, do NOT ask clarifying questions — pick sane defaults and document them): write deploy-ready smart contracts for \"$ADAM\", an Ethereum mainnet memecoin on Uniswap v4. Swap fees buy $IMD and $PNKSTR and distribute them to ADAM holders.\n\n## Token + hook\n- ADAM ERC-20, fixed supply 1,000,000,000, no mint. Owner can only LOWER fees, never raise.\n- Uniswap v4 ETH/ADAM pool, PoolManager 0x000000000004444c5dc75cB358380D2e3dE08A90. Launch without ETH: single-sided ADAM-only position above current price.\n- Hook: 1.5% fee on every buy and sell, taken in ETH, sent to Treasury. No heavy logic inside swaps.\n- Anti-snipe: fee starts at 20% and decays linearly to 1.5% over the first 30 minutes (same split applies).\n\n## Fee split (fixed in code)\n- 10% → teamWallet (constructor param).\n- 90% → holders:\n  - 50% buys $IMD (0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7) via its existing v4 ETH/IMD pool (find the PoolKey onchain).\n  - 50% buys $PNKSTR (0xc50673edb3a7b94e8cad8a7d4e0cd68864e33edf) via its v4 pool:\n    PoolKey { currency0: ETH (address(0)), currency1: PNKSTR, fee: 0, tickSpacing: 60, hooks: 0xfaaad5b731f52cdc9746f2414c823eca9b06e844 }.\n    The PNKSTR hook is unverified and charges a buy tax — measure the real tax on a mainnet fork and account for it in minOut.\n- Both buys: slippage cap + max size per call to limit MEV.\n\n## Distribution\n- IMD + PNKSTR distributed to ADAM holders pro-rata to balance, pull-based claim (dividend-per-share pattern), updated on transfer, multiple reward tokens.\n- Exclude pool, PoolManager, hook, Treasury, Distributor, zero/dead addresses.\n- Treasury.process(): public, anyone can call — executes split, buys, pushes rewards to Distributor.\n- Reentrancy guards, no upgradeability, no blacklist, no owner access to holder funds.\n\n## Deliverables (all in this job)\n1. Foundry repo: ADAM token, hook (mined hook-address flags), Treasury, Distributor, mainnet deploy script (NOT executed — deployer signs manually) with exact constructor params and step list.\n2. Tests: unit + fuzz + mainnet-fork (real IMD and PNKSTR pools): buy/sell tax, 90/10 split, 50/50 IMD/PNKSTR, anti-snipe decay, process(), claims, exclusions, reentrancy, slippage limits.\n3. Short self-audit: findings by severity, all fixes applied.\n4. Short README: deploy, add single-sided liquidity, call process() on a schedule (hourly is enough), claim.\nOut of scope: website, heartbeat, Sepolia deploy.","parentJobId":null,"planHash":"503beb1d97668b8c6e0daec463d384f55af1cb4056ed55230da9c81cb97e803e","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"e497ebcf-33bb-4548-bfd7-fa95843dc898","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-713-task-single-job-do"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51487","feedbackHash":"3367b4c5db1ad941c6bce1204b11a360ca9524bc29ce21688abe89c41f8d4617","nodeKey":"audit_economics","submissionHash":"acde13e2d83688f167a5ccb838f51fc0489013ded31876489589c6e074f74216","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52129","feedbackHash":"ce3e045b04fb2f6ce6e97c8f4dd1ca573764b80b1596dba7a9edd1af4ec1df43","nodeKey":"audit_flow","submissionHash":"687f0c66c08b4fa689e0d33a4f7c589be05c9cb40d78d6257810b6c275805490","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51130","feedbackHash":"082806d0a06fe271574f4441d42e8b7b9746153913290c3a6e0192f9c57449b7","nodeKey":"audit_judge","submissionHash":"15b14dc90e6c7c88cdf4145c80bb9cd427576eb4a527ffd00f12e33103f0ea43","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52099","feedbackHash":"e733b9b56ba260a7c84ea109ccffa3d5811cafc525acfbf2edca211ef8ca9bf5","nodeKey":"audit_judge","submissionHash":"255ba8767715c65f86c69b6ba77d0cd410b7b67ef20cd57e41b654617b396fd0","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51214","feedbackHash":"0056334b0a30eeffac83f9c922f8a9e6d679a6531ba35795c0fee614e4809ead","nodeKey":"audit_judge","submissionHash":"b7c3165a027131fb81d9ec95376464e53d03c798006e8ae8241fb7ed63a7a84b","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50995","feedbackHash":"b8ce3a82f22ceee6a7143e27426a980f2175c811a405a5802e6e78043f3e9a72","nodeKey":"audit_math","submissionHash":"84edd2a8d0b8e9474d2ef9260182c985d5db5c6a25f357d3f77199cc84cb5386","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51225","feedbackHash":"5a19c861e01cd3eb8ca0f5e5e11d8d758b23f03d95ba47fbe4141722157ceadd","nodeKey":"audit_permissions","submissionHash":"9d97c650305d25a510139a6add82bde20f37d53a7039a2da9e6a117bcdae6749","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52136","feedbackHash":"3eeeaae8609c0c9b365f25979b1b72cdaa96c80cf768b5d9c2882e460126504d","nodeKey":"build_contract_project","submissionHash":"5b9e1fb9e8a151d291efeb716b69ef1a8734dcd8a64f88f0c924dcfd15bd8e03","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52013","feedbackHash":"1c70b9e1a3df1c6191de58ea26e56dca1b84fb70ca706c0f9767d549c4a280c7","nodeKey":"build_contract_project","submissionHash":"72f40544efdce3ea61a2bd644d00de6769ab660bc6d2d8a0e5ccd606b0d9202a","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51876","feedbackHash":"8a2e92801e5978fd2ae2b9a2f7a4b294ea0ca9f04cc627fd9c5f8d5ff6dc3e65","nodeKey":"build_contract_project","submissionHash":"6e6e4fa7150d4ba1f6e78a08391dbec1d641cc5515d2243bda2291fc0c402fac","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52086","feedbackHash":"d300ac8ba79c7b6399a447a0e23a1e3ac4e11c84cbfcad939b80bead926feddf","nodeKey":"manifest","submissionHash":"d3b4d16d95bb06c4ed2831fed25afbbaa330d908b9825b4177a451148b18d1c3","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52135","feedbackHash":"ac7b3315a9a7c1795cd55eba281323a23affb119fe3e8e169b7cdbb99704505b","nodeKey":"manifest","submissionHash":"9e92b0d21f8138def6ff112de1f3054e97e0863c48557217ec21429d95306a75","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52092","feedbackHash":"251bfde3a8eb117a366cb314b7e4fa5d4bdfd6e165faa9a86d6e06f428d99eb9","nodeKey":"manifest","submissionHash":"e7f58dc73d9860d08e56e29288a9b7481beaffdf2c44c538b50bd29c14702f56","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52138","feedbackHash":"46233b943eceac725fbbcd6f8b2e52298a81ca93d35a1f4ae699049cc082163f","nodeKey":"write_foundry_tests","submissionHash":"477f556b3bb8c1e15470fee04b46bddb4f4ef839f58d38719cca7df25cef433b","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51313","feedbackHash":"518acb549d9ca96558a2abe39c7ec855d45ae223174c7473b63cdc907a58c705","nodeKey":"write_foundry_tests","submissionHash":"cc32cb683589902d7f5495130032aff694ba0d042ea1424adb74a3978b58225e","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51556","feedbackHash":"66d8fdc24030d3c5ddbb68099d881b455de12708d507f66ab4eafd4a53408eff","nodeKey":"write_foundry_tests","submissionHash":"773defab701f3a5c0ed179a7f0276fab44bca6446e452f13e0277df9de3d21dc","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"9f6d22f5a60cb9a01fd42d6772fe4a4a7827d1e32f8de65f22da3175ea9b2141","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"0499829b879ec3d5","findings":[],"hash":"09416b2814c78918105b5b06499543a9730adb65c4926a3a694f4d29bad996a4","nodeId":"0a8840e7-8209-4552-8c32-9e63aa3b3eb9","outcome":"failed","summary":"This content was flagged for possible cybersecurity risk. If this seems wrong, try rephrasing your request. If you’re doing authorized security work that requires more cyber permissive safeguards, apply for Daybreak access via https://platform.openai.com/settings/organization/status-and-access before retrying.","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":"gpt-6-astra","outputTokens":0,"runtime":"codex","turns":0,"wallClockMs":20104}},{"artifacts":[],"attempt":2,"bundleHash":null,"device":"232b62e021f6f394","findings":[],"hash":"15b14dc90e6c7c88cdf4145c80bb9cd427576eb4a527ffd00f12e33103f0ea43","nodeId":"0a8840e7-8209-4552-8c32-9e63aa3b3eb9","outcome":"completed","summary":"Both findings I raised last round are settled, and I report no new ones. The findings file holds an empty list plus a coverage row for all 20 entry points, each marked as holding.\n\n- **Post-factory hook deployment (fixed).** With an existing Treasury supplied, the deploy script now deploys only the hook against it. The author's new test passes: fees from the hooked pool reach the factory-deployed Distributor, and a staker claims both reward tokens. A mismatched token, mismatched reward tokens, and a liquidity amount above the deployer's holding all revert.\n- **Wallet holders earn nothing without staking (dropped).** My proof still fails exactly as before, so the behaviour is unchanged. The dispute is right, though: the launch rules require a plain token with no transfer hook, and those rules override the brief. I did not have the author's exploit test, but the claim-transfer-claim argument holds on reasoning. The README states the deviation and why.\n\nThe four specialist proofs that were attached all pass on the current code: three for the dust-staker sweep and one for the partial-fill fee. The other specialist findings date from earlier rounds; I confirmed by reading the code that the `exit()` revert and exact-output fee basis are fixed. The 119 offline tests pass.\n\n- **Not verified: the mainnet-fork suite.** It skips without an RPC URL, and the public endpoint I tried has no state for the pinned block. It is recorded as unreached in the coverage record.\n- **Known limitation, not raised:** a Treasury buy leg stays blocked while its reward token trades more than the tolerance above the last checkpoint price. One blocked leg reroutes to the other after three days. If both stay blocked, the ETH waits with no owner override. The self-audit documents the blocking and the absence of an override.\n\nNo tracked files were changed, and the scratch copies of the proofs are removed.","treeHash":null,"usage":{"cachedInputTokens":841727,"inputTokens":18,"model":"claude-fable-5-1","outputTokens":9082,"runtime":"claude","turns":12,"wallClockMs":770994}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"d3d81960929eb1a1","findings":[{"citation":"resolved","description":"Prior finding 151f8d76cb2517fba31496f4bb833bb3e18b3d33421edf8044a4a5d90894e74c is partially fixed. Removing AdamHook from launch.json removes the unmined-constructor failure, but the replacement path described in launch.json notes is not implemented by the cited script. The notes promise step 4 deploys AdamHook against the factory-deployed Treasury. In fact run() calls deployContracts(), which unconditionally creates a new Distributor here and a new Treasury at line 182, then encodes that new Treasury into the hook at line 201. Config accepts an existing ADAM token, but has no existing Treasury/Distributor option or hook-only deployment entry. A holder who stakes in the manifested Distributor therefore receives none of the fees generated by the script's new hooked pool. The factory pool itself still has no AdamHook swap callbacks, as the author correctly documents. This is an unresolved deployment integration guarantee, downgraded from high because the constructor blocker is removed and a correct manual deployment is possible. Provide a concrete hook-only deployment path accepting the existing Treasury and the owner's actual liquidity allocation, and test that its fees reach the manifested Distributor; alternatively obtain an explicit resolution of the launch-path specification conflict. Merely describing future manual steps in manifest notes does not execute them.","line":180,"path":"script/DeployAdam.s.sol","reproduction":"Ran forge test --match-path 'test/scratch/Review*.t.sol' -vv. ReviewDeployment.test_scriptExistingTokenDeploysNewPipeline uses the real DeployAdam.deployContracts with the local v4 PoolManager/reward pools: deploy the original pipeline, set cfg.adamToken to its ADAM address, then call deployContracts(cfg) as the claimed post-launch path. Expected: new hook.treasury() equals the existing Treasury and its distributor() equals the existing Distributor. Actual: the token is reused, but existing Treasury is 0xf4B037B2017C7cD58BcF45682fa08D0433C6c417 and hook.treasury() is 0x9d2CC36AFb7024019cec0BEAAd5322d5c73b89Ae; the Distributor is also newly deployed. The immutable hook forwards fees only to this new pair. The companion test_poolWithoutAdamHookDoesNotFundTreasury seeded a hookless ETH/ADAM pool (fee 3000, spacing 60, tick 177240, 800,000,000e18 single-sided ADAM); a 1 ETH buy returned 46817648057091787341585693 ADAM with Treasury ETH increase 0. This models the relevant absence of swap callbacks, not a claim that the external factory was executed locally. The 32-unmined-salt probe also reproduced 0 successful AdamHook deployments, but that constructor problem is no longer retained against the revised manifest.","severity":"medium","snippet":"        d.distributor = new AdamDistributor(address(d.token), cfg.imd, cfg.pnkstr, cfg.poolManager, address(0));","title":"The proposed post-factory hook deployment does not reuse the manifested reward pipeline"},{"citation":"resolved","description":"Confirmed the independent test writer's wallet-eligibility finding. The requested distribution is pro rata to ADAM wallet balances, updated on transfer. _pending instead uses only stakedBalance, and LaunchToken inherits plain ERC20 transfers without distributor accounting. Consequently even the sole eligible wallet holder has no reward entitlement. The backlog-stream fix correctly prevents the old dust sweep; it does not repair this separate eligibility mismatch. README disclosure accurately describes the staking model but does not establish requester approval to replace wallet dividends. Implement the requested wallet-balance accounting or explicitly resolve the specification/platform conflict before representing the staking implementation as satisfying it. This is a broken functional guarantee, not a claim of a new custody exploit.","line":222,"path":"src/AdamDistributor.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {AdamDistributor} from \"src/AdamDistributor.sol\";\n\ncontract ReviewWalletEligibility is Test {\n    function test_walletHolderCanClaimProRataRewards() public {\n        LaunchToken adam = new LaunchToken();\n        LaunchToken imd = new LaunchToken();\n        LaunchToken pnk = new LaunchToken();\n        AdamDistributor distributor = new AdamDistributor(\n            address(adam), address(imd), address(pnk), address(0x9000), address(0)\n        );\n        address alice = address(0xA11CE);\n        adam.transfer(alice, adam.totalSupply());\n        imd.approve(address(distributor), 100e18);\n        pnk.approve(address(distributor), 200e18);\n        distributor.notifyReward(address(imd), 100e18);\n        distributor.notifyReward(address(pnk), 200e18);\n        vm.prank(alice);\n        distributor.claim();\n        emit log_named_uint(\"Alice IMD\", imd.balanceOf(alice));\n        emit log_named_uint(\"Alice PNKSTR\", pnk.balanceOf(alice));\n        emit log_named_uint(\"Unallocated IMD\", distributor.unallocated(address(imd)));\n        assertApproxEqAbs(imd.balanceOf(alice), 100e18, 1, \"wallet holder lost her IMD entitlement\");\n        assertApproxEqAbs(pnk.balanceOf(alice), 200e18, 1, \"wallet holder lost her PNKSTR entitlement\");\n    }\n}","reproduction":"Ran forge test --match-path 'test/scratch/Review*.t.sol' -vv. ReviewWalletEligibility.test_walletHolderCanClaimProRataRewards deploys LaunchToken, two fixed-supply ERC20 reward stand-ins, and AdamDistributor; transfers the entire 1e27 ADAM supply to Alice (0xA11CE), who is not excluded; approves and notifies 100e18 IMD and 200e18 PNKSTR; then Alice calls claim() without staking. Expected under the brief: Alice receives 100e18 and 200e18, within one wei. Actual: both payouts are 0, and 100e18 IMD remains unallocated. The assertion fails with 'wallet holder lost her IMD entitlement: 0 !~= 100000000000000000000'.","severity":"medium","snippet":"        uint256 staked = stakedBalance[account];\n        if (staked == 0) return 0;","title":"Ordinary ADAM wallet holders still receive no dividends without staking"}],"hash":"255ba8767715c65f86c69b6ba77d0cd410b7b67ef20cd57e41b654617b396fd0","nodeId":"0a8840e7-8209-4552-8c32-9e63aa3b3eb9","outcome":"completed","summary":"Wrote [.imd-findings.json](/root/.identitymd-356/work/e497ebcf-33bb-4548-bfd7-fa95843dc898/0a8840e7-8209-4552-8c32-9e63aa3b3eb9/.imd-findings.json) with two reproduced medium findings:\n\n- Post-factory deployment creates a new reward pipeline instead of reusing the manifested contracts.\n- Unstaked wallet holders receive no dividends.\n\nTen prior findings are resolved; deployment integration remains partially fixed. Coverage includes all 20 entry points.\n\nPassed: 111 offline checks, seven supplied proofs, six mainnet-fork tests. Production files unchanged.","treeHash":null,"usage":{"cachedInputTokens":2465152,"inputTokens":176479,"model":"gpt-6-astra","outputTokens":11560,"runtime":"codex","turns":5,"wallClockMs":560486}},{"artifacts":[],"attempt":1,"bundleHash":"a3817fdae5e364d1e7c5fb575e309d9848cc9808df8ae416d6883732af8fd89c","device":"62491201fe455e89","findings":[{"description":"quoteMinOut() reads sqrtPriceX96 from getSlot0 at the moment unlockCallback runs and derives minOut as spot-after-fees-and-tax minus slippageBps. process() is public, so the same transaction can first move the pool price and then call process(): minOut is recomputed from the moved price and the 3% tolerance is measured against that price. The 'slippage cap' the brief asked for therefore does not bound what the treasury pays; only maxEthPerBuy and the reward pools' own depth and fees do. Measured on the local environment (200 ETH IMD pool): after a 100 ETH pump the treasury accepted 44% of the output it had quoted one call earlier. Measured on a mainnet fork of the real IMD pool (block 26126378, liquidity 1.03e22, 1% LP fee + 0.1% protocol fee): pumps of 1/5/20/50/100 ETH all lose the attacker ETH (-0.019/-0.094/-0.37/-0.92/-1.79 ETH) while the treasury receives 102/101/96/88/76% of its pre-pump floor. So today this is a griefing vector with bounded loss (at most 2 ETH per 10-minute cooldown, far less than the attacker pays), not a profitable extraction; it becomes profitable if IMD pool depth shrinks by roughly 15x or a shallower pool is ever configured. Suggested fix: bound the executed price against a reference that the caller cannot move in the same block, e.g. store the sqrtPrice observed at the previous successful buy of each leg and refuse the leg if the current price deviates more than a fixed percentage from it (the keeper then retries next cooldown), or quote minOut off-chain and pass it in with a signed keeper role. Keep slippageBps as the per-call tolerance on top of that reference.","line":272,"path":"src/AdamTreasury.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"@uniswap/v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"@uniswap/v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {Currency, CurrencyLibrary} from \"@uniswap/v4-core/src/types/Currency.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"@uniswap/v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"@uniswap/v4-core/src/libraries/TickMath.sol\";\nimport {PoolSwapTest} from \"@uniswap/v4-core/src/test/PoolSwapTest.sol\";\nimport {PoolModifyLiquidityTest} from \"@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {LiquidityAmounts} from \"@uniswap/v4-periphery/src/libraries/LiquidityAmounts.sol\";\nimport {MockERC20} from \"solmate/src/test/utils/mocks/MockERC20.sol\";\n\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {AdamDistributor} from \"src/AdamDistributor.sol\";\nimport {AdamTreasury} from \"src/AdamTreasury.sol\";\n\n/// @notice Proof for: AdamTreasury.quoteMinOut anchors the slippage floor to the pool's *current* spot price,\n/// which the caller of process() can move in the same transaction. The configured 3% tolerance therefore\n/// bounds nothing: after a 100 ETH pump the treasury accepts ~44% of the pre-pump output.\n///\n/// Expected (a floor that is independent of same-block price manipulation, or a refusal): either the IMD\n/// leg refuses to buy, or the distributor receives at least the floor quoted *before* the manipulation.\n/// Actual: the leg buys at the manipulated price and the distributor receives far less than that floor.\ncontract ProofSpotAnchoredMinOut is Test {\n    PoolManager internal pm;\n    PoolSwapTest internal router;\n    PoolModifyLiquidityTest internal lp;\n    MockERC20 internal imd;\n    MockERC20 internal pnkstr;\n    PoolKey internal imdKey;\n    PoolKey internal pnkKey;\n    AdamDistributor internal dist;\n    AdamTreasury internal treasury;\n\n    receive() external payable {}\n\n    function setUp() public {\n        pm = new PoolManager(address(this));\n        router = new PoolSwapTest(pm);\n        lp = new PoolModifyLiquidityTest(pm);\n        imd = new MockERC20(\"IMD\", \"IMD\", 18);\n        pnkstr = new MockERC20(\"PNKSTR\", \"PNKSTR\", 18);\n        imd.mint(address(this), 1e36);\n        pnkstr.mint(address(this), 1e36);\n        imd.approve(address(lp), type(uint256).max);\n        pnkstr.approve(address(lp), type(uint256).max);\n        imd.approve(address(router), type(uint256).max);\n        vm.deal(address(this), 10_000 ether);\n\n        imdKey = PoolKey(CurrencyLibrary.ADDRESS_ZERO, Currency.wrap(address(imd)), 10_000, 200, IHooks(address(0)));\n        pnkKey = PoolKey(CurrencyLibrary.ADDRESS_ZERO, Currency.wrap(address(pnkstr)), 0, 60, IHooks(address(0)));\n        _initAndSeed(imdKey, 54_000, -887_200, 887_200, 200 ether);\n        _initAndSeed(pnkKey, 120_420, -887_220, 887_220, 200 ether);\n\n        LaunchToken adam = new LaunchToken();\n        dist = new AdamDistributor(address(adam), address(imd), address(pnkstr), address(pm), address(0));\n        treasury = new AdamTreasury(\n            address(dist),\n            makeAddr(\"team\"),\n            address(pm),\n            address(imd),\n            10_000,\n            200,\n            address(0),\n            0,\n            address(pnkstr),\n            0,\n            60,\n            address(0),\n            0,\n            1 ether, // maxEthPerBuy\n            300, // slippageBps: 3%\n            0 // cooldown\n        );\n    }\n\n    function _initAndSeed(PoolKey memory key, int24 tick, int24 lower, int24 upper, uint256 ethAmount) internal {\n        uint160 sqrtP = TickMath.getSqrtPriceAtTick(tick);\n        pm.initialize(key, sqrtP);\n        uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(\n            sqrtP, TickMath.getSqrtPriceAtTick(lower), TickMath.getSqrtPriceAtTick(upper), ethAmount, type(uint128).max\n        );\n        lp.modifyLiquidity{value: ethAmount}(\n            key, ModifyLiquidityParams(lower, upper, int256(uint256(liquidity)), 0), \"\"\n        );\n    }\n\n    function _swap(PoolKey memory key, bool zeroForOne, int256 amount, uint256 value) internal {\n        router.swap{value: value}(\n            key,\n            SwapParams(zeroForOne, amount, zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1),\n            PoolSwapTest.TestSettings(false, false),\n            \"\"\n        );\n    }\n\n    function test_slippageFloorDoesNotSurviveSameBlockPriceManipulation() public {\n        (bool ok,) = payable(address(treasury)).call{value: 1 ether}(\"\");\n        require(ok);\n        // The floor the treasury itself computes for its 0.45 ETH IMD buy, before anyone touches the pool.\n        uint256 floorBeforeManipulation = treasury.quoteMinOut(0, 0.45 ether);\n\n        // Same transaction, any caller: pump IMD with 100 ETH against ~200 ETH of liquidity ...\n        _swap(imdKey, true, -int256(100 ether), 100 ether);\n        // ... then have the treasury buy. Its minOut is re-quoted from the pumped spot price.\n        treasury.process();\n        // (the attacker would now sell back; omitted, it does not change what the treasury received)\n\n        uint256 received = imd.balanceOf(address(dist));\n        bool refused = treasury.leg(0).pending == 0.45 ether && received == 0;\n        assertTrue(\n            refused || received >= floorBeforeManipulation,\n            \"treasury bought below its own pre-manipulation floor: slippage cap is anchored to a manipulable spot\"\n        );\n    }\n}","reproduction":"Local: fund treasury 1 ETH; fairMin = quoteMinOut(0, 0.45 ether) = 95.65e18 IMD; swap 100 ETH -> IMD on the IMD pool; process(). Expected: IMD leg refuses or distributor receives >= 95.65e18. Actual: distributor receives 44.05e18 (44% of the floor), leg marked healthy. Fork: FOUNDRY_PROFILE=fork forge test --match-test test_measureSandwichAroundProcessOnRealImdPool -vv prints the attacker's loss and the received percentage on live state.","severity":"medium","title":"Treasury slippage floor is anchored to the manipulable in-block spot price, so the configured tolerance bounds nothing"},{"description":"beforeSwap computes the fee from params.amountSpecified and takes it before the pool swaps. If the pool fills only part of the request (price limit reached, or no ETH liquidity in range) the fee is still the full percentage of the request. Two concrete cases: (1) at launch the pool holds no ETH (single-sided ADAM); a holder who received ADAM outside the pool (the factory's swarm/requester allocation exists from day one) submits an exact-output sell of 1 ETH. The pool delivers 0 ETH and consumes 0 ADAM, but the hook takes 20% of 1 ETH = 0.2 ETH, and the seller's ETH delta is -0.2 ETH: they pay 0.2 ETH for nothing if their router can settle a negative output delta. (2) An exact-input buy of 10 ETH with a sqrtPriceLimit one tick below spot swaps 0.062 ETH into the pool yet pays 0.15 ETH fee (about 240% of the amount actually swapped). The Universal Router rejects both outcomes (TAKE_ALL reverts on a negative delta; it uses no price limit), so ordinary users are not affected, but any integrator calling PoolManager.swap with a limit or handling partial fills overpays. Suggested fix: for the specified-ETH cases, compute the fee in afterSwap from the actual delta (as the other two cases already do) and return it via afterSwapReturnDelta, or clamp the beforeSwap fee to the filled amount.","line":166,"path":"src/AdamHook.sol","reproduction":"Local env (LocalV4), at launch: deal alice 1e24 ADAM; alice swaps zeroForOne=false, amountSpecified=+1 ether, limit MAX_SQRT_PRICE-1, sending 0.5 ETH to PoolSwapTest. Expected: fee proportional to ETH actually received (0). Actual: delta0 = -0.2 ETH, delta1 = 0, treasury +0.2 ETH, alice ETH -0.2 ETH, alice ADAM unchanged. Second case after the decay: alice swaps zeroForOne=true, amountSpecified=-10 ether, sqrtPriceLimitX96 = price at (currentTick - 60). Expected: fee ~1.5% of the 0.062 ETH swapped (~0.0009 ETH). Actual: treasury +0.15 ETH, alice paid 0.212 ETH.","severity":"low","title":"Hook charges the fee on the requested ETH amount, not the filled amount, for exact-input buys and exact-output sells"},{"description":"Any ETH amount is split and executed, including amounts where the pool's fee rounding makes the output fall below minOut (for the 1%-fee IMD pool, inputs of a few wei: the pool rounds 9 wei to 8 wei of input, more than the 3% tolerance). Such a call records failingSince for a perfectly healthy leg. failingSince is only cleared by a successful buy, and the reroute fires on any failure that happens 3 days or more after it. So: an attacker sends 10 wei and calls process() during a quiet period; if no real inflow succeeds in the next 3 days and the next real attempt fails once for a transient reason (brief volatility beyond 3%, a temporary PNKSTR hook change), the whole IMD share is rerouted to PNKSTR immediately instead of after 3 days of genuine failure. No funds are lost and the split is only skewed, but the 3-day safety delay is defeated. Suggested fix: ignore legs whose ethIn is below a minimum (e.g. skip execution and keep the wei pending when ethIn < 1e12), and/or restart failingSince when the leg has succeeded or when pending was zero at the time of the failure.","line":313,"path":"src/AdamTreasury.sol","reproduction":"LocalV4: send 10 wei to the treasury, call process(). Expected: a healthy pool is not marked failing (or dust is skipped). Actual: leg(0).pending = 4, leg(0).failingSince = block.timestamp, leg(1) executed (PNKSTR pool has fee 0). Then warp 3 days, fund 1 ETH, make the IMD leg fail once (e.g. a 4% price move in the IMD pool in the same block) and call process(): the IMD share is rerouted to PNKSTR on that first real failure.","severity":"low","title":"Dust-sized process() calls mark a healthy leg as failing and can trigger an immediate reroute on a later transient failure"},{"description":"exit() calls _unstake(stakedBalance[msg.sender]) first, which reverts with ZeroAmount when the stake is zero, before _claim runs. A holder who unstaked in full (rewards stay claimable by design) cannot use exit() to collect them and must know to call claim() instead. Funds are not at risk; the entry point documented as 'withdraw all and claim' simply fails in a legitimate state. Suggested fix: in exit(), only unstake when stakedBalance > 0.","line":121,"path":"src/AdamDistributor.sol","reproduction":"Stake, let rewards arrive, unstake(fullStake). earned() > 0. Call exit(). Expected: rewards paid. Actual: revert ZeroAmount(). claim() succeeds.","severity":"low","title":"exit() reverts for a holder who has already unstaked everything but still has accrued rewards"},{"description":"The forceApprove + distributor.notifyReward calls run inside the try block's success branch, outside the catch. If IMD or PNKSTR ever reverts on transferFrom from the treasury (pause, blocklist, upgrade), process() reverts as a whole; the treasury has no withdrawal path and all parameters are immutable, so every fee from then on is locked. Both tokens are plain today (the fork test checks PNKSTR has no transfer tax), so this is a dependency note, not a defect. If an escape hatch is wanted, moving the push into the try/catch and keeping bought tokens in the treasury (flushRewards already exists) would keep the ETH side alive.","line":309,"path":"src/AdamTreasury.sol","reproduction":null,"severity":"info","title":"A reward-token transfer failure inside the success branch of _executeLeg reverts process() and would strand ETH permanently"},{"description":"The brief lists hook and Treasury among the excluded addresses. The deploy script passes address(0) as excludedExtra because both are deployed after the distributor. Neither contract has any code path that could call stake(), so there is no exploitable effect; noted only because the written exclusion list differs from the brief.","line":84,"path":"src/AdamDistributor.sol","reproduction":null,"severity":"info","title":"Distributor exclusion set does not contain the hook or the treasury"},{"description":"The tree contains contracts, script and tests but no README.md or audit notes, although the contract comments refer to a README section ('What differs from the brief'). The brief asks for rewards 'pro-rata to balance, updated on transfer'; the implementation requires holders to stake ADAM in the distributor, because the launch token must stay a plain ERC-20 without transfer hooks. Consequences worth stating to holders: unstaked ADAM earns nothing, and rewards that arrive while nobody is staked are parked and go entirely to whoever is staked at the next notifyReward. These are design decisions, not defects, but they must be documented for the launch.","line":17,"path":"src/AdamDistributor.sol","reproduction":null,"severity":"info","title":"Deliverables 3 and 4 (self-audit, README) are not in the working tree, and the distribution model is staking rather than balance-tracking"}],"hash":"477f556b3bb8c1e15470fee04b46bddb4f4ef839f58d38719cca7df25cef433b","nodeId":"c351a697-c3a7-425f-b397-d518567526f6","outcome":"completed","summary":"The findings file is excluded by the harness's own `.git/info/exclude`, alongside the reads directory, so it is collected from the working tree rather than committed. Nothing else is pending.\n\n## Recap\n\n**Delivered** (all under `test/`, plus the findings file the task asked for at the repo root):\n\n- `test/invariant/DistributorInvariant.t.sol`: a six-actor handler (whale to 1-wei) over stake, unstake, claim, exit, notify, direct donations and excluded-address attempts, with ten invariants: stake sums, ADAM custody, per-actor ADAM conservation, notified rewards fully accounted, solvency with a dust bound of one wei per settlement, claims never exceeding distribution, donations never credited, monotone accumulator, excluded set never staking.\n- `test/invariant/TreasuryInvariant.t.sol`: a handler over the real local v4 stack driving all four swap types through the hook (fee asserted against the decay schedule per swap), funding, `process()`, `payTeam()`, `flushRewards()`, staking and claiming, time, fee lowering and raise attempts, and the PNKSTR hook changing tax or dying. Postconditions per call are parsed from the treasury's events (per-leg buys, failures, reroutes, max size per call). Eleven invariants cover ETH conservation, exact 10/90 split, holders' share never leaking, PoolManager ETH matching the hook's own accounting, no stray assets in hook or treasury, ADAM supply location, reward-token conservation through to claims, distributor solvency, fee bounds, and leg-state consistency.\n- `test/unit/FailurePaths.t.sol`: 38 edge and failure-path tests: one-wei and odd-wei splits, exact cap and cooldown boundaries, backlog drained at most max-per-leg per call, 3-day reroute boundary to the second, gas-burning team wallet, a hostile pool hook re-entering every treasury entry point during the treasury's own swap, third-party unlock, protocol-fee-aware quotes, zero and maximum slippage, fee rollback on reverted swaps, anti-snipe on sells and exact-output buys, dust rounding, pending-owner and renounced-owner cases, one-wei staker with a 1e33 reward, stray ADAM, and a 1000-run pro-rata fuzz.\n- `test/fork/MainnetFork.t.sol`: fixed three pre-existing assertions that fail against live state (ADAM rounding dust, one-wei claim rounding, and a wrong assumption about where the PNKSTR hook keeps its tax) and added three fork tests, including a sandwich measurement on the real IMD pool. The fork suite passed 8/8 at block ~26126400. It stays excluded from the default profile and needs network.\n\n**Results**: `forge build` clean; default suite 117 passed, 0 failed, with both invariant suites at 0 handler reverts under fail-on-revert.\n\n**Findings reported** in `.imd-findings.json` (7):\n\n- **Medium**: the treasury's slippage floor is re-quoted from the spot price inside the same transaction, so a caller can pump before `process()` and the 3% tolerance bounds nothing. Locally the treasury accepted 44% of its own prior floor. On the real IMD pool today every pump size loses the attacker ETH, so damage is bounded griefing, not extraction. A self-contained failing proof is attached.\n- **Low**: the hook charges fee on the requested ETH for exact-input buys and exact-output sells, so partial fills overpay (0.2 ETH paid for 0 ETH received at launch; 0.15 ETH fee on 0.062 ETH swapped).\n- **Low**: a dust-sized `process()` marks a healthy leg as failing, which can defeat the 3-day reroute delay.\n- **Low**: `exit()` reverts for a holder who already unstaked fully but has rewards; `claim()` works.\n- **Info**: reward push sits outside the try/catch, exclusion set omits hook and treasury, and the README and self-audit deliverables are absent from the tree while the staking model deviates from the brief.\n\n**Still owed and not runnable without network**: nothing beyond the fork suite, which I was able to run here.","treeHash":"ea8339014c49d33de0481922668f87b0ba87c67c","usage":{"cachedInputTokens":7671177,"inputTokens":1122,"model":"claude-fable-5-1","outputTokens":152746,"runtime":"claude","turns":93,"wallClockMs":2723343}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"de319b702da6aa2e","findings":[],"hash":"4958275ae6f681f53e9a17b3a5a6c9280ade704f623028f73b52a846a9a404f9","nodeId":"e6d2de9d-71ab-46aa-af44-65333fb63a28","outcome":"failed","summary":"runtime reported <synthetic>, not the required premium model claude-opus-5-5","treeHash":null,"usage":{"cachedInputTokens":906595,"inputTokens":24,"model":"<synthetic>","outputTokens":29318,"runtime":"claude","turns":16,"wallClockMs":520177}},{"artifacts":[],"attempt":1,"bundleHash":"a12994045f82034ee5a7e6c0ef2642aed9cead15ca8e024d1856daf1b97ee6dd","device":"00920b27421b9a80","findings":[],"hash":"5b9e1fb9e8a151d291efeb716b69ef1a8734dcd8a64f88f0c924dcfd15bd8e03","nodeId":"ea5f0fc8-acff-4ba1-9a1d-ebc099b7a52f","outcome":"completed","summary":"","treeHash":"0d6cebb40bd523a4c5d86c1f4b26f0dcc32ce2ad","usage":{"cachedInputTokens":8960469,"inputTokens":1890,"model":"claude-fable-5-1","outputTokens":154771,"runtime":"claude","turns":61,"wallClockMs":3032892}},{"artifacts":[],"attempt":2,"bundleHash":null,"device":"93c37f17670e4d98","findings":[],"hash":"6116c73637613acba53aa949d7f198cdbf2c1ba4d0550cde7306daacf1fecf86","nodeId":"e6d2de9d-71ab-46aa-af44-65333fb63a28","outcome":"failed","summary":"runtime reported <synthetic>, not the required premium model claude-fable-5-1","treeHash":null,"usage":{"cachedInputTokens":253785,"inputTokens":130,"model":"<synthetic>","outputTokens":27977,"runtime":"claude","turns":23,"wallClockMs":725020}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"63c29c49a249ab7e","findings":[{"citation":"resolved","description":"AdamDistributor._distribute parks every reward that arrives while totalStaked == 0 in `unallocated[token]` and then folds the entire parked amount into the very next distribution, pro-rata to whoever happens to be staked at that moment. `stake()` and `notifyReward()` are both permissionless (the latter by design, see the Trust model comment), there is no minimum stake, no eligibility delay and no streaming. At launch nobody is staked yet (the project's own test_processSplitsTenNinetyAndBuysBothTokens documents that `process()` output waits in `unallocated`), and the launch window is exactly when the treasury earns the most (20% anti-snipe fee decaying to 1.5%). Anyone can call `process()` during that window so the fee ETH becomes IMD/PNKSTR sitting in `unallocated`, then stake 1 wei of ADAM and call `notifyReward(token, 1)` with 1 wei of the reward token: `_distribute` computes distributed = 1 + unallocated and credits all of it to the single 1 wei stake, which the attacker then claims. Every real holder who stakes afterwards gets nothing from the launch-period fees. This breaks the brief's 'distributed to ADAM holders pro-rata to balance' and pays holder funds to the wrong party at a cost of 1 wei ADAM, 1 wei of each reward token and three transactions. Design-preserving fixes: only fold `unallocated` in once totalStaked is at least a fixed minimum (e.g. a fraction of supply), or stream it over a period, or have the Treasury hold rewards until a minimum stake exists.","line":165,"path":"src/AdamDistributor.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\n\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {AdamDistributor} from \"src/AdamDistributor.sol\";\n\n/// @dev Minimal mintable ERC-20 standing in for IMD / PNKSTR (no external dependencies).\ncontract RewardToken {\n    string public name;\n    string public symbol;\n    uint8 public constant decimals = 18;\n    uint256 public totalSupply;\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n\n    constructor(string memory n) {\n        name = n;\n        symbol = n;\n    }\n\n    function mint(address to, uint256 amount) external {\n        totalSupply += amount;\n        balanceOf[to] += amount;\n    }\n\n    function approve(address spender, uint256 amount) external returns (bool) {\n        allowance[msg.sender][spender] = amount;\n        return true;\n    }\n\n    function transfer(address to, uint256 amount) external returns (bool) {\n        balanceOf[msg.sender] -= amount;\n        balanceOf[to] += amount;\n        return true;\n    }\n\n    function transferFrom(address from, address to, uint256 amount) external returns (bool) {\n        uint256 a = allowance[from][msg.sender];\n        if (a != type(uint256).max) allowance[from][msg.sender] = a - amount;\n        balanceOf[from] -= amount;\n        balanceOf[to] += amount;\n        return true;\n    }\n}\n\n/// @notice Rewards that arrive while nobody is staked are parked in `unallocated` and then handed, in full,\n/// to whoever is staked at the moment of the next `notifyReward`. Both `notifyReward` and `stake` are\n/// permissionless, so a 1 wei staker can take the whole launch-period pot with a 1 wei notify.\n///\n/// Fails on the current code: the attacker ends up with the entire 1,000 IMD pot although they hold one wei\n/// of a 1e27 supply. Passes once the unallocated pot is no longer released to an arbitrarily small stake\n/// (e.g. only folded in once a minimum total stake exists, or streamed instead of dumped).\ncontract UnallocatedCaptureTest is Test {\n    LaunchToken internal adam;\n    RewardToken internal imd;\n    RewardToken internal pnkstr;\n    AdamDistributor internal dist;\n\n    address internal treasury = makeAddr(\"treasury\");\n    address internal attacker = makeAddr(\"attacker\");\n    address internal alice = makeAddr(\"alice\");\n\n    uint256 internal constant POT = 1_000e18;\n\n    function setUp() public {\n        adam = new LaunchToken();\n        imd = new RewardToken(\"IMD\");\n        pnkstr = new RewardToken(\"PNKSTR\");\n        dist = new AdamDistributor(address(adam), address(imd), address(pnkstr), makeAddr(\"poolManager\"), address(0));\n\n        // Launch period: the treasury's process() has already bought POT of IMD and pushed it while nobody\n        // was staked (this is exactly what the project's own test_processSplitsTenNinetyAndBuysBothTokens shows).\n        imd.mint(treasury, POT);\n        vm.startPrank(treasury);\n        imd.approve(address(dist), POT);\n        dist.notifyReward(address(imd), POT);\n        vm.stopPrank();\n        assertEq(dist.unallocated(address(imd)), POT);\n\n        // Alice is a real holder with 1% of the supply; the attacker holds one wei of ADAM and one wei of IMD.\n        adam.transfer(alice, 10_000_000e18);\n        adam.transfer(attacker, 1);\n        imd.mint(attacker, 1);\n    }\n\n    function test_oneWeiStakerCannotTakeTheWholeUnallocatedPot() public {\n        // Attacker: stake 1 wei, then notify 1 wei to trigger the fold-in while they are the only staker.\n        vm.startPrank(attacker);\n        adam.approve(address(dist), 1);\n        dist.stake(1);\n        imd.approve(address(dist), 1);\n        dist.notifyReward(address(imd), 1);\n        vm.stopPrank();\n\n        // Alice stakes right after, in the same block.\n        vm.startPrank(alice);\n        adam.approve(address(dist), 10_000_000e18);\n        dist.stake(10_000_000e18);\n        vm.stopPrank();\n\n        uint256 attackerEarned = dist.earned(attacker, address(imd));\n        uint256 aliceEarned = dist.earned(alice, address(imd));\n\n        // Expected: a 1 wei stake out of 1e25 staked is entitled to (essentially) nothing of the pot.\n        // Actual on current code: attackerEarned == POT + 1, aliceEarned == 0.\n        assertLt(attackerEarned, POT / 100, \"1 wei staker captured the launch-period pot\");\n        assertGt(aliceEarned + dist.unallocated(address(imd)), POT / 2, \"pot is gone for the real holders\");\n    }\n}","reproduction":"State: fresh AdamDistributor, totalStaked == 0. 1) treasury (or anyone) calls notifyReward(IMD, 1000e18) -> unallocated[IMD] == 1000e18, nobody credited. 2) attacker holding 1 wei ADAM and 1 wei IMD: stake(1); notifyReward(IMD, 1). In _distribute: totalStaked == 1, distributed = 1 + 1000e18, rewardPerShare[IMD] += (1000e18+1) * 2^128 / 1. 3) earned(attacker, IMD) == 1000e18 + 1. Expected: ~0 for a 1 wei stake out of a 1e27 supply. 4) alice stakes 10,000,000e18 right after: earned(alice, IMD) == 0 and unallocated[IMD] == 0; the pot is gone. Proof test/scratch/UnallocatedCapture.t.sol fails on current code with '1 wei staker captured the launch-period pot: 1000000000000000000001 >= 10000000000000000000'.","severity":"high","snippet":"        distributed = amount + unallocated[token];\n        unallocated[token] = 0;\n        rewardPerShare[token] += FullMath.mulDiv(distributed, MAGNITUDE, totalStaked);","title":"Rewards parked in `unallocated` are released in full to whoever is staked at the next notify; a 1 wei staker takes the whole launch-period pot"},{"citation":"resolved","description":"unlockCallback computes minOut from quoteMinOut, which reads `poolManager.getSlot0` of the target pool at the moment of the swap. The spot price in that slot is whatever the previous swap in the same block/bundle left it at, and `process()` is permissionless, so the caller can move the price first and the floor moves with it. The brief asks for a 'slippage cap' to limit MEV; this floor only caps the treasury's own price impact (which is tiny for 1 ETH on today's pools), not the price the treasury pays relative to the fair/previous-block price. Measured on a mainnet fork (block 26126256, IMD pool liquidity 1.03e22 ~660 ETH virtual reserve, 1% LP fee + 0.1% protocol fee): a 100 ETH front-run makes the treasury accept 180.67 IMD for 1 ETH where the honest floor was 235.58 IMD (-23%) and the leg does not revert. On current depth the attacker loses money on the round trip (-1.79 ETH at K=100 ETH, -0.019 ETH at K=1 ETH, PNKSTR similar because of its 10% buy tax), so this is not profitably exploitable today at maxEthPerBuy = 1 ETH. It becomes profitable when the IMD pool's effective ETH reserve falls below roughly 50 ETH (profit ~= 1 ETH * K/(x0+K) - 2% * K > 0), or whenever the attacker is a large LP of that pool and earns its own fees back; memecoin-pool depth is not under the project's control and both pools are external dependencies. Impact: holders receive less IMD/PNKSTR per ETH than the quoted floor promises; every `process()` call (one per 10 minute cooldown, up to 1 ETH per leg) is exposed. Suggested fix: anchor the floor to a reference that cannot be moved inside the transaction, e.g. the sqrtPrice observed at the previous `process()` call (at least `cooldown` earlier) with a bounded deviation, or require the spot at process time to be within X% of that stored reference and skip the leg otherwise (it is already fault tolerant).","line":235,"path":"src/AdamTreasury.sol","reproduction":"Mainnet fork at block 26126256, deployment via DeployAdam.deployContracts with mainnetConfig, treasury funded with 2.2222 ETH so each leg swaps 1 ETH. honestMin = treasury.quoteMinOut(0, 1 ether) = 235578120488056077769 before any manipulation. Bundle: (a) attacker buys IMD with 100 ETH on the ETH/IMD pool (fee 10000, ts 200, no hook); (b) attacker calls treasury.process(); (c) attacker sells the IMD back. Observed: distributor receives 180670198764611244138 IMD from the leg (23% below honestMin), leg(0).pending == 0 (the leg did not revert), attacker net -1.79 ETH. Same with K=30 ETH: treasury gets 221.87 IMD (-5.8%), attacker -0.56 ETH. Expected per the brief's 'slippage cap': the leg should either deliver at least the honest floor or decline (keep pending). Scratch probe: test/scratch/ForkProbe.t.sol::test_sandwichIMD / test_sandwichPNKSTR (fork, not a proof file).","severity":"medium","snippet":"        uint256 minOut = quoteMinOut(legId, ethIn);","title":"Treasury slippage floor is derived from the pool's spot price inside the same transaction, so `slippageBps` does not bound loss against a manipulated price"},{"citation":"resolved","description":"`failingSince` is set on the first failure and only cleared by a success. The reroute condition compares the current failure's timestamp against that first failure, with no requirement that any attempt in between failed (or happened at all). A single transient failure (e.g. a volatile minute where the PNKSTR hook's effective output dips below the 10% + 3% assumption, or a block where the pool's price sits in a liquidity gap) followed by no `process()` call that reaches a successful swap of that leg for 3 days, and then one more transient failure, moves every pending wei of that leg to the other token for good. The NatSpec promises 'if a leg has failed continuously for LEG_FALLBACK_DELAY'. Impact is limited to the IMD/PNKSTR mix (holders still receive value), hence low. Fix: require a minimum number of consecutive failed attempts, or reset `failingSince` when an attempt is skipped because `ethIn == 0`, or store the last-attempt timestamp and require failures to span the delay with attempts at both ends.","line":313,"path":"src/AdamTreasury.sol","reproduction":"1) T0: fund 1 ETH, pnkstrHook tax temporarily 15% (MockTaxHook.setTaxBps(1500)); process() -> PNKSTR leg fails, leg(1).failingSince = T0, pending 0.45 ETH. 2) tax back to 10%; nobody calls process() (no new fees) for 3 days — or callers only arrive when the leg has nothing new. 3) T0 + 3 days: tax momentarily 15% again, process() -> catch branch sees block.timestamp >= failingSince + 3 days -> all PNKSTR pending (0.45 ETH plus anything added since) is moved to the IMD leg and bought as IMD. Expected per NatSpec: reroute only after 3 days of continuous failure; here there were two isolated failures and zero successful retries in between were even attempted.","severity":"low","snippet":"            if (l.failingSince == 0) {\n                l.failingSince = uint64(block.timestamp);\n            } else if (block.timestamp >= uint256(l.failingSince) + LEG_FALLBACK_DELAY) {","title":"Leg reroute triggers on two isolated failures 3 days apart, not on 3 days of continuous failure"},{"citation":"resolved","description":"Rewards are credited to whoever is staked at the instant `notifyReward` runs; there is no lock, no eligibility delay and no time weighting, and `process()` is permissionless, so a holder (or anyone who buys ADAM) can stake in the block of the push (or bundle stake -> process -> exit) and leave immediately with the full pro-rata share, diluting holders who were staked for the whole accrual period. This is largely inherent to the dividend-per-share pattern the brief requested (a balance-snapshot design has the same property), so it is reported as a documented property rather than a defect in the requested design; the staking variant makes it cheaper than the brief's auto-tracking design because an existing holder pays no trading fee to stake/unstake around the push. It should be stated in the (currently missing) README/self-audit, and can be softened by streaming each notified amount over the cooldown window or requiring a minimum staking age.","line":102,"path":"src/AdamDistributor.sol","reproduction":"alice stakes 1,000,000e18 ADAM and holds for 30 days. whale holds 99,000,000e18 ADAM unstaked. Reward push of 100e18 IMD is about to happen (process() visible in the mempool, or the whale bundles it): whale stake(99,000,000e18); notifyReward(IMD, 100e18); whale exit(). Observed (test/scratch/LowChecks.t.sol::test_jitStakeCapturesProRataWithoutHoldingThroughTime): whale receives 98.999999999999999999 IMD, alice earned 0.999999999999999999 IMD although she carried the stake for the entire 30 days.","severity":"low","snippet":"        _settle(msg.sender);\n        stakedBalance[msg.sender] += amount;\n        totalStaked += amount;","title":"Reward distribution is a snapshot at notify time: a just-in-time staker captures the pro-rata share of each `process()` push without holding through time"},{"citation":"resolved","description":"beforeInitialize restricts which pool may use this hook, but Uniswap v4 pool creation is permissionless and the PoolManager only calls a hook for keys that include it. A PoolKey {ETH, ADAM, fee 3000, ts 60, hooks 0x0} can be initialized by anyone, LPs who bought ADAM can seed it, and every buy/sell routed there sends nothing to the Treasury. The brief states '1.5% fee on every buy and sell'; with a plain LaunchToken (required by the launch floor) and a hook-based fee this cannot be enforced globally, so routing/aggregators will prefer the fee-free pool once it has depth. Not fixable in code without changing the agreed design (token-level fee is forbidden); it must be documented as a known limitation in the README and the official frontend must pin the hooked PoolKey.","line":150,"path":"src/AdamHook.sol","reproduction":"Local v4 PoolManager with the hooked ETH/ADAM pool launched by the owner. rando calls poolManager.initialize(PoolKey(ETH, ADAM, 3000, 60, IHooks(address(0))), sqrtP at tick 177240) - succeeds (no owner check is reachable). An LP adds 100,000,000e18 ADAM single-sided in [108180, 177240]. A buyer swaps 1 ETH exact-in on that key: receives 33520515517546771040622348 ADAM, treasury ETH balance delta == 0 (test/scratch/LowChecks.t.sol::test_hooklessPoolBypassesTheFee). Expected per brief: 0.015 ETH to the treasury on every buy.","severity":"low","snippet":"        if (!key.currency0.isAddressZero() || Currency.unwrap(key.currency1) != adam) revert InvalidPool();","title":"The 1.5% fee is only enforced on pools that carry the hook; anyone can create a hookless ETH/ADAM pool and trade fee-free"},{"citation":"resolved","description":"exit() passes the caller's current stake to _unstake, which reverts with ZeroAmount when it is 0, before _claim runs. A user who unstaked earlier (rewards stay claimable by design) and later calls exit() to collect them gets a revert instead of their rewards; they must know to call claim() instead. Funds are not lost, so low. Fix: skip the unstake when the stake is zero (`if (staked != 0) _unstake(staked);`).","line":121,"path":"src/AdamDistributor.sol","reproduction":"alice stake(100e18); notifyReward(IMD, 10e18); alice unstake(100e18) -> earned(alice, IMD) ~= 10e18 > 0. alice exit() -> reverts AdamDistributor.ZeroAmount (test/scratch/OogProbe.t.sol::test_exitWithZeroStakeButRewardsReverts). Expected: exit() withdraws whatever is staked (possibly nothing) and pays the 10e18 IMD.","severity":"low","snippet":"        _unstake(stakedBalance[msg.sender]);","title":"`exit()` reverts for a holder who has already unstaked but still has unclaimed rewards"},{"citation":"resolved","description":"Run with `FOUNDRY_PROFILE=fork forge test` at block 26126249: test_pnkstrHookBuyTaxIsTenPercent fails because the PNKSTR hook's own PNKSTR balance does not change (0 vs 169.46e18 expected) - the measured tax is 10.01% (correct) but the hook forwards it elsewhere, so the test and the LocalV4 MockTaxHook model the periphery wrongly (harmless to the Treasury, which only checks net output, but the 'measured on a mainnet fork' claim rests on this suite). test_deploymentAndSingleSidedPosition fails with 389 wei ADAM left on the deployer (PositionManager rounding; the assertion is too strict). test_endToEndOnMainnetFork fails on `earned(alice) == imdGot` by 1 wei (floor rounding in the per-share math; the unit tests already allow 1 wei). None of these are contract defects, but the fork suite is the only check against the real IMD/PNKSTR pools and it does not pass, so it cannot serve as the regression gate the brief requires ('mainnet-fork (real IMD and PNKSTR pools)').","line":102,"path":"test/fork/MainnetFork.t.sol","reproduction":"FOUNDRY_PROFILE=fork forge test -vv (RPC https://ethereum-rpc.publicnode.com, block 26126249): [FAIL: all ADAM is in the position: 389 != 0] test_deploymentAndSingleSidedPosition; [FAIL: assertion failed: 4142219748933465225 != 4142219748933465226] test_endToEndOnMainnetFork; [FAIL: assertion failed: 0 !~= 169464806465880465753 (max delta 1%)] test_pnkstrHookBuyTaxIsTenPercent (log: 'PNKSTR hook buy tax (bps, incl. price impact): 1001'). Expected: suite passes against the live pools.","severity":"low","snippet":"        assertApproxEqRel(IERC20(cfg.pnkstr).balanceOf(cfg.pnkstrHooks) - hookBefore, spot - got, 1e16);","title":"Mainnet-fork suite fails against current mainnet state (3 of 5 tests); one failure is a wrong assumption about where the PNKSTR hook sends its tax"},{"citation":"resolved","description":"The brief lists 'Exclude pool, PoolManager, hook, Treasury, Distributor, zero/dead addresses'. The distributor excludes zero, dead, itself, the ADAM token, the PoolManager and the two reward tokens; the hook and the Treasury are not excluded and the only slot for an extra address is passed as address(0) because both are deployed after the distributor (circular dependency). Neither contract has a code path that stakes, so this is not exploitable; it is a documented deviation from the brief that should be stated in the README, or resolved by precomputing the hook/treasury addresses (CREATE2) and passing them, or by letting the Treasury be excluded via the existing `excludedExtra` slot and deploying the Distributor after it.","line":180,"path":"script/DeployAdam.s.sol","reproduction":"After DeployAdam.deployContracts: distributor.isExcluded(address(hook)) == false and distributor.isExcluded(address(treasury)) == false (expected true per brief); distributor.isExcluded(poolManager) == true.","severity":"info","snippet":"        d.distributor = new AdamDistributor(address(d.token), cfg.imd, cfg.pnkstr, cfg.poolManager, address(0));","title":"Exclusion list omits the hook and the Treasury required by the brief; deploy script passes address(0) for the extra exclusion"},{"citation":"resolved","description":"The tracked tree has no README, no self-audit and no .md file at all (git ls-files shows only sources, tests, script and config). Deliverables 3 (self-audit by severity) and 4 (README: deploy, add single-sided liquidity, call process() hourly, claim) are missing, and the most important design deviation (holders must stake instead of being tracked on transfer, because LaunchToken must be a plain ERC-20) is only explained in NatSpec that refers to a README section that was never written. Operationally relevant facts that live nowhere else: the hooked PoolKey frontends must pin, the hookless-pool limitation (finding 5), the JIT-stake property (finding 4), the trust assumptions of the hook owner (initialize, lowerFee, renounce), and the measured PNKSTR tax and its source.","line":20,"path":"src/AdamDistributor.sol","reproduction":"`git ls-files | grep -i -E 'readme|audit|\\.md$'` returns nothing; `ls` of the repository root shows no README. src/AdamDistributor.sol:20 says 'See README \"What differs from the brief\"'.","severity":"info","snippet":"/// \"shadow balance\" scheme cannot. See README \"What differs from the brief\".","title":"README and self-audit deliverables are absent; code comments point readers to a README section that does not exist"}],"hash":"687f0c66c08b4fa689e0d33a4f7c589be05c9cb40d78d6257810b6c275805490","nodeId":"d532fc69-3f53-4f18-b4f7-3f9d93f2df0e","outcome":"completed","summary":"Review complete. No tracked file was changed; the output is `.imd-findings.json` at the repository root plus one proof file under `test/scratch/`.\n\n**What I found (9 findings, ranked):**\n\n- **High, reward capture at launch.** Rewards pushed while nobody is staked sit in `unallocated` and are then released in full to whoever is staked at the next notify. Since both staking and `notifyReward` are permissionless, an attacker with 1 wei of ADAM and 1 wei of IMD takes the entire launch-period pot. Proof test `test/scratch/UnallocatedCapture.t.sol` fails on current code (attacker earns 1000 IMD + 1 wei, Alice earns 0).\n- **Medium, slippage floor is same-transaction spot.** The treasury's `minOut` is read from the pool's slot0 inside the swap, so a caller who moves the price first moves the floor with it. On a mainnet fork a 100 ETH front-run made the treasury accept 23% less IMD than the honest floor without reverting. At today's pool depth the attacker loses money on the round trip, so it is not profitable now, but it becomes profitable if the IMD pool's effective reserve falls below roughly 50 ETH or the attacker is a major LP.\n- **Low.** Leg reroute fires on two isolated failures 3 days apart rather than continuous failure. Just-in-time stakers take a full pro-rata share of each push (inherent to the requested pattern, needs documenting). Anyone can create a hookless ETH/ADAM pool that pays no fee. `exit()` reverts for a holder who already unstaked but has rewards. The fork suite fails 3 of 5 tests against current mainnet, one because the PNKSTR hook does not keep its tax at its own address.\n- **Info.** The distributor does not exclude the hook or treasury as the brief asked. README and self-audit deliverables are missing while code comments reference a README section.\n\n**Leads checked and rejected:** low-gas griefing of a leg through the try/catch has no feasible gas window (below 490,206 gas the whole call reverts, above it both legs buy), and the Slither reentrancy flags are covered by the guards. Hook delta handling for all four swap types was traced against v4-core and holds.\n\n**Coverage:** all 20 listed entry points have a row (16 hold, 4 carry findings), plus rows for the ETH accounting invariant, hook delta neutrality, and the two rejected leads.","treeHash":null,"usage":{"cachedInputTokens":2194002,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":84065,"runtime":"claude","turns":46,"wallClockMs":1381760}},{"artifacts":[],"attempt":1,"bundleHash":"d4c2bd8c845050fbf29a58d536efb9e58a3fdc22113893946098b15a10c951a1","device":"d03fc84904b54d9e","findings":[],"hash":"6e6e4fa7150d4ba1f6e78a08391dbec1d641cc5515d2243bda2291fc0c402fac","nodeId":"ea5f0fc8-acff-4ba1-9a1d-ebc099b7a52f","outcome":"completed","summary":"Work is complete. Both findings were reproduced; one is fixed and one is disputed with evidence.\n\n**Finding d62ef1a7 (post-factory hook deployment): fixed.** The existing-token path really did construct a second Distributor and Treasury and wire the hook to the new one. The deploy script now has a hook-only mode:\n\n- A new `treasury` config field, read from the `TREASURY` environment variable in `run()`, switches the script to reuse an existing pipeline.\n- `resolvePipeline` reads the Distributor from that Treasury and the ADAM token from the Distributor, and reverts on a token or reward-leg mismatch.\n- `run()` also reads `LIQUIDITY_ADAM` for the owner's real allocation, and `launchPool` reverts if the deployer holds less than that.\n- New test `test/unit/PostFactoryHook.t.sol` builds the pipeline from a third-party \"factory\" address, runs the hook-only path, and checks that a buy's fee lands in the existing Treasury and that `process()` and a claim pay through the existing Distributor. Mismatched config and an oversized allocation are rejected.\n- README documents both modes and the post-factory step list. The self-audit records the residual trust in the operator-supplied Treasury address.\n\n**Finding 99658b4c (wallet holders earn nothing without staking): disputed.** The proof fails on the starting tree exactly as reported, but the behaviour it asserts cannot be delivered securely under this launch's rules. Wallet dividends \"updated on transfer\" need a transfer hook in the token, and the launch rules require a plain LaunchToken and refuse one with extra transfer behaviour. In scratch I built the minimal hook-less wallet-balance distributor that satisfies the proof and showed it lets a holder claim, move tokens to a fresh wallet, and claim again, draining an honest holder's share. The contracts are unchanged. The README now states the conflict and its resolution explicitly, and the responses file carries the full argument.\n\n**Verification.** `forge build` passes. `forge test` passes all 97 permanent default tests, and the six mainnet-fork tests also passed in this session. `forge fmt --check` is clean on every delivered path; the only diff it reports is a missing trailing newline in the scratch copy of the reviewer's proof, which is deleted before verification. The disputed proof still fails by design. The responses file is in the repository root and is excluded from git by the harness's own exclude list, not by anything I changed. No protected paths were touched, and nothing was committed or broadcast.","treeHash":"7551b6b020384f7c35fae46e26cada5872641189","usage":{"cachedInputTokens":2000942,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":41520,"runtime":"claude","turns":49,"wallClockMs":1066551}},{"artifacts":[],"attempt":1,"bundleHash":"08088f36850d6297db406816e43d7402478237f58c177ff89d04f7cc595e7d1c","device":"95e219c7eba8a53f","findings":[],"hash":"72f40544efdce3ea61a2bd644d00de6769ab660bc6d2d8a0e5ccd606b0d9202a","nodeId":"ea5f0fc8-acff-4ba1-9a1d-ebc099b7a52f","outcome":"completed","summary":"Addressed all ten findings in [.imd-responses.json](/root/.identitymd/work/e497ebcf-33bb-4548-bfd7-fa95843dc898/ea5f0fc8-acff-4ba1-9a1d-ebc099b7a52f/.imd-responses.json). Added deployment documentation and a self-audit.\n\nValidation passed: `forge build`, 93 default tests, all three supplied proofs, six mainnet-fork tests, and `forge fmt --check`.\n\nThe accepted staking architecture remains; hook/Treasury exclusion omissions are explicitly documented. No deployment was executed.","treeHash":"92e0687a0676c22b14f5bc6327588a276f181630","usage":{"cachedInputTokens":3096960,"inputTokens":130825,"model":"gpt-6-astra","outputTokens":34460,"runtime":"codex","turns":8,"wallClockMs":1451368}},{"artifacts":[],"attempt":1,"bundleHash":"cc8f74681b25cd69eeb7333d31790f77fbab3709c79e8855d45f6976c6b95a2f","device":"f01afe38b46a5f7e","findings":[{"description":"The requested distribution follows ADAM wallet balances and updates on transfer. The accepted implementation instead uses opt-in stakedBalance, so ordinary holders receive no rewards and wallet transfers do not checkpoint past entitlements. This is the documented, previously disputed architecture deviation, not a newly discovered withdrawal exploit. The plain LaunchToken requirement explains the choice but does not fulfill the supplied dividend requirement; the specification conflict needs an explicit resolution, not a passing test asserting zero dividends is correct.","line":221,"path":"src/AdamDistributor.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {AdamDistributor} from \"src/AdamDistributor.sol\";\n\ncontract WalletDividendProof is Test {\n    function test_walletHolderReceivesDividendsAcrossTransfer() public {\n        LaunchToken adam = new LaunchToken();\n        // Plain fixed-supply ERC-20 stand-ins; reward metadata is irrelevant to this proof.\n        LaunchToken imd = new LaunchToken();\n        LaunchToken pnkstr = new LaunchToken();\n        AdamDistributor distributor = new AdamDistributor(\n            address(adam), address(imd), address(pnkstr), address(0x9000), address(0)\n        );\n        address alice = address(0xA11CE);\n        address bob = address(0xB0B);\n        uint256 supply = adam.totalSupply();\n        adam.transfer(alice, supply);\n        imd.approve(address(distributor), 200e18);\n        pnkstr.approve(address(distributor), 400e18);\n\n        distributor.notifyReward(address(imd), 100e18);\n        distributor.notifyReward(address(pnkstr), 200e18);\n        vm.prank(alice);\n        adam.transfer(bob, supply);\n        distributor.notifyReward(address(imd), 100e18);\n        distributor.notifyReward(address(pnkstr), 200e18);\n\n        vm.prank(alice);\n        distributor.claim();\n        vm.prank(bob);\n        distributor.claim();\n        assertApproxEqAbs(imd.balanceOf(alice), 100e18, 1, \"past rewards belong to the original holder\");\n        assertApproxEqAbs(pnkstr.balanceOf(alice), 200e18, 1);\n        assertApproxEqAbs(imd.balanceOf(bob), 100e18, 1, \"future rewards follow the transferred balance\");\n        assertApproxEqAbs(pnkstr.balanceOf(bob), 200e18, 1);\n    }\n}","reproduction":"Run the attached test with forge test --match-path test/scratch/WalletDividendProof.t.sol -vv. Alice holds the entire 1e27 ADAM supply when 100e18 IMD and 200e18 PNKSTR are notified. She then transfers the full supply to Bob, and the same rewards are notified again. Neither holder stakes. Both claim. Expected: Alice receives her first-epoch 100e18 IMD / 200e18 PNKSTR and Bob receives the second epoch. Actual: both receive zero; all 200e18 IMD and 400e18 PNKSTR remain unallocated. Reproduced locally: first payout assertion fails with 0 versus 100000000000000000000.","severity":"medium","title":"Automatic wallet-holder dividends remain unimplemented"}],"hash":"773defab701f3a5c0ed179a7f0276fab44bca6446e452f13e0277df9de3d21dc","nodeId":"c351a697-c3a7-425f-b397-d518567526f6","outcome":"completed","summary":"Added four Treasury tests covering timing boundaries, protocol fees, and reentrancy, including 1,000 fuzz cases. Accepted files remain unchanged.\n\nValidation: build passed; 119 tests passed; 32,768 invariant calls completed without unexpected reverts. Fork checks skipped because no RPC was configured.\n\nReported the medium-severity wallet-dividend mismatch, with a reproduced failing proof, in `.imd-findings.json`.","treeHash":"60d60c03ca9ac661a08c853f0de2a78274394854","usage":{"cachedInputTokens":3783168,"inputTokens":133439,"model":"gpt-6-astra","outputTokens":17216,"runtime":"codex","turns":11,"wallClockMs":1022955}},{"artifacts":[],"attempt":3,"bundleHash":null,"device":"b5e3297a04468fd3","findings":[{"citation":"resolved","description":"On exact-input buys and exact-input sells the hook charges feeBps of the ETH on the user's side of the trade (ETH paid in / ETH received). On the two exact-output paths it charges feeBps of the ETH on the pool's side: afterSwap (line 180-181) takes bps of the ETH the pool charged for an exact-output buy and the buyer pays that on top; beforeSwap (line 166-168) takes bps of the net ETH the seller asked for and makes the pool pay that much more. For a fee rate r the exact-output buyer therefore pays an effective r/(1+r) of the ETH they spend and the exact-output seller gives up ADAM worth r/(1+r) of gross, versus r on the exact-input paths. During the 30-minute anti-snipe window (r = 20%) this is 16.67% vs 20%: a sniper routing through exact-output swaps gets the same ADAM for 0.96 ETH that an exact-input buyer pays 1.00 ETH for, and the Treasury receives 0.16 ETH instead of 0.20 ETH on every such buy. In steady state the gap is 1.478% vs 1.5%. The brief's '20% decaying to 1.5% on every buy and sell' therefore only holds for exact-input swaps; every router that supports exact-output (the Universal Router does) pays the lower rate. Fix: on the two exact-output paths compute fee = ethAmount * bps / (BPS - bps) so that fee / (ethAmount + fee) == bps / BPS, i.e. the fee is the same fraction of the user-side ETH regardless of swap type (and round the fee up rather than down).","line":180,"path":"src/AdamHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"@uniswap/v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"@uniswap/v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {Currency, CurrencyLibrary} from \"@uniswap/v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"@uniswap/v4-core/src/types/BalanceDelta.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"@uniswap/v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"@uniswap/v4-core/src/libraries/TickMath.sol\";\nimport {PoolSwapTest} from \"@uniswap/v4-core/src/test/PoolSwapTest.sol\";\nimport {PoolModifyLiquidityTest} from \"@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {LiquidityAmounts} from \"@uniswap/v4-periphery/src/libraries/LiquidityAmounts.sol\";\n\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {AdamHook} from \"src/AdamHook.sol\";\nimport {HookMiner} from \"script/utils/HookMiner.sol\";\n\n/// @dev Treasury stand-in: only needs to receive the ETH fee.\ncontract FeeSink {\n    receive() external payable {}\n}\n\n/// @notice AdamHook charges `feeBps` of the ETH the *user* pays on exact-input buys, but `feeBps` of the ETH the\n/// *pool* charges on exact-output buys. Buying the same ADAM with an exact-output swap therefore costs less fee:\n/// during anti-snipe (20%) the effective rate is 16.67% of the ETH paid; in steady state 1.478% instead of 1.5%.\n/// The fee rate must be the same fraction of the ETH the buyer pays regardless of the swap type.\ncontract ProofFeeBasisTest is Test {\n    uint256 internal constant BPS = 10_000;\n\n    PoolManager internal poolManager;\n    PoolSwapTest internal swapRouter;\n    PoolModifyLiquidityTest internal lpRouter;\n    LaunchToken internal adam;\n    FeeSink internal treasury;\n    AdamHook internal hook;\n    PoolKey internal adamKey;\n    address internal hookOwner = makeAddr(\"hookOwner\");\n\n    receive() external payable {}\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        poolManager = new PoolManager(address(this));\n        swapRouter = new PoolSwapTest(poolManager);\n        lpRouter = new PoolModifyLiquidityTest(poolManager);\n        vm.deal(address(this), 1_000 ether);\n        // The hook takes its ETH fee before the buyer settles; the mainnet PoolManager holds other pools' ETH.\n        vm.deal(address(poolManager), 1_000 ether);\n\n        adam = new LaunchToken();\n        treasury = new FeeSink();\n        uint160 flags = uint160(0x2000 | 0x80 | 0x40 | 0x8 | 0x4);\n        bytes memory args = abi.encode(address(poolManager), address(adam), address(treasury), hookOwner);\n        (address expected, bytes32 salt) = HookMiner.find(address(this), flags, type(AdamHook).creationCode, args);\n        hook = new AdamHook{salt: salt}(poolManager, address(adam), address(treasury), hookOwner);\n        require(address(hook) == expected, \"hook address\");\n\n        adamKey = PoolKey({\n            currency0: CurrencyLibrary.ADDRESS_ZERO,\n            currency1: Currency.wrap(address(adam)),\n            fee: 0,\n            tickSpacing: 60,\n            hooks: IHooks(address(hook))\n        });\n        int24 initialTick = 177_240;\n        int24 lowerTick = 108_180;\n        uint160 sqrtP = TickMath.getSqrtPriceAtTick(initialTick);\n        vm.prank(hookOwner);\n        poolManager.initialize(adamKey, sqrtP);\n        uint128 liquidity =\n            LiquidityAmounts.getLiquidityForAmount1(TickMath.getSqrtPriceAtTick(lowerTick), sqrtP, 1_000_000_000e18);\n        adam.approve(address(lpRouter), type(uint256).max);\n        lpRouter.modifyLiquidity(\n            adamKey,\n            ModifyLiquidityParams({\n                tickLower: lowerTick, tickUpper: initialTick, liquidityDelta: int256(uint256(liquidity)), salt: 0\n            }),\n            \"\"\n        );\n        adam.approve(address(swapRouter), type(uint256).max);\n    }\n\n    function _swap(bool zeroForOne, int256 amountSpecified, uint256 value) internal returns (BalanceDelta) {\n        return swapRouter.swap{value: value}(\n            adamKey,\n            SwapParams({\n                zeroForOne: zeroForOne,\n                amountSpecified: amountSpecified,\n                sqrtPriceLimitX96: zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1\n            }),\n            PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}),\n            \"\"\n        );\n    }\n\n    /// @dev At launch (fee 20%): an exact-input buy of 1 ETH pays 0.2 ETH fee and receives X ADAM.\n    /// An exact-output buy of the same X ADAM must also pay 20% of the ETH it spends, i.e. cost the same 1 ETH.\n    function test_exactOutputBuyPaysSameFeeRateAsExactInput() public {\n        assertEq(hook.currentFeeBps(), 2000, \"anti-snipe fee at launch\");\n\n        uint256 snap = vm.snapshotState();\n        uint256 tBefore = address(treasury).balance;\n        BalanceDelta dIn = _swap(true, -int256(1 ether), 1 ether);\n        uint256 adamOut = uint256(uint128(dIn.amount1()));\n        uint256 feeIn = address(treasury).balance - tBefore;\n        assertEq(feeIn, 0.2 ether, \"exact-input: 20% of the ETH paid\");\n        vm.revertToState(snap);\n\n        tBefore = address(treasury).balance;\n        BalanceDelta dOut = _swap(true, int256(adamOut), 2 ether);\n        uint256 ethPaid = uint256(uint128(-dOut.amount0()));\n        uint256 feeOut = address(treasury).balance - tBefore;\n        assertEq(uint256(uint128(dOut.amount1())), adamOut, \"same ADAM received\");\n\n        // Current code: pool charges 0.8 ETH, fee = 20% * 0.8 = 0.16, buyer pays 0.96 ETH -> 16.67% effective.\n        // Expected: fee is 20% of what the buyer pays -> 0.2 ETH fee, 1 ETH paid (tolerance for 1-wei rounding).\n        assertApproxEqAbs((feeOut * BPS) / ethPaid, 2000, 1, \"exact-output effective fee rate must be 20%\");\n        assertApproxEqAbs(ethPaid, 1 ether, 1e6, \"same ADAM must cost the same ETH either way\");\n    }\n\n    /// @dev Same asymmetry on sells: exact-input sell pays fee on the gross ETH the pool pays; exact-output sell\n    /// pays fee on the net ETH the seller asked for, so it gives up less ADAM for the same net ETH.\n    function test_exactOutputSellPaysSameFeeRateAsExactInput() public {\n        vm.warp(uint256(hook.launchTimestamp()) + hook.DECAY_DURATION());\n        _swap(true, -int256(5 ether), 5 ether);\n        uint256 adamBal = adam.balanceOf(address(this));\n\n        uint256 snap = vm.snapshotState();\n        uint256 tBefore = address(treasury).balance;\n        BalanceDelta dIn = _swap(false, -int256(adamBal / 2), 0);\n        uint256 ethNet = uint256(uint128(dIn.amount0()));\n        uint256 feeIn = address(treasury).balance - tBefore;\n        assertEq(feeIn, ((ethNet + feeIn) * 150) / BPS, \"exact-input sell: 1.5% of gross ETH out\");\n        vm.revertToState(snap);\n\n        tBefore = address(treasury).balance;\n        BalanceDelta dOut = _swap(false, int256(ethNet), 0);\n        uint256 adamSold = uint256(uint128(-dOut.amount1()));\n        uint256 feeOut = address(treasury).balance - tBefore;\n        // Expected: fee is 1.5% of the gross ETH the pool pays (net + fee), as on the exact-input path.\n        assertApproxEqAbs(feeOut, ((ethNet + feeOut) * 150) / BPS, 1, \"exact-output sell fee must be 1.5% of gross\");\n        assertApproxEqRel(adamSold, adamBal / 2, 1e12, \"same net ETH must cost the same ADAM either way\");\n    }\n}","reproduction":"Local v4 PoolManager, ADAM pool launched exactly as DeployAdam.launchPool (tick 177240, single-sided 1e27 ADAM), fee at launch = 2000 bps.\n1. Exact-input buy, amountSpecified = -1e18 ETH: treasury receives 0.200000000000000000 ETH, buyer receives X = 38344909273318868692113715 ADAM.\n2. Revert state. Exact-output buy, amountSpecified = +X ADAM: pool charges 0.8 ETH, hook fee = 0.8 * 2000/10000 = 0.16 ETH, buyer pays 0.960000000000000000 ETH total.\n   Expected: same ADAM costs the same 1.0 ETH with 0.2 ETH fee (20%). Actual: 0.96 ETH paid, 0.16 ETH fee, effective 1666 bps.\n3. After decay (150 bps): exact-input sell of 99063935802869071317338032 ADAM -> net 2682930576271958103 ETH, fee 40856810806171950 (149 bps of gross).\n   Exact-output sell for the same net ETH -> fee 40243958644079371, seller gives only 99039001298261258140906763 ADAM (147 bps of gross).\nProof: test/scratch/ProofFeeBasis.t.sol fails on this code with '1666 !~= 2000' and passes once exact-output fees are taken as bps/(BPS-bps) of the pool-side amount.","severity":"medium","snippet":"        uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n        uint256 fee = _takeFee(params.zeroForOne, ethAmount);","title":"Hook fee basis differs between exact-input and exact-output swaps: exact-output buys pay 16.67% instead of 20% during anti-snipe (1.478% instead of 1.5% afterwards)"},{"citation":"resolved","description":"unlockCallback computes minOut from getSlot0 at the moment of the swap (line 235 -> 272-280). The only thing this bounds is the Treasury's own price impact; any price an attacker sets one call earlier becomes the reference, and minOut moves with it. Because process() is public (as the brief requires), the attacker does not need to see a keeper's transaction in the mempool: they bundle pump -> process() -> dump themselves, so the NatSpec advice to use a private relay does not help. The brief asks for 'slippage cap + max size per call to limit MEV'; as implemented only the max size limits MEV, and the loss per call is bounded by pool depth and pool fees, not by slippageBps. With the IMD pool (1% fee) the sandwich is profitable whenever its ETH-side depth is below roughly 100 ETH; measured on a local pool: depth 50 ETH -> attacker nets 0.139 ETH per process() with a 20 ETH front-run (holders lose ~14% of the IMD leg), depth 25 ETH -> 0.37 ETH (37%). At today's mainnet depth (~658 ETH virtual reserve, read from PoolManager storage at block 26126341) the attack costs more in pool fees than it extracts, and the PNKSTR leg is protected by that hook's 10% buy tax; the finding is a broken guarantee that becomes a loss as soon as IMD liquidity thins. Fix options that keep the design: keep a reference price per leg (e.g. sqrtPriceX96 observed at the previous process() / last successful buy, or an EMA updated per call) and refuse the leg when the current spot deviates from it by more than slippageBps; or bound the fill against a TWAP-style observation rather than the instantaneous slot0.","line":272,"path":"src/AdamTreasury.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"@uniswap/v4-core/src/PoolManager.sol\";\nimport {IHooks} from \"@uniswap/v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {Currency, CurrencyLibrary} from \"@uniswap/v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"@uniswap/v4-core/src/types/BalanceDelta.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"@uniswap/v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"@uniswap/v4-core/src/libraries/TickMath.sol\";\nimport {PoolSwapTest} from \"@uniswap/v4-core/src/test/PoolSwapTest.sol\";\nimport {PoolModifyLiquidityTest} from \"@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {LiquidityAmounts} from \"@uniswap/v4-periphery/src/libraries/LiquidityAmounts.sol\";\nimport {MockERC20} from \"solmate/src/test/utils/mocks/MockERC20.sol\";\n\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {AdamDistributor} from \"src/AdamDistributor.sol\";\nimport {AdamTreasury} from \"src/AdamTreasury.sol\";\n\n/// @notice `quoteMinOut` reads the pool's spot price inside the same transaction as the swap, so the slippage\n/// floor moves with whatever price an attacker set one call earlier. `process()` is permissionless, so the\n/// attacker bundles: pump IMD -> process() -> dump. The treasury fills >35% below the pre-pump floor and the\n/// \"slippage cap\" never fires. A fixed treasury must refuse (keep the ETH pending) or fill within `slippageBps`\n/// of the price that prevailed before the attacker's front-run.\ncontract ProofSlippageTest is Test {\n    uint256 internal constant BPS = 10_000;\n    uint32 internal constant COOLDOWN = 10 minutes;\n\n    PoolManager internal poolManager;\n    PoolSwapTest internal swapRouter;\n    PoolModifyLiquidityTest internal lpRouter;\n    MockERC20 internal imd;\n    MockERC20 internal pnkstr;\n    PoolKey internal imdKey;\n    PoolKey internal pnkstrKey;\n    LaunchToken internal adam;\n    AdamDistributor internal distributor;\n    AdamTreasury internal treasury;\n    address internal teamWallet = makeAddr(\"teamWallet\");\n\n    receive() external payable {}\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        poolManager = new PoolManager(address(this));\n        swapRouter = new PoolSwapTest(poolManager);\n        lpRouter = new PoolModifyLiquidityTest(poolManager);\n        vm.deal(address(this), 10_000 ether);\n\n        imd = new MockERC20(\"IMD\", \"IMD\", 18);\n        pnkstr = new MockERC20(\"PNKSTR\", \"PNKSTR\", 18);\n        imd.mint(address(this), 1e36);\n        pnkstr.mint(address(this), 1e36);\n        imd.approve(address(lpRouter), type(uint256).max);\n        pnkstr.approve(address(lpRouter), type(uint256).max);\n        imd.approve(address(swapRouter), type(uint256).max);\n\n        imdKey = PoolKey({\n            currency0: CurrencyLibrary.ADDRESS_ZERO,\n            currency1: Currency.wrap(address(imd)),\n            fee: 10_000,\n            tickSpacing: 200,\n            hooks: IHooks(address(0))\n        });\n        pnkstrKey = PoolKey({\n            currency0: CurrencyLibrary.ADDRESS_ZERO,\n            currency1: Currency.wrap(address(pnkstr)),\n            fee: 0,\n            tickSpacing: 60,\n            hooks: IHooks(address(0))\n        });\n        _initAndSeed(imdKey, 54_000, -887_200, 887_200, 50 ether);\n        _initAndSeed(pnkstrKey, 120_420, -887_220, 887_220, 200 ether);\n\n        adam = new LaunchToken();\n        distributor = new AdamDistributor(address(adam), address(imd), address(pnkstr), address(poolManager), address(0));\n        treasury = new AdamTreasury(\n            address(distributor),\n            teamWallet,\n            address(poolManager),\n            address(imd),\n            10_000,\n            200,\n            address(0),\n            0,\n            address(pnkstr),\n            0,\n            60,\n            address(0),\n            0,\n            1 ether,\n            300,\n            COOLDOWN\n        );\n    }\n\n    function _initAndSeed(PoolKey memory key, int24 tick, int24 lower, int24 upper, uint256 ethAmount) internal {\n        uint160 sqrtP = TickMath.getSqrtPriceAtTick(tick);\n        poolManager.initialize(key, sqrtP);\n        uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(\n            sqrtP, TickMath.getSqrtPriceAtTick(lower), TickMath.getSqrtPriceAtTick(upper), ethAmount, type(uint128).max\n        );\n        lpRouter.modifyLiquidity{value: ethAmount}(\n            key,\n            ModifyLiquidityParams({\n                tickLower: lower, tickUpper: upper, liquidityDelta: int256(uint256(liquidity)), salt: 0\n            }),\n            \"\"\n        );\n    }\n\n    function _swap(PoolKey memory key, bool zeroForOne, int256 amountSpecified, uint256 value)\n        internal\n        returns (BalanceDelta)\n    {\n        return swapRouter.swap{value: value}(\n            key,\n            SwapParams({\n                zeroForOne: zeroForOne,\n                amountSpecified: amountSpecified,\n                sqrtPriceLimitX96: zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1\n            }),\n            PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}),\n            \"\"\n        );\n    }\n\n    function _fund(uint256 amount) internal {\n        (bool ok,) = payable(address(treasury)).call{value: amount}(\"\");\n        require(ok);\n    }\n\n    function test_sandwichedProcessMustNotFillBelowPrePumpFloor() public {\n        // An honest process() first, so any price reference a fixed treasury keeps is established at the fair price.\n        _fund(1 ether);\n        treasury.process();\n        uint256 imdBefore = imd.balanceOf(address(distributor));\n        vm.warp(block.timestamp + COOLDOWN);\n\n        // 2.3 ETH of fees arrive: cap 2.222 ETH -> exactly 1 ETH per leg.\n        _fund(2.3 ether);\n        uint256 floorFair = treasury.quoteMinOut(0, 1 ether);\n\n        // Attacker, in one bundle: pump IMD with 20 ETH (pool depth 50 ETH), call process(), dump.\n        uint256 attackerEthBefore = address(this).balance;\n        uint256 attackerImdBefore = imd.balanceOf(address(this));\n        _swap(imdKey, true, -int256(20 ether), 20 ether);\n        treasury.process();\n        uint256 bought = imd.balanceOf(address(distributor)) - imdBefore;\n        _swap(imdKey, false, -int256(imd.balanceOf(address(this)) - attackerImdBefore), 0);\n        int256 attackerProfit = int256(address(this).balance) - int256(attackerEthBefore);\n\n        emit log_named_uint(\"pre-pump minOut for 1 ETH\", floorFair);\n        emit log_named_uint(\"IMD actually bought for 1 ETH\", bought);\n        emit log_named_int(\"attacker net ETH\", attackerProfit);\n\n        // Expected (fixed): either the leg is refused and its ETH stays pending, or it fills within 3% of the\n        // pre-pump price. Actual (current code): the leg fills ~40% below that floor and the attacker nets ~0.14 ETH.\n        bool refused = treasury.leg(0).pending == 1 ether && bought == 0;\n        bool filledFairly = bought >= floorFair;\n        assertTrue(refused || filledFairly, \"slippage floor followed the attacker's spot price\");\n    }\n}","reproduction":"Local v4 PoolManager; hookless ETH/IMD pool with 50 ETH full-range depth, fee 1%; Treasury(maxEthPerBuy = 1 ETH, slippageBps = 300).\n1. Fund 1 ETH, process() (fair fill, establishes any reference a fixed version would keep). Warp cooldown. Fund 2.3 ETH (cap 2.222 -> 1 ETH per leg).\n2. quoteMinOut(0, 1 ether) = 208821411098003554484 IMD (pre-pump floor).\n3. Attacker, same block: swap 20 ETH -> IMD on the pool; call treasury.process(); sell all IMD back.\n   Expected: the IMD leg is refused (pending stays 1 ETH) or fills >= 97% of the pre-pump price.\n   Actual: the leg fills, Treasury receives 109479591633818261553 IMD (47.6% below the pre-pump floor), leg(0).pending == 0, attacker net +135979074513432781 wei ETH.\nScan (test/scratch/Sandwich.t.sol, 1 ETH leg): depth 25 ETH: +0.371 ETH with 20 ETH front-run; 50 ETH: +0.139 ETH; 100 ETH: -0.0006 ETH (unprofitable); 650 ETH: -0.017 ETH.\nProof: test/scratch/ProofSlippage.t.sol fails on this code with 'slippage floor followed the attacker's spot price'.","severity":"medium","snippet":"        (uint160 sqrtPriceX96,, uint24 protocolFee, uint24 lpFee) = poolManager.getSlot0(l.key.toId());","title":"quoteMinOut derives the slippage floor from the pool's spot price inside the same transaction, so the 'slippage cap' never fires under a sandwich of the permissionless process()"},{"citation":"resolved","description":"quoteMinOut requires the fill to be within slippageBps of spot after fees. The Treasury's own price impact of a 1 ETH exact-input buy exceeds 3% as soon as the pool's ETH-side depth is below about 33 ETH, so every 1 ETH attempt reverts with InsufficientOutput. A smaller chunk (0.2 ETH) would pass, but _executeLeg never lowers the attempt size: as long as pending >= maxEthPerBuy it retries exactly maxEthPerBuy, fails, and accumulates more. After LEG_FALLBACK_DELAY the whole backlog is moved to the other leg, so holders get 0% IMD / 100% PNKSTR instead of 50/50 for that period, and the IMD leg only recovers when outside liquidity returns. Boundary x invariant seam: the per-call cap (boundary) is also the retry size, and the retry can only ever be at least as large as the attempt that failed. Today's mainnet IMD depth (~658 ETH) is far above the threshold, so this is latent; it becomes live if IMD liquidity drops ~20x or if maxEthPerBuy is raised. Fix: on a failed attempt halve the attempt size for the next call (e.g. keep a per-leg attempt divisor reset on success), or bound the attempt by a fraction of the pool's current liquidity read from getLiquidity().","line":300,"path":"src/AdamTreasury.sol","reproduction":"Local v4 PoolManager; hookless ETH/IMD pool with 20 ETH full-range depth, fee 1%; Treasury(maxEthPerBuy = 1 ETH, slippageBps = 300, cooldown 10 min).\n1. Fund 0.4444 ETH, process(): 0.2 ETH per leg, IMD leg succeeds (impact ~1%).\n2. Fund 6 ETH at once, then process() 6 times (one per cooldown): each call splits 2.222 ETH, each IMD attempt is 1 ETH, impact ~4.7% > 3% -> InsufficientOutput every time.\n   State after round 6: leg(0).pending = 2700000000000000000, leg(0).failingSince = first failure timestamp, no IMD bought.\n3. 10 more process() calls with no new fees: still 1 ETH attempts, still failing; IMD balance of distributor unchanged over 16 rounds.\n4. warp(failingSince + 3 days), process(): leg(0).pending -> 0, leg(1).pending += 2.7 ETH (1 ETH bought in the same call, 1.7 ETH left) - the IMD share is converted to PNKSTR.\n   Expected: the leg buys in chunks the pool can absorb (0.2 ETH works). Reproduced by test/scratch/Claims.t.sol:ThinPoolTest.","severity":"low","snippet":"        uint256 ethIn = l.pending < maxEthPerBuy ? l.pending : maxEthPerBuy;","title":"A failing leg always retries at min(pending, maxEthPerBuy); once pending >= maxEthPerBuy the attempt size never shrinks, so a pool that cannot absorb maxEthPerBuy within slippageBps strands the leg un"},{"citation":"resolved","description":"Three of the five fork tests fail when run against mainnet today (FOUNDRY_PROFILE=fork forge test, block 26126346): (a) line 122 asserts the deployer holds 0 ADAM after the single-sided mint, but LiquidityAmounts.getLiquidityForAmount1 floors the liquidity, so the position consumes 1e27 - 389 wei and 389 wei stay with the deployer; (b) line 180 asserts earned(alice, IMD) == the IMD received, but the dividend-per-share accumulator floors twice (mulDiv in _distribute and _pending) and returns 4170340508572387576 for 4170340508572387577 received; (c) line 102 assumes the PNKSTR hook keeps the tax as its own PNKSTR balance; on mainnet the hook's balance does not change (0 vs 169464806465880465753) while the measured tax is still 1001 bps, so the test's model of the external hook is wrong even though the Treasury's assumption (10% of output) is right. None of these are contract defects, but the brief's deliverable 2 is a passing fork suite, and these assertions will keep failing on every block. The two passing fork tests do confirm the live numbers the Treasury relies on: PNKSTR tax 10.01% of output; at 1 ETH, IMD out 244.15 vs minOut 237.18 and PNKSTR out 152087 vs minOut 147908.","line":122,"path":"test/fork/MainnetFork.t.sol","reproduction":"FOUNDRY_PROFILE=fork forge test -vv (RPC https://ethereum-rpc.publicnode.com, block 26126346):\n[FAIL: all ADAM is in the position: 389 != 0] test_deploymentAndSingleSidedPosition()\n[FAIL: assertion failed: 4170340508572387576 != 4170340508572387577] test_endToEndOnMainnetFork()\n[FAIL: assertion failed: 0 !~= 169464806465880465753 (max delta: 1%, real delta: 100%)] test_pnkstrHookBuyTaxIsTenPercent()\nExpected: assertLe(balanceOf(script), small dust) / assertApproxEqAbs(earned, imdGot, 1) / drop or re-model the hook-balance assertion.","severity":"low","snippet":"        assertEq(d.token.balanceOf(address(script)), 0, \"all ADAM is in the position\");","title":"Mainnet-fork suite fails at the current block on exact-equality assertions the integer math does not satisfy (389 wei ADAM left outside the position, 1-wei floor in earned, PNKSTR hook does not keep i"},{"citation":"resolved","description":"exit() calls _unstake(stakedBalance[msg.sender]) first, and _unstake reverts on amount == 0, so the combined 'withdraw and claim' entry point is unusable exactly at the zero-stake boundary even though rewardsAccrued is non-zero. claim() still works, so no funds are at risk; a front end that only exposes exit() would show the user a revert. Fix: skip the unstake when the staked balance is zero.","line":121,"path":"src/AdamDistributor.sol","reproduction":"stake(bal); notifyReward(IMD, 1e18); unstake(bal); earned(self, IMD) > 0; exit() -> revert ZeroAmount(). Expected: exit() claims the pending IMD. Reproduced by test/scratch/Claims.t.sol:test_D_exitRevertsWhenUnstakedButRewardsPending.","severity":"info","snippet":"        _unstake(stakedBalance[msg.sender]);","title":"exit() reverts with ZeroAmount for a holder who already unstaked everything but still has unclaimed rewards"},{"citation":"resolved","description":"The tree contains no README and no self-audit, while AdamDistributor's NatSpec points readers to README 'What differs from the brief'. Items that need to be stated for the deployer/holders: (1) the hook only governs its own ETH/ADAM pool; because LaunchToken must be a plain ERC-20, anyone can open a hookless ETH/ADAM pool (or the launch factory's own pool, which per the launch rules seeds 80% of supply at a 1.25% pool fee) and trade without paying the 1.5% treasury fee, so the fee stream depends on the hook pool being the deepest venue; (2) DeployAdam.launchPool hard-codes LIQUIDITY_ADAM = 1e27 (the full supply) so the manual script cannot be used as-is by a deployer who holds the post-factory share; (3) the Distributor is stake-based, not balance-based, so an ADAM holder earns nothing until they stake (the brief asks for distribution 'pro-rata to balance, updated on transfer'). These are scope/documentation gaps rather than code defects.","line":20,"path":"src/AdamDistributor.sol","reproduction":"ls repository root: no README*, no AUDIT*/SELF-AUDIT*; grep -r 'What differs from the brief' finds only the NatSpec reference at src/AdamDistributor.sol:20.","severity":"info","snippet":"/// \"shadow balance\" scheme cannot. See README \"What differs from the brief\".","title":"Deliverables 3 and 4 (self-audit, README) are absent; the code references a README section that does not exist, and three design limitations are undocumented"}],"hash":"84edd2a8d0b8e9474d2ef9260182c985d5db5c6a25f357d3f77199cc84cb5386","nodeId":"e6d2de9d-71ab-46aa-af44-65333fb63a28","outcome":"completed","summary":"The review is complete. The findings file is at `.imd-findings.json` (valid JSON, 6 findings, 24 coverage rows covering all 20 listed entry points plus 4 invariants). No source, test, config or lib file was changed; my only writes are the findings file and `test/scratch/`, which is gitignored.\n\n**What I found (math precision, boundary and numerical-gap area)**\n\n1. **Medium, `src/AdamHook.sol:180`.** The fee basis differs by swap type. Exact-input swaps charge the rate on the user's ETH, exact-output swaps charge it on the pool's ETH. During the anti-snipe window an exact-output buyer pays an effective 16.67% instead of 20% (same ADAM for 0.96 ETH instead of 1.00 ETH), and 1.478% instead of 1.5% afterwards. Proof test `test/scratch/ProofFeeBasis.t.sol` fails on this code with `1666 !~= 2000`.\n\n2. **Medium, `src/AdamTreasury.sol:272`.** The slippage floor is computed from slot0 in the same transaction as the swap, so it moves with any sandwich. Since `process()` is public, the attacker bundles pump, `process()`, dump. On a 50 ETH-deep IMD pool the attacker nets 0.139 ETH per call and the treasury fills 47% below the pre-pump floor without reverting. At today's mainnet depth (about 658 ETH, read from PoolManager storage) the sandwich is unprofitable, and PNKSTR is protected by its 10% buy tax, so this is a broken guarantee that becomes a loss when IMD liquidity thins. Proof test `test/scratch/ProofSlippage.t.sol` fails on this code.\n\n3. **Low, `src/AdamTreasury.sol:300`.** A failing leg always retries at `min(pending, maxEthPerBuy)` and never shrinks. On a pool too thin to absorb 1 ETH within 3%, 2.7 ETH of IMD backlog never executes across 16 rounds and is then dumped into PNKSTR by the 3-day fallback. Latent at current depth.\n\n4. **Low, `test/fork/MainnetFork.t.sol:122`.** Three of five fork tests fail at block 26126346 on exact-equality assertions: 389 wei ADAM left outside the position, a 1-wei floor in `earned`, and a wrong model of where the PNKSTR hook keeps its tax. The passing fork tests confirm the live numbers the treasury relies on (tax 10.01%, both 1 ETH quotes satisfiable).\n\n5. **Info.** `exit()` reverts at the zero-stake boundary while rewards are claimable; `claim()` still works.\n\n6. **Info.** README and self-audit deliverables are absent, the code cites a README section that does not exist, and three design limitations need documenting (hookless-pool fee avoidance, hard-coded 1e27 launch liquidity, stake-based rather than balance-based distribution).\n\n**What held.** Split arithmetic in `process()` is exact to the wei, the decay formula is monotone with correct boundaries at 0, 1799 and 1800 seconds, the distributor's accumulator cannot overflow for 18-decimal supplies and loses at most 1 wei per settle, and every slither and aderyn lead I checked either had a guard on the path or an error too small to matter. The full offline suite passes (77 tests).","treeHash":null,"usage":{"cachedInputTokens":5165678,"inputTokens":962,"model":"claude-fable-5-1","outputTokens":82117,"runtime":"claude","turns":48,"wallClockMs":1237440}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"4dd74fd7c315da80","findings":[{"citation":"resolved","description":"Asymmetry between the two fee paths. afterSwap (exact-output buy, exact-input sell) charges currentFeeBps of the ETH that actually moved (delta.amount0()). beforeSwap (exact-input buy, exact-output sell) charges currentFeeBps of params.amountSpecified and takes it from the PoolManager before the pool has decided how much of that amount fills. Uniswap v4 stops an exact-input swap at sqrtPriceLimitX96 and only charges the trader for the part that filled, but the hook's fee has already been taken on the whole request, and Hooks.afterSwap then adds that full fee to the trader's delta. The same happens on exact-output sells: the fee is 1.5% of the ETH requested even when the limit stops the pool after paying a fraction of it. A price limit is the normal way a router protects a trader against a sandwich, and a front-runner who pushes the price to the victim's limit makes the victim pay the full fee on a tiny fill. During the anti-snipe window the overcharge is 20% of the requested size: a 10 ETH exact-input buy that fills 1 ETH costs 1 ETH + 2 ETH fee. The overpaid ETH goes to the Treasury, so it is extracted from traders, not stolen by a third party. No test exercises a price limit; every test swaps at MIN/MAX. Fix options that keep the design: in afterSwap, for the two beforeSwap-fee cases, compare the ETH the pool actually moved (delta.amount0()) with the amount the fee was charged on and revert (e.g. PartialFillNotSupported) when they differ, or document that exact-input buys / exact-output sells must not use a price limit. The fee cannot be re-based in afterSwap because afterSwap can only return a delta in the unspecified currency.","line":166,"path":"src/AdamHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"@uniswap/v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"@uniswap/v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {PoolIdLibrary} from \"@uniswap/v4-core/src/types/PoolId.sol\";\nimport {Currency, CurrencyLibrary} from \"@uniswap/v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"@uniswap/v4-core/src/types/BalanceDelta.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"@uniswap/v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"@uniswap/v4-core/src/libraries/TickMath.sol\";\nimport {StateLibrary} from \"@uniswap/v4-core/src/libraries/StateLibrary.sol\";\nimport {PoolSwapTest} from \"@uniswap/v4-core/src/test/PoolSwapTest.sol\";\nimport {PoolModifyLiquidityTest} from \"@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {LiquidityAmounts} from \"@uniswap/v4-periphery/src/libraries/LiquidityAmounts.sol\";\nimport {MockERC20} from \"solmate/src/test/utils/mocks/MockERC20.sol\";\n\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {AdamDistributor} from \"src/AdamDistributor.sol\";\nimport {AdamTreasury} from \"src/AdamTreasury.sol\";\nimport {AdamHook} from \"src/AdamHook.sol\";\n\n/// @notice AdamHook charges exact-input buys (and exact-output sells) on the amount the trader *requested*,\n/// in beforeSwap, before the pool has decided how much of it will actually fill. A trader who protects\n/// themselves with a sqrtPriceLimit (or whose limit is reached because somebody moved the price first) pays\n/// the full fee on ETH that never entered the pool. The afterSwap paths charge on the real delta, so the\n/// four swap types are not charged the same way.\n///\n/// Fails on the current code: a 10 ETH exact-input buy capped at the price a 1 ETH buy reaches fills\n/// 0.985 ETH and pays 0.15 ETH of fees (15.2%, not 1.5%). Passes once the hook either refuses the partial\n/// fill or charges on what actually filled.\ncontract ProofHookPartialFillFeeTest is Test {\n    using PoolIdLibrary for PoolKey;\n    using StateLibrary for IPoolManager;\n\n    uint160 constant FLAGS = 0x2000 | 0x80 | 0x40 | 0x8 | 0x4;\n    int24 constant INITIAL_TICK = 177_240;\n    int24 constant LOWER_TICK = 108_180;\n\n    PoolManager poolManager;\n    PoolSwapTest swapRouter;\n    PoolModifyLiquidityTest lpRouter;\n    LaunchToken adam;\n    AdamDistributor distributor;\n    AdamTreasury treasury;\n    AdamHook hook;\n    PoolKey key;\n\n    receive() external payable {}\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        poolManager = new PoolManager(address(this));\n        swapRouter = new PoolSwapTest(poolManager);\n        lpRouter = new PoolModifyLiquidityTest(poolManager);\n        adam = new LaunchToken();\n        MockERC20 imd = new MockERC20(\"IMD\", \"IMD\", 18);\n        MockERC20 pnk = new MockERC20(\"PNKSTR\", \"PNKSTR\", 18);\n        distributor = new AdamDistributor(address(adam), address(imd), address(pnk), address(poolManager), address(0));\n        treasury = new AdamTreasury(\n            address(distributor),\n            makeAddr(\"team\"),\n            address(poolManager),\n            address(imd),\n            10_000,\n            200,\n            address(0),\n            0,\n            address(pnk),\n            0,\n            60,\n            address(0),\n            1000,\n            1 ether,\n            300,\n            600\n        );\n\n        bytes memory initCode = abi.encodePacked(\n            type(AdamHook).creationCode, abi.encode(address(poolManager), address(adam), address(treasury), address(this))\n        );\n        bytes32 codeHash = keccak256(initCode);\n        bytes32 salt;\n        for (uint256 s;; ++s) {\n            address predicted =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), s, codeHash)))));\n            if (uint160(predicted) & 0x3FFF == FLAGS) {\n                salt = bytes32(s);\n                break;\n            }\n        }\n        hook = new AdamHook{salt: salt}(poolManager, address(adam), address(treasury), address(this));\n\n        key = PoolKey({\n            currency0: CurrencyLibrary.ADDRESS_ZERO,\n            currency1: Currency.wrap(address(adam)),\n            fee: 0,\n            tickSpacing: 60,\n            hooks: IHooks(address(hook))\n        });\n        uint160 sqrtP = TickMath.getSqrtPriceAtTick(INITIAL_TICK);\n        poolManager.initialize(key, sqrtP);\n        uint128 liquidity =\n            LiquidityAmounts.getLiquidityForAmount1(TickMath.getSqrtPriceAtTick(LOWER_TICK), sqrtP, 1_000_000_000e18);\n        adam.approve(address(lpRouter), type(uint256).max);\n        lpRouter.modifyLiquidity(\n            key,\n            ModifyLiquidityParams({\n                tickLower: LOWER_TICK, tickUpper: INITIAL_TICK, liquidityDelta: int256(uint256(liquidity)), salt: 0\n            }),\n            \"\"\n        );\n        adam.approve(address(swapRouter), type(uint256).max);\n        vm.deal(address(this), 100 ether);\n        // The hook takes its fee from the singleton before the buyer settles; mainnet's PoolManager holds tens\n        // of thousands of ETH from other pools, so give this fresh one a float too.\n        vm.deal(address(poolManager), 1_000 ether);\n        // steady state: 1.5%\n        vm.warp(block.timestamp + 30 minutes);\n    }\n\n    function _swap(bool zeroForOne, int256 amountSpecified, uint160 limit, uint256 value) internal returns (BalanceDelta) {\n        return swapRouter.swap{value: value}(\n            key,\n            SwapParams({zeroForOne: zeroForOne, amountSpecified: amountSpecified, sqrtPriceLimitX96: limit}),\n            PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}),\n            \"\"\n        );\n    }\n\n    function test_exactInputBuyWithPriceLimitIsChargedOnlyOnWhatFilled() public {\n        // Where does a 1 ETH buy leave the price?\n        uint256 snap = vm.snapshotState();\n        _swap(true, -1 ether, TickMath.MIN_SQRT_PRICE + 1, 1 ether);\n        (uint160 sqrtAfterOneEth,,,) = IPoolManager(address(poolManager)).getSlot0(key.toId());\n        vm.revertToState(snap);\n\n        uint256 treasuryBefore = address(treasury).balance;\n        uint256 poolBefore = address(poolManager).balance;\n\n        // Ask for 10 ETH exact-in, but refuse to move the price further than a 1 ETH buy would.\n        try this.swapExternal(true, -10 ether, sqrtAfterOneEth, 10 ether) returns (BalanceDelta) {\n            uint256 filled = address(poolManager).balance - poolBefore; // ETH the pool actually took\n            uint256 fee = address(treasury).balance - treasuryBefore;\n            assertLt(filled, 2 ether, \"test setup: the limit must cut the fill well below 10 ETH\");\n            assertLe(fee, (filled * 150) / 10_000 + 1, \"fee charged on ETH that never entered the pool\");\n        } catch {\n            // A hook that refuses partial fills on the beforeSwap-fee paths is an acceptable fix.\n        }\n    }\n\n    function swapExternal(bool zeroForOne, int256 amountSpecified, uint160 limit, uint256 value)\n        external\n        returns (BalanceDelta)\n    {\n        return _swap(zeroForOne, amountSpecified, limit, value);\n    }\n}","reproduction":"LocalV4 harness, after the decay (fee 1.5%): (1) snapshot, buyExactIn(1 ether), read slot0 sqrtPrice P1, revert snapshot. (2) swapRouter.swap{value: 10 ether}(adamKey, SwapParams{zeroForOne: true, amountSpecified: -10 ether, sqrtPriceLimitX96: P1}). Observed: PoolManager ETH balance +0.985 ETH (the fill), Treasury +0.150 ETH (1.5% of the 10 ETH requested), trader charged 1.135 ETH. Expected: fee 1.5% of 0.985 ETH = 0.014775 ETH. Effective fee 1522 bps. Exact-output sell variant: buyExactIn(5 ether); find the price after selling 10% of the ADAM; swap(zeroForOne false, amountSpecified +4 ether, limit at that price): trader receives 0.535 ETH, Treasury takes 0.060 ETH (1.5% of the 4 ETH requested) = 11.2% of what was paid out. At launch (currentFeeBps 2000) the 10 ETH buy that fills ~1 ETH pays 2 ETH in fees. The attached proof reproduces the exact-input case standalone and asserts fee <= 1.5% of the filled amount, accepting a revert as a fix.","severity":"medium","snippet":"        uint256 ethAmount =\n            params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n        uint256 fee = _takeFee(params.zeroForOne, ethAmount);","title":"AdamHook charges the beforeSwap-path fee on the requested amount, not on what filled: price-limited exact-input buys and exact-output sells overpay without bound"},{"citation":"resolved","description":"Trust gap (public entry point x reward timing x first-staker asymmetry). While totalStaked == 0, every notifyReward parks the amount in unallocated[token]. The next _distribute with totalStaked > 0 adds the entire unallocated balance to rewardPerShare at that instant, i.e. to the stakers of that block only. notifyReward (line 130) is callable by anyone for a configured reward token, and process()/flushRewards() are public too, so the fold can be forced at will. The window is exactly the one the anti-snipe fee is meant to protect: the 20%->1.5% fees of the first 30 minutes arrive before any buyer could have staked (a holder must buy, then approve, then stake), and process() can be called by anyone during that time, moving the whole pot into unallocated. The first account to stake 1 wei of ADAM and call notifyReward(token, 1) in the same transaction receives 100% of it, with no capital at risk and no time staked. Every other holder of the period gets nothing, which contradicts the brief's pro-rata distribution and the staking model's own 'from the moment they stake' promise more than the documented staking deviation implies. Mitigations that keep the design: do not release unallocated in one shot (stream it over the next N notifications or over a time window), and restrict notifyReward to the Treasury (keep flushRewards) so the fold cannot be forced by an arbitrary caller; note that restricting notifyReward alone is not enough because the attacker can fund the Treasury with 0.001 ETH and call process() instead.","line":165,"path":"src/AdamDistributor.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {MockERC20} from \"solmate/src/test/utils/mocks/MockERC20.sol\";\n\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {AdamDistributor} from \"src/AdamDistributor.sol\";\n\n/// @notice Rewards that arrive while nobody is staked are parked in `unallocated` and handed in full to\n/// whoever is staked at the next `notifyReward`. `notifyReward` is callable by anyone with 1 wei of a reward\n/// token, so a holder of 1 wei ADAM can stake, trigger the fold, and exit in one transaction, taking every\n/// reward earned before the first stake (at launch: the whole anti-snipe window's fees).\n///\n/// Fails on the current code: the attacker receives 100% of 100e18 IMD for 1 wei ADAM + 1 wei IMD.\n/// Passes once pre-stake rewards can no longer be claimed atomically by a dust staker.\ncontract ProofDistributorUnallocatedCaptureTest is Test {\n    LaunchToken adam;\n    MockERC20 imd;\n    MockERC20 pnkstr;\n    AdamDistributor dist;\n    address attacker = makeAddr(\"attacker\");\n\n    function setUp() public {\n        adam = new LaunchToken();\n        imd = new MockERC20(\"IMD\", \"IMD\", 18);\n        pnkstr = new MockERC20(\"PNKSTR\", \"PNKSTR\", 18);\n        dist = new AdamDistributor(address(adam), address(imd), address(pnkstr), makeAddr(\"poolManager\"), address(0));\n        imd.mint(address(this), 1e30);\n        imd.approve(address(dist), type(uint256).max);\n    }\n\n    function test_dustStakerCannotTakePreStakeRewardsAtomically() public {\n        // Treasury pushes launch-window rewards while nobody has staked yet.\n        dist.notifyReward(address(imd), 100e18);\n        assertEq(dist.unallocated(address(imd)), 100e18);\n        assertEq(dist.totalStaked(), 0);\n\n        // Attacker holds 1 wei ADAM and 1 wei IMD.\n        adam.transfer(attacker, 1);\n        imd.mint(attacker, 1);\n\n        vm.startPrank(attacker);\n        adam.approve(address(dist), 1);\n        imd.approve(address(dist), 1);\n        dist.stake(1);\n        // Trigger the fold of `unallocated` while being the only staker.\n        try dist.notifyReward(address(imd), 1) {} catch {}\n        dist.exit();\n        vm.stopPrank();\n\n        // The attacker, holding one wei of a 1e27 supply, must not walk away with the pre-stake rewards.\n        assertLe(imd.balanceOf(attacker), 1e18, \"dust staker captured the pre-stake rewards\");\n        assertEq(adam.balanceOf(attacker), 1, \"stake returned\");\n    }\n}","reproduction":"Standalone (proof attached): deploy LaunchToken, two MockERC20 reward tokens and AdamDistributor; as the treasury stand-in call notifyReward(imd, 100e18) while totalStaked == 0 -> unallocated[imd] == 100e18. Give attacker 1 wei ADAM and 1 wei IMD. Attacker, in one transaction: adam.approve(dist, 1); dist.stake(1); imd.approve(dist, 1); dist.notifyReward(imd, 1); dist.exit(). Observed: imd.balanceOf(attacker) == 100000000000000000001 (all of it), adam back to the attacker. Expected: a 1-wei holder of a 1e27 supply gets at most a negligible share. Same result in the LocalV4 harness via the real flow: alice buyExactIn(5 ether) before anyone stakes; treasury.process() -> unallocated[imd] = 7.3945e18; attacker stakes 1 wei, notifyReward(imd, 1), exit -> attacker holds 7394509548599802401 wei IMD.","severity":"medium","snippet":"        distributed = amount + unallocated[token];\n        unallocated[token] = 0;","title":"Rewards received before the first stake are handed in full to whoever is staked at the next notify, and anyone can trigger that with 1 wei: a dust staker takes the whole launch-window pot atomically"},{"citation":"resolved","description":"Trust gap (access x economics). process() may be called by anyone, and the only price check on each buy is quoteMinOut, which reads sqrtPriceX96 from getSlot0 inside the same unlock callback (line 235 -> 272) immediately before poolManager.swap. A caller who first moves the pool price moves the floor with it, so the 3% tolerance only limits the Treasury's own price impact, never a manipulated price. Because the caller can be the manipulator, the attack is atomic (front-run, process(), back-run in one transaction) and bears no mempool risk. Loss per call is bounded by maxEthPerBuy per leg (1 ETH each) and by the cooldown (10 minutes). What makes it unprofitable today is not the check but the pools: the IMD pool charges 1% LP fee + 0.1% protocol fee each way and, read live on mainnet, has liquidity 0x230ffcecdabe88976d7 (about 1.03e22, 3.5x the local harness's 200 ETH full-range pool); the PNKSTR pool has 0 LP fee but a 10% buy tax on the front-run and liquidity about 1.58e23 (1.9x the harness). Linearising, a sandwich on a v-ETH victim buy in a pool with ETH-side depth R and round-trip fee f pays off when v/R > f, i.e. R below ~100 ETH for the IMD leg (f = 1%) and ~10 ETH for the PNKSTR leg (f = 10%); both pools are far above that now, so this is a resilience issue that becomes real if their liquidity thins. No test exercises price manipulation around process(); testFuzz_quoteIsLinear only shows the quote scales with input. A design-preserving fix is a cross-block reference: checkpoint each leg's sqrtPrice at every successful process() and refuse (fail the leg, keep pending) when the current spot deviates from the checkpoint by more than a bound, or require a private-relay keeper by making process() callable only by an allowlisted keeper set, which is a scope decision for the requester.","line":272,"path":"src/AdamTreasury.sol","reproduction":"LocalV4 harness (IMD pool: 200 ETH full range at ~223 IMD/ETH, 1% fee), after decay: fund the Treasury with 10 ETH, process() once (legit), warp +COOLDOWN. fairQuote = quoteMinOut(0, 1 ether) * 10000 / 9700 = 216.98e18 IMD. Attacker (1000 ETH) in one transaction: swap 100 ETH -> IMD on imdKey; treasury.process(); sell all IMD back. Observed: distributor receives 97.08e18 IMD for the 1 ETH IMD leg = 44% of the fair output, and process() does not revert (minOut was computed from the manipulated spot). Attacker ETH 1000 -> 998.88 (loses 1.12 ETH to pool fees and own impact), so at this depth the attack is pure griefing; the Treasury's holders lose 0.56 ETH of value per call. Expected: a slippage cap should make the leg fail (keep pending) when the execution price is far from an unmanipulated reference.","severity":"low","snippet":"        (uint160 sqrtPriceX96,, uint24 protocolFee, uint24 lpFee) = poolManager.getSlot0(l.key.toId());","title":"Treasury slippage floor is derived from the same-transaction slot0, so a permissionless process() caller chooses the price the Treasury accepts; only pool depth and pool fees, not the 'slippage cap', "},{"citation":"resolved","description":"Pair asymmetry between exit() and claim(). exit() is documented as 'withdraw all staked ADAM and claim every reward in one call', but it calls _unstake(stakedBalance[msg.sender]) first, and _unstake reverts on amount == 0. A holder who already unstaked (rewards stay claimable by design, see unstake's NatSpec) and then uses exit() to collect them is reverted, while claim() succeeds for the same state. Funds are not at risk; integrations that always call exit() to settle a position will fail for this state. Fix: in exit(), skip _unstake when the staked balance is zero.","line":121,"path":"src/AdamDistributor.sol","reproduction":"LocalV4 harness: alice buyExactIn(1 ether), approve + stake(all); treasury.process() (alice earns IMD/PNKSTR); alice unstake(all) -> earned(alice, imd) > 0, stakedBalance 0; alice exit() -> reverts AdamDistributor.ZeroAmount(). Expected: exit() pays out the accrued rewards (as claim() does) when there is nothing left to unstake.","severity":"low","snippet":"        _unstake(stakedBalance[msg.sender]);","title":"AdamDistributor.exit() reverts with ZeroAmount when the caller has nothing staked but has accrued rewards"},{"citation":"resolved","description":"Owner-only self-harm, recorded as a trust note rather than a vulnerability. Ownable2Step protects transferOwnership with an accept step, but renounceOwnership() (inherited from Ownable, not overridden) sets owner() to address(0) in one call. Since beforeInitialize requires sender == owner() and PoolManager.initialize never passes address(0) as sender, a renounce before launch makes every initialize revert OnlyOwnerCanInitialize; the hook address (which had to be mined) cannot be reused and the deployment must be repeated. After launch, renouncing is harmless and even desirable (it freezes feeBps). Suggested guard: revert renounceOwnership() while launchTimestamp == 0.","line":149,"path":"src/AdamHook.sol","reproduction":"Deploy the system as in the script; before poolManager.initialize(adamKey, sqrtP): hookOwner calls hook.renounceOwnership(). Then hookOwner (or anyone) calls poolManager.initialize(adamKey, sqrtP) -> reverts with OnlyOwnerCanInitialize() wrapped by the PoolManager; launchTimestamp stays 0 forever. Expected per the two-step ownership design: no single call should be able to strand the launch.","severity":"info","snippet":"        if (sender != owner()) revert OnlyOwnerCanInitialize();","title":"Hook owner can renounceOwnership() before the pool exists, after which beforeInitialize can never pass and the mined hook is dead"},{"citation":"resolved","description":"The brief lists pool, PoolManager, hook, Treasury, Distributor, zero and dead as excluded. AdamDistributor's constructor excludes zero, dead, itself, the token, the PoolManager, both reward tokens and one extra address; the script passes address(0) for the extra slot, so isExcluded(treasury) and isExcluded(hook) are false. The exclusion is only consulted in stake() on msg.sender, and AdamTreasury / AdamHook have no code path that calls stake(), so no reward can leak to them; the gap is a documentation/compliance one. If the author wants the set to match the brief, the Distributor must be deployed after the Treasury and Hook or take a second extra slot (the Hook address is only known after mining, so the deploy order in the script would need to change).","line":180,"path":"script/DeployAdam.s.sol","reproduction":"After DeployAdam.deployContracts(cfg): distributor.isExcluded(address(treasury)) == false and distributor.isExcluded(address(hook)) == false (test_constructorState only asserts the other entries). No state change is reachable from this: neither contract exposes a call into AdamDistributor.stake.","severity":"info","snippet":"        d.distributor = new AdamDistributor(address(d.token), cfg.imd, cfg.pnkstr, cfg.poolManager, address(0));","title":"Deploy script leaves the Treasury and the Hook out of the Distributor's excluded set (brief asks for both); harmless because neither contract can call stake()"}],"hash":"9d97c650305d25a510139a6add82bde20f37d53a7039a2da9e6a117bcdae6749","nodeId":"fed92c6b-de7b-43bf-842e-89c8e8d9afb6","outcome":"completed","summary":"The review is complete. The report is in `.imd-findings.json` at the repository root (excluded from git by the repo's own exclude file, as intended), the working tree is clean, and no source file was changed.\n\n**Area covered:** Access Control, Trust Gap and Asymmetry across the hook, Treasury, Distributor, token and deploy script. All 20 listed entry points have a coverage row, plus four invariant rows. I also ran the existing unit and fuzz suites once (all green) and read the live IMD and PNKSTR pools from mainnet to ground the slippage analysis.\n\n**Findings, by severity**\n\n- **Medium, AdamHook.beforeSwap.** The two beforeSwap fee paths charge on the amount the trader requested, before the pool decides how much fills. The afterSwap paths charge on the real delta. A price-limited 10 ETH exact-input buy that fills 0.985 ETH pays 0.15 ETH, an effective 15.2% fee, and 2 ETH during the anti-snipe window. The exact-output sell path has the same defect. A standalone proof test is attached and fails on the current code.\n- **Medium, AdamDistributor.notifyReward / _distribute.** Rewards that arrive before anyone stakes are handed in full to whoever is staked at the next notification, and anyone can force that with 1 wei. A holder of 1 wei ADAM captured all pre-stake rewards atomically. The launch window's 20% fees land in exactly this state. Proof attached. Restricting notifyReward alone is not enough, since a public `process()` call reaches the same fold.\n- **Low, AdamTreasury.quoteMinOut.** The slippage floor is read from the same-transaction slot0, so a permissionless `process()` caller sets the price the Treasury accepts. The Treasury bought at 44% of fair value in the harness. At live mainnet depth the attacker loses more than holders do, so the cap and pool fees, not the slippage check, are what protects funds today.\n- **Low, AdamDistributor.exit.** Reverts when nothing is staked but rewards are accrued, while `claim()` succeeds for the same state.\n- **Info.** Renouncing hook ownership before the pool exists strands the mined hook (owner self-harm). The deploy script leaves Treasury and Hook out of the Distributor's excluded set, which is harmless because neither can call stake.\n\n**Verified as holding:** owner powers are limited to lowering the fee and initializing the one ETH/ADAM pool; no third party can reach `unlockCallback`; hook re-entry from the PNKSTR hook or the team wallet is blocked; the Treasury's ETH accounting identity holds; the hook never holds funds.\n\n**Notes outside the findings list:** the repo has no README or self-audit yet, both required by the brief. The unit suite's first ETH buy only succeeds because the IMD and PNKSTR test pools pre-fund the PoolManager; the hook takes its fee from the singleton before the buyer settles, which is fine on mainnet (about 45,700 ETH held) but worth knowing for any fresh-chain deployment. The brief's full-supply single-sided position and the hook-fee pool also need reconciling with the platform's own factory pool, which the README should address.","treeHash":null,"usage":{"cachedInputTokens":2100556,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":79098,"runtime":"claude","turns":43,"wallClockMs":1175329}},{"artifacts":[],"attempt":1,"bundleHash":"a2076ae40daba51ea7efd5e22ae54ba8e95ac3df151a2266d73c300dbd346e29","device":"b97527597dd934a0","findings":[],"hash":"9e92b0d21f8138def6ff112de1f3054e97e0863c48557217ec21429d95306a75","nodeId":"e4e26c53-987e-49fb-ab4e-c5e08177a460","outcome":"completed","summary":"The response file is excluded from git by the harness's own `.git/info/exclude`, which is the expected handling for that channel. The work is complete.\n\n**Outcome:** the finding reproduced and is fixed within the manifest's scope. The diff touches only `launch.json`, and the finding's answer is in `.imd-responses.json`.\n\n**What reproduced**\n- A scratch test CREATE2-deployed AdamHook from a factory-style probe with salts 1 through 32. Zero succeeded, because the constructor rejects any address whose low 14 bits are not 0x20cc.\n- The same test deployed LaunchToken, AdamDistributor and AdamTreasury with arbitrary salts and the manifest's exact arguments. All three succeeded.\n\n**What changed in launch.json**\n- AdamHook is removed from the contracts list. Nothing the factory deploys depends on it: the Treasury constructor takes no hook address, its receive function accepts ETH from anyone, and the Distributor's extra exclusion stays zero.\n- Constructor arguments for AdamDistributor and AdamTreasury are unchanged and still match the source.\n- The notes now spell out the post-launch path already present in the accepted tree: the owner mines a salt and deploys AdamHook against the factory-deployed Treasury using step 4 of the deploy script, then initializes and single-sided seeds the hooked ETH/ADAM PoolKey from their own ADAM share. They state that only that hooked PoolKey pays the 1.5% fee and anti-snipe decay, and that frontends must pin it.\n\n**What the manifest cannot fix**\nThe second half of the finding, that the factory's own pool carries no AdamHook, is a platform constraint. The factory attaches its initialization-only guard, the guard has no swap callbacks, and the schema has no custom-hook field. The README already discloses that hookless pools pay nothing to the Treasury. The response file records this as confirmed but outside the manifest's reach.\n\n**Verification**\n- The manifest validates against the LaunchManifest schema plus the uniqueness, backward-reference and initialPrice refinements.\n- `forge build` succeeds and `forge test` passes all 95 tests.","treeHash":"b922d320db927dd80db03452c0b9210238ac4e7c","usage":{"cachedInputTokens":856475,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":14013,"runtime":"claude","turns":16,"wallClockMs":350321}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"9e51ef2afd7c2af8","findings":[{"citation":"resolved","description":"Rewards pushed by the Treasury while totalStaked == 0 are parked in `unallocated` and, per the NatSpec, 'folded into the next distribution'. The fold-in happens inside `_distribute`, which is reached by `notifyReward(token, amount)` for any caller and any amount >= 1 wei (line 130, no access control, no minimum). Nothing ties the backlog to the stakers it was meant for: whoever is staked at the moment of the next notify receives all of it pro-rata, and a 1 wei stake is 100% of totalStaked when nobody else has staked yet. At launch this state is the norm: buyers hold ADAM but staking is a separate, later transaction, while the anti-snipe fee (20% -> 1.5%) and `process()` are already running, so the first few ETH of IMD/PNKSTR rewards accumulate in `unallocated`. A bot that stakes 1 wei of ADAM and calls `notifyReward` with 1 wei of IMD and 1 wei of PNKSTR (or sends 1 wei of each to the Treasury and calls `flushRewards()`) takes the entire backlog and exits. The same mechanism lets the bot take 100% of every later `process()` as long as it is the only staker, which contradicts the brief's 'pro-rata to balance' intent. Economic Security guide: 'Abuse boundaries: zero balance, first participant'; Invariant guide: 'reward backlog belongs to future stakers' is broken.","line":165,"path":"src/AdamDistributor.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {AdamDistributor} from \"src/AdamDistributor.sol\";\n\ncontract RewardToken is ERC20 {\n    constructor() ERC20(\"R\", \"R\") {\n        _mint(msg.sender, 1e30);\n    }\n}\n\n/// @notice Rewards that arrive while nobody is staked are parked in `unallocated` and folded into the next\n/// distribution. `notifyReward` is permissionless and accepts 1 wei, so whoever stakes first can trigger that\n/// fold-in alone, with a 1 wei stake, and claim the whole backlog. Expected: a 1 wei staker cannot take a\n/// backlog it did not earn. Actual: it takes all of it.\ncontract UnallocatedSweepTest is Test {\n    LaunchToken internal adam;\n    RewardToken internal imd;\n    RewardToken internal pnkstr;\n    AdamDistributor internal dist;\n\n    address internal attacker = makeAddr(\"attacker\");\n    address internal holder = makeAddr(\"holder\");\n    address internal poolManager = makeAddr(\"poolManager\");\n\n    function setUp() public {\n        adam = new LaunchToken();\n        imd = new RewardToken();\n        pnkstr = new RewardToken();\n        dist = new AdamDistributor(address(adam), address(imd), address(pnkstr), poolManager, address(0));\n\n        // An honest holder bought 10% of the supply at launch; the attacker holds a single wei of ADAM\n        // and a single wei of each reward token.\n        adam.transfer(holder, 100_000_000e18);\n        adam.transfer(attacker, 1);\n        imd.transfer(attacker, 1);\n        pnkstr.transfer(attacker, 1);\n    }\n\n    function test_dustStakerSweepsRewardsAccruedWhileNobodyWasStaked() public {\n        // Launch fees were processed before anyone staked: 1,000 IMD and 100,000 PNKSTR are parked.\n        imd.approve(address(dist), type(uint256).max);\n        pnkstr.approve(address(dist), type(uint256).max);\n        dist.notifyReward(address(imd), 1_000e18);\n        dist.notifyReward(address(pnkstr), 100_000e18);\n        assertEq(dist.unallocated(address(imd)), 1_000e18);\n        assertEq(dist.unallocated(address(pnkstr)), 100_000e18);\n\n        // Attacker: stake 1 wei, notify 1 wei of each token (low-level so a permission fix does not revert the\n        // test), then exit. No capital, three transactions.\n        vm.startPrank(attacker);\n        adam.approve(address(dist), 1);\n        dist.stake(1);\n        imd.approve(address(dist), 1);\n        pnkstr.approve(address(dist), 1);\n        (bool ok1,) = address(dist).call(abi.encodeCall(AdamDistributor.notifyReward, (address(imd), 1)));\n        (bool ok2,) = address(dist).call(abi.encodeCall(AdamDistributor.notifyReward, (address(pnkstr), 1)));\n        ok1;\n        ok2;\n        dist.exit();\n        vm.stopPrank();\n\n        // The honest holder stakes right after.\n        vm.startPrank(holder);\n        adam.approve(address(dist), type(uint256).max);\n        dist.stake(100_000_000e18);\n        vm.stopPrank();\n\n        // A 1 wei staker must not walk away with a 1,000 IMD / 100,000 PNKSTR backlog.\n        assertLt(imd.balanceOf(attacker), 1e18, \"dust staker took the IMD backlog\");\n        assertLt(pnkstr.balanceOf(attacker), 1e18, \"dust staker took the PNKSTR backlog\");\n        // ...and the backlog is still there for real stakers (parked or already credited to the holder).\n        assertGe(\n            dist.unallocated(address(imd)) + dist.earned(holder, address(imd)), 1_000e18 - 1e18, \"IMD backlog lost\"\n        );\n    }\n}","reproduction":"State: distributor deployed, nobody staked; Treasury.process() has pushed 1,000 IMD and 100,000 PNKSTR (unallocated[imd]=1000e18, unallocated[pnkstr]=100000e18). Attacker holds 1 wei ADAM, 1 wei IMD, 1 wei PNKSTR. Calls: attacker stake(1); attacker notifyReward(IMD, 1); attacker notifyReward(PNKSTR, 1); attacker exit(). Expected: a 1 wei staker that provided 1 wei of reward takes at most dust. Actual: attacker's IMD balance = 1000000000000000000001 (1,000 IMD + 1 wei) and PNKSTR balance = 100,000 PNKSTR + 1 wei; unallocated is 0 and the holder who stakes next earns nothing from the launch backlog. Proof test test/scratch/UnallocatedSweep.t.sol fails on the current code with 'dust staker took the IMD backlog: 1000000000000000000001 >= 1000000000000000000'. Suggested fix (preserves the staking design): do not fold the backlog in on an arbitrary notify; e.g. stream `unallocated` into rewardPerShare over a fixed period, or only fold it in once totalStaked exceeds a meaningful floor, and restrict notifyReward to the Treasury so the fold-in moment cannot be chosen by the beneficiary.","severity":"high","snippet":"        distributed = amount + unallocated[token];","title":"Distributor: 1 wei staker plus permissionless 1 wei notifyReward sweeps the whole `unallocated` reward backlog"},{"citation":"resolved","description":"`quoteMinOut` reads `sqrtPriceX96` from `poolManager.getSlot0` inside `unlockCallback`, i.e. after any front-running swap in the same block has already moved the pool. The check `amountOut >= spot_after_manipulation * (1 - tax) * (1 - 3%)` therefore passes whenever the Treasury's own 1 ETH swap moves the pool less than 3%, regardless of how far the price already is from fair. The NatSpec relies on keepers using a private relay, but `process()` is callable by anyone, so the attacker simply bundles front-run -> process() -> back-run; no mempool visibility is needed and the cooldown only sets the schedule. Measured on a mainnet fork (block 26126314): a 100 ETH front-run on the real ETH/IMD pool makes the Treasury receive 179.75 IMD for 0.99 ETH where the pre-attack quote was 241.71 IMD (25.6% shortfall) with the leg marked successful; 300 ETH gives a 47.8% shortfall. On the real ETH/PNKSTR pool a 100 ETH front-run gives a 33.8% shortfall. At today's depth the attacker loses money (-1.79 ETH on IMD because of the 1% LP fee each way; -20.8 ETH on PNKSTR because its hook taxes both directions, ~19% round trip), so this is a loss under specific conditions: the loss becomes profitable for an outsider when the IMD pool's depth falls below roughly 50 ETH (local repro: 40 ETH depth / 40 ETH front-run -> treasury 75% short, attacker +0.13 ETH; 10 ETH depth / 25 ETH front-run -> 92% short, attacker +0.58 ETH), and it is profitable at any depth for an LP who holds most of the pool's liquidity because the LP fees that make it unprofitable flow back to them. Loss per call is capped by maxEthPerBuy at 1 ETH per leg (2 ETH per cooldown), and it falls on ADAM stakers, who receive fewer reward tokens. Economic Security guide: 'Sandwich every price-dependent operation'; Flow Gap seam execution x periphery x first principles ('slippage cap ... to limit MEV').","line":235,"path":"src/AdamTreasury.sol","reproduction":"Local (test/scratch/Sandwich.t.sol, LocalV4 environment): deploy an ETH/IMD pool with 40 ETH of full-range depth (fee 1%) and a Treasury with maxEthPerBuy=1 ETH, slippageBps=300 pointing at it; fund the Treasury with 2.2 ETH. Attacker (any EOA): (1) swap 40 ETH -> IMD on that pool; (2) call treasury.process(); (3) sell all IMD back. Expected: the IMD leg either reverts (InsufficientOutput) or receives within 3% of the pre-attack price. Actual: leg(0).pending == 0 (leg succeeded), distributor received 75.31% less IMD than the pre-attack spot quote, attacker's ETH balance rose by 131649602354111560 wei. Mainnet fork at block 26126314 with the real pools and a 100 ETH front-run: IMD leg received 179751843973103456848 vs fair 241706716375981088085 (-25.6%), PNKSTR leg 99898595616526534209101 vs 150957832631946834044978 (-33.8%); attacker P&L -1.79 ETH and -20.8 ETH respectively. Suggested fix within the design: bound minOut against a reference that the same transaction cannot move, e.g. the pool's price at the previous process() (store sqrtPrice per leg and require the new spot within a band of it), or an on-chain TWAP/observation, and/or let the caller pass minOut amounts with the permissionless path restricted to a stored-reference check.","severity":"medium","snippet":"        uint256 minOut = quoteMinOut(legId, ethIn);","title":"Treasury: minOut is derived from slot0 in the same transaction as the swap, so slippageBps bounds the Treasury's own impact but not a sandwich; public process() lets the attacker pick the moment"},{"citation":"resolved","description":"For exact-output sells `beforeSwap` computes the fee on `amountSpecified` (the ETH the seller asks for) and makes the pool pay that much more, so the fee is 1.5% of net = 1.478% of what the pool actually pays. For exact-input sells `afterSwap` computes it on the gross ETH the pool paid, i.e. a true 1.5%. The same asymmetry exists on buys (exact-out: 1.5% of the pool cost added on top). A seller who routes as exact-output pays 2.2 bps less of the ETH leg than one who routes as exact-input. Minor and non-exploitable beyond routing choice, but it means the two sell paths are not economically equivalent and the stated 1.5% holds only for one of them.","line":166,"path":"src/AdamHook.sol","reproduction":"LocalV4, after the decay: sellExactOut(0.5 ether) -> pool pays 0.5075 ETH gross, treasury gets 0.0075 ETH = 1.4778% of gross (test log: 507500000000000000, 7500000000000000, 14778). sellExactIn(adam/10) -> pool pays 0.155464 ETH gross, treasury gets 0.0023320 ETH = 1.4999% of gross (log: 155464325088113084, 2331964876321696, 14999). Expected: identical fee rate on both sell paths. Fix: in beforeSwap for exact-output, charge fee = amount * bps / (BPS - bps) so the fee is 1.5% of the gross ETH leg, or document that exact-output swaps pay 1.5% on top of the net amount.","severity":"low","snippet":"        uint256 ethAmount =\n            params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);","title":"Hook: exact-output swaps pay 1.5% of the net amount, exact-input swaps 1.5% of the gross; the 'fee on every buy and sell' is 1.478% on half the paths"},{"citation":"resolved","description":"LaunchToken is a plain ERC-20 (required by the launch rules) and the fee lives only in the hook, which only governs pools whose key names it. Anyone can initialize a second ETH/ADAM v4 pool with hooks = address(0) (or a v2/v3 pool) and route trades there with zero fee to the Treasury. Once an LP migrates liquidity, buyers paying 1.5% in the hooked pool are strictly worse off, so volume and the holder reward stream drift to the fee-free pool. This is inherent to the brief's hook-only fee on a plain token, so it is a documentation/expectation defect rather than a code bug, but the brief's 'fee on every buy and sell' is not what the contracts guarantee and the README that should state this (deliverable 4, referenced from src/AdamDistributor.sol:20) does not exist in the tree.","line":22,"path":"src/AdamHook.sol","reproduction":"LocalV4 (test/scratch/Misc.t.sol::test_hooklessPoolBypassesFee): alice calls poolManager.initialize(PoolKey{ETH, ADAM, fee 3000, tickSpacing 60, hooks 0}, sqrtPriceAtTick(currentTick)); the test contract adds 1 ETH + ADAM full-range liquidity through PoolModifyLiquidityTest; bob swaps 0.5 ETH -> ADAM on that key. Expected per brief: 0.0075 ETH reaches the Treasury. Actual: treasury balance unchanged (assertEq passes), bob holds ADAM. Mitigation options are outside the token (which must stay plain): document the limitation in the README and keep the hooked pool the deepest; nothing in code can prevent it.","severity":"low","snippet":"/// @notice Uniswap v4 hook for the single ETH/ADAM pool. Charges a fee in ETH on every buy and sell and","title":"Fee can be bypassed entirely by trading ADAM in any pool that does not use AdamHook (not documented; no README exists)"},{"citation":"resolved","description":"Running `FOUNDRY_PROFILE=fork forge test` against https://ethereum-rpc.publicnode.com (block 26126314) gives: test_pnkstrHookBuyTaxIsTenPercent FAIL '0 !~= 169464806465880465753' at this line (the real hook does not keep the tax as PNKSTR at its own address; the measured output shortfall is 1001 bps so the 10% figure itself holds); test_deploymentAndSingleSidedPosition FAIL 'all ADAM is in the position: 389 != 0' at line 122 (LiquidityAmounts rounding leaves 389 wei of ADAM with the deployer); test_endToEndOnMainnetFork FAIL '4190159466920675581 != 4190159466920675582' at line 180 (one-wei floor rounding in the per-share accumulator, which the unit tests already tolerate with assertApproxEqAbs). The brief lists mainnet-fork tests as a deliverable and these are the tests meant to justify PNKSTR_BUY_TAX_BPS = 1000 and the minOut model; as written they cannot pass against the chain they target, so the fork evidence for the economics is currently red. Also: the fork scratch run showed the real PNKSTR hook taxes sells too (1 ETH -> 152087 PNKSTR -> 0.8098 ETH back), which the local MockTaxHook does not model; that only affects attacker cost in finding 2, not the Treasury's buy path.","line":102,"path":"test/fork/MainnetFork.t.sol","reproduction":"Command: FOUNDRY_PROFILE=fork forge test -vv. Expected: 5 passed. Actual: 2 passed, 3 failed with the messages above. Fix: assert the hook's tax by output shortfall only (drop the hook-balance assertion), compare the deployer's leftover ADAM with assertLe(…, 1e3) and use assertApproxEqAbs(…, 1) for the per-share rounding.","severity":"low","snippet":"        assertApproxEqRel(IERC20(cfg.pnkstr).balanceOf(cfg.pnkstrHooks) - hookBefore, spot - got, 1e16);","title":"Mainnet-fork suite fails 3 of 5 tests on current mainnet state; its PNKSTR tax test asserts a custody model the real hook does not have"},{"citation":"resolved","description":"`currentFeeBps` decays from 20% over 30 minutes measured from `beforeInitialize`. The single-sided ADAM position is minted by a later transaction in `launchPool` (approve, Permit2 approve, modifyLiquidities), each signed manually per the brief ('deployer signs manually'). Until the position exists there is nothing to snipe, but the window is burning: if the mint lands 30 minutes or more after initialize (a stuck or re-signed transaction on a hardware wallet is enough), the first buyers pay 1.5% instead of 20% and the anti-snipe protection the brief asks for never applies; a partial delay shortens it proportionally. The same deploy script runs initialize and mint from the same broadcast but cannot make them atomic.","line":151,"path":"src/AdamHook.sol","reproduction":"LocalV4 without _launchAdamPool's liquidity step: hookOwner calls poolManager.initialize(adamKey, sqrtP) at T (launchTimestamp = T). Add the single-sided position at T + 30 minutes. First buyExactIn(1 ether): expected 0.2 ETH to the Treasury (anti-snipe), actual 0.015 ETH because currentFeeBps() == 150 (elapsed >= DECAY_DURATION). Fix: start the clock at the first swap (set launchTimestamp lazily in beforeSwap when it is 0) or at the first liquidity add (add afterAddLiquidity permission), so the 30-minute window begins when trading is actually possible.","severity":"low","snippet":"        launchTimestamp = uint64(block.timestamp);","title":"Anti-snipe clock starts at pool initialization, not when liquidity exists; the deploy script initializes and mints in separate transactions"},{"citation":"resolved","description":"The tree contains no README, no self-audit, and no document explaining the deviations the code makes from the brief: rewards go to *stakers* rather than to holders pro-rata to balance; Treasury and hook are not in the distributor's exclusion list (harmless, they cannot stake); a 3-day leg outage silently reroutes the 50/50 split to the surviving leg; the fee can be avoided in hookless pools; process() throughput is capped at 2.22 ETH per 10-minute cooldown (53 ETH/day with the brief's hourly schedule, so a 20%-fee launch backlog of e.g. 20 ETH takes 9 hourly calls to clear). Users and the deployer need these facts to operate the system (stake to earn; call process() at least every cooldown during launch).","line":20,"path":"src/AdamDistributor.sol","reproduction":"`git ls-files | grep -i readme` returns nothing; `find . -iname '*.md' -not -path './lib/*' -not -path './.imd/*'` returns nothing. src/AdamDistributor.sol:20 references README \"What differs from the brief\". Expected: README with deploy, add single-sided liquidity, process() schedule, claim (= stake + claim here) and the deviation list; a short self-audit with findings by severity.","severity":"info","snippet":"/// \"shadow balance\" scheme cannot. See README \"What differs from the brief\".","title":"Deliverables 3 and 4 (self-audit, README) are absent; the staking deviation from the brief is only mentioned in NatSpec that points to a README that does not exist"}],"hash":"acde13e2d83688f167a5ccb838f51fc0489013ded31876489589c6e074f74216","nodeId":"004093f2-7350-4ce8-8b8b-15278d41e43b","outcome":"completed","summary":"The review is complete. No tracked files changed; the only outputs are the findings file and the scratch tests under the ignored test/scratch directory.\n\n**Result: 7 findings written to .imd-findings.json, with a coverage row for all 20 entry points plus 6 invariant and lead rows.**\n\nFindings, by severity:\n\n- **High, Distributor backlog sweep.** Rewards that arrive while nobody is staked are parked in `unallocated` and folded into the next distribution. `notifyReward` is permissionless and accepts 1 wei, so a 1 wei staker can trigger the fold-in alone and claim the whole launch-phase backlog. Proof test attached and fails on current code (attacker ends with 1,000 IMD + 1 wei).\n- **Medium, Treasury sandwich.** `quoteMinOut` reads slot0 in the same transaction as the swap, so the 3% slippage bound only limits the treasury's own impact, not the distance from the fair price. Measured on a mainnet fork at block 26126314: a 100 ETH front-run makes the IMD leg accept 25.6% less than fair and still succeed. At current depth the attacker loses money (IMD pool fee, PNKSTR hook taxes both directions), so this is a loss under specific conditions: pool depth under roughly 50 ETH or an attacker who is the dominant LP.\n- **Low.** Exact-output swaps pay 1.478% of gross versus 1.5% on exact-input paths. Fee is bypassable through any hookless ETH/ADAM pool, which no README documents. Three of five mainnet-fork tests fail on current mainnet state, including a wrong assumption about where the PNKSTR hook keeps its tax. The anti-snipe clock starts at pool initialization rather than at liquidity, and the script cannot make those two steps atomic.\n- **Info.** README and self-audit deliverables are absent; the NatSpec points to a README section that does not exist.\n\nCoverage is complete for the Economic Security, Invariant and Flow Gap guides. One lead stayed unreached: a reverting IMD or PNKSTR transfer inside `notifyReward` would revert all of `process()`, but neither token exposes a pause, blacklist or proxy surface on mainnet, so it could not be reproduced. The stake-before-process snapshot behaviour was examined and judged inherent to the dividend-per-share design the brief requests, not a defect.","treeHash":null,"usage":{"cachedInputTokens":2426282,"inputTokens":546,"model":"claude-fable-5-1","outputTokens":64280,"runtime":"claude","turns":50,"wallClockMs":1008427}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"dd2ee4882a1be950","findings":[{"citation":"resolved","description":"AdamDistributor._distribute parks every reward that arrives while totalStaked == 0 in unallocated[token] (line 162) and folds the entire parked amount into the very next distribution (line 165), pro-rata to whoever is staked at that instant. stake() and notifyReward() are permissionless, there is no minimum stake, no eligibility delay and no streaming. At launch nobody is staked (holders must buy, approve and stake in later transactions) while the 20%->1.5% anti-snipe fees are the largest the Treasury will ever earn, and anyone can call process() during that window, so the whole launch pot lands in unallocated. The first account to stake 1 wei of ADAM and call notifyReward(token, 1) with 1 wei of the reward token receives 100% of it; the same works for every later process() as long as the attacker is the only staker. This pays holder funds to the wrong party at the cost of 1 wei ADAM, 1 wei of each reward token and three transactions, and it contradicts the brief's 'distributed to ADAM holders pro-rata'. Reported by audit_flow, audit_permissions, audit_economics and write_foundry_tests; merged here. Design-preserving fixes: stream the parked amount into rewardPerShare over a period or across the next N notifications instead of dumping it, and/or only fold it in once totalStaked exceeds a meaningful floor; restricting notifyReward to the Treasury alone is not enough because the attacker can fund the Treasury with dust and call process().","line":165,"path":"src/AdamDistributor.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {AdamDistributor} from \"src/AdamDistributor.sol\";\n\ncontract RewardToken is ERC20 {\n    constructor() ERC20(\"R\", \"R\") {\n        _mint(msg.sender, 1e30);\n    }\n}\n\n/// @notice Rewards that arrive while nobody is staked are parked in `unallocated` and folded into the next\n/// distribution. `notifyReward` is permissionless and accepts 1 wei, so whoever stakes first can trigger that\n/// fold-in alone, with a 1 wei stake, and claim the whole backlog. Expected: a 1 wei staker cannot take a\n/// backlog it did not earn. Actual: it takes all of it.\ncontract UnallocatedSweepTest is Test {\n    LaunchToken internal adam;\n    RewardToken internal imd;\n    RewardToken internal pnkstr;\n    AdamDistributor internal dist;\n\n    address internal attacker = makeAddr(\"attacker\");\n    address internal holder = makeAddr(\"holder\");\n    address internal poolManager = makeAddr(\"poolManager\");\n\n    function setUp() public {\n        adam = new LaunchToken();\n        imd = new RewardToken();\n        pnkstr = new RewardToken();\n        dist = new AdamDistributor(address(adam), address(imd), address(pnkstr), poolManager, address(0));\n\n        // An honest holder bought 10% of the supply at launch; the attacker holds a single wei of ADAM\n        // and a single wei of each reward token.\n        adam.transfer(holder, 100_000_000e18);\n        adam.transfer(attacker, 1);\n        imd.transfer(attacker, 1);\n        pnkstr.transfer(attacker, 1);\n    }\n\n    function test_dustStakerSweepsRewardsAccruedWhileNobodyWasStaked() public {\n        // Launch fees were processed before anyone staked: 1,000 IMD and 100,000 PNKSTR are parked.\n        imd.approve(address(dist), type(uint256).max);\n        pnkstr.approve(address(dist), type(uint256).max);\n        dist.notifyReward(address(imd), 1_000e18);\n        dist.notifyReward(address(pnkstr), 100_000e18);\n        assertEq(dist.unallocated(address(imd)), 1_000e18);\n        assertEq(dist.unallocated(address(pnkstr)), 100_000e18);\n\n        // Attacker: stake 1 wei, notify 1 wei of each token (low-level so a permission fix does not revert the\n        // test), then exit. No capital, three transactions.\n        vm.startPrank(attacker);\n        adam.approve(address(dist), 1);\n        dist.stake(1);\n        imd.approve(address(dist), 1);\n        pnkstr.approve(address(dist), 1);\n        (bool ok1,) = address(dist).call(abi.encodeCall(AdamDistributor.notifyReward, (address(imd), 1)));\n        (bool ok2,) = address(dist).call(abi.encodeCall(AdamDistributor.notifyReward, (address(pnkstr), 1)));\n        ok1;\n        ok2;\n        dist.exit();\n        vm.stopPrank();\n\n        // The honest holder stakes right after.\n        vm.startPrank(holder);\n        adam.approve(address(dist), type(uint256).max);\n        dist.stake(100_000_000e18);\n        vm.stopPrank();\n\n        // A 1 wei staker must not walk away with a 1,000 IMD / 100,000 PNKSTR backlog.\n        assertLt(imd.balanceOf(attacker), 1e18, \"dust staker took the IMD backlog\");\n        assertLt(pnkstr.balanceOf(attacker), 1e18, \"dust staker took the PNKSTR backlog\");\n        // ...and the backlog is still there for real stakers (parked or already credited to the holder).\n        assertGe(\n            dist.unallocated(address(imd)) + dist.earned(holder, address(imd)), 1_000e18 - 1e18, \"IMD backlog lost\"\n        );\n    }\n}","reproduction":"State: fresh AdamDistributor, totalStaked == 0; the treasury (any caller) notifies 1,000e18 IMD and 100,000e18 PNKSTR -> unallocated[imd] == 1000e18, unallocated[pnkstr] == 100000e18, nobody credited. Attacker holds 1 wei ADAM, 1 wei IMD, 1 wei PNKSTR. Calls: stake(1); notifyReward(imd, 1); notifyReward(pnkstr, 1); exit(). _distribute computes distributed = 1 + unallocated and rewardPerShare += distributed * 2^128 / 1. Expected: a 1 wei staker out of a 1e27 supply gets at most dust. Actual (test/scratch/ProofUnallocated.t.sol, fails on current code): imd.balanceOf(attacker) == 1000000000000000000001, pnkstr.balanceOf(attacker) == 100000e18 + 1, unallocated == 0; a holder of 100,000,000e18 ADAM who stakes right after earns 0 from the launch backlog. The same result is reachable through the real flow in the LocalV4 harness: buyExactIn(5 ether) before anyone stakes, treasury.process() -> unallocated[imd] = 7.39e18; attacker stake(1) + notifyReward(imd, 1) + exit() takes all of it.","severity":"high","snippet":"        distributed = amount + unallocated[token];","title":"Rewards parked in `unallocated` are released in full to whoever is staked at the next notify; a 1 wei staker takes the whole launch-period pot"},{"citation":"resolved","description":"Two integration gaps make the manifested launch unable to deliver the brief's core mechanism (1.5% ETH fee on every ETH/ADAM trade -> Treasury -> IMD/PNKSTR -> holders). (1) AdamHook's constructor runs Hooks.validateHookPermissions (src/AdamHook.sol:84), which reverts with HookAddressNotValid unless the deployed address carries the flags 0x20cc in its low 14 bits (1 chance in 16,384 per salt). The factory/floor harness deploys project contracts with a supplied salt (Project.protected.t.sol: probe.deploy(code, IMD_PROJECT_SALT_i)) and the manifest schema has no salt or mining field, so unless the deployment pipeline mines salts (no evidence of such support exists; the manifest's own notes say the blocker remains) the AdamHook deployment fails with 'project constructor failed' and the launch is rejected. (2) Even if the hook is deployed, the factory initialises the launch pool with its PoolInitializationGuard as the hook (launch.json pool: fee 3000, tickSpacing 60) and seeds 80% of the supply there. AdamHook.beforeSwap is only invoked for a PoolKey that names AdamHook, and AdamHook.beforeInitialize requires sender == $owner, so the hooked pool can only be created later by the owner with the 10% of supply they receive. The factory pool is then the deepest ETH/ADAM venue and charges no Treasury fee; every buy and sell routed through it (and through any third-party hookless pool, which anyone can initialise, reproduced below) pays nothing to the Treasury, the Distributor never receives IMD/PNKSTR, and the anti-snipe schedule never applies to the launch liquidity. Needed before admission: a deployment path that mines the AdamHook salt (or a project contract that mines and CREATE2-deploys the hook inside its own constructor), plus a decision on how the hooked pool gets created and seeded; the README must state that only the hooked PoolKey pays the fee and the official frontend must pin it. Reported in part as the hookless-pool limitation by audit_flow, audit_economics and audit_math; merged here with the manifest gap.","line":42,"path":"launch.json","reproduction":"(1) test/scratch/Review.t.sol::test_M_hookConstructorRevertsWithUnminedSalt: CREATE2-deploy abi.encodePacked(type(AdamHook).creationCode, abi.encode(poolManager, adam, treasury, owner)) with salts bytes32(1)..bytes32(32) from a probe contract. Expected (for a factory deployment): a usable hook. Actual: 0 of 32 deployments succeed (create2 returns address(0), constructor reverts HookAddressNotValid); only HookMiner.find with ~16k candidate salts produces a valid address. (2) test/scratch/Review.t.sol::test_G_hooklessPoolBypassesFee: after decay, alice initialises PoolKey{ETH, ADAM, fee 3000, tickSpacing 60, hooks 0} at the hooked pool's sqrtPrice, 1 ETH + ADAM full-range liquidity is added, bob swaps 0.5 ETH exact-in on that key. Expected per brief: 0.0075 ETH to the Treasury. Actual: bob receives 10815249502245295453005403 ADAM and the Treasury balance is unchanged. The manifest's pool entry (hooks = factory guard) is exactly such a key.","severity":"high","snippet":"      \"contract\": \"AdamHook\",\n      \"constructorArgs\": [\n        \"0x000000000004444c5dc75cb358380d2e3de08a90\",\n        \"$token\",\n        \"$contract:AdamTreasury\",\n        \"$owner\"\n      ]","title":"Launch manifest cannot instantiate the fee pipeline: AdamHook reverts under an unmined CREATE2 salt and the factory pool carries no AdamHook, so the Treasury never receives a fee"},{"citation":"resolved","description":"unlockCallback computes minOut from quoteMinOut (line 235), which reads sqrtPriceX96 from poolManager.getSlot0 at the moment of the swap. The only thing the 3% tolerance bounds is the Treasury's own price impact: any price an attacker sets one call earlier becomes the reference and the floor moves with it. Because process() is public, the attacker needs no mempool visibility and bundles pump -> process() -> dump atomically, so the NatSpec advice to use a private relay does not help. Loss per call is capped by maxEthPerBuy (1 ETH per leg, 2 ETH per 10-minute cooldown) and falls on ADAM stakers, who receive fewer reward tokens. Measured on a mainnet fork at the current block (test/fork/MainnetFork.t.sol::test_measureSandwichAroundProcessOnRealImdPool): a 50 ETH pump makes the Treasury accept 88% of its pre-pump floor and the attacker loses 0.92 ETH to the pool's 1.1% round-trip fees, so at today's ~650 ETH virtual depth this is griefing with bounded loss; it becomes profitable once the IMD pool's ETH-side depth falls below roughly 100 ETH (local measurement: 50 ETH depth / 20 ETH pump -> attacker +0.136 ETH, treasury receives 52% of the floor), and it is profitable at any depth for an LP who holds most of that pool's liquidity because the fees flow back to them. The brief asks for a 'slippage cap ... to limit MEV'; as implemented only the size cap limits it. Reported by all five contributors; merged. Design-preserving fix: checkpoint each leg's sqrtPrice at every successful buy (or keep an EMA) and refuse the leg (keep pending, it is already fault tolerant) when the current spot deviates from the checkpoint by more than a bound; keep slippageBps as the per-call tolerance on top of that reference.","line":272,"path":"src/AdamTreasury.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"@uniswap/v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"@uniswap/v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {Currency, CurrencyLibrary} from \"@uniswap/v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"@uniswap/v4-core/src/types/BalanceDelta.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"@uniswap/v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"@uniswap/v4-core/src/libraries/TickMath.sol\";\nimport {PoolSwapTest} from \"@uniswap/v4-core/src/test/PoolSwapTest.sol\";\nimport {PoolModifyLiquidityTest} from \"@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {LiquidityAmounts} from \"@uniswap/v4-periphery/src/libraries/LiquidityAmounts.sol\";\nimport {MockERC20} from \"solmate/src/test/utils/mocks/MockERC20.sol\";\n\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {AdamDistributor} from \"src/AdamDistributor.sol\";\nimport {AdamTreasury} from \"src/AdamTreasury.sol\";\n\n/// @notice AdamTreasury derives each buy's minimum output from the pool's slot0 inside the same transaction as the\n/// swap, so the configured 3% tolerance is measured against whatever price the caller of the permissionless\n/// `process()` set one call earlier. Local ETH/IMD pool with 50 ETH of full-range depth (1% fee):\n/// pump 20 ETH -> process() -> dump. The IMD leg is marked successful although it received ~47% less IMD than\n/// the floor quoted before the pump.\n///\n/// Fails on the current code. Passes once the leg either refuses (keeps its ETH pending) or fills within the\n/// tolerance of a reference the same transaction cannot move (e.g. the price checkpointed at the previous\n/// successful process()).\ncontract ProofTreasurySlippageFloorTest is Test {\n    PoolManager pm;\n    PoolSwapTest swapRouter;\n    PoolModifyLiquidityTest lpRouter;\n    MockERC20 imd;\n    MockERC20 pnk;\n    PoolKey imdKey;\n    PoolKey pnkKey;\n    AdamDistributor dist;\n    AdamTreasury treasury;\n\n    receive() external payable {}\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        pm = new PoolManager(address(this));\n        swapRouter = new PoolSwapTest(pm);\n        lpRouter = new PoolModifyLiquidityTest(pm);\n        imd = new MockERC20(\"IMD\", \"IMD\", 18);\n        pnk = new MockERC20(\"PNKSTR\", \"PNKSTR\", 18);\n        imd.mint(address(this), 1e36);\n        pnk.mint(address(this), 1e36);\n        imd.approve(address(lpRouter), type(uint256).max);\n        pnk.approve(address(lpRouter), type(uint256).max);\n        imd.approve(address(swapRouter), type(uint256).max);\n        vm.deal(address(this), 10_000 ether);\n\n        imdKey = PoolKey({\n            currency0: CurrencyLibrary.ADDRESS_ZERO,\n            currency1: Currency.wrap(address(imd)),\n            fee: 10_000,\n            tickSpacing: 200,\n            hooks: IHooks(address(0))\n        });\n        pnkKey = PoolKey({\n            currency0: CurrencyLibrary.ADDRESS_ZERO,\n            currency1: Currency.wrap(address(pnk)),\n            fee: 0,\n            tickSpacing: 60,\n            hooks: IHooks(address(0))\n        });\n        _initAndSeed(imdKey, 54_000, -887_200, 887_200, 50 ether);\n        _initAndSeed(pnkKey, 120_420, -887_220, 887_220, 200 ether);\n\n        LaunchToken adam = new LaunchToken();\n        dist = new AdamDistributor(address(adam), address(imd), address(pnk), address(pm), address(0));\n        treasury = new AdamTreasury(\n            address(dist),\n            makeAddr(\"team\"),\n            address(pm),\n            address(imd),\n            10_000,\n            200,\n            address(0),\n            0,\n            address(pnk),\n            0,\n            60,\n            address(0),\n            0,\n            1 ether,\n            300,\n            600\n        );\n\n        // One honest process() first, so a fixed implementation has a reference price to anchor to.\n        _fund(1 ether);\n        treasury.process();\n        vm.warp(block.timestamp + 600);\n    }\n\n    function _initAndSeed(PoolKey memory key, int24 tick, int24 lower, int24 upper, uint256 ethAmount) internal {\n        uint160 sqrtP = TickMath.getSqrtPriceAtTick(tick);\n        pm.initialize(key, sqrtP);\n        uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(\n            sqrtP, TickMath.getSqrtPriceAtTick(lower), TickMath.getSqrtPriceAtTick(upper), ethAmount, type(uint128).max\n        );\n        lpRouter.modifyLiquidity{value: ethAmount}(\n            key,\n            ModifyLiquidityParams({\n                tickLower: lower, tickUpper: upper, liquidityDelta: int256(uint256(liquidity)), salt: 0\n            }),\n            \"\"\n        );\n    }\n\n    function _fund(uint256 amount) internal {\n        (bool ok,) = payable(address(treasury)).call{value: amount}(\"\");\n        require(ok);\n    }\n\n    function _swap(PoolKey memory key, bool zeroForOne, int256 amountSpecified, uint256 value)\n        internal\n        returns (BalanceDelta)\n    {\n        return swapRouter.swap{value: value}(\n            key,\n            SwapParams({\n                zeroForOne: zeroForOne,\n                amountSpecified: amountSpecified,\n                sqrtPriceLimitX96: zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1\n            }),\n            PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}),\n            \"\"\n        );\n    }\n\n    function test_sandwichedProcessDoesNotFillFarBelowTheUnmanipulatedPrice() public {\n        _fund(2.3 ether); // cap 2.222 ETH -> 1 ETH per leg\n        uint256 fairFloor = treasury.quoteMinOut(0, 1 ether);\n        uint256 distBefore = imd.balanceOf(address(dist));\n\n        // Attacker, all in one transaction: pump the IMD pool, call process(), dump.\n        BalanceDelta pump = _swap(imdKey, true, -20 ether, 20 ether);\n        treasury.process();\n        _swap(imdKey, false, -int256(uint256(uint128(pump.amount1()))), 0);\n\n        uint256 received = imd.balanceOf(address(dist)) - distBefore;\n        bool refused = treasury.leg(0).pending == 1 ether;\n        emit log_named_uint(\"pre-pump floor for 1 ETH (IMD wei)\", fairFloor);\n        emit log_named_uint(\"IMD received by the distributor\", received);\n        emit log_named_uint(\"received as % of pre-pump floor\", (received * 100) / fairFloor);\n        emit log_named_int(\"attacker ETH delta (wei)\", int256(address(this).balance) - int256(10_000 ether - 250 ether - 1 ether - 2.3 ether));\n        assertTrue(\n            refused || received >= (fairFloor * 97) / 100, \"slippage floor followed the attacker's spot price\"\n        );\n    }\n}","reproduction":"test/scratch/ProofSlippage.t.sol (fails on current code): local v4 PoolManager, hookless ETH/IMD pool with 50 ETH full-range depth and 1% fee, Treasury(maxEthPerBuy = 1 ETH, slippageBps = 300, cooldown 600). One honest process() first, warp past cooldown, fund 2.3 ETH (1 ETH per leg). fairFloor = quoteMinOut(0, 1 ether) = 208821411098003554484 IMD. Attacker in one transaction: swap 20 ETH -> IMD; treasury.process(); sell the IMD back. Expected per the brief's slippage cap: the IMD leg is refused (leg(0).pending stays 1 ETH) or fills >= 97% of the pre-pump floor. Actual: leg(0).pending == 0 (leg marked successful), the distributor receives 109479591633818261553 IMD (52% of the floor), attacker net +0.136 ETH. Same shape in the LocalV4 harness (200 ETH depth, 100 ETH pump): received 97.7e18 vs fair floor 212.6e18 (45%), attacker -1.12 ETH. Mainnet fork, 50 ETH pump: 88% of floor, attacker -0.92 ETH.","severity":"medium","snippet":"        (uint160 sqrtPriceX96,, uint24 protocolFee, uint24 lpFee) = poolManager.getSlot0(l.key.toId());","title":"Treasury slippage floor is derived from the pool's spot price inside the same transaction, so `slippageBps` never bounds a sandwich of the permissionless process()"},{"citation":"resolved","description":"For the two paths where ETH is the specified currency (exact-input buy, exact-output sell) the fee is computed from params.amountSpecified and taken from the PoolManager in beforeSwap, before the pool has decided how much of the request fills. The afterSwap paths charge on the real delta, so the four swap types are not charged the same way. Uniswap v4 stops a swap at sqrtPriceLimitX96 (or when liquidity runs out) and only charges the trader for the filled part, but Hooks.afterSwap then adds the full beforeSwap fee to the trader's delta. Consequences: a trader who protects themselves with a price limit (the normal sandwich defence) pays the full fee on ETH that never entered the pool, and a front-runner who pushes the price to the victim's limit makes them pay 1.5% (20% during anti-snipe) of the whole request on a tiny fill; at launch the pool holds no ETH, so an exact-output sell of 1 ETH by anyone holding ADAM outside the pool (the factory allocations exist from day one) fills 0, consumes 0 ADAM and still costs the seller 0.2 ETH. The Universal Router rejects a negative output delta (TAKE_ALL) and sets no price limit, so its users are not affected; any integrator calling PoolManager.swap with a limit or handling partial fills loses ETH to the Treasury. Reported by audit_permissions (medium) and write_foundry_tests (low); merged at medium because a user can lose a bounded amount of ETH for no fill. Fix options that keep the design: in afterSwap, for the two beforeSwap-fee cases compare the ETH the pool actually moved (delta.amount0()) with the amount the fee was charged on and revert on a mismatch (PartialFillNotSupported), the fee cannot be re-based in afterSwap because afterSwap can only return a delta in the unspecified currency, so the alternative is to document that these two paths must not be used with a price limit.","line":166,"path":"src/AdamHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"@uniswap/v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"@uniswap/v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {PoolIdLibrary} from \"@uniswap/v4-core/src/types/PoolId.sol\";\nimport {Currency, CurrencyLibrary} from \"@uniswap/v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"@uniswap/v4-core/src/types/BalanceDelta.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"@uniswap/v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"@uniswap/v4-core/src/libraries/TickMath.sol\";\nimport {StateLibrary} from \"@uniswap/v4-core/src/libraries/StateLibrary.sol\";\nimport {PoolSwapTest} from \"@uniswap/v4-core/src/test/PoolSwapTest.sol\";\nimport {PoolModifyLiquidityTest} from \"@uniswap/v4-core/src/test/PoolModifyLiquidityTest.sol\";\nimport {LiquidityAmounts} from \"@uniswap/v4-periphery/src/libraries/LiquidityAmounts.sol\";\nimport {MockERC20} from \"solmate/src/test/utils/mocks/MockERC20.sol\";\n\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {AdamDistributor} from \"src/AdamDistributor.sol\";\nimport {AdamTreasury} from \"src/AdamTreasury.sol\";\nimport {AdamHook} from \"src/AdamHook.sol\";\n\n/// @notice AdamHook charges exact-input buys (and exact-output sells) on the amount the trader *requested*,\n/// in beforeSwap, before the pool has decided how much of it will actually fill. A trader who protects\n/// themselves with a sqrtPriceLimit (or whose limit is reached because somebody moved the price first) pays\n/// the full fee on ETH that never entered the pool. The afterSwap paths charge on the real delta, so the\n/// four swap types are not charged the same way.\n///\n/// Fails on the current code: a 10 ETH exact-input buy capped at the price a 1 ETH buy reaches fills\n/// 0.985 ETH and pays 0.15 ETH of fees (15.2%, not 1.5%). Passes once the hook either refuses the partial\n/// fill or charges on what actually filled.\ncontract ProofHookPartialFillFeeTest is Test {\n    using PoolIdLibrary for PoolKey;\n    using StateLibrary for IPoolManager;\n\n    uint160 constant FLAGS = 0x2000 | 0x80 | 0x40 | 0x8 | 0x4;\n    int24 constant INITIAL_TICK = 177_240;\n    int24 constant LOWER_TICK = 108_180;\n\n    PoolManager poolManager;\n    PoolSwapTest swapRouter;\n    PoolModifyLiquidityTest lpRouter;\n    LaunchToken adam;\n    AdamDistributor distributor;\n    AdamTreasury treasury;\n    AdamHook hook;\n    PoolKey key;\n\n    receive() external payable {}\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        poolManager = new PoolManager(address(this));\n        swapRouter = new PoolSwapTest(poolManager);\n        lpRouter = new PoolModifyLiquidityTest(poolManager);\n        adam = new LaunchToken();\n        MockERC20 imd = new MockERC20(\"IMD\", \"IMD\", 18);\n        MockERC20 pnk = new MockERC20(\"PNKSTR\", \"PNKSTR\", 18);\n        distributor = new AdamDistributor(address(adam), address(imd), address(pnk), address(poolManager), address(0));\n        treasury = new AdamTreasury(\n            address(distributor),\n            makeAddr(\"team\"),\n            address(poolManager),\n            address(imd),\n            10_000,\n            200,\n            address(0),\n            0,\n            address(pnk),\n            0,\n            60,\n            address(0),\n            1000,\n            1 ether,\n            300,\n            600\n        );\n\n        bytes memory initCode = abi.encodePacked(\n            type(AdamHook).creationCode, abi.encode(address(poolManager), address(adam), address(treasury), address(this))\n        );\n        bytes32 codeHash = keccak256(initCode);\n        bytes32 salt;\n        for (uint256 s;; ++s) {\n            address predicted =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), s, codeHash)))));\n            if (uint160(predicted) & 0x3FFF == FLAGS) {\n                salt = bytes32(s);\n                break;\n            }\n        }\n        hook = new AdamHook{salt: salt}(poolManager, address(adam), address(treasury), address(this));\n\n        key = PoolKey({\n            currency0: CurrencyLibrary.ADDRESS_ZERO,\n            currency1: Currency.wrap(address(adam)),\n            fee: 0,\n            tickSpacing: 60,\n            hooks: IHooks(address(hook))\n        });\n        uint160 sqrtP = TickMath.getSqrtPriceAtTick(INITIAL_TICK);\n        poolManager.initialize(key, sqrtP);\n        uint128 liquidity =\n            LiquidityAmounts.getLiquidityForAmount1(TickMath.getSqrtPriceAtTick(LOWER_TICK), sqrtP, 1_000_000_000e18);\n        adam.approve(address(lpRouter), type(uint256).max);\n        lpRouter.modifyLiquidity(\n            key,\n            ModifyLiquidityParams({\n                tickLower: LOWER_TICK, tickUpper: INITIAL_TICK, liquidityDelta: int256(uint256(liquidity)), salt: 0\n            }),\n            \"\"\n        );\n        adam.approve(address(swapRouter), type(uint256).max);\n        vm.deal(address(this), 100 ether);\n        // The hook takes its fee from the singleton before the buyer settles; mainnet's PoolManager holds tens\n        // of thousands of ETH from other pools, so give this fresh one a float too.\n        vm.deal(address(poolManager), 1_000 ether);\n        // steady state: 1.5%\n        vm.warp(block.timestamp + 30 minutes);\n    }\n\n    function _swap(bool zeroForOne, int256 amountSpecified, uint160 limit, uint256 value) internal returns (BalanceDelta) {\n        return swapRouter.swap{value: value}(\n            key,\n            SwapParams({zeroForOne: zeroForOne, amountSpecified: amountSpecified, sqrtPriceLimitX96: limit}),\n            PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}),\n            \"\"\n        );\n    }\n\n    function test_exactInputBuyWithPriceLimitIsChargedOnlyOnWhatFilled() public {\n        // Where does a 1 ETH buy leave the price?\n        uint256 snap = vm.snapshotState();\n        _swap(true, -1 ether, TickMath.MIN_SQRT_PRICE + 1, 1 ether);\n        (uint160 sqrtAfterOneEth,,,) = IPoolManager(address(poolManager)).getSlot0(key.toId());\n        vm.revertToState(snap);\n\n        uint256 treasuryBefore = address(treasury).balance;\n        uint256 poolBefore = address(poolManager).balance;\n\n        // Ask for 10 ETH exact-in, but refuse to move the price further than a 1 ETH buy would.\n        try this.swapExternal(true, -10 ether, sqrtAfterOneEth, 10 ether) returns (BalanceDelta) {\n            uint256 filled = address(poolManager).balance - poolBefore; // ETH the pool actually took\n            uint256 fee = address(treasury).balance - treasuryBefore;\n            assertLt(filled, 2 ether, \"test setup: the limit must cut the fill well below 10 ETH\");\n            assertLe(fee, (filled * 150) / 10_000 + 1, \"fee charged on ETH that never entered the pool\");\n        } catch {\n            // A hook that refuses partial fills on the beforeSwap-fee paths is an acceptable fix.\n        }\n    }\n\n    function swapExternal(bool zeroForOne, int256 amountSpecified, uint160 limit, uint256 value)\n        external\n        returns (BalanceDelta)\n    {\n        return _swap(zeroForOne, amountSpecified, limit, value);\n    }\n}","reproduction":"test/scratch/ProofPartialFill.t.sol (fails on current code), steady state 1.5%: snapshot, buyExactIn(1 ether), read sqrtPrice P1, revert. swap{value: 10 ether}(key, SwapParams(zeroForOne true, amountSpecified -10 ether, sqrtPriceLimitX96 P1)). Observed: PoolManager ETH +0.985 ETH (the fill), Treasury +0.150 ETH (1.5% of the 10 ETH requested). Expected: 0.014775 ETH. test/scratch/Review.t.sol::test_C3 with the limit one tick below spot: filled 62326235860304942 wei, fee 150000000000000000 wei, trader paid 0.212 ETH (fee 240% of the swapped amount). test_C2 at launch (20%): alice holds 1e24 ADAM, swaps zeroForOne false, amountSpecified +1 ether, limit MAX_SQRT_PRICE-1. Observed delta0 = -0.2 ETH, delta1 = 0, alice ETH -0.2 ETH, ADAM unchanged, Treasury +0.2 ETH. Expected: fee proportional to the 0 ETH received.","severity":"medium","snippet":"        uint256 ethAmount =\n            params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n        uint256 fee = _takeFee(params.zeroForOne, ethAmount);","title":"beforeSwap charges the fee on the requested ETH amount, not on what fills: exact-input buys with a price limit and exact-output sells overpay, and at launch an exact-output sell pays 20% of the reques"},{"citation":"resolved","description":"Exact-input buys charge r of the ETH the user pays and exact-input sells charge r of the ETH the pool pays out (gross), but exact-output buys charge r of the ETH the pool charged and add it on top (user pays P + rP, effective r/(1+r) of spend), and exact-output sells (beforeSwap, line 166-168) charge r of the net ETH requested and make the pool pay that much more (r/(1+r) of gross). The same trade therefore pays a different fee depending on which swap type the router picks: during the 30-minute anti-snipe window 16.67% vs 20% (the Treasury receives 0.16 ETH instead of 0.20 ETH per ETH of buying), in steady state 1.478% vs 1.5%. Routers that support exact-output (the Universal Router does) get the lower rate on every such swap. No funds are lost and the steady-state gap is 2.2 bps, hence low. Reported by audit_math (medium) and audit_economics (low); merged. Fix: on the two exact-output paths compute fee = ethAmount * bps / (BPS - bps) so that fee / (ethAmount + fee) == bps / BPS.","line":179,"path":"src/AdamHook.sol","reproduction":"test/scratch/Review.t.sol::test_D_exactOutputBuyPaysLowerRateAtLaunch (LocalV4, fee 2000 bps at launch): exact-input buy of 1 ETH -> Treasury 0.200000000000000000 ETH, X ADAM received. Revert state; exact-output buy of the same X ADAM -> buyer pays 0.960000000000000000 ETH in total, Treasury receives 0.160000000000000000 ETH, effective 1666 bps. Expected: the same ADAM costs 1.0 ETH with 0.2 ETH fee. Sells (existing unit test test_sellExactOutput_feeIsAddedOnTopOfEthOut): exact-output sell of 0.5 ETH -> pool pays 0.5075 ETH, Treasury 0.0075 ETH = 1.478% of gross, while an exact-input sell pays 1.5% of gross.","severity":"low","snippet":"        int128 ethDelta = delta.amount0();\n        uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\n        uint256 fee = _takeFee(params.zeroForOne, ethAmount);","title":"Exact-output swaps pay fee/(1+fee) of the user-side ETH instead of the fee rate: 16.67% instead of 20% at launch, 1.478% instead of 1.5% afterwards"},{"citation":"resolved","description":"exit() passes the caller's current stake to _unstake, which reverts with ZeroAmount when it is 0, before _claim runs. A holder who unstaked earlier (rewards stay claimable by design, see unstake's NatSpec) and later calls the entry point documented as 'withdraw all staked ADAM and claim every reward in one call' is reverted and must know to call claim() instead; an integration that always settles through exit() fails for this state. No funds at risk. Reported by all four specialists and the test writer; merged. Fix: in exit(), skip _unstake when stakedBalance[msg.sender] == 0.","line":121,"path":"src/AdamDistributor.sol","reproduction":"test/scratch/Review.t.sol::test_E_exitRevertsWithZeroStakeAndRewards (LocalV4): alice buyExactIn(1 ether), stakes all, treasury.process() (fund 1 ETH), alice unstake(all) -> earned(alice, imd) == 99863298716427918633 > 0, stakedBalance 0. alice exit() -> reverts AdamDistributor.ZeroAmount(). alice claim() -> pays the 99.86e18 IMD. Expected: exit() pays the rewards.","severity":"low","snippet":"        _unstake(stakedBalance[msg.sender]);","title":"exit() reverts for a holder who already unstaked but still has unclaimed rewards"},{"citation":"resolved","description":"failingSince is set on the first failed attempt and only cleared by a success; the reroute compares the current failure's timestamp against that first failure with no requirement that any attempt in between failed, or happened at all, so the NatSpec promise 'if a leg has failed continuously for LEG_FALLBACK_DELAY' is not what the code checks. Two amplifiers: (a) a transient failure followed by a quiet period (no process() call that reaches a successful swap for that leg, e.g. no new fees) and one more transient failure moves the whole pending amount of that leg to the other token; (b) any ETH amount is split and executed, including dust where the IMD pool's fee rounding (input rounded down by 1 wei of a few wei) exceeds the 3% tolerance, so an attacker can send 10 wei and call process() to stamp failingSince on a perfectly healthy IMD leg at any time. Impact is limited to the IMD/PNKSTR mix of the holders' rewards (value is not lost), hence low. Reported by audit_flow and write_foundry_tests; merged. Fix: require the reroute to be preceded by a minimum number of consecutive failed attempts (or reset failingSince whenever an attempt is skipped because ethIn == 0), and skip execution when ethIn is below a small minimum, keeping the wei pending.","line":313,"path":"src/AdamTreasury.sol","reproduction":"test/scratch/Review.t.sol::test_F1_dustMarksHealthyLegFailing: send 10 wei to the Treasury and call process(). Observed: leg(0).pending == 4, leg(0).failingSince == block.timestamp (InsufficientOutput: the pool rounds the 4 wei input to 3 wei after its 1% fee), leg(1) executed. test_F2_isolatedFailuresReroute: T0: pnkstrHook tax 15%, fund 1 ETH, process() -> PNKSTR leg fails, failingSince = T0, pending 0.45 ETH; tax back to 10%; no calls for 3 days; T0 + 3 days: fund 1 ETH, tax momentarily 15%, process(). Observed: leg(1).pending == 0, leg(0).pending == 0.9 ETH (both PNKSTR shares rerouted to IMD). Expected per NatSpec: no reroute after two isolated failures with no attempts in between.","severity":"low","snippet":"            if (l.failingSince == 0) {\n                l.failingSince = uint64(block.timestamp);\n            } else if (block.timestamp >= uint256(l.failingSince) + LEG_FALLBACK_DELAY) {","title":"Leg reroute triggers on two isolated failures 3 days apart, not on continuous failure, and a dust-sized process() marks a healthy leg as failing"},{"citation":"resolved","description":"quoteMinOut requires the fill to be within slippageBps of spot after fees; the Treasury's own impact for a 1 ETH exact-input buy exceeds 3% once the pool's ETH-side depth is below roughly 33 ETH. _executeLeg never lowers the attempt size: as long as pending >= maxEthPerBuy it retries exactly maxEthPerBuy, fails, and accumulates more, although a smaller chunk (0.2 ETH) would pass. After LEG_FALLBACK_DELAY the backlog is moved to the other leg, so holders get 0% IMD / 100% PNKSTR for that period instead of 50/50. Today's mainnet IMD depth (~650 ETH virtual reserve) is far above the threshold, so this is latent; it becomes live if IMD liquidity drops ~20x or maxEthPerBuy is raised. Reported by audit_math. Fix: halve the attempt size on each failed attempt (per-leg divisor reset on success), or bound the attempt by a fraction of getLiquidity().","line":300,"path":"src/AdamTreasury.sol","reproduction":"test/scratch/Review.t.sol::test_L_thinPoolRetryNeverShrinks: local hookless ETH/IMD2 pool with 20 ETH full-range depth (fee 1%), Treasury(maxEthPerBuy 1 ETH, slippage 300, cooldown 10 min). Fund 2.3 ETH, process(): leg(0).pending == 1 ETH, failingSince set. Five more process() calls one cooldown apart: pending stays >= 1 ETH (1.035 ETH after the unsplit remainder is added), failingSince unchanged, 0 IMD2 bought in 6 rounds. A second Treasury with maxEthPerBuy = 0.2 ETH against the same pool buys at once (leg(0).pending == 0). warp(failingSince + 3 days), process(): leg(0).pending -> 0, the IMD share is converted to PNKSTR.","severity":"low","snippet":"        uint256 ethIn = l.pending < maxEthPerBuy ? l.pending : maxEthPerBuy;","title":"A failing leg always retries min(pending, maxEthPerBuy); the attempt never shrinks, so a pool that cannot absorb 1 ETH within 3% strands the leg until it is rerouted"},{"citation":"resolved","description":"The tracked tree has no README, no self-audit and no .md file; AdamDistributor's NatSpec refers to README 'What differs from the brief'. The brief lists both as deliverables (deploy, add single-sided liquidity, call process() on a schedule, claim; findings by severity). Facts that live nowhere else and that holders, keepers and the deployer need: rewards go to stakers, not to balances tracked on transfer (unstaked ADAM earns nothing; claim = stake + claim); a just-in-time staker captures the pro-rata share of each push without holding through time (inherent to the dividend-per-share pattern, cheaper here than in a balance-tracked design); only the hooked PoolKey pays the fee and the factory pool / any hookless pool does not (finding 2); the hook owner's powers (initialize once, lowerFee, renounce); the measured PNKSTR tax (10.01% of output at the fork block) and the 1000 bps constant it justifies; throughput of process() (2.22 ETH per 10-minute cooldown); the 3-day reroute; the exclusion set that omits hook and Treasury. Reported by all four specialists and the test writer; merged.","line":20,"path":"src/AdamDistributor.sol","reproduction":"git ls-files | grep -i -E 'readme|audit|\\.md$' returns nothing (only sources, tests, script, config and launch.json are tracked); grep -r 'What differs from the brief' finds only the NatSpec reference at src/AdamDistributor.sol:20. Expected: a README covering deploy, single-sided liquidity, process() schedule and claim, plus a short self-audit.","severity":"low","snippet":"/// \"shadow balance\" scheme cannot. See README \"What differs from the brief\".","title":"Deliverables 3 and 4 (self-audit, README) are absent; the code points to a README section that does not exist and the design deviations are undocumented"},{"citation":"resolved","description":"The brief excludes pool, PoolManager, hook, Treasury, Distributor and zero/dead addresses. The constructor excludes zero, dead, itself, ADAM, the PoolManager and both reward tokens; the hook and the Treasury are deployed after the Distributor (circular dependency) and the only extra slot is address(0) in both the script and launch.json. Neither AdamHook nor AdamTreasury has a code path that calls stake(), so nothing is exploitable; it is a documented deviation. Options: precompute the hook/treasury addresses, deploy the Distributor after the Treasury and pass it in the extra slot, or state the deviation in the README.","line":180,"path":"script/DeployAdam.s.sol","reproduction":"After DeployAdam.deployContracts (LocalV4 setUp): distributor.isExcluded(address(hook)) == false and distributor.isExcluded(address(treasury)) == false; distributor.isExcluded(poolManager) == true. No state change is reachable from this.","severity":"info","snippet":"        d.distributor = new AdamDistributor(address(d.token), cfg.imd, cfg.pnkstr, cfg.poolManager, address(0));","title":"Distributor exclusion set omits the hook and the Treasury the brief lists; the deploy script and manifest pass address(0) for the extra slot"},{"citation":"resolved","description":"currentFeeBps decays from 20% over 30 minutes measured from beforeInitialize. The single-sided ADAM position is minted by a later transaction (approve, Permit2 approve, modifyLiquidities), each signed manually. If the mint lands 30 minutes or more after initialize (a stuck or re-signed hardware-wallet transaction is enough) the first buyers pay 1.5% and the anti-snipe protection never applies; a partial delay shortens it proportionally. Deployer-operational, so informational. Fix: start the clock lazily at the first swap (set launchTimestamp in beforeSwap when it is 0) or at the first liquidity add.","line":151,"path":"src/AdamHook.sol","reproduction":"test/scratch/Review.t.sol::test_J_antiSnipeClockStartsAtInitialize: deploy a fresh system, hook owner initialises the pool at T (currentFeeBps == 2000), the single-sided position is added at T + 30 minutes, first buyExactIn(1 ether). Expected 0.2 ETH to the Treasury; actual 0.015 ETH (fee 150 bps, decay already elapsed).","severity":"info","snippet":"        launchTimestamp = uint64(block.timestamp);","title":"Anti-snipe clock starts at pool initialization, not when liquidity exists; the deploy script initializes and mints in separate transactions"}],"hash":"b7c3165a027131fb81d9ec95376464e53d03c798006e8ae8241fb7ed63a7a84b","nodeId":"0a8840e7-8209-4552-8c32-9e63aa3b3eb9","outcome":"completed","summary":"Review complete. The findings file is written at `.imd-findings.json` (11 findings, 3 with proofs, 23 coverage rows covering all 20 entry points plus three invariant rows). No tracked file was changed; scratch tests live in the gitignored `test/scratch/`.\n\n**What I kept, after reproducing each against the code**\n\n1. **High** – `AdamDistributor` folds the entire pre-stake `unallocated` pot into the next notify, so a 1 wei staker plus a 1 wei `notifyReward` takes the whole launch-window pot. Four contributors reported it; merged, proof attached (fails on current code with the attacker holding 1000e18 + 1 IMD).\n2. **High** – The launch manifest cannot instantiate the fee pipeline. The hook constructor reverts under any unmined CREATE2 salt (0 of 32 salts deploy in my test), and the factory pool uses the guard hook, so AdamHook governs no pool and the Treasury never receives a fee. I merged the specialists' \"hookless pool bypass\" into this as the amplifier.\n3. **Medium** – The Treasury's slippage floor reads slot0 in the same transaction as the swap, so a pump → `process()` → dump bundle moves the floor with it. Reported by all five; I wrote a self-contained proof (50 ETH pool, 20 ETH pump: leg accepted at 52% of the pre-pump floor, attacker +0.136 ETH). On today's mainnet depth it is unprofitable griefing (fork test: 88% of floor, attacker −0.92 ETH).\n4. **Medium** – beforeSwap charges the fee on the requested amount, not the fill. A price-limited 10 ETH buy fills 0.062 ETH yet pays 0.15 ETH; an exact-output sell at launch pays 0.2 ETH for a zero fill. Proof attached.\n5. **Low** – Exact-output swaps pay r/(1+r): 16.67% instead of 20% at launch.\n6. **Low** – `exit()` reverts when the stake is zero but rewards are pending.\n7. **Low** – `failingSince` reroute fires on two isolated failures 3 days apart, and a 10 wei `process()` stamps a healthy leg as failing (two reports merged).\n8. **Low** – A failing leg never shrinks its retry size, so a thin pool strands the leg until reroute.\n9. **Low** – README and self-audit deliverables are missing.\n10. **Info** – Exclusion set omits hook and Treasury. 11. **Info** – Anti-snipe clock starts at initialize, not at the liquidity mint.\n\n**What I dropped**\n\n- The three \"mainnet-fork suite fails\" reports are stale. The test-writer commit already loosened the assertions, and the suite passes 8 of 8 against live mainnet at the current block.\n- The reward-token-pause note and the renounce-before-launch note are dependency/owner self-harm items, not defects; the latter is noted in the coverage row.\n\n**Severity calls worth noting**: I raised the unallocated capture to high (permissionless, dust-cost, pays holder funds to the wrong party), kept the sandwich at medium (bounded loss, unprofitable at current depth), and lowered the exact-output fee basis to low (2.2 bps in steady state).","treeHash":null,"usage":{"cachedInputTokens":4410267,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":70810,"runtime":"claude","turns":59,"wallClockMs":1037629}},{"artifacts":[],"attempt":1,"bundleHash":"3d9393470eabc071de5f4114aca3e1299c99f99c9cb63f953208ed66278eca8e","device":"c4e8f98d7a2a37dc","findings":[{"description":"The brief requires rewards pro rata to ADAM wallet balances, updated on transfer. The accepted implementation instead uses totalStaked/stakedBalance; LaunchToken has no distributor transfer callback. An ordinary holder receives nothing and newly notified rewards can belong entirely to a sole staker. This is already disclosed in the existing README as an intentional architecture deviation, not a newly discovered hidden exploit. The conservation tests validate custody in that architecture but cannot establish the requested wallet-holder eligibility. Implement wallet-balance dividend accounting or explicitly resolve the specification conflict; tests must not bless zero wallet-holder rewards as meeting the brief.","line":174,"path":"src/AdamDistributor.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {LaunchToken} from \"src/LaunchToken.sol\";\nimport {AdamDistributor} from \"src/AdamDistributor.sol\";\n\n/// @notice Reproduces the brief's unmet wallet-holder dividend requirement without a fork.\ncontract WalletEligibilityProof is Test {\n    function test_unstakedHolderReceivesProRataRewards() public {\n        LaunchToken adam = new LaunchToken();\n        LaunchToken imd = new LaunchToken();\n        LaunchToken pnk = new LaunchToken();\n        AdamDistributor d = new AdamDistributor(address(adam), address(imd), address(pnk), address(0x9000), address(0));\n        address alice = address(0xA11CE);\n        adam.transfer(alice, adam.totalSupply());\n        imd.approve(address(d), 100e18);\n        pnk.approve(address(d), 200e18);\n        d.notifyReward(address(imd), 100e18);\n        d.notifyReward(address(pnk), 200e18);\n        vm.prank(alice);\n        d.claim();\n        // Alice owns 100% of all eligible ADAM. No opt-in staking was required by the brief.\n        assertApproxEqAbs(imd.balanceOf(alice), 100e18, 1, \"wallet holder lost her IMD entitlement\");\n        assertApproxEqAbs(pnk.balanceOf(alice), 200e18, 1, \"wallet holder lost her PNKSTR entitlement\");\n    }\n}","reproduction":"Run the attached WalletEligibilityProof with forge test --match-path <proof-path>. Deploy LaunchToken ADAM, two standard fixed-supply ERC20 reward stand-ins and AdamDistributor; transfer all 1,000,000,000 ADAM to Alice without staking; notify 100e18 IMD and 200e18 PNKSTR; Alice calls claim(). Expected under the brief: Alice, the sole eligible holder, gets 100e18 IMD and 200e18 PNKSTR, within one wei. Actual: both balances are zero and rewards remain unallocated. Locally reproduced: wallet holder lost her IMD entitlement: 0 !~= 100000000000000000000.","severity":"medium","title":"Wallet holders receive no dividends unless they opt into staking"}],"hash":"cc32cb683589902d7f5495130032aff694ba0d042ea1424adb74a3978b58225e","nodeId":"c351a697-c3a7-425f-b397-d518567526f6","outcome":"completed","summary":"Added adversarial unit/fuzz tests and four invariants across two randomized harnesses.\n\nValidation passed: `forge build`, 111 offline test entries, and all six mainnet-fork tests.\n\nReported one medium specification gap in `.imd-findings.json`: unstaked ADAM holders receive no rewards. Included a reproduced failing proof. Coverage and assumptions are documented in `test/TESTING.md`.","treeHash":"b007161436eff900f169a39075ae0407c0f07559","usage":{"cachedInputTokens":2774400,"inputTokens":161555,"model":"gpt-6-astra","outputTokens":24519,"runtime":"codex","turns":8,"wallClockMs":946118}},{"artifacts":[],"attempt":1,"bundleHash":"cf0eeebf1a915a8ab5dbb95f60e209c09e6c8476df2bea65a8f7499d97111d1f","device":"38c0c4e8e4438177","findings":[],"hash":"d3b4d16d95bb06c4ed2831fed25afbbaa330d908b9825b4177a451148b18d1c3","nodeId":"e4e26c53-987e-49fb-ab4e-c5e08177a460","outcome":"completed","summary":"Created [launch.json](/home/imd-worker2/.identitymd/work/e497ebcf-33bb-4548-bfd7-fa95843dc898/e4e26c53-987e-49fb-ab4e-c5e08177a460/launch.json), the only submission change.\n\nSchema/ABI validation, `forge build`, 77 default tests, and 9 protected checks passed. Protected checks used a locally mined hook salt; mainnet forks were not run.\n\nNotes document staking requirements and existing factory/hook integration blockers. Full deployment readiness remains unresolved.","treeHash":"8c3175b4e30f622ea1b849ae53165c4471424817","usage":{"cachedInputTokens":866560,"inputTokens":53677,"model":"gpt-6-astra","outputTokens":9748,"runtime":"codex","turns":5,"wallClockMs":415681}},{"artifacts":[],"attempt":1,"bundleHash":"a65cdd8079a3ad5c6572beffaf26114919b4aed41c4076977783b5517983561f","device":"ddeff5e2c1df0f4a","findings":[],"hash":"e7f58dc73d9860d08e56e29288a9b7481beaffdf2c44c538b50bd29c14702f56","nodeId":"e4e26c53-987e-49fb-ab4e-c5e08177a460","outcome":"completed","summary":"Updated [launch.json](/home/seat23/.identitymd/work/e497ebcf-33bb-4548-bfd7-fa95843dc898/e4e26c53-987e-49fb-ab4e-c5e08177a460/launch.json) notes to match the accepted implementation. Constructor arguments and pool settings remain unchanged.\n\nSchema and ABI checks passed. Offline build and all 106 tests passed, including protected checks and a second fuzz seed with 512 runs. Mainnet-fork tests were not rerun.\n\nOnly `launch.json` is in the deliverable diff.","treeHash":"a9c2a411692b94466197386232cd1f3aaf218e57","usage":{"cachedInputTokens":965888,"inputTokens":64110,"model":"gpt-6-astra","outputTokens":8756,"runtime":"codex","turns":6,"wallClockMs":395367}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"c4617da5f1fd5ce8","findings":[],"hash":"ed34bac92eb60706536b43f0636030297d82342656038e8e205d79899102d30d","nodeId":"c351a697-c3a7-425f-b397-d518567526f6","outcome":"failed","summary":"could not restore accepted work: accepted work conflicts with a dependency, which plan validation should have prevented: ","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":null,"outputTokens":0,"runtime":"claude","turns":0,"wallClockMs":478}}],"verification":[{"checks":[{"durationMs":92353,"exitCode":0,"name":"build","output":"Compiling 120 files with Solc 0.8.26\nSolc 0.8.26 finished in 92.17s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to view\n   --> test/invariant/TreasuryInvariant.t.sol:459:5:\n    |\n459 |     function _parseLegEvents()\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:138:77\n    │\n138 │                 || pnkstrTaxBps >= BPS || imd == pnkstr || imdTickSpacing > uint24(type(int24).max)\n    │                                                                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint24' 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/AdamTreasury.sol:139:40\n    │\n139 │                 || pnkstrTickSpacing > uint24(type(int24).max)\n    │                                        ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint24' 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/AdamTreasury.sol:153:26\n    │\n153 │             tickSpacing: int24(imdTickSpacing),\n    │                          ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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/AdamTreasury.sol:161:26\n    │\n161 │             tickSpacing: int24(pnkstrTickSpacing),\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamHook.sol:109:13\n    │\n109 │         if (elapsed >= DECAY_DURATION) return base;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamHook.sol:151:27\n    │\n151 │         launchTimestamp = uint64(block.timestamp);\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamHook.sol:153:35\n    │\n153 │         emit PoolLaunched(poolId, uint64(block.timestamp));\n    │                                   ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamHook.sol:167:42\n    │\n167 │             params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamDistributor.sol:105:9\n    │\n105 │         emit Staked(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamHook.sol:167:77\n    │\n167 │             params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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-no-eth]: external call can be reentered before `_legs` is updated\n    ╭▸ src/AdamTreasury.sol:304:13\n    │\n304 │         try poolManager.unlock(abi.encode(legId, ethIn)) returns (bytes memory result) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-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/AdamDistributor.sol:191:13\n    │\n191 │             emit RewardClaimed(account, token, amount);\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/AdamDistributor.sol:140:9\n    │\n140 │         emit RewardNotified(token, msg.sender, received, distributed);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamHook.sol:180:44\n    │\n180 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:180:52\n    │\n180 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:180:74\n    │\n180 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:180:82\n    │\n180 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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-no-eth]: external call can be reentered before `_legs` is updated\n    ╭▸ src/AdamTreasury.sol:310:13\n    │\n310 │             distributor.notifyReward(token, amountOut);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:178:13\n    │\n178 │         if (block.timestamp < availableAt) revert CooldownActive(availableAt);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamTreasury.sol:308:13\n    │\n308 │             emit LegBought(legId, token, ethIn, amountOut);\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/AdamTreasury.sol:312:13\n    │\n312 │             emit LegFailed(legId, ethIn, reason);\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/AdamTreasury.sol:321:17\n    │\n321 │                 emit LegRerouted(legId, other, moved);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:184:25\n    │\n184 │         lastProcessed = uint64(block.timestamp);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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-eth]: uncapped ETH transfer can be reentered before `_status` is updated\n    ╭▸ src/AdamTreasury.sol:207:22\n    │\n207 │         (bool ok,) = teamWallet.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-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/AdamTreasury.sol:209:9\n    │\n209 │         emit TeamPaid(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/AdamTreasury.sol:219:13\n    │\n219 │             distributor.notifyReward(token, balance);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/AdamTreasury.sol:216:31\n    │\n216 │             uint256 balance = IERC20(token).balanceOf(address(this));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/AdamTreasury.sol:219:13\n    │\n219 │             distributor.notifyReward(token, balance);\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/AdamTreasury.sol:220:13\n    │\n220 │             emit RewardsFlushed(token, balance);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:239:53\n    │\n239 │                 zeroForOne: true, amountSpecified: -int256(ethIn), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1\n    │                                                     ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' 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/AdamHook.sol:249:9\n    │\n249 │         emit FeeTaken(isBuy, ethAmount, fee, bps);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:245:30\n    │\n245 │         if (ethDelta >= 0 || uint256(uint128(-ethDelta)) != ethIn) revert PartialFill();\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/AdamTreasury.sol:245:38\n    │\n245 │         if (ethDelta >= 0 || uint256(uint128(-ethDelta)) != ethIn) revert PartialFill();\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/AdamTreasury.sol:247:29\n    │\n247 │         uint256 amountOut = uint256(uint128(outDelta));\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/AdamTreasury.sol:247:37\n    │\n247 │         uint256 amountOut = uint256(uint128(outDelta));\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/AdamTreasury.sol:250:9\n    │\n250 │         poolManager.settle{value: ethIn}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/AdamTreasury.sol:280:16\n    │\n280 │         return (afterTax * (BPS - slippageBps)) / BPS;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamDistributor.sol:178:9\n    │\n178 │         emit Unstaked(msg.sender, amount);\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/AdamTreasury.sol:291:13\n    │\n291 │             emit TeamPaid(amount);\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/AdamTreasury.sol:294:13\n    │\n294 │             emit TeamPaymentDeferred(amount);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[return-bomb]: external call with a gas limit may copy unbounded return data\n    ╭▸ src/AdamTreasury.sol:289:22\n    │\n289 │         (bool ok,) = teamWallet.call{value: amount, gas: TEAM_PUSH_GAS}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:315:24\n    │\n315 │             } else if (block.timestamp >= uint256(l.failingSince) + LEG_FALLBACK_DELAY) {\n    │                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:314:34\n    │\n314 │                 l.failingSince = uint64(block.timestamp);\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/fuzz/Treasury.fuzz.t.sol:64:25\n   │\n64 │         uint256 clock = block.timestamp;\n   │                         ━━━━━━━━━━━━━━━\n   ‡\n71 │             vm.warp(clock);\n   │             ────────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:293:25\n    │\n293 │         assertEq(since, block.timestamp);\n    │                         ━━━━━━━━━━━━━━━\n    ‡\n296 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:296:17\n    │\n296 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:415:17\n    │\n415 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":3962,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/unit/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 79833, ~: 80349)\n[PASS] test_metadata() (gas: 24465)\n[PASS] test_mintsFixedSupplyToDeployer() (gas: 23278)\n[PASS] test_noMintOrAdminEntryPoints() (gas: 143123)\n[PASS] test_transferMovesExactAmount() (gas: 74094)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 34859)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 4.59ms (3.71ms CPU time)\n\nRan 19 tests for test/unit/AdamDistributor.t.sol:AdamDistributorTest\n[PASS] testFuzz_claimsNeverExceedDistributed(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 1220423, ~: 1248602)\n[PASS] testFuzz_stakeUnstakeRoundTrip(uint256,uint256) (runs: 256, μ: 219363, ~: 219787)\n[PASS] test_claimIsReentrancySafe() (gas: 1363700)\n[PASS] test_claimWithNothingIsANoop() (gas: 65484)\n[PASS] test_constructorState() (gas: 78111)\n[PASS] test_constructorValidation() (gas: 10329)\n[PASS] test_exitWithdrawsAndClaims() (gas: 421962)\n[PASS] test_extraExclusionIsApplied() (gas: 17863)\n[PASS] test_noOwnerOrAdminEntryPoints() (gas: 117926)\n[PASS] test_notifyRejectsUnknownTokenAndZero() (gas: 1127136)\n[PASS] test_notifyRequiresAllowance() (gas: 50735)\n[PASS] test_rewardsBeforeAnyStakeAreHeldThenFoldedIn() (gas: 371636)\n[PASS] test_rewardsOnlyAccrueWhileStaked() (gas: 617987)\n[PASS] test_rewardsProRataAcrossTwoTokens() (gas: 972593)\n[PASS] test_stakeAndUnstake() (gas: 243777)\n[PASS] test_stakeRejectsZeroAndExcluded() (gas: 204484)\n[PASS] test_tokensSentDirectlyAreNotStakedButRewardsFromBalanceDiff() (gas: 200548)\n[PASS] test_unstakeKeepsAccruedRewardsClaimable() (gas: 490402)\n[PASS] test_unstakeMoreThanStakedReverts() (gas: 192244)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 26.80ms (34.65ms CPU time)\n\nRan 2 tests for test/unit/Integration.t.sol:IntegrationTest\n[PASS] test_excludedAddressesEarnNothing() (gas: 934371)\n[PASS] test_fullLifecycle() (gas: 2037307)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 697.36ms (1.87ms CPU time)\n\nRan 20 tests for test/unit/AdamTreasury.t.sol:AdamTreasuryTest\n[PASS] test_anyoneCanProcess() (gas: 474873)\n[PASS] test_capPerCallLeavesRemainderUnsplit() (gas: 517735)\n[PASS] test_constructorValidation() (gas: 15276)\n[PASS] test_cooldownBetweenCalls() (gas: 851741)\n[PASS] test_distributorPendingRewardsFlowToStakersLater() (gas: 1318457)\n[PASS] test_flushRewardsForwardsStrandedTokens() (gas: 186540)\n[PASS] test_immutablesAndLegs() (gas: 84773)\n[PASS] test_legFailsWhenHookTaxExceedsAssumption_pendingIsKept() (gas: 778679)\n[PASS] test_legFailureEmitsReason() (gas: 445875)\n[PASS] test_legsReceiveEqualEth() (gas: 513916)\n[PASS] test_noWithdrawalPathForAnyone() (gas: 147108)\n[PASS] test_ownSwapImpactAboveSlippageIsRejected() (gas: 433807)\n[PASS] test_processRevertsWhenNothingToDo() (gas: 43969)\n[PASS] test_processSplitsTenNinetyAndBuysBothTokens() (gas: 583269)\n[PASS] test_quoteMinOutMatchesSpotMath() (gas: 64415)\n[PASS] test_rerouteAfterThreeDaysOfFailure() (gas: 961938)\n[PASS] test_rerouteWorksInBothDirections() (gas: 682453)\n[PASS] test_teamPushFailureIsDeferredNotBlocking() (gas: 839749)\n[PASS] test_teamWalletCannotReenterProcess() (gas: 2207507)\n[PASS] test_unlockCallbackIsGuarded() (gas: 63177)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 717.90ms (9.03ms CPU time)\n\nRan 4 tests for test/fuzz/Treasury.fuzz.t.sol:TreasuryFuzzTest\n[PASS] testFuzz_accountingIdentityHolds(uint256,uint16,uint8) (runs: 256, μ: 986895, ~: 878101)\n[PASS] testFuzz_capAndConservation(uint256) (runs: 256, μ: 520893, ~: 520917)\n[PASS] testFuzz_quoteIsLinear(uint256) (runs: 256, μ: 82299, ~: 82322)\n[PASS] testFuzz_splitIsExact(uint256) (runs: 256, μ: 505329, ~: 505420)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 940.69ms (168.57ms CPU time)\n\nRan 4 tests for test/unit/Integration.t.sol:DeployWiringTest\n[PASS] test_contractsAreWiredTogether() (gas: 62158)\n[PASS] test_deployWithExistingToken() (gas: 53569001)\n[PASS] test_launchPoolRejectsMisconfiguration() (gas: 142197)\n[PASS] test_mainnetConfigConstants() (gas: 14465)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 998.97ms (269.78ms CPU time)\n\nRan 22 tests for test/unit/AdamHook.t.sol:AdamHookTest\n[PASS] testFuzz_buyFeeMatchesSchedule(uint256,uint32) (runs: 256, μ: 196623, ~: 196681)\n[PASS] testFuzz_decayIsMonotoneAndBounded(uint32,uint32) (runs: 256, μ: 30780, ~: 30776)\n[PASS] test_addressCarriesExactlyTheRequiredFlags() (gas: 11946)\n[PASS] test_antiSnipeFeeAtLaunchIs20Percent() (gas: 177467)\n[PASS] test_buyExactInput_takes1_5PercentOfEthIn() (gas: 200398)\n[PASS] test_buyExactOutput_feeIsAddedOnTopOfEthIn() (gas: 202810)\n[PASS] test_callbacksRejectNonPoolManager() (gas: 90560)\n[PASS] test_constructorRejectsUnflaggedAddress() (gas: 12526)\n[PASS] test_decaySchedule() (gas: 57312)\n[PASS] test_feeCannotBeRaisedOrKept() (gas: 122115)\n[PASS] test_feeEventsAreEmitted() (gas: 189383)\n[PASS] test_initialState() (gas: 38026)\n[PASS] test_initializeOnlyOnce() (gas: 46245)\n[PASS] test_initializeRejectsWrongPair() (gas: 59387314)\n[PASS] test_initializeRequiresOwner() (gas: 59493715)\n[PASS] test_lowerFee() (gas: 64977)\n[PASS] test_lowerFeeDuringDecayKeepsDecaying() (gas: 54508)\n[PASS] test_lowerFeeToZeroDisablesFee() (gas: 213993)\n[PASS] test_onlyOwnerLowersFee() (gas: 35222)\n[PASS] test_sellExactInput_takes1_5PercentOfEthOut() (gas: 339796)\n[PASS] test_sellExactOutput_feeIsAddedOnTopOfEthOut() (gas: 318992)\n[PASS] test_twoStepOwnershipTransfer() (gas: 144539)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 1.05s (680.20ms CPU time)\n\nRan 1 test for test/invariant/TreasuryInvariant.t.sol:TreasuryInvariantTest\n[PASS]\nTreasuryInvariantTest invariants:\n[PASS] invariant_adamSupplyIsConserved\n[PASS] invariant_distributorCanPayAllHolders\n[PASS] invariant_earmarksNeverExceedBalance\n[PASS] invariant_feeScheduleIsBounded\n[PASS] invariant_holdersShareIsSwappedOrPending\n[PASS] invariant_hookAndTreasuryHoldNoStrayAssets\n[PASS] invariant_legStateIsConsistent\n[PASS] invariant_poolManagerEthMatchesHookAccounting\n[PASS] invariant_rewardTokensAreConserved\n[PASS] invariant_teamShareIsExactlyTenPercent\n[PASS] invariant_treasuryEthIsConserved\n TreasuryInvariantTest invariants (runs: 64, calls: 2560, reverts: 0)\n\n╭-----------------+-------------------+-------+---------+----------╮\n| Contract        | Selector          | Calls | Reverts | Discards |\n+==================================================================+\n| TreasuryHandler | buyExactIn        | 271   | 0       | 0        |\n|-----------------+-------------------+-------+---------+----------|\n| TreasuryHandler | buyExactOut       | 107   | 0       | 0        |\n|-----------------+-------------------+-------+---------+----------|\n| TreasuryHandler | claim             | 126   | 0       | 0        |\n|-----------------+-------------------+-------+---------+----------|\n| TreasuryHandler | donateAndFlush    | 146   | 0       | 0        |\n|-----------------+-------------------+-------+---------+----------|\n| TreasuryHandler | fund              | 122   | 0       | 0        |\n|-----------------+-------------------+-------+---------+----------|\n| TreasuryHandler | lowerFee          | 147   | 0       | 0        |\n|-----------------+-------------------+-------+---------+----------|\n| TreasuryHandler | payTeam           | 110   | 0       | 0        |\n|-----------------+-------------------+-------+---------+----------|\n| TreasuryHandler | process           | 257   | 0       | 0        |\n|-----------------+-------------------+-------+---------+----------|\n| TreasuryHandler | raiseFeeAttempt   | 135   | 0       | 0        |\n|-----------------+-------------------+-------+---------+----------|\n| TreasuryHandler | sellExactIn       | 113   | 0       | 0        |\n|-----------------+-------------------+-------+---------+----------|\n| TreasuryHandler | sellExactOut      | 145   | 0       | 0        |\n|-----------------+-------------------+-------+---------+----------|\n| TreasuryHandler | setPnkstrPoolDead | 139   | 0       | 0        |\n|-----------------+-------------------+-------+---------+----------|\n| TreasuryHandler | setPnkstrTax      | 130   | 0       | 0        |\n|-----------------+-------------------+-------+---------+----------|\n| TreasuryHandler | setTeamAccept     | 129   | 0       | 0        |\n|-----------------+-------------------+-------+---------+----------|\n| TreasuryHandler | stake             | 124   | 0       | 0        |\n|-----------------+-------------------+-------+---------+----------|\n| TreasuryHandler | unstake           | 93    | 0       | 0        |\n|-----------------+-------------------+-------+---------+----------|\n| TreasuryHandler | warp              | 266   | 0       | 0        |\n╰-----------------+-------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.05s (910.62ms CPU time)\n\nRan 38 tests for test/unit/FailurePaths.t.sol:FailurePathsTest\n[PASS] testFuzz_exactOutputBuyFeeMatchesSchedule(uint256,uint32) (runs: 256, μ: 208408, ~: 208486)\n[PASS] testFuzz_sellFeeMatchesSchedule(uint256,uint32) (runs: 256, μ: 355507, ~: 346155)\n[PASS] testFuzz_twoStakersSplitExactlyProRata(uint256,uint256,uint256) (runs: 1000, μ: 640976, ~: 640958)\n[PASS] test_antiSnipeAppliesToSellsAndExactOutputBuysToo() (gas: 466786)\n[PASS] test_backlogIsDrainedAtMostMaxPerLegPerCall() (gas: 1178167)\n[PASS] test_capBoundary_exactCapIsFullyProcessed_oneMoreWaits() (gas: 830341)\n[PASS] test_claimTwicePaysOnce() (gas: 1185355)\n[PASS] test_constructorRejectsEveryBadLimit() (gas: 17714)\n[PASS] test_cooldownBoundary_offByOne() (gas: 853420)\n[PASS] test_decayIsLinearAtEverySecondBoundary() (gas: 51837)\n[PASS] test_feeOnSubUnitAmountsRoundsDownNotUp() (gas: 307416)\n[PASS] test_feeTakenBeforeTheSwapIsRolledBackIfTheSwapReverts() (gas: 109883)\n[PASS] test_flushRewardsWithNothingIsANoop_andIgnoresForeignTokens() (gas: 1068379)\n[PASS] test_gasBurningTeamWalletCannotBlockProcess() (gas: 1040347646)\n[PASS] test_hookCallbacksForOtherHooksRevert() (gas: 52351)\n[PASS] test_hookCannotReceiveEth() (gas: 32953)\n[PASS] test_lateStakerGetsNothingFromEarlierDistributions() (gas: 1270732)\n[PASS] test_maxSlippageParameterIsAccepted_andLimitsHowBadAFillCanBe() (gas: 737419)\n[PASS] test_notifyFromTwoSourcesBothCount() (gas: 1076166)\n[PASS] test_oddWeiGoesToThePnkstrLeg() (gas: 455556)\n[PASS] test_oneWeiStakerAndHugeRewardDoNotOverflow() (gas: 980121)\n[PASS] test_pendingOwnerCannotLowerFeeBeforeAccepting() (gas: 103436)\n[PASS] test_processOneWei_nothingLostTeamRoundsToZero() (gas: 325332)\n[PASS] test_processTwiceInSameBlockIsRejectedEvenWithFreshEth() (gas: 537204)\n[PASS] test_quoteIncludesProtocolFeeAndLegStillBuys() (gas: 728929)\n[PASS] test_quoteRevertsForUninitializedPool() (gas: 49769)\n[PASS] test_renouncedOwnershipFreezesTheFee() (gas: 258918)\n[PASS] test_rerouteDoesNotFireOnTheFirstFailureAfterTheDelay() (gas: 769996)\n[PASS] test_rewardTokenAndAdamAddressesCannotStake() (gas: 106318)\n[PASS] test_stakingAgainDoesNotChangeAlreadyEarnedRewards() (gas: 1470955)\n[PASS] test_strayAdamSentDirectlyIsNotStakeAndNotClaimable() (gas: 553176)\n[PASS] test_successResetsTheFailureClock() (gas: 1219394)\n[PASS] test_thirdPartyUnlockNeverReachesTheTreasuryCallback() (gas: 83639)\n[PASS] test_unlockCallbackWithUnknownLegIsRejected() (gas: 37263)\n[PASS] test_unstakeToExactlyOneWeiAndBack() (gas: 1543130)\n[PASS] test_untrustedPoolHookCannotReenterTreasuryDuringItsSwap() (gas: 43005315)\n[PASS] test_zeroCooldownAllowsBackToBackCalls() (gas: 831233)\n[PASS] test_zeroSlippageToleranceRefusesAnyPriceImpact() (gas: 409394)\nSuite result: ok. 38 passed; 0 failed; 0 skipped; finished in 1.93s (1.55s CPU time)\n\nRan 1 test for test/invariant/DistributorInvariant.t.sol:DistributorInvariantTest\n[PASS]\nDistributorInvariantTest invariants:\n[PASS] invariant_actorsAdamIsConserved\n[PASS] invariant_adamHeldEqualsTotalStaked\n[PASS] invariant_claimsNeverExceedDistribution\n[PASS] invariant_distributorIsSolventWithBoundedDust\n[PASS] invariant_donationsAreNeverCredited\n[PASS] invariant_emptyPoolParksRewards\n[PASS] invariant_excludedNeverStake\n[PASS] invariant_notifiedRewardsAreFullyAccounted\n[PASS] invariant_rewardPerShareIsMonotone\n[PASS] invariant_sumOfStakesEqualsTotalStaked\n DistributorInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭--------------------+-----------------+-------+---------+----------╮\n| Contract           | Selector        | Calls | Reverts | Discards |\n+===================================================================+\n| DistributorHandler | claim           | 1030  | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| DistributorHandler | donate          | 984   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| DistributorHandler | exit            | 968   | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| DistributorHandler | notify          | 2144  | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| DistributorHandler | stake           | 1040  | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| DistributorHandler | stakeAsExcluded | 1031  | 0       | 0        |\n|--------------------+-----------------+-------+---------+----------|\n| DistributorHandler | unstake         | 995   | 0       | 0        |\n╰--------------------+-----------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.81s (3.81s CPU time)\n\nRan 10 test suites in 3.82s (11.23s CPU time): 117 tests passed, 0 failed, 0 skipped (117 total tests)\n","passed":true},{"durationMs":66,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"AdamDistributor.claim()\",\"AdamDistributor.exit()\",\"AdamDistributor.notifyReward(address,uint256)\",\"AdamDistributor.stake(uint256)\",\"AdamDistributor.unstake(uint256)\",\"AdamHook.acceptOwnership()\",\"AdamHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"AdamHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"AdamHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"AdamHook.lowerFee(uint16)\",\"AdamHook.renounceOwnership()\",\"AdamHook.transferOwnership(address)\",\"AdamTreasury.flushRewards()\",\"AdamTreasury.payTeam()\",\"AdamTreasury.process()\",\"AdamTreasury.receive()\",\"AdamTreasury.unlockCallback(bytes)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"foundry.toml\":32,\"remappings.txt\":9,\"script/DeployAdam.s.sol\":265,\"script/utils/HookMiner.sol\":60,\"src/AdamDistributor.sol\":213,\"src/AdamHook.sol\":251,\"src/AdamTreasury.sol\":325,\"src/LaunchToken.sol\":17,\"src/interfaces/IAdamDistributor.sol\":8,\"test/fork/MainnetFork.t.sol\":245,\"test/fuzz/Treasury.fuzz.t.sol\":101,\"test/invariant/DistributorInvariant.t.sol\":335,\"test/invariant/TreasuryInvariant.t.sol\":661,\"test/unit/AdamDistributor.t.sol\":334,\"test/unit/AdamHook.t.sol\":271,\"test/unit/AdamTreasury.t.sol\":495,\"test/unit/FailurePaths.t.sol\":921,\"test/unit/Integration.t.sol\":159,\"test/unit/LaunchToken.t.sol\":64,\"test/utils/LocalV4.sol\":222,\"test/utils/MockTaxHook.sol\":129},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"477f556b3bb8c1e15470fee04b46bddb4f4ef839f58d38719cca7df25cef433b","verifiedTreeHash":"ea8339014c49d33de0481922668f87b0ba87c67c","verifierVersion":"0.1.0+41305fb5"},{"checks":[{"durationMs":76191,"exitCode":0,"name":"build","output":"Compiling 117 files with Solc 0.8.26\nSolc 0.8.26 finished in 75.99s\nCompiler run successful!\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamDistributor.sol:105:9\n    │\n105 │         emit Staked(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:138:77\n    │\n138 │                 || pnkstrTaxBps >= BPS || imd == pnkstr || imdTickSpacing > uint24(type(int24).max)\n    │                                                                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint24' 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/AdamTreasury.sol:139:40\n    │\n139 │                 || pnkstrTickSpacing > uint24(type(int24).max)\n    │                                        ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint24' 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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamHook.sol:109:13\n    │\n109 │         if (elapsed >= DECAY_DURATION) return base;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:153:26\n    │\n153 │             tickSpacing: int24(imdTickSpacing),\n    │                          ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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/AdamTreasury.sol:161:26\n    │\n161 │             tickSpacing: int24(pnkstrTickSpacing),\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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/AdamDistributor.sol:191:13\n    │\n191 │             emit RewardClaimed(account, token, amount);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamHook.sol:151:27\n    │\n151 │         launchTimestamp = uint64(block.timestamp);\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamHook.sol:153:35\n    │\n153 │         emit PoolLaunched(poolId, uint64(block.timestamp));\n    │                                   ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamDistributor.sol:140:9\n    │\n140 │         emit RewardNotified(token, msg.sender, received, distributed);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_legs` is updated\n    ╭▸ src/AdamTreasury.sol:304:13\n    │\n304 │         try poolManager.unlock(abi.encode(legId, ethIn)) returns (bytes memory result) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamHook.sol:167:42\n    │\n167 │             params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamHook.sol:167:77\n    │\n167 │             params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamHook.sol:180:44\n    │\n180 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:180:52\n    │\n180 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:180:74\n    │\n180 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:180:82\n    │\n180 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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-no-eth]: external call can be reentered before `_legs` is updated\n    ╭▸ src/AdamTreasury.sol:310:13\n    │\n310 │             distributor.notifyReward(token, amountOut);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:178:13\n    │\n178 │         if (block.timestamp < availableAt) revert CooldownActive(availableAt);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamTreasury.sol:308:13\n    │\n308 │             emit LegBought(legId, token, ethIn, amountOut);\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/AdamTreasury.sol:312:13\n    │\n312 │             emit LegFailed(legId, ethIn, reason);\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/AdamTreasury.sol:321:17\n    │\n321 │                 emit LegRerouted(legId, other, moved);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:184:25\n    │\n184 │         lastProcessed = uint64(block.timestamp);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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-eth]: uncapped ETH transfer can be reentered before `_status` is updated\n    ╭▸ src/AdamTreasury.sol:207:22\n    │\n207 │         (bool ok,) = teamWallet.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-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/AdamTreasury.sol:209:9\n    │\n209 │         emit TeamPaid(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/AdamTreasury.sol:219:13\n    │\n219 │             distributor.notifyReward(token, balance);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/AdamTreasury.sol:216:31\n    │\n216 │             uint256 balance = IERC20(token).balanceOf(address(this));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/AdamTreasury.sol:219:13\n    │\n219 │             distributor.notifyReward(token, balance);\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/AdamTreasury.sol:220:13\n    │\n220 │             emit RewardsFlushed(token, balance);\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/AdamHook.sol:249:9\n    │\n249 │         emit FeeTaken(isBuy, ethAmount, fee, 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/AdamDistributor.sol:178:9\n    │\n178 │         emit Unstaked(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:239:53\n    │\n239 │                 zeroForOne: true, amountSpecified: -int256(ethIn), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1\n    │                                                     ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' 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/AdamTreasury.sol:245:30\n    │\n245 │         if (ethDelta >= 0 || uint256(uint128(-ethDelta)) != ethIn) revert PartialFill();\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/AdamTreasury.sol:245:38\n    │\n245 │         if (ethDelta >= 0 || uint256(uint128(-ethDelta)) != ethIn) revert PartialFill();\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/AdamTreasury.sol:247:29\n    │\n247 │         uint256 amountOut = uint256(uint128(outDelta));\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/AdamTreasury.sol:247:37\n    │\n247 │         uint256 amountOut = uint256(uint128(outDelta));\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/AdamTreasury.sol:250:9\n    │\n250 │         poolManager.settle{value: ethIn}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/AdamTreasury.sol:280:16\n    │\n280 │         return (afterTax * (BPS - slippageBps)) / BPS;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamTreasury.sol:291:13\n    │\n291 │             emit TeamPaid(amount);\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/AdamTreasury.sol:294:13\n    │\n294 │             emit TeamPaymentDeferred(amount);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[return-bomb]: external call with a gas limit may copy unbounded return data\n    ╭▸ src/AdamTreasury.sol:289:22\n    │\n289 │         (bool ok,) = teamWallet.call{value: amount, gas: TEAM_PUSH_GAS}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:315:24\n    │\n315 │             } else if (block.timestamp >= uint256(l.failingSince) + LEG_FALLBACK_DELAY) {\n    │                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:314:34\n    │\n314 │                 l.failingSince = uint64(block.timestamp);\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/fuzz/Treasury.fuzz.t.sol:64:25\n   │\n64 │         uint256 clock = block.timestamp;\n   │                         ━━━━━━━━━━━━━━━\n   ‡\n71 │             vm.warp(clock);\n   │             ────────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:293:25\n    │\n293 │         assertEq(since, block.timestamp);\n    │                         ━━━━━━━━━━━━━━━\n    ‡\n296 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:296:17\n    │\n296 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:415:17\n    │\n415 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":1366,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/unit/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 80106, ~: 80325)\n[PASS] test_metadata() (gas: 24465)\n[PASS] test_mintsFixedSupplyToDeployer() (gas: 23278)\n[PASS] test_noMintOrAdminEntryPoints() (gas: 143123)\n[PASS] test_transferMovesExactAmount() (gas: 74094)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 34859)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 4.11ms (4.75ms CPU time)\n\nRan 19 tests for test/unit/AdamDistributor.t.sol:AdamDistributorTest\n[PASS] testFuzz_claimsNeverExceedDistributed(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 1218739, ~: 1248495)\n[PASS] testFuzz_stakeUnstakeRoundTrip(uint256,uint256) (runs: 256, μ: 219404, ~: 219778)\n[PASS] test_claimIsReentrancySafe() (gas: 1363700)\n[PASS] test_claimWithNothingIsANoop() (gas: 65484)\n[PASS] test_constructorState() (gas: 78111)\n[PASS] test_constructorValidation() (gas: 10329)\n[PASS] test_exitWithdrawsAndClaims() (gas: 421962)\n[PASS] test_extraExclusionIsApplied() (gas: 17863)\n[PASS] test_noOwnerOrAdminEntryPoints() (gas: 117926)\n[PASS] test_notifyRejectsUnknownTokenAndZero() (gas: 1127136)\n[PASS] test_notifyRequiresAllowance() (gas: 50735)\n[PASS] test_rewardsBeforeAnyStakeAreHeldThenFoldedIn() (gas: 371636)\n[PASS] test_rewardsOnlyAccrueWhileStaked() (gas: 617987)\n[PASS] test_rewardsProRataAcrossTwoTokens() (gas: 972593)\n[PASS] test_stakeAndUnstake() (gas: 243777)\n[PASS] test_stakeRejectsZeroAndExcluded() (gas: 204484)\n[PASS] test_tokensSentDirectlyAreNotStakedButRewardsFromBalanceDiff() (gas: 200548)\n[PASS] test_unstakeKeepsAccruedRewardsClaimable() (gas: 490402)\n[PASS] test_unstakeMoreThanStakedReverts() (gas: 192244)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 40.41ms (53.34ms CPU time)\n\nRan 20 tests for test/unit/AdamTreasury.t.sol:AdamTreasuryTest\n[PASS] test_anyoneCanProcess() (gas: 474873)\n[PASS] test_capPerCallLeavesRemainderUnsplit() (gas: 517735)\n[PASS] test_constructorValidation() (gas: 15276)\n[PASS] test_cooldownBetweenCalls() (gas: 851741)\n[PASS] test_distributorPendingRewardsFlowToStakersLater() (gas: 1318457)\n[PASS] test_flushRewardsForwardsStrandedTokens() (gas: 186540)\n[PASS] test_immutablesAndLegs() (gas: 84773)\n[PASS] test_legFailsWhenHookTaxExceedsAssumption_pendingIsKept() (gas: 778679)\n[PASS] test_legFailureEmitsReason() (gas: 445875)\n[PASS] test_legsReceiveEqualEth() (gas: 513916)\n[PASS] test_noWithdrawalPathForAnyone() (gas: 147108)\n[PASS] test_ownSwapImpactAboveSlippageIsRejected() (gas: 433807)\n[PASS] test_processRevertsWhenNothingToDo() (gas: 43969)\n[PASS] test_processSplitsTenNinetyAndBuysBothTokens() (gas: 583269)\n[PASS] test_quoteMinOutMatchesSpotMath() (gas: 64415)\n[PASS] test_rerouteAfterThreeDaysOfFailure() (gas: 961938)\n[PASS] test_rerouteWorksInBothDirections() (gas: 682453)\n[PASS] test_teamPushFailureIsDeferredNotBlocking() (gas: 839749)\n[PASS] test_teamWalletCannotReenterProcess() (gas: 2207507)\n[PASS] test_unlockCallbackIsGuarded() (gas: 63177)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 798.36ms (13.28ms CPU time)\n\nRan 2 tests for test/unit/Integration.t.sol:IntegrationTest\n[PASS] test_excludedAddressesEarnNothing() (gas: 934371)\n[PASS] test_fullLifecycle() (gas: 2037307)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 833.25ms (1.98ms CPU time)\n\nRan 4 tests for test/fuzz/Treasury.fuzz.t.sol:TreasuryFuzzTest\n[PASS] testFuzz_accountingIdentityHolds(uint256,uint16,uint8) (runs: 256, μ: 1036442, ~: 1000129)\n[PASS] testFuzz_capAndConservation(uint256) (runs: 256, μ: 520896, ~: 520917)\n[PASS] testFuzz_quoteIsLinear(uint256) (runs: 256, μ: 82287, ~: 82322)\n[PASS] testFuzz_splitIsExact(uint256) (runs: 256, μ: 505335, ~: 505420)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 850.77ms (146.61ms CPU time)\n\nRan 4 tests for test/unit/Integration.t.sol:DeployWiringTest\n[PASS] test_contractsAreWiredTogether() (gas: 62158)\n[PASS] test_deployWithExistingToken() (gas: 53569001)\n[PASS] test_launchPoolRejectsMisconfiguration() (gas: 142197)\n[PASS] test_mainnetConfigConstants() (gas: 14465)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.13s (304.40ms CPU time)\n\nRan 22 tests for test/unit/AdamHook.t.sol:AdamHookTest\n[PASS] testFuzz_buyFeeMatchesSchedule(uint256,uint32) (runs: 256, μ: 196640, ~: 196709)\n[PASS] testFuzz_decayIsMonotoneAndBounded(uint32,uint32) (runs: 256, μ: 30789, ~: 30776)\n[PASS] test_addressCarriesExactlyTheRequiredFlags() (gas: 11946)\n[PASS] test_antiSnipeFeeAtLaunchIs20Percent() (gas: 177467)\n[PASS] test_buyExactInput_takes1_5PercentOfEthIn() (gas: 200398)\n[PASS] test_buyExactOutput_feeIsAddedOnTopOfEthIn() (gas: 202810)\n[PASS] test_callbacksRejectNonPoolManager() (gas: 90560)\n[PASS] test_constructorRejectsUnflaggedAddress() (gas: 12526)\n[PASS] test_decaySchedule() (gas: 57312)\n[PASS] test_feeCannotBeRaisedOrKept() (gas: 122115)\n[PASS] test_feeEventsAreEmitted() (gas: 189383)\n[PASS] test_initialState() (gas: 38026)\n[PASS] test_initializeOnlyOnce() (gas: 46245)\n[PASS] test_initializeRejectsWrongPair() (gas: 59387314)\n[PASS] test_initializeRequiresOwner() (gas: 59493715)\n[PASS] test_lowerFee() (gas: 64977)\n[PASS] test_lowerFeeDuringDecayKeepsDecaying() (gas: 54508)\n[PASS] test_lowerFeeToZeroDisablesFee() (gas: 213993)\n[PASS] test_onlyOwnerLowersFee() (gas: 35222)\n[PASS] test_sellExactInput_takes1_5PercentOfEthOut() (gas: 339796)\n[PASS] test_sellExactOutput_feeIsAddedOnTopOfEthOut() (gas: 318992)\n[PASS] test_twoStepOwnershipTransfer() (gas: 144539)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 1.18s (673.44ms CPU time)\n\nRan 7 test suites in 1.18s (4.83s CPU time): 77 tests passed, 0 failed, 0 skipped (77 total tests)\n","passed":true},{"durationMs":92,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"AdamDistributor.claim()\",\"AdamDistributor.exit()\",\"AdamDistributor.notifyReward(address,uint256)\",\"AdamDistributor.stake(uint256)\",\"AdamDistributor.unstake(uint256)\",\"AdamHook.acceptOwnership()\",\"AdamHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"AdamHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"AdamHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"AdamHook.lowerFee(uint16)\",\"AdamHook.renounceOwnership()\",\"AdamHook.transferOwnership(address)\",\"AdamTreasury.flushRewards()\",\"AdamTreasury.payTeam()\",\"AdamTreasury.process()\",\"AdamTreasury.receive()\",\"AdamTreasury.unlockCallback(bytes)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"foundry.toml\":32,\"remappings.txt\":9,\"script/DeployAdam.s.sol\":265,\"script/utils/HookMiner.sol\":60,\"src/AdamDistributor.sol\":213,\"src/AdamHook.sol\":251,\"src/AdamTreasury.sol\":325,\"src/LaunchToken.sol\":17,\"src/interfaces/IAdamDistributor.sol\":8,\"test/fork/MainnetFork.t.sol\":192,\"test/fuzz/Treasury.fuzz.t.sol\":101,\"test/unit/AdamDistributor.t.sol\":334,\"test/unit/AdamHook.t.sol\":271,\"test/unit/AdamTreasury.t.sol\":495,\"test/unit/Integration.t.sol\":159,\"test/unit/LaunchToken.t.sol\":64,\"test/utils/LocalV4.sol\":222,\"test/utils/MockTaxHook.sol\":129},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":3759,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-eth at src/AdamTreasury.sol:176: Reentrancy in AdamTreasury.process() (src/AdamTreasury.sol#176-200):\n[high/medium] reentrancy-eth at src/AdamTreasury.sol:176: Reentrancy in AdamTreasury.process() (src/AdamTreasury.sol#176-200):\n[medium/medium] divide-before-multiply at src/AdamTreasury.sol:270: AdamTreasury.quoteMinOut(uint8,uint256) (src/AdamTreasury.sol#270-281) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/AdamTreasury.sol:213: AdamTreasury.flushRewards() (src/AdamTreasury.sol#213-222) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/AdamTreasury.sol:287: AdamTreasury._pushTeam(uint256) (src/AdamTreasury.sol#287-296) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/AdamTreasury.sol:176: AdamTreasury.process() (src/AdamTreasury.sol#176-200) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/AdamHook.sol:104: AdamHook.currentFeeBps() (src/AdamHook.sol#104-112) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/AdamDistributor.sol:130: AdamDistributor.notifyReward(address,uint256) (src/AdamDistributor.sol#130-141) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/AdamTreasury.sol:298: Reentrancy in AdamTreasury._executeLeg(uint8) (src/AdamTreasury.sol#298-324):\n[medium/medium] unused-return at src/AdamTreasury.sol:229: AdamTreasury.unlockCallback(bytes) (src/AdamTreasury.sol#229-253) ignores return value by poolManager.settle{value: ethIn}() (src/AdamTreasury.sol#250)\n[medium/medium] unused-return at src/AdamTreasury.sol:270: AdamTreasury.quoteMinOut(uint8,uint256) (src/AdamTreasury.sol#270-281) ignores return value by (sqrtPriceX96,None,protocolFee,lpFee) = poolManager.getSlot0(l.key.toId()) (src/AdamTreasury.sol#272)\n[low/medium] calls-loop at src/AdamTreasury.sol:213: AdamTreasury.flushRewards() (src/AdamTreasury.sol#213-222) has external calls inside a loop: distributor.notifyReward(token,balance) (src/AdamTreasury.sol#219)\n[low/medium] calls-loop at src/AdamTreasury.sol:213: AdamTreasury.flushRewards() (src/AdamTreasury.sol#213-222) has external calls inside a loop: balance = IERC20(token).balanceOf(address(this)) (src/AdamTreasury.sol#216)\n[low/medium] reentrancy-benign at src/AdamTreasury.sol:176: Reentrancy in AdamTreasury.process() (src/AdamTreasury.sol#176-200):\n[low/medium] reentrancy-benign at src/AdamTreasury.sol:176: Reentrancy in AdamTreasury.process() (src/AdamTreasury.sol#176-200):\n[low/medium] reentrancy-benign at src/AdamTreasury.sol:287: Reentrancy in AdamTreasury._pushTeam(uint256) (src/AdamTreasury.sol#287-296):\n[low/medium] reentrancy-events at src/AdamHook.sol:245: Reentrancy in AdamHook._takeFee(bool,uint256) (src/AdamHook.sol#245-250):\n[low/medium] return-bomb at src/AdamTreasury.sol:287: AdamTreasury._pushTeam(uint256) (src/AdamTreasury.sol#287-296) tries to limit the gas of an external call that controls implicit decoding\n[low/medium] timestamp at src/AdamTreasury.sol:287: AdamTreasury._pushTeam(uint256) (src/AdamTreasury.sol#287-296) uses timestamp for comparisons\n[low/medium] timestamp at src/AdamHook.sol:245: AdamHook._takeFee(bool,uint256) (src/AdamHook.sol#245-250) uses timestamp for comparisons\n[low/medium] timestamp at src/AdamTreasury.sol:176: AdamTreasury.process() (src/AdamTreasury.sol#176-200) uses timestamp for comparisons\n[low/medium] timestamp at src/AdamHook.sol:147: AdamHook.beforeInitialize(address,PoolKey,uint160) (src/AdamHook.sol#147-155) uses timestamp for comparisons\n[low/medium] timestamp at src/AdamTreasury.sol:298: AdamTreasury._executeLeg(uint8) (src/AdamTreasury.sol#298-324) uses timestamp for comparisons\n[low/medium] timestamp at src/AdamHook.sol:104: AdamHook.currentFeeBps() (src/AdamHook.sol#104-112) uses timestamp for comparisons\n[low/medium] timestamp at src/AdamTreasury.sol:229: AdamTreasury.unlockCallback(bytes) (src/AdamTreasury.sol#229-253) uses timestamp for comparisons","passed":true},{"durationMs":589,"exitCode":0,"name":"aderyn","output":"[high] eth-send-unchecked-address at src/AdamTreasury.sol:176: ETH transferred without address checks (2 places)\n[low] centralization-risk at src/AdamHook.sol:37: Centralization Risk (2 places)\n[low] costly-loop at src/AdamDistributor.sol:185: Costly operations inside loop (2 places)\n[low] large-numeric-literal at src/AdamHook.sol:41: Large Numeric Literal (3 places)\n[low] require-revert-in-loop at src/AdamDistributor.sol:185: Loop Contains `require`/`revert` (3 places)\n[low] state-variable-could-be-immutable at src/AdamDistributor.sol:33: State Variable Could Be Immutable\n[low] uninitialized-local-variable at src/AdamDistributor.sol:185: Uninitialized Local Variable (3 places)\n[low] unused-public-function at src/AdamDistributor.sol:152: Public Function Not Used Internally\n[low] unused-state-variable at src/AdamTreasury.sol:50: Unused State Variable","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"5b9e1fb9e8a151d291efeb716b69ef1a8734dcd8a64f88f0c924dcfd15bd8e03","verifiedTreeHash":"0d6cebb40bd523a4c5d86c1f4b26f0dcc32ce2ad","verifierVersion":"0.1.0+41305fb5"},{"checks":[{"durationMs":113148,"exitCode":0,"name":"build","output":"Compiling 119 files with Solc 0.8.26\nSolc 0.8.26 finished in 112.91s\nCompiler run successful!\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:148:97\n    │\n148 │                 || imdTaxBps >= BPS || pnkstrTaxBps >= BPS || imd == pnkstr || imdTickSpacing > uint24(type(int24).max)\n    │                                                                                                 ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint24' 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/AdamDistributor.sol:116:9\n    │\n116 │         emit Staked(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:149:40\n    │\n149 │                 || pnkstrTickSpacing > uint24(type(int24).max)\n    │                                        ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint24' 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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamHook.sol:114:13\n    │\n114 │         if (elapsed >= DECAY_DURATION) return base;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:163:26\n    │\n163 │             tickSpacing: int24(imdTickSpacing),\n    │                          ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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/AdamTreasury.sol:171:26\n    │\n171 │             tickSpacing: int24(pnkstrTickSpacing),\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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/AdamHook.sol:168:31\n    │\n168 │             launchTimestamp = uint64(block.timestamp);\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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-no-eth]: external call can be reentered before `_legs` is updated\n    ╭▸ src/AdamTreasury.sol:328:13\n    │\n328 │         try poolManager.unlock(abi.encode(legId, ethIn)) returns (bytes memory result) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamHook.sol:176:42\n    │\n176 │             params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamDistributor.sol:204:13\n    │\n204 │             emit RewardClaimed(account, token, amount);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamHook.sol:176:77\n    │\n176 │             params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamDistributor.sol:152:9\n    │\n152 │         emit RewardNotified(token, msg.sender, received, distributed);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamHook.sol:191:46\n    │\n191 │                 params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamHook.sol:191:81\n    │\n191 │                 params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamHook.sol:194:47\n    │\n194 │                 params.amountSpecified < 0 ? -int256(requested - prepaidFee) : int256(requested + prepaidFee);\n    │                                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' 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/AdamHook.sol:194:80\n    │\n194 │                 params.amountSpecified < 0 ? -int256(requested - prepaidFee) : int256(requested + prepaidFee);\n    │                                                                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' 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/AdamHook.sol:199:44\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:199:52\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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-no-eth]: external call can be reentered before `_legs` is updated\n    ╭▸ src/AdamTreasury.sol:335:13\n    │\n335 │             distributor.notifyReward(token, amountOut);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamHook.sol:199:74\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:199:82\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:195:13\n    │\n195 │         if (block.timestamp < availableAt) revert CooldownActive(availableAt);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamTreasury.sol:333:13\n    │\n333 │             emit LegBought(legId, token, ethIn, amountOut);\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/AdamTreasury.sol:337:13\n    │\n337 │             emit LegFailed(legId, ethIn, reason);\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/AdamTreasury.sol:351:17\n    │\n351 │                 emit LegRerouted(legId, other, moved);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:201:25\n    │\n201 │         lastProcessed = uint64(block.timestamp);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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-eth]: uncapped ETH transfer can be reentered before `_status` is updated\n    ╭▸ src/AdamTreasury.sol:224:22\n    │\n224 │         (bool ok,) = teamWallet.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-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/AdamTreasury.sol:226:9\n    │\n226 │         emit TeamPaid(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/AdamTreasury.sol:236:13\n    │\n236 │             distributor.notifyReward(token, balance);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/AdamTreasury.sol:233:31\n    │\n233 │             uint256 balance = IERC20(token).balanceOf(address(this));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/AdamTreasury.sol:236:13\n    │\n236 │             distributor.notifyReward(token, balance);\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/AdamTreasury.sol:237:13\n    │\n237 │             emit RewardsFlushed(token, balance);\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/AdamDistributor.sol:191:9\n    │\n191 │         emit Unstaked(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamDistributor.sol:233:13\n    │\n233 │         if (elapsed > BACKLOG_DURATION) elapsed = BACKLOG_DURATION;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamDistributor.sol:255:77\n    │\n255 │                 backlogStream[token] = BacklogStream(unallocated[token], 0, uint64(block.timestamp));\n    │                                                                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamTreasury.sol:256:53\n    │\n256 │                 zeroForOne: true, amountSpecified: -int256(ethIn), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1\n    │                                                     ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' 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/AdamTreasury.sol:262:30\n    │\n262 │         if (ethDelta >= 0 || uint256(uint128(-ethDelta)) != ethIn) revert PartialFill();\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/AdamTreasury.sol:262:38\n    │\n262 │         if (ethDelta >= 0 || uint256(uint128(-ethDelta)) != ethIn) revert PartialFill();\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/AdamTreasury.sol:264:29\n    │\n264 │         uint256 amountOut = uint256(uint128(outDelta));\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/AdamTreasury.sol:264:37\n    │\n264 │         uint256 amountOut = uint256(uint128(outDelta));\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/AdamTreasury.sol:267:9\n    │\n267 │         poolManager.settle{value: ethIn}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/AdamTreasury.sol:298:16\n    │\n298 │         return (afterTax * (BPS - slippageBps)) / BPS;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamHook.sol:273:9\n    │\n273 │         emit FeeTaken(params.zeroForOne, ethAmount, fee, 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/AdamTreasury.sol:309:13\n    │\n309 │             emit TeamPaid(amount);\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/AdamTreasury.sol:312:13\n    │\n312 │             emit TeamPaymentDeferred(amount);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[return-bomb]: external call with a gas limit may copy unbounded return data\n    ╭▸ src/AdamTreasury.sol:307:22\n    │\n307 │         (bool ok,) = teamWallet.call{value: amount, gas: TEAM_PUSH_GAS}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:319:32\n    │\n319 │         if (l.failures != 0 && block.timestamp > uint256(l.lastFailure) + allowedGap) _resetFailures(l);\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:345:47\n    │\n345 │             if (l.failures >= MIN_FAILURES && block.timestamp >= uint256(l.failingSince) + LEG_FALLBACK_DELAY) {\n    │                                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:339:34\n    │\n339 │                 l.failingSince = uint64(block.timestamp);\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamTreasury.sol:341:29\n    │\n341 │             l.lastFailure = uint64(block.timestamp);\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/fuzz/Treasury.fuzz.t.sol:64:25\n   │\n64 │         uint256 clock = block.timestamp;\n   │                         ━━━━━━━━━━━━━━━\n   ‡\n71 │             vm.warp(clock);\n   │             ────────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamDistributor.t.sol:369:25\n    │\n369 │         uint256 start = block.timestamp;\n    │                         ━━━━━━━━━━━━━━━\n370 │         assertEq(dist.earned(alice, address(imd)), 0, \"no instant jackpot at threshold\");\n371 │         vm.warp(start + 1 days);\n    │         ─────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamDistributor.t.sol:393:25\n    │\n393 │         uint256 start = block.timestamp;\n    │                         ━━━━━━━━━━━━━━━\n394 │         vm.warp(start + 1 days);\n    │         ─────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:243:34\n    │\n243 │         assertEq(l.failingSince, block.timestamp);\n    │                                  ━━━━━━━━━━━━━━━\n    ‡\n248 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ─────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:248:17\n    │\n248 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:417:17\n    │\n417 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:489:17\n    │\n489 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/unit/Revision.t.sol:32:17\n   │\n32 │         vm.warp(block.timestamp + COOLDOWN);\n   │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/Revision.t.sol:107:17\n    │\n107 │         vm.warp(block.timestamp + 7 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":1924,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/unit/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 80026, ~: 80337)\n[PASS] test_metadata() (gas: 24465)\n[PASS] test_mintsFixedSupplyToDeployer() (gas: 23278)\n[PASS] test_noMintOrAdminEntryPoints() (gas: 143123)\n[PASS] test_transferMovesExactAmount() (gas: 74094)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 34859)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 3.50ms (4.23ms CPU time)\n\nRan 24 tests for test/unit/AdamDistributor.t.sol:AdamDistributorTest\n[PASS] testFuzz_backlogConservation(uint128,uint32) (runs: 256, μ: 582463, ~: 605002)\n[PASS] testFuzz_claimsNeverExceedDistributed(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 1312286, ~: 1338742)\n[PASS] testFuzz_stakeUnstakeRoundTrip(uint256,uint256) (runs: 256, μ: 250631, ~: 251364)\n[PASS] test_backlogPausesBelowThresholdWithoutRetroactiveVesting() (gas: 907369)\n[PASS] test_backlogStreamsAndNewcomerCannotClaimPastTime() (gas: 1496660)\n[PASS] test_claimIsReentrancySafe() (gas: 1391380)\n[PASS] test_claimWithNothingIsANoop() (gas: 70106)\n[PASS] test_constructorState() (gas: 78331)\n[PASS] test_constructorValidation() (gas: 10329)\n[PASS] test_dustCannotCaptureBacklogEvenAfterWaiting() (gas: 792687)\n[PASS] test_exitAfterUnstakeStillClaims() (gas: 557146)\n[PASS] test_exitWithdrawsAndClaims() (gas: 452396)\n[PASS] test_extraExclusionIsApplied() (gas: 17907)\n[PASS] test_noOwnerOrAdminEntryPoints() (gas: 118344)\n[PASS] test_notifyRejectsUnknownTokenAndZero() (gas: 1127290)\n[PASS] test_notifyRequiresAllowance() (gas: 50801)\n[PASS] test_rewardsBeforeAnyStakeWaitForMeaningfulStake() (gas: 400286)\n[PASS] test_rewardsOnlyAccrueWhileStaked() (gas: 669298)\n[PASS] test_rewardsProRataAcrossTwoTokens() (gas: 1040157)\n[PASS] test_stakeAndUnstake() (gas: 275331)\n[PASS] test_stakeRejectsZeroAndExcluded() (gas: 204572)\n[PASS] test_tokensSentDirectlyAreNotStakedButRewardsFromBalanceDiff() (gas: 220431)\n[PASS] test_unstakeKeepsAccruedRewardsClaimable() (gas: 526684)\n[PASS] test_unstakeMoreThanStakedReverts() (gas: 207505)\nSuite result: ok. 24 passed; 0 failed; 0 skipped; finished in 48.81ms (94.84ms CPU time)\n\nRan 2 tests for test/unit/Integration.t.sol:IntegrationTest\n[PASS] test_excludedAddressesEarnNothing() (gas: 1111838)\n[PASS] test_fullLifecycle() (gas: 2254150)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 186.39ms (2.99ms CPU time)\n\nRan 20 tests for test/unit/AdamTreasury.t.sol:AdamTreasuryTest\n[PASS] test_anyoneCanProcess() (gas: 493995)\n[PASS] test_capPerCallLeavesRemainderUnsplit() (gas: 546448)\n[PASS] test_constructorValidation() (gas: 15276)\n[PASS] test_cooldownBetweenCalls() (gas: 890008)\n[PASS] test_distributorPendingRewardsFlowToStakersLater() (gas: 1668526)\n[PASS] test_flushRewardsForwardsStrandedTokens() (gas: 188424)\n[PASS] test_immutablesAndLegs() (gas: 94419)\n[PASS] test_legFailsWhenHookTaxExceedsAssumption_pendingIsKept() (gas: 1057483)\n[PASS] test_legFailureEmitsReason() (gas: 483403)\n[PASS] test_legsReceiveEqualEth() (gas: 533038)\n[PASS] test_noWithdrawalPathForAnyone() (gas: 147438)\n[PASS] test_ownSwapImpactAboveSlippageIsRejected() (gas: 494486)\n[PASS] test_processRevertsWhenNothingToDo() (gas: 43991)\n[PASS] test_processSplitsTenNinetyAndBuysBothTokens() (gas: 616811)\n[PASS] test_quoteMinOutMatchesSpotMath() (gas: 69243)\n[PASS] test_rerouteAfterThreeDaysOfFailure() (gas: 10501328)\n[PASS] test_rerouteWorksInBothDirections() (gas: 5707518)\n[PASS] test_teamPushFailureIsDeferredNotBlocking() (gas: 858871)\n[PASS] test_teamWalletCannotReenterProcess() (gas: 2264891)\n[PASS] test_unlockCallbackIsGuarded() (gas: 63177)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 217.64ms (24.93ms CPU time)\n\nRan 4 tests for test/unit/PostFactoryHook.t.sol:PostFactoryHookTest\n[PASS] test_feesFromHookedPoolReachManifestedDistributor() (gas: 1322900)\n[PASS] test_hookOnlyDeploymentReusesManifestedPipeline() (gas: 65960)\n[PASS] test_launchPoolRejectsAllocationAboveHolding() (gas: 89595)\n[PASS] test_resolvePipelineRejectsMismatchedConfig() (gas: 250817)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 232.35ms (1.66ms CPU time)\n\nRan 22 tests for test/unit/AdamHook.t.sol:AdamHookTest\n[PASS] testFuzz_buyFeeMatchesSchedule(uint256,uint32) (runs: 256, μ: 331215, ~: 331455)\n[PASS] testFuzz_decayIsMonotoneAndBounded(uint32,uint32) (runs: 256, μ: 196489, ~: 196467)\n[PASS] test_addressCarriesExactlyTheRequiredFlags() (gas: 11990)\n[PASS] test_antiSnipeFeeAtLaunchIs20Percent() (gas: 185840)\n[PASS] test_buyExactInput_takes1_5PercentOfEthIn() (gas: 344650)\n[PASS] test_buyExactOutput_feeIsAddedOnTopOfEthIn() (gas: 346566)\n[PASS] test_callbacksRejectNonPoolManager() (gas: 90662)\n[PASS] test_constructorRejectsUnflaggedAddress() (gas: 12526)\n[PASS] test_decaySchedule() (gas: 223238)\n[PASS] test_feeCannotBeRaisedOrKept() (gas: 122115)\n[PASS] test_feeEventsAreEmitted() (gas: 333635)\n[PASS] test_initialState() (gas: 43167)\n[PASS] test_initializeOnlyOnce() (gas: 46279)\n[PASS] test_initializeRejectsWrongPair() (gas: 6491850)\n[PASS] test_initializeRequiresOwner() (gas: 6602669)\n[PASS] test_lowerFee() (gas: 240682)\n[PASS] test_lowerFeeDuringDecayKeepsDecaying() (gas: 220132)\n[PASS] test_lowerFeeToZeroDisablesFee() (gas: 358245)\n[PASS] test_onlyOwnerLowersFee() (gas: 35222)\n[PASS] test_sellExactInput_takes1_5PercentOfEthOut() (gas: 487228)\n[PASS] test_sellExactOutput_feeIsAddedOnTopOfEthOut() (gas: 467067)\n[PASS] test_twoStepOwnershipTransfer() (gas: 144671)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 271.00ms (245.38ms CPU time)\n\nRan 4 tests for test/unit/Integration.t.sol:DeployWiringTest\n[PASS] test_contractsAreWiredTogether() (gas: 62290)\n[PASS] test_deployWithExistingToken() (gas: 460115604)\n[PASS] test_launchPoolRejectsMisconfiguration() (gas: 145489)\n[PASS] test_mainnetConfigConstants() (gas: 14612)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.66s (1.44s CPU time)\n\nRan 4 tests for test/fuzz/Treasury.fuzz.t.sol:TreasuryFuzzTest\n[PASS] testFuzz_accountingIdentityHolds(uint256,uint16,uint8) (runs: 256, μ: 1100728, ~: 1063080)\n[PASS] testFuzz_capAndConservation(uint256) (runs: 256, μ: 549605, ~: 549630)\n[PASS] testFuzz_quoteIsLinear(uint256) (runs: 256, μ: 91954, ~: 91987)\n[PASS] testFuzz_splitIsExact(uint256) (runs: 256, μ: 524437, ~: 524542)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.66s (193.49ms CPU time)\n\nRan 11 tests for test/unit/Revision.t.sol:RevisionTest\n[PASS] testFuzz_exactOutputFeesAreGrossRate(uint32,uint128) (runs: 256, μ: 339360, ~: 339529)\n[PASS] test_constructorCheckpointAlsoProtectsFirstProcess() (gas: 641507)\n[PASS] test_dustWaitsWithoutStartingFailureStreak() (gas: 735279)\n[PASS] test_emptySellCannotChargeFeeOrStartClock() (gas: 372591)\n[PASS] test_exactOutputAndInputBuySameGrossFeeAtLaunch() (gas: 371741)\n[PASS] test_firstFilledSwapStartsFullDecayAfterDeploymentDelay() (gas: 223443)\n[PASS] test_hookAndTreasuryHaveNoStakingEntryPoint() (gas: 69673)\n[PASS] test_partialInputBuyRevertsAndReturnsEntireFee() (gas: 456031)\n[PASS] test_sandwichCannotLowerCheckpointFloor() (gas: 1640145)\n[PASS] test_thinPoolRecoversAtSmallerSize() (gas: 2347683)\n[PASS] test_twoIsolatedFailuresDoNotReroute() (gas: 959835)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 1.66s (50.51ms CPU time)\n\nRan 9 test suites in 1.66s (5.94s CPU time): 97 tests passed, 0 failed, 0 skipped (97 total tests)\n","passed":true},{"durationMs":137,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"AdamDistributor.claim()\",\"AdamDistributor.exit()\",\"AdamDistributor.notifyReward(address,uint256)\",\"AdamDistributor.stake(uint256)\",\"AdamDistributor.unstake(uint256)\",\"AdamHook.acceptOwnership()\",\"AdamHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"AdamHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"AdamHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"AdamHook.lowerFee(uint16)\",\"AdamHook.renounceOwnership()\",\"AdamHook.transferOwnership(address)\",\"AdamTreasury.flushRewards()\",\"AdamTreasury.payTeam()\",\"AdamTreasury.process()\",\"AdamTreasury.receive()\",\"AdamTreasury.unlockCallback(bytes)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":83,\"SELF_AUDIT.md\":31,\"foundry.toml\":32,\"remappings.txt\":9,\"script/DeployAdam.s.sol\":308,\"script/utils/HookMiner.sol\":60,\"src/AdamDistributor.sol\":259,\"src/AdamHook.sol\":275,\"src/AdamTreasury.sol\":362,\"src/LaunchToken.sol\":17,\"src/interfaces/IAdamDistributor.sol\":8,\"test/fork/MainnetFork.t.sol\":234,\"test/fuzz/Treasury.fuzz.t.sol\":101,\"test/unit/AdamDistributor.t.sol\":423,\"test/unit/AdamHook.t.sol\":278,\"test/unit/AdamTreasury.t.sol\":500,\"test/unit/Integration.t.sol\":159,\"test/unit/LaunchToken.t.sol\":64,\"test/unit/PostFactoryHook.t.sol\":151,\"test/unit/Revision.t.sol\":197,\"test/utils/LocalV4.sol\":223,\"test/utils/MockTaxHook.sol\":129},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"6e6e4fa7150d4ba1f6e78a08391dbec1d641cc5515d2243bda2291fc0c402fac","verifiedTreeHash":"7551b6b020384f7c35fae46e26cada5872641189","verifierVersion":"0.1.0+41305fb5"},{"checks":[{"durationMs":96567,"exitCode":0,"name":"build","output":"Compiling 118 files with Solc 0.8.26\nSolc 0.8.26 finished in 96.32s\nCompiler run successful!\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamDistributor.sol:116:9\n    │\n116 │         emit Staked(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:148:97\n    │\n148 │                 || imdTaxBps >= BPS || pnkstrTaxBps >= BPS || imd == pnkstr || imdTickSpacing > uint24(type(int24).max)\n    │                                                                                                 ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint24' 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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamHook.sol:114:13\n    │\n114 │         if (elapsed >= DECAY_DURATION) return base;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:149:40\n    │\n149 │                 || pnkstrTickSpacing > uint24(type(int24).max)\n    │                                        ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint24' 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/AdamTreasury.sol:163:26\n    │\n163 │             tickSpacing: int24(imdTickSpacing),\n    │                          ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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/AdamTreasury.sol:171:26\n    │\n171 │             tickSpacing: int24(pnkstrTickSpacing),\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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/AdamHook.sol:168:31\n    │\n168 │             launchTimestamp = uint64(block.timestamp);\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamHook.sol:176:42\n    │\n176 │             params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamHook.sol:176:77\n    │\n176 │             params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamDistributor.sol:204:13\n    │\n204 │             emit RewardClaimed(account, token, amount);\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/AdamDistributor.sol:152:9\n    │\n152 │         emit RewardNotified(token, msg.sender, received, distributed);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamHook.sol:191:46\n    │\n191 │                 params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamHook.sol:191:81\n    │\n191 │                 params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamHook.sol:194:47\n    │\n194 │                 params.amountSpecified < 0 ? -int256(requested - prepaidFee) : int256(requested + prepaidFee);\n    │                                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' 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/AdamHook.sol:194:80\n    │\n194 │                 params.amountSpecified < 0 ? -int256(requested - prepaidFee) : int256(requested + prepaidFee);\n    │                                                                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' 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/AdamHook.sol:199:44\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:199:52\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:199:74\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:199:82\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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-no-eth]: external call can be reentered before `_legs` is updated\n    ╭▸ src/AdamTreasury.sol:328:13\n    │\n328 │         try poolManager.unlock(abi.encode(legId, ethIn)) returns (bytes memory result) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_legs` is updated\n    ╭▸ src/AdamTreasury.sol:335:13\n    │\n335 │             distributor.notifyReward(token, amountOut);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:195:13\n    │\n195 │         if (block.timestamp < availableAt) revert CooldownActive(availableAt);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamTreasury.sol:333:13\n    │\n333 │             emit LegBought(legId, token, ethIn, amountOut);\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/AdamTreasury.sol:337:13\n    │\n337 │             emit LegFailed(legId, ethIn, reason);\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/AdamTreasury.sol:351:17\n    │\n351 │                 emit LegRerouted(legId, other, moved);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:201:25\n    │\n201 │         lastProcessed = uint64(block.timestamp);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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-eth]: uncapped ETH transfer can be reentered before `_status` is updated\n    ╭▸ src/AdamTreasury.sol:224:22\n    │\n224 │         (bool ok,) = teamWallet.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-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/AdamTreasury.sol:226:9\n    │\n226 │         emit TeamPaid(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/AdamTreasury.sol:236:13\n    │\n236 │             distributor.notifyReward(token, balance);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/AdamTreasury.sol:233:31\n    │\n233 │             uint256 balance = IERC20(token).balanceOf(address(this));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/AdamTreasury.sol:236:13\n    │\n236 │             distributor.notifyReward(token, balance);\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/AdamTreasury.sol:237:13\n    │\n237 │             emit RewardsFlushed(token, balance);\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/AdamHook.sol:273:9\n    │\n273 │         emit FeeTaken(params.zeroForOne, ethAmount, fee, bps);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:256:53\n    │\n256 │                 zeroForOne: true, amountSpecified: -int256(ethIn), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1\n    │                                                     ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' 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/AdamTreasury.sol:262:30\n    │\n262 │         if (ethDelta >= 0 || uint256(uint128(-ethDelta)) != ethIn) revert PartialFill();\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/AdamTreasury.sol:262:38\n    │\n262 │         if (ethDelta >= 0 || uint256(uint128(-ethDelta)) != ethIn) revert PartialFill();\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/AdamTreasury.sol:264:29\n    │\n264 │         uint256 amountOut = uint256(uint128(outDelta));\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/AdamTreasury.sol:264:37\n    │\n264 │         uint256 amountOut = uint256(uint128(outDelta));\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/AdamTreasury.sol:267:9\n    │\n267 │         poolManager.settle{value: ethIn}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/AdamTreasury.sol:298:16\n    │\n298 │         return (afterTax * (BPS - slippageBps)) / BPS;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamDistributor.sol:191:9\n    │\n191 │         emit Unstaked(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamDistributor.sol:233:13\n    │\n233 │         if (elapsed > BACKLOG_DURATION) elapsed = BACKLOG_DURATION;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamDistributor.sol:255:77\n    │\n255 │                 backlogStream[token] = BacklogStream(unallocated[token], 0, uint64(block.timestamp));\n    │                                                                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamTreasury.sol:309:13\n    │\n309 │             emit TeamPaid(amount);\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/AdamTreasury.sol:312:13\n    │\n312 │             emit TeamPaymentDeferred(amount);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[return-bomb]: external call with a gas limit may copy unbounded return data\n    ╭▸ src/AdamTreasury.sol:307:22\n    │\n307 │         (bool ok,) = teamWallet.call{value: amount, gas: TEAM_PUSH_GAS}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:319:32\n    │\n319 │         if (l.failures != 0 && block.timestamp > uint256(l.lastFailure) + allowedGap) _resetFailures(l);\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:345:47\n    │\n345 │             if (l.failures >= MIN_FAILURES && block.timestamp >= uint256(l.failingSince) + LEG_FALLBACK_DELAY) {\n    │                                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:339:34\n    │\n339 │                 l.failingSince = uint64(block.timestamp);\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamTreasury.sol:341:29\n    │\n341 │             l.lastFailure = uint64(block.timestamp);\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/fuzz/Treasury.fuzz.t.sol:64:25\n   │\n64 │         uint256 clock = block.timestamp;\n   │                         ━━━━━━━━━━━━━━━\n   ‡\n71 │             vm.warp(clock);\n   │             ────────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamDistributor.t.sol:369:25\n    │\n369 │         uint256 start = block.timestamp;\n    │                         ━━━━━━━━━━━━━━━\n370 │         assertEq(dist.earned(alice, address(imd)), 0, \"no instant jackpot at threshold\");\n371 │         vm.warp(start + 1 days);\n    │         ─────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamDistributor.t.sol:393:25\n    │\n393 │         uint256 start = block.timestamp;\n    │                         ━━━━━━━━━━━━━━━\n394 │         vm.warp(start + 1 days);\n    │         ─────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:243:34\n    │\n243 │         assertEq(l.failingSince, block.timestamp);\n    │                                  ━━━━━━━━━━━━━━━\n    ‡\n248 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ─────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:248:17\n    │\n248 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:417:17\n    │\n417 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:489:17\n    │\n489 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/unit/Revision.t.sol:32:17\n   │\n32 │         vm.warp(block.timestamp + COOLDOWN);\n   │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/Revision.t.sol:107:17\n    │\n107 │         vm.warp(block.timestamp + 7 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":2080,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/unit/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 79740, ~: 80337)\n[PASS] test_metadata() (gas: 24465)\n[PASS] test_mintsFixedSupplyToDeployer() (gas: 23278)\n[PASS] test_noMintOrAdminEntryPoints() (gas: 143123)\n[PASS] test_transferMovesExactAmount() (gas: 74094)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 34859)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 9.80ms (11.86ms CPU time)\n\nRan 24 tests for test/unit/AdamDistributor.t.sol:AdamDistributorTest\n[PASS] testFuzz_backlogConservation(uint128,uint32) (runs: 256, μ: 579433, ~: 604990)\n[PASS] testFuzz_claimsNeverExceedDistributed(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 1313870, ~: 1338764)\n[PASS] testFuzz_stakeUnstakeRoundTrip(uint256,uint256) (runs: 256, μ: 250813, ~: 251354)\n[PASS] test_backlogPausesBelowThresholdWithoutRetroactiveVesting() (gas: 907369)\n[PASS] test_backlogStreamsAndNewcomerCannotClaimPastTime() (gas: 1496660)\n[PASS] test_claimIsReentrancySafe() (gas: 1391380)\n[PASS] test_claimWithNothingIsANoop() (gas: 70106)\n[PASS] test_constructorState() (gas: 78331)\n[PASS] test_constructorValidation() (gas: 10329)\n[PASS] test_dustCannotCaptureBacklogEvenAfterWaiting() (gas: 792687)\n[PASS] test_exitAfterUnstakeStillClaims() (gas: 557146)\n[PASS] test_exitWithdrawsAndClaims() (gas: 452396)\n[PASS] test_extraExclusionIsApplied() (gas: 17907)\n[PASS] test_noOwnerOrAdminEntryPoints() (gas: 118344)\n[PASS] test_notifyRejectsUnknownTokenAndZero() (gas: 1127290)\n[PASS] test_notifyRequiresAllowance() (gas: 50801)\n[PASS] test_rewardsBeforeAnyStakeWaitForMeaningfulStake() (gas: 400286)\n[PASS] test_rewardsOnlyAccrueWhileStaked() (gas: 669298)\n[PASS] test_rewardsProRataAcrossTwoTokens() (gas: 1040157)\n[PASS] test_stakeAndUnstake() (gas: 275331)\n[PASS] test_stakeRejectsZeroAndExcluded() (gas: 204572)\n[PASS] test_tokensSentDirectlyAreNotStakedButRewardsFromBalanceDiff() (gas: 220431)\n[PASS] test_unstakeKeepsAccruedRewardsClaimable() (gas: 526684)\n[PASS] test_unstakeMoreThanStakedReverts() (gas: 207505)\nSuite result: ok. 24 passed; 0 failed; 0 skipped; finished in 48.83ms (113.66ms CPU time)\n\nRan 2 tests for test/unit/Integration.t.sol:IntegrationTest\n[PASS] test_excludedAddressesEarnNothing() (gas: 1111844)\n[PASS] test_fullLifecycle() (gas: 2254186)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 234.49ms (4.90ms CPU time)\n\nRan 11 tests for test/unit/Revision.t.sol:RevisionTest\n[PASS] testFuzz_exactOutputFeesAreGrossRate(uint32,uint128) (runs: 256, μ: 339360, ~: 339529)\n[PASS] test_constructorCheckpointAlsoProtectsFirstProcess() (gas: 641507)\n[PASS] test_dustWaitsWithoutStartingFailureStreak() (gas: 735279)\n[PASS] test_emptySellCannotChargeFeeOrStartClock() (gas: 372591)\n[PASS] test_exactOutputAndInputBuySameGrossFeeAtLaunch() (gas: 371741)\n[PASS] test_firstFilledSwapStartsFullDecayAfterDeploymentDelay() (gas: 223443)\n[PASS] test_hookAndTreasuryHaveNoStakingEntryPoint() (gas: 69673)\n[PASS] test_partialInputBuyRevertsAndReturnsEntireFee() (gas: 456031)\n[PASS] test_sandwichCannotLowerCheckpointFloor() (gas: 1640145)\n[PASS] test_thinPoolRecoversAtSmallerSize() (gas: 2347683)\n[PASS] test_twoIsolatedFailuresDoNotReroute() (gas: 959835)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 282.12ms (84.33ms CPU time)\n\nRan 20 tests for test/unit/AdamTreasury.t.sol:AdamTreasuryTest\n[PASS] test_anyoneCanProcess() (gas: 493995)\n[PASS] test_capPerCallLeavesRemainderUnsplit() (gas: 546448)\n[PASS] test_constructorValidation() (gas: 15276)\n[PASS] test_cooldownBetweenCalls() (gas: 890008)\n[PASS] test_distributorPendingRewardsFlowToStakersLater() (gas: 1668532)\n[PASS] test_flushRewardsForwardsStrandedTokens() (gas: 188424)\n[PASS] test_immutablesAndLegs() (gas: 94419)\n[PASS] test_legFailsWhenHookTaxExceedsAssumption_pendingIsKept() (gas: 1057483)\n[PASS] test_legFailureEmitsReason() (gas: 483403)\n[PASS] test_legsReceiveEqualEth() (gas: 533038)\n[PASS] test_noWithdrawalPathForAnyone() (gas: 147438)\n[PASS] test_ownSwapImpactAboveSlippageIsRejected() (gas: 494486)\n[PASS] test_processRevertsWhenNothingToDo() (gas: 43991)\n[PASS] test_processSplitsTenNinetyAndBuysBothTokens() (gas: 616811)\n[PASS] test_quoteMinOutMatchesSpotMath() (gas: 69243)\n[PASS] test_rerouteAfterThreeDaysOfFailure() (gas: 10501328)\n[PASS] test_rerouteWorksInBothDirections() (gas: 5707518)\n[PASS] test_teamPushFailureIsDeferredNotBlocking() (gas: 858871)\n[PASS] test_teamWalletCannotReenterProcess() (gas: 2264891)\n[PASS] test_unlockCallbackIsGuarded() (gas: 63177)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 291.20ms (37.50ms CPU time)\n\nRan 22 tests for test/unit/AdamHook.t.sol:AdamHookTest\n[PASS] testFuzz_buyFeeMatchesSchedule(uint256,uint32) (runs: 256, μ: 331241, ~: 331478)\n[PASS] testFuzz_decayIsMonotoneAndBounded(uint32,uint32) (runs: 256, μ: 196468, ~: 196467)\n[PASS] test_addressCarriesExactlyTheRequiredFlags() (gas: 11968)\n[PASS] test_antiSnipeFeeAtLaunchIs20Percent() (gas: 185840)\n[PASS] test_buyExactInput_takes1_5PercentOfEthIn() (gas: 344650)\n[PASS] test_buyExactOutput_feeIsAddedOnTopOfEthIn() (gas: 346566)\n[PASS] test_callbacksRejectNonPoolManager() (gas: 90662)\n[PASS] test_constructorRejectsUnflaggedAddress() (gas: 12526)\n[PASS] test_decaySchedule() (gas: 223238)\n[PASS] test_feeCannotBeRaisedOrKept() (gas: 122115)\n[PASS] test_feeEventsAreEmitted() (gas: 333635)\n[PASS] test_initialState() (gas: 43167)\n[PASS] test_initializeOnlyOnce() (gas: 46279)\n[PASS] test_initializeRejectsWrongPair() (gas: 6491828)\n[PASS] test_initializeRequiresOwner() (gas: 6602647)\n[PASS] test_lowerFee() (gas: 240682)\n[PASS] test_lowerFeeDuringDecayKeepsDecaying() (gas: 220132)\n[PASS] test_lowerFeeToZeroDisablesFee() (gas: 358245)\n[PASS] test_onlyOwnerLowersFee() (gas: 35222)\n[PASS] test_sellExactInput_takes1_5PercentOfEthOut() (gas: 487228)\n[PASS] test_sellExactOutput_feeIsAddedOnTopOfEthOut() (gas: 467067)\n[PASS] test_twoStepOwnershipTransfer() (gas: 144671)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 294.14ms (221.63ms CPU time)\n\nRan 4 tests for test/fuzz/Treasury.fuzz.t.sol:TreasuryFuzzTest\n[PASS] testFuzz_accountingIdentityHolds(uint256,uint16,uint8) (runs: 256, μ: 1066650, ~: 1027338)\n[PASS] testFuzz_capAndConservation(uint256) (runs: 256, μ: 549596, ~: 549630)\n[PASS] testFuzz_quoteIsLinear(uint256) (runs: 256, μ: 91952, ~: 91978)\n[PASS] testFuzz_splitIsExact(uint256) (runs: 256, μ: 524410, ~: 524542)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 310.79ms (169.56ms CPU time)\n\nRan 4 tests for test/unit/Integration.t.sol:DeployWiringTest\n[PASS] test_contractsAreWiredTogether() (gas: 62268)\n[PASS] test_deployWithExistingToken() (gas: 460111966)\n[PASS] test_launchPoolRejectsMisconfiguration() (gas: 142197)\n[PASS] test_mainnetConfigConstants() (gas: 14465)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.86s (1.63s CPU time)\n\nRan 8 test suites in 1.86s (3.33s CPU time): 93 tests passed, 0 failed, 0 skipped (93 total tests)\n","passed":true},{"durationMs":134,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"AdamDistributor.claim()\",\"AdamDistributor.exit()\",\"AdamDistributor.notifyReward(address,uint256)\",\"AdamDistributor.stake(uint256)\",\"AdamDistributor.unstake(uint256)\",\"AdamHook.acceptOwnership()\",\"AdamHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"AdamHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"AdamHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"AdamHook.lowerFee(uint16)\",\"AdamHook.renounceOwnership()\",\"AdamHook.transferOwnership(address)\",\"AdamTreasury.flushRewards()\",\"AdamTreasury.payTeam()\",\"AdamTreasury.process()\",\"AdamTreasury.receive()\",\"AdamTreasury.unlockCallback(bytes)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":74,\"SELF_AUDIT.md\":28,\"foundry.toml\":32,\"remappings.txt\":9,\"script/DeployAdam.s.sol\":265,\"script/utils/HookMiner.sol\":60,\"src/AdamDistributor.sol\":259,\"src/AdamHook.sol\":275,\"src/AdamTreasury.sol\":362,\"src/LaunchToken.sol\":17,\"src/interfaces/IAdamDistributor.sol\":8,\"test/fork/MainnetFork.t.sol\":234,\"test/fuzz/Treasury.fuzz.t.sol\":101,\"test/unit/AdamDistributor.t.sol\":423,\"test/unit/AdamHook.t.sol\":278,\"test/unit/AdamTreasury.t.sol\":500,\"test/unit/Integration.t.sol\":159,\"test/unit/LaunchToken.t.sol\":64,\"test/unit/Revision.t.sol\":197,\"test/utils/LocalV4.sol\":223,\"test/utils/MockTaxHook.sol\":129},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":4727,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-eth at src/AdamTreasury.sol:193: Reentrancy in AdamTreasury.process() (src/AdamTreasury.sol#193-217):\n[high/medium] reentrancy-eth at src/AdamTreasury.sol:193: Reentrancy in AdamTreasury.process() (src/AdamTreasury.sol#193-217):\n[medium/medium] divide-before-multiply at src/AdamTreasury.sol:287: AdamTreasury.quoteMinOut(uint8,uint256) (src/AdamTreasury.sol#287-299) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/AdamDistributor.sol:249: AdamDistributor._syncBacklogStreams() (src/AdamDistributor.sol#249-258) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/AdamTreasury.sol:230: AdamTreasury.flushRewards() (src/AdamTreasury.sol#230-239) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/AdamDistributor.sol:142: AdamDistributor.notifyReward(address,uint256) (src/AdamDistributor.sol#142-153) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/AdamDistributor.sol:238: AdamDistributor._releaseBacklog(address) (src/AdamDistributor.sol#238-245) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/AdamTreasury.sol:305: AdamTreasury._pushTeam(uint256) (src/AdamTreasury.sol#305-314) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/AdamHook.sol:109: AdamHook.currentFeeBps() (src/AdamHook.sol#109-117) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/AdamTreasury.sol:193: AdamTreasury.process() (src/AdamTreasury.sol#193-217) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/AdamTreasury.sol:316: Reentrancy in AdamTreasury._executeLeg(uint8) (src/AdamTreasury.sol#316-354):\n[medium/medium] unused-return at src/AdamTreasury.sol:287: AdamTreasury.quoteMinOut(uint8,uint256) (src/AdamTreasury.sol#287-299) ignores return value by (sqrtPriceX96,None,protocolFee,lpFee) = poolManager.getSlot0(l.key.toId()) (src/AdamTreasury.sol#289)\n[medium/medium] unused-return at src/AdamTreasury.sol:124: AdamTreasury.constructor(address,address,address,address,uint24,uint24,address,uint16,address,uint24,uint24,address,uint16,uint256,uint16,uint32) (src/AdamTreasury.sol#124-182) ignores return value by (_legs[i].checkpointSqrtPriceX96,None,None,None) = poolManager.getSlot0(_legs[i].key.toId()) (src/AdamTreasury.sol#179)\n[medium/medium] unused-return at src/AdamTreasury.sol:246: AdamTreasury.unlockCallback(bytes) (src/AdamTreasury.sol#246-270) ignores return value by poolManager.settle{value: ethIn}() (src/AdamTreasury.sol#267)\n[medium/medium] unused-return at src/AdamTreasury.sol:316: AdamTreasury._executeLeg(uint8) (src/AdamTreasury.sol#316-354) ignores return value by (l.checkpointSqrtPriceX96,None,None,None) = poolManager.getSlot0(l.key.toId()) (src/AdamTreasury.sol#332)\n[low/medium] calls-loop at src/AdamTreasury.sol:230: AdamTreasury.flushRewards() (src/AdamTreasury.sol#230-239) has external calls inside a loop: distributor.notifyReward(token,balance) (src/AdamTreasury.sol#236)\n[low/medium] calls-loop at src/AdamTreasury.sol:230: AdamTreasury.flushRewards() (src/AdamTreasury.sol#230-239) has external calls inside a loop: balance = IERC20(token).balanceOf(address(this)) (src/AdamTreasury.sol#233)\n[low/medium] reentrancy-benign at src/AdamTreasury.sol:305: Reentrancy in AdamTreasury._pushTeam(uint256) (src/AdamTreasury.sol#305-314):\n[low/medium] reentrancy-benign at src/AdamTreasury.sol:193: Reentrancy in AdamTreasury.process() (src/AdamTreasury.sol#193-217):\n[low/medium] reentrancy-benign at src/AdamTreasury.sol:193: Reentrancy in AdamTreasury.process() (src/AdamTreasury.sol#193-217):\n[low/medium] reentrancy-events at src/AdamHook.sol:269: Reentrancy in AdamHook._takeFee(SwapParams,uint256) (src/AdamHook.sol#269-274):\n[low/medium] return-bomb at src/AdamTreasury.sol:305: AdamTreasury._pushTeam(uint256) (src/AdamTreasury.sol#305-314) tries to limit the gas of an external call that controls implicit decoding\n[low/medium] timestamp at src/AdamTreasury.sol:316: AdamTreasury._executeLeg(uint8) (src/AdamTreasury.sol#316-354) uses timestamp for comparisons\n[low/medium] timestamp at src/AdamHook.sol:269: AdamHook._takeFee(SwapParams,uint256) (src/AdamHook.sol#269-274) uses timestamp for comparisons\n[low/medium] timestamp at src/AdamHook.sol:109: AdamHook.currentFeeBps() (src/AdamHook.sol#109-117) uses timestamp for comparisons\n[low/medium] timestamp at src/AdamDistributor.sol:238: AdamDistributor._releaseBacklog(address) (src/AdamDistributor.sol#238-245) uses timestamp for comparisons\n[low/medium] timestamp at src/AdamHook.sol:162: AdamHook.beforeSwap(address,PoolKey,SwapParams,bytes) (src/AdamHook.sol#162-179) uses timestamp for comparisons\n[low/medium] timestamp at src/AdamHook.sol:181: AdamHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/AdamHook.sol#181-202) uses timestamp for comparisons\n[low/medium] timestamp at src/AdamDistributor.sol:229: AdamDistributor._backlogDue(address) (src/AdamDistributor.sol#229-235) uses timestamp for comparisons\n[low/medium] timestamp at src/AdamDistributor.sol:249: AdamDistributor._syncBacklogStreams() (src/AdamDistributor.sol#249-258) uses timestamp for comparisons\n[low/medium] timestamp at src/AdamTreasury.sol:193: AdamTreasury.process() (src/AdamTreasury.sol#193-217) uses timestamp for comparisons\n[low/medium] timestamp at src/AdamDistributor.sol:210: AdamDistributor._settle(address) (src/AdamDistributor.sol#210-219) uses timestamp for comparisons","passed":true},{"durationMs":750,"exitCode":0,"name":"aderyn","output":"[high] eth-send-unchecked-address at src/AdamTreasury.sol:193: ETH transferred without address checks (2 places)\n[low] centralization-risk at src/AdamHook.sol:38: Centralization Risk (2 places)\n[low] costly-loop at src/AdamDistributor.sol:198: Costly operations inside loop (3 places)\n[low] large-numeric-literal at src/AdamHook.sol:42: Large Numeric Literal (3 places)\n[low] require-revert-in-loop at src/AdamDistributor.sol:198: Loop Contains `require`/`revert` (3 places)\n[low] state-variable-could-be-immutable at src/AdamDistributor.sol:36: State Variable Could Be Immutable\n[low] storage-array-length-not-cached at src/AdamDistributor.sol:250: Storage Array Length not Cached\n[low] uninitialized-local-variable at src/AdamDistributor.sol:198: Uninitialized Local Variable (4 places)\n[low] unused-public-function at src/AdamDistributor.sol:164: Public Function Not Used Internally\n[low] unused-state-variable at src/AdamTreasury.sol:55: Unused State Variable","passed":true},{"durationMs":12352,"exitCode":0,"name":"proof 13f1a21b95fc","output":"Compiling 111 files with Solc 0.8.26\nSolc 0.8.26 finished in 11.53s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-dd93a65a/Proof_13f1a21b95fc.t.sol:UnallocatedSweepTest\n[PASS] test_dustStakerSweepsRewardsAccruedWhileNobodyWasStaked() (gas: 1383792)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.39ms (513.68µs CPU time)\n\nRan 1 test suite in 5.50ms (1.39ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true},{"durationMs":12132,"exitCode":0,"name":"proof 4f15f22a207e","output":"2026-10-05T14:09:52.957267Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-0lk0HN/repo/test/imd-proof-dd93a65a/Proof_13f1a21b95fc.t.sol\": No such file or directory (os error 2)\nCompiling 1 files with Solc 0.8.26\nSolc 0.8.26 finished in 11.36s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-7022ab3f/Proof_4f15f22a207e.t.sol:ProofTreasurySlippageFloorTest\n[PASS] test_sandwichedProcessDoesNotFillFarBelowTheUnmanipulatedPrice() (gas: 641306)\nLogs:\n  pre-pump floor for 1 ETH (IMD wei): 208821411098003554484\n  IMD received by the distributor: 0\n  received as % of pre-pump floor: 0\n  attacker ETH delta (wei): -342591941182768838\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.67ms (758.60µs CPU time)\n\nRan 1 test suite in 6.69ms (3.67ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true},{"durationMs":13194,"exitCode":0,"name":"proof 5ab1717199ce","output":"2026-10-05T14:10:05.182860Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-0lk0HN/repo/test/imd-proof-7022ab3f/Proof_4f15f22a207e.t.sol\": No such file or directory (os error 2)\nCompiling 1 files with Solc 0.8.26\nSolc 0.8.26 finished in 12.32s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-3592af81/Proof_5ab1717199ce.t.sol:ProofHookPartialFillFeeTest\n[PASS] test_exactInputBuyWithPriceLimitIsChargedOnlyOnWhatFilled() (gas: 350881)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 46.05ms (474.69µs CPU time)\n\nRan 1 test suite in 46.93ms (46.05ms 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":"72f40544efdce3ea61a2bd644d00de6769ab660bc6d2d8a0e5ccd606b0d9202a","verifiedTreeHash":"92e0687a0676c22b14f5bc6327588a276f181630","verifierVersion":"0.1.0+41305fb5"},{"checks":[{"durationMs":127346,"exitCode":0,"name":"build","output":"Compiling 124 files with Solc 0.8.26\nSolc 0.8.26 finished in 127.17s\nCompiler run successful!\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:148:97\n    │\n148 │                 || imdTaxBps >= BPS || pnkstrTaxBps >= BPS || imd == pnkstr || imdTickSpacing > uint24(type(int24).max)\n    │                                                                                                 ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint24' 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/AdamTreasury.sol:149:40\n    │\n149 │                 || pnkstrTickSpacing > uint24(type(int24).max)\n    │                                        ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint24' 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/AdamTreasury.sol:163:26\n    │\n163 │             tickSpacing: int24(imdTickSpacing),\n    │                          ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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/AdamTreasury.sol:171:26\n    │\n171 │             tickSpacing: int24(pnkstrTickSpacing),\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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/AdamDistributor.sol:116:9\n    │\n116 │         emit Staked(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_legs` is updated\n    ╭▸ src/AdamTreasury.sol:328:13\n    │\n328 │         try poolManager.unlock(abi.encode(legId, ethIn)) returns (bytes memory result) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-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/AdamDistributor.sol:204:13\n    │\n204 │             emit RewardClaimed(account, token, amount);\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/AdamDistributor.sol:152:9\n    │\n152 │         emit RewardNotified(token, msg.sender, received, distributed);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_legs` is updated\n    ╭▸ src/AdamTreasury.sol:335:13\n    │\n335 │             distributor.notifyReward(token, amountOut);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:195:13\n    │\n195 │         if (block.timestamp < availableAt) revert CooldownActive(availableAt);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamTreasury.sol:333:13\n    │\n333 │             emit LegBought(legId, token, ethIn, amountOut);\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/AdamTreasury.sol:337:13\n    │\n337 │             emit LegFailed(legId, ethIn, reason);\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/AdamTreasury.sol:351:17\n    │\n351 │                 emit LegRerouted(legId, other, moved);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:201:25\n    │\n201 │         lastProcessed = uint64(block.timestamp);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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-eth]: uncapped ETH transfer can be reentered before `_status` is updated\n    ╭▸ src/AdamTreasury.sol:224:22\n    │\n224 │         (bool ok,) = teamWallet.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-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/AdamTreasury.sol:226:9\n    │\n226 │         emit TeamPaid(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/AdamTreasury.sol:236:13\n    │\n236 │             distributor.notifyReward(token, balance);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/AdamTreasury.sol:233:31\n    │\n233 │             uint256 balance = IERC20(token).balanceOf(address(this));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/AdamTreasury.sol:236:13\n    │\n236 │             distributor.notifyReward(token, balance);\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/AdamTreasury.sol:237:13\n    │\n237 │             emit RewardsFlushed(token, balance);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:256:53\n    │\n256 │                 zeroForOne: true, amountSpecified: -int256(ethIn), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1\n    │                                                     ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' 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/AdamTreasury.sol:262:30\n    │\n262 │         if (ethDelta >= 0 || uint256(uint128(-ethDelta)) != ethIn) revert PartialFill();\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/AdamTreasury.sol:262:38\n    │\n262 │         if (ethDelta >= 0 || uint256(uint128(-ethDelta)) != ethIn) revert PartialFill();\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/AdamTreasury.sol:264:29\n    │\n264 │         uint256 amountOut = uint256(uint128(outDelta));\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/AdamTreasury.sol:264:37\n    │\n264 │         uint256 amountOut = uint256(uint128(outDelta));\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/AdamTreasury.sol:267:9\n    │\n267 │         poolManager.settle{value: ethIn}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/AdamTreasury.sol:298:16\n    │\n298 │         return (afterTax * (BPS - slippageBps)) / BPS;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamDistributor.sol:191:9\n    │\n191 │         emit Unstaked(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamHook.sol:114:13\n    │\n114 │         if (elapsed >= DECAY_DURATION) return base;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamDistributor.sol:233:13\n    │\n233 │         if (elapsed > BACKLOG_DURATION) elapsed = BACKLOG_DURATION;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamDistributor.sol:255:77\n    │\n255 │                 backlogStream[token] = BacklogStream(unallocated[token], 0, uint64(block.timestamp));\n    │                                                                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamHook.sol:168:31\n    │\n168 │             launchTimestamp = uint64(block.timestamp);\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamHook.sol:176:42\n    │\n176 │             params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamHook.sol:176:77\n    │\n176 │             params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamTreasury.sol:309:13\n    │\n309 │             emit TeamPaid(amount);\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/AdamTreasury.sol:312:13\n    │\n312 │             emit TeamPaymentDeferred(amount);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[return-bomb]: external call with a gas limit may copy unbounded return data\n    ╭▸ src/AdamTreasury.sol:307:22\n    │\n307 │         (bool ok,) = teamWallet.call{value: amount, gas: TEAM_PUSH_GAS}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamHook.sol:191:46\n    │\n191 │                 params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamHook.sol:191:81\n    │\n191 │                 params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamHook.sol:194:47\n    │\n194 │                 params.amountSpecified < 0 ? -int256(requested - prepaidFee) : int256(requested + prepaidFee);\n    │                                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' 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/AdamHook.sol:194:80\n    │\n194 │                 params.amountSpecified < 0 ? -int256(requested - prepaidFee) : int256(requested + prepaidFee);\n    │                                                                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' 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/AdamHook.sol:199:44\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:199:52\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:199:74\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:199:82\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:319:32\n    │\n319 │         if (l.failures != 0 && block.timestamp > uint256(l.lastFailure) + allowedGap) _resetFailures(l);\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:345:47\n    │\n345 │             if (l.failures >= MIN_FAILURES && block.timestamp >= uint256(l.failingSince) + LEG_FALLBACK_DELAY) {\n    │                                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:339:34\n    │\n339 │                 l.failingSince = uint64(block.timestamp);\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamTreasury.sol:341:29\n    │\n341 │             l.lastFailure = uint64(block.timestamp);\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamHook.sol:273:9\n    │\n273 │         emit FeeTaken(params.zeroForOne, ethAmount, fee, bps);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/fuzz/Treasury.fuzz.t.sol:64:25\n   │\n64 │         uint256 clock = block.timestamp;\n   │                         ━━━━━━━━━━━━━━━\n   ‡\n71 │             vm.warp(clock);\n   │             ────────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamDistributor.t.sol:369:25\n    │\n369 │         uint256 start = block.timestamp;\n    │                         ━━━━━━━━━━━━━━━\n370 │         assertEq(dist.earned(alice, address(imd)), 0, \"no instant jackpot at threshold\");\n371 │         vm.warp(start + 1 days);\n    │         ─────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamDistributor.t.sol:393:25\n    │\n393 │         uint256 start = block.timestamp;\n    │                         ━━━━━━━━━━━━━━━\n394 │         vm.warp(start + 1 days);\n    │         ─────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:243:34\n    │\n243 │         assertEq(l.failingSince, block.timestamp);\n    │                                  ━━━━━━━━━━━━━━━\n    ‡\n248 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ─────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:248:17\n    │\n248 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:417:17\n    │\n417 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:489:17\n    │\n489 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/unit/Revision.t.sol:32:17\n   │\n32 │         vm.warp(block.timestamp + COOLDOWN);\n   │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/Revision.t.sol:107:17\n    │\n107 │         vm.warp(block.timestamp + 7 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":5236,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/unit/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 80058, ~: 80319)\n[PASS] test_metadata() (gas: 24465)\n[PASS] test_mintsFixedSupplyToDeployer() (gas: 23278)\n[PASS] test_noMintOrAdminEntryPoints() (gas: 143123)\n[PASS] test_transferMovesExactAmount() (gas: 74094)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 34859)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 5.34ms (5.98ms CPU time)\n\nRan 24 tests for test/unit/AdamDistributor.t.sol:AdamDistributorTest\n[PASS] testFuzz_backlogConservation(uint128,uint32) (runs: 256, μ: 586083, ~: 605008)\n[PASS] testFuzz_claimsNeverExceedDistributed(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 1312205, ~: 1338764)\n[PASS] testFuzz_stakeUnstakeRoundTrip(uint256,uint256) (runs: 256, μ: 250771, ~: 251354)\n[PASS] test_backlogPausesBelowThresholdWithoutRetroactiveVesting() (gas: 907369)\n[PASS] test_backlogStreamsAndNewcomerCannotClaimPastTime() (gas: 1496660)\n[PASS] test_claimIsReentrancySafe() (gas: 1391380)\n[PASS] test_claimWithNothingIsANoop() (gas: 70106)\n[PASS] test_constructorState() (gas: 78331)\n[PASS] test_constructorValidation() (gas: 10329)\n[PASS] test_dustCannotCaptureBacklogEvenAfterWaiting() (gas: 792687)\n[PASS] test_exitAfterUnstakeStillClaims() (gas: 557146)\n[PASS] test_exitWithdrawsAndClaims() (gas: 452396)\n[PASS] test_extraExclusionIsApplied() (gas: 17907)\n[PASS] test_noOwnerOrAdminEntryPoints() (gas: 118344)\n[PASS] test_notifyRejectsUnknownTokenAndZero() (gas: 1127290)\n[PASS] test_notifyRequiresAllowance() (gas: 50801)\n[PASS] test_rewardsBeforeAnyStakeWaitForMeaningfulStake() (gas: 400286)\n[PASS] test_rewardsOnlyAccrueWhileStaked() (gas: 669298)\n[PASS] test_rewardsProRataAcrossTwoTokens() (gas: 1040157)\n[PASS] test_stakeAndUnstake() (gas: 275331)\n[PASS] test_stakeRejectsZeroAndExcluded() (gas: 204572)\n[PASS] test_tokensSentDirectlyAreNotStakedButRewardsFromBalanceDiff() (gas: 220431)\n[PASS] test_unstakeKeepsAccruedRewardsClaimable() (gas: 526684)\n[PASS] test_unstakeMoreThanStakedReverts() (gas: 207505)\nSuite result: ok. 24 passed; 0 failed; 0 skipped; finished in 64.20ms (162.21ms CPU time)\n\nRan 12 tests for test/unit/DistributorAdversarial.t.sol:DistributorAdversarialTest\n[PASS] testFuzz_taxedRewardCreditsOnlyReceivedTokens(uint256,uint16) (runs: 1000, μ: 497246, ~: 499731)\n[PASS] testFuzz_twoEpochsFollowStakeOwnership(uint256,uint256,uint256,uint256) (runs: 1000, μ: 839912, ~: 846810)\n[PASS] test_allFixedExclusionsRejectStakeBeforeTokenInteraction() (gas: 418004)\n[PASS] test_claimCallbackCannotReenterAnyHolderOrRewardAction() (gas: 499308)\n[PASS] test_failedSecondPayoutRollsBackFirstPayoutAndCanRecover() (gas: 1014441)\n[PASS] test_failedStakeRollsBackBacklogAndShares() (gas: 554541)\n[PASS] test_falseReturningIntakeCannotCreateCredit() (gas: 244782)\n[PASS] test_fullSupplyCanStakeAndExit() (gas: 572641)\n[PASS] test_noReturnTokenCanNotifyAndClaim() (gas: 455102)\n[PASS] test_notifyCallbackCannotReenterAnyHolderOrRewardAction() (gas: 346692)\n[PASS] test_oneWeiAndCompleteTransferTax() (gas: 520743)\n[PASS] test_pausedRewardCannotTrapUnstakedPrincipal() (gas: 607688)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 67.13ms (132.92ms CPU time)\n\nRan 4 tests for test/unit/PostFactoryHook.t.sol:PostFactoryHookTest\n[PASS] test_feesFromHookedPoolReachManifestedDistributor() (gas: 1322900)\n[PASS] test_hookOnlyDeploymentReusesManifestedPipeline() (gas: 65960)\n[PASS] test_launchPoolRejectsAllocationAboveHolding() (gas: 89595)\n[PASS] test_resolvePipelineRejectsMismatchedConfig() (gas: 250817)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 186.38ms (1.14ms CPU time)\n\nRan 2 tests for test/unit/Integration.t.sol:IntegrationTest\n[PASS] test_excludedAddressesEarnNothing() (gas: 1111838)\n[PASS] test_fullLifecycle() (gas: 2254150)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 192.31ms (2.99ms CPU time)\n\nRan 11 tests for test/unit/Revision.t.sol:RevisionTest\n[PASS] testFuzz_exactOutputFeesAreGrossRate(uint32,uint128) (runs: 256, μ: 339377, ~: 339529)\n[PASS] test_constructorCheckpointAlsoProtectsFirstProcess() (gas: 641507)\n[PASS] test_dustWaitsWithoutStartingFailureStreak() (gas: 735279)\n[PASS] test_emptySellCannotChargeFeeOrStartClock() (gas: 372591)\n[PASS] test_exactOutputAndInputBuySameGrossFeeAtLaunch() (gas: 371741)\n[PASS] test_firstFilledSwapStartsFullDecayAfterDeploymentDelay() (gas: 223443)\n[PASS] test_hookAndTreasuryHaveNoStakingEntryPoint() (gas: 69673)\n[PASS] test_partialInputBuyRevertsAndReturnsEntireFee() (gas: 456031)\n[PASS] test_sandwichCannotLowerCheckpointFloor() (gas: 1640145)\n[PASS] test_thinPoolRecoversAtSmallerSize() (gas: 2347683)\n[PASS] test_twoIsolatedFailuresDoNotReroute() (gas: 959835)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 263.38ms (110.38ms CPU time)\n\nRan 20 tests for test/unit/AdamTreasury.t.sol:AdamTreasuryTest\n[PASS] test_anyoneCanProcess() (gas: 493995)\n[PASS] test_capPerCallLeavesRemainderUnsplit() (gas: 546448)\n[PASS] test_constructorValidation() (gas: 15276)\n[PASS] test_cooldownBetweenCalls() (gas: 890008)\n[PASS] test_distributorPendingRewardsFlowToStakersLater() (gas: 1668526)\n[PASS] test_flushRewardsForwardsStrandedTokens() (gas: 188424)\n[PASS] test_immutablesAndLegs() (gas: 94419)\n[PASS] test_legFailsWhenHookTaxExceedsAssumption_pendingIsKept() (gas: 1057483)\n[PASS] test_legFailureEmitsReason() (gas: 483403)\n[PASS] test_legsReceiveEqualEth() (gas: 533038)\n[PASS] test_noWithdrawalPathForAnyone() (gas: 147438)\n[PASS] test_ownSwapImpactAboveSlippageIsRejected() (gas: 494486)\n[PASS] test_processRevertsWhenNothingToDo() (gas: 43991)\n[PASS] test_processSplitsTenNinetyAndBuysBothTokens() (gas: 616811)\n[PASS] test_quoteMinOutMatchesSpotMath() (gas: 69243)\n[PASS] test_rerouteAfterThreeDaysOfFailure() (gas: 10501328)\n[PASS] test_rerouteWorksInBothDirections() (gas: 5707518)\n[PASS] test_teamPushFailureIsDeferredNotBlocking() (gas: 858871)\n[PASS] test_teamWalletCannotReenterProcess() (gas: 2264891)\n[PASS] test_unlockCallbackIsGuarded() (gas: 63177)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 270.02ms (16.46ms CPU time)\n\nRan 22 tests for test/unit/AdamHook.t.sol:AdamHookTest\n[PASS] testFuzz_buyFeeMatchesSchedule(uint256,uint32) (runs: 256, μ: 331187, ~: 331448)\n[PASS] testFuzz_decayIsMonotoneAndBounded(uint32,uint32) (runs: 256, μ: 196467, ~: 196457)\n[PASS] test_addressCarriesExactlyTheRequiredFlags() (gas: 11990)\n[PASS] test_antiSnipeFeeAtLaunchIs20Percent() (gas: 185840)\n[PASS] test_buyExactInput_takes1_5PercentOfEthIn() (gas: 344650)\n[PASS] test_buyExactOutput_feeIsAddedOnTopOfEthIn() (gas: 346566)\n[PASS] test_callbacksRejectNonPoolManager() (gas: 90662)\n[PASS] test_constructorRejectsUnflaggedAddress() (gas: 12526)\n[PASS] test_decaySchedule() (gas: 223238)\n[PASS] test_feeCannotBeRaisedOrKept() (gas: 122115)\n[PASS] test_feeEventsAreEmitted() (gas: 333635)\n[PASS] test_initialState() (gas: 43167)\n[PASS] test_initializeOnlyOnce() (gas: 46279)\n[PASS] test_initializeRejectsWrongPair() (gas: 6491850)\n[PASS] test_initializeRequiresOwner() (gas: 6602669)\n[PASS] test_lowerFee() (gas: 240682)\n[PASS] test_lowerFeeDuringDecayKeepsDecaying() (gas: 220132)\n[PASS] test_lowerFeeToZeroDisablesFee() (gas: 358245)\n[PASS] test_onlyOwnerLowersFee() (gas: 35222)\n[PASS] test_sellExactInput_takes1_5PercentOfEthOut() (gas: 487228)\n[PASS] test_sellExactOutput_feeIsAddedOnTopOfEthOut() (gas: 467067)\n[PASS] test_twoStepOwnershipTransfer() (gas: 144671)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 277.75ms (308.53ms CPU time)\n\nRan 4 tests for test/unit/HookAdversarial.t.sol:HookAdversarialTest\n[PASS] testFuzz_exactInputSellChargesGrossEthAtAnyDecayTime(uint256,uint32,uint16) (runs: 1000, μ: 385893, ~: 386370)\n[PASS] testFuzz_exactOutputSellDeliversRequestedNetAndGrossFee(uint256,uint32) (runs: 1000, μ: 351483, ~: 351835)\n[PASS] test_decayBoundariesAndNoFeeResurrection() (gas: 526002)\n[PASS] test_partialExactOutputSellRevertsFeeAndPoolMovement() (gas: 480909)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 277.85ms (218.63ms CPU time)\n\nRan 4 tests for test/fuzz/Treasury.fuzz.t.sol:TreasuryFuzzTest\n[PASS] testFuzz_accountingIdentityHolds(uint256,uint16,uint8) (runs: 256, μ: 1146487, ~: 1063290)\n[PASS] testFuzz_capAndConservation(uint256) (runs: 256, μ: 549606, ~: 549630)\n[PASS] testFuzz_quoteIsLinear(uint256) (runs: 256, μ: 91947, ~: 91978)\n[PASS] testFuzz_splitIsExact(uint256) (runs: 256, μ: 524439, ~: 524542)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 287.70ms (285.61ms CPU time)\n\nRan 4 tests for test/unit/TreasuryBoundaries.t.sol:TreasuryBoundariesTest\n[PASS] testFuzz_directionalProtocolFeesPreserveExecutableOutputFloors(uint16,uint16) (runs: 1000, μ: 845364, ~: 845377)\n[PASS] test_cooldownRejectsOneSecondEarlyAndAcceptsExactBoundary() (gas: 996842)\n[PASS] test_deferredAndDirectTeamPaymentsGuardEveryMaintenanceEntryPoint() (gas: 1543795)\n[PASS] test_failureGapContinuesAtBoundaryAndResetsOneSecondLater() (gas: 1146904)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 287.76ms (114.92ms CPU time)\n\nRan 4 tests for test/unit/Integration.t.sol:DeployWiringTest\n[PASS] test_contractsAreWiredTogether() (gas: 62290)\n[PASS] test_deployWithExistingToken() (gas: 460115604)\n[PASS] test_launchPoolRejectsMisconfiguration() (gas: 145489)\n[PASS] test_mainnetConfigConstants() (gas: 14612)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.54s (1.34s CPU time)\n\nRan 1 test for test/invariant/Treasury.invariant.t.sol:TreasuryInvariantTest\n[PASS]\nTreasuryInvariantTest invariants:\n[PASS] invariant_ethIsConservedAcrossIndependentCustodians\n[PASS] invariant_rewardsReachDistributorWithoutUnbackedCredit\n TreasuryInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭-----------------+------------------+-------+---------+----------╮\n| Contract        | Selector         | Calls | Reverts | Discards |\n+=================================================================+\n| TreasuryHandler | donateReward     | 2019  | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | elapse           | 2090  | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | flush            | 2075  | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | fund             | 2009  | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | market           | 1924  | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | payTeam          | 2087  | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | process          | 2116  | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | teamAvailability | 2064  | 0       | 0        |\n╰-----------------+------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.65s (2.49s CPU time)\n\nRan 1 test for test/invariant/Distributor.invariant.t.sol:DistributorInvariantTest\n[PASS]\nDistributorInvariantTest invariants:\n[PASS] invariant_principalAndFixedSupplyAreConserved\n[PASS] invariant_rewardsAreBackedAndConserved\n DistributorInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭--------------------+-------------------+-------+---------+----------╮\n| Contract           | Selector          | Calls | Reverts | Discards |\n+=====================================================================+\n| DistributorHandler | claim             | 1800  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| DistributorHandler | donate            | 1902  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| DistributorHandler | elapse            | 1782  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| DistributorHandler | invalidWithdrawal | 1841  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| DistributorHandler | move              | 1743  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| DistributorHandler | reward            | 1871  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| DistributorHandler | roundTrip         | 1790  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| DistributorHandler | stake             | 1817  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| DistributorHandler | unstake           | 1838  | 0       | 0        |\n╰--------------------+-------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.07s (5.07s CPU time)\n\nRan 14 test suites in 5.07s (11.43s CPU time): 119 tests passed, 0 failed, 0 skipped (119 total tests)\n","passed":true},{"durationMs":92,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"AdamDistributor.claim()\",\"AdamDistributor.exit()\",\"AdamDistributor.notifyReward(address,uint256)\",\"AdamDistributor.stake(uint256)\",\"AdamDistributor.unstake(uint256)\",\"AdamHook.acceptOwnership()\",\"AdamHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"AdamHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"AdamHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"AdamHook.lowerFee(uint16)\",\"AdamHook.renounceOwnership()\",\"AdamHook.transferOwnership(address)\",\"AdamTreasury.flushRewards()\",\"AdamTreasury.payTeam()\",\"AdamTreasury.process()\",\"AdamTreasury.receive()\",\"AdamTreasury.unlockCallback(bytes)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":83,\"SELF_AUDIT.md\":31,\"foundry.toml\":32,\"remappings.txt\":9,\"script/DeployAdam.s.sol\":308,\"script/utils/HookMiner.sol\":60,\"src/AdamDistributor.sol\":259,\"src/AdamHook.sol\":275,\"src/AdamTreasury.sol\":362,\"src/LaunchToken.sol\":17,\"src/interfaces/IAdamDistributor.sol\":8,\"test/REVISION.md\":39,\"test/TESTING.md\":61,\"test/fork/MainnetFork.t.sol\":239,\"test/fuzz/Treasury.fuzz.t.sol\":101,\"test/invariant/Distributor.invariant.t.sol\":255,\"test/invariant/Treasury.invariant.t.sol\":244,\"test/unit/AdamDistributor.t.sol\":423,\"test/unit/AdamHook.t.sol\":278,\"test/unit/AdamTreasury.t.sol\":500,\"test/unit/DistributorAdversarial.t.sol\":308,\"test/unit/HookAdversarial.t.sol\":101,\"test/unit/Integration.t.sol\":159,\"test/unit/LaunchToken.t.sol\":64,\"test/unit/PostFactoryHook.t.sol\":151,\"test/unit/Revision.t.sol\":197,\"test/unit/TreasuryBoundaries.t.sol\":195,\"test/utils/LocalV4.sol\":223,\"test/utils/MockTaxHook.sol\":129},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"773defab701f3a5c0ed179a7f0276fab44bca6446e452f13e0277df9de3d21dc","verifiedTreeHash":"60d60c03ca9ac661a08c853f0de2a78274394854","verifierVersion":"0.1.0+41305fb5"},{"checks":[{"durationMs":77420,"exitCode":0,"name":"build","output":"Compiling 118 files with Solc 0.8.26\nSolc 0.8.26 finished in 77.25s\nCompiler run successful!\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:148:97\n    │\n148 │                 || imdTaxBps >= BPS || pnkstrTaxBps >= BPS || imd == pnkstr || imdTickSpacing > uint24(type(int24).max)\n    │                                                                                                 ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint24' 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/AdamTreasury.sol:149:40\n    │\n149 │                 || pnkstrTickSpacing > uint24(type(int24).max)\n    │                                        ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint24' 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/AdamTreasury.sol:163:26\n    │\n163 │             tickSpacing: int24(imdTickSpacing),\n    │                          ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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/AdamTreasury.sol:171:26\n    │\n171 │             tickSpacing: int24(pnkstrTickSpacing),\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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/AdamDistributor.sol:116:9\n    │\n116 │         emit Staked(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamHook.sol:114:13\n    │\n114 │         if (elapsed >= DECAY_DURATION) return base;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_legs` is updated\n    ╭▸ src/AdamTreasury.sol:328:13\n    │\n328 │         try poolManager.unlock(abi.encode(legId, ethIn)) returns (bytes memory result) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamHook.sol:168:31\n    │\n168 │             launchTimestamp = uint64(block.timestamp);\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamHook.sol:176:42\n    │\n176 │             params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamHook.sol:176:77\n    │\n176 │             params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamDistributor.sol:204:13\n    │\n204 │             emit RewardClaimed(account, token, amount);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamHook.sol:191:46\n    │\n191 │                 params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamHook.sol:191:81\n    │\n191 │                 params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamHook.sol:194:47\n    │\n194 │                 params.amountSpecified < 0 ? -int256(requested - prepaidFee) : int256(requested + prepaidFee);\n    │                                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' 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/AdamHook.sol:194:80\n    │\n194 │                 params.amountSpecified < 0 ? -int256(requested - prepaidFee) : int256(requested + prepaidFee);\n    │                                                                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' 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-no-eth]: external call can be reentered before `_legs` is updated\n    ╭▸ src/AdamTreasury.sol:335:13\n    │\n335 │             distributor.notifyReward(token, amountOut);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamHook.sol:199:44\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:199:52\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:199:74\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:199:82\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamDistributor.sol:152:9\n    │\n152 │         emit RewardNotified(token, msg.sender, received, distributed);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:195:13\n    │\n195 │         if (block.timestamp < availableAt) revert CooldownActive(availableAt);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamTreasury.sol:333:13\n    │\n333 │             emit LegBought(legId, token, ethIn, amountOut);\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/AdamTreasury.sol:337:13\n    │\n337 │             emit LegFailed(legId, ethIn, reason);\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/AdamTreasury.sol:351:17\n    │\n351 │                 emit LegRerouted(legId, other, moved);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:201:25\n    │\n201 │         lastProcessed = uint64(block.timestamp);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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-eth]: uncapped ETH transfer can be reentered before `_status` is updated\n    ╭▸ src/AdamTreasury.sol:224:22\n    │\n224 │         (bool ok,) = teamWallet.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-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/AdamTreasury.sol:226:9\n    │\n226 │         emit TeamPaid(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/AdamTreasury.sol:236:13\n    │\n236 │             distributor.notifyReward(token, balance);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/AdamTreasury.sol:233:31\n    │\n233 │             uint256 balance = IERC20(token).balanceOf(address(this));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/AdamTreasury.sol:236:13\n    │\n236 │             distributor.notifyReward(token, balance);\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/AdamTreasury.sol:237:13\n    │\n237 │             emit RewardsFlushed(token, balance);\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/AdamDistributor.sol:191:9\n    │\n191 │         emit Unstaked(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamDistributor.sol:233:13\n    │\n233 │         if (elapsed > BACKLOG_DURATION) elapsed = BACKLOG_DURATION;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamDistributor.sol:255:77\n    │\n255 │                 backlogStream[token] = BacklogStream(unallocated[token], 0, uint64(block.timestamp));\n    │                                                                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamTreasury.sol:256:53\n    │\n256 │                 zeroForOne: true, amountSpecified: -int256(ethIn), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1\n    │                                                     ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' 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/AdamTreasury.sol:262:30\n    │\n262 │         if (ethDelta >= 0 || uint256(uint128(-ethDelta)) != ethIn) revert PartialFill();\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/AdamTreasury.sol:262:38\n    │\n262 │         if (ethDelta >= 0 || uint256(uint128(-ethDelta)) != ethIn) revert PartialFill();\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/AdamTreasury.sol:264:29\n    │\n264 │         uint256 amountOut = uint256(uint128(outDelta));\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/AdamTreasury.sol:264:37\n    │\n264 │         uint256 amountOut = uint256(uint128(outDelta));\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/AdamTreasury.sol:267:9\n    │\n267 │         poolManager.settle{value: ethIn}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/AdamTreasury.sol:298:16\n    │\n298 │         return (afterTax * (BPS - slippageBps)) / BPS;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamHook.sol:273:9\n    │\n273 │         emit FeeTaken(params.zeroForOne, ethAmount, fee, 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/AdamTreasury.sol:309:13\n    │\n309 │             emit TeamPaid(amount);\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/AdamTreasury.sol:312:13\n    │\n312 │             emit TeamPaymentDeferred(amount);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[return-bomb]: external call with a gas limit may copy unbounded return data\n    ╭▸ src/AdamTreasury.sol:307:22\n    │\n307 │         (bool ok,) = teamWallet.call{value: amount, gas: TEAM_PUSH_GAS}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:319:32\n    │\n319 │         if (l.failures != 0 && block.timestamp > uint256(l.lastFailure) + allowedGap) _resetFailures(l);\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:345:47\n    │\n345 │             if (l.failures >= MIN_FAILURES && block.timestamp >= uint256(l.failingSince) + LEG_FALLBACK_DELAY) {\n    │                                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:339:34\n    │\n339 │                 l.failingSince = uint64(block.timestamp);\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamTreasury.sol:341:29\n    │\n341 │             l.lastFailure = uint64(block.timestamp);\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/fuzz/Treasury.fuzz.t.sol:64:25\n   │\n64 │         uint256 clock = block.timestamp;\n   │                         ━━━━━━━━━━━━━━━\n   ‡\n71 │             vm.warp(clock);\n   │             ────────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamDistributor.t.sol:369:25\n    │\n369 │         uint256 start = block.timestamp;\n    │                         ━━━━━━━━━━━━━━━\n370 │         assertEq(dist.earned(alice, address(imd)), 0, \"no instant jackpot at threshold\");\n371 │         vm.warp(start + 1 days);\n    │         ─────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamDistributor.t.sol:393:25\n    │\n393 │         uint256 start = block.timestamp;\n    │                         ━━━━━━━━━━━━━━━\n394 │         vm.warp(start + 1 days);\n    │         ─────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:243:34\n    │\n243 │         assertEq(l.failingSince, block.timestamp);\n    │                                  ━━━━━━━━━━━━━━━\n    ‡\n248 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ─────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:248:17\n    │\n248 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:417:17\n    │\n417 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:489:17\n    │\n489 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/unit/Revision.t.sol:32:17\n   │\n32 │         vm.warp(block.timestamp + COOLDOWN);\n   │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/Revision.t.sol:107:17\n    │\n107 │         vm.warp(block.timestamp + 7 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":1612,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/unit/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 80165, ~: 80343)\n[PASS] test_metadata() (gas: 24465)\n[PASS] test_mintsFixedSupplyToDeployer() (gas: 23278)\n[PASS] test_noMintOrAdminEntryPoints() (gas: 143123)\n[PASS] test_transferMovesExactAmount() (gas: 74094)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 34859)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 39.07ms (7.52ms CPU time)\n\nRan 24 tests for test/unit/AdamDistributor.t.sol:AdamDistributorTest\n[PASS] testFuzz_backlogConservation(uint128,uint32) (runs: 256, μ: 585924, ~: 605008)\n[PASS] testFuzz_claimsNeverExceedDistributed(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 1310564, ~: 1338902)\n[PASS] testFuzz_stakeUnstakeRoundTrip(uint256,uint256) (runs: 256, μ: 251041, ~: 251354)\n[PASS] test_backlogPausesBelowThresholdWithoutRetroactiveVesting() (gas: 907369)\n[PASS] test_backlogStreamsAndNewcomerCannotClaimPastTime() (gas: 1496660)\n[PASS] test_claimIsReentrancySafe() (gas: 1391380)\n[PASS] test_claimWithNothingIsANoop() (gas: 70106)\n[PASS] test_constructorState() (gas: 78331)\n[PASS] test_constructorValidation() (gas: 10329)\n[PASS] test_dustCannotCaptureBacklogEvenAfterWaiting() (gas: 792687)\n[PASS] test_exitAfterUnstakeStillClaims() (gas: 557146)\n[PASS] test_exitWithdrawsAndClaims() (gas: 452396)\n[PASS] test_extraExclusionIsApplied() (gas: 17907)\n[PASS] test_noOwnerOrAdminEntryPoints() (gas: 118344)\n[PASS] test_notifyRejectsUnknownTokenAndZero() (gas: 1127290)\n[PASS] test_notifyRequiresAllowance() (gas: 50801)\n[PASS] test_rewardsBeforeAnyStakeWaitForMeaningfulStake() (gas: 400286)\n[PASS] test_rewardsOnlyAccrueWhileStaked() (gas: 669298)\n[PASS] test_rewardsProRataAcrossTwoTokens() (gas: 1040157)\n[PASS] test_stakeAndUnstake() (gas: 275331)\n[PASS] test_stakeRejectsZeroAndExcluded() (gas: 204572)\n[PASS] test_tokensSentDirectlyAreNotStakedButRewardsFromBalanceDiff() (gas: 220431)\n[PASS] test_unstakeKeepsAccruedRewardsClaimable() (gas: 526684)\n[PASS] test_unstakeMoreThanStakedReverts() (gas: 207505)\nSuite result: ok. 24 passed; 0 failed; 0 skipped; finished in 39.99ms (75.96ms CPU time)\n\nRan 2 tests for test/unit/Integration.t.sol:IntegrationTest\n[PASS] test_excludedAddressesEarnNothing() (gas: 1111844)\n[PASS] test_fullLifecycle() (gas: 2254186)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 175.06ms (2.26ms CPU time)\n\nRan 11 tests for test/unit/Revision.t.sol:RevisionTest\n[PASS] testFuzz_exactOutputFeesAreGrossRate(uint32,uint128) (runs: 256, μ: 339342, ~: 339517)\n[PASS] test_constructorCheckpointAlsoProtectsFirstProcess() (gas: 641507)\n[PASS] test_dustWaitsWithoutStartingFailureStreak() (gas: 735279)\n[PASS] test_emptySellCannotChargeFeeOrStartClock() (gas: 372591)\n[PASS] test_exactOutputAndInputBuySameGrossFeeAtLaunch() (gas: 371741)\n[PASS] test_firstFilledSwapStartsFullDecayAfterDeploymentDelay() (gas: 223443)\n[PASS] test_hookAndTreasuryHaveNoStakingEntryPoint() (gas: 69673)\n[PASS] test_partialInputBuyRevertsAndReturnsEntireFee() (gas: 456031)\n[PASS] test_sandwichCannotLowerCheckpointFloor() (gas: 1640145)\n[PASS] test_thinPoolRecoversAtSmallerSize() (gas: 2347683)\n[PASS] test_twoIsolatedFailuresDoNotReroute() (gas: 959835)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 184.69ms (39.74ms CPU time)\n\nRan 22 tests for test/unit/AdamHook.t.sol:AdamHookTest\n[PASS] testFuzz_buyFeeMatchesSchedule(uint256,uint32) (runs: 256, μ: 331194, ~: 331448)\n[PASS] testFuzz_decayIsMonotoneAndBounded(uint32,uint32) (runs: 256, μ: 196468, ~: 196467)\n[PASS] test_addressCarriesExactlyTheRequiredFlags() (gas: 11968)\n[PASS] test_antiSnipeFeeAtLaunchIs20Percent() (gas: 185840)\n[PASS] test_buyExactInput_takes1_5PercentOfEthIn() (gas: 344650)\n[PASS] test_buyExactOutput_feeIsAddedOnTopOfEthIn() (gas: 346566)\n[PASS] test_callbacksRejectNonPoolManager() (gas: 90662)\n[PASS] test_constructorRejectsUnflaggedAddress() (gas: 12526)\n[PASS] test_decaySchedule() (gas: 223238)\n[PASS] test_feeCannotBeRaisedOrKept() (gas: 122115)\n[PASS] test_feeEventsAreEmitted() (gas: 333635)\n[PASS] test_initialState() (gas: 43167)\n[PASS] test_initializeOnlyOnce() (gas: 46279)\n[PASS] test_initializeRejectsWrongPair() (gas: 6491828)\n[PASS] test_initializeRequiresOwner() (gas: 6602647)\n[PASS] test_lowerFee() (gas: 240682)\n[PASS] test_lowerFeeDuringDecayKeepsDecaying() (gas: 220132)\n[PASS] test_lowerFeeToZeroDisablesFee() (gas: 358245)\n[PASS] test_onlyOwnerLowersFee() (gas: 35222)\n[PASS] test_sellExactInput_takes1_5PercentOfEthOut() (gas: 487228)\n[PASS] test_sellExactOutput_feeIsAddedOnTopOfEthOut() (gas: 467067)\n[PASS] test_twoStepOwnershipTransfer() (gas: 144671)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 214.72ms (150.68ms CPU time)\n\nRan 4 tests for test/fuzz/Treasury.fuzz.t.sol:TreasuryFuzzTest\n[PASS] testFuzz_accountingIdentityHolds(uint256,uint16,uint8) (runs: 256, μ: 1122923, ~: 1063275)\n[PASS] testFuzz_capAndConservation(uint256) (runs: 256, μ: 549605, ~: 549630)\n[PASS] testFuzz_quoteIsLinear(uint256) (runs: 256, μ: 91957, ~: 91997)\n[PASS] testFuzz_splitIsExact(uint256) (runs: 256, μ: 524441, ~: 524542)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 214.73ms (197.38ms CPU time)\n\nRan 20 tests for test/unit/AdamTreasury.t.sol:AdamTreasuryTest\n[PASS] test_anyoneCanProcess() (gas: 493995)\n[PASS] test_capPerCallLeavesRemainderUnsplit() (gas: 546448)\n[PASS] test_constructorValidation() (gas: 15276)\n[PASS] test_cooldownBetweenCalls() (gas: 890008)\n[PASS] test_distributorPendingRewardsFlowToStakersLater() (gas: 1668532)\n[PASS] test_flushRewardsForwardsStrandedTokens() (gas: 188424)\n[PASS] test_immutablesAndLegs() (gas: 94419)\n[PASS] test_legFailsWhenHookTaxExceedsAssumption_pendingIsKept() (gas: 1057483)\n[PASS] test_legFailureEmitsReason() (gas: 483403)\n[PASS] test_legsReceiveEqualEth() (gas: 533038)\n[PASS] test_noWithdrawalPathForAnyone() (gas: 147438)\n[PASS] test_ownSwapImpactAboveSlippageIsRejected() (gas: 494486)\n[PASS] test_processRevertsWhenNothingToDo() (gas: 43991)\n[PASS] test_processSplitsTenNinetyAndBuysBothTokens() (gas: 616811)\n[PASS] test_quoteMinOutMatchesSpotMath() (gas: 69243)\n[PASS] test_rerouteAfterThreeDaysOfFailure() (gas: 10501328)\n[PASS] test_rerouteWorksInBothDirections() (gas: 5707518)\n[PASS] test_teamPushFailureIsDeferredNotBlocking() (gas: 858871)\n[PASS] test_teamWalletCannotReenterProcess() (gas: 2264891)\n[PASS] test_unlockCallbackIsGuarded() (gas: 63177)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 1.46s (20.51ms CPU time)\n\nRan 4 tests for test/unit/Integration.t.sol:DeployWiringTest\n[PASS] test_contractsAreWiredTogether() (gas: 62268)\n[PASS] test_deployWithExistingToken() (gas: 460111966)\n[PASS] test_launchPoolRejectsMisconfiguration() (gas: 142197)\n[PASS] test_mainnetConfigConstants() (gas: 14465)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.46s (1.32s CPU time)\n\nRan 8 test suites in 1.46s (3.79s CPU time): 93 tests passed, 0 failed, 0 skipped (93 total tests)\n","passed":true},{"durationMs":97,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"AdamDistributor.claim()\",\"AdamDistributor.exit()\",\"AdamDistributor.notifyReward(address,uint256)\",\"AdamDistributor.stake(uint256)\",\"AdamDistributor.unstake(uint256)\",\"AdamHook.acceptOwnership()\",\"AdamHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"AdamHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"AdamHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"AdamHook.lowerFee(uint16)\",\"AdamHook.renounceOwnership()\",\"AdamHook.transferOwnership(address)\",\"AdamTreasury.flushRewards()\",\"AdamTreasury.payTeam()\",\"AdamTreasury.process()\",\"AdamTreasury.receive()\",\"AdamTreasury.unlockCallback(bytes)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":74,\"SELF_AUDIT.md\":28,\"foundry.toml\":32,\"launch.json\":49,\"remappings.txt\":9,\"script/DeployAdam.s.sol\":265,\"script/utils/HookMiner.sol\":60,\"src/AdamDistributor.sol\":259,\"src/AdamHook.sol\":275,\"src/AdamTreasury.sol\":362,\"src/LaunchToken.sol\":17,\"src/interfaces/IAdamDistributor.sol\":8,\"test/fork/MainnetFork.t.sol\":234,\"test/fuzz/Treasury.fuzz.t.sol\":101,\"test/unit/AdamDistributor.t.sol\":423,\"test/unit/AdamHook.t.sol\":278,\"test/unit/AdamTreasury.t.sol\":500,\"test/unit/Integration.t.sol\":159,\"test/unit/LaunchToken.t.sol\":64,\"test/unit/Revision.t.sol\":197,\"test/utils/LocalV4.sol\":223,\"test/utils/MockTaxHook.sol\":129},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"9e92b0d21f8138def6ff112de1f3054e97e0863c48557217ec21429d95306a75","verifiedTreeHash":"b922d320db927dd80db03452c0b9210238ac4e7c","verifierVersion":"0.1.0+41305fb5"},{"checks":[{"durationMs":143542,"exitCode":0,"name":"build","output":"Compiling 122 files with Solc 0.8.26\nSolc 0.8.26 finished in 143.29s\nCompiler run successful!\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamHook.sol:114:13\n    │\n114 │         if (elapsed >= DECAY_DURATION) return base;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:148:97\n    │\n148 │                 || imdTaxBps >= BPS || pnkstrTaxBps >= BPS || imd == pnkstr || imdTickSpacing > uint24(type(int24).max)\n    │                                                                                                 ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint24' 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/AdamTreasury.sol:149:40\n    │\n149 │                 || pnkstrTickSpacing > uint24(type(int24).max)\n    │                                        ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint24' 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/AdamTreasury.sol:163:26\n    │\n163 │             tickSpacing: int24(imdTickSpacing),\n    │                          ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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/AdamTreasury.sol:171:26\n    │\n171 │             tickSpacing: int24(pnkstrTickSpacing),\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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/AdamHook.sol:168:31\n    │\n168 │             launchTimestamp = uint64(block.timestamp);\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamHook.sol:176:42\n    │\n176 │             params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamHook.sol:176:77\n    │\n176 │             params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamHook.sol:191:46\n    │\n191 │                 params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamHook.sol:191:81\n    │\n191 │                 params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamHook.sol:194:47\n    │\n194 │                 params.amountSpecified < 0 ? -int256(requested - prepaidFee) : int256(requested + prepaidFee);\n    │                                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' 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/AdamHook.sol:194:80\n    │\n194 │                 params.amountSpecified < 0 ? -int256(requested - prepaidFee) : int256(requested + prepaidFee);\n    │                                                                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' 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/AdamHook.sol:199:44\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:199:52\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:199:74\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:199:82\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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-no-eth]: external call can be reentered before `_legs` is updated\n    ╭▸ src/AdamTreasury.sol:328:13\n    │\n328 │         try poolManager.unlock(abi.encode(legId, ethIn)) returns (bytes memory result) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-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/AdamHook.sol:273:9\n    │\n273 │         emit FeeTaken(params.zeroForOne, ethAmount, fee, bps);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_legs` is updated\n    ╭▸ src/AdamTreasury.sol:335:13\n    │\n335 │             distributor.notifyReward(token, amountOut);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:195:13\n    │\n195 │         if (block.timestamp < availableAt) revert CooldownActive(availableAt);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamTreasury.sol:333:13\n    │\n333 │             emit LegBought(legId, token, ethIn, amountOut);\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/AdamTreasury.sol:337:13\n    │\n337 │             emit LegFailed(legId, ethIn, reason);\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/AdamDistributor.sol:116:9\n    │\n116 │         emit Staked(msg.sender, amount);\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/AdamTreasury.sol:351:17\n    │\n351 │                 emit LegRerouted(legId, other, moved);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:201:25\n    │\n201 │         lastProcessed = uint64(block.timestamp);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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-eth]: uncapped ETH transfer can be reentered before `_status` is updated\n    ╭▸ src/AdamTreasury.sol:224:22\n    │\n224 │         (bool ok,) = teamWallet.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-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/AdamTreasury.sol:226:9\n    │\n226 │         emit TeamPaid(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/AdamTreasury.sol:236:13\n    │\n236 │             distributor.notifyReward(token, balance);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/AdamTreasury.sol:233:31\n    │\n233 │             uint256 balance = IERC20(token).balanceOf(address(this));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/AdamTreasury.sol:236:13\n    │\n236 │             distributor.notifyReward(token, balance);\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/AdamTreasury.sol:237:13\n    │\n237 │             emit RewardsFlushed(token, balance);\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/AdamDistributor.sol:204:13\n    │\n204 │             emit RewardClaimed(account, token, amount);\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/AdamDistributor.sol:152:9\n    │\n152 │         emit RewardNotified(token, msg.sender, received, distributed);\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/AdamDistributor.sol:191:9\n    │\n191 │         emit Unstaked(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamDistributor.sol:233:13\n    │\n233 │         if (elapsed > BACKLOG_DURATION) elapsed = BACKLOG_DURATION;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamDistributor.sol:255:77\n    │\n255 │                 backlogStream[token] = BacklogStream(unallocated[token], 0, uint64(block.timestamp));\n    │                                                                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamTreasury.sol:256:53\n    │\n256 │                 zeroForOne: true, amountSpecified: -int256(ethIn), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1\n    │                                                     ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' 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/AdamTreasury.sol:262:30\n    │\n262 │         if (ethDelta >= 0 || uint256(uint128(-ethDelta)) != ethIn) revert PartialFill();\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/AdamTreasury.sol:262:38\n    │\n262 │         if (ethDelta >= 0 || uint256(uint128(-ethDelta)) != ethIn) revert PartialFill();\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/AdamTreasury.sol:264:29\n    │\n264 │         uint256 amountOut = uint256(uint128(outDelta));\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/AdamTreasury.sol:264:37\n    │\n264 │         uint256 amountOut = uint256(uint128(outDelta));\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/AdamTreasury.sol:267:9\n    │\n267 │         poolManager.settle{value: ethIn}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/AdamTreasury.sol:298:16\n    │\n298 │         return (afterTax * (BPS - slippageBps)) / BPS;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamTreasury.sol:309:13\n    │\n309 │             emit TeamPaid(amount);\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/AdamTreasury.sol:312:13\n    │\n312 │             emit TeamPaymentDeferred(amount);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[return-bomb]: external call with a gas limit may copy unbounded return data\n    ╭▸ src/AdamTreasury.sol:307:22\n    │\n307 │         (bool ok,) = teamWallet.call{value: amount, gas: TEAM_PUSH_GAS}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:319:32\n    │\n319 │         if (l.failures != 0 && block.timestamp > uint256(l.lastFailure) + allowedGap) _resetFailures(l);\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:345:47\n    │\n345 │             if (l.failures >= MIN_FAILURES && block.timestamp >= uint256(l.failingSince) + LEG_FALLBACK_DELAY) {\n    │                                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:339:34\n    │\n339 │                 l.failingSince = uint64(block.timestamp);\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamTreasury.sol:341:29\n    │\n341 │             l.lastFailure = uint64(block.timestamp);\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/fuzz/Treasury.fuzz.t.sol:64:25\n   │\n64 │         uint256 clock = block.timestamp;\n   │                         ━━━━━━━━━━━━━━━\n   ‡\n71 │             vm.warp(clock);\n   │             ────────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamDistributor.t.sol:369:25\n    │\n369 │         uint256 start = block.timestamp;\n    │                         ━━━━━━━━━━━━━━━\n370 │         assertEq(dist.earned(alice, address(imd)), 0, \"no instant jackpot at threshold\");\n371 │         vm.warp(start + 1 days);\n    │         ─────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamDistributor.t.sol:393:25\n    │\n393 │         uint256 start = block.timestamp;\n    │                         ━━━━━━━━━━━━━━━\n394 │         vm.warp(start + 1 days);\n    │         ─────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:243:34\n    │\n243 │         assertEq(l.failingSince, block.timestamp);\n    │                                  ━━━━━━━━━━━━━━━\n    ‡\n248 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ─────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:248:17\n    │\n248 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:417:17\n    │\n417 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:489:17\n    │\n489 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/unit/Revision.t.sol:32:17\n   │\n32 │         vm.warp(block.timestamp + COOLDOWN);\n   │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/Revision.t.sol:107:17\n    │\n107 │         vm.warp(block.timestamp + 7 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":6232,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 24 tests for test/unit/AdamDistributor.t.sol:AdamDistributorTest\n[PASS] testFuzz_backlogConservation(uint128,uint32) (runs: 256, μ: 585789, ~: 604996)\n[PASS] testFuzz_claimsNeverExceedDistributed(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 1313569, ~: 1339105)\n[PASS] testFuzz_stakeUnstakeRoundTrip(uint256,uint256) (runs: 256, μ: 251151, ~: 251363)\n[PASS] test_backlogPausesBelowThresholdWithoutRetroactiveVesting() (gas: 907369)\n[PASS] test_backlogStreamsAndNewcomerCannotClaimPastTime() (gas: 1496660)\n[PASS] test_claimIsReentrancySafe() (gas: 1391380)\n[PASS] test_claimWithNothingIsANoop() (gas: 70106)\n[PASS] test_constructorState() (gas: 78331)\n[PASS] test_constructorValidation() (gas: 10329)\n[PASS] test_dustCannotCaptureBacklogEvenAfterWaiting() (gas: 792687)\n[PASS] test_exitAfterUnstakeStillClaims() (gas: 557146)\n[PASS] test_exitWithdrawsAndClaims() (gas: 452396)\n[PASS] test_extraExclusionIsApplied() (gas: 17907)\n[PASS] test_noOwnerOrAdminEntryPoints() (gas: 118344)\n[PASS] test_notifyRejectsUnknownTokenAndZero() (gas: 1127290)\n[PASS] test_notifyRequiresAllowance() (gas: 50801)\n[PASS] test_rewardsBeforeAnyStakeWaitForMeaningfulStake() (gas: 400286)\n[PASS] test_rewardsOnlyAccrueWhileStaked() (gas: 669298)\n[PASS] test_rewardsProRataAcrossTwoTokens() (gas: 1040157)\n[PASS] test_stakeAndUnstake() (gas: 275331)\n[PASS] test_stakeRejectsZeroAndExcluded() (gas: 204572)\n[PASS] test_tokensSentDirectlyAreNotStakedButRewardsFromBalanceDiff() (gas: 220431)\n[PASS] test_unstakeKeepsAccruedRewardsClaimable() (gas: 526684)\n[PASS] test_unstakeMoreThanStakedReverts() (gas: 207505)\nSuite result: ok. 24 passed; 0 failed; 0 skipped; finished in 63.95ms (177.01ms CPU time)\n\nRan 6 tests for test/unit/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 80005, ~: 80361)\n[PASS] test_metadata() (gas: 24465)\n[PASS] test_mintsFixedSupplyToDeployer() (gas: 23278)\n[PASS] test_noMintOrAdminEntryPoints() (gas: 143123)\n[PASS] test_transferMovesExactAmount() (gas: 74094)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 34859)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 67.62ms (68.25ms CPU time)\n\nRan 12 tests for test/unit/DistributorAdversarial.t.sol:DistributorAdversarialTest\n[PASS] testFuzz_taxedRewardCreditsOnlyReceivedTokens(uint256,uint16) (runs: 1000, μ: 496524, ~: 499583)\n[PASS] testFuzz_twoEpochsFollowStakeOwnership(uint256,uint256,uint256,uint256) (runs: 1000, μ: 839436, ~: 846817)\n[PASS] test_allFixedExclusionsRejectStakeBeforeTokenInteraction() (gas: 418004)\n[PASS] test_claimCallbackCannotReenterAnyHolderOrRewardAction() (gas: 499308)\n[PASS] test_failedSecondPayoutRollsBackFirstPayoutAndCanRecover() (gas: 1014441)\n[PASS] test_failedStakeRollsBackBacklogAndShares() (gas: 554541)\n[PASS] test_falseReturningIntakeCannotCreateCredit() (gas: 244782)\n[PASS] test_fullSupplyCanStakeAndExit() (gas: 572641)\n[PASS] test_noReturnTokenCanNotifyAndClaim() (gas: 455102)\n[PASS] test_notifyCallbackCannotReenterAnyHolderOrRewardAction() (gas: 346692)\n[PASS] test_oneWeiAndCompleteTransferTax() (gas: 520743)\n[PASS] test_pausedRewardCannotTrapUnstakedPrincipal() (gas: 607688)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 67.75ms (138.27ms CPU time)\n\nRan 20 tests for test/unit/AdamTreasury.t.sol:AdamTreasuryTest\n[PASS] test_anyoneCanProcess() (gas: 493995)\n[PASS] test_capPerCallLeavesRemainderUnsplit() (gas: 546448)\n[PASS] test_constructorValidation() (gas: 15276)\n[PASS] test_cooldownBetweenCalls() (gas: 890008)\n[PASS] test_distributorPendingRewardsFlowToStakersLater() (gas: 1668532)\n[PASS] test_flushRewardsForwardsStrandedTokens() (gas: 188424)\n[PASS] test_immutablesAndLegs() (gas: 94419)\n[PASS] test_legFailsWhenHookTaxExceedsAssumption_pendingIsKept() (gas: 1057483)\n[PASS] test_legFailureEmitsReason() (gas: 483403)\n[PASS] test_legsReceiveEqualEth() (gas: 533038)\n[PASS] test_noWithdrawalPathForAnyone() (gas: 147438)\n[PASS] test_ownSwapImpactAboveSlippageIsRejected() (gas: 494486)\n[PASS] test_processRevertsWhenNothingToDo() (gas: 43991)\n[PASS] test_processSplitsTenNinetyAndBuysBothTokens() (gas: 616811)\n[PASS] test_quoteMinOutMatchesSpotMath() (gas: 69243)\n[PASS] test_rerouteAfterThreeDaysOfFailure() (gas: 10501328)\n[PASS] test_rerouteWorksInBothDirections() (gas: 5707518)\n[PASS] test_teamPushFailureIsDeferredNotBlocking() (gas: 858871)\n[PASS] test_teamWalletCannotReenterProcess() (gas: 2264891)\n[PASS] test_unlockCallbackIsGuarded() (gas: 63177)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 170.06ms (21.57ms CPU time)\n\nRan 2 tests for test/unit/Integration.t.sol:IntegrationTest\n[PASS] test_excludedAddressesEarnNothing() (gas: 1111844)\n[PASS] test_fullLifecycle() (gas: 2254186)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 195.63ms (3.12ms CPU time)\n\nRan 11 tests for test/unit/Revision.t.sol:RevisionTest\n[PASS] testFuzz_exactOutputFeesAreGrossRate(uint32,uint128) (runs: 256, μ: 339374, ~: 339529)\n[PASS] test_constructorCheckpointAlsoProtectsFirstProcess() (gas: 641507)\n[PASS] test_dustWaitsWithoutStartingFailureStreak() (gas: 735279)\n[PASS] test_emptySellCannotChargeFeeOrStartClock() (gas: 372591)\n[PASS] test_exactOutputAndInputBuySameGrossFeeAtLaunch() (gas: 371741)\n[PASS] test_firstFilledSwapStartsFullDecayAfterDeploymentDelay() (gas: 223443)\n[PASS] test_hookAndTreasuryHaveNoStakingEntryPoint() (gas: 69673)\n[PASS] test_partialInputBuyRevertsAndReturnsEntireFee() (gas: 456031)\n[PASS] test_sandwichCannotLowerCheckpointFloor() (gas: 1640145)\n[PASS] test_thinPoolRecoversAtSmallerSize() (gas: 2347683)\n[PASS] test_twoIsolatedFailuresDoNotReroute() (gas: 959835)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 288.99ms (82.18ms CPU time)\n\nRan 4 tests for test/fuzz/Treasury.fuzz.t.sol:TreasuryFuzzTest\n[PASS] testFuzz_accountingIdentityHolds(uint256,uint16,uint8) (runs: 256, μ: 1125270, ~: 1063112)\n[PASS] testFuzz_capAndConservation(uint256) (runs: 256, μ: 549609, ~: 549630)\n[PASS] testFuzz_quoteIsLinear(uint256) (runs: 256, μ: 91953, ~: 91978)\n[PASS] testFuzz_splitIsExact(uint256) (runs: 256, μ: 524424, ~: 524542)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 290.43ms (375.48ms CPU time)\n\nRan 22 tests for test/unit/AdamHook.t.sol:AdamHookTest\n[PASS] testFuzz_buyFeeMatchesSchedule(uint256,uint32) (runs: 256, μ: 331184, ~: 331444)\n[PASS] testFuzz_decayIsMonotoneAndBounded(uint32,uint32) (runs: 256, μ: 196462, ~: 196467)\n[PASS] test_addressCarriesExactlyTheRequiredFlags() (gas: 11968)\n[PASS] test_antiSnipeFeeAtLaunchIs20Percent() (gas: 185840)\n[PASS] test_buyExactInput_takes1_5PercentOfEthIn() (gas: 344650)\n[PASS] test_buyExactOutput_feeIsAddedOnTopOfEthIn() (gas: 346566)\n[PASS] test_callbacksRejectNonPoolManager() (gas: 90662)\n[PASS] test_constructorRejectsUnflaggedAddress() (gas: 12526)\n[PASS] test_decaySchedule() (gas: 223238)\n[PASS] test_feeCannotBeRaisedOrKept() (gas: 122115)\n[PASS] test_feeEventsAreEmitted() (gas: 333635)\n[PASS] test_initialState() (gas: 43167)\n[PASS] test_initializeOnlyOnce() (gas: 46279)\n[PASS] test_initializeRejectsWrongPair() (gas: 6491828)\n[PASS] test_initializeRequiresOwner() (gas: 6602647)\n[PASS] test_lowerFee() (gas: 240682)\n[PASS] test_lowerFeeDuringDecayKeepsDecaying() (gas: 220132)\n[PASS] test_lowerFeeToZeroDisablesFee() (gas: 358245)\n[PASS] test_onlyOwnerLowersFee() (gas: 35222)\n[PASS] test_sellExactInput_takes1_5PercentOfEthOut() (gas: 487228)\n[PASS] test_sellExactOutput_feeIsAddedOnTopOfEthOut() (gas: 467067)\n[PASS] test_twoStepOwnershipTransfer() (gas: 144671)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 309.10ms (235.14ms CPU time)\n\nRan 4 tests for test/unit/HookAdversarial.t.sol:HookAdversarialTest\n[PASS] testFuzz_exactInputSellChargesGrossEthAtAnyDecayTime(uint256,uint32,uint16) (runs: 1000, μ: 385725, ~: 386370)\n[PASS] testFuzz_exactOutputSellDeliversRequestedNetAndGrossFee(uint256,uint32) (runs: 1000, μ: 351509, ~: 351839)\n[PASS] test_decayBoundariesAndNoFeeResurrection() (gas: 526002)\n[PASS] test_partialExactOutputSellRevertsFeeAndPoolMovement() (gas: 480909)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 309.17ms (244.17ms CPU time)\n\nRan 4 tests for test/unit/Integration.t.sol:DeployWiringTest\n[PASS] test_contractsAreWiredTogether() (gas: 62268)\n[PASS] test_deployWithExistingToken() (gas: 460111966)\n[PASS] test_launchPoolRejectsMisconfiguration() (gas: 142197)\n[PASS] test_mainnetConfigConstants() (gas: 14465)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.75s (1.52s CPU time)\n\nRan 1 test for test/invariant/Treasury.invariant.t.sol:TreasuryInvariantTest\n[PASS]\nTreasuryInvariantTest invariants:\n[PASS] invariant_ethIsConservedAcrossIndependentCustodians\n[PASS] invariant_rewardsReachDistributorWithoutUnbackedCredit\n TreasuryInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭-----------------+------------------+-------+---------+----------╮\n| Contract        | Selector         | Calls | Reverts | Discards |\n+=================================================================+\n| TreasuryHandler | donateReward     | 2137  | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | elapse           | 2054  | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | flush            | 2002  | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | fund             | 2089  | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | market           | 2042  | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | payTeam          | 2010  | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | process          | 2020  | 0       | 0        |\n|-----------------+------------------+-------+---------+----------|\n| TreasuryHandler | teamAvailability | 2030  | 0       | 0        |\n╰-----------------+------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 3.20s (3.04s CPU time)\n\nRan 1 test for test/invariant/Distributor.invariant.t.sol:DistributorInvariantTest\n[PASS]\nDistributorInvariantTest invariants:\n[PASS] invariant_principalAndFixedSupplyAreConserved\n[PASS] invariant_rewardsAreBackedAndConserved\n DistributorInvariantTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭--------------------+-------------------+-------+---------+----------╮\n| Contract           | Selector          | Calls | Reverts | Discards |\n+=====================================================================+\n| DistributorHandler | claim             | 1863  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| DistributorHandler | donate            | 1913  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| DistributorHandler | elapse            | 1767  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| DistributorHandler | invalidWithdrawal | 1738  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| DistributorHandler | move              | 1818  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| DistributorHandler | reward            | 1824  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| DistributorHandler | roundTrip         | 1773  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| DistributorHandler | stake             | 1838  | 0       | 0        |\n|--------------------+-------------------+-------+---------+----------|\n| DistributorHandler | unstake           | 1850  | 0       | 0        |\n╰--------------------+-------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 6.03s (6.02s CPU time)\n\nRan 12 test suites in 6.03s (12.74s CPU time): 111 tests passed, 0 failed, 0 skipped (111 total tests)\n","passed":true},{"durationMs":96,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"AdamDistributor.claim()\",\"AdamDistributor.exit()\",\"AdamDistributor.notifyReward(address,uint256)\",\"AdamDistributor.stake(uint256)\",\"AdamDistributor.unstake(uint256)\",\"AdamHook.acceptOwnership()\",\"AdamHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"AdamHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"AdamHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"AdamHook.lowerFee(uint16)\",\"AdamHook.renounceOwnership()\",\"AdamHook.transferOwnership(address)\",\"AdamTreasury.flushRewards()\",\"AdamTreasury.payTeam()\",\"AdamTreasury.process()\",\"AdamTreasury.receive()\",\"AdamTreasury.unlockCallback(bytes)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":74,\"SELF_AUDIT.md\":28,\"foundry.toml\":32,\"remappings.txt\":9,\"script/DeployAdam.s.sol\":265,\"script/utils/HookMiner.sol\":60,\"src/AdamDistributor.sol\":259,\"src/AdamHook.sol\":275,\"src/AdamTreasury.sol\":362,\"src/LaunchToken.sol\":17,\"src/interfaces/IAdamDistributor.sol\":8,\"test/TESTING.md\":61,\"test/fork/MainnetFork.t.sol\":239,\"test/fuzz/Treasury.fuzz.t.sol\":101,\"test/invariant/Distributor.invariant.t.sol\":255,\"test/invariant/Treasury.invariant.t.sol\":244,\"test/unit/AdamDistributor.t.sol\":423,\"test/unit/AdamHook.t.sol\":278,\"test/unit/AdamTreasury.t.sol\":500,\"test/unit/DistributorAdversarial.t.sol\":308,\"test/unit/HookAdversarial.t.sol\":101,\"test/unit/Integration.t.sol\":159,\"test/unit/LaunchToken.t.sol\":64,\"test/unit/Revision.t.sol\":197,\"test/utils/LocalV4.sol\":223,\"test/utils/MockTaxHook.sol\":129},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"cc32cb683589902d7f5495130032aff694ba0d042ea1424adb74a3978b58225e","verifiedTreeHash":"b007161436eff900f169a39075ae0407c0f07559","verifierVersion":"0.1.0+41305fb5"},{"checks":[{"durationMs":85547,"exitCode":0,"name":"build","output":"Compiling 117 files with Solc 0.8.26\nSolc 0.8.26 finished in 85.33s\nCompiler run successful!\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:138:77\n    │\n138 │                 || pnkstrTaxBps >= BPS || imd == pnkstr || imdTickSpacing > uint24(type(int24).max)\n    │                                                                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint24' 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/AdamDistributor.sol:105:9\n    │\n105 │         emit Staked(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:139:40\n    │\n139 │                 || pnkstrTickSpacing > uint24(type(int24).max)\n    │                                        ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint24' 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/AdamTreasury.sol:153:26\n    │\n153 │             tickSpacing: int24(imdTickSpacing),\n    │                          ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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/AdamTreasury.sol:161:26\n    │\n161 │             tickSpacing: int24(pnkstrTickSpacing),\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamHook.sol:109:13\n    │\n109 │         if (elapsed >= DECAY_DURATION) return base;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamDistributor.sol:191:13\n    │\n191 │             emit RewardClaimed(account, token, amount);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamHook.sol:151:27\n    │\n151 │         launchTimestamp = uint64(block.timestamp);\n    │                           ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamDistributor.sol:140:9\n    │\n140 │         emit RewardNotified(token, msg.sender, received, distributed);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamHook.sol:153:35\n    │\n153 │         emit PoolLaunched(poolId, uint64(block.timestamp));\n    │                                   ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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-no-eth]: external call can be reentered before `_legs` is updated\n    ╭▸ src/AdamTreasury.sol:304:13\n    │\n304 │         try poolManager.unlock(abi.encode(legId, ethIn)) returns (bytes memory result) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamHook.sol:167:42\n    │\n167 │             params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamHook.sol:167:77\n    │\n167 │             params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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-no-eth]: external call can be reentered before `_legs` is updated\n    ╭▸ src/AdamTreasury.sol:310:13\n    │\n310 │             distributor.notifyReward(token, amountOut);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamHook.sol:180:44\n    │\n180 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:180:52\n    │\n180 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:180:74\n    │\n180 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:180:82\n    │\n180 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:178:13\n    │\n178 │         if (block.timestamp < availableAt) revert CooldownActive(availableAt);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamTreasury.sol:308:13\n    │\n308 │             emit LegBought(legId, token, ethIn, amountOut);\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/AdamTreasury.sol:312:13\n    │\n312 │             emit LegFailed(legId, ethIn, reason);\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/AdamTreasury.sol:321:17\n    │\n321 │                 emit LegRerouted(legId, other, moved);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:184:25\n    │\n184 │         lastProcessed = uint64(block.timestamp);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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-eth]: uncapped ETH transfer can be reentered before `_status` is updated\n    ╭▸ src/AdamTreasury.sol:207:22\n    │\n207 │         (bool ok,) = teamWallet.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-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/AdamTreasury.sol:209:9\n    │\n209 │         emit TeamPaid(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/AdamTreasury.sol:219:13\n    │\n219 │             distributor.notifyReward(token, balance);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/AdamTreasury.sol:216:31\n    │\n216 │             uint256 balance = IERC20(token).balanceOf(address(this));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/AdamTreasury.sol:219:13\n    │\n219 │             distributor.notifyReward(token, balance);\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/AdamTreasury.sol:220:13\n    │\n220 │             emit RewardsFlushed(token, balance);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:239:53\n    │\n239 │                 zeroForOne: true, amountSpecified: -int256(ethIn), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1\n    │                                                     ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' 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/AdamTreasury.sol:245:30\n    │\n245 │         if (ethDelta >= 0 || uint256(uint128(-ethDelta)) != ethIn) revert PartialFill();\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/AdamTreasury.sol:245:38\n    │\n245 │         if (ethDelta >= 0 || uint256(uint128(-ethDelta)) != ethIn) revert PartialFill();\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/AdamTreasury.sol:247:29\n    │\n247 │         uint256 amountOut = uint256(uint128(outDelta));\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/AdamTreasury.sol:247:37\n    │\n247 │         uint256 amountOut = uint256(uint128(outDelta));\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/AdamTreasury.sol:250:9\n    │\n250 │         poolManager.settle{value: ethIn}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/AdamTreasury.sol:280:16\n    │\n280 │         return (afterTax * (BPS - slippageBps)) / BPS;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamDistributor.sol:178:9\n    │\n178 │         emit Unstaked(msg.sender, amount);\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/AdamHook.sol:249:9\n    │\n249 │         emit FeeTaken(isBuy, ethAmount, fee, 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/AdamTreasury.sol:291:13\n    │\n291 │             emit TeamPaid(amount);\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/AdamTreasury.sol:294:13\n    │\n294 │             emit TeamPaymentDeferred(amount);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[return-bomb]: external call with a gas limit may copy unbounded return data\n    ╭▸ src/AdamTreasury.sol:289:22\n    │\n289 │         (bool ok,) = teamWallet.call{value: amount, gas: TEAM_PUSH_GAS}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:315:24\n    │\n315 │             } else if (block.timestamp >= uint256(l.failingSince) + LEG_FALLBACK_DELAY) {\n    │                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:314:34\n    │\n314 │                 l.failingSince = uint64(block.timestamp);\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/fuzz/Treasury.fuzz.t.sol:64:25\n   │\n64 │         uint256 clock = block.timestamp;\n   │                         ━━━━━━━━━━━━━━━\n   ‡\n71 │             vm.warp(clock);\n   │             ────────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:293:25\n    │\n293 │         assertEq(since, block.timestamp);\n    │                         ━━━━━━━━━━━━━━━\n    ‡\n296 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:296:17\n    │\n296 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:415:17\n    │\n415 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":1663,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/unit/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 80126, ~: 80373)\n[PASS] test_metadata() (gas: 24465)\n[PASS] test_mintsFixedSupplyToDeployer() (gas: 23278)\n[PASS] test_noMintOrAdminEntryPoints() (gas: 143123)\n[PASS] test_transferMovesExactAmount() (gas: 74094)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 34859)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 5.82ms (6.50ms CPU time)\n\nRan 19 tests for test/unit/AdamDistributor.t.sol:AdamDistributorTest\n[PASS] testFuzz_claimsNeverExceedDistributed(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 1217242, ~: 1248395)\n[PASS] testFuzz_stakeUnstakeRoundTrip(uint256,uint256) (runs: 256, μ: 219496, ~: 219778)\n[PASS] test_claimIsReentrancySafe() (gas: 1363700)\n[PASS] test_claimWithNothingIsANoop() (gas: 65484)\n[PASS] test_constructorState() (gas: 78111)\n[PASS] test_constructorValidation() (gas: 10329)\n[PASS] test_exitWithdrawsAndClaims() (gas: 421962)\n[PASS] test_extraExclusionIsApplied() (gas: 17863)\n[PASS] test_noOwnerOrAdminEntryPoints() (gas: 117926)\n[PASS] test_notifyRejectsUnknownTokenAndZero() (gas: 1127136)\n[PASS] test_notifyRequiresAllowance() (gas: 50735)\n[PASS] test_rewardsBeforeAnyStakeAreHeldThenFoldedIn() (gas: 371636)\n[PASS] test_rewardsOnlyAccrueWhileStaked() (gas: 617987)\n[PASS] test_rewardsProRataAcrossTwoTokens() (gas: 972593)\n[PASS] test_stakeAndUnstake() (gas: 243777)\n[PASS] test_stakeRejectsZeroAndExcluded() (gas: 204484)\n[PASS] test_tokensSentDirectlyAreNotStakedButRewardsFromBalanceDiff() (gas: 200548)\n[PASS] test_unstakeKeepsAccruedRewardsClaimable() (gas: 490402)\n[PASS] test_unstakeMoreThanStakedReverts() (gas: 192244)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 38.26ms (50.09ms CPU time)\n\nRan 20 tests for test/unit/AdamTreasury.t.sol:AdamTreasuryTest\n[PASS] test_anyoneCanProcess() (gas: 474873)\n[PASS] test_capPerCallLeavesRemainderUnsplit() (gas: 517735)\n[PASS] test_constructorValidation() (gas: 15276)\n[PASS] test_cooldownBetweenCalls() (gas: 851741)\n[PASS] test_distributorPendingRewardsFlowToStakersLater() (gas: 1318457)\n[PASS] test_flushRewardsForwardsStrandedTokens() (gas: 186540)\n[PASS] test_immutablesAndLegs() (gas: 84773)\n[PASS] test_legFailsWhenHookTaxExceedsAssumption_pendingIsKept() (gas: 778679)\n[PASS] test_legFailureEmitsReason() (gas: 445875)\n[PASS] test_legsReceiveEqualEth() (gas: 513916)\n[PASS] test_noWithdrawalPathForAnyone() (gas: 147108)\n[PASS] test_ownSwapImpactAboveSlippageIsRejected() (gas: 433807)\n[PASS] test_processRevertsWhenNothingToDo() (gas: 43969)\n[PASS] test_processSplitsTenNinetyAndBuysBothTokens() (gas: 583269)\n[PASS] test_quoteMinOutMatchesSpotMath() (gas: 64415)\n[PASS] test_rerouteAfterThreeDaysOfFailure() (gas: 961938)\n[PASS] test_rerouteWorksInBothDirections() (gas: 682453)\n[PASS] test_teamPushFailureIsDeferredNotBlocking() (gas: 839749)\n[PASS] test_teamWalletCannotReenterProcess() (gas: 2207507)\n[PASS] test_unlockCallbackIsGuarded() (gas: 63177)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 841.48ms (14.12ms CPU time)\n\nRan 2 tests for test/unit/Integration.t.sol:IntegrationTest\n[PASS] test_excludedAddressesEarnNothing() (gas: 934371)\n[PASS] test_fullLifecycle() (gas: 2037307)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 897.52ms (10.93ms CPU time)\n\nRan 4 tests for test/unit/Integration.t.sol:DeployWiringTest\n[PASS] test_contractsAreWiredTogether() (gas: 62158)\n[PASS] test_deployWithExistingToken() (gas: 53569001)\n[PASS] test_launchPoolRejectsMisconfiguration() (gas: 142197)\n[PASS] test_mainnetConfigConstants() (gas: 14465)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.26s (335.13ms CPU time)\n\nRan 22 tests for test/unit/AdamHook.t.sol:AdamHookTest\n[PASS] testFuzz_buyFeeMatchesSchedule(uint256,uint32) (runs: 256, μ: 196638, ~: 196692)\n[PASS] testFuzz_decayIsMonotoneAndBounded(uint32,uint32) (runs: 256, μ: 30773, ~: 30776)\n[PASS] test_addressCarriesExactlyTheRequiredFlags() (gas: 11946)\n[PASS] test_antiSnipeFeeAtLaunchIs20Percent() (gas: 177467)\n[PASS] test_buyExactInput_takes1_5PercentOfEthIn() (gas: 200398)\n[PASS] test_buyExactOutput_feeIsAddedOnTopOfEthIn() (gas: 202810)\n[PASS] test_callbacksRejectNonPoolManager() (gas: 90560)\n[PASS] test_constructorRejectsUnflaggedAddress() (gas: 12526)\n[PASS] test_decaySchedule() (gas: 57312)\n[PASS] test_feeCannotBeRaisedOrKept() (gas: 122115)\n[PASS] test_feeEventsAreEmitted() (gas: 189383)\n[PASS] test_initialState() (gas: 38026)\n[PASS] test_initializeOnlyOnce() (gas: 46245)\n[PASS] test_initializeRejectsWrongPair() (gas: 59387314)\n[PASS] test_initializeRequiresOwner() (gas: 59493715)\n[PASS] test_lowerFee() (gas: 64977)\n[PASS] test_lowerFeeDuringDecayKeepsDecaying() (gas: 54508)\n[PASS] test_lowerFeeToZeroDisablesFee() (gas: 213993)\n[PASS] test_onlyOwnerLowersFee() (gas: 35222)\n[PASS] test_sellExactInput_takes1_5PercentOfEthOut() (gas: 339796)\n[PASS] test_sellExactOutput_feeIsAddedOnTopOfEthOut() (gas: 318992)\n[PASS] test_twoStepOwnershipTransfer() (gas: 144539)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 1.40s (925.50ms CPU time)\n\nRan 4 tests for test/fuzz/Treasury.fuzz.t.sol:TreasuryFuzzTest\n[PASS] testFuzz_accountingIdentityHolds(uint256,uint16,uint8) (runs: 256, μ: 1062169, ~: 1001054)\n[PASS] testFuzz_capAndConservation(uint256) (runs: 256, μ: 520901, ~: 520917)\n[PASS] testFuzz_quoteIsLinear(uint256) (runs: 256, μ: 82293, ~: 82322)\n[PASS] testFuzz_splitIsExact(uint256) (runs: 256, μ: 505314, ~: 505420)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.40s (360.85ms CPU time)\n\nRan 7 test suites in 1.41s (5.85s CPU time): 77 tests passed, 0 failed, 0 skipped (77 total tests)\n","passed":true},{"durationMs":98,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"AdamDistributor.claim()\",\"AdamDistributor.exit()\",\"AdamDistributor.notifyReward(address,uint256)\",\"AdamDistributor.stake(uint256)\",\"AdamDistributor.unstake(uint256)\",\"AdamHook.acceptOwnership()\",\"AdamHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"AdamHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"AdamHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"AdamHook.lowerFee(uint16)\",\"AdamHook.renounceOwnership()\",\"AdamHook.transferOwnership(address)\",\"AdamTreasury.flushRewards()\",\"AdamTreasury.payTeam()\",\"AdamTreasury.process()\",\"AdamTreasury.receive()\",\"AdamTreasury.unlockCallback(bytes)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"foundry.toml\":32,\"launch.json\":58,\"remappings.txt\":9,\"script/DeployAdam.s.sol\":265,\"script/utils/HookMiner.sol\":60,\"src/AdamDistributor.sol\":213,\"src/AdamHook.sol\":251,\"src/AdamTreasury.sol\":325,\"src/LaunchToken.sol\":17,\"src/interfaces/IAdamDistributor.sol\":8,\"test/fork/MainnetFork.t.sol\":192,\"test/fuzz/Treasury.fuzz.t.sol\":101,\"test/unit/AdamDistributor.t.sol\":334,\"test/unit/AdamHook.t.sol\":271,\"test/unit/AdamTreasury.t.sol\":495,\"test/unit/Integration.t.sol\":159,\"test/unit/LaunchToken.t.sol\":64,\"test/utils/LocalV4.sol\":222,\"test/utils/MockTaxHook.sol\":129},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"d3b4d16d95bb06c4ed2831fed25afbbaa330d908b9825b4177a451148b18d1c3","verifiedTreeHash":"8c3175b4e30f622ea1b849ae53165c4471424817","verifierVersion":"0.1.0+41305fb5"},{"checks":[{"durationMs":88982,"exitCode":0,"name":"build","output":"Compiling 119 files with Solc 0.8.26\nSolc 0.8.26 finished in 88.79s\nCompiler run successful!\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamDistributor.sol:116:9\n    │\n116 │         emit Staked(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:148:97\n    │\n148 │                 || imdTaxBps >= BPS || pnkstrTaxBps >= BPS || imd == pnkstr || imdTickSpacing > uint24(type(int24).max)\n    │                                                                                                 ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint24' 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/AdamTreasury.sol:149:40\n    │\n149 │                 || pnkstrTickSpacing > uint24(type(int24).max)\n    │                                        ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint24' 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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamHook.sol:114:13\n    │\n114 │         if (elapsed >= DECAY_DURATION) return base;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:163:26\n    │\n163 │             tickSpacing: int24(imdTickSpacing),\n    │                          ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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/AdamTreasury.sol:171:26\n    │\n171 │             tickSpacing: int24(pnkstrTickSpacing),\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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/AdamHook.sol:168:31\n    │\n168 │             launchTimestamp = uint64(block.timestamp);\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamHook.sol:176:42\n    │\n176 │             params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamHook.sol:176:77\n    │\n176 │             params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamDistributor.sol:204:13\n    │\n204 │             emit RewardClaimed(account, token, amount);\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/AdamDistributor.sol:152:9\n    │\n152 │         emit RewardNotified(token, msg.sender, received, distributed);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_legs` is updated\n    ╭▸ src/AdamTreasury.sol:328:13\n    │\n328 │         try poolManager.unlock(abi.encode(legId, ethIn)) returns (bytes memory result) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamHook.sol:191:46\n    │\n191 │                 params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamHook.sol:191:81\n    │\n191 │                 params.amountSpecified < 0 ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\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/AdamHook.sol:194:47\n    │\n194 │                 params.amountSpecified < 0 ? -int256(requested - prepaidFee) : int256(requested + prepaidFee);\n    │                                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' 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/AdamHook.sol:194:80\n    │\n194 │                 params.amountSpecified < 0 ? -int256(requested - prepaidFee) : int256(requested + prepaidFee);\n    │                                                                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' 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/AdamHook.sol:199:44\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:199:52\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:199:74\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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/AdamHook.sol:199:82\n    │\n199 │         uint256 ethAmount = ethDelta < 0 ? uint256(uint128(-ethDelta)) : uint256(uint128(ethDelta));\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-no-eth]: external call can be reentered before `_legs` is updated\n    ╭▸ src/AdamTreasury.sol:335:13\n    │\n335 │             distributor.notifyReward(token, amountOut);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:195:13\n    │\n195 │         if (block.timestamp < availableAt) revert CooldownActive(availableAt);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamTreasury.sol:333:13\n    │\n333 │             emit LegBought(legId, token, ethIn, amountOut);\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/AdamTreasury.sol:337:13\n    │\n337 │             emit LegFailed(legId, ethIn, reason);\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/AdamTreasury.sol:351:17\n    │\n351 │                 emit LegRerouted(legId, other, moved);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:201:25\n    │\n201 │         lastProcessed = uint64(block.timestamp);\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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-eth]: uncapped ETH transfer can be reentered before `_status` is updated\n    ╭▸ src/AdamTreasury.sol:224:22\n    │\n224 │         (bool ok,) = teamWallet.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-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/AdamTreasury.sol:226:9\n    │\n226 │         emit TeamPaid(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/AdamTreasury.sol:236:13\n    │\n236 │             distributor.notifyReward(token, balance);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/AdamTreasury.sol:233:31\n    │\n233 │             uint256 balance = IERC20(token).balanceOf(address(this));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/AdamTreasury.sol:236:13\n    │\n236 │             distributor.notifyReward(token, balance);\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/AdamTreasury.sol:237:13\n    │\n237 │             emit RewardsFlushed(token, balance);\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/AdamDistributor.sol:191:9\n    │\n191 │         emit Unstaked(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamDistributor.sol:233:13\n    │\n233 │         if (elapsed > BACKLOG_DURATION) elapsed = BACKLOG_DURATION;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamDistributor.sol:255:77\n    │\n255 │                 backlogStream[token] = BacklogStream(unallocated[token], 0, uint64(block.timestamp));\n    │                                                                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamHook.sol:273:9\n    │\n273 │         emit FeeTaken(params.zeroForOne, ethAmount, fee, bps);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:256:53\n    │\n256 │                 zeroForOne: true, amountSpecified: -int256(ethIn), sqrtPriceLimitX96: TickMath.MIN_SQRT_PRICE + 1\n    │                                                     ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' 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/AdamTreasury.sol:262:30\n    │\n262 │         if (ethDelta >= 0 || uint256(uint128(-ethDelta)) != ethIn) revert PartialFill();\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/AdamTreasury.sol:262:38\n    │\n262 │         if (ethDelta >= 0 || uint256(uint128(-ethDelta)) != ethIn) revert PartialFill();\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/AdamTreasury.sol:264:29\n    │\n264 │         uint256 amountOut = uint256(uint128(outDelta));\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/AdamTreasury.sol:264:37\n    │\n264 │         uint256 amountOut = uint256(uint128(outDelta));\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/AdamTreasury.sol:267:9\n    │\n267 │         poolManager.settle{value: ethIn}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/AdamTreasury.sol:298:16\n    │\n298 │         return (afterTax * (BPS - slippageBps)) / BPS;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/AdamTreasury.sol:309:13\n    │\n309 │             emit TeamPaid(amount);\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/AdamTreasury.sol:312:13\n    │\n312 │             emit TeamPaymentDeferred(amount);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[return-bomb]: external call with a gas limit may copy unbounded return data\n    ╭▸ src/AdamTreasury.sol:307:22\n    │\n307 │         (bool ok,) = teamWallet.call{value: amount, gas: TEAM_PUSH_GAS}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:319:32\n    │\n319 │         if (l.failures != 0 && block.timestamp > uint256(l.lastFailure) + allowedGap) _resetFailures(l);\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/AdamTreasury.sol:345:47\n    │\n345 │             if (l.failures >= MIN_FAILURES && block.timestamp >= uint256(l.failingSince) + LEG_FALLBACK_DELAY) {\n    │                                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/AdamTreasury.sol:339:34\n    │\n339 │                 l.failingSince = uint64(block.timestamp);\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/AdamTreasury.sol:341:29\n    │\n341 │             l.lastFailure = uint64(block.timestamp);\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/fuzz/Treasury.fuzz.t.sol:64:25\n   │\n64 │         uint256 clock = block.timestamp;\n   │                         ━━━━━━━━━━━━━━━\n   ‡\n71 │             vm.warp(clock);\n   │             ────────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamDistributor.t.sol:369:25\n    │\n369 │         uint256 start = block.timestamp;\n    │                         ━━━━━━━━━━━━━━━\n370 │         assertEq(dist.earned(alice, address(imd)), 0, \"no instant jackpot at threshold\");\n371 │         vm.warp(start + 1 days);\n    │         ─────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamDistributor.t.sol:393:25\n    │\n393 │         uint256 start = block.timestamp;\n    │                         ━━━━━━━━━━━━━━━\n394 │         vm.warp(start + 1 days);\n    │         ─────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:243:34\n    │\n243 │         assertEq(l.failingSince, block.timestamp);\n    │                                  ━━━━━━━━━━━━━━━\n    ‡\n248 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ─────────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:248:17\n    │\n248 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:417:17\n    │\n417 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/AdamTreasury.t.sol:489:17\n    │\n489 │         vm.warp(block.timestamp + COOLDOWN);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/unit/Revision.t.sol:32:17\n   │\n32 │         vm.warp(block.timestamp + COOLDOWN);\n   │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/unit/Revision.t.sol:107:17\n    │\n107 │         vm.warp(block.timestamp + 7 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":1746,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/unit/LaunchToken.t.sol:LaunchTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 80151, ~: 80325)\n[PASS] test_metadata() (gas: 24465)\n[PASS] test_mintsFixedSupplyToDeployer() (gas: 23278)\n[PASS] test_noMintOrAdminEntryPoints() (gas: 143123)\n[PASS] test_transferMovesExactAmount() (gas: 74094)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 34859)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 4.12ms (7.11ms CPU time)\n\nRan 24 tests for test/unit/AdamDistributor.t.sol:AdamDistributorTest\n[PASS] testFuzz_backlogConservation(uint128,uint32) (runs: 256, μ: 585633, ~: 605008)\n[PASS] testFuzz_claimsNeverExceedDistributed(uint256,uint256,uint256,uint256,uint256) (runs: 256, μ: 1310304, ~: 1338705)\n[PASS] testFuzz_stakeUnstakeRoundTrip(uint256,uint256) (runs: 256, μ: 250767, ~: 251354)\n[PASS] test_backlogPausesBelowThresholdWithoutRetroactiveVesting() (gas: 907369)\n[PASS] test_backlogStreamsAndNewcomerCannotClaimPastTime() (gas: 1496660)\n[PASS] test_claimIsReentrancySafe() (gas: 1391380)\n[PASS] test_claimWithNothingIsANoop() (gas: 70106)\n[PASS] test_constructorState() (gas: 78331)\n[PASS] test_constructorValidation() (gas: 10329)\n[PASS] test_dustCannotCaptureBacklogEvenAfterWaiting() (gas: 792687)\n[PASS] test_exitAfterUnstakeStillClaims() (gas: 557146)\n[PASS] test_exitWithdrawsAndClaims() (gas: 452396)\n[PASS] test_extraExclusionIsApplied() (gas: 17907)\n[PASS] test_noOwnerOrAdminEntryPoints() (gas: 118344)\n[PASS] test_notifyRejectsUnknownTokenAndZero() (gas: 1127290)\n[PASS] test_notifyRequiresAllowance() (gas: 50801)\n[PASS] test_rewardsBeforeAnyStakeWaitForMeaningfulStake() (gas: 400286)\n[PASS] test_rewardsOnlyAccrueWhileStaked() (gas: 669298)\n[PASS] test_rewardsProRataAcrossTwoTokens() (gas: 1040157)\n[PASS] test_stakeAndUnstake() (gas: 275331)\n[PASS] test_stakeRejectsZeroAndExcluded() (gas: 204572)\n[PASS] test_tokensSentDirectlyAreNotStakedButRewardsFromBalanceDiff() (gas: 220431)\n[PASS] test_unstakeKeepsAccruedRewardsClaimable() (gas: 526684)\n[PASS] test_unstakeMoreThanStakedReverts() (gas: 207505)\nSuite result: ok. 24 passed; 0 failed; 0 skipped; finished in 38.94ms (78.52ms CPU time)\n\nRan 20 tests for test/unit/AdamTreasury.t.sol:AdamTreasuryTest\n[PASS] test_anyoneCanProcess() (gas: 493995)\n[PASS] test_capPerCallLeavesRemainderUnsplit() (gas: 546448)\n[PASS] test_constructorValidation() (gas: 15276)\n[PASS] test_cooldownBetweenCalls() (gas: 890008)\n[PASS] test_distributorPendingRewardsFlowToStakersLater() (gas: 1668526)\n[PASS] test_flushRewardsForwardsStrandedTokens() (gas: 188424)\n[PASS] test_immutablesAndLegs() (gas: 94419)\n[PASS] test_legFailsWhenHookTaxExceedsAssumption_pendingIsKept() (gas: 1057483)\n[PASS] test_legFailureEmitsReason() (gas: 483403)\n[PASS] test_legsReceiveEqualEth() (gas: 533038)\n[PASS] test_noWithdrawalPathForAnyone() (gas: 147438)\n[PASS] test_ownSwapImpactAboveSlippageIsRejected() (gas: 494486)\n[PASS] test_processRevertsWhenNothingToDo() (gas: 43991)\n[PASS] test_processSplitsTenNinetyAndBuysBothTokens() (gas: 616811)\n[PASS] test_quoteMinOutMatchesSpotMath() (gas: 69243)\n[PASS] test_rerouteAfterThreeDaysOfFailure() (gas: 10501328)\n[PASS] test_rerouteWorksInBothDirections() (gas: 5707518)\n[PASS] test_teamPushFailureIsDeferredNotBlocking() (gas: 858871)\n[PASS] test_teamWalletCannotReenterProcess() (gas: 2264891)\n[PASS] test_unlockCallbackIsGuarded() (gas: 63177)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 185.93ms (19.04ms CPU time)\n\nRan 4 tests for test/unit/PostFactoryHook.t.sol:PostFactoryHookTest\n[PASS] test_feesFromHookedPoolReachManifestedDistributor() (gas: 1322900)\n[PASS] test_hookOnlyDeploymentReusesManifestedPipeline() (gas: 65960)\n[PASS] test_launchPoolRejectsAllocationAboveHolding() (gas: 89595)\n[PASS] test_resolvePipelineRejectsMismatchedConfig() (gas: 250817)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 193.29ms (1.29ms CPU time)\n\nRan 2 tests for test/unit/Integration.t.sol:IntegrationTest\n[PASS] test_excludedAddressesEarnNothing() (gas: 1111838)\n[PASS] test_fullLifecycle() (gas: 2254150)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 196.51ms (2.22ms CPU time)\n\nRan 4 tests for test/fuzz/Treasury.fuzz.t.sol:TreasuryFuzzTest\n[PASS] testFuzz_accountingIdentityHolds(uint256,uint16,uint8) (runs: 256, μ: 1118923, ~: 1063074)\n[PASS] testFuzz_capAndConservation(uint256) (runs: 256, μ: 549603, ~: 549630)\n[PASS] testFuzz_quoteIsLinear(uint256) (runs: 256, μ: 91949, ~: 91978)\n[PASS] testFuzz_splitIsExact(uint256) (runs: 256, μ: 524471, ~: 524542)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 260.24ms (128.43ms CPU time)\n\nRan 11 tests for test/unit/Revision.t.sol:RevisionTest\n[PASS] testFuzz_exactOutputFeesAreGrossRate(uint32,uint128) (runs: 256, μ: 339400, ~: 339529)\n[PASS] test_constructorCheckpointAlsoProtectsFirstProcess() (gas: 641507)\n[PASS] test_dustWaitsWithoutStartingFailureStreak() (gas: 735279)\n[PASS] test_emptySellCannotChargeFeeOrStartClock() (gas: 372591)\n[PASS] test_exactOutputAndInputBuySameGrossFeeAtLaunch() (gas: 371741)\n[PASS] test_firstFilledSwapStartsFullDecayAfterDeploymentDelay() (gas: 223443)\n[PASS] test_hookAndTreasuryHaveNoStakingEntryPoint() (gas: 69673)\n[PASS] test_partialInputBuyRevertsAndReturnsEntireFee() (gas: 456031)\n[PASS] test_sandwichCannotLowerCheckpointFloor() (gas: 1640145)\n[PASS] test_thinPoolRecoversAtSmallerSize() (gas: 2347683)\n[PASS] test_twoIsolatedFailuresDoNotReroute() (gas: 959835)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 1.55s (1.40s CPU time)\n\nRan 4 tests for test/unit/Integration.t.sol:DeployWiringTest\n[PASS] test_contractsAreWiredTogether() (gas: 62290)\n[PASS] test_deployWithExistingToken() (gas: 460115604)\n[PASS] test_launchPoolRejectsMisconfiguration() (gas: 145489)\n[PASS] test_mainnetConfigConstants() (gas: 14612)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 1.55s (1.38s CPU time)\n\nRan 22 tests for test/unit/AdamHook.t.sol:AdamHookTest\n[PASS] testFuzz_buyFeeMatchesSchedule(uint256,uint32) (runs: 256, μ: 331277, ~: 331481)\n[PASS] testFuzz_decayIsMonotoneAndBounded(uint32,uint32) (runs: 256, μ: 196480, ~: 196467)\n[PASS] test_addressCarriesExactlyTheRequiredFlags() (gas: 11990)\n[PASS] test_antiSnipeFeeAtLaunchIs20Percent() (gas: 185840)\n[PASS] test_buyExactInput_takes1_5PercentOfEthIn() (gas: 344650)\n[PASS] test_buyExactOutput_feeIsAddedOnTopOfEthIn() (gas: 346566)\n[PASS] test_callbacksRejectNonPoolManager() (gas: 90662)\n[PASS] test_constructorRejectsUnflaggedAddress() (gas: 12526)\n[PASS] test_decaySchedule() (gas: 223238)\n[PASS] test_feeCannotBeRaisedOrKept() (gas: 122115)\n[PASS] test_feeEventsAreEmitted() (gas: 333635)\n[PASS] test_initialState() (gas: 43167)\n[PASS] test_initializeOnlyOnce() (gas: 46279)\n[PASS] test_initializeRejectsWrongPair() (gas: 6491850)\n[PASS] test_initializeRequiresOwner() (gas: 6602669)\n[PASS] test_lowerFee() (gas: 240682)\n[PASS] test_lowerFeeDuringDecayKeepsDecaying() (gas: 220132)\n[PASS] test_lowerFeeToZeroDisablesFee() (gas: 358245)\n[PASS] test_onlyOwnerLowersFee() (gas: 35222)\n[PASS] test_sellExactInput_takes1_5PercentOfEthOut() (gas: 487228)\n[PASS] test_sellExactOutput_feeIsAddedOnTopOfEthOut() (gas: 467067)\n[PASS] test_twoStepOwnershipTransfer() (gas: 144671)\nSuite result: ok. 22 passed; 0 failed; 0 skipped; finished in 1.55s (174.70ms CPU time)\n\nRan 9 test suites in 1.55s (5.52s CPU time): 97 tests passed, 0 failed, 0 skipped (97 total tests)\n","passed":true},{"durationMs":76,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"AdamDistributor.claim()\",\"AdamDistributor.exit()\",\"AdamDistributor.notifyReward(address,uint256)\",\"AdamDistributor.stake(uint256)\",\"AdamDistributor.unstake(uint256)\",\"AdamHook.acceptOwnership()\",\"AdamHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"AdamHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"AdamHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"AdamHook.lowerFee(uint16)\",\"AdamHook.renounceOwnership()\",\"AdamHook.transferOwnership(address)\",\"AdamTreasury.flushRewards()\",\"AdamTreasury.payTeam()\",\"AdamTreasury.process()\",\"AdamTreasury.receive()\",\"AdamTreasury.unlockCallback(bytes)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":83,\"SELF_AUDIT.md\":31,\"foundry.toml\":32,\"launch.json\":49,\"remappings.txt\":9,\"script/DeployAdam.s.sol\":308,\"script/utils/HookMiner.sol\":60,\"src/AdamDistributor.sol\":259,\"src/AdamHook.sol\":275,\"src/AdamTreasury.sol\":362,\"src/LaunchToken.sol\":17,\"src/interfaces/IAdamDistributor.sol\":8,\"test/fork/MainnetFork.t.sol\":234,\"test/fuzz/Treasury.fuzz.t.sol\":101,\"test/unit/AdamDistributor.t.sol\":423,\"test/unit/AdamHook.t.sol\":278,\"test/unit/AdamTreasury.t.sol\":500,\"test/unit/Integration.t.sol\":159,\"test/unit/LaunchToken.t.sol\":64,\"test/unit/PostFactoryHook.t.sol\":151,\"test/unit/Revision.t.sol\":197,\"test/utils/LocalV4.sol\":223,\"test/utils/MockTaxHook.sol\":129},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"e7f58dc73d9860d08e56e29288a9b7481beaffdf2c44c538b50bd29c14702f56","verifiedTreeHash":"a9c2a411692b94466197386232cd1f3aaf218e57","verifierVersion":"0.1.0+41305fb5"}]}