{"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":"babb0da2-bd96-4fe7-b753-945acdf5872e","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"63b6c6ca8afebdfbed7fc7dea66a3d2a4d380cbea63c717c8a86e110750f916a","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":"d21df9e1d6764605ee1b34a95d10d3d3e66feb2ef9202374eb6c0345a7db4475","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":"662e740adfee37c4f39ee6796bacb000f2958a25e51289f79c0f540e0d2126b0","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"4a775ae9639d8fd30137cd7160a9d7e64e57d2afef5df2158e87ae0a8975f4f5","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":"05548f829fcc538dc33669ce2e3bcdec1a17459d4bf3799f10ee3344a95e9c74","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":"75487cfe51e2cf615fbf619fab72b86b0b8d260b3ed6ccb270ae0929ec2657b7","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":"dee06d892a376798779dff601a28156a781d45927abd92ca998797bc2c686a15","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"fe81f34f10e44ead3c7b05ba58cb7bd543ae70737547e176272dae4c8380a806","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\nSTART FROM EXISTING CODE: this exact project was already built and accepted (build_contract_project, all checks passed) in job a38b37bd-6caa-4756-9aa2-6033c44ac713; its source is the accepted bundle https://api.imd.fun/bundles/8d918c729607f1be5c14bc1b4d2e4b162b6f121f8462e77dad3af25760b1d064. Download it and use it as the starting point; keep its logic and only fix what review finds. That job stopped at manifest only because the pool start price was missing.\n\nPool (launch.json): Ethereum univ4_hook policy 34, paired with native ETH (currency0 = ETH, currency1 = OG), fee 12500, tickSpacing 60, opening market cap 10 ETH per policy (100,000,000 OG per ETH), initialPrice 792281625142643375935439503360000 (decimal sqrtPriceX96).\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":"d9c1a2894371e15af596857de2ba76826e76e281509f40d9d1891b4c15e4592e","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"babb0da2-bd96-4fe7-b753-945acdf5872e","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1040-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":"52193","feedbackHash":"66c40116e0bd6650b6cf9aaeb253c12935a190932ca6286e81284d2d72ceae16","nodeKey":"audit_economics","submissionHash":"63b6c6ca8afebdfbed7fc7dea66a3d2a4d380cbea63c717c8a86e110750f916a","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52192","feedbackHash":"4e0a8c317af2992a0dbe3cfbc03173ed68f7442ea8333aab9ed82f7e021f3cc8","nodeKey":"audit_flow","submissionHash":"d21df9e1d6764605ee1b34a95d10d3d3e66feb2ef9202374eb6c0345a7db4475","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51488","feedbackHash":"29b48e20df9ac24b3b1f6839ec31000bfa98494fb38e926e157ea3a43c4ad4e0","nodeKey":"audit_judge","submissionHash":"662e740adfee37c4f39ee6796bacb000f2958a25e51289f79c0f540e0d2126b0","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51511","feedbackHash":"8c03bc6d18ef02992a2b707c361aa622d3c8ea19b6a7c2a2470459ef05b6f703","nodeKey":"audit_math","submissionHash":"4a775ae9639d8fd30137cd7160a9d7e64e57d2afef5df2158e87ae0a8975f4f5","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52188","feedbackHash":"7dcc29c1829109ab4366c7464fd303c0bf428e0b9e6016973a25e4f6bee8715d","nodeKey":"audit_permissions","submissionHash":"05548f829fcc538dc33669ce2e3bcdec1a17459d4bf3799f10ee3344a95e9c74","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51093","feedbackHash":"0b31045d185d553a6c65303d87a79a1e4bd8d34364c434abf055d9ebdd4b8f4a","nodeKey":"build_contract_project","submissionHash":"75487cfe51e2cf615fbf619fab72b86b0b8d260b3ed6ccb270ae0929ec2657b7","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51066","feedbackHash":"e9e3c75b36720e4fcd108a98f885bc53888063b9107b014bf581b9fe2f69ac89","nodeKey":"manifest","submissionHash":"dee06d892a376798779dff601a28156a781d45927abd92ca998797bc2c686a15","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51431","feedbackHash":"538cceb2553989baf8a504bbec91301e3364787fdf3b75e1f9adca408b858628","nodeKey":"write_foundry_tests","submissionHash":"fe81f34f10e44ead3c7b05ba58cb7bd543ae70737547e176272dae4c8380a806","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"9c423fd83e5e05f0958829fa0473c44e5ab89dc992f50146d82f8c5205aaf04e","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"d4b53d656a3fbe07","findings":[{"citation":"resolved","description":"The brief states 'Start price = max(10x the previous auction's sale price, 500,000 OG)' and lists 'last sale price' (singular) among the website views. OGAuction.list reads lastSalePrice[id], which is per tokenId, so every tokenId's first auction opens at 500,000 OG no matter what the immediately preceding auction sold for, and the escalation only applies when the same NFT is exited and sold again. The only sale-history view is lastSalePrice(uint256); there is no view returning the price of the most recent sale across the collection. The README documents the per-tokenId interpretation, so this is a spec-text deviation the author chose consciously rather than an accounting error; no funds are at risk. It is reported so the judge can decide whether the global reading was intended (asymmetry lens: the paired surfaces 'sale' and 'next listing' are keyed differently from the brief's wording).","line":47,"path":"src/OGAuction.sol","reproduction":"Fresh system (test/SystemBase.sol _system(false,true)), mint ids 1 and 2 to ALICE. (1) ALICE activate(1,1); warp +24h; ALICE exit(1) -> price(1) == 500_000e18. (2) BOB buy(1, 500_000e18, BOB, now) -> lastSalePrice(1) == 500_000e18. (3) ALICE activate(2,1); warp +24h; ALICE exit(2). Expected under the brief's wording: price(2) == 5_000_000e18 (10x the previous auction's sale). Actual: price(2) == 500_000e18 and lastSalePrice(2) == 0. Executed in test/scratch/AuctionHistory.t.sol (passes on current code, demonstrating the per-id behaviour).","severity":"low","snippet":"        uint256 start = lastSalePrice[id] * 10;","title":"Auction start price keys on the same tokenId's last sale, not on the previous auction's sale, and no global last-sale view exists"},{"citation":"resolved","description":"Mainnet read on 2026-10-08 (eth_call owner() on 0x999ce0CE8C5f7661e0c74a568FfE27CEB9177bDB) returns 0x90738ABe9b04622Dc0b3d015a3964Cc7D1Fd1859, the same address hardcoded as TEAM. The verified SwarmPepe source (Blockscout) gives the owner grant()/grantEach() (allowlist up to 3 per wallet), setMintOpen() and setRoyaltyReceiver(); mintOpen is currently true, totalMinted 1,242 of MAX_SUPPLY 5,000, and the owner address itself is blocked from claiming. No burn, pause or transfer-lock exists, so active NFTs, their levels, pending ETH, the 24h lock, exit and auction custody cannot be interfered with by the collection owner; the OG contracts hold no owner powers. The gap is economic: the party receiving 1% of every trade can also allot up to ~3,758 further NFTs to wallets it chooses at zero mint cost, each of which (after burning 50,000 OG) dilutes every existing holder's share of the 2.5% reward stream and backlog. README line 102 records collection-owner powers generically but does not state that the collection owner is the team wallet. This is a trust assumption to document, not a code defect, and it does not block launch.","line":20,"path":"src/OGHook.sol","reproduction":"State: mainnet, SPEPE owner() == TEAM, mintOpen() == true. Calls from TEAM: SwarmPepe.grant([W], 3) where W is any wallet other than TEAM; then from W: SwarmPepe.claim() -> three new tokenIds are minted to W (totalMinted +3). W approves OG and calls OGDistributor.activate(newId, 1) three times (150,000 OG burned) -> totalWeight rises by 3 and every pre-existing NFT's share of all later fees falls proportionally. Expected per the brief: no post-launch owner powers affect reward distribution; actual: the fee recipient controls who may mint new eligible NFTs. Verification commands: cast call 0x999ce0CE8C5f7661e0c74a568FfE27CEB9177bDB 'owner()(address)' and 'mintOpen()(bool)' on Ethereum mainnet.","severity":"info","snippet":"    address public constant TEAM = 0x90738ABe9b04622Dc0b3d015a3964Cc7D1Fd1859;","title":"Trust assumption: the fixed team wallet is also the owner of the SPEPE collection and can grow the eligible NFT set at will"}],"hash":"05548f829fcc538dc33669ce2e3bcdec1a17459d4bf3799f10ee3344a95e9c74","nodeId":"ff8d1415-96bc-4aeb-929a-0c6eadae818b","outcome":"completed","summary":"The review is complete. The findings file is written at the repository root, no tracked file was changed, and the scratch tests live only under the ignored test/scratch/ directory.\n\n**Outcome.** Across the Access Control, Trust Gap and Asymmetry checklists I found no defect that loses funds, pays the wrong party or breaks a guarantee. Every state-changing entry point authenticates the caller it assumes, and the paired surfaces (activate/exit, direct-fee/claim-fee, list/buy, beforeSwap/afterSwap) mirror each other's storage writes. Two items are reported, neither blocking:\n\n- **Low, auction history keyed per tokenId.** The brief says the start price is ten times the previous auction's sale, and asks for a \"last sale price\" view. The code uses that same NFT's last sale, so every NFT's first auction opens at 500,000 OG regardless of what just sold, and only a per-id view exists. The README documents this reading, so it is a conscious interpretation for the judge to confirm. A scratch test shows the exact sequence and numbers.\n- **Info, trust assumption.** A live mainnet read shows the SPEPE collection's owner is the hardcoded team wallet. That owner cannot touch active NFTs, locks or custody, but it can allot new mint slots at zero cost and so dilute the reward pool it also earns 1% of. The README notes collection-owner powers but not that they belong to the fee recipient.\n\n**What I verified executably.** The full offline suite passes (40 tests). I added probes showing that an unprivileged swapper can force the ERC-6909 claim path by draining the manager inside their own unlock, and that a later exit still pays exactly its pending ETH, so that path is not a griefing vector. An exit attempted inside a foreign unlock reverts as documented and succeeds standalone. The mainnet fork lifecycle passes against the real OpenZeppelin SPEPE contract, confirming the auction's receiver guard and the exit transfer path with the genuine collection.\n\n**Coverage.** All 21 listed entry points have a row, 20 as holds and one (OGAuction.list) tied to the low finding, plus two invariant rows. Nothing was left unreached. Static-analysis leads (reentrancy in afterSwap and payTeam, strict equalities, the uninitialized local) were traced and none produced a reachable failure.","treeHash":null,"usage":{"cachedInputTokens":2263956,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":74456,"runtime":"claude","turns":43,"wallClockMs":1452326}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"fbcdfc017217af1f","findings":[{"citation":"resolved","description":"The brief says the start price is max(10x the previous auction's sale price, 500,000 OG). The implementation reads lastSalePrice[id], so only that tokenId's own previous sale counts; a tokenId that has never sold always starts at 500,000 OG regardless of how high the immediately preceding auction (of another tokenId) cleared. README.md and docs/revision-review.md document this as a deliberate, accepted interpretation and the math under it is correct (start >= 500,000e18 keeps wadLn's argument in (0, 1e18) and a sale bounded by the 1e9 OG supply keeps the next start <= 1e28 wei, inside the exp/ln range). Reported only so the judge can confirm the interpretation; no arithmetic or safety defect follows from it.","line":47,"path":"src/OGAuction.sol","reproduction":"State: tokenId 1 exits and is bought at the start price 500,000 OG (lastSalePrice[1] = 500,000e18). Then tokenId 2 exits for the first time. Actual: auction.price(2) == 500,000e18 (lastSalePrice[2] == 0). Under the global reading the start would be 5,000,000e18. Conversely tokenId 1 exiting again starts at 5,000,000e18 regardless of any intervening sale of another tokenId.","severity":"info","snippet":"        uint256 start = lastSalePrice[id] * 10;","title":"Auction start price keys 'previous auction's sale price' per tokenId, not to the most recent auction overall"}],"hash":"4a775ae9639d8fd30137cd7160a9d7e64e57d2afef5df2158e87ae0a8975f4f5","nodeId":"389e83b6-ad34-48eb-96fd-fdb58aa3494a","outcome":"completed","summary":"The review is complete. The findings file is written and validated, the scratch probes are removed, and the tree is otherwise untouched.\n\n**Result: no arithmetic, boundary, or numerical-gap defect found.** One informational note is recorded. The file `.imd-findings.json` holds that note plus a coverage record answering all 22 entry points and four invariants.\n\n**What I checked in the assigned area**\n\n- **Hook fee math, all four swap modes.** I proved by hand and confirmed by 3,000-run fuzzing that the split `team = gross/100`, `normal = floor(gross × 3.5%) − team`, `surplus = fee − baseFee` never underflows, sums exactly to the fee taken, and that surplus is exactly zero after minute 60 and on every sell. Partial fills at random price limits under random decay rates behaved identically to full fills.\n- **Quote-and-match logic.** For exact-input buys the recomputed partial fee is never larger than the pre-quote fee, so the real swap fills the same amount. For exact-output sells the gross-up identity `requested + floor(gross × r) == gross` holds and `requested + fee >= actual` on partial fills, so the `QuoteMismatch` check cannot misfire.\n- **Distributor accumulator and stream.** Scaled credit and debt settle at the current accumulator, so no retroactive accrual. The vesting expression cannot underflow, backlog never goes negative, and every `_schedule` call is preceded by a checkpoint. A three-holder stream-restart fuzz with exits left 2 wei of dust and never exceeded solvency.\n- **Claim fallback.** The tokens-only launch fixture seeds only 0.1 ETH, so fees above that mint ERC-6909 claims. Entitlement at swap time, redemption on exit, and a rejecting team wallet all reconciled to the wei.\n- **Auction pricing.** Start prices from any reachable sale (0 to the full supply) keep the logarithm argument positive and the price monotone between start and the 50,000 OG floor.\n- **Boundaries.** SPEPE's bytecode exposes the standard ERC-721 surface and no burn selector. Delivered ABI files match the compiled contracts. The full offline suite passes.\n\n**The one note.** The auction keys \"previous auction's sale price\" per tokenId, so a tokenId's first listing always starts at 500,000 OG even if another tokenId just sold for far more. The README documents this as the accepted interpretation and nothing numerical depends on it, so it is filed as informational for the judge to confirm.\n\n**Not reached.** The live mainnet fork test was not executed here, so SPEPE's real `safeTransferFrom` behaviour against the auction receiver rests on the bytecode selector evidence and the project's recorded fork run rather than on a run of my own.","treeHash":null,"usage":{"cachedInputTokens":2392399,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":73834,"runtime":"claude","turns":48,"wallClockMs":1027213}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"5d667e4b0751bcb5","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 implementation keys the escalation by tokenId (lastSalePrice[id]), so a sale of one NFT never raises the start price of the next NFT listed. Economically this means the 10x deterrent only ever binds on repeated round-trips of the same tokenId; every first exit of any of the 1,242+ NFTs lists at the 500,000 OG minimum regardless of what the market just paid for another one. The accepted bundle documents this as a deliberate interpretation, so this is reported as a spec-reading divergence for the requester to confirm, not as a fund-loss defect. If the global reading is intended, replace the per-id mapping with a single lastSalePrice and keep the per-id history as an extra view.","line":47,"path":"src/OGAuction.sol","reproduction":"State: ALICE owns #1 and BOB owns #2, both activated at L1 and past the 24h lock. Calls: (1) ALICE: dist.exit(1) -> auction.price(1) == 500_000e18. (2) BOB: auction.buy(1, 500_000e18, BOB, now) -> auction.lastSalePrice(1) == 500_000e18. (3) BOB: dist.exit(2). Expected under the literal spec: auction.price(2) == 5_000_000e18 (10x the previous auction's sale). Actual: auction.price(2) == 500_000e18 (lastSalePrice[2] is 0, so start = MIN_START). Verified with a scratch Foundry test on this tree.","severity":"low","snippet":"        uint256 start = lastSalePrice[id] * 10;","title":"Auction start price escalates per tokenId, not from the previous auction's sale price"},{"citation":"resolved","description":"Flow gap (execution x periphery). When a fee was recorded as an ERC-6909 claim (manager ETH balance below the fee at swap time), unfundedFees becomes non-zero and every subsequent exit() first calls hook.redeemFees(), which calls poolManager.unlock(). PoolManager.unlock reverts with AlreadyUnlocked if the manager is already unlocked, so an exit executed from inside any router/unlock callback (batched 'sell OG then exit' flows, smart-account multicalls that open an unlock, or an exit attempted from a callback of another swap) reverts with no way for that caller to opt out of the redemption. The state is not permanent: anyone can call hook.redeemFees() in a separate top-level transaction and the exit then succeeds, so impact is a composability/availability limitation rather than a loss. The README documents this behaviour; it is recorded here so the judge has the concrete trigger. A minimal fix is to make the redemption best-effort (skip it when poolManager.isUnlocked() or wrap it in try/catch) and only require funding when pending(id) exceeds the contract's liquid balance.","line":199,"path":"src/OGDistributor.sol","reproduction":"State: fresh manager seeded with OG only (tokens-only launch seed), NFT #1 activated at L1 by contract E (which implements unlockCallback and onERC721Received). Calls: (1) buy 0.1 ETH exact-input after the decay hour -> fee minted as claims, dist.unfundedFees() == 0.0025e18. (2) warp +24h. (3) E calls poolManager.unlock('') and inside its unlockCallback calls dist.exit(1). Expected: exit pays E its pending ETH (the ETH is fundable). Actual: reverts (PoolManager AlreadyUnlocked bubbled through hook.redeemFees). (4) Top-level hook.redeemFees() then E.go(1) again -> succeeds, dist.level(1) == 0. Verified with a scratch Foundry test on this tree.","severity":"low","snippet":"        if (unfundedFees != 0) IOGPool(hook).redeemFees();","title":"exit() reverts for any caller inside an active PoolManager unlock while deferred fee claims are outstanding"},{"citation":"resolved","description":"Economic observation against the brief's rule 'No claim that keeps the NFT'. The listing created by exit() is buyable in the same transaction (price() returns startPrice at elapsed == 0 and buy() has no minimum listing age, no exclusion of the previous owner and no restriction on being called from the exit's own ETH-send callback). An owner can therefore call exit(id), receive all pending ETH, and in the same transaction buy the NFT back at the 500,000 OG start price and re-activate it. At the policy opening price of 100,000,000 OG per ETH the round trip burns 900,000 OG (500,000 auction + 400,000 re-activation at L3), about 0.009 ETH, against whatever ETH was pending. The per-token 10x escalation limits this to a few cheap rounds per NFT (next start 5,000,000 OG, then 50,000,000), and all proceeds are burned, so the mechanism is arguably the spec's intended exit route. It is reported so the requester can decide whether a minimum listing age, a short delay before the first buy, or a higher minimum start is wanted; no code change is required if the current economics are accepted.","line":69,"path":"src/OGAuction.sol","reproduction":"State: contract C owns #7, activated at L3, 1 ETH of normal fees received, warp +24h. Calls (single transaction): C.claim() -> dist.exit(7); inside C's receive(): auction.buy(7, max, C, now); after exit returns: dist.activate(7, 3). Result: C.balance == 1 ether, nft.ownerOf(7) == C, dist.level(7) == 3, token.totalBurned() increased by exactly 900_000e18, auction.lastSalePrice(7) == 500_000e18. Expected per the brief: the only way to receive the pending ETH is to give up the NFT. Verified with a scratch Foundry test on this tree.","severity":"low","snippet":"        uint256 cost = price(id);","title":"Exit plus same-transaction auction buy-back is a de-facto 'claim that keeps the NFT' costing 500,000 OG"},{"citation":"resolved","description":"The accumulator correctly prevents retroactive rewards, but the only cost of joining is the fixed OG burn, which at the policy opening price is 0.0005 ETH (L1) to 0.004 ETH (L3). A holder who sees a large buy in the mempool can front-run it with an L3 activation and immediately receive 4/(totalWeight) of that swap's 2.5% reward, then exit after the 24h lock (keeping what accrued). The 24h lock delays exit but does not reduce the captured share, and the exited NFT can be bought back from its auction (see finding 3). This is consistent with the brief (fixed costs, no oracle, weighted accrual), so it is recorded as an economic property for the requester, not as a defect.","line":189,"path":"src/OGDistributor.sol","reproduction":"State: ALICE #1 active at L1 (weight 1), totalWeight 1, after the decay hour. Calls: BOB activates #2 at L3 (burns 400,000 OG) in the block before a 10 ETH exact-input buy. Result: reward share of the buy = 0.25 ETH; dist.pending(2) == 0.2 ETH, dist.pending(1) == 0.05 ETH. BOB's cost at the policy price: 400,000 OG = 0.004 ETH. Verified with a scratch Foundry test on this tree (fixture price 1e6 OG/ETH, same shares).","severity":"info","snippet":"        debtScaled[id] = levelWeight(newLevel) * accPerWeight;","title":"No minimum tenure: an activation in the same block as a large swap captures that swap's full weighted share"},{"citation":"resolved","description":"The brief asks for backlog to be 'streamed into the accumulator linearly over 30 days once something is active'. _schedule() always restarts a full 30-day window for the whole unstreamed remainder, so (a) every launch-hour buy with surplus restarts the clock while NFTs are active, and (b) after all NFTs exit and one re-activates, the remainder streams at a slower rate than the original schedule. Conservation holds (verified: no backlog is lost or double-counted; backlogLeft reaches 0 at streamEnd), and the accepted bundle documents this policy, so this is informational.","line":108,"path":"src/OGDistributor.sol","reproduction":"State: 30 ETH backlog with totalWeight 0, ALICE activates #1 (stream 1 ETH/day). Calls: warp +10 days, ALICE exits (pending 10 ETH paid, backlogLeft 20 ETH, streamEnd 0); BOB activates #2. Expected under a strict 'remaining 20 days' reading: 1 ETH/day, done in 20 days. Actual: streamEnd = now + 30 days, 0.667 ETH/day; after 15 days pending(2) == 10 ETH. Matches the existing test test_streamPausesWithZeroWeightAndRestarts.","severity":"info","snippet":"        streamEnd = backlog == 0 || totalWeight == 0 ? 0 : block.timestamp + STREAM_DURATION;","title":"Backlog stream re-amortizes the remainder over a fresh 30 days on every launch-surplus arrival and after every zero-weight pause"},{"citation":"resolved","description":"launchFeeNow() decays from the beforeInitialize timestamp. The README and the launch description state the factory initializes and seeds policy liquidity in one transaction, in which case this is exactly 'the first 60 minutes after the pool opens'. If the deployment process ever initialized first and added liquidity in a later block, the 50% window would be partly consumed before anyone could trade. Recorded as a deployment assumption for the admission check, not a code defect.","line":108,"path":"src/OGHook.sol","reproduction":"State: factory initializes at T, policy liquidity is added at T + 30 minutes. First possible buy at T + 30 minutes pays launchFeeNow() == 0.2675e18 (26.75%) instead of 50%, and decayMinutesLeft() == 30. With same-transaction seeding (the documented process) the first buy pays 50%.","severity":"info","snippet":"        openedAt = block.timestamp;","title":"Launch-protection decay clock starts at pool initialization, not at first liquidity"}],"hash":"63b6c6ca8afebdfbed7fc7dea66a3d2a4d380cbea63c717c8a86e110750f916a","nodeId":"03221e45-e6a5-4b24-a122-f78b50c0aa60","outcome":"completed","summary":"The review is complete and `.imd-findings.json` is written at the repository root with 6 findings and 25 coverage rows. No source files were changed; the scratch tests live only under the ignored `test/scratch/` directory.\n\n**Outcome: no critical, high or medium defect in the economics.** I traced the Economic Security, Invariant and Flow Gap guides against every entry point and could not break fee accounting, accumulator math, backlog conservation, ETH solvency, or weight conservation. The existing suite of 40 tests passes on this tree.\n\n**What I verified in depth**\n\n- Hook fee algebra for all four swap modes, including partial fills. The reverting self-quote always reproduces the real swap's ETH delta, and the 1% / 2.5% / surplus split cannot underflow after the decay hour.\n- Deferred ERC-6909 claims are always backed by PoolManager ETH, so redemption and exit cannot be permanently blocked.\n- Accumulator, scaled credit/debt, stream scheduling and checkpoints conserve every wei. Dust only stays in the contract.\n- The SPEPE collection source on Blockscout is plain OpenZeppelin ERC721 with Ownable2Step. No transfer override, pause or operator filter exists, so exits cannot be blocked by the collection owner.\n\n**Findings reported, all with concrete reproductions**\n\n1. Low. Auction start-price escalation is keyed per tokenId, not by the previous auction's sale as the brief reads. Token 2 lists at 500,000 OG right after token 1 sold for 500,000 OG.\n2. Low. While deferred claims are outstanding, `exit` reverts for any caller already inside a PoolManager unlock, because redemption needs a fresh unlock. A top-level `redeemFees` call clears it.\n3. Low. Exit plus a same-transaction auction buy-back acts as a claim that keeps the NFT. One transaction collected 1 ETH pending, re-bought the NFT and re-activated at L3 for 900,000 OG burned, about 0.009 ETH at the opening price.\n4. Info. No minimum tenure: an L3 activation front-running a 10 ETH buy captures 0.2 ETH of its reward for a 0.004 ETH burn.\n5. Info. Stream restarts re-amortize the remainder over a fresh 30 days after pauses and launch surplus.\n6. Info. The decay clock starts at pool initialization, so the launch must seed liquidity in the same transaction as documented.\n\n**Not reached:** nothing in the assigned area. The live mainnet fork suite was not executed here, since it needs an archive RPC; the offline suite and my scratch probes ran against a real PoolManager.","treeHash":null,"usage":{"cachedInputTokens":2968319,"inputTokens":642,"model":"claude-fable-5-1","outputTokens":77542,"runtime":"claude","turns":48,"wallClockMs":1488614}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"5835e48821d8827d","findings":[{"citation":"resolved","description":"The brief states 'Start price = 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 mapping, so the sale of one NFT never raises the start price of the next NFT listed: every tokenId's first auction opens at the 500,000 OG minimum regardless of what the immediately preceding auction cleared, and the only history view is lastSalePrice(uint256). All four specialists reported this independently; merged here as one finding. README.md documents the per-tokenId reading as a deliberate interpretation and the math under it is sound (start >= 500,000e18 keeps wadLn's argument positive; a sale bounded by the 1e9 OG supply keeps the next start <= 1e28 wei). No funds are at risk, so this is a spec-text divergence for the requester to confirm rather than an accounting defect. If the global reading is intended: keep one storage lastSalePrice updated in buy(), use it in list(), and keep the per-id mapping as an extra view.","line":47,"path":"src/OGAuction.sol","reproduction":"Fresh system (test/SystemBase.sol _system(true,true)), warp past the decay hour, mint #1 to ALICE and #2 to BOB, both activate at L1, warp 24h. (1) ALICE: dist.exit(1) -> auction.price(1) == 500_000e18. (2) BOB: auction.buy(1, 500_000e18, BOB, now) -> auction.lastSalePrice(1) == 500_000e18. (3) BOB: dist.exit(2). Expected under the brief's wording: auction.price(2) == 5_000_000e18 (10x the previous auction's sale). Actual: auction.price(2) == 500_000e18 and auction.lastSalePrice(2) == 0. Executed in test/scratch/Repro.t.sol test_auctionStartKeyedPerTokenId on this tree (passes, demonstrating the per-id behaviour). Also: OGAuction has no lastSalePrice() without an argument.","severity":"low","snippet":"        uint256 start = lastSalePrice[id] * 10;","title":"Auction start price keys the 10x rule on the same tokenId's last sale, not the previous auction's sale, and there is no global lastSalePrice() view"},{"citation":"resolved","description":"When a fee was recorded as an ERC-6909 claim (manager ETH balance below the fee at afterSwap time), unfundedFees becomes non-zero and every later exit() unconditionally calls hook.redeemFees(), which calls poolManager.unlock(). PoolManager.unlock reverts with AlreadyUnlocked when the manager is already unlocked (lib/v4-core/src/PoolManager.sol line 105), so an exit executed from inside any router or smart-account unlock callback reverts with no way for that caller to opt out, even when the distributor's liquid balance would cover the payout. The state is not permanent: anyone can call hook.redeemFees() in a separate top-level transaction and the exit then succeeds, and on the real mainnet PoolManager (which holds large native balances) the claim path is practically never taken, so the impact is a composability/availability limitation rather than a loss. README.md documents it. Minimal fix: skip the redemption when poolManager.isUnlocked() is true (or wrap it in try/catch) and only require funding when pending(id) exceeds address(this).balance.","line":199,"path":"src/OGDistributor.sol","reproduction":"State: fresh manager seeded with OG only (test/scratch/Repro.t.sol test_exitInsideUnlockRevertsWhileClaimsOutstanding): _system(false,true); router.liquidity(key, ModifyLiquidityParams(120000,138120,1e22,0)); contract E (implements unlockCallback and onERC721Received) owns #1 and activates L1. (1) warp +1h; buy 0.1 ETH exact input -> fee minted as claims, dist.unfundedFees() == 0.0025e18. (2) warp +24h. (3) E calls poolManager.unlock('') and inside its unlockCallback calls dist.exit(1). Expected: exit pays E 0.0025 ETH. Actual: reverts with IPoolManager.AlreadyUnlocked bubbled through hook.redeemFees. (4) Top-level hook.redeemFees() then E.go(1) again -> succeeds, dist.level(1) == 0, E.balance == 0.0025e18. Executed on this tree.","severity":"low","snippet":"        if (unfundedFees != 0) IOGPool(hook).redeemFees();","title":"exit() reverts for any caller inside an active PoolManager unlock while deferred fee claims are outstanding"},{"citation":"resolved","description":"The listing created by exit() is buyable in the same transaction: price() returns startPrice at elapsed == 0 and buy() has no minimum listing age and no exclusion of the previous owner, and it is callable from the exiting owner's ETH-receive callback because OGAuction and OGDistributor carry separate Guard instances. An owner can therefore call exit(id), receive all pending ETH, buy the NFT back at the start price inside the payout callback, and re-activate it after exit returns. At the policy opening price the first round trip burns 900,000 OG (500,000 auction + 400,000 L3 re-activation), about 0.009 ETH, against whatever ETH was pending; the per-token 10x escalation makes the next rounds 5,000,000 and 50,000,000 OG, and all proceeds are burned, so this is arguably the exit route the brief's own auction design permits. Reported so the requester can decide whether a minimum listing age, a short delay before the first buy, or a higher minimum start is wanted; no code change is required if the current economics are accepted.","line":69,"path":"src/OGAuction.sol","reproduction":"test/scratch/Repro.t.sol test_exitWithBuybackInsidePayoutKeepsNFT: contract C owns #7, activates L3, 1 ETH of normal fees received (_fees(1 ether,0)), warp +24h. Single transaction C.go(): dist.exit(7); inside C.receive(): auction.buy(7, max, C, now); after exit returns: dist.activate(7, 3). Result on this tree: C.balance == 1 ether, nft.ownerOf(7) == C, dist.level(7) == 3, token.totalBurned() increased by exactly 900_000e18, auction.lastSalePrice(7) == 500_000e18. Expected per the brief's 'No claim that keeps the NFT': the only way to receive the pending ETH is to give up the NFT.","severity":"info","snippet":"        uint256 cost = price(id);","title":"Exit plus same-transaction auction buy-back is a de-facto claim that keeps the NFT, priced at the 500,000 OG start"},{"citation":"resolved","description":"Verified on Ethereum mainnet on 2026-10-08 through two independent public RPCs (eth-mainnet.public.blastapi.io and ethereum-rpc.publicnode.com): SwarmPepe.owner() at 0x999ce0CE8C5f7661e0c74a568FfE27CEB9177bDB returns 0x90738ABe9b04622Dc0b3d015a3964Cc7D1Fd1859, the address hardcoded as TEAM. The collection bytecode exposes Ownable2Step (owner/pendingOwner/acceptOwnership), claim(), mint-open and allowlist setters; mintOpen() is true and totalMinted() is 1,242 of MAX_SUPPLY 5,000. The OG contracts hold no owner powers and no collection transfer hook exists, so the collection owner cannot touch levels, pending ETH, locks, exits or auction custody. The gap is economic: the party receiving 1% of every trade can also allot up to ~3,758 further NFTs to wallets it chooses at zero mint cost, each of which (after burning 50,000 OG) dilutes every existing holder's share of the 2.5% reward stream and backlog. README.md line 102 records collection-owner powers generically but does not state that the collection owner is the team wallet. A trust assumption to document, not a code defect; it does not block launch.","line":20,"path":"src/OGHook.sol","reproduction":"cast call 0x999ce0CE8C5f7661e0c74a568FfE27CEB9177bDB 'owner()(address)' --rpc-url https://eth-mainnet.public.blastapi.io -> 0x90738ABe9b04622Dc0b3d015a3964Cc7D1Fd1859 (same result from https://ethereum-rpc.publicnode.com). Then from TEAM: SwarmPepe.grant([W], 3); from W: SwarmPepe.claim() -> three new tokenIds; W approves OG and calls OGDistributor.activate(newId, 1) three times -> totalWeight rises by 3 and every pre-existing NFT's share of all later fees falls proportionally (no snapshot or enumeration limits eligibility, see README 'newly minted NFTs are eligible').","severity":"info","snippet":"    address public constant TEAM = 0x90738ABe9b04622Dc0b3d015a3964Cc7D1Fd1859;","title":"Trust assumption: the fixed team wallet is the owner of the SPEPE collection and can grant free mints that dilute every active NFT"},{"citation":"resolved","description":"The brief asks for backlog to be 'streamed into the accumulator linearly over 30 days once something is active'. _schedule() always restarts a full 30-day window for the whole unstreamed remainder, so every launch-hour buy with surplus restarts the clock while NFTs are active (bounded to the first 60 minutes, negligible), and after all NFTs exit and one re-activates the remainder streams at a slower rate than the original schedule. Conservation holds: _checkpoint() releases the vested part before each restart, backlogLeft() reaches 0 at streamEnd, and no backlog is lost or double-counted (checked by the LifecycleInvariantTest and AccountingInvariantTest campaigns, which pass). README.md documents this as the chosen scheduling policy, so this is informational.","line":108,"path":"src/OGDistributor.sol","reproduction":"test/Distributor.t.sol test_streamPausesWithZeroWeightAndRestarts on this tree: 30 ETH backlog with totalWeight 0, ALICE activates #1 (stream 1 ETH/day), warp +10 days, ALICE exits (paid 10 ETH, backlogLeft 20 ETH, streamEnd 0); BOB activates #2. Under a strict 'remaining 20 days' reading pending(2) after 15 days would be 15 ETH; actual: streamEnd = now + 30 days, rate 0.667 ETH/day, pending(2) == 10 ETH after 15 days.","severity":"info","snippet":"        streamEnd = backlog == 0 || totalWeight == 0 ? 0 : block.timestamp + STREAM_DURATION;","title":"Backlog stream re-amortizes the remainder over a fresh 30 days on every launch-surplus arrival and after every zero-weight pause"}],"hash":"662e740adfee37c4f39ee6796bacb000f2958a25e51289f79c0f540e0d2126b0","nodeId":"49255f18-8379-4d5e-8e2b-6cf63e754698","outcome":"completed","summary":"The review is complete. The findings file is in place at the repository root, and no source, test, or config file was changed.\n\n**Verdict: no blocking defects.** Every specialist finding reproduced against the code, but none rises above low. The hook's fee mechanics, delta accounting, deferred-claim backing, reward accumulator, lock, exit, and auction logic all hold under tracing and under execution.\n\n**What I ran, beyond reading**\n- Full offline suite: 62 passed, 0 failed, 2 fork tests skipped as designed.\n- Both mainnet fork suites against real SPEPE at block 26147052 over a public RPC: 3 passed.\n- Scratch reproductions of each specialist claim, plus two probes of my own (empty-pool quote path, launch-window ETH activation on an ETH-less manager). All behaved as the code intends.\n- Two independent RPCs confirm the SPEPE owner is the hardcoded team wallet.\n- The shipped ABI files equal the compiled ABIs. The v4 self-sender skip the quote relies on exists in the vendored library.\n\n**Findings written (5), in severity order**\n1. **Low.** Auction start price keys the 10x rule per tokenId, and there is no argument-free last-sale view. Four specialists reported this; merged into one. Spec-wording divergence, documented by the author, no funds at risk.\n2. **Low.** `exit()` reverts inside any active PoolManager unlock while deferred fee claims are outstanding. Self-resolving with a top-level redeem, and practically unreachable on the real mainnet manager.\n3. **Info.** Exit plus same-transaction buy-back keeps the NFT for a 500,000 OG burn. Permitted by the brief's own auction design; the per-token 10x escalation limits it.\n4. **Info.** Trust assumption: the team wallet owns the SPEPE collection and can grant free mints that dilute active holders. Verified on chain today.\n5. **Info.** Backlog re-amortizes over a fresh 30 days on each surplus arrival or zero-weight pause. Conservation holds; documented policy.\n\nI dropped the front-run-activation and decay-clock notes as by-design and deployment assumptions respectively, not defects.\n\n**Coverage record:** all 21 entry points answered, 23 rows hold, 3 cite a finding, 0 unreached, plus rows for the solvency, weight, hook-delta, fork, and manifest checks.\n\n**Static-analysis leads:** all slither and aderyn lines were checked. The reentrancy lines are guarded by `busy`, `idle`, or `nonReentrant`; the equality, timestamp, and unused-return lines are benign by inspection.\n\nNote for the requester: since findings 1 and 2 are low and documented interpretations of the accepted bundle, whether to change behaviour is a product decision rather than a required fix.","treeHash":null,"usage":{"cachedInputTokens":2732914,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":78553,"runtime":"claude","turns":46,"wallClockMs":1245394}},{"artifacts":[],"attempt":1,"bundleHash":"9516e05de337da39340ea929fcb3ce6ba7306e1660cf3e13c04672dbcf578d0c","device":"575a41a8798743c2","findings":[],"hash":"75487cfe51e2cf615fbf619fab72b86b0b8d260b3ed6ccb270ae0929ec2657b7","nodeId":"1f844151-6776-43f1-b64c-95a9945ef091","outcome":"completed","summary":"Restored the accepted bundle and preserved its production logic. Added [launch.json](/home/imd/.identitymd/work/babb0da2-bd96-4fe7-b753-945acdf5872e/1f844151-6776-43f1-b64c-95a9945ef091/launch.json) with the requested price and policy details.\n\nVerified:\n\n- Build and formatting pass.\n- Offline tests: 40 passed; one fork test intentionally skipped.\n- Mainnet fork: all 41 passed.\n- Protected checks: all 11 passed.\n- Independent review: no new confirmed contract defects.\n- SPEPE minting is open; `totalMinted()` is 1,242.\n\nContracts deploy together through the launch. No mainnet transaction was broadcast.","treeHash":"df27ba3a78d563a4e3703c19b4713ced12c1b272","usage":{"cachedInputTokens":2681088,"inputTokens":126958,"model":"gpt-6-astra","outputTokens":18401,"runtime":"codex","turns":7,"wallClockMs":694271}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"a7b8747ac077deb9","findings":[{"citation":"resolved","description":"The brief says the start price is max(10x the previous auction's sale price, 500,000 OG) and lists a single 'last sale price' view for the website. The implementation stores lastSalePrice per tokenId (mapping(uint256 => uint256) public lastSalePrice) and uses only that tokenId's own history when listing, so every tokenId's first auction starts at 500,000 OG regardless of what the previous auction (of any tokenId) sold for, and the site gets lastSalePrice(uint256) instead of a no-argument lastSalePrice(). The README documents this as the accepted per-tokenId interpretation and it has a defensible rationale (the 10x escalation then targets an owner who repeatedly exits and buys back the same NFT), so this is recorded for the judge as a spec-interpretation note, not a code defect. No funds are at risk; control flow around list/buy/onERC721Received is otherwise correct.","line":47,"path":"src/OGAuction.sol","reproduction":"Setup: two active NFTs, ids 1 and 2, both past the 24h lock. 1) owner of id 1 calls OGDistributor.exit(1): AuctionListed(1, 500000e18). 2) warp 20 hours; a buyer calls OGAuction.buy(1, maxPrice, buyer, deadline) at roughly 139,000 OG (price(1) at t=20h); lastSalePrice(1) is now about 139e21. 3) owner of id 2 calls OGDistributor.exit(2). Actual: AuctionListed(2, 500000e18) because lastSalePrice[2] == 0. Under a global reading of 'the previous auction's sale price' the start would be about 1,390,000 OG. Also actual: OGAuction has no lastSalePrice() view without an argument; only lastSalePrice(uint256).","severity":"info","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 the previous auction sale; no single lastSalePrice view"}],"hash":"d21df9e1d6764605ee1b34a95d10d3d3e66feb2ef9202374eb6c0345a7db4475","nodeId":"3d7343fa-789a-463a-9f3a-b94dbda4015c","outcome":"completed","summary":"The review is complete. The findings file is valid JSON, the quoted snippet matches lines 47 and 48 of the auction source, and the working tree is clean apart from the gitignored scratch directory and the findings file itself.\n\n**Outcome: no control-flow defect found in the assigned area.** The Execution Trace, Periphery and First Principles passes over all six source files turned up one spec-interpretation note and nothing that loses funds or breaks a guarantee.\n\n**What I verified**\n\n- **Hook swap flow in all four modes.** I traced the reverting self-quote, the before/after delta accounting against v4-core's Hooks and PoolManager code, and proved algebraically that the re-run swap always reproduces the quoted ETH delta, including partial fills at a binding price limit. The fee ordering fee ≥ baseFee ≥ team holds for every path, so the surplus subtraction cannot underflow. Scratch fuzzing with 512 runs per mode at the real launch price, random limits and random elapsed time inside the decay window confirmed the fee equals gross × rate within 2 wei and the hook ends with zero balance, zero delta and zero leftover claims.\n- **Claims fallback.** Mixed buy and sell swaps in one unlock on a manager holding no ETH mint claims that match the distributor's unfunded total exactly, and a single redeem clears both sides. The team wallet is a plain EOA on mainnet and the mainnet PoolManager holds about 52,000 ETH, so the direct take path applies at launch.\n- **Distributor accounting.** Backlog equals streamTotal minus released during a stream, the stream end is zero whenever total weight is zero, checkpoint always precedes schedule, and exits redeem unfunded claims before paying. An ETH-funded upgrade on the claims path during decay followed by exits paid exactly the pending amounts.\n- **Periphery.** The collection is a verified plain OpenZeppelin ERC721 with no burn or transfer overrides. Committed ABI files match the compiled output. Guard, HookFlags and the interfaces behave as the core assumes.\n\n**The one note recorded** is that the auction's 10x rule and the `lastSalePrice` view are keyed per tokenId, while the brief reads as a single previous-auction price. The README documents this as the accepted interpretation and it has a sound rationale, so I filed it as info with a concrete call sequence for the judge rather than as a defect.\n\n**Coverage** has a row for all 21 listed entry points plus four invariant and assumption rows. Every entry point is marked `holds` except `OGAuction.list`, which points at the info note.","treeHash":null,"usage":{"cachedInputTokens":2951493,"inputTokens":674,"model":"claude-fable-5-1","outputTokens":72600,"runtime":"claude","turns":49,"wallClockMs":1467998}},{"artifacts":[],"attempt":1,"bundleHash":"fb856fcc8b539a2fca17e6e5af71cd409dfc9fc59f2e992b764f086768d3358a","device":"a9158e223bd79f25","findings":[],"hash":"dee06d892a376798779dff601a28156a781d45927abd92ca998797bc2c686a15","nodeId":"2a054204-bbba-4557-96d5-22a0e225c16d","outcome":"completed","summary":"Updated `launch.json` with implementation details, website views, and verified mint status. Constructor arguments, permissions, and pool price match the accepted code.\n\nValidation passed: supplied schema, `forge build`, and all 41 tests on a mainnet fork. Only `launch.json` changed.","treeHash":"6107426871ce439a9d5adb90940a1c67508096ab","usage":{"cachedInputTokens":884736,"inputTokens":74133,"model":"gpt-6-astra","outputTokens":8091,"runtime":"codex","turns":5,"wallClockMs":337068}},{"artifacts":[],"attempt":1,"bundleHash":"ce47404a5417a913abcf5c9318681d587da08598af14f387c7220085ec1cb1e5","device":"74f6c6912a376f27","findings":[],"hash":"fe81f34f10e44ead3c7b05ba58cb7bd543ae70737547e176272dae4c8380a806","nodeId":"42de0291-af23-42a6-89a0-925b2da1a406","outcome":"completed","summary":"Added adversarial tests, fuzz properties, and multi-owner lifecycle invariants under `test/`.\n\n- Build passed.\n- Offline: 62 passed, 2 fork suites skipped.\n- Mainnet fork: 3 passed.\n- New invariants: 24,576 calls, zero reverts.\n\nProduction code is unchanged. No confirmed defects found. Coverage and review details are in [test/REVIEW.md](test/REVIEW.md).","treeHash":"5a1e27e4182eda02c7affa32772a27e9f4dadb15","usage":{"cachedInputTokens":3414784,"inputTokens":144626,"model":"gpt-6-astra","outputTokens":27434,"runtime":"codex","turns":10,"wallClockMs":1049144}}],"verification":[{"checks":[{"durationMs":46865,"exitCode":0,"name":"build","output":"Compiling 88 files with Solc 0.8.26\nSolc 0.8.26 finished in 46.74s\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/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[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-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[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[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[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/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[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[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: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-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[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":1701,"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 325.89µs (0.00ns 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.71ms (489.23µ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 1.80ms (2.58ms CPU time)\n\nRan 2 tests for test/LaunchPolicy.t.sol:LaunchPolicyTest\n[PASS] test_policyPriceSupportsFourModesAndETHActivation() (gas: 11169327)\n[PASS] test_policyPriceTokensOnlyLaunchClaimsAndExit() (gas: 10129053)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 3.21ms (4.27ms CPU time)\n\nRan 4 tests for test/Distributor.t.sol:AuctionTest\n[PASS] testFuzz_monotonicPrice(uint32,uint32) (runs: 256, μ: 32162, ~: 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 4.27ms (3.75ms CPU time)\n\nRan 2 tests for test/Token.t.sol:TokenTest\n[PASS] testFuzz_plainTransfers(uint256) (runs: 256, μ: 69404, ~: 69702)\n[PASS] test_supplyPlainTransferAllowanceAndBurn() (gas: 309826)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 4.39ms (4.52ms CPU time)\n\nRan 12 tests for test/Hook.t.sol:HookTest\n[PASS] testFuzz_feeBuyAndSell(uint96,bool,bool) (runs: 256, μ: 296440, ~: 272323)\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 15.31ms (19.99ms CPU time)\n\nRan 3 tests for test/ReviewRegression.t.sol:ReviewRegressionTest\n[PASS] testFuzz_tinyFeeRounding(uint16,bool,bool,uint16) (runs: 256, μ: 270757, ~: 264958)\n[PASS] test_fourModesInSameUnlockSettleNetAmounts() (gas: 1155992)\n[PASS] test_teamReentryCannotReuseFeeCredit() (gas: 527011)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 18.55ms (18.52ms CPU time)\n\nRan 11 tests for test/Distributor.t.sol:DistributorTest\n[PASS] testFuzz_roundingConservesETH(uint96) (runs: 256, μ: 1211359, ~: 1211273)\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 24.75ms (27.23ms CPU time)\n\nRan 1 test for test/Launch.t.sol:LaunchTest\n[PASS] test_realCreate2DeploymentInitializesAllContracts() (gas: 38835754)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 30.85ms (30.56ms 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 | 1640  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | advance  | 1646  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | buy      | 1660  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | exit     | 1633  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | fees     | 1613  | 0       | 0        |\n╰---------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.62s (1.62s CPU time)\n\nRan 11 test suites in 1.62s (1.73s CPU time): 40 tests passed, 0 failed, 1 skipped (41 total tests)\n","passed":true},{"durationMs":54,"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\":119,\"abi/OG.json\":260,\"abi/OGAuction.json\":383,\"abi/OGDistributor.json\":713,\"abi/OGHook.json\":837,\"docs/accepted-validation.md\":27,\"docs/checks/mainnet-all-tests.txt\":86,\"docs/checks/mainnet-lifecycle.txt\":7,\"docs/checks/offline-tests.txt\":86,\"docs/checks/revision-build.txt\":673,\"docs/checks/revision-empty-environment-tests.txt\":97,\"docs/checks/revision-launch-tests.txt\":14,\"docs/checks/revision-mainnet-all-tests.txt\":97,\"docs/checks/revision-mainnet-lifecycle.txt\":9,\"docs/checks/revision-offline-tests.txt\":97,\"docs/checks/revision-protected-tests.txt\":22,\"docs/checks/revision-source-sha256.json\":24,\"docs/checks/source-sha256.json\":21,\"docs/dependencies.txt\":3,\"docs/independent-review.md\":46,\"docs/launch-config.json\":40,\"docs/mainnet-verification-current.json\":438,\"docs/mainnet-verification.json\":548,\"docs/provenance.json\":8,\"docs/revision-review.md\":30,\"docs/validation.md\":56,\"foundry.toml\":19,\"launch.json\":27,\"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\":69,\"test/LaunchPolicy.t.sol\":98,\"test/MainnetFork.t.sol\":64,\"test/PoolRouter.sol\":65,\"test/ReviewRegression.t.sol\":98,\"test/SystemBase.sol\":104,\"test/Token.t.sol\":40,\"test/mocks/MockERC20.sol\":9,\"test/mocks/MockSpepe.sol\":24},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":3370,"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":340,"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":"75487cfe51e2cf615fbf619fab72b86b0b8d260b3ed6ccb270ae0929ec2657b7","verifiedTreeHash":"df27ba3a78d563a4e3703c19b4713ced12c1b272","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":45320,"exitCode":0,"name":"build","output":"Compiling 88 files with Solc 0.8.26\nSolc 0.8.26 finished in 45.19s\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-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]: `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/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[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[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[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[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[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[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[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-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-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":1718,"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 295.28µ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.56ms (2.42ms 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.77ms (683.47µs CPU time)\n\nRan 2 tests for test/LaunchPolicy.t.sol:LaunchPolicyTest\n[PASS] test_policyPriceSupportsFourModesAndETHActivation() (gas: 11169327)\n[PASS] test_policyPriceTokensOnlyLaunchClaimsAndExit() (gas: 10129053)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 3.23ms (4.31ms CPU time)\n\nRan 2 tests for test/Token.t.sol:TokenTest\n[PASS] testFuzz_plainTransfers(uint256) (runs: 256, μ: 69509, ~: 69702)\n[PASS] test_supplyPlainTransferAllowanceAndBurn() (gas: 309826)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 4.56ms (4.78ms CPU time)\n\nRan 4 tests for test/Distributor.t.sol:AuctionTest\n[PASS] testFuzz_monotonicPrice(uint32,uint32) (runs: 256, μ: 32071, ~: 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 4.92ms (4.11ms CPU time)\n\nRan 12 tests for test/Hook.t.sol:HookTest\n[PASS] testFuzz_feeBuyAndSell(uint96,bool,bool) (runs: 256, μ: 293662, ~: 272305)\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 16.17ms (21.60ms CPU time)\n\nRan 3 tests for test/ReviewRegression.t.sol:ReviewRegressionTest\n[PASS] testFuzz_tinyFeeRounding(uint16,bool,bool,uint16) (runs: 256, μ: 270024, ~: 264958)\n[PASS] test_fourModesInSameUnlockSettleNetAmounts() (gas: 1155992)\n[PASS] test_teamReentryCannotReuseFeeCredit() (gas: 527011)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 19.08ms (19.44ms CPU time)\n\nRan 11 tests for test/Distributor.t.sol:DistributorTest\n[PASS] testFuzz_roundingConservesETH(uint96) (runs: 256, μ: 1211369, ~: 1211273)\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 27.66ms (29.76ms CPU time)\n\nRan 1 test for test/Launch.t.sol:LaunchTest\n[PASS] test_realCreate2DeploymentInitializesAllContracts() (gas: 38835754)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 32.62ms (32.32ms 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 | 1608  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | advance  | 1646  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | buy      | 1684  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | exit     | 1654  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | fees     | 1600  | 0       | 0        |\n╰---------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.63s (1.63s CPU time)\n\nRan 11 test suites in 1.64s (1.75s CPU time): 40 tests passed, 0 failed, 1 skipped (41 total tests)\n","passed":true},{"durationMs":50,"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\":119,\"abi/OG.json\":260,\"abi/OGAuction.json\":383,\"abi/OGDistributor.json\":713,\"abi/OGHook.json\":837,\"docs/accepted-validation.md\":27,\"docs/checks/mainnet-all-tests.txt\":86,\"docs/checks/mainnet-lifecycle.txt\":7,\"docs/checks/offline-tests.txt\":86,\"docs/checks/revision-build.txt\":673,\"docs/checks/revision-empty-environment-tests.txt\":97,\"docs/checks/revision-launch-tests.txt\":14,\"docs/checks/revision-mainnet-all-tests.txt\":97,\"docs/checks/revision-mainnet-lifecycle.txt\":9,\"docs/checks/revision-offline-tests.txt\":97,\"docs/checks/revision-protected-tests.txt\":22,\"docs/checks/revision-source-sha256.json\":24,\"docs/checks/source-sha256.json\":21,\"docs/dependencies.txt\":3,\"docs/independent-review.md\":46,\"docs/launch-config.json\":40,\"docs/mainnet-verification-current.json\":438,\"docs/mainnet-verification.json\":548,\"docs/provenance.json\":8,\"docs/revision-review.md\":30,\"docs/validation.md\":56,\"foundry.toml\":19,\"launch.json\":31,\"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\":69,\"test/LaunchPolicy.t.sol\":98,\"test/MainnetFork.t.sol\":64,\"test/PoolRouter.sol\":65,\"test/ReviewRegression.t.sol\":98,\"test/SystemBase.sol\":104,\"test/Token.t.sol\":40,\"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":"dee06d892a376798779dff601a28156a781d45927abd92ca998797bc2c686a15","verifiedTreeHash":"6107426871ce439a9d5adb90940a1c67508096ab","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":73137,"exitCode":0,"name":"build","output":"Compiling 93 files with Solc 0.8.26\nSolc 0.8.26 finished in 73.00s\nCompiler run successful with warnings:\nWarning (2519): This declaration shadows an existing declaration.\n  --> test/LifecycleInvariants.t.sol:98:31:\n   |\n98 |     function swap(uint96 raw, bool buy, bool exactInput, uint8 actorSeed) public observe {\n   |                               ^^^^^^^^\nNote: The shadowed declaration is here:\n   --> test/LifecycleInvariants.t.sol:148:5:\n    |\n148 |     function buy(uint256 rawId, uint8 payerSeed, uint8 recipientSeed) public observe {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\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[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[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[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[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[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[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[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[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[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[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[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[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[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[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-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[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-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[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[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-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[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[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[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":7268,"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 295.27µs (0.00ns CPU time)\n\nRan 1 test for test/AdversarialMainnetFork.t.sol:AdversarialMainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 303.25µ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 2.00ms (3.14ms CPU time)\n\nRan 1 test for test/AdversarialFees.t.sol:DeferredFeesAdversarialTest\n[PASS] test_claimAndDirectFeesMixedBeforeRedemption() (gas: 10600869)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.12ms (1.87ms 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.25ms (751.18µs CPU time)\n\nRan 2 tests for test/LaunchPolicy.t.sol:LaunchPolicyTest\n[PASS] test_policyPriceSupportsFourModesAndETHActivation() (gas: 11169327)\n[PASS] test_policyPriceTokensOnlyLaunchClaimsAndExit() (gas: 10129053)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.73ms (3.67ms CPU time)\n\nRan 4 tests for test/Distributor.t.sol:AuctionTest\n[PASS] testFuzz_monotonicPrice(uint32,uint32) (runs: 256, μ: 32169, ~: 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 13.05ms (12.84ms CPU time)\n\nRan 2 tests for test/Token.t.sol:TokenTest\n[PASS] testFuzz_plainTransfers(uint256) (runs: 256, μ: 69460, ~: 69726)\n[PASS] test_supplyPlainTransferAllowanceAndBurn() (gas: 309826)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 18.87ms (19.08ms CPU time)\n\nRan 1 test for test/Launch.t.sol:LaunchTest\n[PASS] test_realCreate2DeploymentInitializesAllContracts() (gas: 38835754)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 23.87ms (23.54ms CPU time)\n\nRan 3 tests for test/ReviewRegression.t.sol:ReviewRegressionTest\n[PASS] testFuzz_tinyFeeRounding(uint16,bool,bool,uint16) (runs: 256, μ: 273607, ~: 279368)\n[PASS] test_fourModesInSameUnlockSettleNetAmounts() (gas: 1155992)\n[PASS] test_teamReentryCannotReuseFeeCredit() (gas: 527011)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 57.00ms (56.67ms CPU time)\n\nRan 12 tests for test/Hook.t.sol:HookTest\n[PASS] testFuzz_feeBuyAndSell(uint96,bool,bool) (runs: 256, μ: 294593, ~: 272305)\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 62.70ms (67.38ms CPU time)\n\nRan 11 tests for test/Distributor.t.sol:DistributorTest\n[PASS] testFuzz_roundingConservesETH(uint96) (runs: 256, μ: 1211389, ~: 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 62.84ms (47.37ms CPU time)\n\nRan 12 tests for test/AdversarialLifecycle.t.sol:AdversarialLifecycleTest\n[PASS] testFuzz_threeRewardEpochsPreserveFractionsAcrossUpgrades(uint96,uint96,uint96) (runs: 1000, μ: 1298952, ~: 1298859)\n[PASS] test_ETHActivationRefundRejectRollsBackPoolFeeBurnAndWeight() (gas: 2205138)\n[PASS] test_approvedOperatorCannotActivateUpgradeOrExit() (gas: 512014)\n[PASS] test_auctionBadRecipientsAndUnderfundedBuyerLeaveListing() (gas: 705980)\n[PASS] test_auctionReceiverRejectRollsBackBurnAllowanceAndListing() (gas: 1831498)\n[PASS] test_auctionReentryCannotBuyDifferentLiveListing() (gas: 2067771)\n[PASS] test_eventsIncludeUpgradeDifferenceExitAndGiftedSale() (gas: 860139)\n[PASS] test_exitCannotReenterAnotherEligibleNFT() (gas: 1752084)\n[PASS] test_fundingRejectsWrongAmountsAndCannotChangeEntitlement() (gas: 608547)\n[PASS] test_insufficientBurnAllowanceRollsBackUpgradeAndStream() (gas: 812226)\n[PASS] test_invalidLevelBoundariesAndNonexistentNFT() (gas: 652264)\n[PASS] test_transferUpgradeLockExactBoundaryAndZeroRewardExit() (gas: 741737)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 63.00ms (66.76ms CPU time)\n\nRan 2 tests for test/AuctionProperties.t.sol:AuctionPropertiesTest\n[PASS] testFuzz_exponentialCurveHasGeometricMidpoint(uint32) (runs: 1000, μ: 670731, ~: 670651)\n[PASS] test_paginationCapEmptyPagesAndMiddleRemoval() (gas: 45365382)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 70.44ms (89.70ms CPU time)\n\nRan 5 tests for test/AdversarialFees.t.sol:AdversarialFeesTest\n[PASS] testFuzz_actualSettlementAndEvents(uint96,uint16,bool,bool,bool) (runs: 1000, μ: 363521, ~: 349953)\n[PASS] test_callbackRejectsZeroAndNarrowingOverflow() (gas: 528771)\n[PASS] test_decaySecondAndMinuteBoundaries() (gas: 258388)\n[PASS] test_quoteLeavesSamePriceLiquidityAndLPGrowthAsUnhookedPool() (gas: 1208719)\n[PASS] test_wrongPoolEveryBoundFieldAndUnpairedAfterSwap() (gas: 228103)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 70.54ms (71.04ms 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 | 1741  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | advance  | 1622  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | buy      | 1647  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | exit     | 1582  | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RewardHandler | fees     | 1600  | 0       | 0        |\n╰---------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.67s (1.67s CPU time)\n\nRan 2 tests for test/LifecycleInvariants.t.sol:LifecycleInvariantTest\n[PASS]\nLifecycleInvariantTest invariants:\n[PASS] invariant_OGSupplyAndEveryBurnConserved\n[PASS] invariant_valueConservationAndClaimBacking\n[PASS] invariant_weightCountsAndAuctionCustody\n LifecycleInvariantTest invariants (runs: 256, calls: 24576, reverts: 0)\n\n╭------------------+--------------+-------+---------+----------╮\n| Contract         | Selector     | Calls | Reverts | Discards |\n+==============================================================+\n| LifecycleHandler | activate     | 2454  | 0       | 0        |\n|------------------+--------------+-------+---------+----------|\n| LifecycleHandler | advance      | 2468  | 0       | 0        |\n|------------------+--------------+-------+---------+----------|\n| LifecycleHandler | buy          | 2415  | 0       | 0        |\n|------------------+--------------+-------+---------+----------|\n| LifecycleHandler | donate       | 2472  | 0       | 0        |\n|------------------+--------------+-------+---------+----------|\n| LifecycleHandler | exit         | 2415  | 0       | 0        |\n|------------------+--------------+-------+---------+----------|\n| LifecycleHandler | moveOG       | 2476  | 0       | 0        |\n|------------------+--------------+-------+---------+----------|\n| LifecycleHandler | redeem       | 2481  | 0       | 0        |\n|------------------+--------------+-------+---------+----------|\n| LifecycleHandler | swap         | 2564  | 0       | 0        |\n|------------------+--------------+-------+---------+----------|\n| LifecycleHandler | teamReceiver | 2508  | 0       | 0        |\n|------------------+--------------+-------+---------+----------|\n| LifecycleHandler | transferNFT  | 2323  | 0       | 0        |\n╰------------------+--------------+-------+---------+----------╯\n\n[PASS] test_handlerReachesTransferExitSaleAndReactivation() (gas: 3234375)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 7.18s (7.18s CPU time)\n\nRan 17 test suites in 7.19s (9.31s CPU time): 62 tests passed, 0 failed, 2 skipped (64 total tests)\n","passed":true},{"durationMs":59,"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\":119,\"abi/OG.json\":260,\"abi/OGAuction.json\":383,\"abi/OGDistributor.json\":713,\"abi/OGHook.json\":837,\"docs/accepted-validation.md\":27,\"docs/checks/mainnet-all-tests.txt\":86,\"docs/checks/mainnet-lifecycle.txt\":7,\"docs/checks/offline-tests.txt\":86,\"docs/checks/revision-build.txt\":673,\"docs/checks/revision-empty-environment-tests.txt\":97,\"docs/checks/revision-launch-tests.txt\":14,\"docs/checks/revision-mainnet-all-tests.txt\":97,\"docs/checks/revision-mainnet-lifecycle.txt\":9,\"docs/checks/revision-offline-tests.txt\":97,\"docs/checks/revision-protected-tests.txt\":22,\"docs/checks/revision-source-sha256.json\":24,\"docs/checks/source-sha256.json\":21,\"docs/dependencies.txt\":3,\"docs/independent-review.md\":46,\"docs/launch-config.json\":40,\"docs/mainnet-verification-current.json\":438,\"docs/mainnet-verification.json\":548,\"docs/provenance.json\":8,\"docs/revision-review.md\":30,\"docs/validation.md\":56,\"foundry.toml\":19,\"launch.json\":27,\"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/AdversarialFees.t.sol\":216,\"test/AdversarialLifecycle.t.sol\":367,\"test/AdversarialMainnetFork.t.sol\":85,\"test/AuctionProperties.t.sol\":70,\"test/Distributor.t.sol\":360,\"test/Hook.t.sol\":192,\"test/Invariants.t.sol\":123,\"test/Launch.t.sol\":69,\"test/LaunchPolicy.t.sol\":98,\"test/LifecycleInvariants.t.sol\":325,\"test/MainnetFork.t.sol\":64,\"test/PoolRouter.sol\":65,\"test/REVIEW.md\":67,\"test/ReviewRegression.t.sol\":98,\"test/SystemBase.sol\":104,\"test/Token.t.sol\":40,\"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":"fe81f34f10e44ead3c7b05ba58cb7bd543ae70737547e176272dae4c8380a806","verifiedTreeHash":"5a1e27e4182eda02c7affa32772a27e9f4dadb15","verifierVersion":"0.1.0+ad90ce4c"}]}