{"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":"99dca385-b7df-4cc0-b5b4-a70c64efd5f8","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"eab404a804c1877dff4c82ddabefc101009959c56097abd72aa8cc3d8e0013b4","dependsOn":["manifest"],"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":"9946c5f807a30ace7d33ed22eeda5e48744c509268910e7f08b2bf3aba692cc5","dependsOn":["manifest"],"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":"e3a478c918aa7ce23a96345f34b77d2645e647f9e44943139b330d4d5e1f864f","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","tools":[]},"key":"audit_imported_code","kind":"code","role":"review","skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","state":"accepted"},{"acceptedSubmissionHash":"9adca76e8abbb79807509405a7086de1bc5adf83d4ad270f366d62a3a9995271","dependsOn":["manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"47342ceb3e456dd99f48f1c681895e3febe4cf62a0c5bf7c80ee22b2e0f94f22","dependsOn":["manifest"],"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":"10bbffe6dca0241fb8d862263c78798d7afce55d6c341451c05208e873c95421","dependsOn":["manifest"],"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":"49e8039fd198a743bbbef22c6543e4d50b26733df4298d35aec4d56d74c42197","dependsOn":["audit_imported_code"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"}],"objective":"Token name: IMD Offsets. Token symbol: IMDO. Total supply 1,000,000,000 with 18 decimals. Chain id 11155111, paired with ETH, poolBps 8900. Ethereum mainnet (1) is the real target, deployed manually from this repo's script after this launch.\n\nWHAT THIS IS. The workspace is github.com/ToknWrks/imdo at the given commit: the COMPLETE IMDO flywheel repository (Foundry; solc 0.8.26, cancun, via_ir, bytecode_hash \"none\"; vendored lib/). A fork of the MIT-licensed ADAM repository (IMD launch 713), credited in the README. Lineage: swarm job 948f8b1b-a4bd-4689-a346-2536b218e486 built it (build, tests, manifest, four reviews accepted; judge findings applied), review job 37ac5d94-da10-4a27-84d9-246c17a2c3f7 applied, and the Base retirement rail under src/regen reviewed by jobs 9091d583, e9b9f179 and b5fa7968 (all findings applied; docs/REGEN_RAIL.md). This launch is the IdentityMD hackathon entry: launch through the IMD protocol on Sepolia with admission passed before 19 Oct 2026 00:00 UTC.\n\nCONTRACTS (src/, written and tested; do not rewrite). IMDOToken = LaunchToken, constructor(), no owner, mint or tax. ImdoHook: v4 hook for one ETH/IMDO pool (LP fee 0, spacing 60); ETH-side fee 20% flat until the owner calls open() once, then decays over 30 minutes to 1.5%; owner can only lower; fee paid to the immutable treasury inside the swap, or as ERC-6909 claims redeemable only to the treasury; NOT in the manifest. ImdoStaking: FLAT constructor(address imdo, address imd, address manager, address claim, address regenSafe); 24-hour lock reset by every stake, stakeFor only by claim, pro-rata IMD rewards, notifyRegen() payable lifetime ETH credit, withdrawRegen only by regenSafe and never beyond what was notified. ImdoTreasury: FLAT constructor(address staking, address opsWallet, address offsetsSafe, address regenSafe, address manager, address imd, uint24 imdFee, uint24 imdTickSpacing, uint256 maxEthPerBuy, uint16 slippageBps, uint32 cooldown, uint256 regenCap, uint256 regenCapMin, uint256 regenCapMax); no owner or withdrawal; anyone calls process() every 600 s for a 50 bps bounty; net split 1000/2500/2500/4000 bps to opsWallet, offsetsSafe, REGEN (capped per 7-day epoch, overflow to IMD) and an IMD buy on the ETH/IMD v4 pool fee 10000 spacing 200 pushed to staking; setRegenCap by regenSafe within bounds is the ONLY setter. ImdoClaim: constructor(address imdo, address staking, address seatNFT, uint256 seatSize, bytes32 holderRoot, uint256 launch); 100,000,000 IMDO to identity.md seats [0,2000) via claimSeat (ownerOf), 10,000,000 to a Merkle holder list; ten daily tranches, closes at launch + 39 days, unclaimed burns. src/RegenDrop.sol and src/regen/* are Base-chain contracts deployed by script/DeployRegenRail.s.sol: NOT part of this launch. script/DeployImdo.s.sol is the manual mainnet deployment (docs/DEPLOYMENT.md).\n\nMANIFEST. launch.json is in the repo and is the manifest: kind evm_project; token LaunchToken (IMDOToken inherits it, no constructor arguments); contracts in order ImdoStaking (5 FLAT constructorArgs) then ImdoTreasury (14 FLAT constructorArgs; the IMD leg's no-hook pool key is a constant inside the contract, there is no hooks argument), values $token, $owner, $contract:ImdoStaking or lowercase literals (manager 0x000000000004444c5dc75cb358380d2e3de08a90, IMD 0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7, 10000, 200, 1 ETH, 300, 600, 0.5 ETH, 0.05 ETH, 5 ETH); later contracts cannot be referenced, so claim, regenSafe, offsetsSafe and opsWallet are $owner and the notes say what that address can do. Pool fee 3000, spacing 60. Known and intended: the manager and IMD literals have no code on Sepolia, so process() reverts PoolUnavailable there; the Sepolia deployment is the admission artefact and must not be funded. A previous launch job (4ad1a4d9-3aab-4e94-b151-e8f609414a4e) stopped at the manifest on three policy points, all fixed at this commit: no zero-address constructor argument, lowercase addresses, notes under 4,000 characters. Verify the manifest encodes exactly these constructors (flat, 5 and 14 values) and that every admission check (provenance, findings, independent review, bytecode, manifest, invariants, economics) can pass from this tree; if the factory needs a manifest change, change ONLY launch.json and say why.\n\nRULES. Do not modify src/, script/, foundry.toml, remappings.txt, lib/, snapshot/ or test/; scratch tests only under test/scratch/. Build with foundry.toml as committed (README quotes it; never suggest --no-metadata). Run the default profile suite and forge fmt --check and report the counts you observe (test/fork needs an RPC and is excluded by design). Findings need severity, file:line, a reproduction and where possible a failing Foundry proof. Known and documented: a sell into the fully emptied single-sided pool fills 1 wei for 0 ETH and moves slot0's tick to the seller's limit until the next buy (cosmetic; a deploy test covers it). Do not claim an audit was performed; the README says the swarm's reviews are checks. Any copy uses \"regenerative contribution\", never yield, APY, returns, investment, carbon offset or net zero.\n\nDELIVERY. The launch record, the Sepolia deployment of the manifest contracts and factory pool, and the review findings. The code is complete; nothing else is expected.","parentJobId":null,"planHash":"980cb30778e4e9571bd747d890a4321f373316dbe74026fe520e0355aab89b4e","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"99dca385-b7df-4cc0-b5b4-a70c64efd5f8","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1579-imd-offsets-token-symbol-imdo-total-supp"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"52286","feedbackHash":"ce7c2743fb90f26f968629a03c8708b7722d5f549271a5f91ba92090a5dc8e65","nodeKey":"audit_economics","submissionHash":"eab404a804c1877dff4c82ddabefc101009959c56097abd72aa8cc3d8e0013b4","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52270","feedbackHash":"a77e804d91fa7265a1d5b2f55322915a211643a56fc584352d38d3b2d1c3e294","nodeKey":"audit_flow","submissionHash":"9946c5f807a30ace7d33ed22eeda5e48744c509268910e7f08b2bf3aba692cc5","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51298","feedbackHash":"978b4bc908a9debe6095adf454c68368e08f7110c7393b3fb3429eb4e029f3c8","nodeKey":"audit_imported_code","submissionHash":"e3a478c918aa7ce23a96345f34b77d2645e647f9e44943139b330d4d5e1f864f","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52337","feedbackHash":"2bb6ce38ab63d425bd40f34ca8aff96fef36eec3fee917069f547107a29baf37","nodeKey":"audit_judge","submissionHash":"9adca76e8abbb79807509405a7086de1bc5adf83d4ad270f366d62a3a9995271","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51451","feedbackHash":"d9ee9775d8a37e3bb2e4ef4cd8804e0e68954cedd217c0d35e1257d7d6641619","nodeKey":"audit_math","submissionHash":"47342ceb3e456dd99f48f1c681895e3febe4cf62a0c5bf7c80ee22b2e0f94f22","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52665","feedbackHash":"d26a482bd04fe7ca452ff2026c7ba8d80f460c02215eaeb957d771525b5d0ae3","nodeKey":"audit_permissions","submissionHash":"10bbffe6dca0241fb8d862263c78798d7afce55d6c341451c05208e873c95421","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"53925","feedbackHash":"2f21cbf215f2d7885ac016a8eb04b3f974bd70fc4986b179bacff4625b1ef265","nodeKey":"manifest","submissionHash":"49e8039fd198a743bbbef22c6543e4d50b26733df4298d35aec4d56d74c42197","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"92c2c4e5212c57d14cee63875bb112c35ae3c5845cab8bfcb60da761ecd37b82","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"0ef0cddb99617930","findings":[{"citation":"resolved","description":"Trust-gap seam (unprivileged actor x economics x asymmetry). The IMD leg's only sandwich protection is the checkpoint floor: quoteMinOut (src/ImdoTreasury.sol:285-293) quotes from max(spot, decayed checkpoint) minus 300 bps, and _refreshCheckpoint only ever moves the reference up, by at most 200 bps of sqrt-price per full 1 ETH buy. The reference itself is seeded once, in the constructor, from the ETH/IMD pool's slot0 at the moment the deployment transaction executes (line 154); on mainnet the script's preflight requires that pool to exist, so this constructor read is the seed for the life of the contract (the _ensurePool fallback at line 400 is unreachable there). That read is a plain spot read of a 1% no-hook pool that anyone can trade in the same block. A searcher who sees the deployer's DeployImdo broadcast in the public mempool buys IMD immediately before the ImdoTreasury creation (sqrt-price of IMD per ETH falls), lets the constructor record the pushed value, and sells back after it. Nothing in the constructor or the script compares the seeded checkpoint with an expected tick, and the floor can only recover by +2% sqrt per honest full-size buy (about 9 consecutive honest 1 ETH buys for a 16.5% sqrt push, more for smaller buys), while it keeps decaying on the 7-day hyperbola in between. During that window every process() buy can be pushed to the seeded level and still clears the 3% slippage check, so stakers receive materially less IMD per ETH than the design's floor is meant to guarantee. This is bounded (the 7-day decay already exposes a similar magnitude after about a day without an upward refresh) and the attacker pays two 1% pool fees on the push capital, so it is a griefing/weakened-guarantee defect rather than direct theft; severity low. Suggested fix, without changing any constructor argument count: have the script's preflight (or a post-deploy check in docs/DEPLOYMENT.md) compare treasury.leg(0).checkpointSqrtPriceX96 with the expected IMD tick and abort or redeploy on a deviation, and/or submit the deployment through a private relay; the README should name the seed as a spot read.","line":154,"path":"src/ImdoTreasury.sol","reproduction":"Setup from test/utils/LocalV4.sol (ETH/IMD pool at tick 54000 with 200 ETH full-range liquidity, fee 10000). (1) Attacker swaps 40 ETH -> IMD on the IMD pool: sqrtPriceX96 falls from 1178734673953441494749930375074 to 983918759560468693585174144921 (16.5% in sqrt, about 30% in price). (2) ImdoTreasury is constructed with the manifest's 14 arguments: leg(0).checkpointSqrtPriceX96 == 983918759560468693585174144921 (the pushed value), whereas the treasury constructed before the push holds 1178734673953441494749930375074. (3) Attacker sells the IMD back; spot returns to within 3% of the honest seed. (4) Both treasuries receive 2 ETH; the attacker pushes again with 40 ETH and calls process() on each in the same block. Expected: both refuse the buy, since the pool is 30% off its reference. Actual: the honestly seeded treasury emits LegFailed(InsufficientOutput(amountOut 120804758837168454104, minOut 169387700000000000000 approx)) and keeps pending 0.796 ETH, while the front-run-seeded treasury emits LegBought(ethIn 0.796 ETH, amountOut 120804758837168454104), i.e. pays 0.796 ETH for about 29% less IMD than the honest reference requires, and its pending drops to 0. The scratch test under test/scratch/SeedFrontrun.t.sol passes on this code and shows exactly that divergence.","severity":"low","snippet":"            (_imdLeg.checkpointSqrtPriceX96,,,) = poolManager.getSlot0(_imdLeg.key.toId());","title":"Treasury checkpoint floor is seeded from an unprotected spot read in the deployment transaction; a front-runner seeds a low floor that later lets a sandwiched buy pass the min-out check an honestly se"},{"citation":"resolved","description":"The assignment text for this review says 'Pool fee 3000, spacing 60' for the IMDO launch, while launch.json writes fee 12500 and its notes call that 'this launch's policy values'. Per the launch reference, on a project launch the factory opens the pool at the chain's LaunchFees fee whatever pool.fee says, and the manifest should carry the fee the manifest assignment names. Only one of 3000 and 12500 can be the named value. This is a documentation/consistency point for the manifest owner, not a code defect: the constructors (5 and 14 flat arguments) are encoded correctly, addresses are lowercase, notes are 3984 characters (under the 4000 limit), and the mainnet script uses its own LP fee 0 for the hook pool regardless.","line":42,"path":"launch.json","reproduction":"Read launch.json line 42: \"fee\": 12500. Compare with the assignment: 'Pool fee 3000, spacing 60'. Expected: the two agree on the named pool fee. Actual: 12500 vs 3000. No on-chain input triggers a failure; if admission's manifest/policy compatibility check compares pool.fee with a brief-stated 3000, the manifest step would need a one-field change to launch.json.","severity":"info","snippet":"    \"fee\": 12500,","title":"launch.json pool.fee is 12500 while this assignment states the pool fee as 3000; the factory ignores pool.fee for a project launch, so there is no deployment effect, but the manifest and the brief dis"},{"citation":"resolved","description":"The README's manifest paragraph states that launch.json lists `IMDOToken`; launch.json token.contract is `LaunchToken` (IMDOToken is the script-only alias that inherits it unchanged, as the manifest notes say). The manifest is correct for the factory, which expects the token contract to be LaunchToken with no constructor arguments; only the README sentence is stale. Copy-only.","line":80,"path":"README.md","reproduction":"Compare README.md line 80 ('It lists `IMDOToken`') with launch.json lines 3-8 (\"contract\": \"LaunchToken\"). Expected: the README names the same contract as the manifest. Actual: README says IMDOToken, manifest says LaunchToken.","severity":"info","snippet":"It lists `IMDOToken`, then flat-constructor `ImdoStaking` (5 arguments), then `ImdoTreasur","title":"README says the manifest lists IMDOToken, but launch.json's token contract is LaunchToken"}],"hash":"10bbffe6dca0241fb8d862263c78798d7afce55d6c341451c05208e873c95421","nodeId":"505f70f0-49ab-40ab-8f3a-6250989f7b9d","outcome":"completed","summary":"The review is complete and the findings file is in place. Nothing in the repository was modified apart from the findings file and a scratch test under test/scratch, which is excluded from submission.\n\n**Outcome**\n\nThe permission model of the two manifest contracts, ImdoStaking and ImdoTreasury, holds. Every role gate I traced does what the README's trust table says, the stakeFor lock reset and the pre-open 20% hook fee are already recorded in the manifest notes as imported findings, and the manifest encodes the constructors correctly. The default suite and the formatter are clean.\n\n| Check | Result |\n|---|---|\n| forge test (default profile) | 207 passed, 0 failed, 0 skipped |\n| forge fmt --check | clean |\n| launch.json | 5 and 14 flat args in declaration order, lowercase addresses, notes 3984 chars |\n| IMD and PoolManager literals | code on mainnet (symbol IMD), no code on Sepolia |\n\n**Findings written to .imd-findings.json**\n\n- **Low, src/ImdoTreasury.sol:154.** The treasury's checkpoint floor, its only sandwich defence, is seeded from a plain spot read of the ETH/IMD pool inside the deployment transaction. A front-runner who pushes the pool before that transaction seeds a low floor. In the local harness a 40 ETH push seeds a floor 16.5% lower in sqrt terms. An honestly seeded treasury then refuses the same sandwich with InsufficientOutput, while the front-run-seeded one buys 0.796 ETH of IMD for about 29% less than the honest reference requires. The floor only recovers by 2% per honest full-size buy. The scratch test reproduces the divergence. Suggested fix is a preflight or post-deploy comparison of the seeded checkpoint against the expected tick, or a private relay for the deployment.\n- **Info, launch.json:42.** pool.fee is 12500 while this assignment states 3000. The factory opens a project pool at the chain's LaunchFees fee regardless, so there is no deployment effect, but the manifest owner should confirm which value admission expects.\n- **Info, README.md:80.** The README says the manifest lists IMDOToken; launch.json's token contract is LaunchToken. Copy only.\n\n**Coverage**\n\nAll 51 listed entry points have a row. Fifty hold, ImdoTreasury.process references finding 1, and two extra rows record the manifest encoding check and the suite counts. The Base rail contracts under src/regen were read for access control only, as the assignment places them outside this launch.","treeHash":null,"usage":{"cachedInputTokens":2575250,"inputTokens":610,"model":"claude-fable-5-1","outputTokens":49633,"runtime":"claude","turns":38,"wallClockMs":1099855}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"79373c79d1351eba","findings":[{"citation":"resolved","description":"Seam: boundary x invariant. The rise bound is scaled by ethIn / maxEthPerBuy (so the dust-pin regression in test/unit/CheckpointRepeatedPins.t.sol is closed), but a pin made with a FULL buy (ethIn == maxEthPerBuy, weightBps == BPS) still earns the whole MAX_CHECKPOINT_RISE_BPS step each cooldown, and the step compounds on the previous checkpoint because `next >= l.checkpointSqrtPriceX96` re-anchors it (line 372-374). The floor decays only ~0.1% of sqrt-price per 600 s (line 349-353), so N full pins lift the reference by ~1.02^N while the decay takes 7 days * (1.02^N / 1.0153 - 1) to bring it back under spot / (1 - slippage). The invariant the README states for this code path ('repeated dust buys at a pushed price cannot ratchet the floor above spot') only holds at the dust boundary; at the full-size boundary the ratchet is unbounded in N. Funds are never lost (ETH waits as `_imdLeg.pending`, and the treasury's twelve pinned buys fill at a price better than market), so this is griefing with a positive attacker cost, not theft; it is reported because the weight scaling is presented as closing the ratchet and it does not. Concrete numbers from a scratch run against this tree (200 ETH deep ETH/IMD pool at tick 54000, fee 10000, spacing 200, default treasury arguments): after the honest seed buy, twelve rounds of {warp +600 s; sell IMD with a price limit at 103% of the current floor; fund the treasury with 2.52 ETH; process(); buy back to market}. Result: checkpoint / original = 12532 bps, checkpoint / market = 12557 bps (sqrt), i.e. the floor is 1.577x market in price. With the pool back at market and 0.01 ETH funded every cooldown, the next 238 consecutive process() calls fail the IMD leg (LegFailed with InsufficientOutput), 39 hours, before a buy fills. Attacker cost in the run: -4214 IMD and +9.15 ETH, roughly 10 ETH net at the pool's ~221 IMD/ETH, so about 0.8 ETH per pin. A deeper pool makes each pin cheaper in fees and price impact, not dearer. No proof file is supplied (severity low); the reproduction below is a complete Foundry test that passes on this tree and prints the numbers.","line":369,"path":"src/ImdoTreasury.sol","reproduction":"State: fresh PoolManager; ETH/IMD pool (fee 10000, spacing 200, no hook) initialized at tick 54000 and seeded with 200 ETH full-range; ImdoStaking with 1e18 IMDO staked by alice; ImdoTreasury(staking, ops, offsets, regenSafe, manager, imd, 10000, 200, 1 ether, 300, 600, 0.5 ether, 0.05 ether, 5 ether). Steps: (1) send 1 ETH to the treasury, call process() -> checkpoint cp0 = spot. (2) Repeat 12 times: vm.warp(+600); target = floorNow * 103 / 100 where floorNow = cp * 7d / (7d + age); if target > spot, swap IMD->ETH (oneForZero, amountSpecified -1e30, sqrtPriceLimitX96 = target); send 2.52 ETH to the treasury; process() (the 1 ETH buy fills: staking's IMD balance rises); swap ETH->IMD back down to the original market sqrt-price. (3) Read leg(0).checkpointSqrtPriceX96: expected (README) at most a few percent above market and recovering within a day; actual 12557 bps of market sqrt-price. (4) With the pool at market, loop: warp +600, fund 0.01 ETH, process(): expected a fill within the ~1 day the dust-pin test asserts; actual 238 consecutive failures (39 h) before the first fill. Scratch file used: test/scratch/MathProbe.t.sol, test_probe_fullSizePinsRatchet (passes; the numbers are its logs).","severity":"low","snippet":"            uint256 ceilNow = floorNow + FullMath.mulDiv(floorNow, MAX_CHECKPOINT_RISE_BPS * weightBps, BPS * BPS);","title":"Full-size pinned buys ratchet the IMD checkpoint floor 2% (sqrt) per cooldown; twelve pins in two hours stall the IMD leg for 39 hours"},{"citation":"resolved","description":"Seam: boundary x invariant. `cap = maxEthPerBuy * BPS * BPS / (IMD_BPS * (BPS - KEEPER_BPS))` (line 174) is derived so that the imd share of one capped intake is exactly maxEthPerBuy (with the defaults: cap = 2_512_562_814_070_351_758 wei, bounty 12_562_814_070_351_758, net 2.5e18, imd = 2.5e18 - 0.25e18 - 0.625e18 - 0.625e18 = 1e18). `_executeImd` then buys at most maxEthPerBuy per call (line 327-328). The REGEN leg's overflow (regen - accepted, up to the full 0.625 ETH once `regenAccrued[epoch] >= regenCap`) is credited to the same `pending` on line 193, so under sustained inflow at the cap each process() adds up to 1.625 ETH to `pending` and removes 1 ETH: the backlog grows by 0.625 ETH per cooldown with no bound other than the inflow itself, and it is never drained faster than one maxEthPerBuy per 600 s. With the regenCap at its minimum (0.05 ETH) the overflow is 0.575 ETH per process from the first call of each epoch. Funds are held, not lost, and the README already says additional ETH 'stays unsplit', but the pending backlog is a second, undocumented accumulator that the cap formula was written to prevent. Observed in the scratch run on this tree (100 ETH funded, process() every 600 s, no trading): pending after rounds 0..3 = 0.125, 0.750, 1.375, 2.000 ETH (buys filling), then 3.625 ETH at round 4 when the treasury's own price impact in the unarbitraged 200 ETH pool pushes spot under the floor and the buy fails (that part is the documented 'held until the decay catches up' cost); unsplit ETH fell by exactly 2.5126 ETH per round. Expected from the cap formula: pending at most the dust left after a full buy (< 1 gwei) plus the overflow of one call; actual: a monotone +0.625 ETH per round while intake stays at the cap. Severity low: no loss, and reaching the cap needs about 360 ETH of fees per day.","line":193,"path":"src/ImdoTreasury.sol","reproduction":"State: same local v4 setup as finding 1 (200 ETH deep ETH/IMD pool, default treasury arguments, 1e18 IMDO staked). Steps: send 100 ETH to the treasury; then 4 times {vm.warp(+600); treasury.process(); read treasury.leg(0).pending}. Expected (per the cap formula at line 174, one capped intake == one full buy): pending stays below 1 gwei plus one call's overflow, i.e. at most ~0.625 ETH and not growing. Actual: 125000000000000000, 750000000000000000, 1375000000000000000, 2000000000000000000 wei after rounds 0, 1, 2, 3 (+0.625 ETH each round while every buy fills), with unsplitEth() = 97.487, 94.975, 92.462, 89.950 ETH. Scratch file used: test/scratch/MathProbe.t.sol, test_probe_pendingGrowthAtCap (passes; the numbers are its logs).","severity":"low","snippet":"            _imdLeg.pending += imd + overflow;","title":"At the intake cap the IMD leg's pending ETH grows by 0.625 ETH per process() once the REGEN epoch cap is used, because the cap is sized for the imd share alone and the regen overflow is added on top"}],"hash":"47342ceb3e456dd99f48f1c681895e3febe4cf62a0c5bf7c80ee22b2e0f94f22","nodeId":"a67f0adc-5d7d-4b36-b730-cd221fa8f227","outcome":"completed","summary":"The findings file is valid: both snippets match the tree at the cited lines, and all 51 verifier-listed entry points have a coverage row (plus four invariant/note rows). The scratch test under test/scratch/ is mine and nothing else in the tree was changed.\n\n**Recap**\n\nScope was the math of the IMDO launch repository against Pashov's Math Precision, Boundary and Numerical Gap guides. The default suite and formatter on this tree:\n\n| Check | Result |\n|---|---|\n| forge test (default profile, 30 suites) | 207 passed, 0 failed, 0 skipped |\n| forge fmt --check | clean |\n\n**What I found** (both low, written to `.imd-findings.json` with concrete reproductions):\n\n- **Full-size pinned buys ratchet the IMD checkpoint floor.** The rise bound scales by ethIn / maxEthPerBuy, which closes the dust-pin regression, but a full 1 ETH buy at a pushed price still earns the whole 2% sqrt step each cooldown and compounds. In a scratch run, twelve pins over two hours put the floor at 1.577x market price and the IMD leg then failed 238 consecutive process() calls, 39 hours, before filling. Attacker cost was about 10 ETH; funds are held, not lost.\n- **Pending IMD-leg ETH grows under sustained inflow at the intake cap.** The cap formula sizes the imd share to exactly maxEthPerBuy, but the REGEN overflow is added on top, so once the epoch cap is used each process() adds up to 1.625 ETH and buys only 1 ETH. Observed +0.625 ETH per round. No loss, and it needs roughly 360 ETH of fees per day to reach.\n\n**What held.** The hook's fee arithmetic (exact-in bps/BPS, exact-out gross-up bps/(BPS−bps), partial-fill refusal, decay formula boundaries), the staking accumulator (2^128 scaling, backlog stream monotonicity, no underflow paths), the claim tranche math, the treasury split (exact at the cap, dust to IMD), the quote formula, and the Base rail's router arithmetic all traced clean. One entry point is marked unreached: the registry's bech32 and name-key loops. The self-impact stall seen in probe 1 (the treasury's own buys pushing spot under the floor in an unarbitraged pool) is already documented in the README as held-not-lost, so it is noted in coverage rather than reported.\n\nNo proof files were attached since neither finding is critical or high.","treeHash":null,"usage":{"cachedInputTokens":1796304,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":43408,"runtime":"claude","turns":27,"wallClockMs":838127}},{"artifacts":[],"attempt":2,"bundleHash":"c62b6b5d381aa58a66e7a93dc5cc9394e9e2f0a5069a39438959f83076f90c50","device":"96ecbe9236c400bb","findings":[],"hash":"49e8039fd198a743bbbef22c6543e4d50b26733df4298d35aec4d56d74c42197","nodeId":"26a9b5b2-5940-45b1-b9a5-d8f11bc2d13a","outcome":"completed","summary":"The manifest is done and validated. Only launch.json changed.\n\n**What I changed in launch.json and why**\n- pool.fee went from 3000 to 12500. The launch policy fixes the project-pool fee at 1.25% and admission refuses any other value, so the committed 3000 would have failed the manifest check.\n- Notes rewritten to stay under 4,000 characters, state the policy pool values, record the four imported audit findings with their reproduction status, and record the check counts.\n- Everything else is unchanged: kind evm_project, token LaunchToken (IMDO, 18 decimals), ImdoStaking with 5 flat arguments, ImdoTreasury with 14 flat arguments, all literals lowercase, no zero address in any constructor argument.\n\n**Constructor verification against the accepted tree**\n- ImdoStaking at src/ImdoStaking.sol:79 takes (imdo, imd, manager, claim, regenSafe). Manifest gives $token, IMD, manager, $owner, $owner.\n- ImdoTreasury at src/ImdoTreasury.sol:109 takes 14 parameters ending in regenCap, regenCapMin, regenCapMax, with no hooks argument. Manifest gives $contract:ImdoStaking, three $owner roles, manager, IMD, 10000, 200, 1 ETH, 300, 600, 0.5 ETH, 0.05 ETH, 5 ETH.\n- LaunchToken has a no-argument constructor. IMDOToken inherits it unchanged.\n\n**Checks run**\n\n| Check | Result |\n|---|---|\n| forge build | exit 0 |\n| forge test (default profile) | 207 passed, 0 failed, 0 skipped |\n| forge fmt --check | clean on the committed tree |\n| test/fork | excluded by design, needs an RPC |\n\n**Imported audit findings**\nAll four reproduce on the unchanged code. None could be fixed in this task because the write budget is launch.json only. Each is recorded in the notes.\n- Medium, src/ImdoStaking.sol:168. The supplied proof fails with StakeLocked, confirming the claim role can keep a beneficiary locked by staking 1 wei daily. With claim set to $owner this is a live power of the unfunded Sepolia artefact. The mainnet path closes it because ImdoClaim stakes only for msg.sender.\n- Low, src/ImdoHook.sol:108. The supplied proof fails with the fee stuck at 2000 after renounce. Script-only contract, outside the manifest.\n- Low, script/DeployImdo.s.sol:176. Confirmed by reading: preflight never rejects a zero holderRoot. Outside the manifest.\n- Info, docs/REGEN_RAIL.md:51 still says \"round-2 audit\". Confirmed by grep. Copy-only, outside the manifest.\n\nA source fix for the medium finding, overriding stakeFor so it cannot lengthen an existing lock, belongs to a follow-up task with src/ in its write budget.","treeHash":"4442695d69788e3b0316f8d9cd9e0a2226796423","usage":{"cachedInputTokens":618334,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":7862,"runtime":"claude","turns":11,"wallClockMs":324934}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"165e283901cb9619","findings":[],"hash":"7f39006720840790041e0425af9aa7f8dc5daaf55f666ee39400969e9dfd6cac","nodeId":"26a9b5b2-5940-45b1-b9a5-d8f11bc2d13a","outcome":"failed","summary":"workspace routing discovery timed out","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":"gpt-6-astra","outputTokens":0,"runtime":"codex","turns":5,"wallClockMs":1336344}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"f9a12c57a094ca8d","findings":[{"citation":"resolved","description":"postRoot (src/RegenDrop.sol:55) and claim (src/RegenDrop.sol:85) both call _activate() first, so a pending root whose pendingActiveAt has passed is live for them: a claimant can be paid from it in the same block. cancelPending (src/RegenDrop.sol:69) does not call _activate() before checking pendingActiveAt, so in that same state the safe can still delete the root. The contract's own rule (\"a root only becomes claimable ACTIVATION_DELAY after it is posted\"; cancelPending \"drops the waiting root; the live root keeps working\") is therefore order-dependent after the delay: whichever of claim/activate or cancelPending lands first decides whether the root exists. Impact is bounded because the safe is the trusted leg of the rail and could post a different root anyway, and no tokens leave the contract; but a claimant holding a valid proof for a due root can have it voided after the point at which the code says it is claimable. Base-chain contract outside the manifest (deployed by script/DeployRegenRail.s.sol). Fix: call _activate() at the top of cancelPending, as postRoot does, so a due root is live before the pending check.","line":71,"path":"src/RegenDrop.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\";\nimport {RegenDrop} from \"src/RegenDrop.sol\";\n\ncontract MintableToken is ERC20 {\n    constructor() ERC20(\"axlREGEN\", \"REGEN\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\n/// @dev A root whose activation delay has elapsed is live for `postRoot` and `claim` (both call `_activate` first),\n///      but `cancelPending` does not activate first, so the safe can still revoke it after it became claimable.\n///      Fails on the current code: cancelPending succeeds where NoPendingRoot is expected. Passes once cancelPending\n///      activates a due root before checking for a pending one.\ncontract RegenDropCancelDueTest is Test {\n    MintableToken token;\n    RegenDrop drop;\n    address safe = makeAddr(\"safe\");\n    address alice = makeAddr(\"alice\");\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        token = new MintableToken();\n        drop = new RegenDrop(address(token), safe);\n        token.mint(address(drop), 100e18);\n    }\n\n    function _leaf(address a, uint256 c) internal pure returns (bytes32) {\n        return keccak256(bytes.concat(keccak256(abi.encode(a, c))));\n    }\n\n    function test_cancelAfterDelayCannotRevokeClaimableRoot() public {\n        // Single-leaf tree: root == leaf, empty proof.\n        bytes32 root = _leaf(alice, 10e18);\n        vm.prank(safe);\n        drop.postRoot(root, 10e18);\n        vm.warp(block.timestamp + drop.ACTIVATION_DELAY() + 1);\n        // The root is due: a claim in this very block would pay alice 10e18.\n        uint256 snap = vm.snapshotState();\n        bytes32[] memory proof;\n        drop.claim(alice, 10e18, proof);\n        assertEq(token.balanceOf(alice), 10e18);\n        vm.revertToState(snap);\n        // Same state, same block: the safe must not be able to revoke a root that is already claimable.\n        vm.prank(safe);\n        vm.expectRevert(RegenDrop.NoPendingRoot.selector);\n        drop.cancelPending();\n        drop.claim(alice, 10e18, proof);\n        assertEq(token.balanceOf(alice), 10e18);\n    }\n}","reproduction":"State: RegenDrop funded with 100e18; safe calls postRoot(root=leaf(alice,10e18), committed=10e18) at time t. Warp to t + ACTIVATION_DELAY + 1 with nobody having called activate(). (a) claim(alice, 10e18, []) in this state pays alice 10e18 (root activates inside claim). (b) Instead, safe calls cancelPending() in the same state: expected revert NoPendingRoot (the root is due and therefore live, as postRoot/claim treat it); actual: it succeeds, pendingRoot is deleted, root() stays 0x0 and claim(alice, 10e18, []) now reverts InvalidProof. test/scratch/RegenDropCancelDue.t.sol fails on the current code with 'next call did not revert as expected'.","severity":"low","snippet":"        if (pendingActiveAt == 0) revert NoPendingRoot();","title":"RegenDrop.cancelPending revokes a root whose activation delay has already elapsed, which postRoot and claim treat as live"}],"hash":"9946c5f807a30ace7d33ed22eeda5e48744c509268910e7f08b2bf3aba692cc5","nodeId":"0296d08e-a1a4-45fd-b75a-0c3d6ff3bfba","outcome":"completed","summary":"The review is complete and `.imd-findings.json` is written with one finding, its failing proof test, and 52 coverage rows (every listed entry point plus the manifest).\n\n**Outcome**\n\n- **Build and checks:** default suite 207 passed, 0 failed; `forge fmt --check` clean; test/fork excluded by design.\n- **Manifest:** launch.json has only the five schema keys, ImdoStaking with 5 flat args and ImdoTreasury with 14 flat args in declaration order, lowercase addresses, notes at 3984 characters. Every Treasury constructor bound is satisfied by the literal values. No change to launch.json is needed.\n- **Mainnet assumption verified over RPC:** the hardcoded IMD address reports symbol \"IMD\" and the no-hook ETH/IMD pool (fee 10000, spacing 200) has a non-zero slot0 on the PoolManager, so the Treasury's constant pool key resolves on the real target.\n\n**Finding (low, src/RegenDrop.sol:71).** `cancelPending` does not call `_activate` before checking for a pending root, while `postRoot` and `claim` do. Once the 24-hour delay has elapsed, a claim in that block pays out, but the safe can still delete the same root in that block, and the claim then reverts with InvalidProof. The scratch test `test/scratch/RegenDropCancelDue.t.sol` fails on the current code and is attached as proof. Impact is bounded: the safe is the rail's trusted leg, nothing leaves the contract, and the contract is a Base-chain component outside this manifest.\n\n**Coverage.** All entry points in the core launch contracts (token, staking, treasury, hook, claim) traced and marked holds, including the four hook swap shapes and their delta netting, the treasury's split and pending accounting, the 1/64-gas griefing path on the try/catch (not feasible), and the staking backlog stream transitions. The three items already recorded in the manifest notes (stakeFor lock reset, 20% fee without open(), holderRoot zero) were not re-reported. The regen rail and the deploy script were traced at lower depth given the budget; no further defect was substantiated there.","treeHash":null,"usage":{"cachedInputTokens":1227009,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":43507,"runtime":"claude","turns":24,"wallClockMs":942003}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"da7ed792c3bf5b80","findings":[{"citation":"resolved","description":"postRoot (line 55) and claim (line 85) call _activate() first, so a pending root past pendingActiveAt is live for them. cancelPending checks pendingActiveAt without activating first, so after the 24h delay the safe can still delete a root that the contract already treats as claimable; whichever of claim/activate or cancelPending lands first decides. Bounded: the safe is the trusted leg and no tokens leave, but a claimant with a valid proof for a due root can be voided. Base-chain contract outside the launch manifest (script/DeployRegenRail.s.sol). Merged from audit_flow. Fix: call _activate() at the top of cancelPending.","line":71,"path":"src/RegenDrop.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\";\nimport {RegenDrop} from \"src/RegenDrop.sol\";\n\ncontract MintableToken is ERC20 {\n    constructor() ERC20(\"axlREGEN\", \"REGEN\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\n/// @dev A root whose activation delay has elapsed is live for `postRoot` and `claim` (both call `_activate` first),\n///      but `cancelPending` does not activate first, so the safe can still revoke it after it became claimable.\n///      Fails on the current code: cancelPending succeeds where NoPendingRoot is expected. Passes once cancelPending\n///      activates a due root before checking for a pending one.\ncontract RegenDropCancelDueTest is Test {\n    MintableToken token;\n    RegenDrop drop;\n    address safe = makeAddr(\"safe\");\n    address alice = makeAddr(\"alice\");\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        token = new MintableToken();\n        drop = new RegenDrop(address(token), safe);\n        token.mint(address(drop), 100e18);\n    }\n\n    function _leaf(address a, uint256 c) internal pure returns (bytes32) {\n        return keccak256(bytes.concat(keccak256(abi.encode(a, c))));\n    }\n\n    function test_cancelAfterDelayCannotRevokeClaimableRoot() public {\n        // Single-leaf tree: root == leaf, empty proof.\n        bytes32 root = _leaf(alice, 10e18);\n        vm.prank(safe);\n        drop.postRoot(root, 10e18);\n        vm.warp(block.timestamp + drop.ACTIVATION_DELAY() + 1);\n        // The root is due: a claim in this very block would pay alice 10e18.\n        uint256 snap = vm.snapshotState();\n        bytes32[] memory proof;\n        drop.claim(alice, 10e18, proof);\n        assertEq(token.balanceOf(alice), 10e18);\n        vm.revertToState(snap);\n        // Same state, same block: the safe must not be able to revoke a root that is already claimable.\n        vm.prank(safe);\n        vm.expectRevert(RegenDrop.NoPendingRoot.selector);\n        drop.cancelPending();\n        drop.claim(alice, 10e18, proof);\n        assertEq(token.balanceOf(alice), 10e18);\n    }\n}","reproduction":"RegenDrop funded with 100e18; safe postRoot(leaf(alice,10e18), 10e18) at t; warp to t+ACTIVATION_DELAY+1 with nobody calling activate(). (a) claim(alice,10e18,[]) pays alice 10e18. (b) In the same state safe calls cancelPending(): expected revert NoPendingRoot; actual success, pendingRoot deleted, root stays 0x0 and claim(alice,10e18,[]) reverts InvalidProof. Proof run here: FAIL 'next call did not revert as expected'.","severity":"low","snippet":"        if (pendingActiveAt == 0) revert NoPendingRoot();","title":"RegenDrop.cancelPending can revoke a root whose activation delay has already elapsed (claim and postRoot treat it as live)"},{"citation":"resolved","description":"The size weighting (line 368) closes the dust ratchet only. With ethIn == maxEthPerBuy the weight is full, ceilNow = floorNow*1.02, and since floorNow after 600 s is cp*0.99914, next = 1.019*cp >= cp so the anchor re-bases on the raised value (lines 372-374) and the next step compounds. An attacker sells IMD to push spot sqrt-price to ~1.03x floor before each process(); the treasury's 1 ETH buy (~0.25% impact in a 200 ETH pool) leaves post above ceilNow, so the reference rises ~1.9%; the attacker buys back. After 12 rounds the reference is ~1.25x market sqrt-price. quoteMinOut then demands floor^2*0.97 IMD per ETH, which spot cannot meet until the hyperbola brings the floor back to ~1.015x spot: 7d*(1.2557/1.015-1) ~= 39.8 h. No funds lost (ETH waits in _imdLeg.pending, the pinned buys fill at better-than-market prices); attacker pays ~0.8 ETH per pin in fees/price impact, so this is paid griefing. Reported because README line 35 presents the weighting as closing the ratchet. Merged from audit_math finding 1. Fix: bound the rise relative to the reference the buy started from over a window (e.g. no anchor move more than once per CHECKPOINT_DECAY, or cap cumulative rise per day).","line":372,"path":"src/ImdoTreasury.sol","reproduction":"Local v4 (test/utils/LocalV4.sol shape): ETH/IMD pool fee 10000 spacing 200 at tick 54000, 200 ETH full range; treasury with the manifest's 14 values; 1e18 IMDO staked. (1) 1 ETH to treasury, process() -> cp0 = spot. (2) 12x: warp +600; swap IMD->ETH with sqrtPriceLimitX96 = floorNow*103/100; send 2.52 ETH; process() (LegBought); swap ETH->IMD back to the original sqrt-price. (3) leg(0).checkpointSqrtPriceX96 / market ~= 1.2557 (sqrt). (4) Pool at market, every 600 s fund 0.01 ETH and process(): expected a fill within the ~1 day the README implies; actual LegFailed(InsufficientOutput) for ~238 consecutive calls (~39 h).","severity":"low","snippet":"            if (next >= l.checkpointSqrtPriceX96) {\n                l.checkpointSqrtPriceX96 = uint160(next);\n                l.checkpointAt = uint64(block.timestamp);","title":"Full-size buys at a pushed price ratchet the IMD checkpoint ~2% (sqrt) per cooldown with no bound in N; 12 pins stall the IMD leg ~39 hours"},{"citation":"resolved","description":"On the mainnet path the ETH/IMD pool exists when ImdoTreasury is created, so the constructor read is the checkpoint's seed for life (the _ensurePool fallback at line 399 never runs). A searcher who sees the DeployImdo broadcast buys IMD just before the treasury creation (sqrt-price of IMD per ETH falls), the constructor records the pushed value, and the searcher sells back. The floor recovers only by <=2% sqrt per full honest buy while it keeps decaying, so for that window process() buys can be sandwiched down to the seeded level and still pass the 3% minOut. Bounded by the 7-day decay (which already exposes a similar gap after ~1 day) and by two 1% pool fees on the push capital; README line 35 lists manipulation and ordering as operational risks but not that the seed itself is a spot read. Merged from audit_permissions finding 1. Fix without changing the 14-argument constructor: preflight/post-deploy check in script/DeployImdo.s.sol or docs/DEPLOYMENT.md comparing leg(0).checkpointSqrtPriceX96 with the expected IMD tick, and/or a private relay.","line":154,"path":"src/ImdoTreasury.sol","reproduction":"Same local pool (tick 54000, 200 ETH). Attacker swaps 40 ETH->IMD: sqrtPriceX96 1178734673953441494749930375074 -> ~983918759560468693585174144921. Construct ImdoTreasury with the manifest's 14 values: leg(0).checkpointSqrtPriceX96 == the pushed value (a treasury built before the push holds the honest value). Attacker sells back. Fund both with 2 ETH, attacker pushes again with 40 ETH, process() on each: expected both refuse the buy (pool ~30% off reference); actual the honest one emits LegFailed(InsufficientOutput) while the front-run-seeded one emits LegBought(~0.796 ETH) for ~29% less IMD.","severity":"low","snippet":"            (_imdLeg.checkpointSqrtPriceX96,,,) = poolManager.getSlot0(_imdLeg.key.toId());","title":"Treasury checkpoint is seeded from a same-block spot read of a public pool; front-running the deployment seeds a low floor"},{"citation":"resolved","description":"The assignment states 'Pool fee 3000, spacing 60' and README.md line 82 says 'Its fee 3000 is the admission value'; launch.json writes 12500 and its notes call it the policy value. For a project launch the factory opens the pool at the chain's LaunchFees fee whatever pool.fee says, so there is no deployment effect, but the manifest should carry the fee the assignment names (or the README should say 12500). Everything else in the manifest checks: schema keys only, LaunchToken with no args, ImdoStaking 5 and ImdoTreasury 14 flat args matching src/ImdoStaking.sol:79 and src/ImdoTreasury.sol:109 in declaration order, $contract:ImdoStaking references an earlier contract, addresses lowercase and non-zero, uint values as strings, notes 3,984 chars. Merged from audit_permissions and audit_economics (same root).","line":42,"path":"launch.json","reproduction":"Read launch.json line 42 (12500) vs README.md line 82 and the brief (3000). Expected one value; actual two.","severity":"info","snippet":"    \"fee\": 12500,","title":"launch.json pool.fee is 12500 while the brief and README name 3000 as the admission value"},{"citation":"resolved","description":"Copy-only: launch.json token.contract is LaunchToken (correct for the factory; IMDOToken is the script alias that inherits it unchanged). From audit_permissions.","line":80,"path":"README.md","reproduction":"Compare README.md line 80 with launch.json line 4 (\"contract\": \"LaunchToken\").","severity":"info","snippet":"`launch.json` has no `chainId` root property. It lists `IMDOToken`","title":"README says launch.json lists IMDOToken; the manifest's token contract is LaunchToken"}],"hash":"9adca76e8abbb79807509405a7086de1bc5adf83d4ad270f366d62a3a9995271","nodeId":"1629ee4b-0988-44c7-acdd-14614ad71334","outcome":"completed","summary":"I've written the review to `.imd-findings.json`: five findings, all low or info. Nothing is critical, high or medium. No repository file changed.\n\n**Build and checks (committed `foundry.toml`):** the build succeeds, the default suite passes 207 of 207 (fork tests excluded, as the brief says), and `forge fmt --check` is clean.\n\n**Manifest:** `launch.json` encodes the constructors exactly as the brief describes. ImdoStaking takes 5 flat arguments and ImdoTreasury 14, in declaration order. The token is LaunchToken with no arguments. The ImdoStaking reference points to an earlier contract, addresses are lowercase and non-zero, and the notes are 3,984 characters. I found nothing that would stop the factory deploying it. The only mismatch is the pool fee (finding 4), which the factory ignores for a project launch.\n\n**Findings kept:**\n1. **Low – `src/RegenDrop.sol:71`.** `cancelPending` can delete a root whose 24-hour delay has already passed, even though `claim` and `postRoot` treat that root as live. I ran the specialist's proof and it fails for that reason, so it's attached. This contract is Base-only and outside the launch.\n2. **Low – `src/ImdoTreasury.sol:372`.** Full-size treasury buys at a pushed price raise the price floor about 2% each time, with no limit on how many times. Twelve such buys leave the floor about 1.25× market, and IMD buys stop for about 39 hours. No ETH is lost, the attacker pays roughly 0.8 ETH per buy, and the README presents the size weighting as closing this.\n3. **Low – `src/ImdoTreasury.sol:154`.** The price floor is first set from the ETH/IMD pool's price in the same block as deployment, so someone who front-runs the deployment can set it low. The suggested fix is a check in the deploy script or `docs/DEPLOYMENT.md`; the constructor doesn't need to change.\n4. **Info – `launch.json:42`.** The pool fee is 12500, but the brief and README line 82 say 3000. The factory ignores this field for a project launch. The two specialists who reported it are merged here.\n5. **Info – `README.md:80`.** The README says the manifest lists `IMDOToken`; it actually lists `LaunchToken`.\n\nFindings 2 and 3 have no Foundry proof. I checked them by tracing the code and the specialists' numbers, not by running a test.\n\n**Dropped:**\n- **1-IMDO staker gets the whole IMD buy (claimed medium).** The behaviour reproduces, but README line 43 documents it (\"New rewards with any existing stake distribute immediately\"), and anyone can stake and share the rewards.\n- **Repeated sandwiching of treasury buys.** This is the manipulation risk the README already lists, and in the specialist's own numbers it loses money in a 200 ETH pool.\n- **Pending IMD balance grows when intake hits the cap.** This is the documented overflow of REGEN to the IMD leg. The ETH is held either way and drains at the same 1 ETH per 600 seconds.\n\n**Coverage:** 52 rows. Four Base-only RegenRouter and RegenRegistry entry points are marked unreached because I didn't trace them in the time available. Two rows point to findings and the rest hold.","treeHash":null,"usage":{"cachedInputTokens":1457053,"inputTokens":30,"model":"claude-opus-5-5","outputTokens":14959,"runtime":"claude","turns":17,"wallClockMs":922248}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"2d027bc56749d95c","findings":[{"citation":"resolved","description":"Every stakeFor call resets the beneficiary's 24-hour withdrawal lock (src/ImdoStaking.sol:168) and the only guard is msg.sender == claimContract (line 162); the beneficiary is not checked against the caller. In launch.json the ImdoStaking `claim` argument is \"$owner\", an externally owned launch account, so that account can call stakeFor(victim, 1 wei) once a day and the victim's unstake()/exit() revert StakeLocked indefinitely. Staked principal is frozen by a privileged key, which is the 'freezing holder balances' class the launch forbids. The manifest notes disclose this as a trust assumption; it is still a live capability of the deployed Sepolia artefact and of any deployment whose claim address is not a contract that stakes only for msg.sender. On the manual mainnet path ImdoClaim is the claim contract and stakes only for msg.sender (src/ImdoClaim.sol:128), which closes it there. Minimal code fix that preserves the design: in stakeFor, only set unlockTime when it lengthens the beneficiary's own lock by at most one stake window, e.g. keep the existing unlockTime if the beneficiary already has a stake and did not initiate the call, or require beneficiary == tx.origin-independent proof such as a per-beneficiary opt-in; alternatively the adapter can document that the manifest deployment must never be staked into (it is already marked 'must not be funded').","line":168,"path":"src/ImdoStaking.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IMDOToken} from \"src/IMDOToken.sol\";\nimport {ImdoStaking} from \"src/ImdoStaking.sol\";\n\n/// @notice In the launch manifest ImdoStaking's `claim` argument is `$owner`, an externally owned account.\n/// `stakeFor` resets the beneficiary's 24-hour lock on every call, so that account can keep any staker's\n/// principal locked forever by staking 1 wei for them once a day. Fails on the code as it is: Alice staked\n/// 100 IMDO, waited 25 hours, and still cannot unstake because the claim role topped her up with 1 wei.\ncontract StakeForLockFreezeTest is Test {\n    IMDOToken token;\n    ImdoStaking staking;\n    address claimRole = makeAddr(\"owner-as-claim\");\n    address alice = makeAddr(\"alice\");\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        token = new IMDOToken();\n        staking = new ImdoStaking(address(token), address(0x1111), address(0x2222), claimRole, address(0x3333));\n        token.transfer(alice, 100e18);\n        token.transfer(claimRole, 1e18);\n        vm.prank(alice);\n        token.approve(address(staking), type(uint256).max);\n        vm.prank(claimRole);\n        token.approve(address(staking), type(uint256).max);\n    }\n\n    function test_claimRoleCannotExtendAnotherStakersLockIndefinitely() public {\n        vm.prank(alice);\n        staking.stake(100e18);\n        uint256 aliceUnlock = staking.unlockTime(alice);\n        assertEq(aliceUnlock, block.timestamp + 24 hours);\n\n        // One second before Alice's lock ends, the claim role stakes 1 wei for her.\n        vm.warp(aliceUnlock - 1);\n        vm.prank(claimRole);\n        staking.stakeFor(alice, 1);\n\n        // 25 hours after her own stake Alice should be able to withdraw her principal.\n        vm.warp(aliceUnlock + 1 hours);\n        vm.prank(alice);\n        staking.unstake(100e18);\n        assertEq(token.balanceOf(alice), 100e18);\n    }\n}","reproduction":"Deploy ImdoStaking(imdo, imd, manager, claim=EOA_OWNER, regenSafe). Alice approves and calls stake(100e18) at t0 -> unlockTime[alice] = t0+24h. At t0+24h-1s EOA_OWNER (holding 1 wei IMDO, approved) calls stakeFor(alice, 1) -> unlockTime[alice] = t0+48h-1s. At t0+25h Alice calls unstake(100e18): expected success (her own 24-hour lock has elapsed); actual revert StakeLocked(t0+48h-1s). Repeating stakeFor daily keeps her locked forever. test/scratch/StakeForLockFreeze.t.sol fails with StakeLocked(1800172799).","severity":"medium","snippet":"        unlockTime[beneficiary] = block.timestamp + LOCK_DURATION;","title":"ImdoStaking.stakeFor lets the claim role (the manifest's $owner) keep any staker's principal locked forever"},{"citation":"resolved","description":"currentFeeBps() returns LAUNCH_FEE_BPS (2000) while launchTimestamp == 0 (src/ImdoHook.sol:130) and open() is the only writer of launchTimestamp and is onlyOwner. ImdoHook inherits OpenZeppelin Ownable.renounceOwnership unchanged (Ownable2Step does not override it), so one owner transaction, or simply a lost key, leaves every buy and sell paying 20% of the ETH leg forever with no recovery path; the documented guarantee that the owner 'can only lower' the fee does not hold before open(). A renounce before PoolManager.initialize also makes beforeInitialize unsatisfiable (sender != owner() where owner() == 0), bricking the pool creation. Not in the manifest (script-only), so no admission impact; it is an owner-power/operational hazard for the mainnet deployment. Minimal fix preserving the design: override renounceOwnership to revert, and/or let anyone call open() once a fixed deadline after pool initialization has passed.","line":108,"path":"src/ImdoHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {ImdoHook} from \"src/ImdoHook.sol\";\n\n/// @notice ImdoHook inherits Ownable.renounceOwnership unchanged. `open()` is the only way to leave the\n/// 20% launch fee and it is onlyOwner, so a renounce (or a lost owner key) before `open()` fixes the fee\n/// at 2000 bps forever. The hook's own documentation says the owner \"can only lower\" the fee.\ncontract HookRenounceStuckFeeTest is Test {\n    function test_feeCannotBeStuckAtLaunchLevelForever() public {\n        vm.warp(1_800_000_000);\n        // Mine a salt with the permission bits so the constructor's validateHookPermissions passes.\n        bytes memory creation =\n            abi.encodePacked(type(ImdoHook).creationCode, abi.encode(address(0x1111), address(0x2222), address(0x3333), address(this)));\n        bytes32 salt;\n        address predicted;\n        for (uint256 i;; ++i) {\n            salt = bytes32(i);\n            predicted = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(creation))))));\n            if (uint160(predicted) & uint160(0x3FFF) == 0x20cc) break;\n        }\n        ImdoHook hook = new ImdoHook{salt: salt}(IPoolManager(address(0x1111)), address(0x2222), address(0x3333), address(this));\n        assertEq(address(hook), predicted);\n        assertEq(hook.currentFeeBps(), 2000);\n\n        hook.renounceOwnership();\n        assertEq(hook.owner(), address(0));\n\n        // Nobody can call open() any more.\n        vm.expectRevert();\n        hook.open();\n\n        // A year later the fee should have reached the steady-state 1.5%; it is still the 20% launch fee.\n        vm.warp(block.timestamp + 365 days);\n        assertEq(hook.currentFeeBps(), 150);\n    }\n}","reproduction":"Deploy ImdoHook(poolManager, imdo, treasury, owner) at a mined address with flags 0x20cc. owner calls renounceOwnership(); owner() == address(0). Any call to open() reverts OwnableUnauthorizedAccount. Warp 365 days: currentFeeBps() expected 150 (steady state), actual 2000. test/scratch/HookRenounceStuckFee.t.sol fails with 'assertion failed: 2000 != 150'.","severity":"low","snippet":"    function open() external onlyOwner {","title":"ImdoHook: the 20% launch fee is permanent if open() is never called or ownership is renounced"},{"citation":"resolved","description":"preflight() (script/DeployImdo.s.sol:176-200) validates every other input but never checks c.holderRoot != bytes32(0). ImdoClaim accepts a zero root in its constructor and claimHolder reverts InvalidProof unconditionally when holderRoot == 0 (src/ImdoClaim.sol:108-113). The script always transfers CLAIM_FUNDING = 110,000,000 IMDO (seat 100,000,000 + holder 10,000,000) to the claim (line 169), so with a forgotten/zero root the whole holder tranche sits unclaimable until launch + 39 days and is then sent to DEAD by burnUnclaimed(). Because the claim address is baked into ImdoStaking as an immutable, the mistake cannot be corrected after deployment without redeploying everything. Fix: add `|| c.holderRoot == bytes32(0)` to the preflight revert condition (or, if a launch without a holder list is intended, make the funding amount depend on it).","line":186,"path":"script/DeployImdo.s.sol","reproduction":"Call DeployImdo.deploy(Config{..., holderRoot: bytes32(0), ...}) with otherwise valid inputs: expected revert InvalidConfiguration in preflight; actual the deployment completes and claim.holderRoot() == 0. Any holder calling claim.claimHolder(total, proof, false) after launch reverts InvalidProof; after launch + 39 days burnUnclaimed() sends the 10,000,000e18 holder allocation to 0x...dEaD. (test/unit/ImdoDeploy.t.sol only exercises a non-zero root.)","severity":"low","snippet":"                || c.imd.code.length == 0 || c.seatNFT.code.length == 0 || c.launch < block.timestamp + MIN_LAUNCH_LEAD","title":"DeployImdo preflight accepts holderRoot == 0, which makes the 10,000,000 IMDO holder allocation unclaimable and burned"},{"citation":"resolved","description":"The launch rules say not to claim an audit was performed (the README correctly calls the swarm's reviews checks), but docs/REGEN_RAIL.md line 51 says 'round-2 audit, proven on the fork'. The regen rail is outside this launch, but the file ships in the tree being published. Copy fix only: 'round-2 review'.","line":51,"path":"docs/REGEN_RAIL.md","reproduction":"grep -n -w audit docs/REGEN_RAIL.md -> line 51 contains 'round-2 audit'. Expected: no claim of an audit anywhere in the published tree.","severity":"info","snippet":"| `twapGranularity` / `minTonnesBps` | 48 / 9500 | the RELATIVE bar, compared in kVCM: the kVCM the aggregator needs for the offered tonnes (what the dollars buy at SPOT) must be at least 95% of the kVCM those dollars buy at the pool's 48-completed-interval (≥ 24 h) time-weighted price, pool fee taken off. In kVCM and not in tonnes because the live quoter caps a dollar-budget quote at the class's liquid tonnes: compared in tonnes, a class under one batch compared L with L and a same-transaction pump went through up to the ceiling (round-3 review). 24 h because a price HELD across the whole window becomes the average (observations are written on any state change and `sync()` is public and free: round-2 audit, proven on the fork with a 2 h window). A day-long hold ties the mover's capital in a token whose only venue is this pool. 95% because a thin pool drifts: on 2026-10-12 the spot was 2.7% under the day's average at one block and 9.1% under at another (the honest batch skips there and","title":"docs/REGEN_RAIL.md calls a swarm review round an 'audit'"}],"hash":"e3a478c918aa7ce23a96345f34b77d2645e647f9e44943139b330d4d5e1f864f","nodeId":"2d4e44bb-f371-413d-a3d8-a2a73bfb085a","outcome":"completed","summary":"Review complete. Findings are in `.imd-findings.json` at the repository root (4 entries, 2 with failing Foundry proofs under `test/scratch/`, which is gitignored). No tracked file was changed.\n\n**What I read in full.** `src/LaunchToken.sol`, `src/IMDOToken.sol`, `src/ImdoStaking.sol`, `src/ImdoTreasury.sol`, `src/ImdoHook.sol`, `src/ImdoClaim.sol`, `src/interfaces/IImdoStaking.sol`, `script/DeployImdo.s.sol`, `launch.json`, the hook and claim test harnesses, and both evm_project protected suites. Not reached within budget: `src/RegenDrop.sol`, `src/regen/*` and `script/DeployRegenRail.s.sol` (Base rail, not part of this launch), and the fork tests (need an RPC, excluded by design).\n\n**Checks observed on the committed foundry.toml.**\n\n| Check | Result |\n|---|---|\n| `forge build` | clean |\n| `forge fmt --check` | clean |\n| `forge test` (default profile) | 30 suites, 207 passed, 0 failed, 0 skipped |\n\n**Manifest verification.** `launch.json` has exactly the five schema keys. ImdoStaking carries 5 flat args and ImdoTreasury 14, both in declaration order, with `$token`, `$contract:ImdoStaking`, `$owner` and lowercase literals. Notes are 3,477 characters. Pool fee 3000, spacing 60, pairedCurrency zero for ETH, initialPrice a decimal string. The LaunchToken constructor takes no arguments and mints to the deployer. I replicated the protected opcode scan on the built runtimes: all three are under 24,576 bytes with no DELEGATECALL, CALLCODE or SELFDESTRUCT, and neither constructor moves the supply. No change to launch.json is needed.\n\n**Findings.**\n\n- **Medium, `src/ImdoStaking.sol:168`.** `stakeFor` resets the beneficiary's 24-hour lock and only checks the caller is the claim address. In the manifest that address is `$owner`, so the owner can lock any staker's principal forever with a daily 1-wei stakeFor. The notes disclose it, but it is a privileged freeze of staked balances in the deployed artefact. Proof `test/scratch/StakeForLockFreeze.t.sol` fails with `StakeLocked`. The manual mainnet path is closed because ImdoClaim stakes only for msg.sender.\n- **Low, `src/ImdoHook.sol:108`.** `open()` is the only exit from the 20% launch fee and is owner-only, while `renounceOwnership` is inherited unchanged. A renounce or lost key fixes the fee at 20% permanently, and a renounce before pool initialization bricks `beforeInitialize`. Proof `test/scratch/HookRenounceStuckFee.t.sol` fails with `2000 != 150`. Script-only, no admission impact.\n- **Low, `script/DeployImdo.s.sol:186`.** Preflight never rejects a zero `holderRoot`. The script still funds the 10,000,000 IMDO holder tranche, which then becomes unclaimable and burns at the deadline, and the claim address is immutable in staking so nothing can be redeployed in isolation.\n- **Info, `docs/REGEN_RAIL.md:51`.** The text calls a review round an \"audit\", which the launch copy rules forbid. Regen rail only.\n\n**Confirmed clean, with specifics.** Hook delta accounting nets to zero on all four swap shapes, partial fills with a prepaid ETH fee revert, and the deferred-claim path is used when the manager lacks ETH. Treasury `unsplitEth` cannot underflow on any success or failure path, the self-call and unlock callback are gated by both the sender check and `_inProcess`, and keeper gas-griefing of the try/catch is not feasible under the 63/64 rule. Staking backlog accounting keeps `unallocated` above the unreleased stream amount through every threshold transition. Claim tranches, duplicate ids and Merkle replay are handled. No proxies, initializers, mint paths or owner setters exist in the manifest contracts; the treasury's only setter is `setRegenCap` by `regenSafe` within bounds.","treeHash":null,"usage":{"cachedInputTokens":1228552,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":40959,"runtime":"claude","turns":19,"wallClockMs":789546}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"fcb71e606c933181","findings":[{"citation":"resolved","description":"The backlog stream is gated by MIN_BACKLOG_STAKE (10,000,000 IMDO, 1% of supply, src/ImdoStaking.sol:284) so that rewards notified with no stakers are not released to a handful of early stakers. Direct notifications bypass that rule: `_distribute` only parks rewards when totalStaked is exactly zero and otherwise credits the whole amount to whoever is staked, however small the stake. On the mainnet path the hooked ETH/IMDO pool trades from deployment at the 20% launch fee, every fee goes to the treasury, and anyone may call process() the moment ETH is there (lastProcessed starts at 0). Seats and holders cannot claim before `launch`, which the script requires to be at least 1 hour and up to 30 days after deployment, so during that window the only possible stakers are buyers from the pool. One account that buys 1 IMDO and stakes it receives 100% of every IMD buy the treasury makes until someone else stakes: up to 1 ETH of IMD per 600 s (the maxEthPerBuy cap), i.e. up to 6 ETH of IMD in the minimum one-hour launch lead, funded by the launch-fee ETH that the 1% threshold was designed to keep out of tiny hands. The misallocation is permanent: rewardPerShare is advanced and later stakers get nothing from those buys. Economic Security guide item 'Starve shared capacity / first participant' and Invariant guide item 'Abuse boundaries: first participant'. The README sentence 'New rewards with any existing stake distribute immediately' records the behaviour but not that the threshold is thereby defeated for the highest-fee period of the launch.","line":226,"path":"src/ImdoStaking.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 {ModifyLiquidityParams} from \"@uniswap/v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"@uniswap/v4-core/src/libraries/TickMath.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 {IMDOToken} from \"src/IMDOToken.sol\";\nimport {ImdoStaking} from \"src/ImdoStaking.sol\";\nimport {ImdoTreasury} from \"src/ImdoTreasury.sol\";\n\n/// @notice Economic finding: the 1%-of-supply threshold (MIN_BACKLOG_STAKE) gates only the backlog stream.\n/// A reward notified while ANY stake exists is distributed at once, so one account staking 1 IMDO (one\n/// billionth of supply) before the seats can claim receives the entire first IMD buy the treasury makes\n/// from launch-fee ETH. Fails on the current code: the 1-IMDO staker earns the whole buy.\ncontract TinyStakeCaptureTest is Test {\n    PoolManager poolManager;\n    PoolModifyLiquidityTest lpRouter;\n    MockERC20 imd;\n    IMDOToken imdo;\n    ImdoStaking staking;\n    ImdoTreasury treasury;\n    address alice = makeAddr(\"alice\");\n    address claimAddress = makeAddr(\"claim\");\n    address regenSafe = makeAddr(\"regenSafe\");\n\n    receive() external payable {}\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        poolManager = new PoolManager(address(this));\n        lpRouter = new PoolModifyLiquidityTest(poolManager);\n        vm.deal(address(this), 10_000 ether);\n        imd = new MockERC20(\"Identity.md\", \"IMD\", 18);\n        imd.mint(address(this), 1e36);\n        imd.approve(address(lpRouter), type(uint256).max);\n        PoolKey memory key =\n            PoolKey(CurrencyLibrary.ADDRESS_ZERO, Currency.wrap(address(imd)), 10000, 200, IHooks(address(0)));\n        uint160 sqrtP = TickMath.getSqrtPriceAtTick(54000);\n        poolManager.initialize(key, sqrtP);\n        uint128 liquidity = LiquidityAmounts.getLiquidityForAmounts(\n            sqrtP, TickMath.getSqrtPriceAtTick(-887200), TickMath.getSqrtPriceAtTick(887200), 200 ether, type(uint128).max\n        );\n        lpRouter.modifyLiquidity{value: 200 ether}(\n            key, ModifyLiquidityParams(-887200, 887200, int256(uint256(liquidity)), 0), \"\"\n        );\n        imdo = new IMDOToken();\n        staking = new ImdoStaking(address(imdo), address(imd), address(poolManager), claimAddress, regenSafe);\n        treasury = new ImdoTreasury(\n            address(staking),\n            makeAddr(\"ops\"),\n            makeAddr(\"offsets\"),\n            regenSafe,\n            address(poolManager),\n            address(imd),\n            10000,\n            200,\n            1 ether,\n            300,\n            600,\n            0.5 ether,\n            0.05 ether,\n            5 ether\n        );\n    }\n\n    function test_oneImdoStakerCapturesTheWholeFirstBuy() public {\n        // Alice holds one IMDO out of a billion and stakes it before anyone else (seats cannot claim before `launch`).\n        imdo.transfer(alice, 1e18);\n        vm.startPrank(alice);\n        imdo.approve(address(staking), 1e18);\n        staking.stake(1e18);\n        vm.stopPrank();\n        assertLt(staking.totalStaked(), staking.MIN_BACKLOG_STAKE(), \"stake is far below the 1% threshold\");\n\n        // Launch-fee ETH arrives at the treasury (2.5 ETH is one full process() round) and anyone processes it.\n        (bool ok,) = address(treasury).call{value: 2.5 ether}(\"\");\n        require(ok);\n        treasury.process();\n        uint256 bought = imd.balanceOf(address(staking));\n        assertGt(bought, 0, \"the IMD leg bought with ~1 ETH\");\n\n        // Expected: with less than MIN_BACKLOG_STAKE staked the buy is held back (unallocated), as the backlog\n        // rule for the same threshold requires. Actual: the 1-IMDO staker is credited the entire buy.\n        assertEq(staking.earned(alice, address(imd)), 0, \"a 1-IMDO stake must not capture the whole 1 ETH buy\");\n    }\n}","reproduction":"State: fresh deployment, no seat claims yet (block.timestamp < launch). 1) alice buys or receives 1e18 IMDO (one billionth of supply) and calls ImdoStaking.stake(1e18); totalStaked = 1e18 < MIN_BACKLOG_STAKE. 2) 2.5 ETH of hook fees arrive at ImdoTreasury. 3) anyone calls ImdoTreasury.process(): the IMD leg buys with ~1 ETH and calls staking.notifyReward(IMD, ~218e18 in the local pool). Expected (per the threshold rule): with less than 1% of supply staked the IMD is held in `unallocated` and streams only once 10,000,000 IMDO are staked. Actual: staking.earned(alice, IMD) == the entire buy (218053815652557333201 wei in the proof) and the next staker's rewardPerSharePaid starts above it. Proof test fails with `218053815652557333201 != 0`.","severity":"medium","snippet":"        if (totalStaked == 0) {\n            unallocated[token] += amount;\n            return 0;\n        }\n        distributed = amount;\n        rewardPerShare[token] += FullMath.mulDiv(distributed, MAGNITUDE, totalStaked);","title":"Live IMD rewards distribute pro-rata to any non-zero stake, so a 1-IMDO staker captures entire treasury buys before the 1% backlog threshold is reached"},{"citation":"resolved","description":"quoteMinOut uses max(spot, decayed floor) and a 3% tolerance, and the floor refreshes upward only when the price read inside the treasury's own transaction (`post`) stands above the floor. An attacker who front-runs process() with an IMD buy that pushes sqrt-price down to just above the floor makes the treasury buy at that price, and the treasury's own impact leaves `post <= floorNow`, so no refresh happens; the attacker then sells back. Repeating this every cooldown means the reference never rises again while it keeps decaying along cp*7d/(7d+age): the floor is 50% of the reference sqrt-price after 7 days (25% in price), so each 1 ETH buy can be filled at a quarter of the fair IMD amount. This is the sustained version of the sandwich the prior rounds bounded per buy; the README calls MEV resistance limited. Economics: per-buy profit = gap x ethIn (0.03 ETH at a fresh floor, growing toward 0.75 ETH), cost ~ 2 x 1% pool fee on the capital needed to move the price by the gap. In a pool with about 200 ETH of in-range liquidity the 3% round costs ~0.06 ETH for 0.03 ETH profit, so it is unprofitable; it becomes profitable, and self-reinforcing, once in-range liquidity near spot is under roughly 100 ETH. Reported for the judge's weighing because the only recovery is organic unsandwiched buys and there is no administrative repricing.","line":365,"path":"src/ImdoTreasury.sol","reproduction":"State: treasury checkpoint cp at spot S, cooldown 600 s, pool liquidity L. Each round: (1) attacker swaps ETH->IMD until sqrtPrice = floor(t)*1.0151 (just enough for the 3% minOut to fill); (2) process() buys 1 ETH of IMD at that price; post-swap sqrtPrice < floor(t) so _refreshCheckpoint takes the `next = floorNow` branch and stores nothing; (3) attacker sells the IMD back. Expected: a buy at a manipulated price should not leave the reference free to decay indefinitely. Actual: after N rounds the floor is cp*7d/(7d+N*600s); at N=1008 (7 days) the treasury accepts 25% of the reference price per buy. Numbers above give the liquidity threshold at which the rounds are profitable.","severity":"low","snippet":"        uint256 floorNow = FullMath.mulDiv(l.checkpointSqrtPriceX96, CHECKPOINT_DECAY, CHECKPOINT_DECAY + age);\n        uint256 next = floorNow;\n        if (post > floorNow) {","title":"A keeper who sandwiches every treasury buy keeps the post-swap price under the floor, so the checkpoint never refreshes and the decay widens the extractable gap to 75% of each buy after 7 days"},{"citation":"resolved","description":"The manifest's pool block carries fee 12500 and the notes call it the policy value, but README.md:82 says 'Its fee 3000 is the admission value' and the brief for this review also says 'Pool fee 3000, spacing 60'. For a project launch the factory opens the pool at the chain's LaunchFees tier whatever pool.fee says, so no deployment effect follows; the documentation should state one number. Manifest structure otherwise checks: two contracts with 5 and 14 flat constructor args in declaration order matching src/ImdoStaking.sol:79 and src/ImdoTreasury.sol:109, every address lowercase and non-zero, strings for uint256 values, numbers for fee/tickSpacing/decimals, notes 3,984 characters, no extra keys.","line":42,"path":"launch.json","reproduction":"Read launch.json line 42 (12500) and README.md line 82 ('Its fee 3000 is the admission value'). Expected: one consistent admission fee in manifest and docs. Actual: two different values.","severity":"info","snippet":"    \"fee\": 12500,","title":"launch.json pool.fee is 12500 while README line 82 and the assignment text name 3000 as the admission value"}],"hash":"eab404a804c1877dff4c82ddabefc101009959c56097abd72aa8cc3d8e0013b4","nodeId":"da43ffcb-e3e5-47e0-9363-596242fd4b9a","outcome":"completed","summary":"The review is complete and written to `.imd-findings.json`. No tracked file was changed; only the findings file and my scratch proof under test/scratch exist, both ignored by git.\n\n**Checks run on the committed configuration**\n\n| Check | Result |\n|---|---|\n| Default suite (test/fork excluded) | 207 passed, 0 failed, 30 suites |\n| forge fmt --check | clean |\n| Proof test test/scratch/TinyStakeCapture.t.sol | fails on current code as claimed |\n\n**Findings**\n\n- **Medium, src/ImdoStaking.sol:226.** Live IMD rewards distribute pro-rata to any non-zero stake. The 10,000,000 IMDO threshold only gates the backlog stream, so one account staking 1 IMDO before seats can claim receives 100% of every treasury IMD buy, up to 1 ETH of IMD per 600 seconds through the launch lead of at least one hour. The proof stakes 1 IMDO, funds the treasury with 2.5 ETH, processes, and shows the staker earned the entire buy.\n- **Low, src/ImdoTreasury.sol:365.** A keeper who sandwiches every buy keeps the post-swap price under the floor, so the checkpoint never refreshes while it keeps decaying. After 7 days each buy can fill at 25% of the reference price. The round is unprofitable at roughly 200 ETH of in-range liquidity and becomes profitable under about 100 ETH. The README already calls MEV resistance limited, so this is for the judge's weighing.\n- **Info, launch.json:42.** The manifest's pool fee is 12500 while README line 82 and the brief say 3000. The factory uses the chain's fee tier for project launches, so no deployment effect, but the documentation should agree.\n\n**Manifest check.** Both constructors are encoded flat with 5 and 14 values in declaration order, every address lowercase and non-zero, strings for uint256 values, numbers for fee, tickSpacing and decimals, no extra keys, notes at 3,984 characters. No manifest change is needed.\n\n**Coverage.** All 51 entry points have rows. The staking, treasury, claim, token and hook entry points were traced. Five Base-chain rail entry points (RegenRouter, RegenRegistry, UniswapV3Venue) are marked unreached as outside the launch and my budget. The sandwich surface, lock reset, zero holder root and renounce-before-open items from prior rounds were confirmed as already documented and not re-reported.","treeHash":null,"usage":{"cachedInputTokens":1547052,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":38682,"runtime":"claude","turns":32,"wallClockMs":592552}}],"verification":[{"checks":[{"durationMs":218415,"exitCode":0,"name":"build","output":"Compiling 195 files with Solc 0.8.26\nSolc 0.8.26 finished in 218.08s\nCompiler run successful!\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/ImdoClaim.sol:56:51\n   │\n56 │                 || seatSize_ > SEAT_ALLOCATION || launch_ < block.timestamp\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/regen/OffsetsForwarder.sol:67:9\n   │\n67 │         emit Forwarded(amount);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/RegenDrop.sol:64:27\n   │\n64 │         pendingActiveAt = uint64(block.timestamp) + ACTIVATION_DELAY;\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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/ImdoClaim.sol:76:13\n   │\n76 │         if (block.timestamp < launch || block.timestamp >= deadline) return 0;\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/ImdoClaim.sol:76:41\n   │\n76 │         if (block.timestamp < launch || block.timestamp >= deadline) return 0;\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/ImdoClaim.sol:78:13\n   │\n78 │         if (tranches > 10) tranches = 10;\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/RegenDrop.sol:95:9\n   │\n95 │         emit Claimed(account, amount, cumulative);\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/RegenDrop.sol:107:24\n    │\n107 │         if (at == 0 || block.timestamp < at) return;\n    │                        ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/ImdoClaim.sol:128:13\n    │\n128 │             staking.stakeFor(msg.sender, amount);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/ImdoClaim.sol:101:29\n    │\n101 │         if (amount == 0 && !changed) revert NothingUnlocked();\n    │                             ━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n   ╭▸ src/ImdoClaim.sol:98:13\n   │\n98 │             amount += due;\n   │             ━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/ImdoClaim.sol:83:13\n   │\n83 │         if (block.timestamp >= deadline) revert ClaimClosed();\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/ImdoClaim.sol:84:13\n   │\n84 │         if (block.timestamp < launch) revert NothingUnlocked();\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[calls-loop]: external call inside a loop\n   ╭▸ src/ImdoClaim.sol:90:17\n   │\n90 │             if (seatNFT.ownerOf(id) != msg.sender) revert NotOwner();\n   │                 ━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/ImdoClaim.sol:70:29\n   │\n70 │         if (id >= seatSize) revert InvalidToken();\n   │                             ━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/ImdoClaim.sol:90:52\n   │\n90 │             if (seatNFT.ownerOf(id) != msg.sender) revert NotOwner();\n   │                                                    ━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/ImdoClaim.sol:106:13\n    │\n106 │         if (block.timestamp >= deadline) revert ClaimClosed();\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/ImdoClaim.sol:121:9\n    │\n121 │         emit HolderClaimed(msg.sender, amount, stake);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-events-arithmetic]: `regenCap` is changed without an event but is used in arithmetic\n    ╭▸ src/ImdoTreasury.sol:166:9\n    │\n166 │         regenCap = cap;\n    │         ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/ImdoStaking.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/ImdoTreasury.sol:130:64\n    │\n130 │             maxEthPerBuy_ < MIN_ETH_PER_BUY || maxEthPerBuy_ > uint256(uint128(type(int128).max))\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/ImdoTreasury.sol:130:72\n    │\n130 │             maxEthPerBuy_ < MIN_ETH_PER_BUY || maxEthPerBuy_ > uint256(uint128(type(int128).max))\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/ImdoTreasury.sol:132:37\n    │\n132 │                 || imdTickSpacing > uint24(TickMath.MAX_TICK_SPACING) || imdFee > LPFeeLibrary.MAX_LP_FEE\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/ImdoTreasury.sol:147:71\n    │\n147 │             CurrencyLibrary.ADDRESS_ZERO, Currency.wrap(imd), imdFee, int24(imdTickSpacing), IHooks(address(0))\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/ImdoTreasury.sol:155:77\n    │\n155 │             if (_imdLeg.checkpointSqrtPriceX96 != 0) _imdLeg.checkpointAt = 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/ImdoStaking.sol:257:13\n    │\n257 │             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/ImdoStaking.sol:157:9\n    │\n157 │         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 `usdcOf` is updated\n    ╭▸ src/regen/RegenRouter.sol:530:17\n    │\n530 │             try aggregator.retireCreditViaKlima(credit, 0, 0, tonnes, address(usdc), classVault, maxIn, 0, details) {\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `balanceOf` is updated\n    ╭▸ src/regen/RegenRouter.sol:295:26\n    │\n295 │             (bool ok,) = msg.sender.call{value: paid}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/regen/RegenRouter.sol:259:13\n    │\n259 │         if (block.timestamp < nextAt) revert TooSoon(nextAt);\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/regen/RegenRouter.sol:292:9\n    │\n292 │         emit Processed(projectId, depositor, msg.sender, credit, ethIn, paid, bought, spent, tonnes);\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/regen/RegenRouter.sol:298:17\n    │\n298 │                 emit BountyForfeited(projectId, msg.sender, paid);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/regen/RegenRouter.sol:324:16\n    │\n324 │         return ethIn * price / 1e20 * minOutBps / BPS;\n    │                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/regen/RegenRouter.sol:358:42\n    │\n358 │             if (up != 0 || since == 0 || since > block.timestamp || block.timestamp - since < SEQUENCER_GRACE) {\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/regen/RegenRouter.sol:358:69\n    │\n358 │             if (up != 0 || since == 0 || since > block.timestamp || block.timestamp - since < SEQUENCER_GRACE) {\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/regen/RegenRouter.sol:363:46\n    │\n363 │         if (answer <= 0 || updatedAt == 0 || updatedAt > block.timestamp || block.timestamp - updatedAt > maxFeedAge) {\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/regen/RegenRouter.sol:363:77\n    │\n363 │         if (answer <= 0 || updatedAt == 0 || updatedAt > block.timestamp || block.timestamp - updatedAt > maxFeedAge) {\n    │                                                                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/regen/RegenRouter.sol:357:46\n    │\n357 │             (, int256 up, uint256 since,,) = sequencerUptime.latestRoundData();\n    │                                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/ImdoClaim.sol:137:13\n    │\n137 │         if (block.timestamp < deadline) revert TooEarly();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/regen/RegenRouter.sol:362:50\n    │\n362 │         (, int256 answer,, uint256 updatedAt,) = ethUsd.latestRoundData();\n    │                                                  ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/regen/RegenRouter.sol:367:16\n    │\n367 │         return uint256(answer);\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/regen/RegenRouter.sol:377:31\n    │\n377 │         address agg = address(uint160(word));\n    │                               ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint160' is safe 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/ImdoStaking.sol:171:9\n    │\n171 │         emit Staked(beneficiary, 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/ImdoStaking.sol:182:9\n    │\n182 │         emit RewardClaimed(msg.sender, 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/ImdoStaking.sol:190:9\n    │\n190 │         emit RegenNotified(msg.sender, msg.value, distributed);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/ImdoHook.sol:110:27\n    │\n110 │         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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/ImdoHook.sol:132:13\n    │\n132 │         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 `_imdLeg` is updated\n    ╭▸ src/ImdoTreasury.sol:333:13\n    │\n333 │         try this.executeImd(ethIn) returns (uint256 out) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/ImdoHook.sol:190:42\n    │\n190 │             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/ImdoHook.sol:190:77\n    │\n190 │             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-eth]: uncapped ETH transfer can be reentered before `_status` is updated\n    ╭▸ src/ImdoStaking.sol:202:22\n    │\n202 │         (bool ok,) = regenSafe.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/ImdoStaking.sol:204:9\n    │\n204 │         emit RegenWithdrawn(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/ImdoHook.sol:205:46\n    │\n205 │                 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/ImdoHook.sol:205:81\n    │\n205 │                 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/ImdoHook.sol:208:47\n    │\n208 │                 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/ImdoHook.sol:208:80\n    │\n208 │                 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/ImdoHook.sol:213:44\n    │\n213 │         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/ImdoHook.sol:213:52\n    │\n213 │         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/ImdoHook.sol:213:74\n    │\n213 │         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/ImdoHook.sol:213:82\n    │\n213 │         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 `_status` is updated\n    ╭▸ src/ImdoHook.sol:273:9\n    │\n273 │         poolManager.unlock(abi.encode(amount));\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/ImdoHook.sol:275:9\n    │\n275 │         emit FeesRedeemed(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/ImdoHook.sol:273:9\n    │\n273 │         poolManager.unlock(abi.encode(amount));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/ImdoHook.sol:309:17\n    │\n309 │                 emit FeesDeferred(fee);\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/ImdoHook.sol:312:9\n    │\n312 │         emit FeeTaken(params.zeroForOne, ethAmount, fee, bps);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/regen/RegenRouter.sol:390:9\n    │\n390 │         venue.swapEthToToken{value: ethIn}(address(usdc), required, address(this));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/ImdoTreasury.sol:180:22\n    │\n180 │             bounty = amount * KEEPER_BPS / BPS;\n    │                      ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/divide-before-multiply\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/regen/RegenRouter.sol:467:13\n    │\n467 │           try aggregator.quoteRetireCreditForInputTokenInViaKlima(\n    │ ┏━━━━━━━━━━━━━┛\n468 │ ┃             credit, 0, amount, address(usdc), classVault, 0\n469 │ ┃         ) returns (\n    │ ┗━━━━━━━━━┛\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/regen/RegenRouter.sol:461:39\n    │\n461 │         (bool ok, bytes memory ret) = credit.staticcall(abi.encodeCall(IERC20.balanceOf, (classVault)));\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/regen/RegenRouter.sol:489:13\n    │\n489 │         try aggregator.quoteRetireCreditViaKlima(credit, 0, tonnes, token, classVault, 0) returns (\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/regen/RegenRouter.sol:450:13\n    │\n450 │         try this.referenceKvcm(spend) returns (uint256 ref) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/ImdoTreasury.sol:214:13\n    │\n214 │         if (bounty != 0) {\n    │             ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/ImdoTreasury.sol:171:13\n    │\n171 │         if (block.timestamp < availableAt) revert CooldownActive(availableAt);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[divide-before-multiply]: division before multiplication may lose precision\n    ╭▸ src/regen/RegenRouter.sol:437:18\n    │\n437 │         tonnes = tonnes / TONNE_STEP * TONNE_STEP;\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/ImdoTreasury.sol:207:17\n    │\n207 │                 emit RegenSent(regenPending);\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/ImdoTreasury.sol:210:17\n    │\n210 │                 emit RegenDeferred(regenPending, reason);\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/ImdoStaking.sol:239:13\n    │\n239 │         if (block.timestamp < unlockTime[msg.sender]) revert StakeLocked(unlockTime[msg.sender]);\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/ImdoTreasury.sol:219:17\n    │\n219 │                 emit KeeperPaid(msg.sender, bounty);\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/ImdoTreasury.sol:223:17\n    │\n223 │                 emit KeeperPaymentDeferred(msg.sender, bounty);\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/ImdoStaking.sol:244:9\n    │\n244 │         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/ImdoTreasury.sol:177:25\n    │\n177 │         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[calls-loop]: external call inside a loop\n    ╭▸ src/regen/RegenRouter.sol:528:30\n    │\n528 │             uint256 before = usdc.balanceOf(address(this));\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[return-bomb]: external call with a gas limit may copy unbounded return data\n    ╭▸ src/ImdoTreasury.sol:217:26\n    │\n217 │             (bool ok,) = msg.sender.call{value: bounty, gas: 100_000}(\"\");\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/return-bomb\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/regen/RegenRouter.sol:530:17\n    │\n530 │             try aggregator.retireCreditViaKlima(credit, 0, 0, tonnes, address(usdc), classVault, maxIn, 0, details) {\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/regen/RegenRouter.sol:532:50\n    │\n532 │                 return (credit, tonnes, before - usdc.balanceOf(address(this)));\n    │                                                  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/ImdoStaking.sol:286:13\n    │\n286 │         if (elapsed > BACKLOG_DURATION) elapsed = BACKLOG_DURATION;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/regen/RegenRouter.sol:544:15\n    │\n544 │             ++n;\n    │               ━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/regen/RegenRouter.sol:548:29\n    │\n548 │             b[--n] = bytes1(uint8(48 + v % 10));\n    │                             ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' is safe 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/ImdoStaking.sol:308:77\n    │\n308 │                 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/regen/RegenRegistry.sol:78:9\n   │\n78 │         emit ProjectRegistered(id, beneficiary, name, regenAddress, msg.sender);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/regen/RegenRegistry.sol:116:15\n    │\n116 │             k[n++] = c;\n    │               ━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/regen/RegenRegistry.sol:110:21\n    │\n110 │                 if (space) continue;\n    │                     ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/regen/RegenRegistry.sol:131:23\n    │\n131 │         if (prefix != bytes6(\"regen1\")) return false;\n    │                       ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'bytes6' is safe 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/regen/RegenReceiver.sol:34:9\n   │\n34 │         emit Forwarded(projectId, amount);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[encode-packed-collision]: `abi.encodePacked()` called with multiple dynamic type arguments; hash collisions possible\n   ╭▸ src/regen/RegenReceiver.sol:55:38\n   │\n55 │         bytes32 initHash = keccak256(abi.encodePacked(type(RegenReceiver).creationCode, abi.encode(router, projectId)));\n   │                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/encode-packed-collision\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n   ╭▸ src/regen/RegenReceiver.sol:69:9\n   │\n69 │         emit ReceiverDeployed(projectId, receiver);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `_status` is updated\n    ╭▸ src/ImdoTreasury.sol:257:22\n    │\n257 │         (bool ok,) = recipient.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/ImdoTreasury.sol:234:9\n    │\n234 │         emit OpsPaid(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/ImdoTreasury.sol:243:9\n    │\n243 │         emit OffsetsPaid(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/ImdoTreasury.sol:253:9\n    │\n253 │         emit KeeperPaid(msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/ImdoTreasury.sol:257:22\n    │\n257 │         (bool ok,) = recipient.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/ImdoTreasury.sol:266:9\n    │\n266 │         staking.notifyReward(token, balance);\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/ImdoTreasury.sol:268:9\n    │\n268 │         emit RewardsFlushed(token, balance);\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/ImdoTreasury.sol:288:13\n    │\n288 │         if (sqrtPriceX96 < checkpointFloor) sqrtPriceX96 = uint160(checkpointFloor);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/ImdoTreasury.sol:288:60\n    │\n288 │         if (sqrtPriceX96 < checkpointFloor) sqrtPriceX96 = uint160(checkpointFloor);\n    │                                                            ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint160' is safe 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/ImdoTreasury.sol:303:61\n    │\n303 │             poolManager.swap(_imdLeg.key, SwapParams(true, -int256(ethIn), 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/ImdoTreasury.sol:304:37\n    │\n304 │         if (delta.amount0() >= 0 || uint256(-int256(delta.amount0())) != 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/ImdoTreasury.sol:306:29\n    │\n306 │         uint256 amountOut = uint256(uint128(delta.amount1()));\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/ImdoTreasury.sol:306:37\n    │\n306 │         uint256 amountOut = uint256(uint128(delta.amount1()));\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/ImdoTreasury.sol:308:9\n    │\n308 │         poolManager.settle{value: ethIn}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/ImdoTreasury.sol:326:32\n    │\n326 │         if (l.failures != 0 && block.timestamp > uint256(l.lastFailure) + gap) _resetFailures();\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/ImdoTreasury.sol:382:9\n    │\n382 │         emit CheckpointRefreshed(post, uint160(next));\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/ImdoTreasury.sol:337:13\n    │\n337 │             emit LegBought(LEG_IMD, Currency.unwrap(l.key.currency1), ethIn, out);\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/ImdoTreasury.sol:339:13\n    │\n339 │             emit LegFailed(LEG_IMD, ethIn, reason);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/ImdoTreasury.sol:340:51\n    │\n340 │             if (l.failures == 0) 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/ImdoTreasury.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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/ImdoTreasury.sol:373:44\n    │\n373 │                 l.checkpointSqrtPriceX96 = uint160(next);\n    │                                            ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint160' is safe 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/ImdoTreasury.sol:374:34\n    │\n374 │                 l.checkpointAt = 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/ImdoTreasury.sol:379:44\n    │\n379 │                 l.checkpointSqrtPriceX96 = uint160(lifted);\n    │                                            ━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint160' is safe 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/ImdoTreasury.sol:382:40\n    │\n382 │         emit CheckpointRefreshed(post, uint160(next));\n    │                                        ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint160' is safe 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/ImdoTreasury.sol:401:36\n    │\n401 │             _imdLeg.checkpointAt = 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/fork/RegenRouterBaseFork.t.sol:177:21\n    │\n177 │         uint256 t = block.timestamp;\n    │                     ━━━━━━━━━━━━━━━\n    ‡\n180 │             vm.warp(t);\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/RegenDrop.t.sol:98:17\n   │\n98 │         vm.warp(block.timestamp + drop.ACTIVATION_DELAY() - 1);\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/RegenDrop.t.sol:68:17\n   │\n68 │         vm.warp(block.timestamp + drop.ACTIVATION_DELAY());\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/RegenRouter.t.sol:448:17\n    │\n448 │         vm.warp(block.timestamp + INTERVAL);\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/RegenRouter.t.sol:449:26\n    │\n448 │         vm.warp(block.timestamp + INTERVAL);\n    │         ─────────────────────────────────── `vm.warp` changes this environment here\n449 │         feed.set(2500e8, block.timestamp);\n    │                          ━━━━━━━━━━━━━━━\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/RegenRouter.t.sol:541:26\n    │\n541 │         uint256 nextAt = block.timestamp + INTERVAL;\n    │                          ━━━━━━━━━━━━━━━\n    ‡\n545 │         vm.warp(nextAt - 1);\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/RegenRouter.t.sol:542:54\n    │\n542 │         assertEq(router.lastRetiredAt(1, depositor), block.timestamp);\n    │                                                      ━━━━━━━━━━━━━━━\n    ‡\n545 │         vm.warp(nextAt - 1);\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/RegenRouter.t.sol:546:26\n    │\n546 │         feed.set(2500e8, block.timestamp);\n    │                          ━━━━━━━━━━━━━━━\n    ‡\n549 │         vm.warp(nextAt);\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/RegenRouter.t.sol:1018:78\n     │\n 448 │         vm.warp(block.timestamp + INTERVAL);\n     │         ─────────────────────────────────── `vm.warp` changes this environment here\n     ‡\n1018 │         vm.expectRevert(abi.encodeWithSelector(RegenRouter.TooSoon.selector, block.timestamp + INTERVAL));\n     │                                                                              ━━━━━━━━━━━━━━━\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":10520,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/unit/CheckpointRepeatedPins.t.sol:RepeatedPinsTest\n[PASS] test_repeatedDustPinsRatchetTheFloorAboveSpotForDays() (gas: 8637844)\nLogs:\n  checkpoint / original checkpoint (bps): 9999\n  checkpoint / market (bps): 10019\n  failed process() calls before recovery: 0\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 10.56ms (7.70ms CPU time)\n\nRan 2 tests for test/unit/CheckpointRefresh.t.sol:CheckpointRefreshTest\n[PASS] test_checkpointRatchetsDownUnderSandwichedBuys() (gas: 12929322)\nLogs:\n  checkpoint / market (bps): 10019\n  IMD bought in round 2: 137923750651987558555\n  IMD bought in round 19: 133406744792138978705\n\n[PASS] test_inflatedDustBuyCannotPinFloorAboveSpotForDays() (gas: 1530040)\nLogs:\n  failed process() calls before recovery: 0\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 14.92ms (12.11ms CPU time)\n\nRan 6 tests for test/unit/LaunchToken.t.sol:IMDOTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 79928, ~: 80361)\n[PASS] test_metadata() (gas: 24431)\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 7.70ms (7.40ms CPU time)\n\nRan 3 tests for test/unit/RegenDrop.t.sol:RegenDropFuzz\n[PASS] testFuzz_payoutsMatchTheTree(uint96,uint96,uint96) (runs: 256, μ: 532274, ~: 536840)\n[PASS] testFuzz_twoEpochsEndAtTheLatestTotal(uint64,uint64,bool) (runs: 256, μ: 613899, ~: 582526)\n[PASS] testFuzz_underfundedRootRefused(uint96,uint96) (runs: 256, μ: 118045, ~: 118818)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 74.37ms (73.64ms CPU time)\n\nRan 15 tests for test/unit/RegenDrop.t.sol:RegenDropTest\n[PASS] test_aDueRootIsLiveBeforeTheNextPostIsChecked() (gas: 360325)\n[PASS] test_aLoweredTotalPaysNothingMoreAndLeavesTheContractShort() (gas: 782214)\n[PASS] test_aRootCannotPromiseMoreThanTheContractHasHeld() (gas: 554029)\n[PASS] test_aWrongRootCanBeReplacedOrCancelledInsideTheWindow() (gas: 631250)\n[PASS] test_cannotClaimTwiceOrWithABadProof() (gas: 518031)\n[PASS] test_claimPaysTheAccountEvenWhenAnotherCalls() (gas: 407838)\n[PASS] test_claimableView() (gas: 395646)\n[PASS] test_constructorRejectsZero() (gas: 6178)\n[PASS] test_eventsAreEmitted() (gas: 347665)\n[PASS] test_oldProofStopsWorkingOnceANewRootIsLive() (gas: 496471)\n[PASS] test_onlySafePostsAndCancels() (gas: 77497)\n[PASS] test_rootIsNotClaimableUntilTheDelayPasses() (gas: 440868)\n[PASS] test_runningTotalsPayOnlyTheDifference() (gas: 752621)\n[PASS] test_theCommittedTotalNeverShrinks() (gas: 396315)\n[PASS] test_theSafeHasNoWayToTakeTheTokens() (gas: 316961)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 6.81ms (6.03ms CPU time)\n\nRan 7 tests for test/unit/RegenReceiver.t.sol:OffsetsForwarderTest\n[PASS] test_constructorBoundsTheGasLimit() (gas: 1252702)\n[PASS] test_constructorRefusesATreasuryOrBridgeWithNoCode() (gas: 241669)\n[PASS] test_constructorRefusesZeroes() (gas: 342088)\n[PASS] test_forwardPullsThenBridgesEverything() (gas: 191288)\n[PASS] test_nothingAnywhereReverts() (gas: 36873)\n[PASS] test_nothingOwedButBalanceHeldStillBridges() (gas: 97166)\n[PASS] test_onlyTheForwarderCanPullTheLeg() (gas: 82179)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 831.26µs (436.30µs CPU time)\n\nRan 4 tests for test/unit/RegenReceiver.t.sol:RegenReceiverTest\n[PASS] test_ethSentBeforeDeployIsForwardedAfter() (gas: 306149)\n[PASS] test_predictMatchesDeployAndIsStable() (gas: 270521)\n[PASS] test_receiveAcceptsPlainEthWithLittleGas() (gas: 295343)\n[PASS] test_unregisteredProjectCannotGetAReceiver() (gas: 60858)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 380.91µs (231.61µs CPU time)\n\nRan 5 tests for test/unit/DeployRegenRail.t.sol:DeployRegenRailTest\n[PASS] test_aBadRegenAddressFailsTheWholeDeployment() (gas: 16819035)\n[PASS] test_aReceiverDeployedBySomeoneElseFirstIsTaken() (gas: 21957835)\n[PASS] test_deploysRegistersFirstAndDeploysTheReceiver() (gas: 22170534)\n[PASS] test_preflightExercisesTheCreditLeg() (gas: 67392312)\n[PASS] test_preflightRefusesBeforeAnyCreation() (gas: 133355988)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 258.11ms (24.57ms CPU time)\n\nRan 1 test for test/unit/CheckpointDecayAnchor.t.sol:FloorCompoundsTest\n[PASS] test_sandwichedRefreshesCompoundTheDecayBelowTheDocumentedHyperbola() (gas: 263217271)\nLogs:\n  elapsed seconds: 259200\n  documented floor / cp0 (bps): 6999\n  enforced floor / cp0 (bps): 6999\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 304.83ms (302.72ms CPU time)\n\nRan 4 tests for test/unit/TreasuryBoundaries.t.sol:TreasuryBoundariesTest\n[PASS] test_failedFlushRetainsTokensAndClearsApprovalOnSuccessfulRetry() (gas: 680963)\n[PASS] test_keeperDebtsAreIsolatedAndFailedPullPreservesBoth() (gas: 1538634)\n[PASS] test_loweringCapBelowUsedCapacityCannotResplitPendingFunds() (gas: 1505480)\n[PASS] test_oldPendingRegenSurvivesEpochRollAndDoesNotConsumeNewCap() (gas: 1483638)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 304.16ms (2.95ms CPU time)\n\nRan 48 tests for test/unit/RegenRouter.t.sol:RegenRouterTest\n[PASS] testFuzz_ethConservation(uint96,uint8) (runs: 256, μ: 2218569, ~: 1384101)\n[PASS] test_aBrokenPoolQuoteSkips() (gas: 282650)\n[PASS] test_aCallerThatCannotTakeEthForfeitsTheBounty() (gas: 1499802)\n[PASS] test_aCreditWhoseBalanceReadRevertsIsPassedOver() (gas: 1314468)\n[PASS] test_aCreditWhoseRetirementRevertsIsPassedOver() (gas: 923156)\n[PASS] test_aPoolMovedBetweenThePlanAndTheRetirementRevertsTheBatch() (gas: 467734)\n[PASS] test_aProxyWithoutAnAggregatorOrAMalformedOneIsNotAClamp() (gas: 1393629)\n[PASS] test_aPumpedKvcmPoolIsSkippedBeforeAnythingIsSpent() (gas: 412765)\n[PASS] test_aRemainderAloneCanBeRetiredWithTheNextSlice() (gas: 1492252)\n[PASS] test_aRemainderIsRetiredWhenTheEthIsGone() (gas: 1573851)\n[PASS] test_aRemainderUnderMinBatchIsProcessedWithNoBounty() (gas: 1842648)\n[PASS] test_aSandwichedSwapIsRefusedAtTheCeiling() (gas: 1097314)\n[PASS] test_aSliverOfTheFirstCreditDoesNotCaptureTheBatch() (gas: 1398121)\n[PASS] test_aVaultThatAbsorbsOnlyPartLeavesTheRestWithTheDepositor() (gas: 2504646)\n[PASS] test_anAggregatorWordWithDirtyUpperBitsIsNotAClamp() (gas: 832272)\n[PASS] test_anAnswerOnTheClampIsStale() (gas: 1626505)\n[PASS] test_anAverageMovedWithThePriceIsTheDocumentedLimit() (gas: 899776)\n[PASS] test_anUnsupportedTokenIsSkippedNotFatal() (gas: 6708401)\n[PASS] test_atMostOneBatchOfCarryIsSpentPerBatch() (gas: 3502431)\n[PASS] test_batchIsCappedAndTheRestWaits() (gas: 1730045)\n[PASS] test_bridgedEthNamesTheReceiverAsSource() (gas: 1104864)\n[PASS] test_constructorBounds() (gas: 10192965)\n[PASS] test_creditTokensFallThroughInOrder() (gas: 1355956)\n[PASS] test_depositNeedsAProjectAndValue() (gas: 150875)\n[PASS] test_donatedUsdcBelongsToNobody() (gas: 914684)\n[PASS] test_dustCarriesToTheNextBatchOfTheSameProject() (gas: 7234179)\n[PASS] test_emptyClassSkipsAndKeepsTheEth() (gas: 342797)\n[PASS] test_grammarV1InvalidVectorsTheRouterCannotProduce() (gas: 86652)\n[PASS] test_grammarV1ValidVectorsAreWhatTheRouterBuilds() (gas: 660482)\n[PASS] test_messageIsGrammarV1ByteForByte() (gas: 66911)\n[PASS] test_minOutFollowsTheOracle() (gas: 11782)\n[PASS] test_nothingToProcessReverts() (gas: 99101)\n[PASS] test_oneRetirementPerDepositorPerInterval() (gas: 2574822)\n[PASS] test_plainEthIsRefused() (gas: 34057)\n[PASS] test_processRetiresWithTheProjectAsBeneficiary() (gas: 946514)\n[PASS] test_reentryFromTheVenueIsRefused() (gas: 317592)\n[PASS] test_referenceKvcmIsTheAverageLessTheFee() (gas: 149248)\n[PASS] test_staleOrBrokenFeedSkipsAndKeepsTheEth() (gas: 1317588)\n[PASS] test_theAggregatorIsPaidAtMostThePlannedSpendPlusASliver() (gas: 1553747)\n[PASS] test_theAggregatorsRoundUpIsGivenAStepOfSlack() (gas: 962195)\n[PASS] test_theBarHoldsWhenTheClassIsUnderOneBatch() (gas: 1516092)\n[PASS] test_theBarIsTheOracleNotThePool() (gas: 303021)\n[PASS] test_theBarIsTheTimeWeightedPriceWithinTheTolerance() (gas: 1562978)\n[PASS] test_theCeilingHoldsWhateverThePoolSays() (gas: 1230534)\n[PASS] test_theReferenceTakesThePoolFeeOff() (gas: 1231044)\n[PASS] test_theRouterChecksTheFillItselfNotJustTheVenue() (gas: 3985175)\n[PASS] test_theSequencerMustBeUpForAnHour() (gas: 5308995)\n[PASS] test_whenEveryRetirementRevertsTheBatchRevertsAndTheEthStays() (gas: 645275)\nSuite result: ok. 48 passed; 0 failed; 0 skipped; finished in 320.01ms (365.96ms CPU time)\n\nRan 1 test for test/unit/Integration.t.sol:IntegrationTest\n[PASS] test_buyStakeProcessClaimAndWithdraw() (gas: 1545780)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 359.32ms (851.18µs CPU time)\n\nRan 5 tests for test/unit/Revision.t.sol:RevisionTest\n[PASS] testFuzz_exactOutputFeesAreGrossRate(uint32,uint128) (runs: 256, μ: 255832, ~: 256028)\n[PASS] test_emptySellCannotChargeFeeOrStartClock() (gas: 365680)\n[PASS] test_exactOutputAndInputBuySameGrossFeeAtLaunch() (gas: 357369)\n[PASS] test_openStartsFullDecayAfterDeploymentDelayAndSwapsBeforeItPayTheLaunchFee() (gas: 288268)\n[PASS] test_partialInputBuyRevertsAndReturnsEntireFee() (gas: 394742)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 404.01ms (47.63ms CPU time)\n\nRan 12 tests for test/unit/ImdoTreasury.t.sol:ImdoTreasuryTest\n[PASS] testFuzz_splitConservesEveryWei(uint96) (runs: 256, μ: 817088, ~: 880168)\n[PASS] test_capOverflowAndEpochRoll() (gas: 1503553)\n[PASS] test_capSetterBoundsAuthAndNoRetroactiveResplit() (gas: 979559)\n[PASS] test_cooldownAndCallbackAuthorization() (gas: 895434)\n[PASS] test_dustAndFlush() (gas: 528640)\n[PASS] test_exactSplitBountyAndAuthorizedPulls() (gas: 670464)\n[PASS] test_failedRegenRetryDoesNotChargeAgainOrBlockBuy() (gas: 1011544)\n[PASS] test_failedRewardNotifyRollsBackBuyAndHalvesRetry() (gas: 1205157)\n[PASS] test_missingDependenciesDoNotSeedPermanentZeroCheckpoint() (gas: 6084040)\n[PASS] test_rejectedAndReentrantPullPaymentsPreserveOwed() (gas: 1148265)\n[PASS] test_rejectingKeeperCanPullToAlternateRecipient() (gas: 886015)\n[PASS] test_staleCheckpointRecoversWithoutRerouting() (gas: 4483861)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 408.32ms (60.94ms CPU time)\n\nRan 4 tests for test/unit/HookDeferredFees.t.sol:HookDeferredFeesTest\n[PASS] test_failedRedemptionPreservesClaimAndCannotRedirect() (gas: 481346)\n[PASS] test_firstBuyOnTokenOnlyManagerThenPermissionlessRedemption() (gas: 346562)\n[PASS] test_freshManagerExactOutputBuyAlsoDefersFee() (gas: 271630)\n[PASS] test_fundedManagerUsesOriginalDirectPayment() (gas: 187789)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 426.79ms (2.05ms CPU time)\n\nRan 1 test for test/fuzz/Treasury.fuzz.t.sol:TreasuryFuzzTest\n[PASS] testFuzz_capAndConservation(uint96) (runs: 256, μ: 523589, ~: 523602)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 658.11ms (304.40ms CPU time)\n\nRan 5 tests for test/unit/ImdoClaim.t.sol:ImdoClaimTest\n[PASS] test_duplicateIdsAndOwnerChecks() (gas: 250140)\n[PASS] test_holderProofPartialDoubleClaimAndStake() (gas: 529656)\n[PASS] test_launchDeadlineAndBurnBoundaries() (gas: 335174)\n[PASS] test_seatTransferMovesOnlyRemainingEntitlement() (gas: 522259)\n[PASS] test_zeroRootDisablesHolderClaims() (gas: 219423)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 659.91ms (1.19ms CPU time)\n\nRan 23 tests for test/unit/ImdoHook.t.sol:ImdoHookTest\n[PASS] testFuzz_buyFeeMatchesSchedule(uint256,uint32) (runs: 256, μ: 248090, ~: 248311)\n[PASS] testFuzz_decayIsMonotoneAndBounded(uint32,uint32) (runs: 256, μ: 80486, ~: 80460)\n[PASS] test_addressCarriesExactlyTheRequiredFlags() (gas: 6313)\n[PASS] test_antiSnipeFeeAtLaunchIs20Percent() (gas: 178527)\n[PASS] test_buyExactInput_takes1_5PercentOfEthIn() (gas: 252222)\n[PASS] test_buyExactOutput_feeIsAddedOnTopOfEthIn() (gas: 253098)\n[PASS] test_callbacksRejectNonPoolManager() (gas: 90785)\n[PASS] test_constructorRejectsUnflaggedAddress() (gas: 12526)\n[PASS] test_decaySchedule() (gas: 107393)\n[PASS] test_feeCannotBeRaisedOrKept() (gas: 122100)\n[PASS] test_feeEventsAreEmitted() (gas: 241191)\n[PASS] test_initialState() (gas: 43218)\n[PASS] test_initializeOnlyOnce() (gas: 46337)\n[PASS] test_initializeRejectsWrongPair() (gas: 54929091)\n[PASS] test_initializeRequiresOwner() (gas: 55040574)\n[PASS] test_lowerFee() (gas: 114477)\n[PASS] test_lowerFeeDuringDecayKeepsDecaying() (gas: 101561)\n[PASS] test_lowerFeeToZeroDisablesFee() (gas: 264697)\n[PASS] test_onlyOwnerLowersFee() (gas: 35246)\n[PASS] test_openIsOwnerOnlyOnceAndStartsTheClockWithoutGatingTrades() (gas: 454015)\n[PASS] test_sellExactInput_takes1_5PercentOfEthOut() (gas: 369450)\n[PASS] test_sellExactOutput_feeIsAddedOnTopOfEthOut() (gas: 371518)\n[PASS] test_twoStepOwnershipTransfer() (gas: 157226)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 737.32ms (1.06s CPU time)\n\nRan 15 tests for test/unit/RegenRegistry.t.sol:RegenRegistryTest\n[PASS] testFuzz_aMutatedAddressNeverVerifies(uint8,uint8) (runs: 256, μ: 39532, ~: 39548)\n[PASS] testFuzz_randomBytesNeverPassAsRegenAddressUnlessCharsetHolds(bytes) (runs: 256, μ: 6649, ~: 6637)\n[PASS] test_aHighByteIsRefusedNotAPanic() (gas: 54109)\n[PASS] test_badNamesRevert() (gas: 61478)\n[PASS] test_badRegenAddressReverts() (gas: 92031)\n[PASS] test_checksumIsVerified() (gas: 135872)\n[PASS] test_grammarV1InvalidVectorsTheRegistryRefuses() (gas: 105582)\n[PASS] test_nameIsUniqueOnItsKey() (gas: 897544)\n[PASS] test_nameKeyLowersAndCollapsesSpacesOnly() (gas: 99282)\n[PASS] test_nameRules() (gas: 121882)\n[PASS] test_regenAddressRules() (gas: 129525)\n[PASS] test_registerStoresEverythingAndNumbersFromOne() (gas: 490010)\n[PASS] test_theFirstProjectCanBeRegisteredAtCreation() (gas: 242142)\n[PASS] test_unknownProjectReverts() (gas: 19200)\n[PASS] test_zeroBeneficiaryRefused() (gas: 32768)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 876.83ms (893.37ms CPU time)\n\nRan 7 tests for test/unit/ImdoDeploy.t.sol:ImdoDeployTest\n[PASS] test_exactlyOneOffsetsSeat() (gas: 41215077)\n[PASS] test_forwarderSeatIsPredictedCreatedAndChecked() (gas: 64840330)\n[PASS] test_manualSignerRunUsesArgumentsRegardlessOfCaller() (gas: 99072063)\n[PASS] test_scriptDeploysFundsSeedsBurnsLpAndStartsOwnershipTransfer() (gas: 63910512)\n[PASS] test_scriptRejectsMissingCodeWrongChainAndBadTickBeforeCreation() (gas: 20288893)\n[PASS] test_scriptRequiresLaunchLeadBeforeAnyCreation() (gas: 63514725)\n[PASS] test_sellersCanOnlyTakeOutWhatBuyersPaidIn() (gas: 64770621)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 877.91ms (1.64s CPU time)\n\nRan 4 tests for test/unit/HookAdversarial.t.sol:HookAdversarialTest\n[PASS] testFuzz_exactInputSellChargesGrossEthAtAnyDecayTime(uint256,uint32,uint16) (runs: 1000, μ: 429339, ~: 429769)\n[PASS] testFuzz_exactOutputSellDeliversRequestedNetAndGrossFee(uint256,uint32) (runs: 1000, μ: 397584, ~: 397788)\n[PASS] test_decayBoundariesAndNoFeeResurrection() (gas: 571567)\n[PASS] test_partialExactOutputSellRevertsFeeAndPoolMovement() (gas: 472278)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 878.06ms (962.76ms CPU time)\n\nRan 9 tests for test/unit/ClaimAdversarial.t.sol:ClaimAdversarialTest\n[PASS] testFuzz_eachDailyTrancheIsPaidOnlyOnceAcrossWalletAndStake(uint16) (runs: 1000, μ: 3271849, ~: 3304165)\n[PASS] test_batchWithUnauthorizedLastSeatRollsBackEarlierEntitlement() (gas: 229421)\n[PASS] test_emptyBatchNeverChangesBalancesOrLocks() (gas: 56074)\n[PASS] test_excludedNftOwnerCanTakeWalletClaimAfterFailedStake() (gas: 352548)\n[PASS] test_holderRejectsSingleHashLeafAndReorderedProof() (gas: 86101)\n[PASS] test_operatorApprovalDoesNotGiveClaimRights() (gas: 101937)\n[PASS] test_overcommittedHolderTreeCannotSpendSeatReserve() (gas: 354193)\n[PASS] test_recordingNewOwnerWithoutNewTrancheDoesNotResetExistingStakeLock() (gas: 493375)\n[PASS] test_underfundedHolderPaymentRollsBackAndCanRetryAfterFunding() (gas: 281686)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 878.79ms (880.19ms CPU time)\n\nRan 1 test for test/invariant/Treasury.invariant.t.sol:TreasuryInvariantTest\n[PASS] invariant_ethEqualsOwedPlusPendingAndUnsplit() (runs: 128, calls: 6144, reverts: 0)\n\n╭-----------------+----------+-------+---------+----------╮\n| Contract        | Selector | Calls | Reverts | Discards |\n+=========================================================+\n| TreasuryHandler | cap      | 1014  | 0       | 0        |\n|-----------------+----------+-------+---------+----------|\n| TreasuryHandler | fund     | 995   | 0       | 0        |\n|-----------------+----------+-------+---------+----------|\n| TreasuryHandler | keeper   | 1033  | 0       | 0        |\n|-----------------+----------+-------+---------+----------|\n| TreasuryHandler | pay      | 982   | 0       | 0        |\n|-----------------+----------+-------+---------+----------|\n| TreasuryHandler | process  | 1056  | 0       | 0        |\n|-----------------+----------+-------+---------+----------|\n| TreasuryHandler | withdraw | 1064  | 0       | 0        |\n╰-----------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 897.09ms (549.17ms CPU time)\n\nRan 1 test for test/invariant/Staking.invariant.t.sol:StakingInvariantTest\n[PASS] invariant_principalAndRewardConservation() (runs: 128, calls: 8192, reverts: 0)\n\n╭----------------+----------+-------+---------+----------╮\n| Contract       | Selector | Calls | Reverts | Discards |\n+========================================================+\n| StakingHandler | advance  | 1344  | 0       | 0        |\n|----------------+----------+-------+---------+----------|\n| StakingHandler | claim    | 1353  | 0       | 0        |\n|----------------+----------+-------+---------+----------|\n| StakingHandler | notify   | 1414  | 0       | 0        |\n|----------------+----------+-------+---------+----------|\n| StakingHandler | stake    | 1353  | 0       | 0        |\n|----------------+----------+-------+---------+----------|\n| StakingHandler | unstake  | 1378  | 0       | 0        |\n|----------------+----------+-------+---------+----------|\n| StakingHandler | withdraw | 1350  | 0       | 0        |\n╰----------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.71s (1.71s CPU time)\n\nRan 2 tests for test/invariant/RegenRouter.invariant.t.sol:RegenRouterInvariantTest\n[PASS]\nRegenRouterInvariantTest invariants:\n[PASS] invariant_ethIsConserved\n[PASS] invariant_noStandingAllowance\n[PASS] invariant_routerEthIsTheSumOfBalances\n[PASS] invariant_tonnesAreCountedOnce\n[PASS] invariant_usdcIsConservedAndAttributed\n RegenRouterInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭--------------------+-------------+-------+---------+----------╮\n| Contract           | Selector    | Calls | Reverts | Discards |\n+===============================================================+\n| RegenRouterHandler | breakRetire | 791   | 0       | 0        |\n|--------------------+-------------+-------+---------+----------|\n| RegenRouterHandler | deposit     | 806   | 0       | 0        |\n|--------------------+-------------+-------+---------+----------|\n| RegenRouterHandler | moveAverage | 876   | 0       | 0        |\n|--------------------+-------------+-------+---------+----------|\n| RegenRouterHandler | moveFill    | 829   | 0       | 0        |\n|--------------------+-------------+-------+---------+----------|\n| RegenRouterHandler | moveSpot    | 802   | 0       | 0        |\n|--------------------+-------------+-------+---------+----------|\n| RegenRouterHandler | overPull    | 817   | 0       | 0        |\n|--------------------+-------------+-------+---------+----------|\n| RegenRouterHandler | process     | 815   | 0       | 0        |\n|--------------------+-------------+-------+---------+----------|\n| RegenRouterHandler | roundUp     | 802   | 0       | 0        |\n|--------------------+-------------+-------+---------+----------|\n| RegenRouterHandler | supply      | 862   | 0       | 0        |\n|--------------------+-------------+-------+---------+----------|\n| RegenRouterHandler | tick        | 792   | 0       | 0        |\n╰--------------------+-------------+-------+---------+----------╯\n\n[PASS] test_handlerProcessesAndSkips() (gas: 3391499)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.75s (1.74s CPU time)\n\nRan 1 test for test/invariant/Hook.invariant.t.sol:HookInvariantTest\n[PASS] invariant_allChargedFeesRemainRedeemableOnlyToTreasury() (runs: 256, calls: 16384, reverts: 0)\n\n╭-------------+----------+-------+---------+----------╮\n| Contract    | Selector | Calls | Reverts | Discards |\n+=====================================================+\n| HookHandler | advance  | 2722  | 0       | 0        |\n|-------------+----------+-------+---------+----------|\n| HookHandler | buy      | 2777  | 0       | 0        |\n|-------------+----------+-------+---------+----------|\n| HookHandler | lower    | 2685  | 0       | 0        |\n|-------------+----------+-------+---------+----------|\n| HookHandler | open     | 2649  | 0       | 0        |\n|-------------+----------+-------+---------+----------|\n| HookHandler | redeem   | 2835  | 0       | 0        |\n|-------------+----------+-------+---------+----------|\n| HookHandler | sell     | 2716  | 0       | 0        |\n╰-------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.50s (2.00s CPU time)\n\nRan 1 test for test/invariant/Claim.invariant.t.sol:ClaimInvariantTest\n[PASS] invariant_entitlementsAndCustodyMatchIndependentModel() (runs: 256, calls: 24576, reverts: 0)\n\n╭--------------+--------------+-------+---------+----------╮\n| Contract     | Selector     | Calls | Reverts | Discards |\n+==========================================================+\n| ClaimHandler | advance      | 3541  | 0       | 0        |\n|--------------+--------------+-------+---------+----------|\n| ClaimHandler | burn         | 3444  | 0       | 0        |\n|--------------+--------------+-------+---------+----------|\n| ClaimHandler | claimHolder  | 3649  | 0       | 0        |\n|--------------+--------------+-------+---------+----------|\n| ClaimHandler | claimSeat    | 3479  | 0       | 0        |\n|--------------+--------------+-------+---------+----------|\n| ClaimHandler | exit         | 3407  | 0       | 0        |\n|--------------+--------------+-------+---------+----------|\n| ClaimHandler | stakeAgain   | 3526  | 0       | 0        |\n|--------------+--------------+-------+---------+----------|\n| ClaimHandler | transferSeat | 3530  | 0       | 0        |\n╰--------------+--------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 9.37s (9.37s CPU time)\n\nRan 1 test for test/invariant/RegenDrop.invariant.t.sol:RegenDropInvariant\n[PASS]\nRegenDropInvariant invariants:\n[PASS] invariant_claimsStayWithinTheLiveCommitment\n[PASS] invariant_commitmentIsFunded\n[PASS] invariant_tokensAreConserved\n RegenDropInvariant invariants (runs: 256, calls: 128000, reverts: 0)\n\n╭-------------+------------+-------+---------+----------╮\n| Contract    | Selector   | Calls | Reverts | Discards |\n+=======================================================+\n| DropHandler | cancel     | 21097 | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| DropHandler | claimAlice | 21439 | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| DropHandler | claimBob   | 21452 | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| DropHandler | fund       | 21564 | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| DropHandler | postGrowth | 21192 | 0       | 0        |\n|-------------+------------+-------+---------+----------|\n| DropHandler | wait       | 21256 | 0       | 0        |\n╰-------------+------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 10.12s (10.12s CPU time)\n\nRan 7 tests for test/unit/ImdoStaking.t.sol:ImdoStakingTest\n[PASS] testFuzz_lifetimeCreditSurvivesClaimsAndExit(uint128,uint128,uint96) (runs: 256, μ: 742993, ~: 750458)\n[PASS] test_backlogThresholdStreamPauseAndNoPastCredit() (gas: 1742638)\n[PASS] test_failedOrReentrantWithdrawalRollsBack() (gas: 718463)\n[PASS] test_invalidIntakeAndClaims() (gas: 188271)\n[PASS] test_rewardsProRataAndEthIsNeverClaimed() (gas: 898336)\n[PASS] test_stakeForRestrictedAndEveryStakeResetsLock() (gas: 566570)\n[PASS] test_withdrawOnlySafeBoundedEvenWithForcedEthAndNoStake() (gas: 296995)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 10.22s (10.22s CPU time)\n\nRan 11 tests for test/unit/DistributorAdversarial.t.sol:DistributorAdversarialTest\n[PASS] testFuzz_taxedRewardCreditsOnlyReceivedTokens(uint256,uint16) (runs: 1000, μ: 519788, ~: 522512)\n[PASS] testFuzz_twoEpochsFollowStakeOwnership(uint256,uint256,uint256,uint256) (runs: 1000, μ: 891203, ~: 898008)\n[PASS] test_allFixedExclusionsRejectStakeBeforeTokenInteraction() (gas: 418142)\n[PASS] test_claimCallbackCannotReenterAnyHolderOrRewardAction() (gas: 523169)\n[PASS] test_failedStakeRollsBackBacklogAndShares() (gas: 600805)\n[PASS] test_falseReturningIntakeCannotCreateCredit() (gas: 267523)\n[PASS] test_fullSupplyCanStakeAndExit() (gas: 601080)\n[PASS] test_noReturnTokenCanNotifyAndClaim() (gas: 478149)\n[PASS] test_notifyCallbackCannotReenterAnyHolderOrRewardAction() (gas: 370405)\n[PASS] test_oneWeiAndCompleteTransferTax() (gas: 543864)\n[PASS] test_pausedRewardCannotTrapUnstakedPrincipal() (gas: 633583)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 10.22s (20.44s CPU time)\n\nRan 30 test suites in 10.22s (55.26s CPU time): 207 tests passed, 0 failed, 0 skipped (207 total tests)\n","passed":true},{"durationMs":159,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"IMDOToken.approve(address,uint256)\",\"IMDOToken.transfer(address,uint256)\",\"IMDOToken.transferFrom(address,address,uint256)\",\"ImdoClaim.burnUnclaimed()\",\"ImdoClaim.claimHolder(uint256,bytes32[],bool)\",\"ImdoClaim.claimSeat(uint256[],bool)\",\"ImdoHook.acceptOwnership()\",\"ImdoHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"ImdoHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"ImdoHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"ImdoHook.lowerFee(uint16)\",\"ImdoHook.open()\",\"ImdoHook.redeemFees()\",\"ImdoHook.renounceOwnership()\",\"ImdoHook.transferOwnership(address)\",\"ImdoHook.unlockCallback(bytes)\",\"ImdoStaking.claim()\",\"ImdoStaking.claimReward(address)\",\"ImdoStaking.exit()\",\"ImdoStaking.notifyRegen()\",\"ImdoStaking.notifyReward(address,uint256)\",\"ImdoStaking.stake(uint256)\",\"ImdoStaking.stakeFor(address,uint256)\",\"ImdoStaking.unstake(uint256)\",\"ImdoStaking.withdrawRegen(uint256)\",\"ImdoTreasury.claimKeeper(address)\",\"ImdoTreasury.executeImd(uint256)\",\"ImdoTreasury.flushRewards()\",\"ImdoTreasury.payOffsets()\",\"ImdoTreasury.payOps()\",\"ImdoTreasury.process()\",\"ImdoTreasury.receive()\",\"ImdoTreasury.setRegenCap(uint256)\",\"ImdoTreasury.unlockCallback(bytes)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"OffsetsForwarder.forward()\",\"OffsetsForwarder.receive()\",\"RegenDrop.activate()\",\"RegenDrop.cancelPending()\",\"RegenDrop.claim(address,uint256,bytes32[])\",\"RegenDrop.postRoot(bytes32,uint256)\",\"RegenReceiver.forward()\",\"RegenReceiver.receive()\",\"RegenReceiverFactory.deploy(uint256)\",\"RegenRegistry.register(string,string,address)\",\"RegenRouter.deposit(uint256)\",\"RegenRouter.process(uint256,address)\",\"RegenRouter.receive()\",\"UniswapV3Venue.swapEthToToken(address,uint256,address)\"],\"files\":{\".gitignore\":4,\"LICENSE\":21,\"README.md\":90,\"SELF_AUDIT.md\":7,\"docs/DEPLOYMENT.md\":46,\"docs/HOOK_OWNER_SAFE.md\":93,\"docs/REGEN_RAIL.md\":165,\"docs/REVIEW.md\":49,\"foundry.toml\":32,\"launch.json\":47,\"remappings.txt\":9,\"script/DeployImdo.s.sol\":205,\"script/DeployRegenRail.s.sol\":179,\"script/rehearse-mainnet.sh\":31,\"script/utils/HookMiner.sol\":60,\"snapshot/axlregen-52295613.json\":1551,\"snapshot/axlregen-52473907.json\":1696,\"snapshot/axlregen-52485808.json\":1696,\"src/IMDOToken.sol\":6,\"src/ImdoClaim.sol\":142,\"src/ImdoHook.sol\":314,\"src/ImdoStaking.sol\":312,\"src/ImdoTreasury.sol\":405,\"src/LaunchToken.sol\":17,\"src/RegenDrop.sol\":115,\"src/interfaces/IImdoStaking.sol\":7,\"src/regen/OffsetsForwarder.sol\":69,\"src/regen/RegenReceiver.sol\":71,\"src/regen/RegenRegistry.sol\":160,\"src/regen/RegenRouter.sol\":566,\"src/regen/UniswapV3Venue.sol\":58,\"src/regen/interfaces/IAerodromePool.sol\":21,\"src/regen/interfaces/IEthToTokenSwap.sol\":26,\"src/regen/interfaces/IKlimaRetirementAggregator.sol\":82,\"test/REVISION.md\":7,\"test/TESTING.md\":48,\"test/fixtures/mode-e-grammar-v1-vectors.json\":203,\"test/fork/MainnetFork.t.sol\":55,\"test/fork/OffsetsForwarderMainnetFork.t.sol\":57,\"test/fork/RegenRouterBaseFork.t.sol\":232,\"test/fork/SeatClaimMainnetFork.t.sol\":120,\"test/fuzz/Treasury.fuzz.t.sol\":28,\"test/invariant/Claim.invariant.t.sol\":229,\"test/invariant/Hook.invariant.t.sol\":182,\"test/invariant/RegenDrop.invariant.t.sol\":106,\"test/invariant/RegenRouter.invariant.t.sol\":299,\"test/invariant/Staking.invariant.t.sol\":145,\"test/invariant/Treasury.invariant.t.sol\":102,\"test/unit/CheckpointDecayAnchor.t.sol\":144,\"test/unit/CheckpointRefresh.t.sol\":175,\"test/unit/CheckpointRepeatedPins.t.sol\":152,\"test/unit/ClaimAdversarial.t.sol\":175,\"test/unit/DeployRegenRail.t.sol\":194,\"test/unit/DistributorAdversarial.t.sol\":284,\"test/unit/HookAdversarial.t.sol\":104,\"test/unit/HookDeferredFees.t.sol\":125,\"test/unit/ImdoClaim.t.sol\":163,\"test/unit/ImdoDeploy.t.sol\":296,\"test/unit/ImdoHook.t.sol\":298,\"test/unit/ImdoStaking.t.sol\":214,\"test/unit/ImdoTreasury.t.sol\":337,\"test/unit/Integration.t.sol\":43,\"test/unit/LaunchToken.t.sol\":64,\"test/unit/RegenDrop.t.sol\":330,\"test/unit/RegenReceiver.t.sol\":191,\"test/unit/RegenRegistry.t.sol\":207,\"test/unit/RegenRouter.t.sol\":1471,\"test/unit/Revision.t.sol\":82,\"test/unit/TreasuryBoundaries.t.sol\":115,\"test/utils/ClaimHarness.sol\":76,\"test/utils/LocalV4.sol\":165,\"test/utils/Permit2Mock.sol\":23},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"49e8039fd198a743bbbef22c6543e4d50b26733df4298d35aec4d56d74c42197","verifiedTreeHash":"4442695d69788e3b0316f8d9cd9e0a2226796423","verifierVersion":"0.1.0+327e1e9f"}]}