{"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":"efc7b7b2-5cc9-4384-9064-194b3f210c21","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"c37f4c224027ed2406e1c2bbe225234336821745271e17f5e1ba2cdd762d80e8","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"fc33916b21ee42834d5343f90717855105f4f938f9b09b49efcfa36e6805dbd8","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"a11373e4b82f3c70db504a66b8e341bf9a469d08d140fa90f864596a6a47c893","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"7395ec84c7aece8a7eaee38f18331ccfd4004cc6a236ec026fc54803dffd2008","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"555e6251c8ffefed117ea9ea3782d36fd01f1385f190da3e0a871a0ef1061ec5","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"6f6c321289521435ceecc31cf837cdb833f160928227513178538af18746b690","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"587567a5ef71546b5973ceaf6ef1dbbd590f49503b5ac9a6973ed86b89584857","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"587567a5ef71546b5973ceaf6ef1dbbd590f49503b5ac9a6973ed86b89584857","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"82f854c705d20e0556e1ffe10c1ace1736c02b7c295507c873407a3fdf3656d1","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"e9d5dad0920bcca7ed63d55687b8eba63f7eb7fd1e89ed9273f6b3190066c7b7","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","state":"accepted"}],"objective":"Kiln: a Uniswap v4 hook for a new ETH/ZTO pool that charges a lower fee to wallets holding Pepeolithic NFTs, keeps the fee everyone else pays as a ZTO reserve, and uses that reserve to buy Pepeolithic pieces from anyone and sell them back. Two contracts, Launcher and Kiln. Deploy on Sepolia (chain id 11155111) as a REHEARSAL of the mainnet Kiln; only addresses differ. Nothing is upgradeable, pausable or ownable; no admin exists anywhere; the reserve can never be withdrawn, only paid out for pieces.\n\nADDRESSES (constants). The coin standing in for ZTO is Sepolia WETH 0xfFf9976782d46CC05630D1f6eBAb18b2324d6B14 (plain ERC-20, 18 decimals, write 18 as a constant; call it ZTO in the code). Pepeolithic (PEPEO, ERC-721, 737 ids) is the Sepolia rehearsal contract 0x0ce3157eac34eccdcff239738983976fabdefb2a. Uniswap v4 PoolManager on Sepolia 0xE03A1074c86CFeDd5C142C4F04F1a1536e203543. Native ETH is currency0 (address 0), ZTO currency1.\n\nCONSTRUCTORS take static words only (address, uint, bool, bytes32: the deployment manifest supports nothing else, no arrays, no int24), make no external calls and read nothing on-chain (the verifier deploys in an empty EVM). Launcher constructor args: zto, pepeo, poolManager (three addresses, nothing else). EVERY OTHER NUMBER IS A CODE CONSTANT: tickSpacing 60 (int24 constant), lpFee 2000 (0.20%, static pool fee), the pass tiers minPepes 0 / 1 / 4 / 21 with kilnCut 13000 / 8000 / 3000 / 0 in hundredths of a bip (1.30%, 0.80%, 0.30%, 0%), spreadBps 1500 (15%), depth 50. The Kiln is created by the Launcher with CREATE2 and takes (zto, pepeo, poolManager) too; it must NOT validate its own address bits in its constructor; the Launcher checks them after CREATE2. The Launcher exposes initCodeHash() (view) so the salt can be mined off-chain.\n\nLAUNCHER. One permissionless function open(bytes32 salt, uint160 sqrtPriceX96) that succeeds once: (1) deploys the Kiln with CREATE2 and reverts unless its address carries exactly the permission bits for beforeSwap, afterSwap, beforeSwapReturnDelta and afterSwapReturnDelta and no others; (2) initializes the ETH/ZTO pool on the PoolManager with lpFee, tickSpacing and the Kiln as hook at sqrtPriceX96; emits Opened(kiln, poolId). No liquidity is added by the Launcher: the deployer adds a ZTO-only range position later through the normal PositionManager, so the Kiln must not restrict liquidity in any way (no liquidity callbacks).\n\nKILN, FEE PASS. On every swap in its pool the Kiln reads pepes = PEPEO.balanceOf(tx.origin) (routers are msg.sender; tx.origin is the trader) and picks the highest tier whose minPepes <= pepes. The pool's static lpFee goes to liquidity as usual; on top, the Kiln takes kilnCut of the swap as its cut, ALWAYS IN ZTO: when ZTO is the input, from the input (beforeSwap return delta on the specified currency for exact-input, afterSwap on the unspecified for exact-output); when ETH is the input, from the ZTO output (afterSwap return delta for exact-input, beforeSwap for exact-output). Work out each of the four cases so the trader is charged kilnCut of the ZTO side and the pool's accounting settles. The cut is NOT taken as real tokens inside the swap (the trader's input is settled by the router only after the swap, so poolManager.take would revert on an empty manager): the hook settles its return delta by MINTING ERC-6909 claim tokens for ZTO to itself (poolManager.mint(address(this), zto, amount)) and adds the amount to `claims`. collect(): permissionless, burns the Kiln's whole ZTO claim balance and takes real ZTO out of the PoolManager (poolManager.unlock -> burn + take, or the equivalent), moving the amount from `claims` into `reserve`; sell() and buy() call collect() first. Tier 21 pays no cut at all. Emit Passed(trader, pepes, kilnCut, ztoTaken) per swap and Collected(amount) per collect(). No block-held guard; README states that a pass only needs to be in the wallet during the swap.\n\nKILN, PIECES. State: claims (ZTO cut still held as ERC-6909 claims), reserve (real ZTO held for pieces; grows by collect(), seeds and sales of pieces; shrinks only by buying pieces; reserve <= ZTO.balanceOf(Kiln) always), inventory (ids held). Views: claims(), bid() = reserve / depth (real ZTO only, so it is always payable); ask() = bid() * (10000 + spreadBps) / 10000; inventory(), reserve(), tierOf(address), poolKey(). sell(uint256 id): the caller's PEPEO piece is pulled with transferFrom (caller approves first), price = bid() before the transfer, reserve -= price, ZTO.transfer(caller, price) requiring the bool, emits Sold(id, seller, price); reverts if bid() is 0. buy(uint256 id): id must be in inventory; price = ask(); ZTO.transferFrom(caller, kiln, price) requiring the bool, reserve += price, piece sent to caller with transferFrom (never safeTransferFrom, no receiver callbacks), emits Bought(id, buyer, price). seed(uint256 amount): anyone adds ZTO to reserve by transferFrom, emits Seeded(from, amount). No other way moves ZTO or pieces. Pieces arriving by plain transfer without sell() are not inventory and are stuck; README says so. Because bid is reserve/depth it is always payable, falls geometrically as pieces come in and rises with every cut, seed and sale.\n\nTESTS against the real v4 PoolManager (vendor v4-core and v4-periphery test routers) with a mock ZTO and a mock ERC-721: open() once and only at an address with the right bits; a ZTO-only range position above the opening price added through the test liquidity router; swaps in all four cases (ETH in / ZTO in, exact in / exact out) for wallets holding 0, 1, 4 and 21 pieces, checking the ZTO cut equals kilnCut of the ZTO side within rounding, that tier 21 pays nothing, that the cut shows in claims() and after collect() in reserve() and the Kiln's real ZTO balance, that collect() with nothing to collect is a harmless no-op, and that the trader also paid lpFee; sell() pays bid and bid falls afterwards; buy() charges ask and the piece leaves inventory; buy of an id not held reverts; sell at zero reserve reverts; seed() grows bid; reserve never exceeds the Kiln's ZTO balance; nobody can withdraw. README with the rules, the tier table and the two caveats (tx.origin, stuck transfers). BUILD: solidity 0.8.26, optimizer + via-IR (via_ir = true, optimizer_runs = 1), custom errors only, no ReentrancyGuard (external token calls last), Kiln deployed code under 12,000 bytes. Slither: multiply before dividing; string.concat not encodePacked.","parentJobId":null,"planHash":"596d5373993515e76a9f29bf3176ece8113cf61b4064cd27650aef45bd87066f","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"efc7b7b2-5cc9-4384-9064-194b3f210c21","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1111-kiln-uniswap-v4-hook"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51317","feedbackHash":"29482d231e60a6c7cbbaaec5d3f7d112e334d30af48749d677e91e8a4e011e38","nodeKey":"audit_economics","submissionHash":"c37f4c224027ed2406e1c2bbe225234336821745271e17f5e1ba2cdd762d80e8","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50959","feedbackHash":"77bd80c5fdf4facbf7d024ed4098d49990695696f8ab2d01f7b9e4871df9f8f4","nodeKey":"audit_flow","submissionHash":"fc33916b21ee42834d5343f90717855105f4f938f9b09b49efcfa36e6805dbd8","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51232","feedbackHash":"818bff4a7c598711aa06759858bede549daf8d80d2a9e33c33d7a7be8fbd0180","nodeKey":"audit_judge","submissionHash":"a11373e4b82f3c70db504a66b8e341bf9a469d08d140fa90f864596a6a47c893","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52150","feedbackHash":"9c487776c7015ebd9efd4cb70c1ec085bcc453ed02baf30602ee7936dac20dae","nodeKey":"audit_judge","submissionHash":"6e96146d9d808a61f36eea6cc2d78957ed6a3605627926b85a861909270dc8ae","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51875","feedbackHash":"080a17c0a88c5b399ebd2d723fd1153322fd42e56ab2bef6f9d079ea42c1e159","nodeKey":"audit_math","submissionHash":"7395ec84c7aece8a7eaee38f18331ccfd4004cc6a236ec026fc54803dffd2008","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52082","feedbackHash":"0ad1c6b8e64f43c0bf45d19c9c2095fe5c19de07438450ea2c640f06983460c3","nodeKey":"audit_permissions","submissionHash":"555e6251c8ffefed117ea9ea3782d36fd01f1385f190da3e0a871a0ef1061ec5","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51878","feedbackHash":"86c20cae205a11f829252e4ec23a3abee1046dfb58819076124ce99502926883","nodeKey":"build_contract_project","submissionHash":"6f6c321289521435ceecc31cf837cdb833f160928227513178538af18746b690","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52012","feedbackHash":"9ba8a40d43faaf85d95b9108d0339094bb66a3d4277db4e6e3ccc7d868f82824","nodeKey":"build_contract_project","submissionHash":"093da9a1d73fe4b8576832b9f7fb2695dce728afd65b8bc49b45db3c8e826806","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51198","feedbackHash":"3001295ddd6c6e9b53c5dead503ce6abb8abaf8ab82537f4d3dc767122ae98ce","nodeKey":"manifest","submissionHash":"aa56b2069ad51e6c1b3e4b382c6c19253a2076f438ae1ed00eaec60170cdedd3","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51442","feedbackHash":"4ed0bec901f57f58ec0359879eb1bfdd67b4e93e009b50bbf266c748942bdf09","nodeKey":"manifest","submissionHash":"82f854c705d20e0556e1ffe10c1ace1736c02b7c295507c873407a3fdf3656d1","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51507","feedbackHash":"95591f5d6daf913b87127e2384e9f6c52e0317bd447a627dc6e6c0eca7ae9faa","nodeKey":"write_foundry_tests","submissionHash":"e9d5dad0920bcca7ed63d55687b8eba63f7eb7fd1e89ed9273f6b3190066c7b7","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"50904","feedbackHash":"ffe5df100dea49cdea957510d20b662650aba911195a4652a9a159e0a9d7c3b1","nodeKey":"write_foundry_tests","submissionHash":"deff1306057cbddf4be7f48b88a286adf3184a6f0243b039eed368f967d9d06e","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"0fc6b193751441083fbbc0a59751859d801dcc6ce7cca071e4e06ae830c082ff","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"ced4d21b7afdbd6af7bc0cc1023605f10d8ed8cef0adce9d9a10fed96643feb8","device":"8687b633e8d497b9","findings":[],"hash":"093da9a1d73fe4b8576832b9f7fb2695dce728afd65b8bc49b45db3c8e826806","nodeId":"55723e81-f63c-4b1e-aaca-7cb32195e03a","outcome":"completed","summary":"Implemented Launcher, Kiln, vendored dependencies, an offline salt miner, tests, and deployment documentation.\n\n- `forge build`, `forge test`, and `forge fmt --check` pass.\n- All 35 tests pass.\n- Kiln runtime: **5,906 bytes**, below 12,000.\n- No transactions broadcast.\n\nDocumented one necessary correction: with ETH as currency0, a ZTO-only range belongs **below** the opening tick; an above-price range holds ETH.","treeHash":"71993e88296c9ea81b27f1dc6f51542f71e26c2e","usage":{"cachedInputTokens":1320960,"inputTokens":81724,"model":"gpt-6-astra","outputTokens":29784,"runtime":"codex","turns":7,"wallClockMs":811976}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"077d2937780a81bc","findings":[{"citation":"resolved","description":"Asymmetry between the paired market functions. sell() reverts with ZeroBid when bid() == 0 (line 192), but buy() computes price = ask() and proceeds with no check. ask() = bid() * 11500 / 10000 and bid() = reserve / 50, so whenever reserve is 0..49 wei the ask is 0 and buy(id) transfers the piece to the caller for a zero-value ZTO transferFrom (WETH9 and the mock both return true for 0). Reachable: sell() itself can leave reserve at 49 wei (reserve 50 -> bid 1 -> reserve 49). At that point nobody can sell (ZeroBid) while anyone can empty the inventory for nothing. Impact is dust-level because reserve only falls geometrically and 737 ids cannot drive a meaningful reserve below 50 wei, but the guarantee 'buy() charges ask' is violated and the branch pair is not mirrored. Minimal fix that preserves the agreed design: in buy(), revert (e.g. ZeroBid) when price == 0, mirroring sell().","line":206,"path":"src/Kiln.sol","reproduction":"Mock setup (Launcher.open, trader owns id 7 and approved the Kiln). 1) seed(50): reserve = 50, bid() = 1. 2) trader: sell(7): receives 1 wei, reserve = 49, bid() = 0, ask() = 0. 3) attacker (no approval balance needed): buy(7): succeeds, attacker pays 0 ZTO and now owns id 7; reserve stays 49. 4) trader: sell(8) reverts ZeroBid. Expected: buy at a zero ask should revert like sell at a zero bid. Actual: piece leaves inventory for free. Verified by test/scratch/Review.t.sol::testBuyIsFreeWhenReserveBelowDepth (passes on current code, i.e. the free buy succeeds).","severity":"low","snippet":"        uint256 price = ask();","title":"buy() lacks the zero-price guard sell() has: once reserve < depth every inventory piece is free"},{"citation":"resolved","description":"Trust gap (economics x ordering). bid() and ask() are state-dependent quotes that any other caller can move before a pending sell()/buy() is mined: every sell() lowers bid by 2% of reserve, every buy() and seed() raises it, and collect() (called first inside sell/buy) folds in claims. The caller passes only an id, so the price they accept is unbounded in both directions. A seller who quoted 2 ETH-equivalent can receive 1.96 after one other sell lands first; a buyer who quoted 2.3 can pay more after a seed/buy lands first. No attacker profits from deliberately sandwiching (a seed costs more than it raises the bid; the spread blocks buy-then-sell round trips), so this is ordering risk rather than extraction, hence low. The brief fixes the signatures sell(uint256) and buy(uint256), so adding minPrice/maxPrice parameters is a scope decision; the alternative is to document that callers must wrap the call in a contract that checks bid()/ask() first.","line":191,"path":"src/Kiln.sol","reproduction":"seed(100 ether) -> bid() = 2 ether. Trader A owns id 5, trader B owns id 6, both approved. A submits sell(5) expecting 2 ether. B's sell(6) is mined first: B receives 2 ether, reserve = 98 ether, bid() = 1.96 ether. A's sell(5) then executes and A receives 1.96 ether (reserve 96.04). Expected: A can bound the acceptable price. Actual: A has no way to; the numbers match test/Pieces.t.sol::testSeedSellBuyAndEvents (bid 1.96 after one sell).","severity":"low","snippet":"        uint256 price = bid();","title":"sell()/buy() execute at whatever bid/ask holds at mining time: no minimum price, maximum price or deadline"},{"citation":"resolved","description":"Trust gap (access x economics). The tier is read from tx.origin's balance at swap time with no holding requirement, and the README accepts same-transaction borrowing. What the README does not say is that the Kiln itself supplies the loan: whenever inventory holds >= 21 pieces, a 0-piece wallet can buy 21 at ask, swap with kilnCut 0, and sell the 21 back at bid in one transaction. The round-trip cost is only the spread plus geometric drift (about 5.5% of reserve for 21 pieces), independent of the swap size, while the cut avoided is 1.3% of the swap's ZTO side. With reserve 1000 ZTO the rent is ~54.7 ZTO, so any swap above ~4,200 ZTO is cheaper to do rented, and the reserve (which funds the market) stops growing from exactly the traders who would feed it most. The rent paid does land in reserve as spread, so this is an economic design consequence, not a theft; reported so the author can decide whether a per-block or per-transaction guard (sell() refusing an id bought in the same block, or tier read at the start of the tx) is wanted. Any such guard changes the stated 'only during the swap' rule and must be a scope decision.","line":105,"path":"src/Kiln.sol","reproduction":"Liquidity 1000e18 at price 1. Inventory holds ids 0..20, reserve = 1000 ether. whale (0 pieces, tx.origin) does in one tx: buy(0..20) paying sum of asks; swap ZTO exact-in 10,000 ether (zeroForOne=false, amountSpecified=-10000e18); sell(0..20) receiving sum of bids. Measured in test/scratch/Review.t.sol::testWhaleRentsPassFromKilnInventory: rent cost 54,716,882,075,632,611,093 wei (~54.7 ZTO), claims() == 0 after the swap, versus 130 ZTO the same swap pays at tier 0. Expected per brief: a 0-piece trader pays 1.3%. Actual: pays ~0.55% and the Kiln provided the pass.","severity":"low","snippet":"            uint24 rate = tierOf(tx.origin);","title":"The Kiln's own inventory is a pass-rental venue: buy 21, swap cut-free, sell back; cost is bounded by the spread, so large swaps dodge the cut"},{"citation":"resolved","description":"Access control / trust assumption, documented in README step 4. Anyone can front-run the operator's open(salt, price) with their own mined salt and any price; the operator's call then reverts AlreadyOpened and the Kiln lives at a different address than planned. I verified this cannot strand the pool: with zero liquidity a 1 wei ETH exact-input swap (afterSwap unspecified branch, fee 0 on zero output) moves the price to any limit, and ZTO exact-output does the same upward, so the operator can re-price before adding liquidity. No value at risk; recorded so the judge has the trace. Fix is optional: none needed beyond the README's instruction to read Opened/kiln() before funding.","line":39,"path":"src/Launcher.sol","reproduction":"attacker: open(attackerSalt, MAX_SQRT_PRICE-1) succeeds; operator: open(plannedSalt, 2^96) reverts AlreadyOpened; slot0 price = MAX_SQRT_PRICE-1. Operator (0 pieces) swaps zeroForOne=true, amountSpecified=-1, sqrtPriceLimitX96=2^96 through the empty pool: price becomes 2^96, claims() == 0, delta 0/0. Verified by test/scratch/Review.t.sol::testOpenFrontRunPicksPriceButPriceIsMovableWhileEmpty.","severity":"info","snippet":"    function open(bytes32 salt, uint160 sqrtPriceX96) external returns (Kiln deployed) {","title":"open() is permissionless: the first caller fixes the Kiln address (salt) and the opening price; harmless because an empty pool's price is freely movable"},{"citation":"resolved","description":"Asymmetry between user classes and between branches. For ZTO exact-input and ETH exact-output, the cut is taken in beforeSwap on the requested amount and cannot be refunded in afterSwap, so a swap that stops at sqrtPriceLimitX96 reverts for tiers 0/1/4 while the identical swap from a 21-piece wallet (fee 0) partially fills; the unspecified cases (ETH exact-input, ZTO exact-output) partially fill for everyone. README line 41 documents this and routers normally use MIN/MAX limits with separate output checks, so the practical impact is that cut-paying users cannot use sqrtPriceLimitX96 as a partial-fill tool on two of the four paths. No funds at risk (claims are rolled back by the revert). Recorded for coverage; the alternative (charging on realized volume) is not expressible in v4 for the specified currency.","line":130,"path":"src/Kiln.sol","reproduction":"Liquidity 1000e18 at price 1. Tier-0 trader: swap zeroForOne=false, amountSpecified=-100e18, sqrtPriceLimitX96=getSqrtPriceAtTick(60) -> reverts PartialSpecifiedSwap. 21-piece wallet, same params -> succeeds with 0 < |amount1| < 100e18. Verified by test/scratch/Review.t.sol::testPriceLimitedSwapRevertsOnlyForCutPayers.","severity":"info","snippet":"            if (fee != 0 && int256(delta.amount1()) != params.amountSpecified + int256(fee)) {\n                revert PartialSpecifiedSwap();\n            }","title":"Price-limited (partial) swaps revert for cut-paying tiers on the ZTO-specified cases but fill for tier 21: documented asymmetry"},{"citation":"resolved","description":"Composability limit, documented in README (collect section) and the NatSpec at line 153. sell() and buy() call collect() first; with a nonzero claim balance collect() calls poolManager.unlock, which reverts AlreadyUnlocked when called from inside a router's unlockCallback. Any router that swaps in the Kiln's pool and then buys/sells a piece in the same unlock always fails, because the swap itself mints claims. Not an access or funds issue; only atomic swap-then-trade flows are excluded.","line":159,"path":"src/Kiln.sol","reproduction":"Contract R implements unlockCallback: inside it, swap(key, zeroForOne=false, -1e18) (mints claims) then kiln.buy(id). R.unlock(...) reverts with PoolManager.AlreadyUnlocked from kiln.collect(). Expected by a composer: buy executes; actual: whole unlock reverts. Outside an unlock the same two calls succeed sequentially (test/Swaps.t.sol::testSellAndBuyCollectBeforeQuoting).","severity":"info","snippet":"        poolManager.unlock(\"\");","title":"sell()/buy() cannot be composed inside any PoolManager unlock while claims > 0 (collect() nests unlock)"}],"hash":"555e6251c8ffefed117ea9ea3782d36fd01f1385f190da3e0a871a0ef1061ec5","nodeId":"50e02b36-74b9-4ef7-8ee5-5b85ae8d7767","outcome":"completed","summary":"The findings file is valid, every snippet matches its cited line, and the only files touched are the findings file and my scratch test (both are permitted paths).\n\n**Review complete. Findings are in `.imd-findings.json` at the repository root.**\n\n**What I reviewed.** Access Control, Trust Gap and Asymmetry passes over both contracts, following the Pashov mindset and guides. I traced all eight ABI entry points plus four invariants. The build is clean, all 35 existing tests pass, and the Kiln runtime is about 5.9 kB. I also read the vendored PoolManager and Hooks library to confirm the four cut cases settle as claimed.\n\n**No critical, high or medium defects.** The permission model is sound. Every hook callback is guarded by `onlyPoolManager` plus an exact pool-key check. The unlock callback can only be reached through the Kiln's own collect() call. No approvals held by the Kiln can be spent by a third party. There is no admin, withdraw, pause or upgrade path.\n\n**Six findings, three low and three info, each with a concrete reproduction:**\n\n1. **Low.** buy() has no zero-price guard mirroring sell()'s ZeroBid. When reserve is below 50 wei, every inventory piece is free. Dust impact, but the branch pair is not mirrored.\n2. **Low.** sell() and buy() have no price bounds. A caller accepts whatever bid or ask is live when the transaction is mined. Ordering risk, not extraction. The brief fixes the signatures, so a fix is a scope decision.\n3. **Low.** The Kiln's own inventory lets a 0-piece whale rent a 21-piece pass in one transaction. Measured rent was about 54.7 ZTO against a 130 ZTO cut avoided on a 10,000 ZTO swap. Economic consequence of the documented tx.origin rule.\n4. **Info.** open() is front-runnable, as the README says. I verified an empty pool's price can be moved freely afterwards, so no harm results.\n5. **Info.** Price-limited swaps revert for cut-paying tiers on the two ZTO-specified cases but fill for tier 21. Documented in the README.\n6. **Info.** sell() and buy() cannot be composed inside another PoolManager unlock while claims are outstanding. Documented.\n\n**Verification.** Findings 1, 3, 4 and 5 are each demonstrated by a passing scratch test in `test/scratch/Review.t.sol`. Finding 2 uses the numbers from the existing Pieces test. Finding 6 follows from the PoolManager's AlreadyUnlocked check, which I read but did not run.\n\n**Not reached.** Nothing in my assigned area was left unreached. I did not look at the real Sepolia Pepeolithic contract's bytecode, so whether its transferFrom semantics match the mock remains a trust assumption. The static-analysis leads were all either false positives for this code or covered by existing tests.","treeHash":null,"usage":{"cachedInputTokens":933934,"inputTokens":290,"model":"claude-fable-5-1","outputTokens":35708,"runtime":"claude","turns":24,"wallClockMs":566072}},{"artifacts":[],"attempt":2,"bundleHash":null,"device":"433c37ef2c9c708d","findings":[],"hash":"684372f99399618b0abb3f372e4edcaaf0c4c3fe7fe47b1c1263894da0fc10bf","nodeId":"eddc980c-f336-40a9-aef7-16c89ae1aa84","outcome":"failed","summary":"wall-clock budget exhausted","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":"claude-fable-5-1","outputTokens":0,"runtime":"claude","turns":0,"wallClockMs":6918704}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"e764f15311426447","findings":[{"citation":"resolved","description":"Merged from audit_flow and audit_economics (same root cause). open() deploys the Kiln with CREATE2 and then calls poolManager.initialize for the key (ETH, ZTO, 2000, 60, kiln). The Kiln address carries only the four swap bits (0x00cc), so Hooks.isValidHookAddress passes on the bare address and neither beforeInitialize nor afterInitialize is called; v4 never checks that code exists at the hook address. Anyone who knows the salt (it is visible in the pending open() transaction, and script/MineSalt.s.sol plus the README tell the operator to take the first match from index 0 against the public Launcher address and initCodeHash(), so it is computable the moment the Launcher is deployed) can call PoolManager.initialize with the predicted Kiln address as hook, at any price. The operator's open(salt, price) then deploys the Kiln and reverts at line 47 with PoolAlreadyInitialized; the whole call rolls back, kiln() stays zero and that salt is burned for ever. The griefer repeats for every new salt at roughly 50-60k gas per attempt, so the one-shot permissionless launch is deniable indefinitely on a public mempool. No funds are at risk and the stranded pool is dead (its hook has no code), hence medium. Minimal fix preserving the design: in open(), read slot0 for key.toId() and only call initialize when sqrtPriceX96 is zero, emitting Opened either way (an empty pool's price is movable by a zero-liquidity swap, and README step 4 already requires the operator to verify the pool price before funding). Alternatively submit open() through a private relay and mine from an unpredictable starting index, and document the race in the handoff.","line":47,"path":"src/Launcher.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {Kiln} from \"src/Kiln.sol\";\nimport {Launcher} from \"src/Launcher.sol\";\nimport {PoolManager} from \"@uniswap/v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"@uniswap/v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"@uniswap/v4-core/src/types/Currency.sol\";\n\n/// A stranger can initialize the ETH/ZTO pool key for the predicted Kiln address before open()\n/// runs: the Kiln address carries no beforeInitialize bit and v4 never requires hook code at\n/// initialize. open(salt, ...) then reverts with PoolAlreadyInitialized for that salt, for ever.\n/// Expected: open(salt, price) succeeds and stores the Kiln. Actual: it reverts.\ncontract PreInitGriefTest is Test {\n    uint160 internal constant Q96 = 1 << 96;\n    // Constructors make no external calls and initialize never touches the tokens,\n    // so plain nonzero addresses stand in for ZTO and Pepeolithic.\n    address internal constant ZTO = address(0x1000);\n    address internal constant PEPEO = address(0x2000);\n\n    function _predict(address deployer, bytes32 salt, bytes32 hash) internal pure returns (address) {\n        return address(uint160(uint256(keccak256(bytes.concat(hex\"ff\", bytes20(deployer), salt, hash)))));\n    }\n\n    function testStrangerBlocksOpenForTheMinedSalt() public {\n        vm.chainId(11155111);\n        IPoolManager manager = IPoolManager(address(new PoolManager(address(this))));\n        Launcher launcher = new Launcher(ZTO, PEPEO, address(manager));\n\n        // The operator mines the first matching salt off-chain, exactly as script/MineSalt.s.sol does.\n        bytes32 hash = launcher.initCodeHash();\n        bytes32 salt;\n        address predicted;\n        for (uint256 i;; ++i) {\n            predicted = _predict(address(launcher), bytes32(i), hash);\n            if ((uint160(predicted) & 0x3fff) == 0xcc) {\n                salt = bytes32(i);\n                break;\n            }\n        }\n        assertEq(predicted.code.length, 0);\n\n        // A stranger who computed the same salt (or saw open() in the mempool) initializes the\n        // pool key first, with the codeless predicted hook address, at a price of their choice.\n        address stranger = makeAddr(\"stranger\");\n        PoolKey memory key = PoolKey(Currency.wrap(address(0)), Currency.wrap(ZTO), 2000, 60, IHooks(predicted));\n        vm.prank(stranger);\n        manager.initialize(key, 4295128740);\n\n        // The operator's open() for that salt should still succeed; on this code it reverts with\n        // PoolAlreadyInitialized and no Kiln can ever be opened at the mined address.\n        Kiln kiln = launcher.open(salt, Q96);\n        assertEq(address(kiln), predicted);\n        assertEq(address(launcher.kiln()), predicted);\n    }\n}","reproduction":"State: Launcher deployed, open() not yet called. 1) Compute salt = first i with ((keccak256(0xff ++ launcher ++ bytes32(i) ++ initCodeHash()) & 0x3fff) == 0xcc) exactly as MineSalt.find does, and predicted = that CREATE2 address (code.length == 0). 2) As any EOA call poolManager.initialize(PoolKey(Currency(0), Currency(zto), 2000, 60, IHooks(predicted)), 4295128740): succeeds. 3) Operator calls launcher.open(salt, 2**96). Expected: Kiln deployed at predicted, pool initialized, Opened emitted. Actual: revert PoolAlreadyInitialized(); launcher.kiln() == address(0); every later open(salt, *) with that salt reverts identically. Reproduced: both specialist proofs copied to test/scratch/ fail on this tree with [FAIL: PoolAlreadyInitialized()] (forge test --match-path 'test/scratch/Proof_*').","severity":"medium","snippet":"        poolManager.initialize(key, sqrtPriceX96);","title":"Launcher.open() can be blocked for any revealed or predictable salt by pre-initializing the pool key with the codeless predicted Kiln address"},{"citation":"resolved","description":"Merged from audit_math and audit_permissions. sell() reverts with ZeroBid when bid() == 0 (line 192) but buy() computes price = ask() and proceeds. ask() = floor(reserve/50 * 11500/10000) is 0 whenever reserve < 50 wei, and sell() itself reaches that state: reserve 50 -> bid 1 -> reserve 49. At that point nobody can sell (ZeroBid) while anyone can empty the inventory for a zero-value transferFrom (the mock and WETH9 both return true for 0). Impact is dust-level because the reserve only falls geometrically and 737 ids cannot drive a meaningful reserve below 50 wei, but the stated guarantee 'buy() charges ask' is violated and the function pair is not mirrored. Minimal fix: revert (e.g. ZeroBid) in buy() when price == 0.","line":206,"path":"src/Kiln.sol","reproduction":"Mock setup after Launcher.open; trader owns id 7 and approved the Kiln. 1) seed(50): reserve = 50, bid() = 1. 2) trader: sell(7) receives 1 wei; reserve = 49, bid() = 0, ask() = 0. 3) stranger with no ZTO and no allowance: buy(7) succeeds; nft.ownerOf(7) == stranger; reserve stays 49. Expected: buy at a zero ask reverts like sell at a zero bid. Actual: the piece leaves inventory for nothing. Reproduced by test/scratch/Judge.t.sol::testBuyFreeBelowDepth (passes, i.e. the free buy succeeds).","severity":"low","snippet":"        uint256 price = ask();","title":"buy() has no zero-price guard: once reserve falls below depth (50 wei) every inventory piece can be taken for 0 ZTO"},{"citation":"resolved","description":"From audit_economics, reproduced. bid() = reserve/50 and ask() = bid*1.15 are pure functions of the current reserve and buy()/sell() carry no caller price bound. Whenever inventory holds at least one piece, an observer who sees a seed() in the mempool (or a swap whose cut will be collected by the next sell/buy) buys a piece at ask = 0.023R, lets the inflow S land, and sells the same piece back at bid = (1.023R + S)/50. Net profit = S/50 - 0.00254R, positive whenever S > 12.7% of the current reserve, so roughly 2% of every large inflow is diverted from the reserve to the sandwicher. The rent lands in the reserve as spread, so this is a property of the mandated bid/ask formula rather than theft, but seeders are not warned (README: 'Seeds have no shares, rights or refund' says nothing about capture) and the operator's handoff step 6 recommends seeding without mentioning tranching. Mitigations within the spec: seed in tranches below 12.7% of the reserve or through a private relay, and document it; a quote that excludes same-block inflows or caller price bounds would need a scope decision.","line":174,"path":"src/Kiln.sol","reproduction":"State: seed(100 ether), trader sells id 5 at bid 2 ether -> reserve 98 ether, inventory [5]. Attacker holds 100 ZTO and approved the Kiln for ZTO and the NFT. 1) attacker buy(5): pays ask = 2.254 ether; reserve 100.254 ether. 2) victim seed(100 ether): reserve 200.254 ether. 3) attacker sell(5): receives bid = 4.00508 ether. Attacker balance goes from 100 to 101.75108 ether; reserve ends at 196.24892 ether instead of 198 ether. Expected: a seed of 100 raises the reserve by 100. Actual: 1.75108 ZTO of it is captured. Reproduced by test/scratch/Judge.t.sol::testSandwichSeed (logs: attacker paid 2254000000000000000, attacker balance after 101751080000000000000, reserve after 196248920000000000000).","severity":"low","snippet":"        return reserve / depth;","title":"Large reserve inflows (seed or a big collected cut) can be sandwiched through inventory: buy before, sell after, capturing ~2% of the inflow"},{"citation":"resolved","description":"Merged from audit_math (buy price diverges from quoted ask) and audit_permissions (no price bounds), same root cause: the caller passes only an id, so the accepted price is unbounded in both directions. Every sell() lowers bid by 2% of reserve, every buy()/seed() raises it, and buy() calls collect() first (line 203), which folds pending swap claims into reserve before price = ask() is read at line 206, so the on-chain price is strictly larger than the ask() a buyer quoted while claims were pending. A buyer who approved exactly the quoted ask is protected by the allowance revert; one with a larger allowance pays more silently. A seller receives less after another sell lands first. No attacker profits from sandwiching the no-bounds property alone (finding 3 covers the inflow case), so this is ordering risk. README documents that quotes can change and recommends an atomic wrapper with postconditions, and the brief fixes the signatures sell(uint256)/buy(uint256), so adding minPrice/maxPrice/deadline is a scope decision; at minimum the README should state the exact-allowance pattern as the buyer's protection.","line":191,"path":"src/Kiln.sol","reproduction":"State: seed(100 ether); trader sells id 5 -> reserve 98 ether; quoted ask() = 2.254 ether. A tier-0 wallet swaps 10 ZTO exact-input -> claims() = 0.13 ether (not in reserve). Buyer with unlimited allowance calls buy(5): collect() moves 0.13 ether into reserve first, price = 98.13e18/50*11500/10000 = 2.25699 ether. Expected (from the quote): 2.254 ether pulled. Actual: 2.25699 ether pulled. Reproduced by test/scratch/Judge.t.sol::testBuyPaysMoreThanQuotedAfterPendingClaims (logs: quoted 2254000000000000000, paid 2256990000000000000). Seller side: seed(100 ether); A and B each own a piece; B's sell lands first at 2 ether, A's sell then pays 1.96 ether (matches test/Pieces.t.sol::testSeedSellBuyAndEvents).","severity":"low","snippet":"        uint256 price = bid();","title":"sell()/buy() execute at whatever quote holds at mining time: no minimum/maximum price or deadline, and collect() inside buy() raises the price above the ask the buyer quoted"},{"citation":"resolved","description":"From audit_permissions, reproduced; downgraded to info because the brief forbids any block-held guard and the README already states that borrowing pieces within the transaction qualifies. Whenever inventory holds >= 21 pieces, buy(21 ids) at ask, swap with kilnCut 0, sell them back at bid. The round-trip cost is the spread plus geometric drift (about 5.5% of the reserve for 21 pieces) and is independent of swap size, while the cut avoided is 1.3% of the swap's ZTO side, so swaps larger than roughly 4.2x the reserve are cheaper rented. The rent lands in the reserve. Recorded so the author can decide whether this is acceptable; any guard (e.g. sell() refusing an id bought in the same block) changes the stated 'only during the swap' rule and is a scope decision.","line":105,"path":"src/Kiln.sol","reproduction":"Liquidity L=1e22 in [60,1200] plus the base ranges; a seller sells ids 100..120 into the Kiln; reserve = 654.26 ether. whale (0 pieces, tx.origin) in one transaction: buy(100..120), swap ZTO exact-in 300 ether (zeroForOne=false, amountSpecified=-300e18), sell(100..120). Measured: tierOf(whale) == 0 after the buys, claims() == 0 after the swap (a tier-0 wallet would have paid 3.9 ZTO), rent paid 35.798838130010256901 ZTO. Reproduced by test/scratch/Judge.t.sol::testWhaleRentsPass. At this reserve the rent exceeds the cut; break-even is swap size > ~4.2x reserve.","severity":"info","snippet":"            uint24 rate = tierOf(tx.origin);","title":"The Kiln's own inventory is a pass-rental venue: a 0-piece wallet can buy 21 pieces, swap cut-free and sell them back in one transaction; rent is bounded by the spread, not by the swap size"},{"citation":"resolved","description":"Merged from audit_math and audit_permissions, reproduced. sell() and buy() call collect() first; with a nonzero ERC-6909 ZTO balance collect() calls poolManager.unlock, which reverts AlreadyUnlocked when invoked from inside another contract's unlockCallback. Any router that swaps in the Kiln's pool and then buys/sells a piece in the same unlock always fails because the swap itself mints claims, and any dust swap by a third party keeps an integrator's nested flow reverting. Documented in the README (collect section) and NatSpec at line 153; no funds at risk; recorded for coverage.","line":159,"path":"src/Kiln.sol","reproduction":"Contract S implements IUnlockCallback and calls kiln.sell(7) inside its callback; S owns id 7 and set the Kiln as operator; seed(100 ether). Any wallet does a 1 ZTO exact-input swap -> claims() = 0.013 ether. S.run(7) -> manager.unlock -> S.unlockCallback -> kiln.sell -> collect -> poolManager.unlock reverts AlreadyUnlocked. After an external kiln.collect(), S.run(7) succeeds and id 7 is in inventory. Reproduced by test/scratch/Judge.t.sol::testNestedSellRevertsWithClaims.","severity":"info","snippet":"        poolManager.unlock(\"\");","title":"sell()/buy() revert inside any existing PoolManager unlock whenever claims are pending (collect() nests unlock)"},{"citation":"resolved","description":"Merged from audit_economics and audit_permissions, reproduced. By design anyone may call open(salt, price) first (README step 4); the operator's own open() then reverts AlreadyOpened and the Kiln lives at the stranger's address with the stranger's price. The pool is empty so the price is not sticky, but only certain swaps can move it: a ZTO exact-input (or ETH exact-output) from a wallet below tier 21 reverts with PartialSpecifiedSwap because the specified cut minted in beforeSwap cannot be matched by a zero fill, while ETH exact-input, ZTO exact-output, or any swap from a 21-piece wallet moves the price freely. The handoff only says to verify the price before funding liquidity; it should name the recovery path. No funds at risk.","line":39,"path":"src/Launcher.sol","reproduction":"Fresh Launcher l2; stranger calls l2.open(salt, MIN_SQRT_PRICE+1) -> succeeds, slot0 price = 4295128740. Operator's l2.open(salt, 2**96) reverts AlreadyOpened. Tier-0 wallet: swap zeroForOne=false, amountSpecified=-1e18, limit 2**96 -> reverts (PartialSpecifiedSwap). Same wallet: swap zeroForOne=false, amountSpecified=+1 (exact-output), limit 2**96 -> succeeds with zero deltas and slot0 price == 2**96. Reproduced by test/scratch/Judge.t.sol::testStrangerOpensAtExtremePriceAndRepricing.","severity":"info","snippet":"    function open(bytes32 salt, uint160 sqrtPriceX96) external returns (Kiln deployed) {","title":"open() is permissionless: the first caller fixes the Kiln address and the opening price; an extreme price is recoverable only through the unspecified-cut swap paths, which the handoff does not state"},{"citation":"resolved","description":"From audit_economics, verified live. The README calls the dependency 'the supplied ERC-721 collection of 737 pieces'. The contract at 0x0cE3157eAC34ECCdcFF239738983976faBdEFB2A reports name 'Ochre', MAX_SUPPLY 737 and totalSupply 7, with ownerOf(2) and ownerOf(3) reverting ERC721NonexistentToken; the two lowest ids are held by different wallets. No wallet can hold 4 or 21 pieces, so tierOf never returns 3000 or 0 against the live rehearsal contract. It is also a sale contract with an admin role (minting and metadata only, not transfers), which the 'no admin exists anywhere' statement does not cover. Code behaviour is unaffected; this is a rehearsal-scope note for the handoff.","line":13,"path":"README.md","reproduction":"cast call 0x0cE3157eAC34ECCdcFF239738983976faBdEFB2A 'name()(string)' --rpc-url https://ethereum-sepolia-rpc.publicnode.com -> \"Ochre\"; 'totalSupply()(uint256)' -> 7; 'MAX_SUPPLY()(uint256)' -> 737; 'ownerOf(uint256)(address)' 0 -> 0x7B8C742F2e1eEB3fB2C10d72967Fa6d4a22f0479; 1 -> 0x1846927b920FA2D41766ED4F88F1d10e640F1590; 2 and 3 -> revert ERC721NonexistentToken. Expected per README: a 737-piece collection on which all four tiers can be exercised. Actual: a handful of pieces exist (checked 2026-10-09).","severity":"info","snippet":"ZTO here is Sepolia WETH used as a plain ERC-20; Kiln never wraps or unwraps it. Its decimals are the constant 18. Pepeolithic is the supplied ERC-721 collection of 737 pieces. Constructors accept exactly those three addresses, make no external calls, and work with no dependency code present. Addresses are immutable; they are constructor arguments so local tests can substitute mocks. They are not validated against live Sepolia in this project. The deployment operator must verify the supplied dependencies before sending transactions.","title":"The Sepolia Pepeolithic rehearsal contract ('Ochre') has far fewer than 737 minted pieces, so the 4- and 21-piece tiers cannot be rehearsed on Sepolia today"}],"hash":"6e96146d9d808a61f36eea6cc2d78957ed6a3605627926b85a861909270dc8ae","nodeId":"99d52b65-f332-47c3-8ceb-04fe9f940086","outcome":"completed","summary":"Review complete. The findings file is at `.imd-findings.json` with 8 findings and 11 coverage rows. No source files were changed; only the ignored scratch directory and the findings file were written.\n\n**What I kept, after reproducing each against the code**\n\n- **Medium, merged from two specialists:** anyone can pre-initialize the ETH/ZTO pool key with the codeless predicted Kiln address, because the hook bits carry no initialize permission. The operator's `open()` then reverts with `PoolAlreadyInitialized` and that salt is burned. Both specialist proofs fail on this tree exactly as claimed, and one is attached as the proof.\n- **Low:** `buy()` lacks the zero-price guard that `sell()` has, so every inventory piece is free once the reserve drops below 50 wei. Reproduced with seed 50, one sale, then a free buy.\n- **Low:** large reserve inflows can be sandwiched through inventory. A 100 ZTO seed on a 98 ZTO reserve lost 1.75 ZTO to a buy-before, sell-after round trip.\n- **Low, merged from two specialists:** sell and buy execute at the mined-time quote with no bounds, and the collect inside `buy()` raises the price above the quoted ask.\n- **Info:** pass rental from the Kiln's own inventory, nested-unlock reverts, the permissionless first opener fixing the price with a narrow recovery path, and the live Sepolia Pepeolithic contract having only a handful of minted pieces. The last one I verified with live calls to Sepolia.\n\n**What I dropped or downgraded.** The pass-rental finding went from low to info because the brief explicitly forbids holding guards and the README documents same-transaction borrowing. The partial-fill asymmetry was not kept as a finding, since the suite tests it and the README documents it. All slither and aderyn lines were either false positives (ERC-721 interface flagged as ERC-20, intentionally permissionless `open()`, zero-initialized locals) or benign ordering notes.\n\n**My own pass found nothing new.** I traced all four swap cases against the vendored v4 hook accounting, the claim mint and burn flow, inventory swap-and-pop, and the reserve backing invariant. The full suite passes at 53 of 53 tests and the Kiln runtime is 5,906 bytes. All eight entry points have a coverage verdict, four of them pointing at a finding.","treeHash":null,"usage":{"cachedInputTokens":1215316,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":41142,"runtime":"claude","turns":23,"wallClockMs":2618997}},{"artifacts":[],"attempt":1,"bundleHash":"57ecf4613b8c864a28f3a1dccddd1768722cf26d4c923b12a2a134b7df9bf53b","device":"2a6239e0c1b8c774","findings":[],"hash":"6f6c321289521435ceecc31cf837cdb833f160928227513178538af18746b690","nodeId":"55723e81-f63c-4b1e-aaca-7cb32195e03a","outcome":"completed","summary":"The responses file is excluded by the harness's own `.git/info/exclude` rule, so it is collected from disk as intended. All work is complete.\n\n**Outcome.** The medium finding reproduced and is fixed. The proof test now passes, the full suite is 38 for 38, and `forge build` and `forge fmt --check` are clean.\n\n**Code changes (two small diffs, verified and accepted code otherwise untouched):**\n- `src/Launcher.sol`: `open()` now reads the pool's slot0 and calls `initialize` only when the price is still zero. A stranger who pre-initializes the key with the codeless predicted Kiln address no longer burns the salt. `Opened` is emitted either way and the one-shot guard is unchanged.\n- `src/Kiln.sol`: `buy()` reverts with `ZeroBid` when the ask is zero, mirroring `sell()`. The reviewer's free-buy below 50 wei reserve reproduced and is closed.\n\n**Tests added:**\n- `testPreInitializedPoolIsAdoptedAndRepriceable` in the Launcher suite: stranger initializes first at an extreme price, `open` adopts the pool at the predicted address, second `open` reverts, and a zero-piece wallet re-prices the empty pool in both directions with zero deltas and zero claims.\n- `testBuyAtZeroAskRevertsUntilReserveIsSeeded` in the Pieces suite.\n\n**README updates:** handoff step 4 documents the pre-initialization race, that the supplied price is ignored when the pool already exists, the obligation to read the pool price before funding, and the exact recovery swaps. New caveats cover inventory pass rental, missing price bounds with the exact-allowance protection, seed sandwiching with tranching advice, and the live Sepolia Pepeolithic contract having only 7 of 737 pieces minted, which I verified with `cast call` today.\n\n**Responses filed** for all eight findings in `.imd-responses.json`: the medium and the zero-ask low are fixed in code; the sandwich, price-bounds, pass-rental, extreme-price and Sepolia-supply findings are addressed by documentation since the brief mandates the formula, signatures, no-hold rule and permissionless open; the nested-unlock info is disputed as already-documented intended design.","treeHash":"fc15e245b9fbd3db09cfdfc9c6a410455016729b","usage":{"cachedInputTokens":799012,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":18869,"runtime":"claude","turns":29,"wallClockMs":408194}},{"artifacts":[],"attempt":3,"bundleHash":null,"device":"672c29f92850efe0","findings":[{"citation":"resolved","description":"Numerical-gap seam (view vs write). ask() is computed from `reserve`, which excludes the ZTO cut still held as ERC-6909 claims. buy() calls collect() first (line 203), which moves every pending claim into `reserve` before `price = ask()` is read on line 206. The write therefore uses a strictly larger base than the view a buyer quoted off-chain, and buy(uint256) accepts no maximum price or deadline. Any swap, seed() or buy() that lands between the quote and the buy (ordinary traffic, or a deliberate front-run) raises the amount pulled by transferFrom. A buyer who approved exactly the quoted ask is protected by the allowance revert; a buyer with a larger or unlimited allowance pays the higher price silently. The README acknowledges that quotes can change and recommends a wrapper with postconditions, so this is a documented design limitation rather than an accounting error; it is reported because the seller-side counterpart (sell pays at least the quoted bid) is favourable while the buyer side is unfavourable, and the spec-mandated signature gives the buyer no on-chain defence. Minimal fix that preserves the required buy(uint256) signature: none; a `buy(uint256 id, uint256 maxPrice)` overload or documenting the exact-allowance pattern as the protection is the available mitigation.","line":206,"path":"src/Kiln.sol","reproduction":"State: pool with liquidity, reserve = 100 ether after seed(100 ether); trader sells id 5 (reserve 98 ether). Quoted ask() = 98e18/50 * 11500/10000 = 2.254e18. Then a 1000 ZTO exact-input swap by a tier-0 wallet accrues claims = 13e18 (not in reserve). Buyer (unlimited allowance) calls buy(5): collect() first moves 13e18 into reserve (111e18), price = 111e18/50*11500/10000 = 2.553e18. Expected (from the quote): 2.254e18 pulled; actual: 2.553e18 pulled, 13.3% more. Verified in a scratch Foundry test (quoted 2254000000000000000, pending 13000000000000000000, paid 2553000000000000000).","severity":"low","snippet":"        uint256 price = ask();","title":"buy() price diverges from the quoted ask(): collect() inside buy() raises the price and the buyer has no maximum-price bound"},{"citation":"resolved","description":"Boundary x precision seam. sell() guards bid()==0 with ZeroBid, but buy() has no symmetric guard on ask()==0. A sale at reserve in [50, 99] wei pays 1 wei and can leave reserve at 49 wei, where bid() and ask() are both 0. The piece just sold is then in inventory and buy(id) succeeds with transferFrom(msg.sender, kiln, 0), transferring the piece for nothing. Only the seller who accepted a 1 wei bid is affected, and the README states a quote can round to zero below 50 wei, so this is informational.","line":174,"path":"src/Kiln.sol","reproduction":"seed(50); mint id 5 to trader; trader sell(5) -> price = 50/50 = 1, reserve = 49. Now bid() = 49/50 = 0, ask() = 0. A fresh address with no ZTO and no allowance calls buy(5): expected (by analogy with sell's ZeroBid) a revert; actual: succeeds, nft.ownerOf(5) == buyer, reserve stays 49. Verified in a scratch Foundry test.","severity":"info","snippet":"        return reserve / depth;","title":"ask() rounds to zero while inventory is non-empty, so buy() hands a piece out for 0 ZTO once reserve falls below 50 wei"},{"citation":"resolved","description":"Boundary at the PoolManager call. collect() calls poolManager.unlock() whenever the Kiln's ERC-6909 ZTO balance is nonzero. PoolManager.unlock reverts with AlreadyUnlocked when the manager is already unlocked, so a contract that calls sell() or buy() from within its own unlockCallback (e.g. swap-then-sell atomically) reverts whenever any swap has accrued an uncollected cut. Since any dust swap by anyone sets claims > 0, such an integrator can be kept reverting cheaply. Non-nested callers are unaffected and the README states that collection with outstanding claims must happen outside an unlocked manager, so this is informational.","line":159,"path":"src/Kiln.sol","reproduction":"Contract S implements IUnlockCallback and calls kiln.sell(7) inside its callback; S owns id 7 and approved the Kiln; reserve = 100 ether. Any wallet does a 1 ZTO exact-input swap (claims = 0.013e18). S.run(7) -> manager.unlock -> S.unlockCallback -> kiln.sell -> collect -> poolManager.unlock reverts AlreadyUnlocked; whole call reverts. After an external kiln.collect(), S.run(7) succeeds. Verified in a scratch Foundry test.","severity":"info","snippet":"        poolManager.unlock(\"\");","title":"sell()/buy() revert inside any existing PoolManager unlock whenever claims are pending (nested unlock)"}],"hash":"7395ec84c7aece8a7eaee38f18331ccfd4004cc6a236ec026fc54803dffd2008","nodeId":"eddc980c-f336-40a9-aef7-16c89ae1aa84","outcome":"completed","summary":"The review is complete. Findings and coverage are in `.imd-findings.json` at the repository root; no project files were changed, and the scratch tests were removed.\n\n**Outcome.** The swap math holds in all four cases. I traced the specified-side cut in beforeSwap and the unspecified-side cut in afterSwap against the v4 Hooks library delta handling, and the gross-up formula for exact-output keeps the cut within 1 wei of the rate times the gross ZTO side. The existing suite, including the fuzz test, passes. Kiln runtime measured 5,906 bytes.\n\n**Findings written (three, none above low):**\n- **Low.** The price `buy()` pulls can exceed the quoted `ask()`, because `collect()` runs first and `buy(uint256)` has no maximum price. Reproduced: quoted 2.254 ZTO, paid 2.553 ZTO after another wallet's swap accrued claims. The README documents this and recommends an exact allowance or a wrapper.\n- **Info.** Once reserve drops below 50 wei after a 1-wei sale, `ask()` is 0 and `buy()` hands the piece out for nothing. Only the seller who accepted the 1-wei bid is affected. Documented in the README.\n- **Info.** `sell()`/`buy()` revert from inside an existing PoolManager unlock whenever claims are pending, since nested `unlock` reverts. Documented in the README.\n\n**Coverage.** All eight entry points have rows: seven hold, `buy` points at finding 1. Three invariant rows are recorded as holding. One row is marked unreached: real Sepolia Pepeolithic and WETH behaviour, since no fork was used and the mocks are standard solmate tokens.\n\n**Static-analysis leads.** The ERC-721 interface warning, uninitialised locals, unused returns and reentrancy warnings were each checked and are not defects: the interface is correct for ERC-721, the locals default to zero intentionally, and state is written before external token calls.","treeHash":null,"usage":{"cachedInputTokens":939728,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":33518,"runtime":"claude","turns":29,"wallClockMs":561660}},{"artifacts":[],"attempt":1,"bundleHash":"5e65edbf8041134451e46fd607413820d3c8a0866919759e3a8082ca7bea10d8","device":"2b9b0095482c54e6","findings":[],"hash":"82f854c705d20e0556e1ffe10c1ace1736c02b7c295507c873407a3fdf3656d1","nodeId":"d01ed246-32d8-4d9c-9f10-970c3b8de17b","outcome":"completed","summary":"The manifest is verified and needed only one small revision.\n\n**What I checked.** The accepted source has a single deployable, Launcher, whose constructor takes zto, pepeo and poolManager in that order, makes no external calls and reads nothing on-chain. Kiln is created later by the permissionless open() call, so it is correctly absent from the manifest. The existing launch.json already named Launcher with those three Sepolia addresses, used only the keys kind, contracts and notes, and kept notes at about 1,100 characters.\n\n**What I changed.** The Pepeolithic address was written all-lowercase, which solc rejected as a bad checksum when I embedded it in a scratch test. I replaced it with its EIP-55 checksum form, which is the same address, so a strict address validator cannot reject the manifest. No other line changed.\n\n**Verification.**\n- A scratch test deployed Launcher via CREATE2 with the exact manifest arguments on chain id 11155111 in an empty EVM, confirmed the stored addresses, and scanned the runtime for the forbidden opcodes the protected probe checks. It passed.\n- Launcher runtime is 7,988 bytes and Kiln is 5,927 bytes, both under the EIP-170 limit and Kiln under the 12,000 byte brief limit.\n- The full suite passed: 38 tests, 0 failures.\n- Git shows launch.json as the only modified tracked file. The scratch test lives in test/scratch and is discarded before submission.","treeHash":"de02759f5c84c5db548e6c0cfada896b356ce41c","usage":{"cachedInputTokens":209832,"inputTokens":194,"model":"claude-fable-5-1","outputTokens":4230,"runtime":"claude","turns":13,"wallClockMs":118957}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"a18a0c6087e1362f","findings":[],"hash":"a11373e4b82f3c70db504a66b8e341bf9a469d08d140fa90f864596a6a47c893","nodeId":"99d52b65-f332-47c3-8ceb-04fe9f940086","outcome":"completed","summary":"The findings file parses as valid JSON with zero findings and 18 coverage rows, and the tree holds no stray files.\n\n**Outcome: every finding from the previous round is settled and no new defect reproduced.** The findings file at the repository root reports an empty list with a full coverage record.\n\n**Settled as fixed, verified by running the proofs and tests:**\n- The medium finding about open() being blockable by pre-initializing the pool key is fixed. Launcher.open now reads slot0 and only calls initialize when the price is zero. All three earlier proof files pass on this tree under test/scratch, and the author's new test re-prices an adopted empty pool from a zero-piece wallet in both directions.\n- The low finding about buy() handing out pieces for free below 50 wei reserve is fixed. buy() now reverts with ZeroBid at a zero ask, which coincides exactly with a zero bid.\n\n**Settled as documented within the brief's constraints:** the seed sandwich, the missing price bounds, the inventory pass rental, the permissionless opening price recovery, and the Sepolia collection having only 7 minted pieces. Each now has a README caveat or handoff step, and the brief fixes the signatures and formulas involved, so no code change was owed.\n\n**Dropped:** the nested-unlock composability note. The author disputed it as documented design, and I agree there is no fund risk and the README states the limitation.\n\n**New-code review of the changed lines found nothing.** open() has no remaining third-party revert path, since nobody else can place code at the CREATE2 address and getSlot0 is a view. The full suite passes with 61 tests, and the Kiln runtime is 5,927 bytes against the 12,000-byte limit. The static-analysis leads were all benign on inspection.","treeHash":null,"usage":{"cachedInputTokens":577077,"inputTokens":226,"model":"claude-fable-5-1","outputTokens":8029,"runtime":"claude","turns":16,"wallClockMs":238763}},{"artifacts":[],"attempt":1,"bundleHash":"b24609f66fe0582c9e0886677f363b2ec8535b3d671a6b6254144c26d411aeb7","device":"29ea8835baa3dfe1","findings":[],"hash":"aa56b2069ad51e6c1b3e4b382c6c19253a2076f438ae1ed00eaec60170cdedd3","nodeId":"d01ed246-32d8-4d9c-9f10-970c3b8de17b","outcome":"completed","summary":"Created `launch.json` with Launcher and the three supplied Sepolia addresses. Kiln is created later through `Launcher.open()`.\n\nManifest structure and constructor ABI checks passed. `forge build` succeeded with existing lint warnings; all 35 tests passed.\n\nOnly `launch.json` changed.","treeHash":"e681cd543bdfafbb63b96290328eb06edc106cb0","usage":{"cachedInputTokens":200448,"inputTokens":33516,"model":"gpt-6-astra","outputTokens":2868,"runtime":"codex","turns":3,"wallClockMs":172816}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"0476c44a80aa9574","findings":[{"citation":"resolved","description":"open() deploys the Kiln with CREATE2 and then calls poolManager.initialize for the key (ETH, ZTO, 2000, 60, kiln). Uniswap v4's initialize only checks the hook address bits (Hooks.isValidHookAddress is pure) and calls the hook only when the BEFORE_INITIALIZE/AFTER_INITIALIZE bits are set. The Kiln address carries 0x00cc (swap bits only), so anyone can call PoolManager.initialize with the *predicted* Kiln address as hook before the Kiln exists, at any price. After that, open(salt, price) for that salt always reverts with PoolAlreadyInitialized (the Kiln deployment is rolled back with it), so the mined address can never be opened. The salt is predictable: script/MineSalt.s.sol and the README instruct the operator to take the first matching index starting from 0 against the public Launcher address and initCodeHash(), so an attacker can compute it the moment the Launcher is deployed, or simply front-run the open() transaction in the mempool. Each repetition costs the attacker one initialize (~50k gas) and forces the operator to re-mine and resubmit; on a public mempool this can be repeated indefinitely. The pool left behind is dead (its hook has no code, every swap reverts in callHook), so nothing of value is created, but the launch of the only deployable application is blocked. Reserve and user funds are not at risk. Suggested fix: make open() tolerant of an already-initialized pool for the exact key (catch PoolAlreadyInitialized and continue, reading slot0 to confirm it is initialized), or mine the salt from a value the attacker cannot predict and submit open() through a private relay; at minimum document the race in the deployment handoff.","line":46,"path":"src/Launcher.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {Kiln} from \"src/Kiln.sol\";\nimport {Launcher} from \"src/Launcher.sol\";\nimport {PoolManager} from \"@uniswap/v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"@uniswap/v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"@uniswap/v4-core/src/types/Currency.sol\";\n\n/// A stranger can initialize the ETH/ZTO pool key for the predicted Kiln address before open()\n/// runs: the Kiln address carries no beforeInitialize bit and v4 never requires hook code at\n/// initialize. open(salt, ...) then reverts with PoolAlreadyInitialized for that salt, for ever.\n/// Expected: open(salt, price) succeeds and stores the Kiln. Actual: it reverts.\ncontract PreInitGriefTest is Test {\n    uint160 internal constant Q96 = 1 << 96;\n    // Constructors make no external calls and initialize never touches the tokens,\n    // so plain nonzero addresses stand in for ZTO and Pepeolithic.\n    address internal constant ZTO = address(0x1000);\n    address internal constant PEPEO = address(0x2000);\n\n    function _predict(address deployer, bytes32 salt, bytes32 hash) internal pure returns (address) {\n        return address(uint160(uint256(keccak256(bytes.concat(hex\"ff\", bytes20(deployer), salt, hash)))));\n    }\n\n    function testStrangerBlocksOpenForTheMinedSalt() public {\n        vm.chainId(11155111);\n        IPoolManager manager = IPoolManager(address(new PoolManager(address(this))));\n        Launcher launcher = new Launcher(ZTO, PEPEO, address(manager));\n\n        // The operator mines the first matching salt off-chain, exactly as script/MineSalt.s.sol does.\n        bytes32 hash = launcher.initCodeHash();\n        bytes32 salt;\n        address predicted;\n        for (uint256 i;; ++i) {\n            predicted = _predict(address(launcher), bytes32(i), hash);\n            if ((uint160(predicted) & 0x3fff) == 0xcc) {\n                salt = bytes32(i);\n                break;\n            }\n        }\n        assertEq(predicted.code.length, 0);\n\n        // A stranger who computed the same salt (or saw open() in the mempool) initializes the\n        // pool key first, with the codeless predicted hook address, at a price of their choice.\n        address stranger = makeAddr(\"stranger\");\n        PoolKey memory key = PoolKey(Currency.wrap(address(0)), Currency.wrap(ZTO), 2000, 60, IHooks(predicted));\n        vm.prank(stranger);\n        manager.initialize(key, 4295128740);\n\n        // The operator's open() for that salt should still succeed; on this code it reverts with\n        // PoolAlreadyInitialized and no Kiln can ever be opened at the mined address.\n        Kiln kiln = launcher.open(salt, Q96);\n        assertEq(address(kiln), predicted);\n        assertEq(address(launcher.kiln()), predicted);\n    }\n}","reproduction":"State: Launcher deployed, no Kiln yet. 1) Attacker computes salt = first i with ((keccak256(0xff ++ launcher ++ bytes32(i) ++ initCodeHash()) & 0x3fff) == 0xcc) exactly as MineSalt.find does, and predicted = that CREATE2 address (code.length == 0). 2) Attacker calls poolManager.initialize(PoolKey(Currency(0), Currency(zto), 2000, 60, IHooks(predicted)), 4295128740) -> succeeds (no beforeInitialize bit, no code check). 3) Operator calls launcher.open(salt, 2**96). Expected: Kiln deployed at predicted, pool initialized, Opened emitted. Actual: revert PoolAlreadyInitialized(); launcher.kiln() stays address(0); every later open(salt, *) with that salt reverts the same way. Run: forge test --match-path test/scratch/PreInitGrief.t.sol (fails on current code with PoolAlreadyInitialized).","severity":"medium","snippet":"        PoolKey memory key = deployed.poolKey();\n        poolManager.initialize(key, sqrtPriceX96);","title":"Launcher.open() can be bricked for any predictable salt by pre-initializing the pool key with the codeless predicted Kiln address"},{"citation":"resolved","description":"bid() = reserve/50 and ask() = bid*1.15 are pure functions of the current reserve, and buy()/sell() carry no caller price bound (per spec). Whenever inventory holds at least one piece, an attacker who sees a seed() (or a swap whose kilnCut will be collected) can buy a piece at ask = 0.023*R, let the inflow S land, and sell the same piece back at bid = (1.023*R + S)/50. Net profit = S/50 - 0.00254*R, positive whenever S > 12.7% of the current reserve, i.e. roughly 2% of every large seed or cut is diverted from the reserve to the sandwicher instead of accruing to the pieces market. The same holds for a large swap: sell() and buy() call collect() first, so the back-run sell prices in the cut. This is a property of the mandated bid/ask formula rather than an implementation slip, but it means seeding in large tranches on a public mempool leaks value and the README's 'Seeds have no shares, rights or refund' note does not warn seeders that part of their seed can be captured by third parties. Mitigations within the spec: seed in small tranches (S < 0.127*R) or via a private relay; document the behaviour. A design-level mitigation (quote buy against a reserve that excludes same-block inflows, or a caller-supplied maxPrice/minPrice) would need a scope decision since the brief fixes the signatures and formulas.","line":173,"path":"src/Kiln.sol","reproduction":"State: reserve = 98 ZTO after one earlier sell (seed(100), then trader sells id 5 at bid 2 -> reserve 98, inventory [5]). Attacker holds 100 ZTO. 1) attacker: kiln.buy(5) pays ask = (98/50)*1.15 = 2.254 ZTO; reserve = 100.254. 2) victim: kiln.seed(100 ether); reserve = 200.254. 3) attacker: kiln.sell(5) receives bid = 200.254/50 = 4.00508 ZTO; reserve = 196.24892. Attacker net +1.75108 ZTO, reserve ends at 196.249 instead of the 198 it would hold without the sandwich (98 + 100). Expected: a seed of 100 raises the reserve by 100. Actual: 1.75 ZTO of it is captured by the sandwicher. Verified with test/scratch/Sandwich.t.sol (logs: attacker paid 2254000000000000000, net balance change 1751080000000000000, reserve after 196248920000000000000).","severity":"low","snippet":"    function bid() public view returns (uint256) {\n        return reserve / depth;\n    }","title":"Any large reserve inflow (seed or a big swap cut) can be sandwiched through inventory: buy before, sell after, pocketing ~2% of the inflow"},{"citation":"resolved","description":"open() is permissionless by spec and the README states anyone may call it first. A front-runner who reuses the operator's salt (visible in the mempool or predictable, see finding 1) can open the pool at an extreme price such as MIN_SQRT_PRICE+1 (tick -887272). At that price no ZTO-only range can exist (any usable range is above the current tick and needs ETH), so the documented 'ZTO-only position' step cannot be executed. Recovery is possible because the pool is empty: a zero-liquidity swap moves the price to any limit for free, but only through a path the Kiln accepts (ETH-in exact-input or ZTO-in exact-output, whose cut is taken in afterSwap on a realised amount of 0, or any swap from a 21-piece wallet); a ZTO-in exact-input from a wallet below tier 21 reverts with PartialSpecifiedSwap because the specified cut cannot be matched. None of this is in the deployment handoff, which only says to 'verify the pool price before funding liquidity'. No funds are at risk; this is an operational note for the handoff.","line":39,"path":"src/Launcher.sol","reproduction":"State: Launcher deployed, operator about to call open(salt, 2**96). 1) Stranger calls launcher.open(salt, 4295128740) first -> succeeds, Kiln deployed, pool at tick -887272. 2) Operator's open(salt, 2**96) reverts AlreadyOpened. 3) Operator tries to mint a ZTO-only range e.g. ticks [-1200, -60] through PositionManager with amount0Max = 0 -> reverts (the range is above the current tick and requires ETH). 4) Operator attempts to re-price with a tier-0 wallet via a ZTO exact-input swap of 1 wei with sqrtPriceLimit = 2**96 -> Kiln.afterSwap reverts PartialSpecifiedSwap (delta.amount1() = 0 != -1 + 0); an ETH-in exact-input of 1 wei with limit 2**96 succeeds and moves the price. Expected: the handoff documents the race and the recovery path.","severity":"info","snippet":"    function open(bytes32 salt, uint160 sqrtPriceX96) external returns (Kiln deployed) {","title":"Permissionless open() lets a stranger set the opening price; the README does not describe how the operator recovers a wrong price"},{"citation":"resolved","description":"Checked live at Sepolia block 11873750 via eth_call: 0x0cE3157eAC34ECCdcFF239738983976faBdEFB2A reports name() 'Ochre', symbol() 'OCHRE', MAX_SUPPLY() 737, totalSupply() 7, ownerOf(0) = 0x7B8C...0479 (which is also admin()), ownerOf(1) = 0x1846...1590, ownerOf(2..8) revert ERC721NonexistentToken. It is an OpenZeppelin v5 ERC-721 with an admin() role and primary-sale functions (buy, roundOpen, claimSeat, sweep, freeze, releaseUnclaimed); no pause or transfer-validator selectors are present. The Kiln's two transfer paths were exercised against it on a fork: sell(1) by the real holder (transferFrom into Kiln, 2 ZTO of real WETH paid out) and buy(1) by a contract buyer (transferFrom out of Kiln, no receiver callback) both succeed, and WETH transfer/transferFrom return true. So the dependency assumptions hold today. What the README overstates is the collection itself: the rehearsal has 2 transferable pieces, so a wallet cannot hold 4 or 21 of them and the 0.30% and 0% tiers cannot be rehearsed on Sepolia until the sale mints more. The admin of the sale contract is also an outside party that the 'no admin exists anywhere' statement does not cover (it controls minting and metadata, not transfers).","line":13,"path":"README.md","reproduction":"cast call 0x0cE3157eAC34ECCdcFF239738983976faBdEFB2A 'name()(string)' --rpc-url <sepolia> -> \"Ochre\"; 'MAX_SUPPLY()(uint256)' -> 737; 'totalSupply()(uint256)' -> 7; 'ownerOf(uint256)(address)' 2 -> revert ERC721NonexistentToken(2); 'balanceOf(address)(uint256)' of any current holder <= 1, so Kiln.tierOf(holder) returns 8000 and no address can return 3000 or 0 on the rehearsal. Expected per README: a 737-piece collection on which all four tiers can be exercised. Actual: 2 pieces exist.","severity":"info","snippet":"ZTO here is Sepolia WETH used as a plain ERC-20; Kiln never wraps or unwraps it. Its decimals are the constant 18. Pepeolithic is the supplied ERC-721 collection of 737 pieces. Constructors accept exactly those three addresses, make no external calls, and work with no dependency code present. Addresses are immutable; they are constructor arguments so local tests can substitute mocks. They are not validated against live Sepolia in this project. The deployment operator must verify the supplied dependencies before sending transactions.","title":"The Sepolia Pepeolithic rehearsal contract is a sale contract named Ochre with only ids 0 and 1 minted; tiers 4 and 21 are unreachable on the rehearsal until more pieces exist"}],"hash":"c37f4c224027ed2406e1c2bbe225234336821745271e17f5e1ba2cdd762d80e8","nodeId":"251e2f92-9e73-4b02-9b6f-dd10dae53f5e","outcome":"completed","summary":"The review is complete. The findings file is at the repository root, no tracked file was changed, and the only scratch material lives in the ignored test/scratch directory.\n\n**Outcome.** The Kiln's hook arithmetic, claim settlement, reserve accounting and piece market hold up under the Economic Security, Invariant and Flow Gap checklists. The existing 35 tests pass. I verified the hook delta sign and currency mapping against the vendored v4 code, and exercised both Kiln transfer paths against the real Sepolia Pepeolithic and WETH contracts on a fork. One medium finding concerns the launch itself, not the running protocol.\n\n**Findings written to .imd-findings.json**\n\n1. **Medium, with proof test.** Anyone can initialize the ETH/ZTO pool key with the predicted Kiln address before open() runs, because the address carries no initialize bits and v4 never checks for hook code. open() then reverts with PoolAlreadyInitialized for that salt for ever. The salt is predictable since the miner takes the first match from index 0 against public inputs. No funds are at risk, but the launch can be blocked repeatedly at the cost of one initialize per attempt. The proof test fails on the current code.\n2. **Low.** Because bid and ask are pure functions of the current reserve and buy and sell carry no price bound, a large seed or swap cut can be sandwiched through inventory. With a 98 ZTO reserve and a 100 ZTO seed, the attacker nets about 1.75 ZTO. Profit exists whenever the inflow exceeds roughly 12.7% of the reserve. This follows from the mandated formulas, so it is reported as a design property to document or mitigate operationally.\n3. **Info.** A front-runner can open the pool at an extreme price. Recovery via a zero-liquidity swap works only through the afterSwap paths or a 21-piece wallet, and the README does not describe it.\n4. **Info.** The Sepolia rehearsal collection is named Ochre, has only ids 0 and 1 minted, and carries an admin role for its sale. Tiers 4 and 21 cannot be rehearsed until more pieces exist. Transfers through the Kiln work against it today.\n\n**Coverage.** All eight entry points have a row. Five hold, three carry findings. Three invariant rows also hold: reserve never exceeds the Kiln's ZTO balance, claims are fully backed and equal the kilnCut share within one wei, and no withdrawal path exists.\n\n**Static-analysis leads** were all checked and none became findings: the ERC-20 interface warning is an ERC-721 interface, the uninitialized locals are deliberate zeros, ignored returns are harmless, reentrancy warnings are covered by effects-before-interactions with callback-free tokens, and the unprotected-initializer warning points at a view function and a permissionless open() that the brief requires.","treeHash":null,"usage":{"cachedInputTokens":2237541,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":55594,"runtime":"claude","turns":42,"wallClockMs":1008420}},{"artifacts":[],"attempt":1,"bundleHash":"b44eb9ea7a0ae7468c0330007275a62359261075593f3583882c6858c141b2f1","device":"c4f696e22e7a36f7","findings":[],"hash":"deff1306057cbddf4be7f48b88a286adf3184a6f0243b039eed368f967d9d06e","nodeId":"a9f02605-75c0-4ebf-ba62-1c2a34e0a58f","outcome":"completed","summary":"Added failure-path tests, arithmetic edge cases, and multi-wallet reserve/claims/inventory invariants under `test/`.\n\nVerified:\n- `forge build` passes.\n- `forge test`: 53 passed.\n- Invariants: 256 sequences, 24,576 calls, zero unexpected reverts.\n\nDocumented the ZTO-only range-direction mismatch. No new implementation defects found.","treeHash":"8123817a4ca0955c28ccd7bdab33cb39af84082e","usage":{"cachedInputTokens":1633920,"inputTokens":100714,"model":"gpt-6-astra","outputTokens":17823,"runtime":"codex","turns":7,"wallClockMs":504474}},{"artifacts":[],"attempt":1,"bundleHash":"ce291028294b0c6d1c73d220b5bccaaed7fd73d211e7fcaa1ff3589f89823756","device":"2dc755dfe7bd177c","findings":[],"hash":"e9d5dad0920bcca7ed63d55687b8eba63f7eb7fd1e89ed9273f6b3190066c7b7","nodeId":"a9f02605-75c0-4ebf-ba62-1c2a34e0a58f","outcome":"completed","summary":"The revision is complete. The full suite passes with 61 tests, up from 55, and `forge build` is clean.\n\n**What I found.** The brief listed no findings for this round, so I re-read the merged implementation adversarially, with attention to the implementer's newest changes: adopting a pool a stranger pre-initialized at the predicted Kiln address, and the zero-ask guard on `buy()`. I traced all four swap cases through v4's hook-delta accounting, the partial-fill guard, the claim mint and burn path, and the market functions. Nothing produced a concrete failing input, so I wrote no findings file. Two economic consequences the root README discloses (renting the Kiln's own inventory to dodge the cut, and sandwiching a visible seed) are real but documented design outcomes of the no-hold rule and reserve-only pricing, so I pinned them as tests rather than reporting them.\n\n**What I changed.** Only three files under `test/` were touched; everything accepted earlier is left as it was.\n\n- `test/Launcher.t.sol`: two tests for the adoption path. With stranger liquidity, `open()` succeeds, the supplied price is ignored, the pool trades at the stranger's price, and the Kiln still charges and collects its cut there. On an empty adopted pool, the two modes that mint a specified cut in `beforeSwap` revert with `PartialSpecifiedSwap` on the zero fill for 0, 1 and 4 piece wallets, leaving price and claims untouched, while a 21-piece wallet repriced the pool with zero deltas.\n- `test/AdversarialEdges.t.sol`: a sell of an unminted or out-of-collection id reverts without effects. The inventory-rent test shows a zero-piece wallet buys 21 pieces, swaps cut-free, sells them back, the rent lands in the reserve at about 5.5 percent of it regardless of swap size, and it only undercuts the cut for swaps of several reserves. The seed-sandwich property runs 1000 examples, asserting capture never exceeds 2 percent of the inflow, loses below about 12.7 percent of the reserve, and wins above 20 percent, with pinned cases on both sides of breakeven so the conditional branches are not vacuous.\n- `test/README.md`: describes the new coverage and corrects the earlier range-direction note. The brief's \"ZTO-only range above the opening price\" is the same position seen from ZTO's side, which in v4 tick terms sits below the opening tick.\n\n**Still owed outside this run.** Fork runs against the live Sepolia WETH, Pepeolithic and PoolManager remain untested here, as before, since the verifier has no network.","treeHash":"bff1d9787e9a000706fbcd316d84d2b6a18eaad6","usage":{"cachedInputTokens":1823075,"inputTokens":546,"model":"claude-fable-5-1","outputTokens":39473,"runtime":"claude","turns":33,"wallClockMs":606237}},{"artifacts":[],"attempt":3,"bundleHash":null,"device":"468e82a89b9bfe18","findings":[{"citation":"resolved","description":"open(salt, sqrtPriceX96) deploys the Kiln with CREATE2 and then calls poolManager.initialize with the key (ETH, ZTO, 2000, 60, kiln). The Kiln address is a pure function of public data: Launcher.initCodeHash() is a view and the salt is visible in the pending open() transaction. The Kiln carries no beforeInitialize/afterInitialize permission bits, so PoolManager.initialize accepts the key while no code exists at that address (Hooks.isValidHookAddress only checks the flag bits, and the initialize callbacks are skipped). An unprivileged observer who sees open() in the mempool calls poolManager.initialize with the exact same key at any price it likes. The operator transaction then deploys the Kiln and reverts at line 47 with PoolAlreadyInitialized; the whole call rolls back, the Launcher stays unopened and that salt is burned forever (retrying it reverts identically). The operator must mine and reveal a new salt, which the same observer front-runs again, so a griefer can hold the launch open indefinitely for roughly 60k gas per attempt on a public mempool. No funds are lost, but the one-shot permissionless launch the brief requires is deniable by any third party, and the Launcher offers no recovery path. The deeper cause is that the pool key only depends on public inputs and open() treats a pre-existing pool as fatal. Minimal fix preserving the design: in open(), read poolManager slot0 for key.toId() (StateLibrary.getSlot0) and only call initialize when sqrtPriceX96 is zero, emitting Opened either way. This is safe because an empty pool price is not sticky: a zero-delta swap through zero liquidity moves it to any limit (verified against the real PoolManager in scratch), the deployer adds liquidity with explicit tick bounds and slippage checks afterwards, and open() already accepts any caller-chosen price today.","line":47,"path":"src/Launcher.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {Kiln} from \"src/Kiln.sol\";\nimport {Launcher} from \"src/Launcher.sol\";\nimport {PoolManager} from \"@uniswap/v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"@uniswap/v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"@uniswap/v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"@uniswap/v4-core/src/types/PoolKey.sol\";\nimport {PoolId, PoolIdLibrary} from \"@uniswap/v4-core/src/types/PoolId.sol\";\nimport {Currency} from \"@uniswap/v4-core/src/types/Currency.sol\";\nimport {StateLibrary} from \"@uniswap/v4-core/src/libraries/StateLibrary.sol\";\nimport {ERC20} from \"solmate/src/tokens/ERC20.sol\";\nimport {ERC721} from \"solmate/src/tokens/ERC721.sol\";\n\ncontract ScratchZTO is ERC20 {\n    constructor() ERC20(\"ZTO\", \"ZTO\", 18) {}\n}\n\ncontract ScratchPepeo is ERC721 {\n    constructor() ERC721(\"Pepeolithic\", \"PEPEO\") {}\n\n    function tokenURI(uint256) public pure override returns (string memory) {\n        return \"\";\n    }\n}\n\n/// @notice Anyone who sees open(salt, price) in the mempool can initialize the pool keyed to the\n/// predicted Kiln address first (no code is needed there: the Kiln has no initialize permissions),\n/// after which Launcher.open reverts with PoolAlreadyInitialized for that salt. Repeating this for\n/// every salt the operator reveals blocks the launch indefinitely.\ncontract OpenFrontRunTest is Test {\n    using PoolIdLibrary for PoolKey;\n    using StateLibrary for IPoolManager;\n\n    uint160 internal constant Q96 = 1 << 96;\n\n    IPoolManager internal manager;\n    Launcher internal launcher;\n    ScratchZTO internal zto;\n    ScratchPepeo internal pepeo;\n    address internal attacker = makeAddr(\"attacker\");\n\n    function setUp() public {\n        vm.chainId(11155111);\n        zto = new ScratchZTO();\n        pepeo = new ScratchPepeo();\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        launcher = new Launcher(address(zto), address(pepeo), address(manager));\n    }\n\n    function _mine() internal view returns (bytes32 salt) {\n        bytes32 hash = launcher.initCodeHash();\n        for (uint256 i;; ++i) {\n            if ((uint160(_predict(bytes32(i), hash)) & 0x3fff) == 0xcc) return bytes32(i);\n        }\n    }\n\n    function _predict(bytes32 salt, bytes32 hash) internal view returns (address) {\n        return address(uint160(uint256(keccak256(bytes.concat(hex\"ff\", bytes20(address(launcher)), salt, hash)))));\n    }\n\n    function testOpenSurvivesPoolPreInitializedAtPredictedKiln() public {\n        bytes32 salt = _mine();\n        address predicted = _predict(salt, launcher.initCodeHash());\n        assertEq(predicted.code.length, 0);\n\n        // Front-runner: initialize the exact key Launcher.open will use, at a price of its choosing.\n        PoolKey memory key = PoolKey(Currency.wrap(address(0)), Currency.wrap(address(zto)), 2000, 60, IHooks(predicted));\n        vm.prank(attacker);\n        manager.initialize(key, Q96 * 2);\n        (uint160 price,,,) = manager.getSlot0(key.toId());\n        assertEq(price, Q96 * 2);\n\n        // Operator's transaction, as mined off-chain, must still open the Kiln.\n        // On the current code it reverts (PoolAlreadyInitialized) and the salt is burned.\n        Kiln kiln = launcher.open(salt, Q96);\n        assertEq(address(kiln), predicted);\n        assertEq(address(launcher.kiln()), predicted);\n        assertGt(predicted.code.length, 0);\n    }\n}","reproduction":"State: Launcher deployed, open() not yet called. 1) Compute predicted = CREATE2(launcher, salt, launcher.initCodeHash()) for the salt the operator mined (any salt with (addr & 0x3fff) == 0xcc). 2) As any EOA call poolManager.initialize(PoolKey(address(0), ZTO, 2000, 60, IHooks(predicted)), 2*2^96). It succeeds: no code at predicted, no initialize permission bits, isValidHookAddress true. 3) Operator calls launcher.open(salt, 2^96). Expected: Kiln deployed at predicted, pool opened, Opened emitted. Actual: reverts with PoolAlreadyInitialized() from PoolManager; launcher.kiln() stays address(0), predicted has no code, and every later open(salt, *) with that salt reverts the same way. Proof test test/scratch/OpenFrontRun.t.sol fails on the current tree with PoolAlreadyInitialized() and passes once open() tolerates an already-initialized pool (verified with a copy of Launcher carrying that change).","severity":"medium","snippet":"        poolManager.initialize(key, sqrtPriceX96);","title":"Launcher.open() can be blocked by anyone pre-initializing the pool at the predicted Kiln address"}],"hash":"fc33916b21ee42834d5343f90717855105f4f938f9b09b49efcfa36e6805dbd8","nodeId":"39280cdb-0be4-45f3-84be-59b5c2d90b0c","outcome":"completed","summary":"The review is complete. `.imd-findings.json` is written in the repository root with one finding, one proof test, and twelve coverage rows. No source files were changed; only `test/scratch/` holds my working tests, which the ignore rules exclude.\n\n**Finding (medium): Launcher.open() can be denied by anyone.** The pool key only depends on public data, since the salt is visible in the pending transaction and `initCodeHash()` is a view. The Kiln has no initialize permission bits, so PoolManager accepts the key while nothing is deployed at the predicted address. A front-runner calls `initialize` with that key first, and the operator's `open` then reverts with PoolAlreadyInitialized at `src/Launcher.sol:47`. That salt is burned, and each newly revealed salt can be blocked the same way for about 60k gas per attempt. No funds are at risk, but the one-shot launch has no recovery path. The proof test fails on the current code as claimed. I also verified against the real PoolManager that a minimal fix works: skip `initialize` when slot0 already holds a price, which is safe because an empty pool's price can be moved by anyone with a zero-delta swap, so the pre-set price is not sticky.\n\n**What held up in my area.** I traced all four swap orientations through the vendored Hooks library: the cut always lands on currency1, the mint offsets the hook delta exactly, the partial-fill check reads the pre-hook swap delta, and the gross-up formulas match the rate within one wei. The sell, buy, seed and collect paths update state before external calls, and the reserve-backing and claims-backing invariants hold on every path. Nested-unlock reverts and partial-fill reverts are documented design choices, not defects, so I did not report them.\n\n**Periphery checks.** The vendored libraries differ from the local mirror only in formatter whitespace. On Sepolia, all three dependency addresses have code, and the Pepeolithic rehearsal contract answers `balanceOf` and reports ERC-721 support.\n\n**Static-analysis leads.** The ERC-20 interface warning is a false positive on an ERC-721 interface, the uninitialized locals are intentional zero defaults, and the reentrancy and unused-return lines all sit behind effects-before-interactions ordering or atomic reverts. None reproduced as a defect.","treeHash":null,"usage":{"cachedInputTokens":1531163,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":38512,"runtime":"claude","turns":34,"wallClockMs":730036}}],"verification":[{"checks":[{"durationMs":31094,"exitCode":0,"name":"build","output":"Compiling 98 files with Solc 0.8.26\nSolc 0.8.26 finished in 30.91s\nCompiler run successful!\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n   ╭▸ src/Launcher.sol:48:9\n   │\n48 │         emit Opened(address(deployed), key.toId());\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/Launcher.sol:47:9\n   │\n47 │         poolManager.initialize(key, sqrtPriceX96);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/Kiln.sol:111:63\n    │\n111 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(fee.toInt128(), 0), 0);\n    │                                                               ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:106:30\n    │\n106 │             uint256 amount = uint256(exactInput ? -params.amountSpecified : params.amountSpecified);\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/Kiln.sol:142:44\n    │\n142 │         return (IHooks.afterSwap.selector, returned);\n    │                                            ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Kiln.sol:141:9\n    │\n141 │         emit Passed(tx.origin, pepes, rate, fee);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:126:30\n    │\n126 │             uint256 amount = uint256(exactInput ? -params.amountSpecified : params.amountSpecified);\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:130:81\n    │\n130 │             if (fee != 0 && int256(delta.amount1()) != params.amountSpecified + int256(fee)) {\n    │                                                                                 ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:135:30\n    │\n135 │             uint256 amount = uint256(ztoDelta < 0 ? -ztoDelta : ztoDelta);\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[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/Kiln.sol:155:13\n    │\n155 │         if (poolManager.balanceOf(address(this), uint256(uint160(address(zto)))) == 0) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/Kiln.sol:159:9\n    │\n159 │         poolManager.unlock(\"\");\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Kiln.sol:197:9\n    │\n197 │         emit Sold(id, msg.sender, price);\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/Kiln.sol:213:9\n    │\n213 │         emit Bought(id, msg.sender, price);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\n","passed":true},{"durationMs":500,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/ClaimDonation.t.sol:ClaimDonationTest\n[PASS] testCollectBurnsWholeClaimBalanceIncludingDonations() (gas: 649236)\n[PASS] testStrangerCannotTransferKilnsClaims() (gas: 653480)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 30.45ms (777.65µs CPU time)\n\nRan 10 tests for test/Launcher.t.sol:LauncherTest\n[PASS] testApplicationBytecodeHasNoEscapeOpcodes() (gas: 2531370)\n[PASS] testBadBitsRollBackDeploymentAndAllowRetry() (gas: 13966305)\n[PASS] testCannotOpenTwice() (gas: 37182)\n[PASS] testConstructorsWorkWithoutDependencyCodeAndDoNotValidateKilnBits() (gas: 26197)\n[PASS] testFirstETHInputFromZTOOnlyRange() (gas: 623426)\n[PASS] testFirstZTOInputWorksWithNoZTOInManager() (gas: 622209)\n[PASS] testInvalidPriceRollsBackAndCanRetry() (gas: 13965521)\n[PASS] testOfflineMinerMatchesLauncher() (gas: 20173125)\n[PASS] testOpenHasExactFlagsKeyAndNoLiquidity() (gas: 50237)\n[PASS] testZTOOnlyRangeBelowOpeningTickAndETHOnlyAbove() (gas: 544121)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 67.54ms (91.11ms CPU time)\n\nRan 9 tests for test/Swaps.t.sol:SwapsTest\n[PASS] testAllFourCasesAtEveryTier() (gas: 13638109)\n[PASS] testFuzzGrossFeeAndBacking(bool,bool,uint96,uint8) (runs: 256, μ: 826869, ~: 601126)\n[PASS] testOnlyManagerAndOnlyOwnPool() (gas: 150401)\n[PASS] testPeripheryRouterAllFourCasesAndTiers() (gas: 11566294)\n[PASS] testRevertingCollectionRestoresClaimsAndReserve() (gas: 549907)\n[PASS] testSellAndBuyCollectBeforeQuoting() (gas: 930927)\n[PASS] testSpecifiedPartialFillsRevertWithoutRetainingClaims() (gas: 436961)\n[PASS] testTxOriginPassWorksImmediatelyAndRouterHoldingsDoNotCount() (gas: 2416003)\n[PASS] testUnspecifiedPartialFillChargesOnlyRealizedZTO() (gas: 264433)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 71.54ms (62.39ms CPU time)\n\nRan 14 tests for test/Pieces.t.sol:PiecesTest\n[PASS] testBuyWithoutAllowanceRollsBack() (gas: 453047)\n[PASS] testEmptyCollectIsNoOpAndEmits() (gas: 184919)\n[PASS] testFalseERC20BuyRollsBackInventoryAndReserve() (gas: 478348)\n[PASS] testFalseERC20SeedRollsBack() (gas: 139646)\n[PASS] testFalseERC20SellRollsBackNFTAndReserve() (gas: 415344)\n[PASS] testFuzzGeometricBidsAreAlwaysPayable(uint96,uint8) (runs: 256, μ: 3948779, ~: 2371296)\n[PASS] testFuzzMarketSequence(uint256) (runs: 256, μ: 12272894, ~: 12232168)\n[PASS] testInventorySwapAndPopIncludingIdZero() (gas: 1017258)\n[PASS] testNoWithdrawRescueOwnerPauseOrUpgradeEntryPoints() (gas: 455741)\n[PASS] testPlainNFTAndZTODonationsAreNotInventoryOrReserve() (gas: 363562)\n[PASS] testSeedSellBuyAndEvents() (gas: 577085)\n[PASS] testTierBoundaries() (gas: 1726068)\n[PASS] testUnapprovedOrSomeoneElsesPieceCannotBeSold() (gas: 455455)\n[PASS] testZeroReserveAndUnheldIdRevert() (gas: 160285)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 370.47ms (449.95ms CPU time)\n\nRan 4 test suites in 371.24ms (540.00ms CPU time): 35 tests passed, 0 failed, 0 skipped (35 total tests)\n","passed":true},{"durationMs":60,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Kiln.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"Kiln.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"Kiln.buy(uint256)\",\"Kiln.collect()\",\"Kiln.seed(uint256)\",\"Kiln.sell(uint256)\",\"Kiln.unlockCallback(bytes)\",\"Launcher.open(bytes32,uint160)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":8,\"DEPENDENCY_HASHES.json\":97,\"README.md\":96,\"foundry.toml\":14,\"remappings.txt\":4,\"script/MineSalt.s.sol\":21,\"src/Kiln.sol\":223,\"src/Launcher.sol\":50,\"test/ClaimDonation.t.sol\":60,\"test/KilnBase.sol\":115,\"test/Launcher.t.sol\":137,\"test/Pieces.t.sol\":248,\"test/Swaps.t.sol\":252,\"test/mocks/TestTokens.sol\":51},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1888,"exitCode":0,"name":"slither","output":"[medium/high] erc20-interface at src/Kiln.sol:18: IPepeolithic (src/Kiln.sol#16-19) has incorrect ERC20 function interface:IPepeolithic.transferFrom(address,address,uint256) (src/Kiln.sol#18)\n[medium/medium] uninitialized-local at src/Kiln.sol:103: Kiln.beforeSwap(address,PoolKey,SwapParams,bytes).fee (src/Kiln.sol#103) is a local variable never initialized\n[medium/medium] uninitialized-local at src/Kiln.sol:124: Kiln.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes).returned (src/Kiln.sol#124) is a local variable never initialized\n[medium/medium] unused-return at src/Kiln.sol:154: Kiln.collect() (src/Kiln.sol#154-160) ignores return value by poolManager.unlock() (src/Kiln.sol#159)\n[medium/medium] unused-return at src/Launcher.sol:39: Launcher.open(bytes32,uint160) (src/Launcher.sol#39-49) ignores return value by poolManager.initialize(key,sqrtPriceX96) (src/Launcher.sol#47)\n[low/medium] reentrancy-benign at src/Kiln.sol:189: Reentrancy in Kiln.sell(uint256) (src/Kiln.sol#189-200):\n[low/medium] reentrancy-benign at src/Kiln.sol:202: Reentrancy in Kiln.buy(uint256) (src/Kiln.sol#202-216):\n[low/medium] reentrancy-events at src/Kiln.sol:189: Reentrancy in Kiln.sell(uint256) (src/Kiln.sol#189-200):\n[low/medium] reentrancy-events at src/Kiln.sol:114: Reentrancy in Kiln.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/Kiln.sol#114-143):\n[low/medium] reentrancy-events at src/Kiln.sol:202: Reentrancy in Kiln.buy(uint256) (src/Kiln.sol#202-216):\n[low/medium] reentrancy-events at src/Launcher.sol:39: Reentrancy in Launcher.open(bytes32,uint160) (src/Launcher.sol#39-49):","passed":true},{"durationMs":383,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/Kiln.sol:164: Reentrancy: State change after external call\n[high] unprotected-initializer at src/Launcher.sol:35: Unprotected initializer\n[low] large-numeric-literal at src/Kiln.sol:31: Large Numeric Literal (3 places)\n[low] literal-instead-of-constant at src/Kiln.sol:178: Literal Instead of Constant (2 places)\n[low] state-change-without-event at src/Kiln.sol:95: State Change Without Event\n[low] unchecked-return at src/Kiln.sol:159: Unchecked Return (2 places)\n[low] unsafe-erc20-operation at src/Kiln.sol:199: Unsafe ERC20 Operation (3 places)\n[low] unused-public-function at src/Kiln.sol:71: Public Function Not Used Internally","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"093da9a1d73fe4b8576832b9f7fb2695dce728afd65b8bc49b45db3c8e826806","verifiedTreeHash":"71993e88296c9ea81b27f1dc6f51542f71e26c2e","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":26991,"exitCode":0,"name":"build","output":"Compiling 98 files with Solc 0.8.26\nSolc 0.8.26 finished in 26.85s\nCompiler run successful!\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n   ╭▸ src/Launcher.sol:55:9\n   │\n55 │         emit Opened(address(deployed), poolId);\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/Launcher.sol:54:32\n   │\n54 │         if (currentPrice == 0) poolManager.initialize(key, sqrtPriceX96);\n   │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/Kiln.sol:111:63\n    │\n111 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(fee.toInt128(), 0), 0);\n    │                                                               ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:106:30\n    │\n106 │             uint256 amount = uint256(exactInput ? -params.amountSpecified : params.amountSpecified);\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/Kiln.sol:142:44\n    │\n142 │         return (IHooks.afterSwap.selector, returned);\n    │                                            ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Kiln.sol:141:9\n    │\n141 │         emit Passed(tx.origin, pepes, rate, fee);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:126:30\n    │\n126 │             uint256 amount = uint256(exactInput ? -params.amountSpecified : params.amountSpecified);\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:130:81\n    │\n130 │             if (fee != 0 && int256(delta.amount1()) != params.amountSpecified + int256(fee)) {\n    │                                                                                 ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:135:30\n    │\n135 │             uint256 amount = uint256(ztoDelta < 0 ? -ztoDelta : ztoDelta);\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[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/Kiln.sol:155:13\n    │\n155 │         if (poolManager.balanceOf(address(this), uint256(uint160(address(zto)))) == 0) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/Kiln.sol:159:9\n    │\n159 │         poolManager.unlock(\"\");\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Kiln.sol:197:9\n    │\n197 │         emit Sold(id, msg.sender, price);\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/Kiln.sol:215:9\n    │\n215 │         emit Bought(id, msg.sender, price);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\n","passed":true},{"durationMs":439,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/ClaimDonation.t.sol:ClaimDonationTest\n[PASS] testCollectBurnsWholeClaimBalanceIncludingDonations() (gas: 649224)\n[PASS] testStrangerCannotTransferKilnsClaims() (gas: 653456)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.89ms (562.54µs CPU time)\n\nRan 9 tests for test/Swaps.t.sol:SwapsTest\n[PASS] testAllFourCasesAtEveryTier() (gas: 13637917)\n[PASS] testFuzzGrossFeeAndBacking(bool,bool,uint96,uint8) (runs: 256, μ: 815764, ~: 601146)\n[PASS] testOnlyManagerAndOnlyOwnPool() (gas: 150353)\n[PASS] testPeripheryRouterAllFourCasesAndTiers() (gas: 11566102)\n[PASS] testRevertingCollectionRestoresClaimsAndReserve() (gas: 549895)\n[PASS] testSellAndBuyCollectBeforeQuoting() (gas: 930921)\n[PASS] testSpecifiedPartialFillsRevertWithoutRetainingClaims() (gas: 436937)\n[PASS] testTxOriginPassWorksImmediatelyAndRouterHoldingsDoNotCount() (gas: 2415979)\n[PASS] testUnspecifiedPartialFillChargesOnlyRealizedZTO() (gas: 264421)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 49.89ms (73.17ms CPU time)\n\nRan 11 tests for test/Launcher.t.sol:LauncherTest\n[PASS] testApplicationBytecodeHasNoEscapeOpcodes() (gas: 2569838)\n[PASS] testBadBitsRollBackDeploymentAndAllowRetry() (gas: 3827872)\n[PASS] testCannotOpenTwice() (gas: 37204)\n[PASS] testConstructorsWorkWithoutDependencyCodeAndDoNotValidateKilnBits() (gas: 26219)\n[PASS] testFirstETHInputFromZTOOnlyRange() (gas: 623539)\n[PASS] testFirstZTOInputWorksWithNoZTOInManager() (gas: 622296)\n[PASS] testInvalidPriceRollsBackAndCanRetry() (gas: 3831393)\n[PASS] testOfflineMinerMatchesLauncher() (gas: 463222)\n[PASS] testOpenHasExactFlagsKeyAndNoLiquidity() (gas: 50448)\n[PASS] testPreInitializedPoolIsAdoptedAndRepriceable() (gas: 3199487)\n[PASS] testZTOOnlyRangeBelowOpeningTickAndETHOnlyAbove() (gas: 544107)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 318.21ms (29.42ms CPU time)\n\nRan 15 tests for test/Pieces.t.sol:PiecesTest\n[PASS] testBuyAtZeroAskRevertsUntilReserveIsSeeded() (gas: 678633)\n[PASS] testBuyWithoutAllowanceRollsBack() (gas: 453054)\n[PASS] testEmptyCollectIsNoOpAndEmits() (gas: 184941)\n[PASS] testFalseERC20BuyRollsBackInventoryAndReserve() (gas: 478367)\n[PASS] testFalseERC20SeedRollsBack() (gas: 139646)\n[PASS] testFalseERC20SellRollsBackNFTAndReserve() (gas: 415344)\n[PASS] testFuzzGeometricBidsAreAlwaysPayable(uint96,uint8) (runs: 256, μ: 3252989, ~: 1621362)\n[PASS] testFuzzMarketSequence(uint256) (runs: 256, μ: 12095037, ~: 12110077)\n[PASS] testInventorySwapAndPopIncludingIdZero() (gas: 1017317)\n[PASS] testNoWithdrawRescueOwnerPauseOrUpgradeEntryPoints() (gas: 455741)\n[PASS] testPlainNFTAndZTODonationsAreNotInventoryOrReserve() (gas: 363537)\n[PASS] testSeedSellBuyAndEvents() (gas: 577103)\n[PASS] testTierBoundaries() (gas: 1726068)\n[PASS] testUnapprovedOrSomeoneElsesPieceCannotBeSold() (gas: 455442)\n[PASS] testZeroReserveAndUnheldIdRevert() (gas: 160285)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 337.75ms (437.77ms CPU time)\n\nRan 4 test suites in 340.32ms (708.73ms CPU time): 37 tests passed, 0 failed, 0 skipped (37 total tests)\n","passed":true},{"durationMs":67,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Kiln.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"Kiln.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"Kiln.buy(uint256)\",\"Kiln.collect()\",\"Kiln.seed(uint256)\",\"Kiln.sell(uint256)\",\"Kiln.unlockCallback(bytes)\",\"Launcher.open(bytes32,uint160)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":8,\"DEPENDENCY_HASHES.json\":97,\"README.md\":99,\"foundry.toml\":14,\"remappings.txt\":4,\"script/MineSalt.s.sol\":21,\"src/Kiln.sol\":225,\"src/Launcher.sol\":57,\"test/ClaimDonation.t.sol\":60,\"test/KilnBase.sol\":115,\"test/Launcher.t.sol\":179,\"test/Pieces.t.sol\":274,\"test/Swaps.t.sol\":252,\"test/mocks/TestTokens.sol\":51},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1819,"exitCode":0,"name":"slither","output":"[medium/high] erc20-interface at src/Kiln.sol:18: IPepeolithic (src/Kiln.sol#16-19) has incorrect ERC20 function interface:IPepeolithic.transferFrom(address,address,uint256) (src/Kiln.sol#18)\n[medium/medium] uninitialized-local at src/Kiln.sol:103: Kiln.beforeSwap(address,PoolKey,SwapParams,bytes).fee (src/Kiln.sol#103) is a local variable never initialized\n[medium/medium] uninitialized-local at src/Kiln.sol:124: Kiln.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes).returned (src/Kiln.sol#124) is a local variable never initialized\n[medium/medium] unused-return at src/Launcher.sol:41: Launcher.open(bytes32,uint160) (src/Launcher.sol#41-56) ignores return value by poolManager.initialize(key,sqrtPriceX96) (src/Launcher.sol#54)\n[medium/medium] unused-return at src/Launcher.sol:41: Launcher.open(bytes32,uint160) (src/Launcher.sol#41-56) ignores return value by (currentPrice,None,None,None) = poolManager.getSlot0(poolId) (src/Launcher.sol#53)\n[medium/medium] unused-return at src/Kiln.sol:154: Kiln.collect() (src/Kiln.sol#154-160) ignores return value by poolManager.unlock() (src/Kiln.sol#159)\n[low/medium] reentrancy-benign at src/Kiln.sol:189: Reentrancy in Kiln.sell(uint256) (src/Kiln.sol#189-200):\n[low/medium] reentrancy-benign at src/Kiln.sol:202: Reentrancy in Kiln.buy(uint256) (src/Kiln.sol#202-218):\n[low/medium] reentrancy-events at src/Kiln.sol:202: Reentrancy in Kiln.buy(uint256) (src/Kiln.sol#202-218):\n[low/medium] reentrancy-events at src/Kiln.sol:189: Reentrancy in Kiln.sell(uint256) (src/Kiln.sol#189-200):\n[low/medium] reentrancy-events at src/Kiln.sol:114: Reentrancy in Kiln.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/Kiln.sol#114-143):\n[low/medium] reentrancy-events at src/Launcher.sol:41: Reentrancy in Launcher.open(bytes32,uint160) (src/Launcher.sol#41-56):","passed":true},{"durationMs":323,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/Kiln.sol:164: Reentrancy: State change after external call\n[high] unprotected-initializer at src/Launcher.sol:37: Unprotected initializer\n[low] large-numeric-literal at src/Kiln.sol:31: Large Numeric Literal (3 places)\n[low] literal-instead-of-constant at src/Kiln.sol:178: Literal Instead of Constant (2 places)\n[low] state-change-without-event at src/Kiln.sol:95: State Change Without Event\n[low] unchecked-return at src/Kiln.sol:159: Unchecked Return\n[low] unsafe-erc20-operation at src/Kiln.sol:199: Unsafe ERC20 Operation (3 places)\n[low] unused-public-function at src/Kiln.sol:71: Public Function Not Used Internally","passed":true},{"durationMs":7501,"exitCode":0,"name":"proof b35fdf99b051","output":"Compiling 94 files with Solc 0.8.26\nSolc 0.8.26 finished in 6.90s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-be1e3d3c/Proof_b35fdf99b051.t.sol:PreInitGriefTest\n[PASS] testStrangerBlocksOpenForTheMinedSalt() (gas: 7894267)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.71ms (4.52ms CPU time)\n\nRan 1 test suite in 5.43ms (4.71ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"6f6c321289521435ceecc31cf837cdb833f160928227513178538af18746b690","verifiedTreeHash":"fc15e245b9fbd3db09cfdfc9c6a410455016729b","verifierVersion":"0.1.0+21c4e407"},{"checks":[{"durationMs":38836,"exitCode":0,"name":"build","output":"Compiling 98 files with Solc 0.8.26\nSolc 0.8.26 finished in 38.63s\nCompiler run successful!\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n   ╭▸ src/Launcher.sol:55:9\n   │\n55 │         emit Opened(address(deployed), poolId);\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/Launcher.sol:54:32\n   │\n54 │         if (currentPrice == 0) poolManager.initialize(key, sqrtPriceX96);\n   │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/Kiln.sol:111:63\n    │\n111 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(fee.toInt128(), 0), 0);\n    │                                                               ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:106:30\n    │\n106 │             uint256 amount = uint256(exactInput ? -params.amountSpecified : params.amountSpecified);\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/Kiln.sol:142:44\n    │\n142 │         return (IHooks.afterSwap.selector, returned);\n    │                                            ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Kiln.sol:141:9\n    │\n141 │         emit Passed(tx.origin, pepes, rate, fee);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:126:30\n    │\n126 │             uint256 amount = uint256(exactInput ? -params.amountSpecified : params.amountSpecified);\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:130:81\n    │\n130 │             if (fee != 0 && int256(delta.amount1()) != params.amountSpecified + int256(fee)) {\n    │                                                                                 ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:135:30\n    │\n135 │             uint256 amount = uint256(ztoDelta < 0 ? -ztoDelta : ztoDelta);\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[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/Kiln.sol:155:13\n    │\n155 │         if (poolManager.balanceOf(address(this), uint256(uint160(address(zto)))) == 0) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/Kiln.sol:159:9\n    │\n159 │         poolManager.unlock(\"\");\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Kiln.sol:197:9\n    │\n197 │         emit Sold(id, msg.sender, price);\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/Kiln.sol:215:9\n    │\n215 │         emit Bought(id, msg.sender, price);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\n","passed":true},{"durationMs":516,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/ClaimDonation.t.sol:ClaimDonationTest\n[PASS] testCollectBurnsWholeClaimBalanceIncludingDonations() (gas: 649224)\n[PASS] testStrangerCannotTransferKilnsClaims() (gas: 653456)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 3.01ms (644.45µs CPU time)\n\nRan 11 tests for test/Launcher.t.sol:LauncherTest\n[PASS] testApplicationBytecodeHasNoEscapeOpcodes() (gas: 2569838)\n[PASS] testBadBitsRollBackDeploymentAndAllowRetry() (gas: 3827872)\n[PASS] testCannotOpenTwice() (gas: 37204)\n[PASS] testConstructorsWorkWithoutDependencyCodeAndDoNotValidateKilnBits() (gas: 26219)\n[PASS] testFirstETHInputFromZTOOnlyRange() (gas: 623539)\n[PASS] testFirstZTOInputWorksWithNoZTOInManager() (gas: 622296)\n[PASS] testInvalidPriceRollsBackAndCanRetry() (gas: 3831393)\n[PASS] testOfflineMinerMatchesLauncher() (gas: 463222)\n[PASS] testOpenHasExactFlagsKeyAndNoLiquidity() (gas: 50448)\n[PASS] testPreInitializedPoolIsAdoptedAndRepriceable() (gas: 3199487)\n[PASS] testZTOOnlyRangeBelowOpeningTickAndETHOnlyAbove() (gas: 544107)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 15.43ms (26.35ms CPU time)\n\nRan 9 tests for test/Swaps.t.sol:SwapsTest\n[PASS] testAllFourCasesAtEveryTier() (gas: 13637917)\n[PASS] testFuzzGrossFeeAndBacking(bool,bool,uint96,uint8) (runs: 256, μ: 808352, ~: 601164)\n[PASS] testOnlyManagerAndOnlyOwnPool() (gas: 150353)\n[PASS] testPeripheryRouterAllFourCasesAndTiers() (gas: 11566102)\n[PASS] testRevertingCollectionRestoresClaimsAndReserve() (gas: 549895)\n[PASS] testSellAndBuyCollectBeforeQuoting() (gas: 930921)\n[PASS] testSpecifiedPartialFillsRevertWithoutRetainingClaims() (gas: 436937)\n[PASS] testTxOriginPassWorksImmediatelyAndRouterHoldingsDoNotCount() (gas: 2415979)\n[PASS] testUnspecifiedPartialFillChargesOnlyRealizedZTO() (gas: 264421)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 387.93ms (65.28ms CPU time)\n\nRan 15 tests for test/Pieces.t.sol:PiecesTest\n[PASS] testBuyAtZeroAskRevertsUntilReserveIsSeeded() (gas: 678633)\n[PASS] testBuyWithoutAllowanceRollsBack() (gas: 453054)\n[PASS] testEmptyCollectIsNoOpAndEmits() (gas: 184941)\n[PASS] testFalseERC20BuyRollsBackInventoryAndReserve() (gas: 478367)\n[PASS] testFalseERC20SeedRollsBack() (gas: 139646)\n[PASS] testFalseERC20SellRollsBackNFTAndReserve() (gas: 415344)\n[PASS] testFuzzGeometricBidsAreAlwaysPayable(uint96,uint8) (runs: 256, μ: 4084002, ~: 2490859)\n[PASS] testFuzzMarketSequence(uint256) (runs: 256, μ: 12177960, ~: 12081721)\n[PASS] testInventorySwapAndPopIncludingIdZero() (gas: 1017317)\n[PASS] testNoWithdrawRescueOwnerPauseOrUpgradeEntryPoints() (gas: 455741)\n[PASS] testPlainNFTAndZTODonationsAreNotInventoryOrReserve() (gas: 363537)\n[PASS] testSeedSellBuyAndEvents() (gas: 577103)\n[PASS] testTierBoundaries() (gas: 1726068)\n[PASS] testUnapprovedOrSomeoneElsesPieceCannotBeSold() (gas: 455442)\n[PASS] testZeroReserveAndUnheldIdRevert() (gas: 160285)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 388.05ms (497.19ms CPU time)\n\nRan 4 test suites in 388.76ms (794.42ms CPU time): 37 tests passed, 0 failed, 0 skipped (37 total tests)\n","passed":true},{"durationMs":54,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Kiln.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"Kiln.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"Kiln.buy(uint256)\",\"Kiln.collect()\",\"Kiln.seed(uint256)\",\"Kiln.sell(uint256)\",\"Kiln.unlockCallback(bytes)\",\"Launcher.open(bytes32,uint160)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":8,\"DEPENDENCY_HASHES.json\":97,\"README.md\":99,\"foundry.toml\":14,\"launch.json\":14,\"remappings.txt\":4,\"script/MineSalt.s.sol\":21,\"src/Kiln.sol\":225,\"src/Launcher.sol\":57,\"test/ClaimDonation.t.sol\":60,\"test/KilnBase.sol\":115,\"test/Launcher.t.sol\":179,\"test/Pieces.t.sol\":274,\"test/Swaps.t.sol\":252,\"test/mocks/TestTokens.sol\":51},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"82f854c705d20e0556e1ffe10c1ace1736c02b7c295507c873407a3fdf3656d1","verifiedTreeHash":"de02759f5c84c5db548e6c0cfada896b356ce41c","verifierVersion":"0.1.0+21c4e407"},{"checks":[{"durationMs":27947,"exitCode":0,"name":"build","output":"Compiling 98 files with Solc 0.8.26\nSolc 0.8.26 finished in 27.77s\nCompiler run successful!\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n   ╭▸ src/Launcher.sol:48:9\n   │\n48 │         emit Opened(address(deployed), key.toId());\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/Launcher.sol:47:9\n   │\n47 │         poolManager.initialize(key, sqrtPriceX96);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/Kiln.sol:111:63\n    │\n111 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(fee.toInt128(), 0), 0);\n    │                                                               ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:106:30\n    │\n106 │             uint256 amount = uint256(exactInput ? -params.amountSpecified : params.amountSpecified);\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/Kiln.sol:142:44\n    │\n142 │         return (IHooks.afterSwap.selector, returned);\n    │                                            ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Kiln.sol:141:9\n    │\n141 │         emit Passed(tx.origin, pepes, rate, fee);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:126:30\n    │\n126 │             uint256 amount = uint256(exactInput ? -params.amountSpecified : params.amountSpecified);\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:130:81\n    │\n130 │             if (fee != 0 && int256(delta.amount1()) != params.amountSpecified + int256(fee)) {\n    │                                                                                 ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:135:30\n    │\n135 │             uint256 amount = uint256(ztoDelta < 0 ? -ztoDelta : ztoDelta);\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[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/Kiln.sol:155:13\n    │\n155 │         if (poolManager.balanceOf(address(this), uint256(uint160(address(zto)))) == 0) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/Kiln.sol:159:9\n    │\n159 │         poolManager.unlock(\"\");\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Kiln.sol:197:9\n    │\n197 │         emit Sold(id, msg.sender, price);\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/Kiln.sol:213:9\n    │\n213 │         emit Bought(id, msg.sender, price);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\n","passed":true},{"durationMs":445,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/ClaimDonation.t.sol:ClaimDonationTest\n[PASS] testCollectBurnsWholeClaimBalanceIncludingDonations() (gas: 649236)\n[PASS] testStrangerCannotTransferKilnsClaims() (gas: 653480)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 17.73ms (430.06µs CPU time)\n\nRan 10 tests for test/Launcher.t.sol:LauncherTest\n[PASS] testApplicationBytecodeHasNoEscapeOpcodes() (gas: 2531370)\n[PASS] testBadBitsRollBackDeploymentAndAllowRetry() (gas: 13966305)\n[PASS] testCannotOpenTwice() (gas: 37182)\n[PASS] testConstructorsWorkWithoutDependencyCodeAndDoNotValidateKilnBits() (gas: 26197)\n[PASS] testFirstETHInputFromZTOOnlyRange() (gas: 623426)\n[PASS] testFirstZTOInputWorksWithNoZTOInManager() (gas: 622209)\n[PASS] testInvalidPriceRollsBackAndCanRetry() (gas: 13965521)\n[PASS] testOfflineMinerMatchesLauncher() (gas: 20173125)\n[PASS] testOpenHasExactFlagsKeyAndNoLiquidity() (gas: 50237)\n[PASS] testZTOOnlyRangeBelowOpeningTickAndETHOnlyAbove() (gas: 544121)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 39.01ms (49.75ms CPU time)\n\nRan 9 tests for test/Swaps.t.sol:SwapsTest\n[PASS] testAllFourCasesAtEveryTier() (gas: 13638109)\n[PASS] testFuzzGrossFeeAndBacking(bool,bool,uint96,uint8) (runs: 256, μ: 807414, ~: 601158)\n[PASS] testOnlyManagerAndOnlyOwnPool() (gas: 150401)\n[PASS] testPeripheryRouterAllFourCasesAndTiers() (gas: 11566294)\n[PASS] testRevertingCollectionRestoresClaimsAndReserve() (gas: 549907)\n[PASS] testSellAndBuyCollectBeforeQuoting() (gas: 930927)\n[PASS] testSpecifiedPartialFillsRevertWithoutRetainingClaims() (gas: 436961)\n[PASS] testTxOriginPassWorksImmediatelyAndRouterHoldingsDoNotCount() (gas: 2416003)\n[PASS] testUnspecifiedPartialFillChargesOnlyRealizedZTO() (gas: 264433)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 340.55ms (49.63ms CPU time)\n\nRan 14 tests for test/Pieces.t.sol:PiecesTest\n[PASS] testBuyWithoutAllowanceRollsBack() (gas: 453047)\n[PASS] testEmptyCollectIsNoOpAndEmits() (gas: 184919)\n[PASS] testFalseERC20BuyRollsBackInventoryAndReserve() (gas: 478348)\n[PASS] testFalseERC20SeedRollsBack() (gas: 139646)\n[PASS] testFalseERC20SellRollsBackNFTAndReserve() (gas: 415344)\n[PASS] testFuzzGeometricBidsAreAlwaysPayable(uint96,uint8) (runs: 256, μ: 3990379, ~: 2621681)\n[PASS] testFuzzMarketSequence(uint256) (runs: 256, μ: 12195688, ~: 12162107)\n[PASS] testInventorySwapAndPopIncludingIdZero() (gas: 1017258)\n[PASS] testNoWithdrawRescueOwnerPauseOrUpgradeEntryPoints() (gas: 455741)\n[PASS] testPlainNFTAndZTODonationsAreNotInventoryOrReserve() (gas: 363562)\n[PASS] testSeedSellBuyAndEvents() (gas: 577085)\n[PASS] testTierBoundaries() (gas: 1726068)\n[PASS] testUnapprovedOrSomeoneElsesPieceCannotBeSold() (gas: 455455)\n[PASS] testZeroReserveAndUnheldIdRevert() (gas: 160285)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 340.57ms (425.13ms CPU time)\n\nRan 4 test suites in 341.47ms (737.86ms CPU time): 35 tests passed, 0 failed, 0 skipped (35 total tests)\n","passed":true},{"durationMs":53,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Kiln.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"Kiln.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"Kiln.buy(uint256)\",\"Kiln.collect()\",\"Kiln.seed(uint256)\",\"Kiln.sell(uint256)\",\"Kiln.unlockCallback(bytes)\",\"Launcher.open(bytes32,uint160)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":8,\"DEPENDENCY_HASHES.json\":97,\"README.md\":96,\"foundry.toml\":14,\"launch.json\":14,\"remappings.txt\":4,\"script/MineSalt.s.sol\":21,\"src/Kiln.sol\":223,\"src/Launcher.sol\":50,\"test/ClaimDonation.t.sol\":60,\"test/KilnBase.sol\":115,\"test/Launcher.t.sol\":137,\"test/Pieces.t.sol\":248,\"test/Swaps.t.sol\":252,\"test/mocks/TestTokens.sol\":51},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"aa56b2069ad51e6c1b3e4b382c6c19253a2076f438ae1ed00eaec60170cdedd3","verifiedTreeHash":"e681cd543bdfafbb63b96290328eb06edc106cb0","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":39863,"exitCode":0,"name":"build","output":"Compiling 101 files with Solc 0.8.26\nSolc 0.8.26 finished in 39.73s\nCompiler run successful!\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n   ╭▸ src/Launcher.sol:48:9\n   │\n48 │         emit Opened(address(deployed), key.toId());\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/Launcher.sol:47:9\n   │\n47 │         poolManager.initialize(key, sqrtPriceX96);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/Kiln.sol:111:63\n    │\n111 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(fee.toInt128(), 0), 0);\n    │                                                               ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:106:30\n    │\n106 │             uint256 amount = uint256(exactInput ? -params.amountSpecified : params.amountSpecified);\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/Kiln.sol:142:44\n    │\n142 │         return (IHooks.afterSwap.selector, returned);\n    │                                            ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Kiln.sol:141:9\n    │\n141 │         emit Passed(tx.origin, pepes, rate, fee);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:126:30\n    │\n126 │             uint256 amount = uint256(exactInput ? -params.amountSpecified : params.amountSpecified);\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:130:81\n    │\n130 │             if (fee != 0 && int256(delta.amount1()) != params.amountSpecified + int256(fee)) {\n    │                                                                                 ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:135:30\n    │\n135 │             uint256 amount = uint256(ztoDelta < 0 ? -ztoDelta : ztoDelta);\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[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/Kiln.sol:155:13\n    │\n155 │         if (poolManager.balanceOf(address(this), uint256(uint160(address(zto)))) == 0) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/Kiln.sol:159:9\n    │\n159 │         poolManager.unlock(\"\");\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Kiln.sol:197:9\n    │\n197 │         emit Sold(id, msg.sender, price);\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/Kiln.sol:213:9\n    │\n213 │         emit Bought(id, msg.sender, price);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\n","passed":true},{"durationMs":13357,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/ClaimDonation.t.sol:ClaimDonationTest\n[PASS] testCollectBurnsWholeClaimBalanceIncludingDonations() (gas: 649236)\n[PASS] testStrangerCannotTransferKilnsClaims() (gas: 653480)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 15.53ms (328.04µs CPU time)\n\nRan 12 tests for test/Launcher.t.sol:LauncherTest\n[PASS] testApplicationBytecodeHasNoEscapeOpcodes() (gas: 2669901)\n[PASS] testBadBitsRollBackDeploymentAndAllowRetry() (gas: 13966262)\n[PASS] testBothConstructorsRejectEachMissingDependency() (gas: 8480)\n[PASS] testCannotOpenTwice() (gas: 37159)\n[PASS] testConstructorsWorkWithoutDependencyCodeAndDoNotValidateKilnBits() (gas: 26251)\n[PASS] testFirstETHInputFromZTOOnlyRange() (gas: 623419)\n[PASS] testFirstZTOInputWorksWithNoZTOInManager() (gas: 622176)\n[PASS] testInvalidPriceRollsBackAndCanRetry() (gas: 13965512)\n[PASS] testOfflineMinerMatchesLauncher() (gas: 20173105)\n[PASS] testOpenHasExactFlagsKeyAndNoLiquidity() (gas: 50222)\n[PASS] testSupersetAndMissingHookPermissionsAreRejected() (gas: 92020811)\n[PASS] testZTOOnlyRangeBelowOpeningTickAndETHOnlyAbove() (gas: 544118)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 88.26ms (122.94ms CPU time)\n\nRan 14 tests for test/AdversarialEdges.t.sol:AdversarialEdgesTest\n[PASS] testCannotSellAnInventoryPieceTwiceEvenWithPendingClaims() (gas: 690637)\n[PASS] testEntireCollectionFitsInventoryAndCanBeBought() (gas: 137706593)\n[PASS] testEveryPoolKeyFieldIsValidatedByBothCallbacks() (gas: 404541)\n[PASS] testFuzzFeeRoundingBelowOneToken(uint64,uint8,uint8) (runs: 1000, μ: 653959, ~: 486350)\n[PASS] testInsufficientSeedBalanceCannotCreateReserve() (gas: 150056)\n[PASS] testMinimumSignedSwapRevertsWithCustomErrorAndNoEffects() (gas: 132697)\n[PASS] testNearUint256MaximumReserveQuotesAndPaysWithoutOverflow() (gas: 448386)\n[PASS] testOneWeiSwapsInAllModesAndTiers() (gas: 10259581)\n[PASS] testReserveDustAndOneWeiBidBoundary() (gas: 572714)\n[PASS] testRevertingBuyRollsBackItsCollectionAndSwapPop() (gas: 1166527)\n[PASS] testRevertingNFTDeliveryRestoresBuyerTokensAndInventory() (gas: 480131)\n[PASS] testRevertingRouterSettlementRollsBackMintedCutAndPoolPrice() (gas: 347306)\n[PASS] testRevertingUnapprovedSaleRollsBackItsCollection() (gas: 574308)\n[PASS] testZeroSwapRejectedWithoutCut() (gas: 124643)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 104.43ms (135.02ms CPU time)\n\nRan 9 tests for test/Swaps.t.sol:SwapsTest\n[PASS] testAllFourCasesAtEveryTier() (gas: 13638109)\n[PASS] testFuzzGrossFeeAndBacking(bool,bool,uint96,uint8) (runs: 256, μ: 823220, ~: 601146)\n[PASS] testOnlyManagerAndOnlyOwnPool() (gas: 150401)\n[PASS] testPeripheryRouterAllFourCasesAndTiers() (gas: 11566294)\n[PASS] testRevertingCollectionRestoresClaimsAndReserve() (gas: 549907)\n[PASS] testSellAndBuyCollectBeforeQuoting() (gas: 930927)\n[PASS] testSpecifiedPartialFillsRevertWithoutRetainingClaims() (gas: 436961)\n[PASS] testTxOriginPassWorksImmediatelyAndRouterHoldingsDoNotCount() (gas: 2416003)\n[PASS] testUnspecifiedPartialFillChargesOnlyRealizedZTO() (gas: 264433)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 325.99ms (56.73ms CPU time)\n\nRan 14 tests for test/Pieces.t.sol:PiecesTest\n[PASS] testBuyWithoutAllowanceRollsBack() (gas: 453047)\n[PASS] testEmptyCollectIsNoOpAndEmits() (gas: 184919)\n[PASS] testFalseERC20BuyRollsBackInventoryAndReserve() (gas: 478348)\n[PASS] testFalseERC20SeedRollsBack() (gas: 139646)\n[PASS] testFalseERC20SellRollsBackNFTAndReserve() (gas: 415344)\n[PASS] testFuzzGeometricBidsAreAlwaysPayable(uint96,uint8) (runs: 256, μ: 3863318, ~: 2371356)\n[PASS] testFuzzMarketSequence(uint256) (runs: 256, μ: 12165704, ~: 11942300)\n[PASS] testInventorySwapAndPopIncludingIdZero() (gas: 1017258)\n[PASS] testNoWithdrawRescueOwnerPauseOrUpgradeEntryPoints() (gas: 455741)\n[PASS] testPlainNFTAndZTODonationsAreNotInventoryOrReserve() (gas: 363562)\n[PASS] testSeedSellBuyAndEvents() (gas: 577085)\n[PASS] testTierBoundaries() (gas: 1726068)\n[PASS] testUnapprovedOrSomeoneElsesPieceCannotBeSold() (gas: 455455)\n[PASS] testZeroReserveAndUnheldIdRevert() (gas: 160285)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 328.60ms (405.83ms CPU time)\n\nRan 2 tests for test/KilnInvariant.t.sol:KilnInvariantTest\n[PASS] invariant_reserveClaimsInventoryAndSettlement() (runs: 256, calls: 24576, reverts: 0)\n\n╭-------------+------------------+-------+---------+----------╮\n| Contract    | Selector         | Calls | Reverts | Discards |\n+=============================================================+\n| KilnHandler | buy              | 2466  | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| KilnHandler | collect          | 2522  | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| KilnHandler | donateClaims     | 2387  | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| KilnHandler | donatePiece      | 2431  | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| KilnHandler | donateZTO        | 2503  | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| KilnHandler | rejectUnknownBuy | 2458  | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| KilnHandler | seed             | 2465  | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| KilnHandler | sell             | 2441  | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| KilnHandler | swap             | 2459  | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| KilnHandler | transferPass     | 2444  | 0       | 0        |\n╰-------------+------------------+-------+---------+----------╯\n\n[PASS] testHandlerExercisesEveryActionAndSwapMode() (gas: 6335357)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 13.27s (13.25s CPU time)\n\nRan 6 test suites in 13.27s (14.13s CPU time): 53 tests passed, 0 failed, 0 skipped (53 total tests)\n","passed":true},{"durationMs":41,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Kiln.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"Kiln.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"Kiln.buy(uint256)\",\"Kiln.collect()\",\"Kiln.seed(uint256)\",\"Kiln.sell(uint256)\",\"Kiln.unlockCallback(bytes)\",\"Launcher.open(bytes32,uint160)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":8,\"DEPENDENCY_HASHES.json\":97,\"README.md\":96,\"foundry.toml\":14,\"remappings.txt\":4,\"script/MineSalt.s.sol\":21,\"src/Kiln.sol\":223,\"src/Launcher.sol\":50,\"test/AdversarialEdges.t.sol\":272,\"test/ClaimDonation.t.sol\":60,\"test/KilnBase.sol\":115,\"test/KilnInvariant.t.sol\":114,\"test/Launcher.t.sol\":171,\"test/Pieces.t.sol\":248,\"test/README.md\":22,\"test/Swaps.t.sol\":252,\"test/handlers/KilnHandler.sol\":255,\"test/mocks/TestTokens.sol\":51},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"deff1306057cbddf4be7f48b88a286adf3184a6f0243b039eed368f967d9d06e","verifiedTreeHash":"8123817a4ca0955c28ccd7bdab33cb39af84082e","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":51635,"exitCode":0,"name":"build","output":"Compiling 101 files with Solc 0.8.26\nSolc 0.8.26 finished in 51.47s\nCompiler run successful!\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n   ╭▸ src/Launcher.sol:55:9\n   │\n55 │         emit Opened(address(deployed), poolId);\n   │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/Kiln.sol:111:63\n    │\n111 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(fee.toInt128(), 0), 0);\n    │                                                               ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unused-return]: return value of an external call is not used\n   ╭▸ src/Launcher.sol:54:32\n   │\n54 │         if (currentPrice == 0) poolManager.initialize(key, sqrtPriceX96);\n   │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:106:30\n    │\n106 │             uint256 amount = uint256(exactInput ? -params.amountSpecified : params.amountSpecified);\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/Kiln.sol:142:44\n    │\n142 │         return (IHooks.afterSwap.selector, returned);\n    │                                            ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Kiln.sol:141:9\n    │\n141 │         emit Passed(tx.origin, pepes, rate, fee);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:126:30\n    │\n126 │             uint256 amount = uint256(exactInput ? -params.amountSpecified : params.amountSpecified);\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:130:81\n    │\n130 │             if (fee != 0 && int256(delta.amount1()) != params.amountSpecified + int256(fee)) {\n    │                                                                                 ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/Kiln.sol:135:30\n    │\n135 │             uint256 amount = uint256(ztoDelta < 0 ? -ztoDelta : ztoDelta);\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[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/Kiln.sol:155:13\n    │\n155 │         if (poolManager.balanceOf(address(this), uint256(uint160(address(zto)))) == 0) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/Kiln.sol:159:9\n    │\n159 │         poolManager.unlock(\"\");\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/Kiln.sol:197:9\n    │\n197 │         emit Sold(id, msg.sender, price);\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/Kiln.sol:215:9\n    │\n215 │         emit Bought(id, msg.sender, price);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\n","passed":true},{"durationMs":15083,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/ClaimDonation.t.sol:ClaimDonationTest\n[PASS] testCollectBurnsWholeClaimBalanceIncludingDonations() (gas: 649224)\n[PASS] testStrangerCannotTransferKilnsClaims() (gas: 653456)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 3.91ms (851.15µs CPU time)\n\nRan 9 tests for test/Swaps.t.sol:SwapsTest\n[PASS] testAllFourCasesAtEveryTier() (gas: 13637917)\n[PASS] testFuzzGrossFeeAndBacking(bool,bool,uint96,uint8) (runs: 256, μ: 797691, ~: 601149)\n[PASS] testOnlyManagerAndOnlyOwnPool() (gas: 150353)\n[PASS] testPeripheryRouterAllFourCasesAndTiers() (gas: 11566102)\n[PASS] testRevertingCollectionRestoresClaimsAndReserve() (gas: 549895)\n[PASS] testSellAndBuyCollectBeforeQuoting() (gas: 930921)\n[PASS] testSpecifiedPartialFillsRevertWithoutRetainingClaims() (gas: 436937)\n[PASS] testTxOriginPassWorksImmediatelyAndRouterHoldingsDoNotCount() (gas: 2415979)\n[PASS] testUnspecifiedPartialFillChargesOnlyRealizedZTO() (gas: 264421)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 370.82ms (154.77ms CPU time)\n\nRan 18 tests for test/AdversarialEdges.t.sol:AdversarialEdgesTest\n[PASS] testCannotSellAnInventoryPieceTwiceEvenWithPendingClaims() (gas: 690713)\n[PASS] testEntireCollectionFitsInventoryAndCanBeBought() (gas: 137706716)\n[PASS] testEveryPoolKeyFieldIsValidatedByBothCallbacks() (gas: 404535)\n[PASS] testFuzzFeeRoundingBelowOneToken(uint64,uint8,uint8) (runs: 1000, μ: 655644, ~: 486667)\n[PASS] testFuzzSeedSandwichCaptureIsBoundedByTwoPercentOfTheInflow(uint96,uint96) (runs: 1000, μ: 751685, ~: 751895)\n[PASS] testInsufficientSeedBalanceCannotCreateReserve() (gas: 150109)\n[PASS] testMinimumSignedSwapRevertsWithCustomErrorAndNoEffects() (gas: 132731)\n[PASS] testNearUint256MaximumReserveQuotesAndPaysWithoutOverflow() (gas: 448440)\n[PASS] testOneWeiSwapsInAllModesAndTiers() (gas: 10259819)\n[PASS] testRentingTwentyOnePiecesFromInventorySkipsTheCutAndPaysRentIntoReserve() (gas: 14700953)\n[PASS] testReserveDustAndOneWeiBidBoundary() (gas: 572758)\n[PASS] testRevertingBuyRollsBackItsCollectionAndSwapPop() (gas: 1166588)\n[PASS] testRevertingNFTDeliveryRestoresBuyerTokensAndInventory() (gas: 480224)\n[PASS] testRevertingRouterSettlementRollsBackMintedCutAndPoolPrice() (gas: 347406)\n[PASS] testRevertingUnapprovedSaleRollsBackItsCollection() (gas: 574350)\n[PASS] testSeedSandwichPinnedOnBothSidesOfBreakeven() (gas: 1030844)\n[PASS] testSellOfUnmintedOrOutOfCollectionIdRevertsWithoutEffects() (gas: 527345)\n[PASS] testZeroSwapRejectedWithoutCut() (gas: 124721)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 377.67ms (838.18ms CPU time)\n\nRan 15 tests for test/Launcher.t.sol:LauncherTest\n[PASS] testAdoptedPoolWithStrangerLiquidityKeepsStrangerPriceAndStillChargesCut() (gas: 3253558)\n[PASS] testApplicationBytecodeHasNoEscapeOpcodes() (gas: 2710460)\n[PASS] testBadBitsRollBackDeploymentAndAllowRetry() (gas: 3827874)\n[PASS] testBothConstructorsRejectEachMissingDependency() (gas: 8480)\n[PASS] testCannotOpenTwice() (gas: 37225)\n[PASS] testConstructorsWorkWithoutDependencyCodeAndDoNotValidateKilnBits() (gas: 26317)\n[PASS] testEmptyAdoptedPoolSpecifiedCutModesRevertWithoutFullPass() (gas: 5188249)\n[PASS] testFirstETHInputFromZTOOnlyRange() (gas: 623650)\n[PASS] testFirstZTOInputWorksWithNoZTOInManager() (gas: 622403)\n[PASS] testInvalidPriceRollsBackAndCanRetry() (gas: 3831428)\n[PASS] testOfflineMinerMatchesLauncher() (gas: 463224)\n[PASS] testOpenHasExactFlagsKeyAndNoLiquidity() (gas: 50559)\n[PASS] testPreInitializedPoolIsAdoptedAndRepriceable() (gas: 3199429)\n[PASS] testSupersetAndMissingHookPermissionsAreRejected() (gas: 60442239)\n[PASS] testZTOOnlyRangeBelowOpeningTickAndETHOnlyAbove() (gas: 544271)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 377.63ms (75.80ms CPU time)\n\nRan 15 tests for test/Pieces.t.sol:PiecesTest\n[PASS] testBuyAtZeroAskRevertsUntilReserveIsSeeded() (gas: 678633)\n[PASS] testBuyWithoutAllowanceRollsBack() (gas: 453054)\n[PASS] testEmptyCollectIsNoOpAndEmits() (gas: 184941)\n[PASS] testFalseERC20BuyRollsBackInventoryAndReserve() (gas: 478367)\n[PASS] testFalseERC20SeedRollsBack() (gas: 139646)\n[PASS] testFalseERC20SellRollsBackNFTAndReserve() (gas: 415344)\n[PASS] testFuzzGeometricBidsAreAlwaysPayable(uint96,uint8) (runs: 256, μ: 3641766, ~: 2121498)\n[PASS] testFuzzMarketSequence(uint256) (runs: 256, μ: 11975260, ~: 11883013)\n[PASS] testInventorySwapAndPopIncludingIdZero() (gas: 1017317)\n[PASS] testNoWithdrawRescueOwnerPauseOrUpgradeEntryPoints() (gas: 455741)\n[PASS] testPlainNFTAndZTODonationsAreNotInventoryOrReserve() (gas: 363537)\n[PASS] testSeedSellBuyAndEvents() (gas: 577103)\n[PASS] testTierBoundaries() (gas: 1726068)\n[PASS] testUnapprovedOrSomeoneElsesPieceCannotBeSold() (gas: 455442)\n[PASS] testZeroReserveAndUnheldIdRevert() (gas: 160285)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 377.66ms (516.69ms CPU time)\n\nRan 2 tests for test/KilnInvariant.t.sol:KilnInvariantTest\n[PASS] invariant_reserveClaimsInventoryAndSettlement() (runs: 256, calls: 24576, reverts: 0)\n\n╭-------------+------------------+-------+---------+----------╮\n| Contract    | Selector         | Calls | Reverts | Discards |\n+=============================================================+\n| KilnHandler | buy              | 2466  | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| KilnHandler | collect          | 2534  | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| KilnHandler | donateClaims     | 2441  | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| KilnHandler | donatePiece      | 2449  | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| KilnHandler | donateZTO        | 2405  | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| KilnHandler | rejectUnknownBuy | 2471  | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| KilnHandler | seed             | 2366  | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| KilnHandler | sell             | 2507  | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| KilnHandler | swap             | 2465  | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| KilnHandler | transferPass     | 2472  | 0       | 0        |\n╰-------------+------------------+-------+---------+----------╯\n\n[PASS] testHandlerExercisesEveryActionAndSwapMode() (gas: 6335376)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 14.97s (14.97s CPU time)\n\nRan 6 test suites in 14.97s (16.48s CPU time): 61 tests passed, 0 failed, 0 skipped (61 total tests)\n","passed":true},{"durationMs":46,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Kiln.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"Kiln.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"Kiln.buy(uint256)\",\"Kiln.collect()\",\"Kiln.seed(uint256)\",\"Kiln.sell(uint256)\",\"Kiln.unlockCallback(bytes)\",\"Launcher.open(bytes32,uint160)\"],\"files\":{\".gitignore\":4,\"DEPENDENCIES.md\":8,\"DEPENDENCY_HASHES.json\":97,\"README.md\":99,\"foundry.toml\":14,\"remappings.txt\":4,\"script/MineSalt.s.sol\":21,\"src/Kiln.sol\":225,\"src/Launcher.sol\":57,\"test/AdversarialEdges.t.sol\":397,\"test/ClaimDonation.t.sol\":60,\"test/KilnBase.sol\":115,\"test/KilnInvariant.t.sol\":114,\"test/Launcher.t.sol\":302,\"test/Pieces.t.sol\":274,\"test/README.md\":26,\"test/Swaps.t.sol\":252,\"test/handlers/KilnHandler.sol\":255,\"test/mocks/TestTokens.sol\":51},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"e9d5dad0920bcca7ed63d55687b8eba63f7eb7fd1e89ed9273f6b3190066c7b7","verifiedTreeHash":"bff1d9787e9a000706fbcd316d84d2b6a18eaad6","verifierVersion":"0.1.0+21c4e407"}]}