{"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":"2726c329-944c-4bbb-9156-5bc4c8962091","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"94243aef975d72daba38fb4816194a211add21d8179cf6cc06c72d9434cadedb","dependsOn":["manifest"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"487b3c26577b9f3bf26bf3fc952751ab749b8d8e8c5471c0e9edb331dc5aea4a","dependsOn":["manifest"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"590bdffc6603f232b70caee2cd958054839dd598a4609f881a83e4faff11e01d","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","tools":[]},"key":"audit_imported_code","kind":"code","role":"review","skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","state":"accepted"},{"acceptedSubmissionHash":"fe840db224f0857441acc3fe070ef82889e54f7d1c732ca4b387437b3f88e190","dependsOn":["manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"da379fe8e561649bde81cf6fec0895e50c8b8ca0630329810c28916516d97d65","dependsOn":["manifest"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"630e1f2f9f68ab4f88a5999ee2cc002287761324c4edf2c376c9cef0c0267765","dependsOn":["manifest"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"d1e40d07aeb185431c9acbc1f2f8e3b62c7c76dfed2dbdfc3ccd0a4bbb78cda5","dependsOn":["audit_imported_code"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"}],"objective":"Token name: Pupate\nToken symbol: PUPATE\n\nTotal supply 1,000,000,000 with 18 decimals, minted once to its deployer. Ethereum mainnet (chain id 1), a Uniswap v4 hook launch paired with native ETH; 85% of supply to the pool.\n\nLAUNCH THIS REPOSITORY AS IT IS. The workspace is a finished, tested and reviewed project at the given base commit. The deliverable is the launch of this exact code: do not rewrite, refactor or reformat it, and do not modify src/, test/, script/, lib/ or foundry.toml. PupateHook is bound by its constructor to two contracts already deployed on Ethereum mainnet from this source: the vault Cocoon at 0xc4e514e60565a2c04bd657f18bb34b503d6c299d (its sink) and an OpenZeppelin TimelockController with a 48-hour delay at 0x8be826082c0fbaec485048aa26ef42ac147898ec (its owner).\n\nWHAT TO DO. (1) Verify that launch.json is a valid manifest: hook PupateHook with constructor ($poolManager, the Cocoon address, the timelock address); permissions afterInitialize, beforeAddLiquidity, beforeSwap, afterSwap, beforeSwapReturnDelta, afterSwapReturnDelta (flags 0x18CC, validated by the constructor against the hook's own address, so it needs a mined CREATE2 salt); token PupateToken with a zero-argument constructor; pool fee 12500, tick spacing 60, native ETH. Keep pool.initialPrice as it is (792281625142643375935439503360000, the policy's 10 ETH valuation, tick 184216): it is not a reference value to replace. (2) Run the existing suite (forge test; test/fork runs only with MAINNET_RPC_URL set) and report the result. (3) Review the code as it is; report a defect with a reproducing test instead of changing the source.\n\nKEEP THESE FACTS UNCHANGED. PupateToken is a plain ERC-20: no owner, mint, pause, fee, transfer limit or blacklist; burn and burnFrom only reduce supply. PupateHook taxes the ETH side of every swap in its launch pool, the first native-ETH pool initialised on it; swaps whose sender is the sink are not taxed. Sells pay the standing tax of 6%. Buys pay 99% when the pool opens, falling one percentage point a minute to the standing tax about 93 minutes later. The tax is collected inside the swap as ERC-6909 claims on the PoolManager; swap callbacks call only the PoolManager and move no ETH. A taxed swap that names its ETH amount and cannot fill in full reverts (PartialFill). flush() is permissionless and hands the hook's whole ETH balance to Cocoon.depositTax. The owner can only lower the standing tax and transfer ownership.\n\nLIQUIDITY. beforeAddLiquidity admits liquidity only into the launch pool and only in the transaction that initialises it (an EIP-1153 mark set in afterInitialize): the factory's deploy, initialise and seed transaction passes, and every later transaction is refused, in the same block as well. Removing liquidity and collecting fees are never gated. Known residual, kept on purpose and pinned by a test: the mark names no account, so code that gets control later inside the opening transaction could add too; the launch factory calls only the PoolManager, the token and its own registry there. test/fork/LaunchFactory.fork.t.sol rehearses this launch through the real factory.\n\nCONTEXT. Cocoon, FloorFeed and PupateVesting are in src/ for review and are not deployed by this launch. After it, script/PostLaunch.s.sol wires the vault to the new pool, vests the launch wallet's 5% over 12 months, pins the oracle question, and transfers Cocoon and FloorFeed to the timelock. Do not claim the code is audited or secure.\n\nPREVIOUS ATTEMPT. Launch #1400 (base commit 697677a) passed every admission check but findings: Cocoon.skim() could run inside takeAuction's lot transfer and book the taker's unrefunded excess. This commit answers that audit; docs/REVIEW.md records each finding. (1) High, skim re-entry: fixed. skim and depositTax hold the reentrancy lock and the excess is refunded before the lot token is called; the audit's proof is a regression test (test/cocoon/CocoonReentrancy.t.sol); the Cocoon above is a new deployment of the fixed source. (2) Low, _record truncation: fixed (OutOfBounds). (3) Low, fixed-start harvest auctions: the default start is now the 100 ETH upper bound. (4) Low, burn buying nothing while the price is parked above the range: accepted; any buy resumes it. (5) Low, FloorFeed's slow recovery after a low report: accepted; FloorFeed is deployed and unchanged. PupateHook and PupateToken are byte-identical to launch #1400.","parentJobId":null,"planHash":"21c430dbe8ed97a67709d66883eabe08f36e6cf8145bf0477ac0e1fa71634628","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"2726c329-944c-4bbb-9156-5bc4c8962091","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1412-pupate"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51367","feedbackHash":"036d64d155183e08183dabb9fbf2b9cfa85b564d48b28f53b2b3d09ced92809c","nodeKey":"audit_economics","submissionHash":"94243aef975d72daba38fb4816194a211add21d8179cf6cc06c72d9434cadedb","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52608","feedbackHash":"97e802c338134768c048bb2df5c32bfab8067970a40c9b73df7a2c3d7f06471d","nodeKey":"audit_flow","submissionHash":"487b3c26577b9f3bf26bf3fc952751ab749b8d8e8c5471c0e9edb331dc5aea4a","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51168","feedbackHash":"5b0afcc1f9ab9584e1de563f145a92d590451498b07c770a2149d0fcb00c6f78","nodeKey":"audit_imported_code","submissionHash":"590bdffc6603f232b70caee2cd958054839dd598a4609f881a83e4faff11e01d","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52019","feedbackHash":"d00693ee5c96a34be18379b8bce610d08cb098852cbcd45eb95b53351a68c3b5","nodeKey":"audit_judge","submissionHash":"fe840db224f0857441acc3fe070ef82889e54f7d1c732ca4b387437b3f88e190","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51169","feedbackHash":"09d50ab31ae3ad08963659f9d22e3c94976a08fa76177575d88e12f2a12b989e","nodeKey":"audit_math","submissionHash":"da379fe8e561649bde81cf6fec0895e50c8b8ca0630329810c28916516d97d65","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51140","feedbackHash":"ef5fb273ed8198c94bc6e4fc8bb8db76036e2e470e39434c1b5dd55226fd69d2","nodeKey":"audit_permissions","submissionHash":"630e1f2f9f68ab4f88a5999ee2cc002287761324c4edf2c376c9cef0c0267765","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51522","feedbackHash":"e17312a1d94564398c6ba443cc8f1f94e48d6f8e544ecde82b7e4df81e61a17e","nodeKey":"manifest","submissionHash":"d1e40d07aeb185431c9acbc1f2f8e3b62c7c76dfed2dbdfc3ccd0a4bbb78cda5","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"51053953ae986ddd9cddd4eabe9f5271aafc296a94d6551e8db022f185342833","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"3c9b660ee1938912","findings":[],"hash":"487b3c26577b9f3bf26bf3fc952751ab749b8d8e8c5471c0e9edb331dc5aea4a","nodeId":"5a3da63b-c511-4f13-bb7c-0f163cbd2215","outcome":"completed","summary":"The review is complete. The findings file holds an empty findings list and 43 coverage rows, one for each of the 38 listed entry points plus the hook's two view callbacks, the manifest, and the test runs. The working tree is clean apart from the findings file and the scratch directory.\n\n**Result: no reproducible defect in the assigned area.** I traced the Execution Trace, Periphery and First Principles checklists against the hook, token, Cocoon, FloorFeed, vesting and the four libraries, and verified each v4-core behaviour I relied on in the vendored library rather than from memory.\n\n**What I checked and found to hold**\n\n- **Hook swap flow, all four modes.** The tax returned in beforeSwap and recomputed in afterSwap are always equal: same params, same timestamp, and no external call sits between them, so the owner cannot lower the tax mid-swap. The PartialFill check is exact because a full fill makes the pool's specified delta equal the adjusted amount. The claim mint debit is cancelled by the hook delta the manager books afterwards.\n- **Flush path.** Claims are always backed by ETH the manager holds, since swappers settle the tax. The manager calls back only the unlocker, so unlockCallback is reachable only through flush. A sink revert leaves the claims in place.\n- **Liquidity gate.** The mark is transient and keyed to the launch pool. Removals and fee collection route to the ungated remove callback in v4-core. A hook with no code fails InvalidHookResponse, so the pool cannot be opened before deployment.\n- **Cocoon reentry surface.** Every ETH-moving entry point is under one lock. A hostile lot token can reach only startAuction, startImdAuction, burnPupate and the hook's flush. I probed the two interesting ones in scratch tests: flush is refused by the sink's lock and claims stay, and startAuction re-opens on a lying balance without moving ETH.\n- **Seaport interaction.** Seaport's own reentrancy guard stops a concurrent fill of Cocoon's listing during a purchase, so the refund slot cannot be inflated. Consideration items naming Cocoon and fulfiller tips are refused.\n- **Manifest.** Schema keys only, constructor arguments in declaration order, permissions match getHookPermissions and encode 0x18CC, fee 12500, spacing 60, native pair, the initial price kept. Notes are 3,652 characters.\n\n**Test results**\n\n| Run | Outcome |\n|---|---|\n| forge test, offline | 215 passed, 0 failed, 10 skipped |\n| 10 fork tests, public mainnet RPC | all passed |\n| 2 scratch reentry probes | both passed as expected |\n\nThe first Seaport fork attempt failed in setUp with a public-node \"block not found\" error. A retry on the same endpoint passed all seven, so it was node-side, not code.\n\n**Known residuals, not re-reported.** The account-less opening mark, the dust-lot auction deferral, parked-price burns and slow floor recovery are already recorded in the manifest notes and the review log. Nothing I traced contradicts them.","treeHash":null,"usage":{"cachedInputTokens":1646357,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":50303,"runtime":"claude","turns":32,"wallClockMs":795599}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"6208734cdf5317a1","findings":[{"citation":"resolved","description":"startAuction is permissionless, snapshots Cocoon's whole balance of the token as the lot, and opens at params.harvestStartWei (100 ETH by default, the upper bound after launch #1400's finding 3). A running auction cannot be replaced, cancelled or merged: startAuction reverts AuctionRunning until takeAuction clears it, and takeAuction only clears it at the decayed price. A griefer who sends 1 wei of a harvest token to Cocoon and calls startAuction(token) opens an auction whose lot is 1 wei and whose price starts at 100 ETH and halves every two hours (Decay.price). No rational taker pays that for dust, so the auction stands until the 48-hour cutoff makes the lot free. Every unit of that token that arrives meanwhile (the real harvest from the seats) waits for the next lot and cannot be auctioned for up to 48 hours per cycle. The IMD auction has the same shape (line 561): startImdAuction can be called on any non-zero imdBurnBalance, so a dust-sized IMD lot started by anyone holds the IMD-burn queue until its demand decays to the market rate (about 12 hours from the 20,000 IMD per ETH default). Impact is delay and gas, not loss: nothing leaves the pots, the deferred balance is included in the next lot, and the keeper (keeper/README.md says it never takes an auction) cannot shorten the wait. This is the mechanical complement of launch #1400's finding 3: raising the start price to the bound makes a dust lot untakeable rather than takeable too cheaply. Confirmed with a scratch test (test/scratch/DustLot.t.sol, not kept) on the CocoonFixture. The deployed Cocoon at 0xc4e514e60565a2c04bd657f18bb34b503d6c299d carries this code byte for byte; a change would need a redeployment before the launch or acceptance as a known residual, as findings 3 to 5 of the previous audit were.","line":527,"path":"src/Cocoon.sol","reproduction":"State: Cocoon deployed and wired, params at their defaults (harvestStartWei = 100 ether). Steps: (1) griefer mints or transfers 1 wei of harvest token H to Cocoon; (2) griefer calls cocoon.startAuction(H): auctions[H] = (lot 1, start 100 ether, now); (3) the seats' real harvest of 1000e18 H arrives at Cocoon; (4) anyone calls cocoon.startAuction(H): reverts AuctionRunning; (5) warp 47 hours: cocoon.auctionPrice(H) is still above 1e12 wei for a 1-wei lot, startAuction(H) still reverts AuctionRunning; (6) warp to 48 hours: takeAuction(H) with 0 value succeeds, and only then startAuction(H) opens the 1000e18 lot, again at 100 ETH for its own 48-hour curve. Expected: a real harvest can be put up for sale within a bounded time regardless of third-party dust; actual: each cycle is delayed by up to 48 hours for the cost of one wei and two transactions.","severity":"low","snippet":"        if (auctions[token].lot != 0) revert AuctionRunning();","title":"Anyone can hold a harvest token's auction queue for 48 hours with a 1-wei lot started at the 100 ETH default"},{"citation":"resolved","description":"Read from mainnet at block 26176313 through a public RPC. Cocoon 0xc4e514e60565a2c04bd657f18bb34b503d6c299d: owner(), developer() and operator() are all 0x7A2B112aF54419ed0B18e051027f3142C13441D9 (the deployer), wired() is false, and its immutables are the v4 PoolManager 0x000000000004444c5dc75cb358380d2e3de08a90, Seaport 1.6 0x0000000000000068f116a894984e2db1123eb395, the collection 0x0000ec93127baa929e58e97dd0095a2bfb38ec1d, FloorFeed 0x5a215aa9883cefe5fa74085ba66288388b83700c and IMD 0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7. FloorFeed: owner is the same deployer, questionHash is zero, attester 0x5598Aa9146215Bc13eb26f2c692Ad1461Fd32982, evidence chain 1. The timelock 0x8be826082c0fbaec485048aa26ef42ac147898ec has minDelay 172800, the deployer as proposer and canceller, address(0) as executor and no admin besides itself. Both deployed runtimes match this tree's build exactly outside their immutables (0 differing bytes). So the hook is bound, as the request says, to the fixed Cocoon and to a 48-hour timelock; but until script/PostLaunch.s.sol runs, the sink that receives every flushed wei is governed by one EOA with no delay (setParams within bounds, setOperator, wire, transferOwnership), burn() reverts NotWired, and no floor can be reported. docs/deployment.md records this sequence; it is noted here because the manifest review asks for constructor beneficiaries and privileged behaviour to be read from the chain rather than from the notes. Not a code defect; no change to the source is suggested.","line":60,"path":"script/PostLaunch.s.sol","reproduction":"cast call 0xc4e514e60565a2c04bd657f18bb34b503d6c299d 'owner()(address)' and 'wired()(bool)' on mainnet now return 0x7A2B112aF54419ed0B18e051027f3142C13441D9 and false; cast call 0x5A215aa9883cEfE5fa74085ba66288388b83700C 'questionHash()(bytes32)' returns zero. Expected after PostLaunch: owner() of both is the timelock, wired() is true, questionHash is 0x0bd457cf1b0d092b4f12904589b3f08b5c67793cc4ef5292d79b2733ba5bab24 as docs/deployment.md records.","severity":"info","snippet":"        if (cocoon.owner() == deployer) cocoon.transferOwnership(timelock);","title":"At launch time the deployed sink is not yet under the timelock: owner, developer and operator are the deployer EOA, the vault is unwired and the floor question unset"}],"hash":"590bdffc6603f232b70caee2cd958054839dd598a4609f881a83e4faff11e01d","nodeId":"0cf453c9-0820-4400-910d-a9e742bdc55e","outcome":"completed","summary":"Review complete. The findings file is written and no source file was changed.\n\n**Outcome.** No defect in PupateHook or PupateToken; one low finding and one informational note in the vault and its deployment state, both recorded in `.imd-findings.json`.\n\n**Manifest.** `launch.json` is valid on every schema point: only schema keys, hook `PupateHook` with constructor `$poolManager`, the Cocoon address and the timelock address in declaration order, six permissions that match `getHookPermissions` and sum to flags 0x18CC, token `PupateToken` with a zero-argument constructor, fee 12500, tick spacing 60, native ETH, and `initialPrice` exactly 10^4 · 2^96 at tick 184216. Notes are 3,672 characters, addresses lowercase.\n\n**Tests.** The full suite passes on forge 1.8.3: 215 passed, 0 failed, 10 skipped. With a public mainnet RPC the 10 fork tests also pass, including the real-factory launch rehearsal and the live Seaport purchase tests.\n\n**On-chain verification.** The deployed Cocoon and FloorFeed match this tree's build byte for byte outside their immutables, which name the mainnet PoolManager, Seaport 1.6, the IMD collection, the FloorFeed and the IMD token. The timelock has a 48-hour delay, the deployer as proposer, anyone as executor and no external admin. A fork scratch test confirmed the deployed vault's IMD-burn auction completes against the real IMD token. All five runtimes contain no DELEGATECALL, CALLCODE or SELFDESTRUCT.\n\n**Findings.**\n- **Low, Cocoon.sol:527.** Anyone can send 1 wei of a harvest token and open its auction at the 100 ETH default start. The dust lot cannot be replaced, so real harvest arriving during those 48 hours waits for the next lot. The IMD auction has the same shape with a shorter wait. Delay only, no loss; reproduced with a scratch test.\n- **Info, PostLaunch.s.sol:60.** Until the post-launch script runs, the sink's owner, developer and operator are the deployer EOA, the vault is unwired and the floor question is unset. Documented in the repository, recorded here because the hook is bound to that sink from its first swap.\n\n**Coverage.** Read in full: all of `src/` (Cocoon, FloorFeed, PupateHook, PupateToken, PupateVesting, the cocoon and oracle libraries, the interfaces), the PostLaunch and RedeployCocoon scripts, the test fixtures, and the review log. Not read: the vendored v4-core and Seaport libraries, the keeper and site code, and the external contracts the vault talks to (Seaport 1.6, the IMD token, the collection, the launch factory), which were exercised only through the fork tests.","treeHash":null,"usage":{"cachedInputTokens":1817855,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":54613,"runtime":"claude","turns":37,"wallClockMs":796004}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ddc5542bddcad225","findings":[{"citation":"resolved","description":"Access control x asymmetry. Every entry point that moves ETH or a lot holds the transient lock, except startAuction (src/Cocoon.sol:519), which anyone may call with no lock and which sizes its lot from the live token balance (src/Cocoon.sol:528-531). takeAuction (src/Cocoon.sol:542-554) deletes the auction and credits seatPot, then at line 552 pays the taker's excess back through a raw call BEFORE line 553 hands over the lot. A contract taker therefore gets control while auctions[token].lot == 0 and Cocoon still holds the lot. Calling startAuction(token) from that callback passes every check (wired, not PUPATE, lot == 0, balance != 0) and records a new auction whose lot is the whole current balance, including the tokens about to be transferred out at line 553. The paired takeImdAuction/startImdAuction (src/Cocoon.sol:575-584, 560-567) does not have this asymmetry: imdBurnBalance is decremented before _pay, so the same re-entry sizes the IMD lot correctly. Consequences for any harvest token H that the seats earn: (1) the phantom auction blocks startAuction(H) with AuctionRunning, so new H can never be put up at the 100 ETH start price the #1400 audit relied on; (2) the phantom's curve keeps falling from the moment it was opened, and once Cocoon's H balance is at least the phantom lot anyone can take it at that decayed price (zero after 48 hours). An attacker who took an honest auction with 1 wei of excess and re-opened on balance B = L + N (N = harvest that arrived during the auction) can, after 48 hours, send the L it received back and take L + N for 0 ETH, netting the second batch N for free and leaving seatPot unchanged; the trick repeats on that take. Fix options that keep the design: give startAuction (and startImdAuction, for symmetry) the nonReentrant modifier, or transfer the lot before refunding the excess now that skim and depositTax hold the lock. The proof passes with either.","line":552,"path":"src/Cocoon.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {PoolId} from \"v4-core/src/types/PoolId.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {SeaportInterface} from \"seaport-types/src/interfaces/SeaportInterface.sol\";\nimport {Cocoon} from \"src/Cocoon.sol\";\nimport {FloorFeed} from \"src/FloorFeed.sol\";\nimport {IERC20Minimal} from \"src/interfaces/IERC20Minimal.sol\";\nimport {IERC721Minimal} from \"src/interfaces/IERC721Minimal.sol\";\n\n/// @dev A plain harvest token: what a seat earns and Cocoon auctions.\ncontract HarvestToken {\n    mapping(address => uint256) public balanceOf;\n\n    function mint(address to, uint256 amount) external {\n        balanceOf[to] += amount;\n    }\n\n    function transfer(address to, uint256 amount) external returns (bool) {\n        require(balanceOf[msg.sender] >= amount, \"balance\");\n        balanceOf[msg.sender] -= amount;\n        balanceOf[to] += amount;\n        return true;\n    }\n}\n\n/// @dev The collection as the constructor needs it.\ncontract StubCollection {\n    function setApprovalForAll(address, bool) external {}\n}\n\n/// @dev What `Cocoon.wire` reads from the launch hook.\ncontract StubHook {\n    address public sink;\n    PoolId public launchPool;\n    uint40 public openedAt = 1;\n\n    function set(address sink_, PoolId id) external {\n        sink = sink_;\n        launchPool = id;\n    }\n}\n\n/// @dev Takes a harvest auction with 1 wei of excess, and when the excess comes back, before Cocoon\n/// has handed over the lot, opens a new auction for the lot Cocoon is about to lose.\ncontract Taker {\n    Cocoon immutable cocoon;\n    address immutable token;\n\n    constructor(Cocoon cocoon_, address token_) {\n        cocoon = cocoon_;\n        token = token_;\n    }\n\n    function take(uint256 value) external {\n        cocoon.takeAuction{value: value}(token);\n    }\n\n    receive() external payable {\n        // Guarded by try/catch so that any fix (a lock on startAuction, or the lot leaving before\n        // the refund) leaves the take itself intact and only the phantom fails.\n        try cocoon.startAuction(token) {} catch {}\n    }\n}\n\ncontract PhantomAuctionTest is Test {\n    address constant POOL_MANAGER = address(0x1111);\n    address constant SEAPORT = address(0x2222);\n    address constant IMD = address(0x3333);\n    address constant PUPATE = address(0x4444);\n\n    Cocoon cocoon;\n    HarvestToken harvest;\n    Taker taker;\n    address honest = makeAddr(\"honest\");\n\n    function setUp() public {\n        FloorFeed feed = new FloorFeed(address(this), address(0xA77E), 1);\n        StubCollection collection = new StubCollection();\n        StubHook hook = new StubHook();\n        cocoon = new Cocoon(\n            address(this),\n            address(this),\n            address(this),\n            IERC721Minimal(address(collection)),\n            SeaportInterface(SEAPORT),\n            IPoolManager(POOL_MANAGER),\n            feed,\n            IERC20Minimal(IMD)\n        );\n        PoolKey memory key = PoolKey({\n            currency0: Currency.wrap(address(0)),\n            currency1: Currency.wrap(PUPATE),\n            fee: 12_500,\n            tickSpacing: 60,\n            hooks: IHooks(address(hook))\n        });\n        hook.set(address(cocoon), PoolId.wrap(keccak256(abi.encode(key))));\n        cocoon.wire(key);\n\n        harvest = new HarvestToken();\n        taker = new Taker(cocoon, address(harvest));\n        vm.deal(address(taker), 1 ether);\n        vm.deal(honest, 1 ether);\n    }\n\n    /// @notice After a take, the auction on a token never names more of it than Cocoon holds.\n    function test_aTakerCannotLeaveAnAuctionForTokensCocoonNoLongerHolds() public {\n        harvest.mint(address(cocoon), 1000e18);\n        cocoon.startAuction(address(harvest));\n        vm.warp(block.timestamp + 48 hours); // the curve has reached zero\n\n        taker.take(1); // price 0, 1 wei of excess refunded before the lot moves\n\n        assertEq(harvest.balanceOf(address(taker)), 1000e18, \"the taker got the lot\");\n        (uint128 lot,,) = cocoon.auctions(address(harvest));\n        assertLe(lot, harvest.balanceOf(address(cocoon)), \"an auction names tokens Cocoon does not hold\");\n    }\n\n    /// @notice A second batch of harvest that arrives during an auction can only be sold through a\n    /// fresh auction that starts at 100 ETH. Here the taker ends up with it for nothing.\n    function test_theSecondBatchOfHarvestCannotBeTakenForFree() public {\n        harvest.mint(address(cocoon), 1000e18);\n        cocoon.startAuction(address(harvest)); // lot 1000, start 100 ETH\n        harvest.mint(address(cocoon), 500e18); // the next batch arrives during the auction\n        vm.warp(block.timestamp + 48 hours);\n\n        taker.take(1); // takes the 1000 lot at price 0 and, inside the refund, re-opens on 1500\n\n        // Honest flow: the remaining 500 go up at 100 ETH, and the curve has to fall for two days.\n        (bool opened,) = address(cocoon).call(abi.encodeCall(Cocoon.startAuction, (address(harvest))));\n        opened; // on this code: AuctionRunning, the phantom blocks it\n\n        // The taker puts the 1000 it just received back, waits two days, and takes 1500 for 0 ETH.\n        vm.warp(block.timestamp + 48 hours);\n        vm.prank(address(taker));\n        harvest.transfer(address(cocoon), 1000e18);\n        (bool took,) = address(taker).call(abi.encodeCall(Taker.take, (0)));\n        took;\n\n        assertLe(harvest.balanceOf(address(taker)), 1000e18, \"the taker obtained the second batch for free\");\n        assertEq(address(taker).balance, 1 ether, \"the taker paid nothing\");\n    }\n}","reproduction":"State: Cocoon wired; harvest token H; Cocoon holds 1000e18 H; startAuction(H) (lot 1000e18, start 100 ETH); 500e18 more H arrives; warp 48 hours so Decay.price is 0. Call: a contract taker calls takeAuction{value: 1}(H); in its receive() it calls startAuction(H). Actual: the take succeeds, the taker receives 1000e18 H, and auctions[H].lot == 1500e18 while Cocoon holds only 500e18; startAuction(H) now reverts AuctionRunning; 48 hours later the taker transfers 1000e18 H back and takes 1500e18 H for 0 ETH (seatPot stays 0). Expected: after a take, no auction names more H than Cocoon holds; the 500e18 that arrived during the auction can only be sold through a fresh auction starting at 100 ETH. test/scratch/PhantomAuction.t.sol: both tests fail on this code (lot 1000e18 > balance 0; taker holds 1500e18 > 1000e18) and pass with startAuction made nonReentrant.","severity":"high","snippet":"        if (msg.value > price) _pay(msg.sender, msg.value - price);\n        _safeTransfer(token, msg.sender, a.lot);","title":"takeAuction refunds the taker before the lot leaves, and the unguarded startAuction can be re-entered to book a phantom auction over tokens Cocoon no longer holds"},{"citation":"resolved","description":"Not a code defect; recorded as the permission model the launch relies on, read from mainnet at block 26176427 via a public RPC. Cocoon 0xc4e514e60565a2c04bd657f18bb34b503d6c299d: owner, developer and operator are all 0x7A2B112aF54419ed0B18e051027f3142C13441D9; wired() is false; POOL_MANAGER is the mainnet v4 PoolManager 0x000000000004444c5dc75cB358380D2e3dE08A90; FLOOR is 0x5A215aa9883cEfE5fa74085ba66288388b83700C, whose owner is the same EOA. Its runtime bytecode equals this tree's compiled Cocoon once the five immutables are zeroed, so the deployed sink is the fixed source, not the abandoned #1400 vault. Timelock 0x8be826082c0fbaec485048aa26ef42ac147898ec: getMinDelay 172800; PROPOSER_ROLE and CANCELLER_ROLE are held by that one EOA; EXECUTOR_ROLE is held by address(0), so anyone may execute a matured operation; DEFAULT_ADMIN_ROLE is held by the timelock itself. So after PostLaunch the hook's lowerTax/transferOwnership and Cocoon's setParams/setOperator/transferOwnership are that single key behind a 48-hour delay, and until PostLaunch runs they are that key with no delay. Both ownership transfers are one-step. The manifest itself is valid: schema keys only, constructor arguments in declaration order, permissions equal to getHookPermissions (0x18CC), a token with a zero-argument constructor, fee 12500, tick spacing 60, native ETH, initialPrice kept at 792281625142643375935439503360000, notes of 3652 characters.","line":8,"path":"launch.json","reproduction":"cast call 0x8be826082c0fbaec485048aa26ef42ac147898ec \"hasRole(bytes32,address)(bool)\" $(cast keccak PROPOSER_ROLE) 0x7A2B112aF54419ed0B18e051027f3142C13441D9 returns true; the same for CANCELLER_ROLE; hasRole(EXECUTOR_ROLE, 0x0) returns true; Cocoon.owner()/developer()/operator() and FloorFeed.owner() return the EOA; Cocoon.wired() returns false. Expected: exactly this, per the request's description of the pre-PostLaunch state; reported so the judge has the actor behind each privileged entry point.","severity":"info","snippet":"      \"0x8be826082c0fbaec485048aa26ef42ac147898ec\"","title":"Trust assumptions as deployed: one EOA holds every privileged role until PostLaunch, and it is the timelock's sole proposer and canceller"}],"hash":"630e1f2f9f68ab4f88a5999ee2cc002287761324c4edf2c376c9cef0c0267765","nodeId":"0fdd971a-82b0-4676-9625-3f191173683c","outcome":"completed","summary":"The review is complete. The findings file is written and validated, the proof fails on the current code and passes under a minimal fix, and the tracked tree is unchanged.\n\n**Outcome**\n\nOne substantiated defect, rated high, plus one informational trust-assumption record. Both are in `.imd-findings.json` with a coverage row for all 38 listed entry points and five extra invariant rows.\n\n**Finding 1, high: phantom harvest auction via re-entry.** In Cocoon, `takeAuction` deletes the auction and refunds the taker's excess ETH through a raw call before it hands over the lot. `startAuction` is the only lot-moving entry point without the reentrancy lock, and it sizes a lot from the live token balance. A contract taker that sends 1 wei of excess gets control while the auction slot is empty and Cocoon still holds the tokens, so it can open a new auction over tokens that leave in the next line. The IMD auction pair does not have this asymmetry because it decrements its balance before paying.\n- Effect: `startAuction` for that token is blocked with AuctionRunning, and harvest arriving during an auction can be taken for zero ETH after 48 hours by sending the first lot back. The seat pot gets nothing for it, and the trick repeats.\n- Proof: `test/scratch/PhantomAuction.t.sol`, two tests. Both fail on this code. I checked they pass when `startAuction` is given `nonReentrant` and then restored the source. Transferring the lot before the refund also fixes it.\n\n**Finding 2, info: deployed trust model.** Read from mainnet through a public RPC. The Cocoon at the manifest address has runtime bytecode identical to this build apart from immutables, is unwired, and its owner, developer and operator are the single launch EOA, as is the FloorFeed owner. The timelock has a 48-hour delay, that same EOA as sole proposer and canceller, and open execution. Not a defect, recorded so the judge knows who stands behind each privileged entry point.\n\n**Manifest and tests**\n- `launch.json` is valid: schema keys only, three constructor arguments in declaration order, permissions equal to the hook's own and encoding 0x18CC, zero-argument token, fee 12500, spacing 60, native ETH, initial price unchanged, notes under the limit.\n- Existing suite: 215 passed, 0 failed, 10 skipped without an RPC. With a public mainnet RPC the 10 fork tests also pass, including the real factory rehearsal.\n\n**Coverage.** Every listed entry point has a row. All hold except `Cocoon.startAuction` and `Cocoon.takeAuction`, which point to finding 1. The hook's callbacks, ownership paths, the token and the vesting contract traced clean from the access control, trust gap and asymmetry guides. I did not re-derive the swap tax arithmetic or the burn sandwich economics, which belong to the other specialists.","treeHash":null,"usage":{"cachedInputTokens":2666179,"inputTokens":770,"model":"claude-fable-5-1","outputTokens":50512,"runtime":"claude","turns":34,"wallClockMs":799561}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ddfb1efa72fe9a94","findings":[{"citation":"resolved","description":"_checkOrder never inspects p.offerer and never checks whether Cocoon already owns the offered seat; the only occupancy check is `!seats[tokenId].held`. Live Seaport 1.6 skips signature verification and on-chain validation when the order's offerer equals msg.sender (Verifiers._verifySignature returns early). Cocoon is msg.sender of fulfillAdvancedOrder, so an unprivileged caller can hand buySeat an unsigned, never-validated order with offerer = Cocoon, offer = a seat Cocoon holds but has not recorded, and consideration = floor*(1+toleranceBps) ETH paid to the caller. Seaport transfers the seat from Cocoon to Cocoon (recipient = Cocoon), pays the caller out of Cocoon's msg.value, buySeat sees fulfilled && _holds(tokenId), records the seat at cost = ceiling, and pays the caller the 0.5% reward on top. The vault pays the maximum purchase price plus reward for a seat it already owned and could have taken in for free with adopt(). Reachable state: any seat held by Cocoon with seats[id].held == false. That is exactly the documented direct-transfer flow (`adopt`: 'a seat that was sent to Cocoon directly'; test_adoptTakesADirectTransferAndRefusesAnUnsolicitedSafeTransfer): a searcher back-runs the ERC-721 transfer in the same block, before the keeper's adopt(). A second route needs no gift: fill one of Cocoon's own listings with recipient = Cocoon (seat stays, Cocoon receives L), call settleSeat (held = false), then run this order; it pays whenever floor*1.055 > L, i.e. once the floor has risen ~10% above the seat's cost basis. Who loses: the vault's seat pot (and the donor's intent); who gains: any unprivileged caller, 1.055 x floor per unrecorded seat. Violated guarantee: 'Fulfil a Seaport listing of one seat ... paid to others' (a sale by a seller), and 'ETH leaves this contract only to Seaport (a seat purchase) ...' where the purchase buys nothing. Minimal fix preserving the design: in _checkOrder revert BadOrder when p.offerer == address(this), and revert AlreadyHeld when _holds(tokenId) is already true (so an unrecorded seat can only enter the books through adopt).","line":385,"path":"src/Cocoon.sol","reproduction":"Mainnet fork at the current head (~block 26176443; run with MAINNET_RPC_URL=https://ethereum-rpc.publicnode.com forge test --match-path test/scratch/SelfOffer.fork.t.sol -vv). State: fresh Cocoon from this source bound to live Seaport 0x0000000000000068F116a894984e2DB1123eB395 and the live collection 0x0000eC93127BAA929E58E97dd0095A2BFb38ec1D; FloorFeed reports 2.8 ETH; depositTax(10 ETH) so seatPot = 4.25 ETH. (1) The holder of seat 1376 transfers it to Cocoon with transferFrom (the adopt flow). (2) attacker (no seat, no approval) builds OrderParameters{offerer: cocoon, zone: 0, offer: [ERC721 collection #1376, 1, 1], consideration: [NATIVE 2.94 ETH -> attacker], FULL_OPEN, startTime: now-1, endTime: now+1h, zoneHash 0, salt 0xA77AC4, conduitKey 0, totalOriginalConsiderationItems 1} and calls cocoon.buySeat(AdvancedOrder(p, 1, 1, '', ''), []) from the attacker address. Expected: revert (BadOrder or PurchaseFailed: there is no seller and Cocoon already owns the seat). Actual: the call succeeds; attacker.balance == 2.9547 ETH (2.94 price + 0.0147 reward); seatPot drops from 4.25 ETH to 1.2953 ETH; collection.ownerOf(1376) is still Cocoon; seats[1376] == {cost 2.94 ETH, held true}. The attached fork test asserts exactly these values and passes on this code (it demonstrates the loss; a fix makes buySeat revert).\n\n--- test/scratch/SelfOffer.fork.t.sol ---\n// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {SeaportInterface} from \"seaport-types/src/interfaces/SeaportInterface.sol\";\nimport {ItemType, OrderType} from \"seaport-types/src/lib/ConsiderationEnums.sol\";\nimport {\n    AdvancedOrder,\n    ConsiderationItem,\n    CriteriaResolver,\n    OfferItem,\n    OrderParameters\n} from \"seaport-types/src/lib/ConsiderationStructs.sol\";\nimport {Cocoon} from \"src/Cocoon.sol\";\nimport {FloorFeed} from \"src/FloorFeed.sol\";\nimport {IERC20Minimal} from \"src/interfaces/IERC20Minimal.sol\";\nimport {IERC721Minimal} from \"src/interfaces/IERC721Minimal.sol\";\nimport {OracleAttestation} from \"src/oracle/OracleAttestation.sol\";\n\n/// @dev Fork reproduction (MAINNET_RPC_URL): a seat sent to Cocoon directly, awaiting `adopt`, is\n/// \"sold\" to Cocoon by an unprivileged attacker through an order whose offerer is Cocoon itself.\n/// Seaport skips signature verification when the offerer is the caller, and `_checkOrder` never\n/// looks at `offerer` or at whether Cocoon already holds the seat.\ncontract SelfOfferForkTest is Test {\n    address constant SEAPORT = 0x0000000000000068F116a894984e2DB1123eB395;\n    address constant COLLECTION = 0x0000eC93127BAA929E58E97dd0095A2BFb38ec1D;\n    address constant POOL_MANAGER = 0x000000000004444c5dc75cB358380D2e3dE08A90;\n    address constant IMD = 0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7;\n    uint256 constant SEAT = 1376;\n    uint256 constant ATTESTER_KEY = 0xA11CE;\n    bytes32 constant QUESTION = keccak256(\"floor question\");\n\n    bool ready;\n    FloorFeed feed;\n    Cocoon cocoon;\n    IERC721Minimal collection = IERC721Minimal(COLLECTION);\n    address owner = makeAddr(\"timelock\");\n    address attacker = makeAddr(\"attacker\");\n    CriteriaResolver[] noResolvers;\n\n    function setUp() public {\n        string memory url = vm.envOr(\"MAINNET_RPC_URL\", string(\"\"));\n        if (bytes(url).length == 0) return;\n        vm.createSelectFork(url);\n        ready = true;\n        feed = new FloorFeed(owner, vm.addr(ATTESTER_KEY), 1);\n        vm.prank(owner);\n        feed.setQuestion(QUESTION);\n        cocoon = new Cocoon(\n            owner,\n            makeAddr(\"developer\"),\n            makeAddr(\"operator\"),\n            collection,\n            SeaportInterface(SEAPORT),\n            IPoolManager(POOL_MANAGER),\n            feed,\n            IERC20Minimal(IMD)\n        );\n        vm.deal(address(this), 100 ether);\n    }\n\n    function test_attackerSellsCocoonItsOwnUnadoptedSeat() public {\n        if (!ready) {\n            vm.ski","severity":"medium","snippet":"    function _checkOrder(AdvancedOrder calldata order) private view returns (uint256 tokenId, uint256 price) {\n        OrderParameters calldata p = order.parameters;\n        if (p.offer.length != 1 || p.consideration.length == 0) revert BadOrder();","title":"buySeat accepts an order whose offerer is Cocoon itself: anyone can sell Cocoon a seat it already holds (unadopted) and take floor*1.05 plus the reward from the seat pot"},{"citation":"resolved","description":"Trust assumption, documented separately as the methodology asks, not a code defect. After PostLaunch, FloorFeed.owner is the TimelockController 0x8be8...98ec (on-chain: PROPOSER_ROLE and CANCELLER_ROLE held only by 0x7a2b...41d9, EXECUTOR_ROLE open to address(0), min delay 172800 s). A single proposer key can schedule setAttester(key it controls), wait 48 hours, then sign any floor. The rise cap (25% per 6 h, src/FloorFeed.sol:95-99) only slows the climb: 4 reports over 24 h take the floor to ~2.44x. Cocoon then buys seats at up to 1.05x that floor from any seller (buySeat is permissionless and the seller may be the proposer), draining the seat pot at 2.56x market per seat. README's 'The vault trusts no key for the reference price' and 'Nobody holds the keys' overstate this; the 48h delay is the only protection and should be stated as such.","line":112,"path":"src/FloorFeed.sol","reproduction":"Owner path, listed for disclosure only: timelock.schedule(FloorFeed.setAttester(X), delay 172800) by 0x7a2b...41d9; after 48 h anyone executes; X signs attestations with floor = prev*1.25 every 6 h (each passes the MoveTooLarge check exactly at the cap); after 24 h the floor is 2.44x; a seat listed at 1.05*2.44x market = 2.56x market is bought by buySeat. No unprivileged amplifier beyond the public buySeat; severity info.","severity":"info","snippet":"    function setAttester(address attester_) external onlyOwner {\n        if (attester_ == address(0)) revert ZeroAddress();","title":"FloorFeed owner (48h timelock, single EOA proposer) can replace the attester and thereby set the reference price the vault buys seats at; README states the vault 'trusts no key'"},{"citation":"resolved","description":"Consistent with the brief ('taxes the ETH side of every swap in its launch pool') and the required plain-ERC-20 token, so not a defect of the code as specified, but a gap between the README's economic claim ('A tax on every trade', '6% of the ETH side of every buy and sell') and what the contracts guarantee. Anyone who holds PUPATE (the 10% swarm allocation at launch, or tokens bought once through the launch pool) can initialise a hookless ETH/PUPATE pool on the same PoolManager (or a v2/v3 pool) and provide liquidity; swaps there pay no tax. Since the launch pool can never take new liquidity, over time the untaxed pool can become the deeper venue and tax revenue falls to the arbitrage flow between the two pools. The 99% opening tax can likewise be bypassed by anyone who already holds PUPATE at the open.","line":254,"path":"src/PupateHook.sol","reproduction":"Offline, any PoolManager: after the launch, holder H with 1,000,000 PUPATE and 0.01 ETH calls manager.initialize(PoolKey(ETH, PUPATE, 3000, 60, IHooks(address(0))), SQRT_PRICE_OPEN) and modifyLiquidity on that key (no hook, no gate); a buyer swaps 1 ETH there: hook.totalTax() unchanged, manager.balanceOf(hook, 0) unchanged. Expected by the README: 6% (or 99% in the window) of the ETH side collected. Actual: 0.","severity":"info","snippet":"    function _taxed(address sender, PoolKey calldata key) private view returns (bool) {\n        return sender != sink && PoolId.unwrap(key.toId()) == PoolId.unwrap(launchPool);\n    }","title":"The tax is enforced only in the launch pool; PUPATE is a plain ERC-20, so a hookless ETH/PUPATE pool trades untaxed (README claims a tax 'on every trade')"}],"hash":"94243aef975d72daba38fb4816194a211add21d8179cf6cc06c72d9434cadedb","nodeId":"71097867-d06c-49da-818e-610343ac1b37","outcome":"completed","summary":"Review complete. The findings file is written with 3 findings and 45 coverage rows.\n\n**Result of the three assigned tasks**\n\n1. **Manifest**: launch.json is valid. Constructor args are `$poolManager`, the Cocoon address and the timelock address in declaration order and lowercase. Permissions match `getHookPermissions` and encode 0x18CC. Token has a zero-argument constructor. Pool is native ETH, fee 12500, spacing 60, and the initial price is 2^96 × 10,000 (tick 184216, the 10 ETH valuation). Notes are 3,652 characters.\n2. **Suite**: forge 1.8.3 offline gives 215 passed, 0 failed, 10 skipped. With a public mainnet RPC all 10 fork tests pass, including the real-factory rehearsal. On-chain, the Cocoon at 0xc4e5…299d matches this build's runtime except for 29 immutable words, is unwired with zero pots, and the timelock has a 48-hour delay with the deployer EOA as sole proposer.\n3. **Review findings** (in `.imd-findings.json`):\n   - **Medium**: `Cocoon.buySeat` accepts a Seaport order whose offerer is Cocoon itself. Live Seaport skips signature and validation when the offerer is the caller, and `_checkOrder` checks neither the offerer nor whether Cocoon already owns the seat. Any caller can therefore \"sell\" Cocoon a seat it already holds but has not yet adopted and take floor × 1.05 plus the 0.5% reward from the seat pot. Reproduced on a mainnet fork against live Seaport and the live collection: the attacker received 2.9547 ETH for seat 1376, which Cocoon already held. The reachable state is the documented direct-transfer-then-adopt flow, which a searcher can back-run. Fix sketch: reject `offerer == address(this)` and reject seats Cocoon already holds.\n   - **Info**: FloorFeed's owner can replace the attester through the 48-hour timelock, which gives a single proposer key control over the vault's purchase price. This is a trust assumption the README's \"trusts no key\" wording overstates.\n   - **Info**: the tax applies only to the launch pool, so a hookless ETH/PUPATE pool trades untaxed. Consistent with the brief, but the README's \"tax on every trade\" claim is broader than the code guarantees.\n\n**What held**: all four hook swap modes re-derived against v4-core's delta composition, flush and claim accounting, the liquidity gate, Cocoon's balance identity under hostile tokens, auctions, vesting and the token. A burn sandwich was measured as loss-making (minus 0.088 ETH on a 1 ETH front, minus 0.39 ETH on 5 ETH). Known residuals (dust-lot delay, parked price, uncapped floor falls) were confirmed as disclosed and not re-reported.\n\nScratch tests live under `test/scratch/` (the fork reproduction and the sandwich check) and are not part of the submission.","treeHash":null,"usage":{"cachedInputTokens":2429208,"inputTokens":514,"model":"claude-fable-5-1","outputTokens":75561,"runtime":"claude","turns":40,"wallClockMs":1251474}},{"artifacts":[],"attempt":1,"bundleHash":"09c7c23e481aba2d081661f19057f1cf2d99353a34dc32c433b8ba21177f6e23","device":"fd7c32744e171d7d","findings":[],"hash":"d1e40d07aeb185431c9acbc1f2f8e3b62c7c76dfed2dbdfc3ccd0a4bbb78cda5","nodeId":"88c5dc13-dcc0-4255-9f75-953047eb7b7b","outcome":"completed","summary":"Updated [launch.json](/home/imd1161/.identitymd/work/2726c329-944c-4bbb-9156-5bc4c8962091/88c5dc13-dcc0-4255-9f75-953047eb7b7b/launch.json), preserving all deployment inputs and verifying constructors, permissions, and manifest fields.\n\n- `forge build`: passed with warnings.\n- `forge test`: **229 passed, 0 failed, 0 skipped**, including mainnet forks and scratch reproductions.\n- Both imported findings reproduced and documented. The dust-auction delay remains unresolved under the source-change prohibition.\n\nNotes: **3,652/4,000 characters**. Only `launch.json` has a submission change.","treeHash":"031476cb9114befbf7ef666b3560aba20e64f1d6","usage":{"cachedInputTokens":1419136,"inputTokens":88695,"model":"gpt-6-astra","outputTokens":11867,"runtime":"codex","turns":6,"wallClockMs":414146}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"87804e27e9c9f85a","findings":[{"citation":"resolved","description":"Precision x boundary seam. Params.burnImpactBps is documented as 'one burn may move the pool price by at most this', but the cap is enforced in sqrt-price space through an integer square root that rounds down. _sqrt((10000-500)*10000) = _sqrt(95,000,000) = 9746 (9746^2 = 94,984,516), so the limit is 0.9746 x sqrtPrice and the price may fall to 0.94984516 of spot: a 5.0155% move against a 5% parameter. At the upper bound burnImpactBps = 1000: _sqrt(90,000,000) = 9486, 9486^2 = 89,984,196, a 10.0158% move against 10%. The overshoot is at most about 1.6 bps of price and does not compound (each burn is bounded from the new spot), so no funds are lost; the parameter's stated bound is simply not exact. The author's review log already notes '5.02%'. Minimal fix if wanted: round the square root up (9747 gives 4.996%, 9487 gives 9.997%), e.g. y = _sqrt(x); if (y*y < x) y += 1.","line":475,"path":"src/Cocoon.sol","reproduction":"Fixture: CocoonFixture (Cocoon wired to the launch pool, 85% of supply seeded one-sided). Steps: _buyExactIn(5 ether) so the pool holds ETH; owner calls setParams with burnImpactBps = 1000; cocoon.depositTax{value: 40 ether}() so burnPot (17 ETH) far exceeds the 10% cap; roll 5 blocks; cocoon.burn(). Expected: price ratio after/before >= 0.90000000. Actual (test/scratch/MathSeams.t.sol, test_burnReallyMovesPriceMoreThanTheParameterAllows): sqrtPrice after^2 / before^2 = 89,984,195 / 1e8, a 10.0158% move. Pure arithmetic check in test_burnImpactLimitOvershootsTheParameter: 9746^2 = 94,984,516 < 95,000,000 and 9486^2 = 89,984,196 < 90,000,000.","severity":"info","snippet":"        uint256 limit = uint256(sqrtPrice) * _sqrt((BPS - p.burnImpactBps) * BPS) / BPS;","title":"Cocoon.burn: integer sqrt rounds the price limit down, so one burn may move the price slightly past burnImpactBps"},{"citation":"resolved","description":"Boundary x precision refinement of the accepted finding 5 of the launch #1400 audit (slow recovery after a low report). allowedRise = floor(prev * 2500 * gap / (21600 * 10000)). For prev < 86,400 wei a six-hour gap allows less than one wei of rise, so any higher report is refused with MoveTooLarge until gap >= 86,400 * 6h / prev seconds; for prev = 3 wei that is 24 hours per +1 wei, and climbing from a unit-error report of 1 (meaning 1 ETH written in ETH instead of wei) back to 1e18 by reports alone takes about 60 days of near-doubling. Only the attester, a trusted role, can place such a report, and the owner can clear it with setQuestion through the 48-hour timelock, so this is a documented operational limit rather than a loss path. Falls remain unlimited, and mode() falls back to NEUTRAL when the report lapses, so the vault stops buying rather than buying wrong.","line":97,"path":"src/FloorFeed.sol","reproduction":"Fixture: CocoonFixture with the test attester. Steps: _setFloor(3) (a signed report of 3 wei, issued now); vm.warp(+6 hours); _setFloor(4). Expected under the documented '25% per 6 hours' rule: a rise of 1 wei from 3 wei is 33%, so refusing it is correct at 6 hours, but at prev = 3 the allowance is floor(3 * 2500 * 21600 / 216,000,000) = 0, so even a 1 wei rise after 6 hours reverts MoveTooLarge, and only a gap >= 86,400 s admits +1 wei. Actual: test/scratch/MathSeams.t.sol test_tinyFloorCannotRiseWithinTheWindow observes the MoveTooLarge revert.","severity":"info","snippet":"            uint256 allowedRise = FullMath.mulDiv(prev, MAX_RISE_BPS * gap, RISE_WINDOW * BPS);","title":"FloorFeed.report: the rise allowance truncates to zero for very small stored floors, so a wrongly tiny report cannot be corrected within the window"}],"hash":"da379fe8e561649bde81cf6fec0895e50c8b8ca0630329810c28916516d97d65","nodeId":"d5794eed-679e-4836-8766-6747c13b3dd1","outcome":"completed","summary":"The review is complete. `.imd-findings.json` holds two info-level findings and a 42-row coverage record covering every listed entry point; nothing was left unreached. No source file was changed.\n\n**Outcome: no material math defect found in the assigned area.** The hook's tax arithmetic, Cocoon's pot accounting, Decay, the vesting curve and FloorFeed's rise cap all behave as specified at the boundaries I could reach.\n\n**What I verified**\n\n- Hook tax in all four swap modes, fuzzed over the whole 0 to 3 hour launch schedule against the real PoolManager. The trader moves exactly the named amount, the tax equals floor(gross × rate / 10000) within one wei, the PartialFill equality is exact, and dust swaps whose tax rounds to zero skip the mint. The divisor `BPS - rate` can never reach zero because the buy rate tops out at 9900.\n- Cocoon's `depositTax` split conserves every wei (fuzzed 0 to 1000 ETH), `burn` holds back enough for the reward in every partial fill, and `takeAuction`, `takeImdAuction` and `skim` keep the balance identity.\n- Decay is continuous at every half-life boundary. Vesting never releases more than vested and releases everything at the end.\n- Manifest: schema keys only, three constructor arguments in order, permissions match `getHookPermissions` (0x18CC), fee 12500, spacing 60, native ETH, notes within the limit, and `initialPrice` is exactly 10000·2^96 at tick 184216. The pinned Cocoon on mainnet has code and the canonical PoolManager; the timelock's delay is 48 hours.\n- Existing suite: 215 passed, 0 failed, 10 skipped offline. The 10 fork tests all pass against a public mainnet RPC, including the real factory rehearsal.\n\n**Findings reported (both info)**\n\n1. `Cocoon.burn` rounds its integer square root down, so the actual price cap is 5.0155% for a 500 bps parameter and 10.0158% for 1000 bps. Reproduced with a real burn moving the price to 0.89984 of spot. No funds at risk; the author's log already notes 5.02%.\n2. `FloorFeed.report`'s rise allowance truncates to zero for dust floors, so a unit-error report cannot be corrected within the window. This sharpens the already accepted finding 5 and requires the trusted attester to err.\n\n**Not reached within my area:** nothing. Outside it, I did not re-examine access control, the Seaport order flow or the liquidity gate beyond what their tests show. The scratch file at `test/scratch/MathSeams.t.sol` carries the reproductions and is not part of the submission.","treeHash":null,"usage":{"cachedInputTokens":2118578,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":53058,"runtime":"claude","turns":32,"wallClockMs":768253}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"be3be4cc237417f9","findings":[{"citation":"resolved","description":"Every Cocoon entry point that moves ETH or a lot holds the transient lock except startAuction (src/Cocoon.sol:519), which anyone may call and which sizes its lot from the live token balance (lines 528-531). takeAuction (lines 542-554) deletes the auction and credits seatPot, then at line 552 refunds the taker's excess with a raw call BEFORE line 553 transfers the lot. A contract taker gets control while auctions[token].lot == 0 and Cocoon still holds the lot; startAuction(token) from its receive() passes every check (wired, not PUPATE, lot == 0, balance != 0) and records an auction whose lot is the whole current balance, including the tokens that leave at line 553. The paired IMD auction does not have this gap: takeImdAuction decrements imdBurnBalance before it pays. Consequences for any harvest token H the seats earn: the phantom blocks startAuction(H) with AuctionRunning, so new harvest can never be put up at the 100 ETH start the launch #1400 audit relies on; its curve keeps falling from the moment it was opened and reaches zero after 48 hours; and as soon as Cocoon's H balance is at least the phantom lot (new harvest arriving, or the attacker returning the lot it took) anyone takes everything for 0 ETH. The attacker who took an honest auction with 1 wei of excess therefore gets the next batch for free and leaves seatPot unchanged, and repeats the trick on that take. Cost: 1 wei plus gas, permissionless, repeatable. Fix that keeps the design: give startAuction (and startImdAuction, for symmetry) the nonReentrant modifier; alternatively transfer the lot before refunding the excess now that skim and depositTax hold the lock. The attached proof passes with either. Merged from audit_permissions (high); no other specialist reported it. Confirmed by running the specialist's proof: both tests fail on this code for exactly the stated reason.","line":519,"path":"src/Cocoon.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {PoolId} from \"v4-core/src/types/PoolId.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {SeaportInterface} from \"seaport-types/src/interfaces/SeaportInterface.sol\";\nimport {Cocoon} from \"src/Cocoon.sol\";\nimport {FloorFeed} from \"src/FloorFeed.sol\";\nimport {IERC20Minimal} from \"src/interfaces/IERC20Minimal.sol\";\nimport {IERC721Minimal} from \"src/interfaces/IERC721Minimal.sol\";\n\n/// @dev A plain harvest token: what a seat earns and Cocoon auctions.\ncontract HarvestToken {\n    mapping(address => uint256) public balanceOf;\n\n    function mint(address to, uint256 amount) external {\n        balanceOf[to] += amount;\n    }\n\n    function transfer(address to, uint256 amount) external returns (bool) {\n        require(balanceOf[msg.sender] >= amount, \"balance\");\n        balanceOf[msg.sender] -= amount;\n        balanceOf[to] += amount;\n        return true;\n    }\n}\n\n/// @dev The collection as the constructor needs it.\ncontract StubCollection {\n    function setApprovalForAll(address, bool) external {}\n}\n\n/// @dev What `Cocoon.wire` reads from the launch hook.\ncontract StubHook {\n    address public sink;\n    PoolId public launchPool;\n    uint40 public openedAt = 1;\n\n    function set(address sink_, PoolId id) external {\n        sink = sink_;\n        launchPool = id;\n    }\n}\n\n/// @dev Takes a harvest auction with 1 wei of excess, and when the excess comes back, before Cocoon\n/// has handed over the lot, opens a new auction for the lot Cocoon is about to lose.\ncontract Taker {\n    Cocoon immutable cocoon;\n    address immutable token;\n\n    constructor(Cocoon cocoon_, address token_) {\n        cocoon = cocoon_;\n        token = token_;\n    }\n\n    function take(uint256 value) external {\n        cocoon.takeAuction{value: value}(token);\n    }\n\n    receive() external payable {\n        // Guarded by try/catch so that any fix (a lock on startAuction, or the lot leaving before\n        // the refund) leaves the take itself intact and only the phantom fails.\n        try cocoon.startAuction(token) {} catch {}\n    }\n}\n\ncontract PhantomAuctionTest is Test {\n    address constant POOL_MANAGER = address(0x1111);\n    address constant SEAPORT = address(0x2222);\n    address constant IMD = address(0x3333);\n    address constant PUPATE = address(0x4444);\n\n    Cocoon cocoon;\n    HarvestToken harvest;\n    Taker taker;\n    address honest = makeAddr(\"honest\");\n\n    function setUp() public {\n        FloorFeed feed = new FloorFeed(address(this), address(0xA77E), 1);\n        StubCollection collection = new StubCollection();\n        StubHook hook = new StubHook();\n        cocoon = new Cocoon(\n            address(this),\n            address(this),\n            address(this),\n            IERC721Minimal(address(collection)),\n            SeaportInterface(SEAPORT),\n            IPoolManager(POOL_MANAGER),\n            feed,\n            IERC20Minimal(IMD)\n        );\n        PoolKey memory key = PoolKey({\n            currency0: Currency.wrap(address(0)),\n            currency1: Currency.wrap(PUPATE),\n            fee: 12_500,\n            tickSpacing: 60,\n            hooks: IHooks(address(hook))\n        });\n        hook.set(address(cocoon), PoolId.wrap(keccak256(abi.encode(key))));\n        cocoon.wire(key);\n\n        harvest = new HarvestToken();\n        taker = new Taker(cocoon, address(harvest));\n        vm.deal(address(taker), 1 ether);\n        vm.deal(honest, 1 ether);\n    }\n\n    /// @notice After a take, the auction on a token never names more of it than Cocoon holds.\n    function test_aTakerCannotLeaveAnAuctionForTokensCocoonNoLongerHolds() public {\n        harvest.mint(address(cocoon), 1000e18);\n        cocoon.startAuction(address(harvest));\n        vm.warp(block.timestamp + 48 hours); // the curve has reached zero\n\n        taker.take(1); // price 0, 1 wei of excess refunded before the lot moves\n\n        assertEq(harvest.balanceOf(address(taker)), 1000e18, \"the taker got the lot\");\n        (uint128 lot,,) = cocoon.auctions(address(harvest));\n        assertLe(lot, harvest.balanceOf(address(cocoon)), \"an auction names tokens Cocoon does not hold\");\n    }\n\n    /// @notice A second batch of harvest that arrives during an auction can only be sold through a\n    /// fresh auction that starts at 100 ETH. Here the taker ends up with it for nothing.\n    function test_theSecondBatchOfHarvestCannotBeTakenForFree() public {\n        harvest.mint(address(cocoon), 1000e18);\n        cocoon.startAuction(address(harvest)); // lot 1000, start 100 ETH\n        harvest.mint(address(cocoon), 500e18); // the next batch arrives during the auction\n        vm.warp(block.timestamp + 48 hours);\n\n        taker.take(1); // takes the 1000 lot at price 0 and, inside the refund, re-opens on 1500\n\n        // Honest flow: the remaining 500 go up at 100 ETH, and the curve has to fall for two days.\n        (bool opened,) = address(cocoon).call(abi.encodeCall(Cocoon.startAuction, (address(harvest))));\n        opened; // on this code: AuctionRunning, the phantom blocks it\n\n        // The taker puts the 1000 it just received back, waits two days, and takes 1500 for 0 ETH.\n        vm.warp(block.timestamp + 48 hours);\n        vm.prank(address(taker));\n        harvest.transfer(address(cocoon), 1000e18);\n        (bool took,) = address(taker).call(abi.encodeCall(Taker.take, (0)));\n        took;\n\n        assertLe(harvest.balanceOf(address(taker)), 1000e18, \"the taker obtained the second batch for free\");\n        assertEq(address(taker).balance, 1 ether, \"the taker paid nothing\");\n    }\n}","reproduction":"State: Cocoon wired (stub hook returning sink/launchPool/openedAt); a plain harvest token H; Cocoon holds 1000e18 H; startAuction(H) records lot 1000e18 at 100 ETH; 500e18 more H arrives during the auction; warp 48 hours so Decay.price is 0. Call: a contract taker calls takeAuction{value: 1}(H) and in its receive() calls startAuction(H). Actual: the take succeeds, the taker receives 1000e18 H, and auctions[H].lot == 1500e18 while Cocoon holds 500e18; startAuction(H) now reverts AuctionRunning; 48 hours later the taker transfers 1000e18 H back and takes 1500e18 H for 0 ETH, its balance still 1 ether and seatPot still 0. Expected: after a take no auction names more H than Cocoon holds, and the 500e18 that arrived can only be sold through a fresh auction starting at 100 ETH. forge test --match-path test/scratch/PhantomAuction.t.sol: FAIL 'an auction names tokens Cocoon does not hold: 1000000000000000000000 > 0' and FAIL 'the taker obtained the second batch for free: 1500000000000000000000 > 1000000000000000000000'. The two tests pass once startAuction is nonReentrant.","severity":"high","snippet":"    function startAuction(address token) external {","title":"takeAuction refunds the taker before the lot leaves and startAuction holds no lock, so a taker re-enters and books a phantom auction over harvest Cocoon no longer holds"},{"citation":"resolved","description":"_checkOrder never inspects p.offerer and never asks whether Cocoon already owns the offered seat; its only occupancy check is seats[tokenId].held. Live Seaport 1.6 skips signature verification and on-chain validation when the order's offerer equals msg.sender, and Cocoon is msg.sender of fulfillAdvancedOrder (line 336). An unprivileged caller can therefore hand buySeat an unsigned, never-validated order with offerer = Cocoon, offer = a seat Cocoon holds but has not recorded, and consideration = floor x (1 + toleranceBps) in ETH paid to the caller. Seaport transfers the seat from Cocoon to Cocoon (recipient is Cocoon; Cocoon approved Seaport for all in its constructor, and transferFrom to self succeeds on the live collection), pays the caller out of Cocoon's msg.value, buySeat sees fulfilled && _holds(tokenId), records the seat at cost = ceiling, and pays the caller the 0.5% reward on top. The vault pays 1.055 x floor for a seat it already owned and could have taken in for free with adopt(). Reachable state: any seat held by Cocoon with seats[id].held == false, which is exactly the documented direct-transfer flow (adopt: 'a seat that was sent to Cocoon directly', pinned by test_adoptTakesADirectTransferAndRefusesAnUnsolicitedSafeTransfer): a searcher back-runs the ERC-721 transfer before the keeper's adopt(). The same state exists between settleSeat and adopt after a listing was filled with recipient = Cocoon or the seat was sent back (Plan 3 finding 6 flow). Who loses: the seat pot (and the donor's intent); who gains: any caller, 1.055 x floor per unrecorded seat. The contract's own comment ('paid to others', 'ETH leaves this contract only to Seaport (a seat purchase)') is violated: the purchase buys nothing. Fix preserving the design: in _checkOrder revert BadOrder when p.offerer == address(this), and revert AlreadyHeld when _holds(tokenId) is already true, so an unrecorded held seat can only enter the books through adopt. Severity medium: a real loss, bounded by one ceiling price per unrecorded seat and requiring that state. Merged from audit_economics (medium). Reproduced on a mainnet fork against live Seaport (a fork test cannot serve as the proof file, so none is attached).","line":385,"path":"src/Cocoon.sol","reproduction":"Mainnet fork at block 26176592 (MAINNET_RPC_URL=https://ethereum-rpc.publicnode.com forge test --match-path test/scratch/SelfOffer.fork.t.sol -vv). State: fresh Cocoon from this source bound to live Seaport 0x0000000000000068F116a894984e2DB1123eB395 and the live collection 0x0000eC93127BAA929E58E97dd0095A2BFb38ec1D; the holder of seat 1376 transfers it to Cocoon with transferFrom (the adopt flow; seats[1376].held stays false); FloorFeed reports 2.8 ETH; depositTax(10 ETH) so seatPot = 5.95 ETH (accumulate mode). attacker (no seat, no approval, no signature) calls cocoon.buySeat(AdvancedOrder(OrderParameters{offerer: cocoon, zone: 0, offer: [ERC721 collection #1376, 1, 1], consideration: [NATIVE 2.94 ETH -> attacker], FULL_OPEN, startTime now-1, endTime now+1h, zoneHash 0, salt 0xA77AC4, conduitKey 0, totalOriginalConsiderationItems 1}, 1, 1, '', ''), []). Expected: revert (BadOrder or PurchaseFailed: there is no seller and Cocoon already owns the seat); attacker.balance 0; seatPot 5.95 ETH; seats[1376].held false. Actual (logged by the test): the call succeeds; attacker.balance = 2.9547 ETH (2.94 price + 0.0147 reward); seatPot = 2.9953 ETH; seats[1376] = {cost 2.94 ETH, held true}; collection.ownerOf(1376) is still Cocoon. The test asserts the expected behaviour and fails on this code with 'Cocoon bought a seat it already held, from itself'.","severity":"medium","snippet":"    function _checkOrder(AdvancedOrder calldata order) private view returns (uint256 tokenId, uint256 price) {","title":"buySeat accepts an order whose offerer is Cocoon itself: an unrecorded seat Cocoon already holds is 'sold' to it for floor x 1.05 plus the caller reward, paid from the seat pot to the caller"},{"citation":"resolved","description":"startAuction is permissionless and accepts any non-zero balance as the lot. Whoever sends 1 base unit of a harvest token H while Cocoon holds none and calls startAuction(H) fixes an auction of 1 unit at params.harvestStartWei (100 ETH). Harvest that arrives afterwards cannot be auctioned (AuctionRunning) until the dust is taken; Decay.price keeps the dust above 1e12 wei for 47 hours, so nobody pays for it, and it goes for 0 at 48 hours. Each cycle therefore delays the harvest's own auction by up to 48 hours; the attack costs dust plus gas and can be repeated whenever the balance returns to zero. startImdAuction has the same shape: depositTax of 20 wei books 1 wei to imdBurnBalance, startImdAuction opens a 1 wei lot with demand 20000 wei of IMD, and the real IMD-burn ETH accumulating behind it cannot be offered until that lot is taken. Nothing is lost; funds are delayed. This is the residual the manifest notes record as 'Low finding 056274ff67ca' from the previous round, unchanged in this commit; it is distinct from finding 1 (a lock on startAuction does not close it). A minimum lot, or letting a running auction be topped up with the new balance, would close it.","line":529,"path":"src/Cocoon.sol","reproduction":"forge test --match-path test/scratch/DustLot.t.sol (passes: it demonstrates the delay). State: Cocoon wired; harvest token H. Steps: mint 1 unit of H to Cocoon; griefer calls startAuction(H): auctions[H] = {lot 1, start 100 ether}. Mint 1000e18 H to Cocoon (the real harvest); keeper's startAuction(H) reverts AuctionRunning. warp +47 hours: auctionPrice(H) > 1e12 wei, startAuction(H) still reverts AuctionRunning. warp +1 hour: auctionPrice(H) == 0; keeper takes the 1 unit for 0 ETH; only now does startAuction(H) record lot 1000e18 at 100 ETH. Expected: harvest that arrives while a dust lot is up is auctioned without a two-day wait. Actual: the harvest's auction cannot open for 48 hours.","severity":"low","snippet":"        if (balance == 0) revert NothingToAuction();","title":"startAuction fixes a lot of any size at the 100 ETH start, so one base unit of a harvest token parks the real harvest behind a 48-hour dust auction; a 1 wei IMD lot does the same to the IMD auction"},{"citation":"resolved","description":"Params.burnImpactBps is documented as 'one burn may move the pool price by at most this', but the cap is applied in sqrt-price space through _sqrt, which rounds down. _sqrt(95,000,000) = 9746 (9746^2 = 94,984,516 < 95,000,000 < 9747^2 = 95,004,009), so the limit is 0.9746 x sqrtPrice and the price may fall to 0.94984516 of spot, a 5.0155% move. At burnImpactBps = 1000, _sqrt(90,000,000) = 9486 (9486^2 = 89,984,196), a 10.0158% move. The overshoot is at most about 1.6 bps of price, does not compound (each burn is bounded from the new spot), and loses no funds; docs/REVIEW.md already notes '5.02%'. If exactness is wanted, round the root up: y = _sqrt(x); if (y * y < x) y += 1 gives 9747 (4.996%) and 9487 (9.997%). From audit_math (info); verified by arithmetic.","line":475,"path":"src/Cocoon.sol","reproduction":"Arithmetic on the code's own constants: (BPS - 500) * BPS = 95,000,000; _sqrt returns 9746 since 9746^2 = 94,984,516 <= 95,000,000 < 9747^2 = 95,004,009; limit = sqrtPrice * 9746 / 10000; price ratio = 9746^2 / 1e8 = 0.94984516, i.e. a 5.0155% move. With burnImpactBps = 1000: _sqrt(90,000,000) = 9486, 9486^2 / 1e8 = 0.89984196, a 10.0158% move. Expected under the parameter's documentation: at most 5.0000% / 10.0000%. In the CocoonFixture, a buy of 5 ETH followed by setParams(burnImpactBps = 1000), depositTax(40 ETH), roll 5 blocks and burn() leaves sqrtPrice_after^2 / sqrtPrice_before^2 = 0.89984196 when the pot is large enough to reach the limit.","severity":"info","snippet":"        uint256 limit = uint256(sqrtPrice) * _sqrt((BPS - p.burnImpactBps) * BPS) / BPS;","title":"burn: the integer square root rounds the price limit down, so one burn may move the price 5.0155% against a 5% burnImpactBps (10.0158% at the 10% bound)"},{"citation":"resolved","description":"allowedRise = floor(prev * 2500 * gap / (21600 * 10000)). For prev below 86,400 wei a six-hour gap allows less than one wei of rise, so any higher report is refused with MoveTooLarge until gap >= 86,400 * 21600 / prev seconds; at prev = 3 wei that is 24 hours per +1 wei, and climbing from a unit-error report of 1 (one ETH written in ETH instead of wei) back to 1e18 by reports alone takes about 60 days of near-doubling. Only the attester, a trusted role, can place such a report, falls stay unlimited, mode() returns NEUTRAL once the report lapses so the vault stops buying rather than buying wrong, and the owner can clear it with setQuestion through the 48-hour timelock. A boundary refinement of accepted finding 5 of the launch #1400 audit (slow recovery after a low report), recorded as an operational limit, not a loss path. From audit_math (info); verified by arithmetic.","line":97,"path":"src/FloorFeed.sol","reproduction":"CocoonFixture with the test attester: _setFloor(3) (a signed report of 3 wei issued now); vm.warp(+6 hours); _setFloor(4). Arithmetic: allowedRise = mulDiv(3, 2500 * 21600, 21600 * 10000) = floor(162,000,000 / 216,000,000) = 0, so next - prev = 1 > 0 and report reverts MoveTooLarge. Expected under the documented rule (25% per six hours): a one-wei rise from 3 wei is 33%, so refusing it at 6 hours is correct, but at this scale no rise at all is admitted until the gap reaches 86,400 seconds, and the climb back to a sane value takes weeks. Actual: MoveTooLarge at every gap under 24 hours.","severity":"info","snippet":"            uint256 allowedRise = FullMath.mulDiv(prev, MAX_RISE_BPS * gap, RISE_WINDOW * BPS);","title":"FloorFeed.report: the rise allowance truncates to zero for very small stored floors, so a wrongly tiny report cannot be corrected by reports within the window"},{"citation":"resolved","description":"Trust assumptions, documented as the methodology asks; not a code defect. Read from mainnet at block 26176578 through a public RPC: Cocoon 0xc4e514e60565a2c04bd657f18bb34b503d6c299d has code (24,277 bytes, this tree's Cocoon size), its owner, developer and operator are all 0x7A2B112aF54419ed0B18e051027f3142C13441D9, wired() is false, FLOOR is 0x5A215aa9883cEfE5fa74085ba66288388b83700C, and POOL_MANAGER, SEAPORT, COLLECTION and IMD are the live mainnet contracts the fork tests use. Timelock 0x8be826082c0fbaec485048aa26ef42ac147898ec: getMinDelay 172800; PROPOSER_ROLE is held by that EOA; EXECUTOR_ROLE is held by address(0), so anyone may execute a matured operation. After PostLaunch the hook's lowerTax/transferOwnership and Cocoon's setParams/setOperator/transferOwnership/wire are that one key behind 48 hours, and until PostLaunch runs they are that key with no delay; both ownership transfers are one-step. The material power is FloorFeed.setAttester: a proposer-scheduled setAttester(key it controls), executed after 48 hours, lets that key sign any floor; the rise cap (25% per 6 h) only slows the climb (about 2.44x in 24 hours), and buySeat then pays up to 1.05x that floor to any seller, the proposer included. README's 'The vault trusts no key for the reference price' and 'Nobody holds the keys' overstate this: the 48-hour delay is the protection and should be stated as such. Merged from audit_permissions (info, roles as deployed) and audit_economics (info, attester replacement): one actor, one delay.","line":112,"path":"src/FloorFeed.sol","reproduction":"cast call 0x8be826082c0fbaec485048aa26ef42ac147898ec 'getMinDelay()(uint256)' returns 172800; 'hasRole(bytes32,address)(bool)' with keccak('PROPOSER_ROLE') and 0x7A2B112aF54419ed0B18e051027f3142C13441D9 returns true; with keccak('EXECUTOR_ROLE') and address(0) returns true. cast call 0xc4e514e60565a2c04bd657f18bb34b503d6c299d owner()/developer()/operator() return the EOA, wired() returns false. Owner path, for disclosure only: schedule FloorFeed.setAttester(X) with delay 172800; after 48 h anyone executes; X signs floor = prev * 1.25 every 6 h (each passes MoveTooLarge exactly at the cap); after 24 h the floor is about 2.44x; a seat listed at 1.05 x that is bought by the permissionless buySeat. Expected: exactly this, per the request's description of the pre-PostLaunch state.","severity":"info","snippet":"    function setAttester(address attester_) external onlyOwner {","title":"Privileged roles as deployed: one EOA holds every Cocoon and FloorFeed role until PostLaunch and is the timelock's sole proposer and canceller; behind the 48-hour delay it can replace the attester and"},{"citation":"resolved","description":"Consistent with the brief ('taxes the ETH side of every swap in its launch pool') and with the required plain token, so not a defect of the code as specified; a gap between README.md ('A tax on every trade', '6% of the ETH side of every buy and sell') and what the contracts guarantee. Anyone holding PUPATE (the swarm allocation, or tokens bought once through the launch pool) can initialise a hookless ETH/PUPATE pool on the same PoolManager or on v2/v3 and provide liquidity there; swaps in it pay no tax, and the 99% opening tax is bypassed by anyone who already holds PUPATE at the open. Since the launch pool can never take new liquidity, the untaxed venue can become the deeper one over time and tax revenue falls to the arbitrage flow between the two. From audit_economics (info); a documentation correction, no code change implied.","line":255,"path":"src/PupateHook.sol","reproduction":"Any PoolManager after the launch: holder H with 1,000,000 PUPATE and 0.01 ETH calls manager.initialize(PoolKey(ETH, PUPATE, 3000, 60, IHooks(address(0))), sqrtPrice) and modifyLiquidity on that key (no hook gates it); a buyer swaps 1 ETH there. Expected by the README: 6% (99% in the opening window) of the ETH side collected. Actual: hook.totalTax() and manager.balanceOf(hook, 0) unchanged, because _taxed only returns true for the launch pool id.","severity":"info","snippet":"        return sender != sink && PoolId.unwrap(key.toId()) == PoolId.unwrap(launchPool);","title":"The tax is enforced only in the launch pool and PUPATE is a plain ERC-20, so a hookless ETH/PUPATE pool trades untaxed while the README promises a tax 'on every trade'"}],"hash":"fe840db224f0857441acc3fe070ef82889e54f7d1c732ca4b387437b3f88e190","nodeId":"8af9fcb6-cc26-45a5-a0fb-b5d93f932d54","outcome":"completed","summary":"The review is complete. The findings file is on disk at the repository root (git excludes it by the harness's own rule, as it does `test/scratch/`), and no source, test, script or configuration file was changed.\n\n**Verdict: the launch stays parked.** One high finding is confirmed and reproduced in Cocoon, the deployed sink. PupateHook and PupateToken, the two contracts this manifest deploys, hold on every entry point.\n\n**Manifest.** `launch.json` is valid: schema keys only, constructor arguments in declaration order with lowercase addresses, permissions equal to `getHookPermissions` (flags 0x18CC), zero-argument token constructor, fee 12500, tick spacing 60, native ETH, the 10 ETH initial price kept, notes of 3652 characters.\n\n**Suite.** `forge test` on forge 1.8.3: 215 passed, 0 failed, 10 fork tests skipped without an RPC. With a public mainnet RPC the 10 fork tests pass at block 26176592, including the real LaunchFactory rehearsal.\n\n**Findings written to `.imd-findings.json`** (specialist reports merged and recalibrated):\n\n1. **High, Cocoon.** `takeAuction` refunds the taker's excess before transferring the lot, and `startAuction` holds no lock, so a contract taker re-enters and books a phantom auction over harvest Cocoon is about to lose. The phantom blocks fresh auctions and lets the next harvest batch be taken for 0 ETH after its curve decays. I ran the specialist's proof: both tests fail on this code for the stated reason. The proof is attached. Fix: make `startAuction` nonReentrant or transfer the lot before the refund.\n2. **Medium, Cocoon.** `buySeat` accepts an order whose offerer is Cocoon itself. Live Seaport skips signatures when offerer equals caller, so any caller can sell Cocoon an unrecorded seat it already holds for floor x 1.05 plus the reward. Reproduced on a mainnet fork against live Seaport: the attacker received 2.9547 ETH from the seat pot and the seat never moved.\n3. **Low, Cocoon.** One base unit of a harvest token opens a 100 ETH dust auction that parks the real harvest for 48 hours. Delay, no loss. Reproduced with a local test.\n4. **Four info items.** The burn limit's integer square root overshoots the impact parameter by about 1.6 bps; FloorFeed's rise allowance truncates to zero for tiny floors; the single-EOA role model behind the 48h timelock, including attester replacement, read from mainnet; the tax applies only to the launch pool while the README says \"every trade\".\n\nDropped or merged: the two trust-assumption reports from different specialists became one; nothing else was dropped, since every specialist claim reproduced. My own pass over the hook's four swap modes, delta accounting, claim settlement, the liquidity gate and the token found nothing new.\n\n**Coverage.** All 39 listed entry points answered: 35 hold, 4 carry a finding reference, plus rows for `buySeat`, the view callbacks, the manifest, the balance identity and the suite run.","treeHash":null,"usage":{"cachedInputTokens":1866804,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":71970,"runtime":"claude","turns":38,"wallClockMs":1123808}}],"verification":[{"checks":[{"durationMs":8800,"exitCode":0,"name":"build","output":"Compiling 136 files with Solc 0.8.26\nSolc 0.8.26 finished in 8.61s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to pure\n  --> test/FloorFeed.t.sol:49:5:\n   |\n49 |     function _signFor(OracleAttestation.Attestation memory a, uint256 key, uint256 chainId, address consumer)\n   |     ^ (Relevant source part starts here and spans across multiple lines).\n\nwarning[missing-events-access-control]: `owner` is changed without an event but is used for access control\n    ╭▸ src/PupateHook.sol:248:9\n    │\n248 │         owner = to;\n    │         ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/PupateVesting.sol:38:13\n   │\n38 │         if (block.timestamp < START) return 0;\n   │             ━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/PupateVesting.sol:39:13\n   │\n39 │         if (block.timestamp >= uint256(START) + DURATION) return total;\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/PupateVesting.sol:54:9\n   │\n54 │         emit Released(amount);\n   │         ━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-events-access-control]: `owner` is changed without an event but is used for access control\n    ╭▸ src/FloorFeed.sol:140:9\n    │\n140 │         owner = to;\n    │         ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/PupateHook.sol:111:13\n    │\n111 │         if (fallen >= LAUNCH_TAX_BPS - standing) return standing;\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/PupateHook.sol:134:13\n    │\n134 │             emit LaunchPoolSet(id, block.timestamp);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n   ╭▸ src/FloorFeed.sol:85:13\n   │\n85 │         if (a.issuedAt > block.timestamp || block.timestamp > uint256(a.issuedAt) + age) revert Expired();\n   │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PupateHook.sol:126:24\n    │\n126 │             openedAt = uint40(block.timestamp);\n    │                        ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint40' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PupateHook.sol:127:29\n    │\n127 │             openedAtBlock = uint40(block.number);\n    │                             ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint40' is safe 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/FloorFeed.sol:85:45\n   │\n85 │         if (a.issuedAt > block.timestamp || block.timestamp > uint256(a.issuedAt) + age) revert Expired();\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/FloorFeed.sol:104:9\n    │\n104 │         emit Reported(next, a.issuedAt, _freshUntil());\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/PupateHook.sol:209:44\n    │\n209 │         return (IHooks.afterSwap.selector, unspecified);\n    │                                            ━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PupateHook.sol:193:69\n    │\n193 │             if (int256(delta.amount0()) != params.amountSpecified + int256(tax)) revert PartialFill();\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/FloorFeed.sol:149:33\n    │\n149 │         return issuedAt != 0 && block.timestamp <= _freshUntil();\n    │                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PupateHook.sol:198:25\n    │\n198 │             tax = buy ? uint256(uint128(-eth)) * rate / (BPS - rate) : uint256(uint128(eth)) * rate / BPS;\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/PupateHook.sol:198:33\n    │\n198 │             tax = buy ? uint256(uint128(-eth)) * rate / (BPS - rate) : uint256(uint128(eth)) * rate / BPS;\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/PupateHook.sol:198:72\n    │\n198 │             tax = buy ? uint256(uint128(-eth)) * rate / (BPS - rate) : uint256(uint128(eth)) * rate / BPS;\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/PupateHook.sol:198:80\n    │\n198 │             tax = buy ? uint256(uint128(-eth)) * rate / (BPS - rate) : uint256(uint128(eth)) * rate / BPS;\n    │                                                                                ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/PupateHook.sol:220:9\n    │\n220 │         emit Flushed(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/PupateHook.sol:218:9\n    │\n218 │         poolManager.unlock(abi.encode(claims));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/PupateHook.sol:272:39\n    │\n272 │         if (params.zeroForOne) return uint256(-params.amountSpecified) * rate / BPS;\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/PupateHook.sol:273:16\n    │\n273 │         return uint256(params.amountSpecified) * rate / (BPS - 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[missing-events-access-control]: `owner` is changed without an event but is used for access control\n    ╭▸ src/Cocoon.sol:646:9\n    │\n646 │         owner = to;\n    │         ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-access-control\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Cocoon.sol:258:9\n    │\n258 │         emit OwnershipTransferred(address(0), owner_);\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/Cocoon.sol:259:9\n    │\n259 │         emit DeveloperSet(developer_);\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/Cocoon.sol:260:9\n    │\n260 │         emit OperatorSet(operator_);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/oracle/OracleAttestation.sol:63:31\n   │\n63 │         if (sig.length != 65) revert BadSignatureLength();\n   │                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n   ╭▸ src/oracle/OracleAttestation.sol:72:34\n   │\n72 │         if (uint256(s) > HALF_N) revert BadSignatureS();\n   │                                  ━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Cocoon.sol:272:9\n    │\n272 │         emit ProceedsReceived(msg.sender, msg.value);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Cocoon.sol:291:9\n    │\n291 │         emit TaxDeposited(amount, toSeat, toBurn, dev, toImd, m);\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/Cocoon.sol:302:9\n    │\n302 │         emit ProceedsReceived(address(0), surplus);\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/Cocoon.sol:432:9\n    │\n432 │ ┏         emit SeatListed(\n433 │ ┃             tokenId,\n434 │ ┃             SeatListing.startPrice(t),\n435 │ ┃             SeatListing.endPrice(t),\n436 │ ┃             block.timestamp,\n437 │ ┃             block.timestamp + p.listDecay\n438 │ ┃         );\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/Cocoon.sol:346:9\n    │\n346 │         emit SeatBought(tokenId, spent, msg.sender, reward);\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/Cocoon.sol:357:9\n    │\n357 │         emit SeatAdopted(tokenId, floor);\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/Cocoon.sol:373:9\n    │\n373 │         emit SeatSold(tokenId, s.cost);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/Cocoon.sol:372:9\n    │\n372 │         SEAPORT.cancel(orders);\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/Cocoon.sol:402:80\n    │\n402 │             if (c.itemType != ItemType.NATIVE || c.recipient == address(this)) revert BadOrder();\n    │                                                                                ━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/Cocoon.sol:403:57\n    │\n403 │             if (c.startAmount == 0 || c.endAmount == 0) revert BadOrder();\n    │                                                         ━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Cocoon.sol:419:19\n    │\n419 │             cost: uint128(cost),\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/Cocoon.sol:420:23\n    │\n420 │             boughtAt: uint40(block.timestamp),\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint40' is safe 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/Cocoon.sol:431:9\n    │\n431 │         SEAPORT.validate(SeatListing.orders(address(this), address(COLLECTION), tokenId, t));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/Cocoon.sol:455:64\n    │\n455 │             (,, uint256 totalFilled,) = SEAPORT.getOrderStatus(SEAPORT.getOrderHash(orders[i]));\n    │                                                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/Cocoon.sol:455:41\n    │\n455 │             (,, uint256 totalFilled,) = SEAPORT.getOrderStatus(SEAPORT.getOrderHash(orders[i]));\n    │                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/Cocoon.sol:455:41\n    │\n455 │             (,, uint256 totalFilled,) = SEAPORT.getOrderStatus(SEAPORT.getOrderHash(orders[i]));\n    │                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[reentrancy-no-eth]: external call can be reentered before `burnPot` is updated\n    ╭▸ src/Cocoon.sol:478:31\n    │\n478 │         bytes memory result = POOL_MANAGER.unlock(abi.encode(swapBudget, uint160(limit)));\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/Cocoon.sol:486:9\n    │\n486 │         emit Burned(spent, bought, msg.sender, reward);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Cocoon.sol:478:74\n    │\n478 │         bytes memory result = POOL_MANAGER.unlock(abi.encode(swapBudget, uint160(limit)));\n    │                                                                          ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint160' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Cocoon.sol:497:9\n    │\n497 │         emit Burned(0, amount, msg.sender, 0);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Cocoon.sol:507:77\n    │\n507 │         BalanceDelta delta = POOL_MANAGER.swap(launchKey, SwapParams(true, -int256(budget), 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/Cocoon.sol:508:25\n    │\n508 │         uint256 spent = uint256(uint128(-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/Cocoon.sol:508:33\n    │\n508 │         uint256 spent = uint256(uint128(-delta.amount0()));\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/Cocoon.sol:509:26\n    │\n509 │         uint256 bought = uint256(uint128(delta.amount1()));\n    │                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Cocoon.sol:509:34\n    │\n509 │         uint256 bought = uint256(uint128(delta.amount1()));\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/Cocoon.sol:510:25\n    │\n510 │         if (spent != 0) POOL_MANAGER.settle{value: spent}();\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Cocoon.sol:530:73\n    │\n530 │         uint128 lot = balance > type(uint128).max ? type(uint128).max : uint128(balance);\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/Cocoon.sol:531:64\n    │\n531 │         auctions[token] = Auction(lot, params.harvestStartWei, uint40(block.timestamp));\n    │                                                                ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint40' is safe 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/Cocoon.sol:549:9\n    │\n549 │         emit AuctionTaken(token, msg.sender, a.lot, price);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Cocoon.sol:565:30\n    │\n565 │         imdAuction = Auction(uint128(lot), uint128(demand), uint40(block.timestamp));\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/Cocoon.sol:565:44\n    │\n565 │         imdAuction = Auction(uint128(lot), uint128(demand), uint40(block.timestamp));\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/Cocoon.sol:565:61\n    │\n565 │         imdAuction = Auction(uint128(lot), uint128(demand), uint40(block.timestamp));\n    │                                                             ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint40' is safe 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/Cocoon.sol:582:9\n    │\n582 │         emit ImdBurned(msg.sender, demand, a.lot);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/Cocoon.sol:594:13\n    │\n594 │         if (a.expiresAt <= block.timestamp) revert Expired();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Cocoon.sol:599:51\n    │\n599 │         _workerApprovals[digest] = WorkerApproval(uint232(a.tokenId), s.round, true);\n    │                                                   ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint232' is safe 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/Cocoon.sol:626:9\n    │\n626 │         emit DeveloperPaid(amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/Cocoon.sol:686:22\n    │\n686 │         (bool ok,) = to.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/utils/CocoonFixture.sol:75:13\n   │\n75 │         if (block.timestamp <= lastIssuedAt) vm.warp(lastIssuedAt + 1);\n   │             ━━━━━━━━━━━━━━━                  ───────────────────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/utils/CocoonHandler.sol:116:13\n    │\n116 │         if (block.timestamp <= lastIssuedAt) vm.warp(lastIssuedAt + 1);\n    │             ━━━━━━━━━━━━━━━                  ───────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/cocoon/CocoonAuctions.t.sol:27:17\n   │\n27 │         vm.warp(block.timestamp + 2 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.number` may be reused across `vm.roll`\n   ╭▸ test/cocoon/CocoonBurn.t.sol:20:17\n   │\n20 │         vm.roll(block.number + 5);\n   │         ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockNumber()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.number` may be reused across `vm.roll`\n   ╭▸ test/cocoon/CocoonBurn.t.sol:74:17\n   │\n74 │         vm.roll(block.number + 4);\n   │         ────────━━━━━━━━━━━━───── `vm.roll` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockNumber()` 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/utils/CocoonFixture.sol:76:34\n   │\n76 │         uint64 issuedAt = uint64(block.timestamp);\n   │                                  ━━━━━━━━━━━━━━━\n   │\n   ⸬  test/cocoon/CocoonCore.t.sol:96:9\n   │\n96 │         vm.warp(block.timestamp + 4 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/utils/CocoonFixture.sol:75:13\n   │\n75 │         if (block.timestamp <= lastIssuedAt) vm.warp(lastIssuedAt + 1);\n   │             ━━━━━━━━━━━━━━━\n   │\n   ⸬  test/cocoon/CocoonCore.t.sol:96:9\n   │\n96 │         vm.warp(block.timestamp + 4 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/cocoon/CocoonCore.t.sol:96:17\n   │\n96 │         vm.warp(block.timestamp + 4 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/utils/CocoonFixture.sol:76:34\n   │\n75 │         if (block.timestamp <= lastIssuedAt) vm.warp(lastIssuedAt + 1);\n   │                                              ───────────────────────── `vm.warp` changes this environment here\n76 │         uint64 issuedAt = uint64(block.timestamp);\n   │                                  ━━━━━━━━━━━━━━━\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/cocoon/CocoonMarket.t.sol:41:48\n    │\n 41 │             price, startX, endX, decay, uint40(block.timestamp), uint16(block.number)\n    │                                                ━━━━━━━━━━━━━━━\n    ‡\n109 │         vm.warp(block.timestamp + 3.5 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/cocoon/CocoonMarket.t.sol:109:17\n    │\n109 │         vm.warp(block.timestamp + 3.5 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/cocoon/CocoonMarket.t.sol:41:48\n   │\n41 │             price, startX, endX, decay, uint40(block.timestamp), uint16(block.number)\n   │                                                ━━━━━━━━━━━━━━━\n   │\n   ⸬  test/utils/CocoonFixture.sol:95:9\n   │\n95 │         vm.warp(block.timestamp + 6 hours + 1);\n   │         ────────────────────────────────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/utils/CocoonFixture.sol:95:17\n   │\n95 │         vm.warp(block.timestamp + 6 hours + 1);\n   │         ────────━━━━━━━━━━━━━━━─────────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/cocoon/CocoonMarket.t.sol:41:48\n    │\n 41 │             price, startX, endX, decay, uint40(block.timestamp), uint16(block.number)\n    │                                                ━━━━━━━━━━━━━━━\n    ‡\n278 │         vm.warp(block.timestamp + 20 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/cocoon/CocoonMarket.t.sol:278:17\n    │\n278 │         vm.warp(block.timestamp + 20 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/cocoon/CocoonReentrancy.t.sol:34:17\n   │\n34 │         vm.warp(block.timestamp + 48 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/cocoon/CocoonReview.t.sol:193:73\n    │\n193 │             SeatListing.Terms(2.4 ether, 12_000, 10_000, 7 days, uint40(block.timestamp), 1),\n    │                                                                         ━━━━━━━━━━━━━━━\n    ‡\n197 │         vm.warp(block.timestamp + 3.5 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/cocoon/CocoonReview.t.sol:197:17\n    │\n197 │         vm.warp(block.timestamp + 3.5 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/cocoon/CocoonReview.t.sol:73:69\n    │\n 73 │             SeatListing.Terms(price, 10_000, 10_000, 7 days, uint40(block.timestamp), uint16(block.number)),\n    │                                                                     ━━━━━━━━━━━━━━━\n    ‡\n256 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/cocoon/CocoonReview.t.sol:248:30\n    │\n248 │         a.expiresAt = uint64(block.timestamp + 30 days);\n    │                              ━━━━━━━━━━━━━━━\n    ‡\n256 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/cocoon/CocoonReview.t.sol:256:17\n    │\n256 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/cocoon/CocoonReview.t.sol:73:69\n    │\n 73 │             SeatListing.Terms(price, 10_000, 10_000, 7 days, uint40(block.timestamp), uint16(block.number)),\n    │                                                                     ━━━━━━━━━━━━━━━\n    ‡\n327 │         vm.warp(block.timestamp + 1 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/cocoon/CocoonReview.t.sol:327:17\n    │\n327 │         vm.warp(block.timestamp + 1 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.chainid` may be reused across `vm.chainId`\n   ╭▸ src/cocoon/WorkerAuthorization.sol:26:79\n   │\n26 │         return keccak256(abi.encode(DOMAIN_TYPEHASH, NAME_HASH, VERSION_HASH, block.chainid, collection));\n   │                                                                               ━━━━━━━━━━━━━\n   │\n   ⸬  test/cocoon/WorkerAuthorization.t.sol:62:9\n   │\n62 │         vm.chainId(1);\n   │         ───────────── `vm.chainId` changes this environment here\n   │\n   ├ help: capture it with `vm.getChainId()` 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/fork/CocoonSeaport.fork.t.sol:141:81\n    │\n141 │             holder, SeatListing.Terms(2.4 ether, 12_000, 10_000, 7 days, uint40(block.timestamp), 0)\n    │                                                                                 ━━━━━━━━━━━━━━━\n    ‡\n145 │         vm.warp(block.timestamp + 3.5 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/fork/CocoonSeaport.fork.t.sol:145:17\n    │\n145 │         vm.warp(block.timestamp + 3.5 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/fork/CocoonSeaport.fork.t.sol:272:81\n    │\n272 │             holder, SeatListing.Terms(2.8 ether, 10_000, 10_000, 7 days, uint40(block.timestamp), 0)\n    │                                                                                 ━━━━━━━━━━━━━━━\n    ‡\n282 │         vm.warp(block.timestamp + 7 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/fork/CocoonSeaport.fork.t.sol:451:31\n    │\n282 │         vm.warp(block.timestamp + 7 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    ‡\n451 │         a.requestId = bytes32(block.timestamp);\n    │                               ━━━━━━━━━━━━━━━\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/fork/CocoonSeaport.fork.t.sol:459:29\n    │\n282 │         vm.warp(block.timestamp + 7 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    ‡\n459 │         a.issuedAt = uint64(block.timestamp);\n    │                             ━━━━━━━━━━━━━━━\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/fork/CocoonSeaport.fork.t.sol:460:30\n    │\n282 │         vm.warp(block.timestamp + 7 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    ‡\n460 │         a.expiresAt = uint64(block.timestamp + 24 hours);\n    │                              ━━━━━━━━━━━━━━━\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/fork/CocoonSeaport.fork.t.sol:380:69\n    │\n282 │         vm.warp(block.timestamp + 7 days);\n    │         ───────────────────────────────── `vm.warp` changes this environment here\n    ‡\n380 │             SeatListing.Terms(price, 10_000, 10_000, 7 days, uint40(block.timestamp), 0),\n    │                                                                     ━━━━━━━━━━━━━━━\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/fork/CocoonSeaport.fork.t.sol:282:17\n    │\n282 │         vm.warp(block.timestamp + 7 days);\n    │         ────────━━━━━━━━━━━━━━━────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":622,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/Scaffold.t.sol:ScaffoldTest\n[PASS] test_toolchainIsCancun() (gas: 3313)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 194.85µs (75.17µs CPU time)\n\nRan 3 tests for test/fork/LaunchFactory.fork.t.sol:LaunchFactoryForkTest\n[SKIP] test_afterTheFactorysTransactionTheLaunchPoolIsClosedToLiquidity() (gas: 0)\n[SKIP] test_swapsAreTaxedAndTheFactoryStillCollectsItsPoolFees() (gas: 0)\n[SKIP] test_theRealFactoryOpensAndSeedsTheLaunchPoolInOneTransaction() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 3 skipped; finished in 449.26µs (82.49µs CPU time)\n\nRan 5 tests for test/cocoon/SeatListing.t.sol:SeatListingTest\n[PASS] test_componentsMatchTheOrdersFieldForField() (gas: 31891)\n[PASS] test_differentPurchasesOfTheSameSeatGetDifferentSalts() (gas: 16894)\n[PASS] test_prices() (gas: 4305)\n[PASS] test_theDescendingOrder() (gas: 20052)\n[PASS] test_theFlatTailOrder() (gas: 13034)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 337.06µs (420.22µs CPU time)\n\nRan 3 tests for test/OracleAttestation.t.sol:OracleAttestationTest\n[PASS] test_differentConsumerDoesNotRecoverRealSigner() (gas: 9220)\n[PASS] test_discoverAnswerType() (gas: 27095)\nLogs:\n  answerType for uint256 = 3\n\n[PASS] test_realAttestationRecoversRealSigner() (gas: 9213)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 498.87µs (700.00µs CPU time)\n\nRan 7 tests for test/fork/CocoonSeaport.fork.t.sol:CocoonSeaportForkTest\n[SKIP] test_adoptTakesADirectTransferAndRefusesAnUnsolicitedSafeTransfer() (gas: 0)\n[SKIP] test_buysARealListingPublishesItsOrdersAndSellsThroughOne() (gas: 0)\n[SKIP] test_fulfillerTipIsRefused() (gas: 0)\n[SKIP] test_fullRestrictedOrderIsAccepted() (gas: 0)\n[SKIP] test_refundFromAFallingOrderStaysInTheSeatPot() (gas: 0)\n[SKIP] test_repurchaseIncrementsTheRoundWithoutColliding() (gas: 0)\n[SKIP] test_twoItemConsiderationIsAcceptedAndCostIsTheSum() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 7 skipped; finished in 639.44µs (206.98µs CPU time)\n\nRan 9 tests for test/PupateVesting.t.sol:PupateVestingTest\n[PASS] testFuzz_vestedNeverExceedsTheBalanceAndNeverFalls(uint256,uint256) (runs: 256, μ: 44064, ~: 44190)\nLogs:\n  Bound result 57121191\n  Bound result 57500400\n\n[PASS] test_anyoneMayRelease() (gas: 113322)\n[PASS] test_constructorRejectsBadArguments() (gas: 6339)\n[PASS] test_everythingVestsAtTheEnd() (gas: 188052)\n[PASS] test_halfVestsHalfway() (gas: 127783)\n[PASS] test_nothingVestsBeforeTheStart() (gas: 88727)\n[PASS] test_onlyTheBeneficiaryChangesTheBeneficiary() (gas: 184996)\n[PASS] test_releaseIsIdempotent() (gas: 143708)\n[PASS] test_tokensAddedLaterVestOnTheSameCurve() (gas: 154694)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 3.50ms (4.13ms CPU time)\n\nRan 3 tests for test/cocoon/WorkerAuthorization.t.sol:WorkerAuthorizationTest\n[PASS] test_digestIsBoundToTheCollectionAndTheChain() (gas: 10299)\n[PASS] test_digestMatchesTheSpelledOutEncoding() (gas: 5552)\n[PASS] test_everyFieldChangesTheDigest() (gas: 13981)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 5.44ms (170.72µs CPU time)\n\nRan 9 tests for test/cocoon/CocoonAuctions.t.sol:CocoonAuctionsTest\n[PASS] test_afterTheCutoffTheLotIsFree() (gas: 173895)\n[PASS] test_harvestAuctionFallsAndTheTakerGetsTheLot() (gas: 393485)\n[PASS] test_imdAuctionBurnsTheDemandedImdAndPaysTheLot() (gas: 584301)\n[PASS] test_imdAuctionTakerWithoutImdFails() (gas: 255078)\n[PASS] test_imdThatArrivesDuringTheAuctionWaitsForTheNext() (gas: 517037)\n[PASS] test_openingAndTakingAtOnceCostsTheWholeStartPrice() (gas: 269653)\n[PASS] test_theStartPriceIsFixedWhenTheAuctionStarts() (gas: 160825)\n[PASS] test_whatArrivesDuringAnAuctionGoesToTheNextLot() (gas: 360277)\n[PASS] test_whatCannotBeAuctioned() (gas: 590218)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 6.72ms (1.14ms CPU time)\n\nRan 6 tests for test/cocoon/CocoonPairing.t.sol:CocoonPairingTest\n[PASS] test_isValidSignatureNeverReverts() (gas: 117756)\n[PASS] test_onlyTheOperator() (gas: 60532)\n[PASS] test_operatorApprovesAPairingForAHeldSeat() (gas: 122127)\n[PASS] test_pairingApprovalDiesWithTheSeat() (gas: 162017)\n[PASS] test_rejectsTheWrongWalletAnExpiredMessageAndAnUnheldSeat() (gas: 188889)\n[PASS] test_revokeTurnsAnApprovalInvalid() (gas: 106083)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 18.62ms (828.31µs CPU time)\n\nRan 13 tests for test/PupateToken.t.sol:PupateTokenTest\n[PASS] testFuzz_transferNeverChangesTheSupply(address,uint256) (runs: 256, μ: 86705, ~: 87045)\nLogs:\n  Bound result 302400\n\n[PASS] test_burnFromSpendsTheAllowance() (gas: 115277)\n[PASS] test_burnFromWithoutAllowanceReverts() (gas: 34995)\n[PASS] test_burnOfMoreThanTheBalanceReverts() (gas: 34339)\n[PASS] test_burnReducesBalanceAndSupply() (gas: 55217)\n[PASS] test_metadata() (gas: 26725)\n[PASS] test_transferFromBeyondTheAllowanceReverts() (gas: 84572)\n[PASS] test_transferFromSpendsTheAllowance() (gas: 135818)\n[PASS] test_transferMovesBalance() (gas: 88632)\n[PASS] test_transferOfMoreThanTheBalanceReverts() (gas: 37201)\n[PASS] test_transferToTheZeroAddressReverts() (gas: 30093)\n[PASS] test_unlimitedAllowanceIsNotSpent() (gas: 120054)\n[PASS] test_wholeSupplyIsMintedToTheDeployer() (gas: 20961)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 21.34ms (5.05ms CPU time)\n\nRan 7 tests for test/cocoon/CocoonReentrancy.t.sol:CocoonReentrancyTest\n[PASS] test_noGuardedEntryPointRunsFromInsideTheLotTransfer() (gas: 3176587)\n[PASS] test_plainEthSentFromInsideTheLotTransferIsBookedOnce() (gas: 386421)\n[PASS] test_skimCannotBookATakersExcessFromInsideTheLotTransfer() (gas: 423791)\n[PASS] test_skimCannotBookTheImdLotFromInsideTheImdTransfer() (gas: 499291)\n[PASS] test_skimStillBooksEthThatWasForcedIn() (gas: 148291)\n[PASS] test_taxCannotBeDepositedFromInsideTheLotTransfer() (gas: 460463)\n[PASS] test_theExcessIsRefundedBeforeTheLotTokenIsCalled() (gas: 269092)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 21.53ms (2.04ms CPU time)\n\nRan 6 tests for test/cocoon/Decay.t.sol:DecayTest\n[PASS] testFuzz_neverRises(uint256,uint256,uint256) (runs: 256, μ: 15986, ~: 15951)\nLogs:\n  Bound result 467\n  Bound result 5760\n  Bound result 258154\n\n[PASS] test_constants() (gas: 3487)\n[PASS] test_fallsLinearlyInsideAHalfLife() (gas: 6141)\n[PASS] test_halvesEveryHalfLife() (gas: 5813)\n[PASS] test_isZeroFromTheCutoff() (gas: 4666)\n[PASS] test_startsAtTheStartPrice() (gas: 3644)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 22.21ms (22.31ms CPU time)\n\nRan 13 tests for test/cocoon/CocoonMarket.t.sol:CocoonMarketTest\n[PASS] test_aThirdPartyBuysOurListingAndSettleFixesTheBooks() (gas: 953715)\n[PASS] test_adoptNeedsAHeldUnrecordedSeatAndAFreshFloor() (gas: 433690)\n[PASS] test_buySeatRefundStaysInTheSeatPot() (gas: 561575)\n[PASS] test_buysAListingAtTheFloorListsItAndPaysTheCaller() (gas: 571232)\n[PASS] test_buysRightAtTheTolerance() (gas: 405171)\n[PASS] test_reentryDuringThePurchaseIsRejected() (gas: 972942)\n[PASS] test_rejectsAPurchaseThePotCannotCover() (gas: 984937)\n[PASS] test_rejectsOrdersThatAreNotOneSeatForEth() (gas: 1244296)\n[PASS] test_rejectsPricesAboveTheToleranceAndStaleFloors() (gas: 249402)\n[PASS] test_settleAlsoWorksWhenTheSeatWasBurned() (gas: 538377)\n[PASS] test_settleCancelsTheOldListingsSoARepurchaseCannotBeSoldAtTheOldPrice() (gas: 1332361)\n[PASS] test_settleSeatIsPermissionlessAndFixesTheAverage() (gas: 1114779)\n[PASS] test_unsolicitedSafeTransfersAreRejected() (gas: 84668)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 28.09ms (8.30ms CPU time)\n\nRan 5 tests for test/PoolSetup.t.sol:PoolSetupTest\n[PASS] test_buyPaysExactlyTheNamedEthAndReceivesTokens() (gas: 191316)\n[PASS] test_seedingTakesTokensAndNoEth() (gas: 18284)\n[PASS] test_sellPaysOutEth() (gas: 289808)\n[PASS] test_sinkRouterCanBeToldToReject() (gas: 1125337)\n[PASS] test_sinkRouterKeepsItsDepositsWhenItSwaps() (gas: 1324664)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 28.00ms (970.59µs CPU time)\n\nRan 16 tests for test/cocoon/CocoonCore.t.sol:CocoonCoreTest\n[PASS] testFuzz_noWeiIsLostInTheSplit(uint96) (runs: 256, μ: 200842, ~: 204005)\n[PASS] testFuzz_theBalanceIdentityHoldsThroughDepositsClaimsAndProceeds(uint96[6],uint8) (runs: 256, μ: 712061, ~: 716923)\n[PASS] test_aStaleFloorMeansNeutralEvenWithSeats() (gas: 387782)\n[PASS] test_burnAndAdoptNeedTheWire() (gas: 45510)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 19182)\n[PASS] test_defaults() (gas: 81947)\n[PASS] test_depositAccumulatesWhileNoSeatIsHeld() (gas: 297673)\n[PASS] test_depositSplitsEvenlyWhileTheFloorIsUnknown() (gas: 220196)\n[PASS] test_developerClaimsOnlyTheDeveloperBalance() (gas: 274653)\n[PASS] test_modeFollowsTheFloorAgainstTheAveragePurchasePrice() (gas: 1011762)\n[PASS] test_onlyTheDeveloperRotatesTheDeveloper() (gas: 289717)\n[PASS] test_onlyTheOwnerAdministers() (gas: 127060)\n[PASS] test_ownershipAndOperatorCanBeHandedOver() (gas: 169120)\n[PASS] test_plainEthIsSaleProceedsAndGoesToTheBurnPot() (gas: 105157)\n[PASS] test_setParamsChecksEveryBound() (gas: 778111)\n[PASS] test_wireHappensOnceAndOnlyToTheHooksLaunchPool() (gas: 147014)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 39.44ms (33.04ms CPU time)\n\nRan 8 tests for test/cocoon/CocoonBurn.t.sol:CocoonBurnTest\n[PASS] test_aLargeBurnStopsAtTheImpactLimitAndKeepsTheRest() (gas: 603676)\n[PASS] test_aSmallBurnSpendsTheWholeBudgetAndPaysTheCaller() (gas: 640500)\n[PASS] test_burnNeedsAPot() (gas: 39243)\n[PASS] test_burnPupateBurnsWhatCocoonHolds() (gas: 157703)\n[PASS] test_burnWithTheSpotAboveLiquidityBuysNothingAndReverts() (gas: 1298740)\n[PASS] test_burnsAreSpacedOut() (gas: 799550)\n[PASS] test_theOwnerCanTightenOrLoosenTheLimitWithinBounds() (gas: 602095)\n[PASS] test_unlockCallbackIsOnlyForThePoolManagerDuringABurn() (gas: 60417)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 39.45ms (4.45ms CPU time)\n\nRan 13 tests for test/cocoon/CocoonReview.t.sol:CocoonReviewTest\n[PASS] test_aFloorTooLargeForTheBooksCannotBeAdopted() (gas: 275801)\n[PASS] test_aLegitimateSplitPaymentIsAccepted() (gas: 452820)\n[PASS] test_aSeatSentBackBeforeSettleCanBeSettledAndAdopted() (gas: 1047889)\n[PASS] test_auctionsRejectTheCollectionAndNeedTheWire() (gas: 87353)\n[PASS] test_costIsWhatSeaportPaidOutAndTheRewardIsOnIt() (gas: 562963)\n[PASS] test_ethFromAnyoneButSeaportDuringAPurchaseRevertsThePurchase() (gas: 1248060)\n[PASS] test_pairingApprovalDoesNotReviveWhenTheSeatComesBack() (gas: 1100276)\n[PASS] test_rejectsConsiderationThatIsNotTheOriginal() (gas: 90151)\n[PASS] test_rejectsOrdersThatPayCocoon() (gas: 127373)\n[PASS] test_rejectsZeroAmountItems() (gas: 121871)\n[PASS] test_skimBooksForcedEthAsProceeds() (gas: 126150)\n[PASS] test_theLargestCostTheBooksHoldIsRecordedWhole() (gas: 425222)\n[PASS] test_theOperatorCannotMoveValueSeatsOrParameters() (gas: 743612)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 39.37ms (5.65ms CPU time)\n\nRan 45 tests for test/FloorFeed.t.sol:FloorFeedTest\n[PASS] testFuzz_aReportWithinTheAllowanceIsStored(uint256,uint256) (runs: 256, μ: 209265, ~: 208962)\nLogs:\n  Bound result 1003\n  Bound result 2023217592592592591\n\n[PASS] testFuzz_aRiseAboveTheAllowanceNeverChangesTheStoredPrice(uint256,uint256) (runs: 256, μ: 211789, ~: 211731)\nLogs:\n  Bound result 1003\n  Bound result 340282366920938463463374607431768211454\n\n[PASS] test_aRejectedReportNeverChangesTheStoredValue() (gas: 391528)\n[PASS] test_aRiseAboveTheAllowanceIsRejected() (gas: 167884)\n[PASS] test_aRiseOfAQuarterOverSixHoursIsAccepted() (gas: 176219)\n[PASS] test_acceptsValidReport() (gas: 116282)\n[PASS] test_constructorRejectsAZeroEvidenceChain() (gas: 8258)\n[PASS] test_fallsAreNotLimited() (gas: 175483)\n[PASS] test_freshnessIsBoundedByTheAttestationsOwnExpiry() (gas: 147350)\n[PASS] test_holdingAnAttestationBackGainsNothing() (gas: 211160)\n[PASS] test_maxAgeShortensFreshness() (gas: 146965)\n[PASS] test_onlyOwnerCanAdminister() (gas: 106198)\n[PASS] test_ownerCanRotateAttester() (gas: 176382)\n[PASS] test_ownershipCanBeHandedOver() (gas: 114533)\n[PASS] test_questionCannotBeSetToZero() (gas: 34308)\n[PASS] test_raisingMaxAgeRevivesAnOlderReport() (gas: 161923)\n[PASS] test_rejectsAReportOlderThanMaxAge() (gas: 151709)\n[PASS] test_rejectsAShortLivedAttestation() (gas: 196089)\n[PASS] test_rejectsAgreementBelowQuorum() (gas: 51225)\n[PASS] test_rejectsAnswerOfWrongLength() (gas: 57946)\n[PASS] test_rejectsAttestationForAnotherConsumer() (gas: 47831)\n[PASS] test_rejectsAttestationSignedForAnotherChainsDomain() (gas: 47965)\n[PASS] test_rejectsEveryReportBeforeAQuestionIsSet() (gas: 50224)\n[PASS] test_rejectsEvidenceFromAnotherChain() (gas: 50203)\n[PASS] test_rejectsHighS() (gas: 44631)\n[PASS] test_rejectsIssuedInTheFuture() (gas: 54890)\n[PASS] test_rejectsMoreAgreementThanPanel() (gas: 51581)\n[PASS] test_rejectsQuorumBelowTheMinimum() (gas: 50482)\n[PASS] test_rejectsQuorumThatIsNotAMajority() (gas: 188740)\n[PASS] test_rejectsReplayAndOlderReports() (gas: 203174)\n[PASS] test_rejectsShortSignature() (gas: 40581)\n[PASS] test_rejectsWrongAnswerType() (gas: 57758)\n[PASS] test_rejectsWrongQuestion() (gas: 50195)\n[PASS] test_rejectsWrongSigner() (gas: 48015)\n[PASS] test_rejectsZeroAnswer() (gas: 58035)\n[PASS] test_reportGoesStaleAfterMaxAge() (gas: 130651)\n[PASS] test_reportsInQuickSuccessionCannotCompound() (gas: 550627)\n[PASS] test_setMaxAgeIsBounded() (gas: 59578)\n[PASS] test_settingTheQuestionClearsTheStoredReport() (gas: 306240)\n[PASS] test_settingTheSameQuestionAgainClearsTheStoredReport() (gas: 144986)\n[PASS] test_startsEmptyAndNotFresh() (gas: 13193)\n[PASS] test_theAllowanceGrowsWithTheGapBetweenReports() (gas: 292964)\n[PASS] test_theAllowanceStillAppliesAfterTheReportHasGoneStale() (gas: 245258)\n[PASS] test_theFirstReportIsNotRateLimited() (gas: 113462)\n[PASS] test_zeroAddressesAreRejected() (gas: 6145)\nSuite result: ok. 45 passed; 0 failed; 0 skipped; finished in 39.46ms (72.75ms CPU time)\n\nRan 49 tests for test/PupateHook.t.sol:PupateHookTest\n[PASS] testFuzz_buyExactIn_traderPaysWhatTheyNamedAtAnyPointOfTheSchedule(uint256,uint256) (runs: 256, μ: 291099, ~: 296536)\nLogs:\n  Bound result 624553609298204314506\n  Bound result 1508\n\n[PASS] test_aLowerStandingTaxExtendsTheLaunchScheduleDownToIt() (gas: 56037)\n[PASS] test_aPoolWithoutNativeEthDoesNotBecomeTheLaunchPool() (gas: 2085185)\n[PASS] test_aSecondNativePoolIsNotTheLaunchPoolAndTakesNoLiquidity() (gas: 193231)\n[PASS] test_addressEncodesExactlyTheDeclaredPermissions() (gas: 15833)\n[PASS] test_buyAtTheOpenPaysTheLaunchTax() (gas: 251871)\n[PASS] test_buyExactIn_taxIsSixPercentOfTheEthPaid() (gas: 266521)\n[PASS] test_buyExactOutAtTheOpenPaysNinetyNineTimesThePoolLeg() (gas: 246538)\n[PASS] test_buyExactOut_taxIsSixPercentOfTheGrossEthPaid() (gas: 252092)\n[PASS] test_buyTaxFallsOnePointAMinute() (gas: 36383)\n[PASS] test_buyTaxMeetsTheStandingTaxAfterNinetyThreeMinutes() (gas: 36246)\n[PASS] test_buyTaxStartsAtNinetyNinePercent() (gas: 10960)\n[PASS] test_callbacksRejectCallersOtherThanThePoolManager() (gas: 127534)\n[PASS] test_constructorRejectsAnAddressThatDoesNotEncodeItsPermissions() (gas: 11545)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 8291)\n[PASS] test_dustSwapWhoseTaxRoundsToZeroStillGoesThrough() (gas: 199390)\n[PASS] test_exactEthSwapThatCannotFillReverts() (gas: 169975)\n[PASS] test_exactOutSellDuringTheLaunchPaysTheStandingRate() (gas: 398665)\n[PASS] test_exactOutSellThatCannotFillReverts() (gas: 781762)\n[PASS] test_exactTokenSwapMayFillPartlyAndIsTaxedOnWhatMoved() (gas: 269431)\n[PASS] test_firstBuyWorksWhileThePoolHoldsNoEth() (gas: 247268)\n[PASS] test_firstNativePoolBecomesTheLaunchPool() (gas: 37793)\n[PASS] test_flushAlsoDeliversEthForcedIntoTheHook() (gas: 341668)\n[PASS] test_flushHandsTheCollectedTaxToTheSink() (gas: 370899)\n[PASS] test_flushLeavesTheTaxInPlaceWhenTheSinkRejects() (gas: 494053)\n[PASS] test_flushWithNothingCollectedReverts() (gas: 35051)\n[PASS] test_hookRefusesEthFromAnyoneButThePoolManager() (gas: 35873)\n[PASS] test_inTheOpeningTransactionOnlyTheLaunchPoolTakesLiquidity() (gas: 2754754)\n[PASS] test_laterPoolsOnTheHookDoNotReopenTheLaunchPool() (gas: 628340)\n[PASS] test_launchPoolSetEventNamesThePoolAndTheTime() (gas: 1981699)\n[PASS] test_liquidityCanStillBeRemovedAfterTheOpeningBlock() (gas: 131532)\n[PASS] test_liquidityIsClosedOnceTheOpeningTransactionEnds() (gas: 69922)\n[PASS] test_liquidityStaysClosedInLaterBlocks() (gas: 62550)\n[PASS] test_noPoolOnTheHookTakesLiquidityBeforeTheLaunchPoolOpens() (gas: 2096454)\n[PASS] test_onlyTheOwnerCanLowerTheTaxOrHandOverOwnership() (gas: 56839)\n[PASS] test_ownerCanLowerTheStandingTax() (gas: 294664)\n[PASS] test_ownershipCanBeHandedOver() (gas: 112688)\n[PASS] test_ownershipCannotGoToTheZeroAddress() (gas: 34510)\n[PASS] test_sellAtTheOpenPaysOnlyTheStandingTax() (gas: 420153)\n[PASS] test_sellExactIn_taxIsSixPercentOfTheEthPaidOut() (gas: 423350)\n[PASS] test_sellExactOut_taxIsSixPercentOfTheGrossEthPaidOut() (gas: 426905)\n[PASS] test_standingTaxCannotBeRaisedOrRestated() (gas: 65556)\n[PASS] test_taxCanBeLoweredToZero() (gas: 612428)\n[PASS] test_taxedEventReportsSells() (gas: 397201)\n[PASS] test_theOpeningTransactionIsTheGateNotTheAccountThatOpens() (gas: 3765738)\n[PASS] test_theOpeningTransactionSeedsTheLaunchPool() (gas: 2258118)\n[PASS] test_theSinkIsExemptInEveryMode() (gas: 545664)\n[PASS] test_theSinksOwnSwapMayFillPartly() (gas: 206190)\n[PASS] test_theSinksOwnSwapsAreNotTaxed() (gas: 205669)\nSuite result: ok. 49 passed; 0 failed; 0 skipped; finished in 39.73ms (66.06ms CPU time)\n\nRan 2 tests for test/PupateHook.invariant.t.sol:PupateHookInvariantTest\n[PASS]\nPupateHookInvariantTest invariants:\n[PASS] invariant_everyWeiOfTaxIsHeldAsClaimsOrDeliveredToTheSink\n[PASS] invariant_theHookHoldsNoEth\n[PASS] invariant_theStandingTaxNeverRises\n PupateHookInvariantTest invariants (runs: 48, calls: 2304, reverts: 0)\n\n╭----------+--------------+-------+---------+----------╮\n| Contract | Selector     | Calls | Reverts | Discards |\n+======================================================+\n| Trader   | buyExactIn   | 287   | 0       | 0        |\n|----------+--------------+-------+---------+----------|\n| Trader   | buyExactOut  | 316   | 0       | 0        |\n|----------+--------------+-------+---------+----------|\n| Trader   | flush        | 348   | 0       | 0        |\n|----------+--------------+-------+---------+----------|\n| Trader   | lowerTax     | 364   | 0       | 0        |\n|----------+--------------+-------+---------+----------|\n| Trader   | sellExactIn  | 303   | 0       | 0        |\n|----------+--------------+-------+---------+----------|\n| Trader   | sellExactOut | 332   | 0       | 0        |\n|----------+--------------+-------+---------+----------|\n| Trader   | wait         | 354   | 0       | 0        |\n╰----------+--------------+-------+---------+----------╯\n\nLogs:\n  Bound result 4999999000000000000048880\n  Bound result 200\n  Bound result 999999999000001377\n  Bound result 999999999000000711\n  Bound result 639914234988599439\n  Bound result 101\n  Bound result 1999999998000000\n  Bound result 862160168641231651\n  Bound result 4999999000000000000005638\n  Bound result 4999999000000000000009751\n  Bound result 4999999000000000000003602\n  Bound result 999999999000016349\n  Bound result 1148\n  Bound result 100\n  Bound result 4999999000000000000000396\n  Bound result 4999999000000000000009474\n  Bound result 746\n  Bound result 49999999999000013973\n  Bound result 14316833194519532703\n  Bound result 400\n  Bound result 1453545364993647512516253\n  Bound result 2060000000000000000\n  Bound result 59\n  Bound result 49999999999418630442\n  Bound result 790\n  Bound result 12\n  Bound result 605754540536229422\n  Bound result 4999999000000000000017190\n  Bound result 4999999000000000000010001\n  Bound result 538\n  Bound result 42086295088729132745\n  Bound result 180\n  Bound result 4999999000000000000000011\n  Bound result 700\n  Bound result 1\n  Bound result 999999999000887221\n  Bound result 4999999000000000000011395\n\n[PASS] test_theHandlerActuallyTrades() (gas: 870740)\nLogs:\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000000000\n  Bound result 1000000000000000000000000\n  Bound result 1000000000000\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 464.10ms (446.75ms CPU time)\n\nRan 2 tests for test/cocoon/Cocoon.invariant.t.sol:CocoonInvariantTest\n[PASS]\nCocoonInvariantTest invariants:\n[PASS] invariant_aSeatLeavesCocoonOnlyThroughAFilledListing\n[PASS] invariant_cocoonNeverKeepsPupate\n[PASS] invariant_ethLeavesCocoonOnlyThroughSanctionedPaths\n[PASS] invariant_everyWeiOfTaxIsClaimsOrDelivered\n[PASS] invariant_theBalanceIsExactlyTheFourPots\n[PASS] invariant_theBooksMatchTheSeatsHeld\n CocoonInvariantTest invariants (runs: 48, calls: 2304, reverts: 0)\n\n╭---------------+----------------+-------+---------+----------╮\n| Contract      | Selector       | Calls | Reverts | Discards |\n+=============================================================+\n| CocoonHandler | burn           | 175   | 0       | 0        |\n|---------------+----------------+-------+---------+----------|\n| CocoonHandler | claimDeveloper | 176   | 0       | 0        |\n|---------------+----------------+-------+---------+----------|\n| CocoonHandler | deposit        | 198   | 0       | 0        |\n|---------------+----------------+-------+---------+----------|\n| CocoonHandler | donate         | 180   | 0       | 0        |\n|---------------+----------------+-------+---------+----------|\n| CocoonHandler | flush          | 161   | 0       | 0        |\n|---------------+----------------+-------+---------+----------|\n| CocoonHandler | harvest        | 180   | 0       | 0        |\n|---------------+----------------+-------+---------+----------|\n| CocoonHandler | hostileHarvest | 208   | 0       | 0        |\n|---------------+----------------+-------+---------+----------|\n| CocoonHandler | imdCycle       | 161   | 0       | 0        |\n|---------------+----------------+-------+---------+----------|\n| CocoonHandler | listAndBuy     | 180   | 0       | 0        |\n|---------------+----------------+-------+---------+----------|\n| CocoonHandler | reportFloor    | 164   | 0       | 0        |\n|---------------+----------------+-------+---------+----------|\n| CocoonHandler | sellOurs       | 167   | 0       | 0        |\n|---------------+----------------+-------+---------+----------|\n| CocoonHandler | trade          | 173   | 0       | 0        |\n|---------------+----------------+-------+---------+----------|\n| CocoonHandler | warp           | 181   | 0       | 0        |\n╰---------------+----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 2800000000000000000\n  Bound result 387392296338452\n  Bound result 100017805652972308718\n  Bound result 2214\n  Bound result 1960000000000001990\n  Bound result 2800000000000000000\n  Bound result 2800000000000000000\n  Bound result 2800000000000000000\n  Bound result 398386080627\n  Bound result 0\n  Bound result 1960000000004121581\n  Bound result 20570\n  Bound result 2800000000000000000\n  Bound result 133239105828\n  Bound result 2800000000000000000\n  Bound result 100000000000000000972\n  Bound result 2800000000000000000\n  Bound result 100000000000000003247\n  Bound result 49633249624629133922\n  Bound result 1960000002835717308\n  Bound result 2800000000000000000\n  Bound result 104\n  Bound result 157011382\n  Bound result 6489\n  Bound result 3841939590408188039\n  Bound result 1960000000000015312\n  Bound result 2800000000000000000\n  Bound result 100000000000000000972\n  Bound result 9500000000000000248\n  Bound result 2800000000000000000\n  Bound result 3\n  Bound result 2800000000000000000\n  Bound result 4197074683464965566\n  Bound result 2800000000000000000\n  Bound result 2800000000000000000\n  Bound result 2800000000000000000\n  Bound result 2800000000000000000\n  Bound result 100000000000000004517\n  Bound result 2800000000000000000\n  Bound result 3195\n\n[PASS] test_theHandlerActuallyActs() (gas: 2016072)\nLogs:\n  Bound result 10000000000000000000\n  Bound result 2800000000000000000\n  Bound result 86400\n  Bound result 45\n  Bound result 0\n  Bound result 1000000000000000000\n  Bound result 1\n  Bound result 1\n  Bound result 10000000000000000000\n  Bound result 100000000000000000990\n  Bound result 5000000000000000000\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 512.84ms (486.31ms CPU time)\n\nRan 21 test suites in 515.00ms (1.33s CPU time): 215 tests passed, 0 failed, 10 skipped (225 total tests)\n","passed":true},{"durationMs":143,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Cocoon.adopt(uint256)\",\"Cocoon.authorizeWorker((bytes32,address,uint256,bytes32,uint64,string))\",\"Cocoon.burn()\",\"Cocoon.burnPupate()\",\"Cocoon.claimDeveloper()\",\"Cocoon.depositTax()\",\"Cocoon.receive()\",\"Cocoon.revokeWorker(bytes32)\",\"Cocoon.setDeveloper(address)\",\"Cocoon.setOperator(address)\",\"Cocoon.setParams((uint16,uint16,uint16,uint32,uint16,uint16,uint16,uint16,uint128,uint128))\",\"Cocoon.settleSeat(uint256)\",\"Cocoon.skim()\",\"Cocoon.startAuction(address)\",\"Cocoon.startImdAuction()\",\"Cocoon.takeAuction(address)\",\"Cocoon.takeImdAuction()\",\"Cocoon.transferOwnership(address)\",\"Cocoon.unlockCallback(bytes)\",\"Cocoon.wire((address,address,uint24,int24,address))\",\"FloorFeed.report((bytes32,uint256,bytes32,uint8,bytes,uint256,uint64,uint64,bytes32,bytes32,uint16,uint16,uint16,uint64,uint64),bytes)\",\"FloorFeed.setAttester(address)\",\"FloorFeed.setMaxAge(uint64)\",\"FloorFeed.setQuestion(bytes32)\",\"FloorFeed.transferOwnership(address)\",\"PupateHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"PupateHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"PupateHook.flush()\",\"PupateHook.lowerTax(uint16)\",\"PupateHook.receive()\",\"PupateHook.transferOwnership(address)\",\"PupateHook.unlockCallback(bytes)\",\"PupateToken.approve(address,uint256)\",\"PupateToken.burn(uint256)\",\"PupateToken.burnFrom(address,uint256)\",\"PupateToken.transfer(address,uint256)\",\"PupateToken.transferFrom(address,address,uint256)\",\"PupateVesting.release()\",\"PupateVesting.setBeneficiary(address)\"],\"files\":{\".gitattributes\":1,\".gitignore\":6,\"LICENSE\":21,\"README.md\":111,\"brand/README.md\":20,\"brand/assets.html\":120,\"brand/banner.png\":1192,\"brand/favicon-32.png\":8,\"brand/favicon.png\":948,\"brand/key.mjs\":62,\"brand/logo.png\":2127,\"brand/mark-source.png\":3156,\"brand/mark.png\":568,\"brand/og.png\":1791,\"brand/render.mjs\":35,\"deploy/README.md\":76,\"deploy/keeper.env.example\":37,\"deploy/nginx-locations.conf\":40,\"deploy/nginx-site.conf\":10,\"deploy/setup.sh\":91,\"deploy/update.sh\":65,\"docs/REVIEW.md\":136,\"docs/dependencies.md\":20,\"docs/deployment.md\":175,\"docs/phase0-findings.md\":125,\"docs/superpowers/plans/2026-10-06-pupate-plan-1-foundation.md\":782,\"docs/superpowers/plans/2026-10-06-pupate-plan-2-token-and-hook.md\":2040,\"docs/superpowers/plans/2026-10-06-pupate-plan-3-cocoon.md\":154,\"docs/superpowers/specs/2026-10-06-pupate-design.md\":330,\"docs/superpowers/specs/2026-10-10-site-two-layers-design.md\":55,\"docs/v2-mining-proof-of-work.md\":139,\"foundry.toml\":33,\"keeper/.gitignore\":4,\"keeper/README.md\":235,\"keeper/addresses.sepolia.json\":22,\"keeper/bin/imd.mjs\":161,\"keeper/bin/keeper.mjs\":205,\"keeper/lib/abi.mjs\":221,\"keeper/lib/actions.mjs\":351,\"keeper/lib/chain.mjs\":161,\"keeper/lib/config.mjs\":211,\"keeper/lib/imd.mjs\":248,\"keeper/lib/listings.mjs\":222,\"keeper/lib/log.mjs\":60,\"keeper/lib/oracle.mjs\":334,\"keeper/lib/state.mjs\":282,\"keeper/lib/status.mjs\":253,\"keeper/lib/work.mjs\":117,\"keeper/package-lock.json\":258,\"keeper/package.json\":26,\"keeper/questions/floor.check.json\":11,\"keeper/questions/floor.question.txt\":1,\"keeper/questions/floor.quote.json\":30,\"keeper/questions/launch.check.json\":29,\"keeper/questions/launch.sepolia.check.json\":29,\"keeper/test/fork.mjs\":366,\"launch.json\":32,\"script/DeployPreLaunch.s.sol\":68,\"script/PostLaunch.s.sol\":68,\"script/RedeployCocoon.s.sol\":58,\"script/local/LaunchLocal.s.sol\":149,\"script/local/LocalLaunchFactory.sol\":87,\"script/local/README.md\":160,\"script/local/ReportLocal.s.sol\":57,\"script/local/common.mjs\":271,\"script/local/smoke.mjs\":281,\"script/local/up.mjs\":392,\"script/mainnet/README.md\":58,\"script/mainnet/launch.mjs\":843,\"script/sepolia/Mocks.s.sol\":28,\"script/sepolia/README.md\":35,\"script/sepolia/up.mjs\":407,\"site/.env.example\":8,\"site/.gitignore\":15,\"site/.vercelignore\":13,\"site/README.md\":40,\"site/app/apple-icon.png\":123,\"site/app/buy/page.tsx\":13,\"site/app/cocoon/page.tsx\":14,\"site/app/docs/control/page.tsx\":113,\"site/app/docs/launch/page.tsx\":92,\"site/app/docs/layout.tsx\":24,\"site/app/docs/mechanism/page.tsx\":143,\"site/app/docs/oracle/page.tsx\":102,\"site/app/docs/page.tsx\":112,\"site/app/docs/parameters/page.tsx\":97,\"site/app/docs/seats/page.tsx\":116,\"site/app/docs/steps/page.tsx\":124,\"site/app/emerge/page.tsx\":14,\"site/app/feed/page.tsx\":16,\"site/app/flow/page.tsx\":14,\"site/app/globals.css\":628,\"site/app/how/page.tsx\":13,\"site/app/icon.png\":948,\"site/app/inside/page.tsx\":13,\"site/app/layout.tsx\":66,\"site/app/mine/page.tsx\":13,\"site/app/not-found.tsx\":28,\"site/app/page.tsx\":10,\"site/app/record/page.tsx\":14,\"site/app/seat/page.tsx\":17,\"site/app/steps/page.tsx\":14,\"site/app/work/page.tsx\":14,\"site/components/Buy.tsx\":113,\"site/components/BuyPanel.tsx\":123,\"site/components/Chrysalis.tsx\":28,\"site/components/Cocoon.tsx\":80,\"site/components/ConnectWallet.tsx\":45,\"site/components/Contracts.tsx\":78,\"site/components/Emerge.tsx\":98,\"site/components/EmergenceCard.tsx\":197,\"site/components/Feed.tsx\":176,\"site/components/FieldLog.tsx\":33,\"site/components/Flash.tsx\":17,\"site/components/Flow.tsx\":94,\"site/components/Hero.tsx\":53,\"site/components/Home.tsx\":128,\"site/components/How.tsx\":149,\"site/components/Inside.tsx\":30,\"site/components/InsideBar.tsx\":66,\"site/components/KeeperStatus.tsx\":156,\"site/components/Launch.tsx\":57,\"site/components/LineChart.tsx\":160,\"site/components/Mine.tsx\":119,\"site/components/OracleProof.tsx\":163,\"site/components/Providers.tsx\":28,\"site/components/Record.tsx\":85,\"site/components/SeatSheet.tsx\":252,\"site/components/SeatWork.tsx\":94,\"site/components/Shell.tsx\":35,\"site/components/SimContext.tsx\":271,\"site/components/Stages.tsx\":42,\"site/components/Steps.tsx\":147,\"site/components/TopBar.tsx\":92,\"site/components/TryControls.tsx\":70,\"site/components/Web3Provider.tsx\":46,\"site/components/Work.tsx\":111,\"site/components/docs/Doc.tsx\":46,\"site/components/docs/DocsNav.tsx\":33,\"site/lib/abi.ts\":96,\"site/lib/addresses.ts\":90,\"site/lib/attestation.ts\":234,\"site/lib/docs.ts\":24,\"site/lib/live.ts\":289,\"site/lib/rainbowTheme.ts\":46,\"site/lib/seat.ts\":91,\"site/lib/sim.ts\":516,\"site/lib/swap.ts\":101,\"site/lib/work.ts\":85,\"site/next.config.ts\":37,\"site/package-lock.json\":7904,\"site/package.json\":27,\"site/public/cards/prototype-1376.svg\":34,\"site/public/og.png\":1791,\"site/public/skill.md\":184,\"site/scripts/dev.mjs\":15,\"site/scripts/fork-e2e.ts\":109,\"site/scripts/verify-attestation.ts\":32,\"site/tsconfig.json\":21,\"site/vercel.json\":37,\"src/Cocoon.sol\":725,\"src/FloorFeed.sol\":151,\"src/PupateHook.sol\":275,\"src/PupateToken.sol\":86,\"src/PupateVesting.sol\":63,\"src/cocoon/Decay.sol\":16,\"src/cocoon/SeatListing.sol\":122,\"src/cocoon/WorkerAuthorization.sol\":44,\"src/interfaces/IERC20Minimal.sol\":11,\"src/interfaces/IERC721Minimal.sol\":12,\"src/interfaces/ILaunchHook.sol\":11,\"src/interfaces/ITaxSink.sol\":8,\"src/oracle/OracleAttestation.sol\":75,\"test/FloorFeed.t.sol\":548,\"test/OracleAttestation.t.sol\":54,\"test/PoolSetup.t.sol\":67,\"test/PupateHook.invariant.t.sol\":44,\"test/PupateHook.t.sol\":678,\"test/PupateToken.t.sol\":102,\"test/PupateVesting.t.sol\":118,\"test/Scaffold.t.sol\":17,\"test/cocoon/Cocoon.invariant.t.sol\":120,\"test/cocoon/CocoonAuctions.t.sol\":174,\"test/cocoon/CocoonBurn.t.sol\":136,\"test/cocoon/CocoonCore.t.sol\":350,\"test/cocoon/CocoonMarket.t.sol\":336,\"test/cocoon/CocoonPairing.t.sol\":100,\"test/cocoon/CocoonReentrancy.t.sol\":188,\"test/cocoon/CocoonReview.t.sol\":378,\"test/cocoon/Decay.t.sol\":44,\"test/cocoon/SeatListing.t.sol\":104,\"test/cocoon/WorkerAuthorization.t.sol\":90,\"test/fork/CocoonSeaport.fork.t.sol\":465,\"test/fork/LaunchFactory.fork.t.sol\":239,\"test/mocks/MockERC20.sol\":53,\"test/mocks/MockERC721.sol\":82,\"test/mocks/MockSeaport.sol\":154,\"test/mocks/ReenteringToken.sol\":55,\"test/utils/CocoonFixture.sol\":118,\"test/utils/CocoonHandler.sol\":297,\"test/utils/HookFixture.sol\":95,\"test/utils/PoolRouter.sol\":104,\"test/utils/PoolSetup.sol\":95,\"test/utils/SinkRouter.sol\":23,\"test/utils/Trader.sol\":84},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"d1e40d07aeb185431c9acbc1f2f8e3b62c7c76dfed2dbdfc3ccd0a4bbb78cda5","verifiedTreeHash":"031476cb9114befbf7ef666b3560aba20e64f1d6","verifierVersion":"0.1.0+fb8278c5"}]}