{"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":"a38b37bd-6caa-4756-9aa2-6033c44ac713","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"7199b67e8747508bba80960fb47b6cac7c2efcf78fe9b03b4e90c10024646ee3","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"3d4b3310d24ef049a8237339e3998b39f443fe577ed06ed9f0694194d0d677ee","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":null,"dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","state":"waiting"},{"acceptedSubmissionHash":"bccb7434f4881f0542358602dde18c6638481fc2a7038b973ddf2e02d1b7f7f2","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"903d2d23e755f4eb4babf5c8aeac9ce071a3d8b9722fefb9fb27ffa49abe19df","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"883df1c6d0150dfd1c1d34edd453ef6f4484f1c3dbf3e053cf5b7b24e9cd31b6","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"587567a5ef71546b5973ceaf6ef1dbbd590f49503b5ac9a6973ed86b89584857","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"587567a5ef71546b5973ceaf6ef1dbbd590f49503b5ac9a6973ed86b89584857","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":null,"dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"failed"},{"acceptedSubmissionHash":"cfd8b60aefebfd40e441885adc921d5f34481e34a297d454def10ce55589ab70","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","state":"accepted"}],"objective":"Build a token with a Uniswap v4 hook and an NFT reward distributor for the Swarm Pepe collection (SPEPE, 0x999ce0CE8C5f7661e0c74a568FfE27CEB9177bDB, Ethereum mainnet). The collection contract cannot be changed and has no transfer hooks. Minting is still open (verify onchain; totalSupply() reverts, use totalMinted()). Launch kind univ4_hook on Ethereum mainnet, paired with ETH: the launch's standard token and pool with our hook, plus our distributor and auction contracts. Deploy in the launch. No website in this job: an existing site will integrate the contracts later, so keep clean ABIs, events and the views below. No owner powers after launch, no upgradeability, no pause.\n\nToken name: OG. Symbol: OG. The launch's standard token (1,000,000,000, 18 decimals, plain transfers); \"burn\" in this spec means sending to 0x000000000000000000000000000000000000dEaD, counted in a public totalBurned.\n\nWhen the hook acts: on every swap in this pool, taking its fee from the settled ETH deltas (beforeSwap/afterSwap with return deltas), and enforcing the launch protection below.\n\nHOOK FEE: 3.5% on every buy and every sell, always taken in ETH (from the ETH input on buys, from the ETH output on sells), computed on actual settled deltas for exact-input and exact-output swaps. Of it: 2.5% of the trade to the distributor, 1% of the trade to the team wallet 0x90738ABe9b04622Dc0b3d015a3964Cc7D1Fd1859.\n\nLAUNCH PROTECTION: open to everyone from the first block. For the first 60 minutes after the pool opens, the buy fee decays linearly from 50% to the normal 3.5%. Everything above 3.5% goes to the distributor backlog, none to the team. After minute 60: normal fees.\n\nDISTRIBUTOR (per tokenId, weighted, no loops):\n- An NFT earns nothing until activated. Levels and weights: L1 = 1, L2 = 2, L3 = 4.\n- Activation burns OG. Cumulative cost: L1 = 50,000, L2 = 150,000, L3 = 400,000 OG (upgrading pays only the difference). Costs fixed in code, no oracle. Only the current ownerOf(tokenId) can activate or upgrade. Also activation with ETH: the contract buys the exact OG amount through the pool (paying the hook fee like any buyer) and burns it.\n- Accrual: accPerWeight += eth * SCALE / totalWeight; pending(id) = weight[id] * accPerWeight / SCALE - debt[id]. A new activation or upgrade settles pending first and sets debt at the current accumulator, so it never earns past fees.\n- Level and pending ETH belong to the tokenId and travel with the NFT on any transfer or marketplace sale.\n- Backlog: fees that arrive while totalWeight = 0 (and the launch-protection surplus) are held and streamed into the accumulator linearly over 30 days once something is active, never paid as a windfall to the first activator.\n\nEXIT (the only way to get ETH):\n- No claim that keeps the NFT. To receive its pending ETH, the owner hands the NFT to the contract and is paid everything accrued on it in the same transaction. Its level is cleared and its weight leaves totalWeight.\n- Exit is not allowed within 24 hours after the NFT's last activation or upgrade.\n\nAUCTION:\n- The exited NFT is immediately listed in a Dutch auction priced in OG.\n- Start price = max(10x the previous auction's sale price, 500,000 OG). Exponential decay over 36 hours down to a floor of 50,000 OG; at the floor it stays listed until someone buys.\n- 100% of auction proceeds are burned. The buyer receives the NFT at level 0 with zero accrual and must activate it to earn.\n\nVIEWS for the existing website (also emit an event for every activation, upgrade, exit, auction listing, sale and fee split): totalWeight, active NFTs per level, pending(id), level(id), backlog left and its stream end, current auctions with live prices, last sale price, total burned, launch fee now and minutes left of the decay.\n\nTESTS (Foundry, mainnet fork): fee in ETH on both sides and both exact modes and the 2.5/1 split; launch fee decay over 60 minutes and surplus to backlog; weighted accrual and upgrade math; no retroactive rewards; backlog streaming with zero active; level and pending travelling with transfers; exit payout and the 24h lock; auction start/decay/floor and burn; reentrancy on ETH sends and ERC-721 transfers; ETH solvency and weight conservation invariants. Independent review of the economics and every contract.","parentJobId":null,"planHash":"1808a70bcde7c2656aa03cfca6cc0e3a4625e88acae31e8041d3e6f176b733b9","previousHash":"7ac3d59f4f5465266b00baa258adc7e759716342c97c1f4caef9470329f9813d","projectId":"a38b37bd-6caa-4756-9aa2-6033c44ac713","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1036-og-symbol-og-the-launch-s-standard-token"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51316","feedbackHash":"4cc6159602f36e730b4202cb6fc9e1eacab8635d9db5913bcb65f4b4cc41d0ea","nodeKey":"audit_economics","submissionHash":"7199b67e8747508bba80960fb47b6cac7c2efcf78fe9b03b4e90c10024646ee3","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51146","feedbackHash":"803f2a44cc80b475227707854ac6a04538b41d1de7abda88f4d758e303233363","nodeKey":"audit_flow","submissionHash":"3d4b3310d24ef049a8237339e3998b39f443fe577ed06ed9f0694194d0d677ee","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52178","feedbackHash":"4e83f70c226cc394ae3e56cee3c2ca1527d620823341ad007dfc5cc3bb80c907","nodeKey":"audit_math","submissionHash":"bccb7434f4881f0542358602dde18c6638481fc2a7038b973ddf2e02d1b7f7f2","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51540","feedbackHash":"a263ccfbacc3f90eeed7abee08a0b380f8a831ba69b3e8e23883ace6ab2f190f","nodeKey":"audit_permissions","submissionHash":"903d2d23e755f4eb4babf5c8aeac9ce071a3d8b9722fefb9fb27ffa49abe19df","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52375","feedbackHash":"b679de01ca263a75f6b90935a90932eb9319ac63cc8f5fe778ffc189f7b0f462","nodeKey":"write_foundry_tests","submissionHash":"cfd8b60aefebfd40e441885adc921d5f34481e34a297d454def10ce55589ab70","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"0a212c3368ff12f9e0a7e3b2db91ef4d055fe030a23e546f044b986e6c0b8d87","state":"blocked","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"45733b6bb4e97b16","findings":[],"hash":"07efc3938d9b714a655761a958f6ea0b6d09cf82184e91e874c7d5e94cac5178","nodeId":"36afc9ae-8254-4456-a379-7036c71c5c27","outcome":"failed","summary":"needs input: The required launch.json pool.initialPrice has no production value in the brief or accepted work. docs/launch-config.json lists initial sqrtPriceX96 as unresolved, and README.md identifies its numeric price as a test fixture only. The schema requires a decimal sqrtPriceX96 and provides no price placeholder; choosing a production price would invent an economic launch parameter. — What initial production price should the ETH/OG pool use, expressed as decimal sqrtPriceX96 (currency0 = native ETH, currency1 = OG), or equivalently as OG per ETH?","treeHash":null,"usage":{"cachedInputTokens":894080,"inputTokens":64825,"model":"gpt-6-astra","outputTokens":3057,"runtime":"codex","turns":5,"wallClockMs":267544}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"824e6de6196c686f","findings":[{"citation":"resolved","description":"The brief says the Dutch auction's start price is max(10x the previous auction's sale price, 500,000 OG) and lists a single 'last sale price' view. OGAuction.list reads lastSalePrice[id], a per-tokenId value, so the only sale that raises a start price is an earlier sale of that same NFT. Any NFT exiting for the first time always lists at 500,000 OG regardless of what the auction market just cleared at, and a tokenId that once sold at the 50,000 floor relists at the 500,000 minimum rather than 10x the most recent auction sale. The README documents the per-tokenId choice, so this is an interpretation divergence rather than an accounting error, but the deployed behaviour cannot be changed afterwards (no owner), so the requester should confirm which reading they meant before launch. If the global reading is intended, replace the mapping read with a single lastSalePrice storage word updated in buy().","line":47,"path":"src/OGAuction.sol","reproduction":"State: Alice owns SPEPE #1 and Bob owns #2, both activated at L1 and past the 24h lock. 1) Alice calls OGDistributor.exit(1); #1 lists at 500,000 OG. 2) Anyone calls OGAuction.buy(1, 500_000e18, recipient, deadline) at elapsed 0, burning 500,000 OG; lastSalePrice(1) = 500,000 OG. 3) Bob calls OGDistributor.exit(2). Expected under the brief's wording: price(2) == 5,000,000 OG (10x the previous auction's sale). Actual: price(2) == 500,000 OG, because lastSalePrice[2] is 0. Verified with a scratch Foundry test (test_startPriceIsPerTokenId) on the offline fixture.","severity":"low","snippet":"        uint256 start = lastSalePrice[id] * 10;","title":"Auction start price keys on the same tokenId's last sale, not the previous auction's sale"},{"citation":"resolved","description":"exit() is the only path that removes weight from totalWeight, and it requires msg.sender == ownerOf(id). SPEPE has no transfer hooks, so an owner (or a marketplace misconfiguration) can move an activated NFT with the unsafe ERC-721 transferFrom into OGAuction or OGDistributor. Neither contract has any code path that calls exit() or otherwise relinquishes the token: OGAuction only transfers tokens it listed, and OGDistributor never transfers out. The NFT's level and weight then stay in totalWeight forever. Every future fee and backlog stream credits a share to that weight, which is never paid out, so all other active holders are diluted permanently and the stranded share accumulates as unrecoverable ETH in the distributor. The README notes that unsolicited unsafe transfers cannot be rescued, but the effect is not confined to the sender: with totalWeight = 2 the honest holder loses half of every fee for the life of the contract, and the cost to create the condition is one L1 activation (50,000 OG). A permissionless forfeit(id) that is callable only when ownerOf(id) is OGAuction or OGDistributor and that clears the level and moves the stranded pending ETH into backlog would remove the dead weight without adding any owner power.","line":197,"path":"src/OGDistributor.sol","reproduction":"State: fresh deployment, Alice owns #1, Bob owns #2. 1) Alice: activate(1, 1); Bob: activate(2, 1); totalWeight == 2. 2) Alice: SPEPE.transferFrom(Alice, address(auction), 1) (plain, not safe). 3) Hook delivers 2 ETH of normal fees (receiveFees(2e18, 0)). Expected: Bob, the only NFT that can ever exit, is entitled to the full 2 ETH or the system has a way to clear #1. Actual: pending(2) == 1 ETH; pending(1) == 1 ETH is unreachable: exit(1) from Alice reverts Unauthorized (not owner), buy(1) reverts InvalidAuction (never listed), and OGAuction has no function that can call exit. totalWeight stays 2 forever. Verified with a scratch Foundry test (test_unsafeTransferIntoAuctionLeavesPermanentWeight) on the offline fixture.","severity":"low","snippet":"        if (collection.ownerOf(id) != msg.sender || level[id] == 0) revert Unauthorized();","title":"An activated NFT moved by plain transferFrom into the auction or distributor becomes permanent dead weight"},{"citation":"resolved","description":"OGHook.beforeSwap prices ETH-specified swaps by calling poolManager.swap on itself and relies on the manager's Hooks library returning early when msg.sender == address(hook) (otherwise beforeSwap would re-enter and revert Busy, making every exact-input buy and exact-output sell fail). The mainnet fork test documented in validation.md deploys a fresh PoolManager from the vendored v4-core commit rather than using the canonical mainnet manager at 0x000000000004444c5dc75cB358380D2e3dE08A90, so the launch-critical assumption was only tested against vendored code. I deployed the hook against the live manager on a fork at block 26,146,795 and ran all four swap modes at both the launch-hour and normal rates, plus activateWithETH and exit with a real SPEPE owner: every path completed with the expected 2.5%/1% split and no ERC-6909 claim fallback. No defect; recorded so the judge knows this assumption now has evidence and so the author can add the live-manager case to the fork suite.","line":37,"path":"test/SystemBase.sol","reproduction":"No failing input. Evidence: scratch test deploying OGHook via deployCodeTo at an address with low bits 0x20cc with constructor (0x000000000004444c5dc75cB358380D2e3dE08A90, new OG(), address(this)), initializing the ETH/OG 12500/60 pool from the test as factory, seeding full-range liquidity, then trading -0.1 ETH exact-in, +10,000 OG exact-out, -10,000 OG exact-in, +0.05 ETH exact-out at openedAt+1h and the two ETH-specified modes at openedAt+10m: all pass with team == gross/100 and distributor == gross*3.5% - team (launch-hour buys: distributor == gross*42.25% - team).","severity":"info","snippet":"        manager = new PoolManager(address(this));","title":"Shipped fork suite never exercises the live mainnet PoolManager; the self-quote's dependence on its hook self-call skip was unverified until this review"}],"hash":"3d4b3310d24ef049a8237339e3998b39f443fe577ed06ed9f0694194d0d677ee","nodeId":"e551b4eb-7de9-4967-bd03-a2a1b2ff0177","outcome":"completed","summary":"The findings file exists and is deliberately excluded from git by the harness. The working tree is otherwise clean, scratch tests are removed, and no source file was changed.\n\n## Review result\n\nThe assigned area (Execution Trace, Periphery, First Principles) was covered in full across all four contracts, and I found **no critical, high or medium defect**. The findings file holds two low findings and one informational note, with coverage rows for all 21 listed entry points plus four invariants.\n\n**What I verified**\n\n- Traced every hook callback through the v4 PoolManager's delta accounting for all four swap modes. I proved on paper that the exact-output sell's adjusted swap amount equals the quoted gross on full fills, and that partial fills re-bind at the same price limit, so `QuoteMismatch` is unreachable. A 1500-run fuzz over amounts, price limits, launch elapsed 0 to 2 hours and all modes confirmed the fee relation, the 1% team share, zero hook balance, claim totals equal to ERC-6909 balances, and redeemability after every swap.\n- Deployed the hook against the **live mainnet PoolManager** on a fork. The shipped fork test only uses a fresh manager, so the self-quote's reliance on the live manager skipping hook callbacks for the hook's own swap had never been exercised. All modes, `activateWithETH` with a real SPEPE owner, and `exit` pass there.\n- Probed flows the shipped suite skips: claim redemption after a sell drains pool ETH, ETH activation on a fresh manager followed by exit, exit-to-empty and re-activation in one block, repeated launch-hour stream restarts followed by full streaming, upgrades mid-stream, and exit while the team wallet rejects ETH. All behave as intended. Rounding analysis shows surplus is exactly zero after the launch hour, so normal buys can never restart the 30-day stream.\n- Confirmed SPEPE's deployed bytecode exposes a standard ERC-721 surface with no burn, pause or staking selectors, the team address is a plain EOA, and the shipped ABIs match the compiled output.\n\n**Findings written**\n\n1. **Low**: the auction start price keys on the same tokenId's last sale, not \"the previous auction's sale price\" as the brief words it. An NFT exiting for the first time always starts at 500,000 OG even after another NFT just sold for more.\n2. **Low**: an activated NFT moved by plain `transferFrom` into the auction or distributor keeps its weight forever. There is no path to clear it, so all other holders are diluted permanently for a 50,000 OG cost.\n3. **Info**: the live-manager coverage gap above, recorded with the evidence that it now passes.\n\nNo proofs were attached because nothing reached high or critical severity.","treeHash":null,"usage":{"cachedInputTokens":3601763,"inputTokens":642,"model":"claude-fable-5-1","outputTokens":93283,"runtime":"claude","turns":50,"wallClockMs":1676125}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"6b0a28df3d585600","findings":[{"citation":"resolved","description":"The brief defines the Dutch auction start price as max(10x the previous auction's sale price, 500,000 OG) and lists a single 'last sale price' view. OGAuction keys lastSalePrice by tokenId, so a listing only looks at that same NFT's own prior sale. With 1,242 minted (up to 5,000) SPEPE, the 10x ratchet almost never engages: every first exit of any NFT starts at exactly 500,000 OG regardless of what the previous auction cleared at, and a market that just paid 5,000,000 OG for one NFT gets the next one listed at 500,000. The README documents the per-tokenId choice, so this may be an intentional reading; it is reported because the brief's wording ('the previous auction', one 'last sale price' view) reads as a global value and the economic result differs materially (the ratchet is the only mechanism that lets auction burns scale with demand). No funds are lost either way; the judge should settle the intended rule. If global is intended, keep one lastSalePrice set in buy() and read it in list(), and expose it as the 'last sale price' view.","line":47,"path":"src/OGAuction.sol","reproduction":"State: two active NFTs (ids 1 and 2), each level 1, both past the 24h lock. 1) owner of id 1 calls OGDistributor.exit(1); id 1 lists at 500,000 OG. 2) anyone calls OGAuction.buy(1, 500_000e18, buyer, deadline) in the same block, paying the start price; lastSalePrice(1) == 500,000e18. 3) owner of id 2 calls OGDistributor.exit(2). Expected under the brief's global rule: price(2) == 5,000,000e18 (10 x the previous auction's sale). Actual: price(2) == 500,000e18 and lastSalePrice(2) == 0. Verified with test/scratch/Econ.t.sol::test_startPriceIgnoresPreviousAuctionOfOtherToken on this tree.","severity":"low","snippet":"        uint256 start = lastSalePrice[id] * 10;","title":"Auction start price anchors to the same tokenId's last sale, not the previous auction's sale price"},{"citation":"resolved","description":"Level, weight and pending ETH are keyed by tokenId and only the current ownerOf can exit. SPEPE has no burn, but a plain transferFrom to 0x...dEaD, to the auction/distributor/hook contracts (unsafe transfer bypasses onERC721Received) or to any contract without an exit path leaves the NFT's weight in totalWeight permanently. Every future fee and backlog stream keeps allocating that weight's share to pending(id), which no one can ever withdraw, and the remaining holders are permanently diluted by that fraction. Nobody profits, so this is griefing at the cost of the NFT plus its burned OG (50k to 400k OG), and it follows from the brief's rule that rewards travel with the token. Reported so the author can document it (the README mentions unsafe transfers into application contracts but not the permanent dilution of other holders) or decide whether a conservative recovery rule for provably dead owners is wanted. No code change is required by the brief.","line":197,"path":"src/OGDistributor.sol","reproduction":"State: id 1 active at level 3 (weight 4) owned by ALICE, id 2 active at level 1 (weight 1) owned by BOB. 1) ALICE calls SPEPE.transferFrom(ALICE, 0x000000000000000000000000000000000000dEaD, 1). 2) hook delivers 5 ETH of normal fees (receiveFees(5e18, 0)). Result: pending(1) == 4 ETH held by the distributor with no caller able to exit(1) (ownerOf(1) is the dead address), pending(2) == 1 ETH, totalWeight stays 5 forever; all future fees keep losing 80% to the dead weight. Verified with test/scratch/Econ.t.sol::test_activeNFTSentToDeadStrandsWeightAndETH.","severity":"info","snippet":"        if (collection.ownerOf(id) != msg.sender || level[id] == 0) revert Unauthorized();","title":"Weight of an active NFT moved to an address that can never call exit stays in totalWeight forever and strands its ETH share"}],"hash":"7199b67e8747508bba80960fb47b6cac7c2efcf78fe9b03b4e90c10024646ee3","nodeId":"50713d58-6cd3-473d-9be0-b6721ffec59f","outcome":"completed","summary":"Review complete. The findings file is written and validated, and no source files were changed (scratch tests live only under test/scratch/).\n\n**Outcome: no critical, high or medium defect found in the economics.** The fee math, launch decay, weighted accrual, backlog streaming, exit payout and auction pricing all behave as specified. I recorded two findings and a full coverage record in `.imd-findings.json`.\n\n**Findings**\n\n- **Low, auction start price semantics.** The brief says the start price is max(10x the previous auction's sale price, 500,000 OG) and lists one \"last sale price\" view. The code keys the last sale by tokenId, so after any NFT sells at 500,000 OG the next exited NFT still lists at 500,000 instead of 5,000,000. The README documents the per-token choice, so the judge should settle which reading the requester meant. Reproduced in a scratch test.\n- **Info, stranded weight.** An active NFT sent by plain transfer to the dead address or a contract with no exit path keeps its weight in totalWeight forever. Its share of every future fee accrues to pending ETH nobody can withdraw and other holders are permanently diluted. Nobody profits, so it is griefing at the cost of the NFT plus burned OG, and it follows from the brief's rule that rewards travel with the token. Reported for documentation.\n\n**What I verified and how**\n\n- Proved analytically that the exact-output sell quote always reproduces the real swap delta, that the launch surplus can never underflow, and that the 2.5/1 split holds for all four modes.\n- Wrote a 2,000-run fuzz over buy and sell, exact input and output, random price limits and every second of the launch hour against the real pool. No quote mismatch, no split error, no ETH left in the hook, and distributor accounting always equals the ETH received.\n- Confirmed the vendored v4 delta semantics the hook depends on, and checked on mainnet that the team wallet is an EOA and that SPEPE is a plain OpenZeppelin v5 ERC-721 with no burn, transfer hooks or operator filter.\n- Ran the mainnet fork lifecycle test against live SPEPE at two blocks. It passes. One run at \"latest\" reverted because of an RPC transient and passed when pinned to the same block.\n\n**Coverage**\n\nAll 22 listed entry points have rows. Twenty are `holds`, two reference the findings above (OGAuction.list and OGDistributor.exit). Four extra rows record the invariants I checked: ETH solvency, weight conservation, the fee split and decay, and the live fork lifecycle. Nothing in my area was left unreached.","treeHash":null,"usage":{"cachedInputTokens":2920091,"inputTokens":546,"model":"claude-fable-5-1","outputTokens":81068,"runtime":"claude","turns":46,"wallClockMs":1381258}},{"artifacts":[],"attempt":1,"bundleHash":"8d918c729607f1be5c14bc1b4d2e4b162b6f121f8462e77dad3af25760b1d064","device":"60f85cbc35c28a7e","findings":[],"hash":"883df1c6d0150dfd1c1d34edd453ef6f4484f1c3dbf3e053cf5b7b24e9cd31b6","nodeId":"aff73ce5-9206-4d77-be01-c757511afbbd","outcome":"completed","summary":"Implemented OG, the ETH-fee hook, NFT rewards, Dutch auctions, launch wiring, ABIs, and documentation.\n\n- Build and formatting checks pass.\n- Offline: 35 passed, 1 explicit fork skip.\n- Mainnet fork: 36 passed, no failures.\n- SPEPE minting verified open: 1,242 minted.\n- Independent review completed; claim-timing issue fixed.\n\nSee [README](README.md) and [validation results](docs/validation.md). No transactions broadcast; launch price and liquidity parameters remain documented deployment inputs.","treeHash":"ccefb3434f346154f341f50c2a9169e4bd8ceb31","usage":{"cachedInputTokens":5849600,"inputTokens":152124,"model":"gpt-6-astra","outputTokens":56281,"runtime":"codex","turns":13,"wallClockMs":2637793}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"589ef002581a5371","findings":[{"citation":"resolved","description":"The brief says: 'Start price = max(10x the previous auction's sale price, 500,000 OG)' and lists a single 'last sale price' view. The literal reading is one global ratchet: whatever the auction before this one sold for, times ten. The implementation stores lastSalePrice per tokenId (mapping(uint256 => uint256) lastSalePrice) and reads only lastSalePrice[id] in list(), so a tokenId's first listing always starts at 500,000 OG regardless of what other tokens just sold for, and the 'last sale price' view is per id, not global. The README documents the per-token choice, so this is a spec-interpretation deviation rather than a code error; it is in the Asymmetry lens (writer buy() keys by id, reader list() keys by id, while the specification's reader is global). Economic effect under the literal reading: after any token sells at 500,000 OG, the next listed token should open at 5,000,000 OG and decay for 36 h; the code opens it at 500,000 OG, so the burn on that sale can be up to 10x lower than specified. No funds are at risk and nobody can profit beyond the intended Dutch mechanics, so severity is low. If per-token was the intended reading, the fix is a note in the brief; otherwise replace the mapping with a single uint256 lastSalePrice written in buy() and read in list(), and expose it as the 'last sale price' view.","line":47,"path":"src/OGAuction.sol","reproduction":"Setup: SystemBase._system(true, true); mint tokenIds 1 (ALICE) and 2 (BOB); both activate L1; warp 24 h. (1) ALICE calls dist.exit(1): auction.price(1) == 500_000e18. (2) Any buyer calls auction.buy(1, 500_000e18, buyer, now): lastSalePrice(1) == 500_000e18. (3) BOB calls dist.exit(2). Expected under the brief's literal wording: auction.price(2) == 5_000_000e18 (10x the previous auction's sale). Actual: auction.price(2) == 500_000e18 (verified with test/scratch/Probe.t.sol::test_startPriceIsPerTokenNotGlobal, log 'start price token2 after token1 sold at 500k: 500000000000000000000000').","severity":"low","snippet":"        uint256 start = lastSalePrice[id] * 10;\n        if (start < MIN_START) start = MIN_START;","title":"Auction start price keys the 10x rule on the same tokenId's last sale, not on the previous auction's sale"}],"hash":"903d2d23e755f4eb4babf5c8aeac9ce071a3d8b9722fefb9fb27ffa49abe19df","nodeId":"9ea37d65-fb7a-418e-9c17-b8366c669e41","outcome":"completed","summary":"Review complete. The findings file is written and validated, and the tree is otherwise untouched (the findings file and scratch probes are both gitignored).\n\n**Outcome:** no access-control, trust-gap or asymmetry defect found in the hook, distributor or auction. One low-severity spec-conformance finding recorded.\n\n**What I checked in my area**\n\n- Every state-changing entry point the verifier listed, with its guard traced to the caller it assumes. All callbacks authenticate the PoolManager, the self-quote is self-only and always reverts, claim redemption and team payment are permissionless but parameterless with fixed destinations, and the distributor's three fee intake functions are hook-only with matching value checks.\n- Paired surfaces diffed: direct fee take vs ERC‑6909 claim fallback, activate vs activateWithETH, exit vs auction buy, pending view vs exit payout, schedule vs checkpoint. The claim fallback and direct branch produce identical entitlement at swap time, and the unfunded counter always equals outstanding claims.\n- Executed probes under `test/scratch/Probe.t.sol`: forcing the claim path by draining the manager inside an attacker's unlock, then exiting; reentering the auction and activate from an exit payout; redeeming inside a foreign unlock; a sole holder activating with ETH. All behaved as the design intends.\n- External assumptions verified against mainnet: the collection is plain OpenZeppelin ERC‑721 with no burn, pause or transfer gate; the team wallet is an EOA; the mainnet PoolManager holds about 51,800 ETH so the claim fallback is practically unreachable without the drain trick. Shipped ABIs and recorded source hashes match the tree.\n\n**The one finding (low):** `OGAuction.list` computes the 10x start price from that tokenId's own last sale, while the brief's wording (\"the previous auction's sale price\", single \"last sale price\" view) reads as a global ratchet. Reproduction and the executed probe output are in the file. The README documents the per-token choice, so this may be an accepted interpretation rather than a bug.\n\n**Coverage record:** 24 rows, 23 `holds`, 1 `finding`, none unreached. Static-analysis leads (reentrancy, strict equalities, write-after-write on the buying flag) were traced and none reached an exploitable state.","treeHash":null,"usage":{"cachedInputTokens":3017926,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":70723,"runtime":"claude","turns":46,"wallClockMs":1098850}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"f768e94767a9dde3","findings":[{"citation":"resolved","description":"_schedule() is called from _activate() whenever totalWeight goes 0 -> non-zero, from exit() when it goes to 0 (pause), and from _accountFees() on every launch-window surplus. Each call sets streamTotal = remaining backlog, released = 0 and streamEnd = now + 30 days. The brief says backlog is 'streamed into the accumulator linearly over 30 days once something is active'. Because the full STREAM_DURATION is re-applied to whatever is left, a stream that was 29/30 complete when weight drops to zero takes another full 30 days to pay out its last 1/30 once weight returns; the aggregate payout time of a backlog is unbounded if the active set empties repeatedly. During the first 60 minutes every surplus-bearing buy likewise pushes streamEnd to now+30d (at most ~31 days total, so harmless there). No ETH is lost or misdirected: backlog - _vested() is always conserved and solvency holds (verified with 3000-run fuzz and the repository's invariant suite). This is a timing/semantics deviation only; reported so the economics reviewer and author can confirm pause/restart is the intended reading. A fix that keeps the original schedule would store the per-second rate (streamTotal / 30 days) at first schedule and, on resume, set streamEnd = now + remaining/rate instead of now + 30 days.","line":108,"path":"src/OGDistributor.sol","reproduction":"State: fresh deployment, nothing active. 1) hook sends receiveFees(30 ether, 0) while totalWeight == 0 -> backlog = 30 ETH, streamEnd = 0. 2) Alice activates tokenId 1 at L1 at t0 -> streamEnd = t0 + 30 days. 3) At t0 + 29 days Alice calls exit(1): paid 29 ETH, backlogLeft() = 1 ETH, streamEnd = 0 (paused). 4) Same block, Bob activates tokenId 2 at L1 -> streamEnd = t0 + 59 days (observed 5097601 with t0 = 1), not t0 + 30 days. 5) At t0 + 30 days pending(2) = 33333333333333333 wei (0.0333 ETH) instead of the 1 ETH a linear 30-day schedule would have finished paying; the last 1 ETH only fully vests at t0 + 59 days (pending(2) = 1 ETH observed). Test used: test/scratch/Probe5.t.sol (fixture SystemBase._system(false,true), _fees/_activate helpers), all numbers above are the logged outputs.","severity":"info","snippet":"        streamEnd = backlog == 0 || totalWeight == 0 ? 0 : block.timestamp + STREAM_DURATION;","title":"Backlog stream restarts over a fresh 30 days on every empty->active transition, re-stretching the remaining backlog"}],"hash":"bccb7434f4881f0542358602dde18c6638481fc2a7038b973ddf2e02d1b7f7f2","nodeId":"7b44e301-a3c6-4ff7-9e1a-7b2a4f9833f2","outcome":"completed","summary":"Done. The findings file is written and validated; scratch tests are removed and the tree is otherwise untouched (only `.imd-findings.json` is new).\n\n## Summary — Math Precision / Boundary / Numerical Gap review\n\n**Result: no fund-affecting defects found in the assigned area. One `info` finding reported.**\n\n### What I verified (empirically, with scratch Foundry tests, since removed)\n- **Hook fee math, all 4 swap modes** — 3000-run fuzz with random amounts, 5 price-limit regimes (full and partial fills, including tick-crossing across concentrated positions), launch decay at 0–120 min, protocol fee enabled, and the ERC-6909 claims fallback. In every run `fee/gross == rate` within 1 wei, `team == gross/100`, `normal + surplus + team == fee`, and `QuoteMismatch` never fired. I also proved algebraically that for exact-output sells `gross − floor(gross·rate/WAD) == requested` always, and that `surplus ≥ 0` (so `fee − baseFee` cannot underflow).\n- **Dust boundaries** — 1–99 wei swaps round fees to zero without reverting; the only reverts I hit were v4's own `SafeCastOverflow` when my test asked for more than the pool held (identical without the hook).\n- **Distributor accrual/stream** — scaled credit/debt, no retroactive rewards, `_vested()` exact at `streamEnd`, no division by zero, `backlog − _vested()` never underflows; 50×1-wei fees to two L3 holders stayed solvent; claims-path exit after a 40 ETH 50%-fee buy paid exactly `pending`.\n- **Auction price** — `startPrice ≤ 1e28` is bounded by total supply so `wadLn` input is never 0; decay/floor continuous and monotone.\n\n### The one finding (info)\n`_schedule()` re-applies the full 30 days to the *remaining* backlog on every empty→active transition. Repro: 30 ETH backlog, sole L1 holder exits at day 29 (paid 29 ETH), another activates → the last 1 ETH takes a further 30 days (`pending` at the original day-30 end is 0.033 ETH, not 1 ETH). No loss or misdirection; it is a pause/restart reading of \"streamed linearly over 30 days\" the author should confirm.\n\n### Coverage\nAll 21 listed entry points have rows (20 `holds`, `exit` → finding 1), plus three invariant rows. Nothing was left `unreached`.\n\nOutside my area but worth the economics reviewer's eye (not reported as findings): the auction start price is tracked **per tokenId**, while the brief's \"10× the previous auction's sale price\" and the single \"last sale price\" view could be read as global.","treeHash":null,"usage":{"cachedInputTokens":2353097,"inputTokens":491,"model":"claude-fable-5-1","outputTokens":70459,"runtime":"claude","turns":39,"wallClockMs":1044788}},{"artifacts":[],"attempt":1,"bundleHash":"8539ca272030da1e17c2883a65b6f01961ebf7a72173bbc9957f1a559a5695ec","device":"0b9c1289a6def0a1","findings":[],"hash":"cfd8b60aefebfd40e441885adc921d5f34481e34a297d454def10ce55589ab70","nodeId":"d2175e21-0baf-4f23-bf72-3ebade6b5264","outcome":"completed","summary":"Added adversarial tests, fuzz properties, randomized invariants, mainnet fork checks, and a review under `test/`.\n\n- Build passed.\n- Offline suite: 54 passed, 2 fork suites skipped.\n- Mainnet fork: all 3 tests passed.\n- New invariants: 20,480 calls passed.\n- Verified minting open at block 26,146,970.\n- No confirmed contract defects found.","treeHash":"fa75c8b0f49d8dfba14330d7b32ca4d5d643df02","usage":{"cachedInputTokens":3692800,"inputTokens":158672,"model":"gpt-6-astra","outputTokens":27964,"runtime":"codex","turns":8,"wallClockMs":1041489}}],"verification":[{"checks":[{"durationMs":37969,"exitCode":0,"name":"build","output":"Compiling 85 files with Solc 0.8.26\nSolc 0.8.26 finished in 37.84s\nCompiler run successful!\nwarning[missing-events-access-control]: `unfundedFees` is changed without an event but is used for access control\n    ╭▸ src/OGDistributor.sol:121:9\n    │\n121 │         unfundedFees += normal + surplus;\n    │         ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/OGAuction.sol:38:48\n   │\n38 │     constructor(OG token_, ISpepe collection_, address distributor_) {\n   │                                                ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[missing-events-access-control]: `unfundedFees` is changed without an event but is used for access control\n    ╭▸ src/OGDistributor.sol:127:9\n    │\n127 │         unfundedFees -= msg.value;\n    │         ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[missing-events-access-control]: `level` is changed without an event but is used for access control\n    ╭▸ src/OGDistributor.sol:205:16\n    │\n205 │         delete level[id];\n    │                ━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/OGDistributor.sol:46:71\n   │\n46 │     constructor(OG token_, ISpepe collection_, IPoolManager manager_, address hook_) {\n   │                                                                       ━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/OGDistributor.sol:77:24\n   │\n77 │         uint256 now_ = block.timestamp < streamEnd ? block.timestamp : streamEnd;\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/OGHook.sol:117:16\n    │\n117 │         return elapsed >= DECAY ? NORMAL_FEE : NORMAL_FEE + 0.465e18 * (DECAY - elapsed) / DECAY;\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/OGHook.sol:123:16\n    │\n123 │         return elapsed >= DECAY ? 0 : (DECAY - elapsed + 59) / 60;\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/OGAuction.sol:60:13\n   │\n60 │         if (elapsed >= DURATION) return FLOOR;\n   │             ━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n   ╭▸ src/Interfaces.sol:30:22\n   │\n30 │         (bool ok,) = to.call{value: amount}(\"\");\n   │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/OGAuction.sol:61:13\n   │\n61 │         if (elapsed == 0) return a.startPrice;\n   │             ━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/OGAuction.sol:62:34\n   │\n62 │         int256 logarithm = wadLn(int256(FLOOR * 1e18 / a.startPrice));\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/OGAuction.sol:63:41\n   │\n63 │         uint256 result = a.startPrice * uint256(wadExp(logarithm * int256(elapsed) / int256(DURATION))) / 1e18;\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/OGAuction.sol:63:68\n   │\n63 │         uint256 result = a.startPrice * uint256(wadExp(logarithm * int256(elapsed) / int256(DURATION))) / 1e18;\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/OGAuction.sol:63:86\n   │\n63 │         uint256 result = a.startPrice * uint256(wadExp(logarithm * int256(elapsed) / int256(DURATION))) / 1e18;\n   │                                                                                      ━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `entered` is updated\n   ╭▸ src/OGAuction.sol:79:17\n   │\n79 │         require(token.transferFrom(msg.sender, token.DEAD(), cost));\n   │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `entered` is updated\n   ╭▸ src/OGAuction.sol:80:9\n   │\n80 │         collection.safeTransferFrom(address(this), recipient, id);\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/OGHook.sol:163:25\n    │\n163 │             quotedFee = fee;\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/OGAuction.sol:70:13\n   │\n70 │         if (block.timestamp > deadline || cost > maxPrice) revert SlippageOrExpired();\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/OGAuction.sol:81:9\n   │\n81 │         emit AuctionSold(id, msg.sender, recipient, cost);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/OGHook.sol:146:33\n    │\n146 │             uint256 requested = uint256(params.amountSpecified < 0 ? -params.amountSpecified : 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/OGHook.sol:149:48\n    │\n149 │                 quoteParams.amountSpecified = -int256(requested - fee);\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/OGHook.sol:152:29\n    │\n152 │                 if (gross > uint256(uint128(type(int128).max))) revert InvalidAmount();\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/OGHook.sol:152:37\n    │\n152 │                 if (gross > uint256(uint128(type(int128).max))) revert InvalidAmount();\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/OGHook.sol:153:47\n    │\n153 │                 quoteParams.amountSpecified = int256(gross);\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/OGHook.sol:158:31\n    │\n158 │                 if (actual != uint256(-quoteParams.amountSpecified)) fee = actual * rate / (WAD - rate);\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `level` is updated\n    ╭▸ src/OGDistributor.sol:147:17\n    │\n147 │         require(token.transferFrom(msg.sender, token.DEAD(), cost));\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/OGDistributor.sol:109:9\n    │\n109 │         emit StreamScheduled(backlog, streamStart, streamEnd);\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/OGDistributor.sol:192:23\n    │\n192 │         if (old == 0) emit Activated(id, msg.sender, newLevel, cost);\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/OGDistributor.sol:193:14\n    │\n193 │         else emit Upgraded(id, msg.sender, old, newLevel, cost);\n    │              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/OGHook.sol:172:40\n    │\n172 │             if (reason.length != 36 || bytes4(reason) != QuoteResult.selector) {\n    │                                        ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'bytes4' is safe 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 `level` is updated\n    ╭▸ src/OGDistributor.sol:160:36\n    │\n160 │         uint256 spent = abi.decode(poolManager.unlock(abi.encode(cost, msg.value, sqrtPriceLimitX96)), (uint256));\n    │                                    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `entered` is updated\n   ╭▸ src/Interfaces.sol:30:22\n   │\n30 │         (bool ok,) = to.call{value: amount}(\"\");\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/OGDistributor.sol:157:13\n    │\n157 │         if (block.timestamp > deadline) revert SlippageOrExpired();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/OGDistributor.sol:170:69\n    │\n170 │         BalanceDelta delta = poolManager.swap(key, SwapParams(true, int256(cost), limit), \"\");\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/OGDistributor.sol:171:25\n    │\n171 │         uint256 spent = uint256(-int256(delta.amount0()));\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/OGDistributor.sol:172:32\n    │\n172 │         if (delta.amount1() != int256(cost) || spent > budget) revert SlippageOrExpired();\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[unused-return]: return value of an external call is not used\n    ╭▸ src/OGDistributor.sol:174:9\n    │\n174 │         poolManager.settle{value: spent}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `teamCredit` is updated\n    ╭▸ src/OGHook.sol:250:22\n    │\n250 │         (bool ok,) = TEAM.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `busy` is updated\n    ╭▸ src/OGHook.sol:216:17\n    │\n216 │                 poolManager.mint(address(this), 0, fee);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `busy` is updated\n    ╭▸ src/OGHook.sol:220:17\n    │\n220 │                 distributor.receiveFeeClaims(normal, surplus);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `busy` is updated\n    ╭▸ src/OGHook.sol:222:17\n    │\n222 │                 poolManager.take(key.currency0, address(this), fee);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `level` is updated\n    ╭▸ src/OGDistributor.sol:199:32\n    │\n199 │         if (unfundedFees != 0) IOGPool(hook).redeemFees();\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/OGHook.sol:252:14\n    │\n252 │         else emit TeamPaid(amount);\n    │              ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/OGHook.sol:226:9\n    │\n226 │         emit FeeSplit(sender, params.zeroForOne, gross, normal, team, surplus, asClaim);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `entered` is updated\n    ╭▸ src/OGDistributor.sol:210:9\n    │\n210 │         auction.list(id);\n    │         ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `entered` is updated\n    ╭▸ src/OGDistributor.sol:211:9\n    │\n211 │         collection.safeTransferFrom(msg.sender, address(auction), id);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/OGHook.sol:232:16\n    │\n232 │         return uint256(value < 0 ? -int256(value) : int256(value));\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/OGHook.sol:236:21\n    │\n236 │         if (value > uint256(uint128(type(int128).max))) revert InvalidAmount();\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/OGHook.sol:236:29\n    │\n236 │         if (value > uint256(uint128(type(int128).max))) revert InvalidAmount();\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/OGHook.sol:237:16\n    │\n237 │         return int128(int256(value));\n    │                ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe 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/OGHook.sol:237:23\n    │\n237 │         return int128(int256(value));\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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OGDistributor.sol:198:13\n    │\n198 │         if (block.timestamp < lastActivation[id] + EXIT_LOCK) revert Locked();\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/OGDistributor.sol:213:9\n    │\n213 │         emit Exited(id, msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `busy` is updated\n    ╭▸ src/OGHook.sol:270:9\n    │\n270 │         poolManager.unlock(abi.encode(normal, surplus, team));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/OGHook.sol:270:9\n    │\n270 │         poolManager.unlock(abi.encode(normal, surplus, team));\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/OGHook.sol:283:9\n    │\n283 │         emit ClaimsRedeemed(total);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Distributor.t.sol:124:17\n    │\n124 │         vm.warp(block.timestamp + 40 days);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Distributor.t.sol:127:27\n    │\n127 │         uint256 started = block.timestamp;\n    │                           ━━━━━━━━━━━━━━━\n128 │         assertEq(dist.streamEnd(), started + 30 days);\n129 │         vm.warp(started + 15 days);\n    │         ────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Distributor.t.sol:143:17\n    │\n143 │         vm.warp(block.timestamp + 10 days);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Distributor.t.sol:150:17\n    │\n150 │         vm.warp(block.timestamp + 100 days);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Distributor.t.sol:162:17\n    │\n162 │         vm.warp(block.timestamp + 15 days);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Distributor.t.sol:286:25\n    │\n286 │         uint256 start = block.timestamp;\n    │                         ━━━━━━━━━━━━━━━\n287 │         vm.warp(start + 18 hours);\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/Distributor.t.sol:295:43\n    │\n295 │         auction.buy(1, 50_000 ether, BOB, block.timestamp);\n    │                                           ━━━━━━━━━━━━━━━\n    ‡\n305 │         vm.warp(vm.getBlockTimestamp() + 24 hours);\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/Distributor.t.sol:351:25\n    │\n351 │         uint256 start = block.timestamp;\n    │                         ━━━━━━━━━━━━━━━\n352 │         vm.warp(start + first);\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/MainnetFork.t.sol:50:17\n   │\n50 │         vm.warp(block.timestamp + 24 hours);\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/MainnetFork.t.sol:57:17\n   │\n57 │         vm.warp(block.timestamp + 36 hours);\n   │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":1676,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 306.88µs (0.00ns CPU time)\n\nRan 2 tests for test/Hook.t.sol:FreshManagerTest\n[PASS] test_claimRewardsPrecedeLaterActivationAndExitRedeems() (gas: 10251951)\n[PASS] test_firstBuyWithTokensOnlyMintsAndRedeemsClaims() (gas: 9411287)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.51ms (2.33ms CPU time)\n\nRan 2 tests for test/ActivationETH.t.sol:ActivationETHTest\n[PASS] test_exactOGBoughtBurnedWithFeeAndRefund() (gas: 650895)\n[PASS] test_slippageExpiryPartialFillAndInsufficientETHRevert() (gas: 493035)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.86ms (611.97µs CPU time)\n\nRan 4 tests for test/Distributor.t.sol:AuctionTest\n[PASS] testFuzz_monotonicPrice(uint32,uint32) (runs: 256, μ: 32122, ~: 32334)\n[PASS] test_auctionFailuresAndReceiverReentry() (gas: 998954)\n[PASS] test_boundedAuctionViewsAndUnexpectedNFTRejected() (gas: 194948)\n[PASS] test_exponentialDecayFloorBurnAndResaleStart() (gas: 1229353)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 3.80ms (3.62ms CPU time)\n\nRan 2 tests for test/Token.t.sol:TokenTest\n[PASS] testFuzz_plainTransfers(uint256) (runs: 256, μ: 69478, ~: 69726)\n[PASS] test_supplyPlainTransferAllowanceAndBurn() (gas: 309826)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 4.07ms (4.18ms CPU time)\n\nRan 12 tests for test/Hook.t.sol:HookTest\n[PASS] testFuzz_feeBuyAndSell(uint96,bool,bool) (runs: 256, μ: 299241, ~: 315797)\n[PASS] test_decayAndSurplus() (gas: 873559)\n[PASS] test_exactModesRespectAmount() (gas: 826933)\n[PASS] test_feeAllFourModes() (gas: 1006218)\n[PASS] test_partialBuyExactInput() (gas: 325611)\n[PASS] test_partialBuyExactOutput() (gas: 271589)\n[PASS] test_partialSellExactInput() (gas: 266994)\n[PASS] test_partialSellExactOutput() (gas: 325482)\n[PASS] test_permissionsAndUnauthorizedCallbacks() (gas: 148720)\n[PASS] test_rejectingTeamDoesNotBlockSwap() (gas: 482167)\n[PASS] test_sellsHaveNoLaunchSurplus() (gas: 514246)\n[PASS] test_wrongPoolRejected() (gas: 78296)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 21.37ms (24.97ms CPU time)\n\nRan 11 tests for test/Distributor.t.sol:DistributorTest\n[PASS] testFuzz_roundingConservesETH(uint96) (runs: 256, μ: 1211380, ~: 1211274)\n[PASS] test_backlogWaitsAndStreamsWithoutWindfall() (gas: 706836)\n[PASS] test_claimAccountingAtSwapTimeNotRedemption() (gas: 558784)\n[PASS] test_costsAndUnauthorizedOrInvalidActions() (gas: 512441)\n[PASS] test_exitETHReentryAndFailedPayoutRollBack() (gas: 1600994)\n[PASS] test_noRetroactiveRewardsIncludingNewlyMintedNFT() (gas: 630233)\n[PASS] test_streamPausesWithZeroWeightAndRestarts() (gas: 870021)\n[PASS] test_surplusStreamsEvenWithActiveWeight() (gas: 533624)\n[PASS] test_transferCarriesLevelPendingAndLock() (gas: 750683)\n[PASS] test_upgradeResetsLockAndExitNeedsNFTApproval() (gas: 650137)\n[PASS] test_weightedAccrualAndUpgradePreservesPending() (gas: 746507)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 21.97ms (24.40ms CPU time)\n\nRan 1 test for test/Launch.t.sol:LaunchTest\n[PASS] test_realCreate2DeploymentInitializesAllContracts() (gas: 38829158)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 23.50ms (23.22ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:AccountingInvariantTest\n[PASS]\nAccountingInvariantTest invariants:\n[PASS] invariant_ETHSolvencyAndConservation\n[PASS] invariant_burnAccountingAndSupply\n[PASS] invariant_weightAndLevelCountsConserved\n AccountingInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭---------------+----------+-------+---------+----------╮\n| Contract      | Selector | Calls | Reverts | Discards |\n+=======================================================+\n| RewardHandler | activate | 1643  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | advance  | 1576  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | buy      | 1636  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | exit     | 1642  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | fees     | 1695  | 0       | 0        |\n╰---------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.59s (1.59s CPU time)\n\nRan 9 test suites in 1.59s (1.67s CPU time): 35 tests passed, 0 failed, 1 skipped (36 total tests)\n","passed":true},{"durationMs":41,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"OG.approve(address,uint256)\",\"OG.transfer(address,uint256)\",\"OG.transferFrom(address,address,uint256)\",\"OGAuction.buy(uint256,uint256,address,uint256)\",\"OGAuction.list(uint256)\",\"OGDistributor.activate(uint256,uint8)\",\"OGDistributor.activateWithETH(uint256,uint8,uint160,uint256)\",\"OGDistributor.checkpoint()\",\"OGDistributor.exit(uint256)\",\"OGDistributor.fundFeeClaims()\",\"OGDistributor.receiveFeeClaims(uint256,uint256)\",\"OGDistributor.receiveFees(uint256,uint256)\",\"OGDistributor.unlockCallback(bytes)\",\"OGHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"OGHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"OGHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"OGHook.payTeam()\",\"OGHook.quoteNative((address,address,uint24,int24,address),(bool,int256,uint160))\",\"OGHook.receive()\",\"OGHook.redeemFees()\",\"OGHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":7,\"README.md\":117,\"abi/OG.json\":260,\"abi/OGAuction.json\":383,\"abi/OGDistributor.json\":713,\"abi/OGHook.json\":837,\"docs/checks/mainnet-all-tests.txt\":86,\"docs/checks/mainnet-lifecycle.txt\":7,\"docs/checks/offline-tests.txt\":86,\"docs/checks/source-sha256.json\":21,\"docs/dependencies.txt\":3,\"docs/independent-review.md\":46,\"docs/launch-config.json\":31,\"docs/mainnet-verification.json\":548,\"docs/validation.md\":27,\"foundry.toml\":19,\"script/PrepareLaunch.s.sol\":32,\"src/HookFlags.sol\":30,\"src/Interfaces.sol\":33,\"src/OG.sol\":48,\"src/OGAuction.sol\":107,\"src/OGDistributor.sol\":219,\"src/OGHook.sol\":290,\"test/ActivationETH.t.sol\":45,\"test/Distributor.t.sol\":360,\"test/Hook.t.sol\":192,\"test/Invariants.t.sol\":123,\"test/Launch.t.sol\":60,\"test/MainnetFork.t.sol\":64,\"test/PoolRouter.sol\":65,\"test/SystemBase.sol\":97,\"test/Token.t.sol\":40,\"test/mocks/MockERC20.sol\":9,\"test/mocks/MockSpepe.sol\":24},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":3436,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-eth at src/OGHook.sol:187: Reentrancy in OGHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/OGHook.sol#187-229):\n[high/medium] reentrancy-eth at src/OGHook.sol:246: Reentrancy in OGHook._payTeam() (src/OGHook.sol#246-253):\n[medium/high] incorrect-equality at src/OGDistributor.sol:75: OGDistributor._vested() (src/OGDistributor.sol#75-79) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/Interfaces.sol:28: Guard._sendETH(address,uint256) (src/Interfaces.sol#28-32) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/OGDistributor.sol:104: OGDistributor._schedule() (src/OGDistributor.sol#104-110) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/OGAuction.sol:56: OGAuction.price(uint256) (src/OGAuction.sol#56-65) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/OGDistributor.sol:144: Reentrancy in OGDistributor.activate(uint256,uint8) (src/OGDistributor.sol#144-149):\n[medium/medium] reentrancy-no-eth at src/OGDistributor.sol:151: Reentrancy in OGDistributor.activateWithETH(uint256,uint8,uint160,uint256) (src/OGDistributor.sol#151-164):\n[medium/medium] reentrancy-no-eth at src/OGDistributor.sol:196: Reentrancy in OGDistributor.exit(uint256) (src/OGDistributor.sol#196-214):\n[medium/medium] uninitialized-local at src/OGHook.sol:141: OGHook.beforeSwap(address,PoolKey,SwapParams,bytes).fee (src/OGHook.sol#141) is a local variable never initialized\n[medium/medium] unused-return at src/OGHook.sol:261: OGHook.redeemFees() (src/OGHook.sol#261-272) ignores return value by poolManager.unlock(abi.encode(normal,surplus,team)) (src/OGHook.sol#270)\n[medium/medium] unused-return at src/OGDistributor.sol:166: OGDistributor.unlockCallback(bytes) (src/OGDistributor.sol#166-177) ignores return value by poolManager.settle{value: spent}() (src/OGDistributor.sol#174)\n[medium/high] write-after-write at src/OGDistributor.sol:35: OGDistributor.buying (src/OGDistributor.sol#35) is written in both\n[low/medium] missing-zero-check at src/OGAuction.sol:38: OGAuction.constructor(OG,ISpepe,address).distributor_ (src/OGAuction.sol#38) lacks a zero-check on :\n[low/medium] missing-zero-check at src/OGDistributor.sol:46: OGDistributor.constructor(OG,ISpepe,IPoolManager,address).hook_ (src/OGDistributor.sol#46) lacks a zero-check on :\n[low/medium] reentrancy-benign at src/OGDistributor.sol:151: Reentrancy in OGDistributor.activateWithETH(uint256,uint8,uint160,uint256) (src/OGDistributor.sol#151-164):\n[low/medium] reentrancy-benign at src/OGHook.sol:129: Reentrancy in OGHook.beforeSwap(address,PoolKey,SwapParams,bytes) (src/OGHook.sol#129-166):\n[low/medium] reentrancy-benign at src/OGHook.sol:240: Reentrancy in OGHook._distribute(uint256,uint256,uint256) (src/OGHook.sol#240-244):\n[low/medium] reentrancy-benign at src/OGHook.sol:274: Reentrancy in OGHook.unlockCallback(bytes) (src/OGHook.sol#274-285):\n[low/medium] reentrancy-benign at src/OGHook.sol:187: Reentrancy in OGHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/OGHook.sol#187-229):\n[low/medium] reentrancy-benign at src/OGDistributor.sol:196: Reentrancy in OGDistributor.exit(uint256) (src/OGDistributor.sol#196-214):\n[low/medium] reentrancy-benign at src/OGHook.sol:261: Reentrancy in OGHook.redeemFees() (src/OGHook.sol#261-272):\n[low/medium] reentrancy-benign at src/OGDistributor.sol:144: Reentrancy in OGDistributor.activate(uint256,uint8) (src/OGDistributor.sol#144-149):\n[low/medium] reentrancy-events at src/OGHook.sol:274: Reentrancy in OGHook.unlockCallback(bytes) (src/OGHook.sol#274-285):\n[low/medium] reentrancy-events at src/OGHook.sol:240: Reentrancy in OGHook._distribute(uint256,uint256,uint256) (src/OGHook.sol#240-244):\n[low/medium] reentrancy-events at src/OGHook.sol:246: Reentrancy in OGHook._payTeam() (src/OGHook.sol#246-253):\n[low/medium] reentrancy-events at src/OGHook.sol:187: Reentrancy in OGHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/OGHook.sol#187-229):\n[low/medium] reentrancy-events at src/OGHook.sol:187: Reentrancy in OGHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/OGHook.sol#187-229):\n[low/medium] timestamp at src/OGAuction.sol:67: OGAuction.buy(uint256,uint256,address,uint256) (src/OGAuction.sol#67-82) uses timestamp for comparisons\n[low/medium] timestamp at src/OGDistributor.sol:91: OGDistributor._checkpoint() (src/OGDistributor.sol#91-98) uses timestamp for comparisons\n[low/medium] timestamp at src/OGDistributor.sol:151: OGDistributor.activateWithETH(uint256,uint8,uint160,uint256) (src/OGDistributor.sol#151-164) uses timestamp for comparisons\n[low/medium] timestamp at src/OGHook.sol:235: OGHook._int128(uint256) (src/OGHook.sol#235-238) uses timestamp for comparisons\n[low/medium] timestamp at src/OGAuction.sol:101: OGAuction.onERC721Received(address,address,uint256,bytes) (src/OGAuction.sol#101-106) uses timestamp for comparisons\n[low/medium] timestamp at src/OGHook.sol:129: OGHook.beforeSwap(address,PoolKey,SwapParams,bytes) (src/OGHook.sol#129-166) uses timestamp for comparisons\n[low/medium] timestamp at src/OGAuction.sol:44: OGAuction.list(uint256) (src/OGAuction.sol#44-53) uses timestamp for comparisons\n[low/medium] timestamp at src/OGHook.sol:120: OGHook.decayMinutesLeft() (src/OGHook.sol#120-124) uses timestamp for comparisons\n[low/medium] timestamp at src/OGDistributor.sol:75: OGDistributor._vested() (src/OGDistributor.sol#75-79) uses timestamp for comparisons\n[low/medium] timestamp at src/OGAuction.sol:56: OGAuction.price(uint256) (src/OGAuction.sol#56-65) uses timestamp for comparisons\n[low/medium] timestamp at src/OGDistributor.sol:196: OGDistributor.exit(uint256) (src/OGDistributor.sol#196-214) uses timestamp for comparisons\n[low/medium] timestamp at src/OGHook.sol:187: OGHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/OGHook.sol#187-229) uses timestamp for comparisons\n[low/medium] timestamp at src/OGHook.sol:114: OGHook.launchFeeNow() (src/OGHook.sol#114-118) uses timestamp for comparisons\n[low/medium] timestamp at src/OGDistributor.sol:104: OGDistributor._schedule() (src/OGDistributor.sol#104-110) uses timestamp for comparisons","passed":true},{"durationMs":390,"exitCode":0,"name":"aderyn","output":"[high] eth-send-unchecked-address at src/OGHook.sol:256: ETH transferred without address checks\n[high] reentrancy-state-change at src/OGDistributor.sol:156: Reentrancy: State change after external call (11 places)\n[low] empty-require-revert at src/OGAuction.sol:79: Empty `require()` / `revert()` Statement (2 places)\n[low] internal-function-used-once at src/HookFlags.sol:23: Internal Function Used Only Once\n[low] large-numeric-literal at src/OG.sol:9: Large Numeric Literal (6 places)\n[low] literal-instead-of-constant at src/OGAuction.sol:62: Literal Instead of Constant (11 places)\n[low] missing-inheritance at src/OG.sol:5: Missing Inheritance (2 places)\n[low] require-revert-in-loop at src/OGAuction.sol:94: Loop Contains `require`/`revert`\n[low] state-change-without-event at src/OGDistributor.sol:100: State Change Without Event (4 places)\n[low] unchecked-return at src/OGHook.sol:270: Unchecked Return\n[low] unused-public-function at src/OGDistributor.sol:87: Public Function Not Used Internally (2 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"883df1c6d0150dfd1c1d34edd453ef6f4484f1c3dbf3e053cf5b7b24e9cd31b6","verifiedTreeHash":"ccefb3434f346154f341f50c2a9169e4bd8ceb31","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":76664,"exitCode":0,"name":"build","output":"Compiling 91 files with Solc 0.8.26\nSolc 0.8.26 finished in 76.49s\nCompiler run successful!\nwarning[missing-events-access-control]: `unfundedFees` is changed without an event but is used for access control\n    ╭▸ src/OGDistributor.sol:121:9\n    │\n121 │         unfundedFees += normal + surplus;\n    │         ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[missing-events-access-control]: `unfundedFees` is changed without an event but is used for access control\n    ╭▸ src/OGDistributor.sol:127:9\n    │\n127 │         unfundedFees -= msg.value;\n    │         ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[missing-events-access-control]: `level` is changed without an event but is used for access control\n    ╭▸ src/OGDistributor.sol:205:16\n    │\n205 │         delete level[id];\n    │                ━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/OGDistributor.sol:46:71\n   │\n46 │     constructor(OG token_, ISpepe collection_, IPoolManager manager_, address hook_) {\n   │                                                                       ━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/OGDistributor.sol:77:24\n   │\n77 │         uint256 now_ = block.timestamp < streamEnd ? block.timestamp : streamEnd;\n   │                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/OGAuction.sol:38:48\n   │\n38 │     constructor(OG token_, ISpepe collection_, address distributor_) {\n   │                                                ━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n   ╭▸ src/Interfaces.sol:30:22\n   │\n30 │         (bool ok,) = to.call{value: amount}(\"\");\n   │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/OGAuction.sol:60:13\n   │\n60 │         if (elapsed >= DURATION) return FLOOR;\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/OGAuction.sol:61:13\n   │\n61 │         if (elapsed == 0) return a.startPrice;\n   │             ━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/OGAuction.sol:62:34\n   │\n62 │         int256 logarithm = wadLn(int256(FLOOR * 1e18 / a.startPrice));\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/OGAuction.sol:63:41\n   │\n63 │         uint256 result = a.startPrice * uint256(wadExp(logarithm * int256(elapsed) / int256(DURATION))) / 1e18;\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/OGAuction.sol:63:68\n   │\n63 │         uint256 result = a.startPrice * uint256(wadExp(logarithm * int256(elapsed) / int256(DURATION))) / 1e18;\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/OGAuction.sol:63:86\n   │\n63 │         uint256 result = a.startPrice * uint256(wadExp(logarithm * int256(elapsed) / int256(DURATION))) / 1e18;\n   │                                                                                      ━━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'int256' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `entered` is updated\n   ╭▸ src/OGAuction.sol:79:17\n   │\n79 │         require(token.transferFrom(msg.sender, token.DEAD(), cost));\n   │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `entered` is updated\n   ╭▸ src/OGAuction.sol:80:9\n   │\n80 │         collection.safeTransferFrom(address(this), recipient, id);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/OGAuction.sol:70:13\n   │\n70 │         if (block.timestamp > deadline || cost > maxPrice) revert SlippageOrExpired();\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/OGAuction.sol:81:9\n   │\n81 │         emit AuctionSold(id, msg.sender, recipient, cost);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `level` is updated\n    ╭▸ src/OGDistributor.sol:147:17\n    │\n147 │         require(token.transferFrom(msg.sender, token.DEAD(), cost));\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OGHook.sol:117:16\n    │\n117 │         return elapsed >= DECAY ? NORMAL_FEE : NORMAL_FEE + 0.465e18 * (DECAY - elapsed) / DECAY;\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/OGDistributor.sol:109:9\n    │\n109 │         emit StreamScheduled(backlog, streamStart, streamEnd);\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/OGDistributor.sol:192:23\n    │\n192 │         if (old == 0) emit Activated(id, msg.sender, newLevel, cost);\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/OGDistributor.sol:193:14\n    │\n193 │         else emit Upgraded(id, msg.sender, old, newLevel, cost);\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/OGHook.sol:123:16\n    │\n123 │         return elapsed >= DECAY ? 0 : (DECAY - elapsed + 59) / 60;\n    │                ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-no-eth]: external call can be reentered before `level` is updated\n    ╭▸ src/OGDistributor.sol:160:36\n    │\n160 │         uint256 spent = abi.decode(poolManager.unlock(abi.encode(cost, msg.value, sqrtPriceLimitX96)), (uint256));\n    │                                    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `entered` is updated\n   ╭▸ src/Interfaces.sol:30:22\n   │\n30 │         (bool ok,) = to.call{value: amount}(\"\");\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/OGDistributor.sol:157:13\n    │\n157 │         if (block.timestamp > deadline) revert SlippageOrExpired();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/OGDistributor.sol:170:69\n    │\n170 │         BalanceDelta delta = poolManager.swap(key, SwapParams(true, int256(cost), limit), \"\");\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/OGDistributor.sol:171:25\n    │\n171 │         uint256 spent = uint256(-int256(delta.amount0()));\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/OGDistributor.sol:172:32\n    │\n172 │         if (delta.amount1() != int256(cost) || spent > budget) revert SlippageOrExpired();\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[unused-return]: return value of an external call is not used\n    ╭▸ src/OGDistributor.sol:174:9\n    │\n174 │         poolManager.settle{value: spent}();\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/OGHook.sol:163:25\n    │\n163 │             quotedFee = fee;\n    │                         ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/OGHook.sol:146:33\n    │\n146 │             uint256 requested = uint256(params.amountSpecified < 0 ? -params.amountSpecified : params.amountSpecified);\n    │                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `level` is updated\n    ╭▸ src/OGDistributor.sol:199:32\n    │\n199 │         if (unfundedFees != 0) IOGPool(hook).redeemFees();\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/OGHook.sol:149:48\n    │\n149 │                 quoteParams.amountSpecified = -int256(requested - fee);\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/OGHook.sol:152:29\n    │\n152 │                 if (gross > uint256(uint128(type(int128).max))) revert InvalidAmount();\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/OGHook.sol:152:37\n    │\n152 │                 if (gross > uint256(uint128(type(int128).max))) revert InvalidAmount();\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/OGHook.sol:153:47\n    │\n153 │                 quoteParams.amountSpecified = int256(gross);\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/OGHook.sol:158:31\n    │\n158 │                 if (actual != uint256(-quoteParams.amountSpecified)) fee = actual * rate / (WAD - rate);\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `entered` is updated\n    ╭▸ src/OGDistributor.sol:210:9\n    │\n210 │         auction.list(id);\n    │         ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `entered` is updated\n    ╭▸ src/OGDistributor.sol:211:9\n    │\n211 │         collection.safeTransferFrom(msg.sender, address(auction), id);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/OGDistributor.sol:198:13\n    │\n198 │         if (block.timestamp < lastActivation[id] + EXIT_LOCK) revert Locked();\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/OGDistributor.sol:213:9\n    │\n213 │         emit Exited(id, msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/OGHook.sol:172:40\n    │\n172 │             if (reason.length != 36 || bytes4(reason) != QuoteResult.selector) {\n    │                                        ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'bytes4' is safe 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 `teamCredit` is updated\n    ╭▸ src/OGHook.sol:250:22\n    │\n250 │         (bool ok,) = TEAM.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `busy` is updated\n    ╭▸ src/OGHook.sol:216:17\n    │\n216 │                 poolManager.mint(address(this), 0, fee);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `busy` is updated\n    ╭▸ src/OGHook.sol:220:17\n    │\n220 │                 distributor.receiveFeeClaims(normal, surplus);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `busy` is updated\n    ╭▸ src/OGHook.sol:222:17\n    │\n222 │                 poolManager.take(key.currency0, address(this), fee);\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/OGHook.sol:252:14\n    │\n252 │         else emit TeamPaid(amount);\n    │              ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/OGHook.sol:226:9\n    │\n226 │         emit FeeSplit(sender, params.zeroForOne, gross, normal, team, surplus, asClaim);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/OGHook.sol:232:16\n    │\n232 │         return uint256(value < 0 ? -int256(value) : int256(value));\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/OGHook.sol:236:21\n    │\n236 │         if (value > uint256(uint128(type(int128).max))) revert InvalidAmount();\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/OGHook.sol:236:29\n    │\n236 │         if (value > uint256(uint128(type(int128).max))) revert InvalidAmount();\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/OGHook.sol:237:16\n    │\n237 │         return int128(int256(value));\n    │                ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe 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/OGHook.sol:237:23\n    │\n237 │         return int128(int256(value));\n    │                       ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `busy` is updated\n    ╭▸ src/OGHook.sol:270:9\n    │\n270 │         poolManager.unlock(abi.encode(normal, surplus, team));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/OGHook.sol:270:9\n    │\n270 │         poolManager.unlock(abi.encode(normal, surplus, team));\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/OGHook.sol:283:9\n    │\n283 │         emit ClaimsRedeemed(total);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Distributor.t.sol:124:17\n    │\n124 │         vm.warp(block.timestamp + 40 days);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Distributor.t.sol:127:27\n    │\n127 │         uint256 started = block.timestamp;\n    │                           ━━━━━━━━━━━━━━━\n128 │         assertEq(dist.streamEnd(), started + 30 days);\n129 │         vm.warp(started + 15 days);\n    │         ────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Distributor.t.sol:143:17\n    │\n143 │         vm.warp(block.timestamp + 10 days);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Distributor.t.sol:150:17\n    │\n150 │         vm.warp(block.timestamp + 100 days);\n    │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Distributor.t.sol:162:17\n    │\n162 │         vm.warp(block.timestamp + 15 days);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Distributor.t.sol:286:25\n    │\n286 │         uint256 start = block.timestamp;\n    │                         ━━━━━━━━━━━━━━━\n287 │         vm.warp(start + 18 hours);\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/Distributor.t.sol:295:43\n    │\n295 │         auction.buy(1, 50_000 ether, BOB, block.timestamp);\n    │                                           ━━━━━━━━━━━━━━━\n    ‡\n305 │         vm.warp(vm.getBlockTimestamp() + 24 hours);\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/Distributor.t.sol:351:25\n    │\n351 │         uint256 start = block.timestamp;\n    │                         ━━━━━━━━━━━━━━━\n352 │         vm.warp(start + first);\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/MainnetFork.t.sol:50:17\n   │\n50 │         vm.warp(block.timestamp + 24 hours);\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/MainnetFork.t.sol:57:17\n   │\n57 │         vm.warp(block.timestamp + 36 hours);\n   │         ────────━━━━━━━━━━━━━━━──────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":7828,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/MainnetAdversarial.t.sol:MainnetAdversarialTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 523.42µs (0.00ns CPU time)\n\nRan 1 test for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 473.20µs (0.00ns CPU time)\n\nRan 1 test for test/AdversarialHook.t.sol:FreshClaimAdversarialTest\n[PASS] test_launchClaimsIncludeSurplusAndAreNotAccountedTwice() (gas: 9953869)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.00ms (1.62ms CPU time)\n\nRan 2 tests for test/ActivationETH.t.sol:ActivationETHTest\n[PASS] test_exactOGBoughtBurnedWithFeeAndRefund() (gas: 650895)\n[PASS] test_slippageExpiryPartialFillAndInsufficientETHRevert() (gas: 493035)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.44ms (922.30µs CPU time)\n\nRan 2 tests for test/Hook.t.sol:FreshManagerTest\n[PASS] test_claimRewardsPrecedeLaterActivationAndExitRedeems() (gas: 10251951)\n[PASS] test_firstBuyWithTokensOnlyMintsAndRedeemsClaims() (gas: 9411287)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.85ms (3.80ms CPU time)\n\nRan 1 test for test/Launch.t.sol:LaunchTest\n[PASS] test_realCreate2DeploymentInitializesAllContracts() (gas: 38829158)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 29.81ms (29.32ms CPU time)\n\nRan 4 tests for test/Distributor.t.sol:AuctionTest\n[PASS] testFuzz_monotonicPrice(uint32,uint32) (runs: 256, μ: 32066, ~: 31979)\n[PASS] test_auctionFailuresAndReceiverReentry() (gas: 998954)\n[PASS] test_boundedAuctionViewsAndUnexpectedNFTRejected() (gas: 194948)\n[PASS] test_exponentialDecayFloorBurnAndResaleStart() (gas: 1229353)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 54.19ms (53.66ms CPU time)\n\nRan 8 tests for test/AdversarialLifecycle.t.sol:AdversarialLifecycleTest\n[PASS] test_ETHUpgradeUsesOldWeightForItsOwnSwapFee() (gas: 852701)\n[PASS] test_approvedOperatorCannotActivateUpgradeOrExit() (gas: 510500)\n[PASS] test_eventsLockBoundaryAndGiftedAuctionSale() (gas: 973010)\n[PASS] test_failedBurnAndInvalidLevelsAreAtomic() (gas: 617067)\n[PASS] test_hookCannotMisstateFunding() (gas: 85917)\n[PASS] test_noClaimSelectorKeepsNFTAndExtractsRewards() (gas: 356551)\n[PASS] test_refundFailureRevertsSwapBurnFeeAndActivation() (gas: 1767362)\n[PASS] test_rejectedNFTSaleRollsBackBurnPriceAndEnumeration() (gas: 2544340)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 76.66ms (3.07ms CPU time)\n\nRan 11 tests for test/Distributor.t.sol:DistributorTest\n[PASS] testFuzz_roundingConservesETH(uint96) (runs: 256, μ: 1211382, ~: 1211309)\n[PASS] test_backlogWaitsAndStreamsWithoutWindfall() (gas: 706836)\n[PASS] test_claimAccountingAtSwapTimeNotRedemption() (gas: 558784)\n[PASS] test_costsAndUnauthorizedOrInvalidActions() (gas: 512441)\n[PASS] test_exitETHReentryAndFailedPayoutRollBack() (gas: 1600994)\n[PASS] test_noRetroactiveRewardsIncludingNewlyMintedNFT() (gas: 630233)\n[PASS] test_streamPausesWithZeroWeightAndRestarts() (gas: 870021)\n[PASS] test_surplusStreamsEvenWithActiveWeight() (gas: 533624)\n[PASS] test_transferCarriesLevelPendingAndLock() (gas: 750683)\n[PASS] test_upgradeResetsLockAndExitNeedsNFTApproval() (gas: 650137)\n[PASS] test_weightedAccrualAndUpgradePreservesPending() (gas: 746507)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 121.87ms (33.32ms CPU time)\n\nRan 2 tests for test/Token.t.sol:TokenTest\n[PASS] testFuzz_plainTransfers(uint256) (runs: 256, μ: 69690, ~: 69714)\n[PASS] test_supplyPlainTransferAllowanceAndBurn() (gas: 309826)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 121.85ms (122.19ms CPU time)\n\nRan 12 tests for test/Hook.t.sol:HookTest\n[PASS] testFuzz_feeBuyAndSell(uint96,bool,bool) (runs: 256, μ: 298617, ~: 315746)\n[PASS] test_decayAndSurplus() (gas: 873559)\n[PASS] test_exactModesRespectAmount() (gas: 826933)\n[PASS] test_feeAllFourModes() (gas: 1006218)\n[PASS] test_partialBuyExactInput() (gas: 325611)\n[PASS] test_partialBuyExactOutput() (gas: 271589)\n[PASS] test_partialSellExactInput() (gas: 266994)\n[PASS] test_partialSellExactOutput() (gas: 325482)\n[PASS] test_permissionsAndUnauthorizedCallbacks() (gas: 148720)\n[PASS] test_rejectingTeamDoesNotBlockSwap() (gas: 482167)\n[PASS] test_sellsHaveNoLaunchSurplus() (gas: 514246)\n[PASS] test_wrongPoolRejected() (gas: 78296)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 122.11ms (125.46ms CPU time)\n\nRan 3 tests for test/RewardProperties.t.sol:RewardPropertiesTest\n[PASS] testFuzz_allWeightsAndUpgradeHaveNoRetroactiveShare(uint96,uint96) (runs: 1000, μ: 1593839, ~: 1594968)\n[PASS] testFuzz_resaleCurveHasGeometricMidpoint(uint32) (runs: 1000, μ: 1051821, ~: 1051965)\n[PASS] testFuzz_streamJoinTimeAndEmptyRestart(uint32,uint96) (runs: 1000, μ: 1500572, ~: 1500691)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 122.39ms (356.04ms CPU time)\n\nRan 5 tests for test/AdversarialHook.t.sol:AdversarialHookTest\n[PASS] testFuzz_decaySecondsAndCeilingMinutes(uint32) (runs: 1000, μ: 29020, ~: 29052)\n[PASS] testFuzz_dustAmountsSettle(uint16,bool,bool) (runs: 1000, μ: 333213, ~: 329206)\n[PASS] testFuzz_settledFeesDuringDecay(uint96,uint32,bool,bool,bool) (runs: 1000, μ: 367592, ~: 347983)\n[PASS] test_invalidAmountDoesNotLeaveHookBusy() (gas: 554628)\n[PASS] test_teamCallbackCannotPayOrRedeemTwice() (gas: 574916)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 122.87ms (274.37ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:AccountingInvariantTest\n[PASS]\nAccountingInvariantTest invariants:\n[PASS] invariant_ETHSolvencyAndConservation\n[PASS] invariant_burnAccountingAndSupply\n[PASS] invariant_weightAndLevelCountsConserved\n AccountingInvariantTest invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭---------------+----------+-------+---------+----------╮\n| Contract      | Selector | Calls | Reverts | Discards |\n+=======================================================+\n| RewardHandler | activate | 1649  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | advance  | 1704  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | buy      | 1662  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | exit     | 1619  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | fees     | 1558  | 0       | 0        |\n╰---------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.88s (1.87s CPU time)\n\nRan 2 tests for test/SystemSequence.t.sol:SystemSequenceInvariantTest\n[PASS]\nSystemSequenceInvariantTest invariants:\n[PASS] invariant_ETHAndClaimsAreFullyBacked\n[PASS] invariant_fixedSupplyAndAllBurnSourcesReconcile\n[PASS] invariant_weightsAndAuctionInventoryAgreeWithOwnership\n SystemSequenceInvariantTest invariants (runs: 256, calls: 20480, reverts: 0)\n\n╭-----------------------+-------------+-------+---------+----------╮\n| Contract              | Selector    | Calls | Reverts | Discards |\n+==================================================================+\n| SystemSequenceHandler | activate    | 2582  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| SystemSequenceHandler | advance     | 2589  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| SystemSequenceHandler | buy         | 2527  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| SystemSequenceHandler | exitNFT     | 2560  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| SystemSequenceHandler | mintLater   | 2583  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| SystemSequenceHandler | redeem      | 2566  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| SystemSequenceHandler | swap        | 2527  | 0       | 0        |\n|-----------------------+-------------+-------+---------+----------|\n| SystemSequenceHandler | transferNFT | 2546  | 0       | 0        |\n╰-----------------------+-------------+-------+---------+----------╯\n\n[PASS] test_handlerReachesEveryEconomicTransition() (gas: 3291980)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 7.70s (7.70s CPU time)\n\nRan 15 test suites in 7.71s (10.36s CPU time): 54 tests passed, 0 failed, 2 skipped (56 total tests)\n","passed":true},{"durationMs":63,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"OG.approve(address,uint256)\",\"OG.transfer(address,uint256)\",\"OG.transferFrom(address,address,uint256)\",\"OGAuction.buy(uint256,uint256,address,uint256)\",\"OGAuction.list(uint256)\",\"OGDistributor.activate(uint256,uint8)\",\"OGDistributor.activateWithETH(uint256,uint8,uint160,uint256)\",\"OGDistributor.checkpoint()\",\"OGDistributor.exit(uint256)\",\"OGDistributor.fundFeeClaims()\",\"OGDistributor.receiveFeeClaims(uint256,uint256)\",\"OGDistributor.receiveFees(uint256,uint256)\",\"OGDistributor.unlockCallback(bytes)\",\"OGHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"OGHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"OGHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"OGHook.payTeam()\",\"OGHook.quoteNative((address,address,uint24,int24,address),(bool,int256,uint160))\",\"OGHook.receive()\",\"OGHook.redeemFees()\",\"OGHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":7,\"README.md\":117,\"abi/OG.json\":260,\"abi/OGAuction.json\":383,\"abi/OGDistributor.json\":713,\"abi/OGHook.json\":837,\"docs/checks/mainnet-all-tests.txt\":86,\"docs/checks/mainnet-lifecycle.txt\":7,\"docs/checks/offline-tests.txt\":86,\"docs/checks/source-sha256.json\":21,\"docs/dependencies.txt\":3,\"docs/independent-review.md\":46,\"docs/launch-config.json\":31,\"docs/mainnet-verification.json\":548,\"docs/validation.md\":27,\"foundry.toml\":19,\"script/PrepareLaunch.s.sol\":32,\"src/HookFlags.sol\":30,\"src/Interfaces.sol\":33,\"src/OG.sol\":48,\"src/OGAuction.sol\":107,\"src/OGDistributor.sol\":219,\"src/OGHook.sol\":290,\"test/ActivationETH.t.sol\":45,\"test/AdversarialHook.t.sol\":185,\"test/AdversarialLifecycle.t.sol\":271,\"test/Distributor.t.sol\":360,\"test/Hook.t.sol\":192,\"test/Invariants.t.sol\":123,\"test/Launch.t.sol\":60,\"test/MainnetAdversarial.t.sol\":101,\"test/MainnetFork.t.sol\":64,\"test/PoolRouter.sol\":65,\"test/REVIEW.md\":53,\"test/RewardProperties.t.sol\":112,\"test/SystemBase.sol\":97,\"test/SystemSequence.t.sol\":366,\"test/Token.t.sol\":40,\"test/mainnet-observation.json\":36,\"test/mocks/MockERC20.sol\":9,\"test/mocks/MockSpepe.sol\":24},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"cfd8b60aefebfd40e441885adc921d5f34481e34a297d454def10ce55589ab70","verifiedTreeHash":"fa75c8b0f49d8dfba14330d7b32ca4d5d643df02","verifierVersion":"0.1.0+ad90ce4c"}]}