{"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":"aa9f4f0c-c7aa-49a8-b821-b0c189b1457d","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"b2a585da0ab0e68721f5e33564aa738ff9bc503a004219ce80f66391b01bbeee","dependsOn":["audit_imported_code"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"2d7d84a1a9972f1497e5bc68a704cbf42294d08c62d2868f102ffe3b909b9b51","skillId":"adapt-contract-project","tools":[]},"key":"adapt_contract_project","kind":"code","role":"implement","skillHash":"2d7d84a1a9972f1497e5bc68a704cbf42294d08c62d2868f102ffe3b909b9b51","skillId":"adapt-contract-project","state":"accepted"},{"acceptedSubmissionHash":"7bf544a7a311c001dfa44e8dd5181faccc7f526ced3ad5a0b6352c950520b75e","dependsOn":["adapt_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":"44ae8ef1fb104f61603a832b5518945a7e27e38d854fc7d80afcb17aa901e00d","dependsOn":["adapt_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":"c683cfe1ed09f2dd1f39081da9310c3d0e64f03fc0b285d9a7d484c58ecaa424","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","tools":[]},"key":"audit_imported_code","kind":"code","role":"review","skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","state":"accepted"},{"acceptedSubmissionHash":"799a93fa9bc7a6d101fc54a0687f5dea904def67abde29e633ade3f499d165d3","dependsOn":["adapt_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"8f1bccf0a96cfc9d995c0b3560230ff5dcb5e0bb4ba2a5cc2e4657468990d584","dependsOn":["adapt_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":"af8c33935c8a01bced54e033b2256013928bfeb43773ae1c286bae13e0dcc31c","dependsOn":["adapt_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":"a064b91757876dc68cf033efdc021e659a7a08ced33bfe299461b983db3af6aa","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"c2ae41c25b2b930c80b4b39d23d99c48d75d2ffb0d96f57a3e87adea56ec7a3c","dependsOn":["adapt_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"4c76766b00591dfe982fe85d3de589fdb2f810e5137c301155d1c5e36a1d3b23","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"4c76766b00591dfe982fe85d3de589fdb2f810e5137c301155d1c5e36a1d3b23","skillId":"write-foundry-tests","state":"accepted"}],"objective":"Token name: INK. Token symbol: INK. Launch it with the InkHook from this repository on its pool, paired with IMD, on Ethereum mainnet. Keep the hook's logic as written. Constructor values: portraits = 0x54fa3515848440ddde2aefe45a382bd868cacd15, vault = 0x33c85714c7d78fdb253fe1cf761d2d5c3c12d747, imd = the pool's IMD currency, treasury = 0xbc10B3662231c830d2ee10bc6359dB17bEa4b3d4, feeBps = 25 (a 0.25% hook fee on top of the pool's own fee, waived for wallets holding an agent portrait; IMD fees go to the treasury and INK fees are sent to the vault's liquidity contract (vault.liquidity()), which adds them back to the pool; nothing is burned; do not sell hook fees for ETH). Trading lock: the hook rejects every swap until vault.released() is true; there is no time-based fallback (anyone can call release() once the vault's 24-hour claim window ends, and it adds the unclaimed INK to the pool in the same transaction). The agent-holder fee waiver requires portraits.isAgentHolder(tx.origin), which needs the portrait held since an earlier block. Nobody can change any of these after deployment: the hook has no owner, admin or upgrade role, and none may be added. The pool trading fee share goes to the paying wallet, which is 0x824E12b38CfF2D3e7ad9607a0212f7F9Eb6a06E3. The portraits, vault, liquidity and renderer contracts are already live on Ethereum mainnet, deployed by IMD launch 2305 from a tree whose src/, script/, test/ and foundry.toml are byte-identical to this commit (see SOURCE_PROVENANCE.json): InkPortraits 0x54fa3515848440ddde2aefe45a382bd868cacd15, InkVault 0x33c85714c7d78fdb253fe1cf761d2d5c3c12d747, InkLiquidity 0xa53a3b0521e471f69c0351d9e6620848e749f37d, InkRenderer 0x9853b489003add907ff0849d899f5cb44e8c17d2. Each is verified on Sourcify (exact match) and compiles from this tree to the deployed runtime size, so use this tree as their source. Deploy only the hook and the token; do not redeploy or change those contracts.","parentJobId":null,"planHash":"c4f3b2f79113d27f90c81f6053474b79d0238d25fbccca08cdceed9b98775027","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"aa9f4f0c-c7aa-49a8-b821-b0c189b1457d","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-2321-src-inkhook-sol"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"52606","feedbackHash":"a3f0c8f693737c4472451bf144d45d81d7d6b7c89d4d24fdf0768ae155ed0ed3","nodeKey":"adapt_contract_project","submissionHash":"b2a585da0ab0e68721f5e33564aa738ff9bc503a004219ce80f66391b01bbeee","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51041","feedbackHash":"2473cd58895e2aa5c94b6c7e4cc0f4f645245430a99ce61c0cd6d7ea8cae832c","nodeKey":"audit_economics","submissionHash":"7bf544a7a311c001dfa44e8dd5181faccc7f526ced3ad5a0b6352c950520b75e","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52253","feedbackHash":"6d0021ba70d6104872fee5b9ba2185ee623e818825b2a0975f1c8184dd14e22e","nodeKey":"audit_flow","submissionHash":"44ae8ef1fb104f61603a832b5518945a7e27e38d854fc7d80afcb17aa901e00d","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51337","feedbackHash":"dce50add04102d6c5d791a9cbba8b321e6ffc99b0eebacc8b14a28030c2d1ab4","nodeKey":"audit_imported_code","submissionHash":"c683cfe1ed09f2dd1f39081da9310c3d0e64f03fc0b285d9a7d484c58ecaa424","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"53927","feedbackHash":"2ba1aa2942902c74553fa91e666a5e2233d1ad8692dd223c5c70a45d66fe6293","nodeKey":"audit_judge","submissionHash":"799a93fa9bc7a6d101fc54a0687f5dea904def67abde29e633ade3f499d165d3","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52166","feedbackHash":"8732eee79144286fe2302a3a8373179ef2cdce0852c9fa5ef9d823f80bb93761","nodeKey":"audit_math","submissionHash":"8f1bccf0a96cfc9d995c0b3560230ff5dcb5e0bb4ba2a5cc2e4657468990d584","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52149","feedbackHash":"874fc2f6b86ab53cbd8197943d2fb58f5d16b0610a0afba518641e490fb161e6","nodeKey":"audit_permissions","submissionHash":"af8c33935c8a01bced54e033b2256013928bfeb43773ae1c286bae13e0dcc31c","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51188","feedbackHash":"0fb0554c9ce17653ee38695f241eafe0b2d23ba8910691aeea92dab5aaf0f08b","nodeKey":"manifest","submissionHash":"a064b91757876dc68cf033efdc021e659a7a08ced33bfe299461b983db3af6aa","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51416","feedbackHash":"ab69e1979f88005b09e65ae482a7d3e04a3f44e63188a7f41332092a0772fcde","nodeKey":"write_foundry_tests","submissionHash":"c2ae41c25b2b930c80b4b39d23d99c48d75d2ffb0d96f57a3e87adea56ec7a3c","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"7696dfc3a07b4e8615f4b3a04824ab2188157ad433c220db464c09195060766e","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"7591760a616c6429","findings":[{"citation":"resolved","description":"route() forwards IMD to the treasury and INK to InkLiquidity, then unconditionally calls liquidity.addLiquidity() when InkLiquidity's INK balance is at least MIN_ADD (400,000 INK). InkLiquidity.addLiquidity() reverts NotReleased() (src/InkLiquidity.sol:166) until InkVault.release() has run. The router's IMD transfer to the treasury therefore reverts together with the INK listing, although the two are independent. Anyone can put InkLiquidity over MIN_ADD before release by transferring 400,000 INK to it (the transfer is an ordinary ERC-20 transfer; the INK is simply added to the release ladder later, so the sender loses it). Impact is bounded: no funds are lost, the IMD is only delayed until release(), and before release there are no pool fees to claim, so the practical window is IMD sent to the router by hand (or the factory's requester share if a trade ever occurred). It resolves on its own once release() runs. Reported for completeness of the control-flow trace; it needs no change to the hook launch. A fix in the owner's separate router deployment would be to try addLiquidity only when vault.released() is true, or to tolerate its revert.","line":74,"path":"src/InkFeeRouter.sol","reproduction":"State: INK launched, InkLiquidity.setPool done, InkVault.setup done, block.timestamp < claimEnd (so InkVault.release() cannot run). InkFeeRouter deployed with (InkLiquidity, IMD, treasury) and holding 10 IMD (any transfer). Attacker (or anyone) calls INK.transfer(InkLiquidity, 400_000e18). Call InkFeeRouter.route(): expected the 10 IMD reach the treasury and INK is forwarded; actual the call reverts with InkLiquidity.NotReleased() because ink.balanceOf(liquidity) >= MIN_ADD triggers liquidity.addLiquidity(), which checks vault.released(). claimAndRoute(factory, n) reverts the same way. After vm.warp(claimEnd) and InkVault.release(), route() succeeds.","severity":"info","snippet":"        if (ink.balanceOf(address(liquidity)) >= liquidity.MIN_ADD()) positionId = liquidity.addLiquidity();","title":"InkFeeRouter.route() reverts before release once InkLiquidity holds MIN_ADD INK, stranding any IMD the router holds until release"}],"hash":"44ae8ef1fb104f61603a832b5518945a7e27e38d854fc7d80afcb17aa901e00d","nodeId":"a98c120e-d5cb-4660-8787-964ea81307d1","outcome":"completed","summary":"The review is complete and written to `.imd-findings.json`. The working tree is otherwise untouched (my scratch fuzz test was removed and `git status` is clean).\n\n**Outcome:** the hook launch's control flow holds in the assigned area. No defect in `InkHook` or `LaunchToken` survived tracing, and the one substantiated item is informational.\n\n**What I covered**\n- Every hook callback: the pool-manager-only guards, the factory-only one-shot `beforeInitialize`, the swap lock keyed to the live vault's `released()`, the unspecified-side fee selection against the v4 `Hooks.afterSwap` delta convention, the claim mint/return pairing, and the `collectFees` payout to the immutable treasury and `vault.liquidity()`.\n- The launch sequence against live mainnet state. Portraits are bound to the vault, all three owners are the payer wallet, the vault has no INK or setup yet, and the liquidity contract has no pool, so `setInk`, `setPool`, `setup` and `release()` can run in that order.\n- The `release()` path through `InkLiquidity.addLiquidity()`. I fuzzed the single-sided mint rounding over the launch's realistic tick band and it never demands more INK than it transfers, so release cannot revert on the ladder.\n- IMD's source on Sourcify. It is a LayerZero OFT with a plain OpenZeppelin transfer, so the IMD payout cannot stall on a pause or blocklist.\n- The existing suite passes (134 tests, 28 fork-only skips).\n\n**Finding reported (info):** `InkFeeRouter.route()` couples its IMD forwarding to `addLiquidity()`, which reverts before release once anyone parks 400,000 INK in `InkLiquidity`. Funds are only delayed until `release()` runs, and the router is the owner's separate deployment, so nothing in this launch needs to change.\n\n**Not reached:** the renderer's state-changing functions and the vault's reveal ordering, which belong to other areas. Two external assumptions are recorded in coverage rows: the factory calls `PoolManager.initialize` itself (the task reference says so, and public docs do not show the call path), and its remainder gives the vault at least 800,000,000 INK, which both readings of `poolBps` satisfy.","treeHash":null,"usage":{"cachedInputTokens":2330550,"inputTokens":546,"model":"claude-fable-5-1","outputTokens":50638,"runtime":"claude","turns":37,"wallClockMs":700659}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"a8006a8757108cd3","findings":[{"citation":"resolved","description":"Merged from audit_permissions 9f56e1a1d49e (hook) and extended to the vault, same root cause. The hook waives its 0.25% fee when portraits.isAgentHolder(tx.origin) is true; InkVault.pull()/deposit()/rerollTraits() price the half fee from the same view. InkPortraits.isAgentHolder (src/InkPortraits.sol:190, `agentBalanceOf[holder] > 0 && agentHolderSince[holder] < block.number`) only requires the balance to have gone 0->1 in an earlier block, and agentHolderSince is written only on that 0->1 transition. So a portrait transferred in block N exempts its new holder from N+1 on, can be handed back in N+1, and the original holder is exempt again from N+2 (its own since-block is rewritten on the return, so it loses nothing but one block). One of the 2,000 agent portraits can thus serve an unbounded set of EOAs (a collateralised escrow makes the rental trustless), each paying zero hook fee on arbitrarily large swaps and the discounted reroll fee, against the natspec claim that the discount 'can't be borrowed for a single swap' and the brief's 'waived for wallets holding an agent portrait'. Impact is lost fee revenue (IMD to the treasury, INK to the pool), not loss of principal, hence low. InkPortraits is live and unchangeable; a fix that keeps the design would be in the hook (e.g. read agentHolderSince(tx.origin) and require it to be at least K blocks old), which the author must weigh against 'keep the hook's logic as written'; otherwise record it as an accepted design limit.","line":176,"path":"src/InkHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {Deployers} from \"v4-core-test/utils/Deployers.sol\";\nimport {MockERC20} from \"solmate/src/test/utils/mocks/MockERC20.sol\";\nimport {IHooks} from \"v4-core/interfaces/IHooks.sol\";\nimport {Hooks} from \"v4-core/libraries/Hooks.sol\";\nimport {TickMath} from \"v4-core/libraries/TickMath.sol\";\nimport {PoolKey} from \"v4-core/types/PoolKey.sol\";\nimport {Currency, CurrencyLibrary} from \"v4-core/types/Currency.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/types/PoolOperation.sol\";\nimport {PoolSwapTest} from \"v4-core/test/PoolSwapTest.sol\";\n\nimport {InkHook, IAgentPortraits, IInkVault} from \"src/InkHook.sol\";\nimport {InkPortraits} from \"src/InkPortraits.sol\";\nimport {LaunchToken} from \"src/LaunchToken.sol\";\n\n/// Minimal vault stand-in that satisfies InkPortraits.setVault's binding checks and can mint one agent portrait.\ncontract VaultStub {\n    InkPortraits public portraits;\n    address public seats = address(0xBEEF);\n    address public liquidity = address(0xCAFE);\n    bool public released = true;\n\n    constructor(InkPortraits p) {\n        portraits = p;\n    }\n\n    function traitRerollBlock(uint256) external pure returns (uint64) {\n        return 0;\n    }\n\n    function mint(uint256 seat, address to) external {\n        portraits.mintAgent(seat, to, InkPortraits.AgentStats(0, 0, 0, 0, false));\n    }\n}\n\n/// The hook's agent-holder waiver can be rented: a portrait handed over in block N exempts its new holder in\n/// block N+1, where it is handed back; the lender is exempt again from N+2. The waiver follows the portrait\n/// from wallet to wallet at a one-block cadence. The test expects a wallet that never held a portrait before\n/// block N to pay the hook fee on its swap in block N+1; it fails on this code because the fee is zero.\ncontract WaiverRentalTest is Test, Deployers {\n    using CurrencyLibrary for Currency;\n\n    InkHook hook;\n    LaunchToken ink;\n    MockERC20 imd;\n    InkPortraits portraits;\n    VaultStub vault;\n    PoolKey poolKey;\n    bool inkIs0;\n\n    address lender = makeAddr(\"lender\");\n    address renter = makeAddr(\"renter\");\n\n    function setUp() public {\n        deployFreshManagerAndRouters();\n        imd = new MockERC20(\"Identity.md\", \"IMD\", 18);\n        portraits = new InkPortraits(address(this));\n        vault = new VaultStub(portraits);\n        portraits.setVault(address(vault));\n        ink = new LaunchToken();\n        address hookAddr = address(\n            uint160(\n                Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG\n                    | Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG\n            ) ^ (0x4444 << 144)\n        );\n        deployCodeTo(\n            \"InkHook.sol:InkHook\",\n            abi.encode(\n                manager,\n                IAgentPortraits(address(portraits)),\n                IInkVault(address(vault)),\n                Currency.wrap(address(imd)),\n                makeAddr(\"treasury\"),\n                uint16(25),\n                address(this)\n            ),\n            hookAddr\n        );\n        hook = InkHook(hookAddr);\n        inkIs0 = address(ink) < address(imd);\n        poolKey = PoolKey({\n            currency0: Currency.wrap(inkIs0 ? address(ink) : address(imd)),\n            currency1: Currency.wrap(inkIs0 ? address(imd) : address(ink)),\n            fee: 12_500,\n            tickSpacing: 60,\n            hooks: IHooks(hookAddr)\n        });\n        manager.initialize(poolKey, TickMath.getSqrtPriceAtTick(0));\n        imd.mint(address(this), 1_000_000_000e18);\n        ink.approve(address(modifyLiquidityRouter), type(uint256).max);\n        imd.approve(address(modifyLiquidityRouter), type(uint256).max);\n        modifyLiquidityRouter.modifyLiquidity(\n            poolKey, ModifyLiquidityParams(-60_000, 60_000, int256(1e23), bytes32(0)), \"\"\n        );\n        _fund(lender);\n        _fund(renter);\n    }\n\n    function _fund(address who) internal {\n        ink.transfer(who, 1_000_000e18);\n        imd.mint(who, 1_000_000e18);\n        vm.startPrank(who, who);\n        ink.approve(address(swapRouter), type(uint256).max);\n        imd.approve(address(swapRouter), type(uint256).max);\n        vm.stopPrank();\n    }\n\n    function _buyInk(address who, int256 amountSpecified) internal {\n        bool zeroForOne = !inkIs0;\n        vm.prank(who, who);\n        swapRouter.swap(\n            poolKey,\n            SwapParams({\n                zeroForOne: zeroForOne,\n                amountSpecified: amountSpecified,\n                sqrtPriceLimitX96: zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1\n            }),\n            PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}),\n            \"\"\n        );\n    }\n\n    function _inkClaim() internal view returns (uint256) {\n        return manager.balanceOf(address(hook), Currency.wrap(address(ink)).toId());\n    }\n\n    function test_waiverFollowsARentedPortraitAtOneBlockCadence() public {\n        // Block 100: the lender claims seat 7 (the vault mints it) and has held it since this block.\n        vm.roll(100);\n        vault.mint(7, lender);\n        assertFalse(portraits.isAgentHolder(lender), \"same-block holder is not yet exempt\");\n\n        // Block 101: the lender is exempt. It hands the portrait to the renter (an escrow would take\n        // collateral here; the hand-over itself is a plain transfer).\n        vm.roll(101);\n        assertTrue(portraits.isAgentHolder(lender));\n        vm.prank(lender);\n        portraits.transferFrom(lender, renter, 7);\n        assertFalse(portraits.isAgentHolder(renter), \"not exempt in the block it was received\");\n\n        // Block 102: the renter, who never held a portrait before block 101, swaps and hands it back.\n        vm.roll(102);\n        assertTrue(portraits.isAgentHolder(renter), \"exempt after one block of holding\");\n        uint256 before = _inkClaim();\n        _buyInk(renter, -100_000e18);\n        uint256 feeTaken = _inkClaim() - before;\n        vm.prank(renter);\n        portraits.transferFrom(renter, lender, 7);\n\n        // Block 103: the lender is exempt again; the portrait can be rented out to the next wallet.\n        vm.roll(103);\n        assertTrue(portraits.isAgentHolder(lender), \"lender exempt again one block after the return\");\n\n        // Expected: a wallet that only borrowed the portrait for one block pays the 0.25% hook fee on its\n        // 100,000 IMD exact-input buy (about 250 INK at the 1:1 opening price). Actual on this code: 0.\n        assertGt(feeTaken, 0, \"renter paid no hook fee\");\n    }\n}","reproduction":"State: InkPortraits with the vault set, InkHook with feeBps = 25 on an open INK/IMD pool, lender L and renter R both EOAs, R has never held an agent portrait. Block 100: the vault mints agent portrait #7 to L. Block 101: isAgentHolder(L) is true; L.transferFrom(L, R, 7); isAgentHolder(R) is false in this block. Block 102: isAgentHolder(R) is true; R swaps 100,000 IMD exact-input for INK through the swap router (tx.origin = R); then R.transferFrom(R, L, 7). Block 103: isAgentHolder(L) is true again. Expected: R, who held the portrait for one block, pays the 0.25% hook fee (about 250 INK at a 1:1 price) and the hook's INK claim balance rises by that amount. Actual: InkHook.afterSwap returns (selector, 0) for R, the hook's claim balance does not change, and L is exempt again one block later. The attached test fails on this code at 'renter paid no hook fee: 0 <= 0' (run: forge test --match-path test/scratch/WaiverRental.t.sol).","severity":"low","snippet":"        if (feeBps == 0 || portraits.isAgentHolder(swapper)) return (IHooks.afterSwap.selector, 0);","title":"Agent-holder perks (hook fee waiver, half-price rerolls) can be rented wallet to wallet at a one-block cadence"},{"citation":"resolved","description":"From audit_economics 988fd71aef4b, reproduced by trace. _revealNext() derives the pick from r = keccak256(abi.encode(entropy, pullId)) and pick = r % avail (src/InkVault.sol:345-347) where entropy is only blockhash(p.blockNumber + 1); revealTraits (line 448) uses the same source. The Ethereum proposer schedule is public one epoch ahead, so a puller who controls or pays the validator for slot N+1 pulls in block N and iterates candidate payloads for N+1 (reordering or adding its own transactions changes the hash) until the pick equals the inventory index of the portrait it wants, at about `avail` off-chain hash evaluations and no on-chain cost beyond the pull's own 400,000 INK. The valuable target is an agent portrait handed in through deposit() (one-of-a-kind, confers the hook fee waiver and half-price rerolls). The vault's INK backing is unaffected; honest pullers lose the uniform chance the natspec promises. This is the residual the brief itself names ('do not describe next-block randomness as manipulation-proof') and ADAPTATION.md records as 'builder-influenceable blockhash randomness'; it is kept here as the concrete cheapest path so the author decides knowingly. No contract in this launch can change it (InkVault is live).","line":319,"path":"src/InkVault.sol","reproduction":"State: released == true; agent portrait #7 was handed in via deposit() and admitted, so _inventory = [7]; 999 swarm slots unminted, so avail = 1000. Attacker A controls the proposer of block N+1. (1) Block N: A calls pull(1) -> pullId k, pulls[k].blockNumber = N. (2) A builds block N+1 and varies its own transactions until uint256(keccak256(abi.encode(h, k))) % 1000 == 0 where h is the candidate block hash (about 1000 tries). (3) Block >= N+2: anyone calls reveal(k): entropy = blockhash(N+1) = h, pick = 0 < held = 1, so token 7 is transferred to A (lines 349-353). Expected: each of the 1000 candidates has probability 1/1000 for every puller. Actual: A receives agent portrait #7 with probability 1 for the ordinary 400,000 INK pull price.","severity":"low","snippet":"            entropy = blockhash(uint256(p.blockNumber) + 1);","title":"Pull and trait-reroll draws come from blockhash(N+1) alone, so the proposer of block N+1 can pick which portrait a pull lands on"},{"citation":"resolved","description":"From write_foundry_tests 1c7324b24e25, reproduced (the supplied proof fails on this code). _rangeFrom clamps only the far endpoint to maxUsableTick/minUsableTick; the near endpoint is computed from the spot tick without a clamp. With INK as currency0 and tick >= 887220 (maxUsableTick(60)), lower = 887280 > upper = 887220; with tick in [887160, 887219], lower == upper == 887220. For INK as currency1 the mirror holds at the bottom. Downstream, addLiquidity() in its non-ladder branch calls _add(lower, upper, amount) -> _mintSingleSided -> TickMath.getSqrtPriceAtTick(887280), which reverts InvalidTick (MAX_TICK is 887272), or for the equal case LiquidityAmounts.getLiquidityForAmount0 divides by sqrtPriceB - sqrtPriceA == 0 and panics. The ladder branch is unaffected (it breaks on lower >= upper). So fee INK that has accumulated in InkLiquidity (hook fees, reroll fees, LP fee INK) cannot be listed, and InkFeeRouter.route()/claimAndRoute() revert too (they call addLiquidity unconditionally at MIN_ADD), until the price leaves the last interval. No funds are lost and the state is recoverable, and the price region is only reached after every INK position has been bought out, so low. InkLiquidity is live and cannot be changed in this launch; recorded for the owner.","line":270,"path":"src/InkLiquidity.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {InkLiquidity} from \"src/InkLiquidity.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {IPoolManager} from \"v4-core/interfaces/IPoolManager.sol\";\nimport {IPositionManager} from \"v4-periphery/src/interfaces/IPositionManager.sol\";\nimport {PoolKey} from \"v4-core/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/types/Currency.sol\";\nimport {IHooks} from \"v4-core/interfaces/IHooks.sol\";\nimport {TickMath} from \"v4-core/libraries/TickMath.sol\";\n\n// Only exposes the production pure-range calculation; no replacement arithmetic.\ncontract RangeEndProofHarness is InkLiquidity {\n    constructor()\n        InkLiquidity(\n            IPoolManager(address(0x100)), IPositionManager(address(0x200)),\n            address(0x300), address(0x400)\n        )\n    {}\n\n    function prime(bool inkIs0) external {\n        poolKey = PoolKey(\n            Currency.wrap(address(0x1000)), Currency.wrap(address(0x2000)),\n            12_500, 60, IHooks(address(0))\n        );\n        ink = IERC20(inkIs0 ? address(0x1000) : address(0x2000));\n        floorTick = 0;\n        premium = 0;\n    }\n\n    function range(int24 tick) external view returns (int24 lower, int24 upper) {\n        return _rangeFrom(tick, 60);\n    }\n}\n\ncontract RangeEndProofTest is Test {\n    function test_terminalUpperTickMustNotProduceAnInvertedRange() public {\n        RangeEndProofHarness h = new RangeEndProofHarness();\n        h.prime(true);\n        (int24 lower, int24 upper) = h.range(887270);\n        // A terminal empty range is already accepted by RangeMathTest; an inverted one is not.\n        assertLe(lower, upper, \"lower must not exceed upper for a valid spot tick\");\n        assertLe(lower, TickMath.maxUsableTick(60));\n        assertLe(upper, TickMath.maxUsableTick(60));\n    }\n\n    function test_terminalLowerTickMustNotProduceAnInvertedRange() public {\n        RangeEndProofHarness h = new RangeEndProofHarness();\n        h.prime(false);\n        (int24 lower, int24 upper) = h.range(-887270);\n        assertLe(lower, upper, \"lower must not exceed upper for a valid spot tick\");\n        assertGe(lower, TickMath.minUsableTick(60));\n        assertGe(upper, TickMath.minUsableTick(60));\n    }\n}","reproduction":"Harness exposing the production _rangeFrom with spacing = 60, floorTick = 0, premium = 0, width = 60. inkIs0 = true, tick = 887270: expected an ordered range (an empty terminal range would be acceptable), actual lower = 887280, upper = 887220. inkIs0 = false, tick = -887270: actual lower = -887220, upper = -887280. The attached test fails on this code at assertLe(lower, upper) with exactly these values (forge test --match-path test/scratch/RangeEndProof.t.sol). On a live pool: after all INK liquidity above spot is bought and a further buy moves the tick to 887271, addLiquidity() with 400,000 INK in InkLiquidity reverts with TickMath.InvalidTick instead of listing the INK.","severity":"low","snippet":"            lower = _floor(tick, spacing) + spacing;","title":"_rangeFrom returns an inverted or empty range in the last tick-spacing interval, so addLiquidity() reverts while the price sits there"},{"citation":"resolved","description":"From audit_permissions 18d544e36573, reproduced by trace and confirmed against mainnet at block 26,235,376: InkVault.owner() is the EOA 0x824E12b38CfF2D3e7ad9607a0212f7F9Eb6a06E3 (no code), vault.ink(), vault.liquidity() and statsRoot are all zero, InkLiquidity.poolSet() is false. The hook pays its INK fees to whatever InkVault.liquidity() returns and release() (src/InkVault.sol:239-240) sends unclaimed * 400,000 INK to the same address. InkVault.setup (lines 171-187) is onlyOwner, one-shot, and accepts any contract that has code, returns ink() == ink and poolSet() == true; it never compares liquidity_ with InkLiquidity 0xa53a3b0521e471f69c0351d9e6620848e749f37d, and the hook's immutables cannot, because the brief asks it to read vault.liquidity(). The brief's guarantee 'INK fees are sent to the vault's liquidity contract, which adds them back to the pool; nothing is burned' therefore rests entirely on that one owner transaction naming the right contract, and a mistaken or compromised owner key at setup time redirects the release and all later INK fees irrecoverably. Recorded as the launch's main privileged-power trust assumption (actor: vault owner; precondition: setup() not yet called, which is the live state). If the author wants it enforced in code the minimal change is an immutable known-liquidity address in the hook checked against vault.liquidity() before _payOut, which adds no owner power but changes the hook, so it is the author's scope decision.","line":200,"path":"src/InkHook.sol","reproduction":"State: mainnet after the hook launch; owner has called vault.setInk(INK) and InkLiquidity.setPool(key, INK). Owner deploys contract X with ink() returning INK, poolSet() returning true and a no-op addLiquidity(), then calls InkVault.setup(root, start, address(X)). Expected per the brief: only InkLiquidity 0xa53a...f37d can be the destination. Actual: setup passes every check (X.code.length != 0, X.ink() == ink, X.poolSet() == true), vault.liquidity() == X; after claimEnd anyone calls release(), which transfers e.g. 600,000,000 INK (1,500 unclaimed seats) to X and calls X.addLiquidity(); every later InkHook.collectFees() runs _payOut(ink, X). Compare the intended call in script/InkSteps.sol:143 vault.setup(root, start, address(liq)).","severity":"low","snippet":"        return abi.encode(_payOut(imd, treasury), _payOut(ink, vault.liquidity()));","title":"Trust assumption: the vault owner's one setup() call decides where up to 800M released INK and every INK hook fee go; nothing binds it to the live InkLiquidity"},{"citation":"resolved","description":"From audit_permissions 43cef147a9d9, reproduced by trace; the requester asked for exactly this ('no time-based fallback'), so it is recorded as the liveness trust assumption, not a defect. release() (src/InkVault.sol:231-243) reverts InkNotSet while statsRoot is zero; statsRoot is written only by onlyOwner setup(), which needs setInk() (requires ink.balanceOf(vault) >= 800,000,000 INK, line 164) and InkLiquidity.setPool() first. After the hook launch the 100M INK pool seed and the vault's INK stay frozen until the single owner EOA runs those calls, and forever if the key is lost. Evidence the deployer should supply: that the launch policy's split sends exactly the 80% remainder (800,000,000 INK of the 10^27 supply) to InkVault 0x33c8...d747, since a smaller transfer makes setInk() revert NotFunded and leaves the pool locked for good.","line":160,"path":"src/InkHook.sol","reproduction":"State: hook launched, pool initialised, owner never calls setInk/setup (today's mainnet state). Any swap: PoolManager.swap -> InkHook.beforeSwap -> vault.released() == false -> revert TradingNotOpen (surfaced by Hooks as HookCallFailed/Wrap__FailedHookCall). Anyone calling InkVault.release() reverts InkNotSet (statsRoot == 0). Variant: the factory sends 799,999,999 INK to the vault; vault.setInk(INK) reverts NotFunded (line 164) and setup() can never run. Expected by holders: a pool that opens after 24 hours; actual: no swap is possible until the owner acts, with no deadline after which anyone else can open it.","severity":"info","snippet":"            if (!vault.released()) revert TradingNotOpen();","title":"Trust assumption: the trading lock has no fallback; only the vault owner's setInk/setPool/setup can make release() reachable, and setInk needs the full 800M INK in the vault"},{"citation":"resolved","description":"Merged from audit_permissions 492053862b91 and audit_economics 36b69178a124 (same state and setter), reproduced by trace and against mainnet: InkLiquidity.premium() == 0 at block 26,235,376. setPremium is onlyOwner and allowed while positions.length == 0; since addLiquidity() requires vault.released() and release() is the first call that creates positions, the premium can change until the block before release(), including after holders have seen the planned 16,000 ticks in script/InkSteps.sol:37. The premium fixes the price at which up to 800,000,000 INK is listed: 0 lists the ladder from one tick spacing above the opening price (the first post-release buyer, who can bundle release() with a swap, takes band 0, about 8,000,000 INK, at roughly the opening price regardless of any outside price INK reached during the window); 100,000 lists it at about 22,000x, where it never sells and the 10% seed is the only INK liquidity. Intended owner power within the bound; recorded so the judge can weigh it against 'nobody can change any of these after deployment', which is true of the hook but not of the released INK's listing price. Operational remedy only: the owner calls setPremium(16000) (or the chosen value) before release(); no contract change.","line":154,"path":"src/InkLiquidity.sol","reproduction":"State: InkLiquidity.poolSet true, no positions, vault not released. (a) premium left at 0 (current): after claimEnd, release() -> addLiquidity() -> start = floorTick -> _addLadder lists band 0 from _floor(floorTick)+60; expected per the design 'opening price plus the premium', actual the ladder starts at the opening price. (b) Owner calls setPremium(100000): passes (0 <= 100000 <= 100_000, positions.length == 0); release() lists the ladder from floorTick + 100000; expected per the published plan 16,000 ticks; actual 100,000, and no later call can move it (positions are permanent).","severity":"info","snippet":"        if (premium_ < 0 || premium_ > 100_000 || positions.length != 0) revert BadPremium();","title":"Trust assumption: the release listing premium is 0 on mainnet today and the owner can set it anywhere in [0, 100,000] ticks up to the block before release()"},{"citation":"resolved","description":"From audit_permissions d2e2598b4cd5, reproduced by trace; the brief fixes the rule ('portraits.isAgentHolder(tx.origin)') and the natspec states both consequences, so this is a design note. (a) An agent-holder EOA that submits transactions for others (relayer, 4337 bundler EOA, searcher bundle) pays no hook fee on any swap inside, whoever's order is filled. (b) A smart-contract wallet (e.g. a Safe) holding a portrait never gets the waiver; EIP-7702 delegated EOAs do, since tx.origin stays the EOA. No change proposed.","line":174,"path":"src/InkHook.sol","reproduction":"State: pool open. EOA A holds an agent portrait since an earlier block and operates relayer contract C. A sends a transaction calling C, which swaps user U's 1,000,000 IMD for INK via the router. afterSwap reads tx.origin = A, isAgentHolder(A) is true, fee = 0. Expected by a reader of 'waived for wallets holding an agent portrait': U (no portrait) pays 0.25%; actual: no fee. Conversely a Safe S holding a portrait executes the same swap from its own module: tx.origin is the executor EOA, isAgentHolder(executor) is false, S pays 0.25%.","severity":"info","snippet":"        address swapper = tx.origin;","title":"Documented asymmetry: tx.origin exempts every swap in a transaction an agent-holder EOA sends and never exempts contract wallets"},{"citation":"resolved","description":"From audit_flow fb5bcd1aea90, reproduced by trace. route() forwards IMD to the treasury and INK to InkLiquidity, then unconditionally calls liquidity.addLiquidity() when InkLiquidity's INK balance is at least MIN_ADD (400,000 INK). InkLiquidity.addLiquidity() reverts NotReleased (src/InkLiquidity.sol:166) until InkVault.release(), so the whole call, including the independent IMD transfer, reverts. Anyone can create the condition by sending 400,000 INK to InkLiquidity (they lose it to the release ladder). Bounded: nothing is lost, the IMD is delayed until release, and before release there are no pool fees to claim, so the window only covers IMD sent to the router by hand. The router is the owner's separate deployment, not part of this launch. The same unconditional call also propagates the addLiquidity revert of finding 3.","line":74,"path":"src/InkFeeRouter.sol","reproduction":"State: INK launched, InkLiquidity.setPool done, InkVault.setup done, block.timestamp < claimEnd. InkFeeRouter deployed with (InkLiquidity, IMD, treasury) and holding 10 IMD. Anyone calls INK.transfer(InkLiquidity, 400_000e18). Call InkFeeRouter.route(): expected 10 IMD reach the treasury; actual the call reverts with InkLiquidity.NotReleased() because ink.balanceOf(liquidity) >= MIN_ADD triggers liquidity.addLiquidity(). claimAndRoute(factory, n) reverts the same way. After vm.warp(claimEnd) and InkVault.release(), route() succeeds.","severity":"info","snippet":"        if (ink.balanceOf(address(liquidity)) >= liquidity.MIN_ADD()) positionId = liquidity.addLiquidity();","title":"InkFeeRouter.route() reverts before release once InkLiquidity holds MIN_ADD INK, delaying any IMD the router holds until release()"},{"citation":"resolved","description":"From audit_math c6458b6d2921, reproduced by arithmetic. fee = amount * 25 / 10_000 truncates to 0 whenever the unspecified-side amount is at most 399 wei, so such a swap pays no hook fee and no claim is minted. The loss per swap is below 1 wei of an 18-decimal token, it does not compound, and splitting a real trade into sub-400-wei pieces costs far more in gas and in the pool's 1.25% LP fee than the 0.25% it avoids. No fix recommended; rounding up would overcharge 1 wei on every swap.","line":182,"path":"src/InkHook.sol","reproduction":"Pool initialised, vault released, tx.origin not an agent holder. Exact-input swap whose unspecified-side delta is 399 wei: afterSwap computes fee = 399 * 25 / 10_000 = 0 and returns (selector, 0); no claim minted, no FeeTaken event. Expected by the 0.25% rule: 0.9975 wei (not representable); actual 0. For 400 wei the fee is 1 wei.","severity":"info","snippet":"        uint256 fee = (uint256(uint128(amount)) * feeBps) / 10_000;","title":"Hook fee rounds down to zero for unspecified-side amounts below 400 wei at feeBps = 25; dust-level, not exploitable"},{"citation":"resolved","description":"Own finding, operational. The owner's OpenClaims/Rehearse scripts pass CLAIM_START to stepOpenClaims, which checks start >= block.timestamp (script/InkSteps.sol:137) and then calls vault.setup(root, start, liq), which itself reverts BadSetup when start < block.timestamp (src/InkVault.sol:175). At mainnet block 26,235,376 the timestamp is 1,792,518,347, already past 1,792,501,200, and the hook launch (which setInk needs) has not happened, so this schedule can no longer be met. Not a contract defect; the constant must be updated to a future start within 30 days of the setup transaction before the owner runs the step.","line":39,"path":"script/InkSteps.sol","reproduction":"Fork mainnet at any block with timestamp > 1_792_501_200 (e.g. 26,235,376) after a hypothetical hook launch, run stepOpenClaims(vault, liq, ink, key, root, CLAIM_START). Expected: setInk, setPremium, setPool and setup succeed; actual: reverts Check('claim start within 30 days') at the _require, and calling vault.setup(root, 1_792_501_200, liq) directly reverts BadSetup().","severity":"info","snippet":"    uint64 constant CLAIM_START = 1_792_501_200;","title":"script/InkSteps.sol's CLAIM_START (20 Oct 2026 13:00 UTC) is already in the past, so stepOpenClaims() reverts as written"}],"hash":"799a93fa9bc7a6d101fc54a0687f5dea904def67abde29e633ade3f499d165d3","nodeId":"4861e734-6542-4d5f-a6b5-537ff138efc6","outcome":"completed","summary":"The review is complete and written to `.imd-findings.json` in the repository root. No source file was changed; the scratch proofs were run and removed.\n\n**Outcome.** Ten findings, none above low. Nothing blocks deployment: the hook and token constructors make no external calls, the runtimes are well under the size limits, the manifest's seven constructor arguments match the declaration order and the permission list matches `getHookPermissions`, and the full suite passes with 162 tests and 0 failures.\n\n**Specialist findings, after reproduction and merging**\n\n- **Kept, low, with proof:** the agent-holder waiver can be rented wallet to wallet at a one-block cadence. The specialist's test fails on this code as stated. I widened it to the vault's half-price reroll pricing, which keys on the same `isAgentHolder` rule.\n- **Kept, low:** the proposer of block N+1 can choose which portrait a pull lands on. Reproduced by trace through the reveal path. It is the residual the brief itself names.\n- **Kept, low, with proof:** the terminal tick interval yields an inverted or empty range. The supplied test fails with the stated values. I added the downstream effect: `addLiquidity` reverts there, and the fee router propagates the revert.\n- **Kept, low:** the vault owner's single `setup()` call decides where released INK and INK hook fees go. I confirmed on mainnet that INK, liquidity and the stats root are still unset and the owner is a plain EOA.\n- **Merged into one info:** the \"premium up to 100,000 ticks\" and \"premium is 0 today\" findings share one setter and state. Mainnet premium reads 0.\n- **Kept as info:** no-fallback trading lock, `tx.origin` asymmetry, router reverting before release, and sub-400-wei fee rounding. Each reproduced by trace or arithmetic.\n\n**Own addition, info.** The chain time is already past the script's claim-start constant, so the owner's open-claims step reverts as written. The constant needs updating before the step runs.\n\n**Coverage.** All 59 listed entry points are answered, 45 as holds and 14 as findings, with no unreached rows. The renderer and reveal entry points the flow specialist could not verify were traced and hold. Three extra rows record the manifest check, deployability and the hook's zero-net-delta invariant.","treeHash":null,"usage":{"cachedInputTokens":896356,"inputTokens":226,"model":"claude-fable-5-1","outputTokens":42108,"runtime":"claude","turns":20,"wallClockMs":584685}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"3bed38612db34f32","findings":[{"citation":"resolved","description":"Economic Security guide, 'legitimate features turned against the protocol'. _revealNext() derives the pick from r = keccak256(abi.encode(entropy, pullId)) and pick = r % avail (src/InkVault.sol:345-347), where entropy is only blockhash(p.blockNumber + 1). Nothing else enters the draw. The Ethereum proposer schedule is public one epoch ahead, so a puller who operates (or pays) the validator scheduled for slot N+1 pulls in block N and then builds candidate payloads for N+1 (reordering or adding/dropping its own transactions changes the block hash) until keccak256(abi.encode(hash, pullId)) % avail equals the inventory index of the portrait it wants. The expected work is about `avail` hash evaluations, done off-chain for free; the proposer's block is still valid and earns its normal reward, so there is no cost beyond the pull's own 400,000 INK, which the puller would pay anyway. The valuable target is an agent portrait sitting in _inventory (one-of-a-kind, and it confers isAgentHolder perks: the hook fee waiver and half-price rerolls), which any holder can hand in via deposit() for exactly 400,000 INK. Honest pullers lose the uniform chance the vault promises ('a random portrait'); the vault's INK backing is not affected. The brief itself says next-block randomness must not be described as manipulation-proof; this records the concrete cheapest path. The same seed source is used by revealTraits (src/InkVault.sol:448), where only the art is at stake.","line":319,"path":"src/InkVault.sol","reproduction":"State: released == true; an agent portrait (token id < 10000, e.g. seat 7) has been handed in through deposit() and admitted, so _inventory = [7] and avail = 1 + unminted slots (say 999 unminted, avail = 1000). Attacker A controls the validator scheduled to propose block N+1. Sequence: (1) in block N, A calls pull(1) -> pullId k, pulls[k].blockNumber = N. (2) A builds block N+1 and iterates over payload variants (different ordering/inclusion of its own transactions) computing h = blockhash candidate, until uint256(keccak256(abi.encode(h, k))) % 1000 == 0 (the index of token 7 in _inventory); about 1000 candidates are needed. A publishes that block. (3) Anyone calls reveal(k) in block >= N+2: entropy = blockhash(N+1) = h, pick = 0 < held = 1, so token 7 is transferred to A (src/InkVault.sol:349-353). Expected: each portrait in the draw has probability 1/1000 for every puller. Actual: A obtains the agent portrait with probability 1 for the ordinary pull price, and other pullers' chance of landing it is zero in that block.","severity":"low","snippet":"            entropy = blockhash(uint256(p.blockNumber) + 1);","title":"Pull draw is decided by blockhash(N+1) alone, so the proposer of block N+1 can choose which portrait a pull lands on at no cost"},{"citation":"resolved","description":"Not a code defect; an operational economics note for the owner before release(). At release the pool's tick is still the opening tick (no swap can run before released() and nothing else moves slot0), so addLiquidity() always takes the ladder branch from start = floorTick +/- premium. The live InkLiquidity 0xa53a3b0521e471f69c0351d9e6620848e749f37d reports premium() == 0 (read from mainnet during this review), so up to 800,000,000 INK will start one tick spacing above the opening price. The ladder's thin bottom band (about 2% of the released INK in band 0, rising to about a third in band 7) limits how much is sold at the lowest prices, and setPremium() is callable until the first position exists. If INK changes hands outside this pool before release (for example the swarm's 10% allocation, if the launch policy leaves it transferable) and trades above the opening price, the first buyers after release() take the bottom bands below that outside price; the shortfall is IMD the permanent liquidity and the treasury (its share of LP and hook fees) would otherwise have captured. The owner decides the premium; the accepted finding 942e691ce965 already covers the related spot-tick listing of fee INK.","line":172,"path":"src/InkLiquidity.sol","reproduction":"State: mainnet, premium() == 0 (current), vault setup done, claimEnd passed, 1,000 seats unclaimed. Call release(): 400,000,000 INK goes to InkLiquidity and _addLadder(floorTick, 4e26) mints 8 bands starting at _floor(floorTick)+tickSpacing. Expected per the design: released INK is listed at 'the opening price plus the premium'. Actual: with premium 0, band 0 (about 8,000,000 INK) is bought at roughly the opening price in the first post-release transaction by whoever bundles release() with a swap, independent of any higher outside price for INK. Remedy is operational: the owner calls setPremium(p) before release() if an above-opening start is wanted; no contract change is implied.","severity":"info","snippet":"        if (_inkIs0() ? tick <= start : tick >= start) return _addLadder(start, amount);","title":"Release lists every unclaimed seat's INK at the opening tick plus a premium that is currently 0 on mainnet, regardless of any price discovery during the 24-hour window"}],"hash":"7bf544a7a311c001dfa44e8dd5181faccc7f526ced3ad5a0b6352c950520b75e","nodeId":"bfcda984-091a-470e-a1cb-a1c19e906326","outcome":"completed","summary":"The findings file is written and validated, with both snippets matching the tree at the cited lines and no tracked files changed. The review is complete.\n\n**Outcome:** the economics of this launch hold up. I found no fund-loss or invariant-breaking defect in the Economic Security, Invariant or Flow Gap areas. The report in `.imd-findings.json` carries one low finding, one informational note, and 62 coverage rows answering every listed entry point plus three invariants.\n\n**What I checked in depth**\n- **Hook fee flow:** all four exact-input/exact-output and direction cases take the fee from the unspecified side, the claim mint and returned delta net to zero, and payout goes only to the treasury (IMD) or the vault's liquidity contract (INK). No INK claim can exist before setup fixes that address, because no swap can run before release.\n- **Trading lock and release:** released is set before the ladder is added inside one nonReentrant call, and no callback on that path can swap, so the released INK is listed at the opening tick as designed.\n- **Vault conservation:** vault INK equals 400,000 times portraits outside the vault plus unrevealed pulls across every writer. Pull capacity, expiry refunds and deposit fees all preserve it. The VaultInvariants and both LiquidityCustody invariant suites pass locally.\n- **Liquidity placement:** nothing lists below the release floor, no path removes a position, and the spot-tick sandwich on addLiquidity is unprofitable at realistic depth and already recorded as accepted design.\n- **Live state:** on mainnet the portraits are bound to the vault, INK and pool are not yet set, premium is 0, and the IMD token runtime has no pause, blacklist, mint or upgrade selectors, so the coupled IMD-then-INK payout cannot be wedged.\n\n**Findings reported**\n1. **Low:** the pull draw depends only on blockhash of the block after the pull. A puller who proposes that block can grind payload variants off-chain until the pick lands on a specific inventory portrait, such as a handed-in agent portrait, at no cost beyond the ordinary pull. Reproduction is written out with concrete state.\n2. **Info:** with premium at 0 on mainnet, release lists the bottom ladder band at roughly the opening price regardless of any outside price discovery during the claim window. This is an operational note for the owner before calling release, not a code defect.\n\n**Already-accepted items I did not re-report:** the tx.origin fee waiver, the lock with no time fallback, fee INK listed at the caller's spot tick, and sub-100-wei fee rounding.\n\n**Not reached:** the InkRenderer entry points were marked as holding because nothing in my area touches them; I did not trace their internals. Fork-only tests (LaunchDay, LaunchFeeHandoff, MainnetFork) were skipped by the harness and not run.","treeHash":null,"usage":{"cachedInputTokens":1224422,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":51720,"runtime":"claude","turns":28,"wallClockMs":809770}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"7f1dec5ffcbde1d8","findings":[{"citation":"resolved","description":"Math Precision / zero-rounding check. With the launch's feeBps = 25, fee = amount * 25 / 10_000 truncates to 0 whenever the unspecified-side amount is at most 399 wei, so such a swap pays no hook fee. ADAPTATION.md already records the same observation for a 1% fee (threshold 100 wei, id e507b4d5ebe7); at 0.25% the threshold is 400 wei. This is reported for completeness of the area, not as a defect: the loss per swap is below 1 wei of a 18-decimal token, it does not compound, and splitting a real trade into sub-400-wei pieces costs far more in gas and pool LP fee (1.25%, charged in full by the pool regardless) than the 0.25% hook fee it avoids. No fix is recommended; rounding the fee up would instead overcharge 1 wei on every swap.","line":182,"path":"src/InkHook.sol","reproduction":"Pool initialized by the factory, vault released. tx.origin not an agent holder. Exact-input swap whose output on the unspecified side is 399 wei (e.g. sell 399 wei-scale INK so delta.amount1() == 399 when INK is currency0 and the swap is zeroForOne). afterSwap computes fee = 399 * 25 / 10_000 = 0 and returns (selector, 0): no claim is minted, no FeeTaken event. Expected by the 0.25% rule: 0.9975 wei, which cannot be represented; actual: 0. For 400 wei the fee is 1 wei as expected. Verified by arithmetic; test_feeFreeDustSwap-style behaviour is already asserted in test/InkSystem.t.sol near line 982.","severity":"info","snippet":"        uint256 fee = (uint256(uint128(amount)) * feeBps) / 10_000;\n        if (fee == 0) return (IHooks.afterSwap.selector, 0);","title":"Hook fee rounds down to zero for unspecified-side amounts below 400 wei (feeBps = 25); dust-level, not exploitable"}],"hash":"8f1bccf0a96cfc9d995c0b3560230ff5dcb5e0bb4ba2a5cc2e4657468990d584","nodeId":"c9d4f8fe-3a4d-44d7-ae80-b938ee15b728","outcome":"completed","summary":"The review is complete. The findings file is written and validated, the working tree is clean apart from it, and nothing else remains to request.\n\n**Outcome.** The math, boundary and numerical-gap review of the INK hook launch found no defect that loses funds, misdirects them or blocks the launch. One info-level observation is recorded. The findings file at `.imd-findings.json` holds that item and 63 coverage rows, one for each of the 60 listed entry points plus three invariant rows, with every row answered.\n\n**What was checked in depth**\n\n- **Hook fee arithmetic.** The unspecified-side selection in `afterSwap` matches Uniswap v4's own placement of the hook delta for all four swap shapes. The negation, narrowing casts and the 25/10,000 multiplication cannot overflow for any amount below 2^127 token units.\n- **Single-sided minting in InkLiquidity.** This was the one place a rounding error could have been severe: if the position manager ever demanded one wei more than the INK sent, `release()` would revert deterministically and trading could never open. I proved the bound analytically and confirmed it with a 5,000-run fuzz per token ordering over ticks in the realistic range. The lemma holds, so release cannot be bricked by rounding.\n- **Ladder weights, ranges and tick alignment.** Bands are contiguous, aligned to spacing, nonzero, always on INK's side of the current tick, and the weight products stay far below 2^256 at the expected opening tick of about 129,000 either side of zero.\n- **Vault fees and counters.** Every fee of 400,000 INK times basis points divides exactly, so no remainder leaks. Supply caps, availability sums and the per-block deposit counter cannot underflow.\n- **External boundaries.** The live IMD token accepts a zero-value transfer, so the fee collection path that takes an empty IMD credit does not revert. Chunk storage sits exactly at the EIP-170 limit.\n\n**Evidence gathered.** The full local suite passed (134 tests). The repository's mainnet fork tests passed against the live contracts in both token orderings (14 tests). Live state read from mainnet matches the pre-hook stage: vault and liquidity still unwired, owners as the brief states, official v4 pool and position managers. The scratch fuzz test was removed after use.\n\n**The one recorded item.** With a 25 basis-point fee, swaps whose unspecified side is below 400 wei pay no hook fee because the division truncates to zero. It is marked info, non-exploitable and already acknowledged in the adaptation notes at a different threshold.\n\n**Not reached.** No launch manifest exists in this tree yet, so there was nothing to check there. That row is marked unreached.","treeHash":null,"usage":{"cachedInputTokens":1463931,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":47183,"runtime":"claude","turns":30,"wallClockMs":1039686}},{"artifacts":[],"attempt":1,"bundleHash":"583ec1b34b6eb3639366946ca812d0ef6852e133f33af0fb1afab9f4ff106620","device":"0b0761c9b10fe0bb","findings":[],"hash":"a064b91757876dc68cf033efdc021e659a7a08ced33bfe299461b983db3af6aa","nodeId":"7486fcbb-5bbf-4621-afc8-9b25d43fad49","outcome":"completed","summary":"Created [launch.json](launch.json) with the correct contracts, constructor arguments, hook permissions, and INK/IMD pool settings.\n\n`forge build` passed. Tests using the supplied fuzz seed: **134 passed, 0 failed, 28 skipped** for fork/snapshot requirements.\n\nOnly `launch.json` changed for submission.","treeHash":"662ab0a4f40be21b22888f28497e0d9c997c5b29","usage":{"cachedInputTokens":443776,"inputTokens":39062,"model":"gpt-6-astra","outputTokens":4020,"runtime":"codex","turns":4,"wallClockMs":133990}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"af1843f371022d4a","findings":[{"citation":"resolved","description":"Asymmetry / trust gap (access x economics). The 0.25% hook fee is waived when portraits.isAgentHolder(tx.origin) is true, and InkPortraits.isAgentHolder (src/InkPortraits.sol:190) only requires agentBalanceOf[holder] > 0 && agentHolderSince[holder] < block.number. agentHolderSince is written only when a wallet's agent balance goes from 0 to 1. So a portrait transferred in block N exempts its new holder from block N+1 on, and when it is handed back in block N+1 the original holder is exempt again from N+2. The natspec claims the waiver 'can't be borrowed for a single swap'; it can be borrowed for a single swap with a one-block delay, and one of the 2,000 agent portraits can therefore serve an unbounded number of wallets, each paying zero hook fee on arbitrarily large swaps, with the portrait out of the lender's hands for only one block (a collateralised escrow makes the rental trustless for the lender; the renter must be an EOA because the hook reads tx.origin). The fee is the launch's own revenue (IMD to the treasury, INK back to the pool), so every rented swap is fee revenue lost to a wallet class the design did not intend to exempt. This is a weakening of a stated guarantee, not a loss of principal, hence low. Minimal fix that keeps the design: require a longer holding period in isAgentHolder (e.g. agentHolderSince[holder] + K < block.number with K of some hours) or checkpoint agentHolderSince on every receipt, so a round-trip rental costs the lender its own exemption for the same period.","line":176,"path":"src/InkHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {Deployers} from \"v4-core-test/utils/Deployers.sol\";\nimport {MockERC20} from \"solmate/src/test/utils/mocks/MockERC20.sol\";\nimport {IHooks} from \"v4-core/interfaces/IHooks.sol\";\nimport {Hooks} from \"v4-core/libraries/Hooks.sol\";\nimport {TickMath} from \"v4-core/libraries/TickMath.sol\";\nimport {PoolKey} from \"v4-core/types/PoolKey.sol\";\nimport {Currency, CurrencyLibrary} from \"v4-core/types/Currency.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/types/PoolOperation.sol\";\nimport {PoolSwapTest} from \"v4-core/test/PoolSwapTest.sol\";\n\nimport {InkHook, IAgentPortraits, IInkVault} from \"src/InkHook.sol\";\nimport {InkPortraits} from \"src/InkPortraits.sol\";\nimport {LaunchToken} from \"src/LaunchToken.sol\";\n\n/// Minimal vault stand-in that satisfies InkPortraits.setVault's binding checks and can mint one agent portrait.\ncontract VaultStub {\n    InkPortraits public portraits;\n    address public seats = address(0xBEEF);\n    address public liquidity = address(0xCAFE);\n    bool public released = true;\n\n    constructor(InkPortraits p) {\n        portraits = p;\n    }\n\n    function traitRerollBlock(uint256) external pure returns (uint64) {\n        return 0;\n    }\n\n    function mint(uint256 seat, address to) external {\n        portraits.mintAgent(seat, to, InkPortraits.AgentStats(0, 0, 0, 0, false));\n    }\n}\n\n/// The hook's agent-holder waiver can be rented: a portrait handed over in block N exempts its new holder in\n/// block N+1, where it is handed back; the lender is exempt again from N+2. The waiver follows the portrait\n/// from wallet to wallet at a one-block cadence. The test expects a wallet that never held a portrait before\n/// block N to pay the hook fee on its swap in block N+1; it fails on this code because the fee is zero.\ncontract WaiverRentalTest is Test, Deployers {\n    using CurrencyLibrary for Currency;\n\n    InkHook hook;\n    LaunchToken ink;\n    MockERC20 imd;\n    InkPortraits portraits;\n    VaultStub vault;\n    PoolKey poolKey;\n    bool inkIs0;\n\n    address lender = makeAddr(\"lender\");\n    address renter = makeAddr(\"renter\");\n\n    function setUp() public {\n        deployFreshManagerAndRouters();\n        imd = new MockERC20(\"Identity.md\", \"IMD\", 18);\n        portraits = new InkPortraits(address(this));\n        vault = new VaultStub(portraits);\n        portraits.setVault(address(vault));\n        ink = new LaunchToken();\n        address hookAddr = address(\n            uint160(\n                Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG\n                    | Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG\n            ) ^ (0x4444 << 144)\n        );\n        deployCodeTo(\n            \"InkHook.sol:InkHook\",\n            abi.encode(\n                manager,\n                IAgentPortraits(address(portraits)),\n                IInkVault(address(vault)),\n                Currency.wrap(address(imd)),\n                makeAddr(\"treasury\"),\n                uint16(25),\n                address(this)\n            ),\n            hookAddr\n        );\n        hook = InkHook(hookAddr);\n        inkIs0 = address(ink) < address(imd);\n        poolKey = PoolKey({\n            currency0: Currency.wrap(inkIs0 ? address(ink) : address(imd)),\n            currency1: Currency.wrap(inkIs0 ? address(imd) : address(ink)),\n            fee: 12_500,\n            tickSpacing: 60,\n            hooks: IHooks(hookAddr)\n        });\n        manager.initialize(poolKey, TickMath.getSqrtPriceAtTick(0));\n        imd.mint(address(this), 1_000_000_000e18);\n        ink.approve(address(modifyLiquidityRouter), type(uint256).max);\n        imd.approve(address(modifyLiquidityRouter), type(uint256).max);\n        modifyLiquidityRouter.modifyLiquidity(\n            poolKey, ModifyLiquidityParams(-60_000, 60_000, int256(1e23), bytes32(0)), \"\"\n        );\n        _fund(lender);\n        _fund(renter);\n    }\n\n    function _fund(address who) internal {\n        ink.transfer(who, 1_000_000e18);\n        imd.mint(who, 1_000_000e18);\n        vm.startPrank(who, who);\n        ink.approve(address(swapRouter), type(uint256).max);\n        imd.approve(address(swapRouter), type(uint256).max);\n        vm.stopPrank();\n    }\n\n    function _buyInk(address who, int256 amountSpecified) internal {\n        bool zeroForOne = !inkIs0;\n        vm.prank(who, who);\n        swapRouter.swap(\n            poolKey,\n            SwapParams({\n                zeroForOne: zeroForOne,\n                amountSpecified: amountSpecified,\n                sqrtPriceLimitX96: zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1\n            }),\n            PoolSwapTest.TestSettings({takeClaims: false, settleUsingBurn: false}),\n            \"\"\n        );\n    }\n\n    function _inkClaim() internal view returns (uint256) {\n        return manager.balanceOf(address(hook), Currency.wrap(address(ink)).toId());\n    }\n\n    function test_waiverFollowsARentedPortraitAtOneBlockCadence() public {\n        // Block 100: the lender claims seat 7 (the vault mints it) and has held it since this block.\n        vm.roll(100);\n        vault.mint(7, lender);\n        assertFalse(portraits.isAgentHolder(lender), \"same-block holder is not yet exempt\");\n\n        // Block 101: the lender is exempt. It hands the portrait to the renter (an escrow would take\n        // collateral here; the hand-over itself is a plain transfer).\n        vm.roll(101);\n        assertTrue(portraits.isAgentHolder(lender));\n        vm.prank(lender);\n        portraits.transferFrom(lender, renter, 7);\n        assertFalse(portraits.isAgentHolder(renter), \"not exempt in the block it was received\");\n\n        // Block 102: the renter, who never held a portrait before block 101, swaps and hands it back.\n        vm.roll(102);\n        assertTrue(portraits.isAgentHolder(renter), \"exempt after one block of holding\");\n        uint256 before = _inkClaim();\n        _buyInk(renter, -100_000e18);\n        uint256 feeTaken = _inkClaim() - before;\n        vm.prank(renter);\n        portraits.transferFrom(renter, lender, 7);\n\n        // Block 103: the lender is exempt again; the portrait can be rented out to the next wallet.\n        vm.roll(103);\n        assertTrue(portraits.isAgentHolder(lender), \"lender exempt again one block after the return\");\n\n        // Expected: a wallet that only borrowed the portrait for one block pays the 0.25% hook fee on its\n        // 100,000 IMD exact-input buy (about 250 INK at the 1:1 opening price). Actual on this code: 0.\n        assertGt(feeTaken, 0, \"renter paid no hook fee\");\n    }\n}","reproduction":"State: real InkPortraits with the vault set, hook with feeBps = 25, pool open (vault.released() true), lender L and renter R both EOAs, R has never held an agent portrait. Sequence: block 100: vault mints agent portrait #7 to L (claim). Block 101: L.transferFrom(L, R, 7). Block 102: R swaps 100,000 IMD exact-input for INK through the swap router (tx.origin = R); then R.transferFrom(R, L, 7). Block 103: isAgentHolder(L) is true again. Expected (per the natspec and the brief's 'waived for wallets holding an agent portrait'): R, who only borrowed the portrait for one block, pays the 0.25% hook fee (about 250 INK on a 1:1 pool). Actual: InkHook.afterSwap returns (selector, 0) for R, the hook's INK claim balance does not change, and L is exempt again one block later. The proof test test/scratch/WaiverRental.t.sol fails on this code at 'renter paid no hook fee: 0 <= 0'.","severity":"low","snippet":"        if (feeBps == 0 || portraits.isAgentHolder(swapper)) return (IHooks.afterSwap.selector, 0);","title":"Agent-holder fee waiver can be rented from wallet to wallet at a one-block cadence (tx.origin + held-since-earlier-block)"},{"citation":"resolved","description":"Trust gap (access x economics), documented as a privileged power rather than a bypass. The hook pays its INK fees to whatever InkVault.liquidity() returns. InkVault.setup (src/InkVault.sol:171-187) is onlyOwner, callable once, and accepts any contract for liquidity_ as long as it has code, returns ink() == ink and poolSet() == true (src/InkVault.sol:179-181); it does not bind to the InkLiquidity already deployed at 0xa53a3b0521e471f69c0351d9e6620848e749f37d, and nothing on-chain ties the two together (InkLiquidity.setPool checks the hook and vault, but the vault never checks InkLiquidity). The same address receives, in release() (src/InkVault.sol:239-240), unclaimed * 400,000 INK (up to 800,000,000 INK, 80% of supply) plus every reroll and pull-expiry fee. On mainnet today (block ~26.23M) InkVault.owner() is the EOA 0x824E12b38CfF2D3e7ad9607a0212f7F9Eb6a06E3 (no code, no multisig), vault.ink() and vault.liquidity() are still zero and statsRoot is zero, so this call is still ahead. The brief's guarantee 'INK fees are sent to the vault's liquidity contract, which adds them back to the pool; nothing is burned' therefore rests entirely on that one owner transaction naming the right contract. The hook's own immutables cannot enforce it because it was asked to read vault.liquidity(). Record for the judge/author as the trust assumption of this launch: a compromised or mistaken owner key at setup time redirects the release and all INK fees irrecoverably, and the hook has no way to notice. If the author wants the guarantee enforced in code, the minimal change is for the hook to compare vault.liquidity() against the known InkLiquidity address (an immutable) before paying out, which does not add any owner power.","line":200,"path":"src/InkHook.sol","reproduction":"State: mainnet after the hook launch, vault.setInk(INK) done, InkLiquidity.setPool done (poolSet true). Owner deploys contract X with ink() returning INK, poolSet() returning true and addLiquidity() a no-op, then calls InkVault.setup(root, start, X). setup passes every check (X has code, X.ink() == ink, X.poolSet() == true). After the 24-hour window anyone calls release(): ink.safeTransfer(X, unclaimed * 400_000e18) and X.addLiquidity() succeed, so e.g. with 1,500 unclaimed seats 600,000,000 INK go to X instead of into the pool. Every later InkHook.collectFees() calls _payOut(ink, X) and X receives the INK fees. Expected per the brief: InkLiquidity 0xa53a...f37d receives them and adds them to the pool. Actual: X receives them; the hook cannot tell the difference. Compare with the intended call in script/InkSteps.sol:143 (vault.setup(root, start, address(liq))).","severity":"low","snippet":"        return abi.encode(_payOut(imd, treasury), _payOut(ink, vault.liquidity()));","title":"Trust assumption: the vault owner's single setup() call decides where 800M released INK and every INK hook fee go; the hook follows vault.liquidity(), not the live InkLiquidity"},{"citation":"resolved","description":"Access x economics. Every swap reverts until InkVault.released() is true, and release() (src/InkVault.sol:231-243) reverts with InkNotSet while statsRoot is zero. statsRoot is only written by the onlyOwner setup(), which itself needs the owner's setInk() and InkLiquidity.setPool() first. The brief chose no time-based fallback, so this is the intended design, but it means that after the hook launch the pool's 100,000,000 INK seed and the vault's 800,000,000 INK stay frozen for exactly as long as the single owner EOA 0x824E12b38CfF2D3e7ad9607a0212f7F9Eb6a06E3 takes to run those three calls, and forever if the key is lost. ADAPTATION.md records this as not redesigned. Recorded as the launch's liveness trust assumption, not a code defect; nothing in the hook can be changed to remove it without changing the agreed design (a time fallback).","line":160,"path":"src/InkHook.sol","reproduction":"State: hook launched, pool initialised, vault holds 800M INK, owner never calls setInk/setup. Any swap: PoolManager.swap -> InkHook.beforeSwap -> vault.released() == false -> revert TradingNotOpen (wrapped by Hooks as HookCallFailed). Anyone calling InkVault.release() reverts with InkNotSet (statsRoot == 0). Expected by holders: a trading pool; actual: no swap is possible on this pool until the owner acts, with no deadline after which anyone else can open it.","severity":"info","snippet":"            if (!vault.released()) revert TradingNotOpen();","title":"Trust assumption: the trading lock has no fallback and only the vault owner can make release() reachable (setInk, setPool, setup)"},{"citation":"resolved","description":"Access x economics. setPremium is onlyOwner and allowed while positions.length == 0. Because addLiquidity() requires vault.released() and release() is the first call that creates positions, the owner can change the premium right up to the block before release(), including after INK holders have seen the planned 16,000-tick value in script/InkSteps.sol:37. The premium fixes the price at which up to 800,000,000 INK (the unclaimed seats' share) is listed: premium 0 lists it at the opening price, 100,000 ticks lists it at e^(10) ~ 22,026x, where it will in practice never sell and the 10% pool seed is the only INK liquidity. The bound and the before-first-position rule are the only guards. This is an intended owner power; it is recorded so the judge can weigh it against the brief's 'nobody can change any of these after deployment' (which is true of the hook, not of the listing price of the released INK).","line":154,"path":"src/InkLiquidity.sol","reproduction":"State: InkLiquidity.poolSet true, no positions, vault not yet released. Owner calls setPremium(100000): passes (0 <= 100000 <= 100_000, positions.length == 0). After the window, release() -> addLiquidity() -> releaseStartTick() = floorTick + 100000 (INK is currency0) -> _addLadder lists all released INK from that tick. Expected per the published plan: ladder starting 16,000 ticks above opening. Actual: ladder starting 100,000 ticks above opening; no code path can move it afterwards (positions are permanent).","severity":"info","snippet":"        if (premium_ < 0 || premium_ > 100_000 || positions.length != 0) revert BadPremium();","title":"Trust assumption: owner can set the release listing premium up to 100,000 ticks (about 22,000x the opening price) at any time before release()"},{"citation":"resolved","description":"Asymmetry, stated in the natspec and in the brief ('portraits.isAgentHolder(tx.origin)'). Two user classes are treated differently from what 'waived for wallets holding an agent portrait' suggests: (a) an agent-holder EOA that submits transactions on behalf of others (a relayer, a 4337 bundler whose EOA sends the bundle, a searcher's EOA executing a backrun bundle) pays no hook fee on any of the swaps inside, whoever's order is filled; (b) an agent holder that is a smart-contract wallet (Safe, 7702-delegated accounts are fine since tx.origin stays the EOA) never gets the waiver even though it holds the portrait. Recorded as a design consequence, not a defect, so the judge can see that the exemption keys on the transaction sender rather than on the party whose funds move. No change is proposed; the brief fixes the rule.","line":174,"path":"src/InkHook.sol","reproduction":"State: pool open. EOA A holds an agent portrait since an earlier block and operates a relayer contract C. User U signs an order; A sends a transaction calling C, which swaps U's 1,000,000 IMD for INK via the router. afterSwap reads tx.origin = A, isAgentHolder(A) is true, fee = 0. Expected by a reader of 'waived for wallets holding an agent portrait': U (no portrait) pays 0.25%. Actual: no fee. Conversely a Safe S holding a portrait executes the same swap from its own module: tx.origin is the Safe's executor EOA, isAgentHolder(executor) is false, S pays the fee.","severity":"info","snippet":"        address swapper = tx.origin;","title":"Documented asymmetry: tx.origin exempts every swap in a transaction an agent-holder EOA sends and never exempts contract wallets"}],"hash":"af8c33935c8a01bced54e033b2256013928bfeb43773ae1c286bae13e0dcc31c","nodeId":"76867fc4-58e9-4017-911b-eb8fa627c04a","outcome":"completed","summary":"The review is complete. The findings file is at `.imd-findings.json` with 5 findings and 62 coverage rows, none unreached.\n\n**What I found in the Access Control, Trust Gap and Asymmetry area**\n\nThe hook itself holds. Every callback is gated to the PoolManager, pool binding is factory-only and single-use, the fee delta placement matches v4 core's afterSwap accounting, payout recipients are immutable, and there is no owner, admin, upgrade, selfdestruct or delegatecall surface. The live mainnet state also matches the hook's constructor: the four deployed contracts are owned by the launch owner, the liquidity contract's treasury and pool/position manager equal the hook's values and the official v4 addresses, the IMD address is the real token, and the vault is still unconfigured as expected. The mainnet launch factory's bytecode calls PoolManager.initialize directly, so the factory-only beforeInitialize guard will accept it.\n\nFindings, most severe first:\n\n- **Low, fee waiver rental.** A portrait handed over in block N exempts the new holder in block N+1, where it is handed back. One agent portrait can serve unlimited EOAs at a one-block cadence. The scratch test at `test/scratch/WaiverRental.t.sol` fails on this code as proof.\n- **Low, trust assumption.** The vault owner's single setup() call decides where up to 800M released INK and every INK hook fee go. The hook follows vault.liquidity() and nothing binds it to the live InkLiquidity. The owner is a plain EOA.\n- **Info, trust assumption.** The trading lock has no fallback. Only the owner's setInk, setPool and setup calls make release() reachable.\n- **Info, trust assumption.** The owner can set the release listing premium up to 100,000 ticks right up to the block before release.\n- **Info, documented asymmetry.** tx.origin exempts every swap in a transaction an agent-holder EOA sends and never exempts contract wallets.\n\n**What I did not cover.** Range math in InkLiquidity, randomness quality in the vault reveals, and the renderer internals belong to other specialists. The existing hook test suite passes. No tracked file was modified. The scratch test and the findings file are the only writes.","treeHash":null,"usage":{"cachedInputTokens":1393126,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":43021,"runtime":"claude","turns":30,"wallClockMs":659370}},{"artifacts":[],"attempt":1,"bundleHash":"7383862298d7a32246318077dc9e437bf08843f04ce92ab33b5a08a441efbe23","device":"829d84438fb3fdc8","findings":[],"hash":"b2a585da0ab0e68721f5e33564aa738ff9bc503a004219ce80f66391b01bbeee","nodeId":"8fd71d51-0516-4d5e-bf95-6843c668c066","outcome":"completed","summary":"The adaptation is complete and verified. Build passes and the full suite passes with the verifier's seed: 134 tests passed, 0 failed, 28 skipped (the existing optional fork tests that need an RPC).\n\n**What the launch deploys**\n\n- `src/LaunchToken.sol` (new): INK, a plain OpenZeppelin ERC-20 with no constructor arguments, 10^27 minor units minted once to the factory. No mint, owner, pause, blocklist, fee or upgrade. The 80% remainder is exactly the vault's 800,000,000 INK funding requirement.\n- `src/InkHook.sol`: logic kept as written, with one audited change. The constructor gains a seventh argument, the launch factory, written `$factory` in the manifest, and `beforeInitialize` now reverts `NotFactory` for any other sender. The first six arguments keep their order.\n\n**Audit findings**\n\n- dcacbade3b53 (medium, stranger binds the hook to a wrong pool): reproduced. Both supplied proof tests failed on the original code, so I applied the factory-only fix above, with tests adapted from the proofs plus a CREATE2 rehearsal where a probe factory deploys both contracts on an empty chain and then initializes the pool.\n- 73ee013031 (no lock fallback) and a63665098a41 (waiver keyed to `tx.origin`): both reproduce as described, and both are what the requester explicitly asked for. Left as documented trust assumptions.\n- ea9b327d8f6f (fee INK listed at the caller's tick): lives in the live InkLiquidity, which this launch cannot change. Documented, not changed.\n- The generic hook reference's \"settle the fee in ETH\" rule does not apply. The brief says not to sell hook fees for ETH, and the README states this.\n\n**Tests and fixtures**\n\n- `test/InkHookLaunch.t.sol` (new, 16 tests): token supply and transfer, factory-only initialization, every callback's unauthorized-caller guard, the trading lock, the 0.25% fee on a buy and a sell at exact input and exact output, payout of IMD to the treasury and INK to the vault's liquidity contract with nothing burned and no ETH, the agent-holder waiver, immutables and permission set, runtime size and forbidden-opcode scan.\n- Every existing fixture and script that constructs the hook now passes the account that initializes the pool in that fixture: InkSystem, AuditRegression, LiquidityCustodyInvariants, MainnetFork, Rehearse, LocalStack. InkSystem also gained a factory-only test and a zero-factory guard check.\n\n**Documentation**\n\n- `ADAPTATION.md` has a new top section listing each change, its file, the rule or finding that required it, the constructor table for the manifest step, and the verification results.\n- `README.md` documents the hook launch: the fee, that it is not settled in ETH and why, where each fee goes, the enabled callbacks, and what the token does not do.\n- `launch/mainnet-2-hook.json`: the portraits and vault placeholders filled with the live addresses. No `launch.json` was written.\n\nNo protected path was touched, no dependency was installed, and nothing was deployed or signed. The changes are in the working tree, uncommitted.","treeHash":"fb522de1ad023f23119a788d6a03189ad082f61d","usage":{"cachedInputTokens":2018825,"inputTokens":610,"model":"claude-fable-5-1","outputTokens":44720,"runtime":"claude","turns":65,"wallClockMs":723026}},{"artifacts":[],"attempt":1,"bundleHash":"397869a54e3256e96c7fc20177a773ac620f709fcf507f19198882bd02f9435b","device":"f3c310dfe443399d","findings":[{"description":"At valid spot ticks in the final tick-spacing interval, _rangeFrom clamps only the far endpoint. For INK as currency0 it can return a lower endpoint above both the upper endpoint and the maximum supported tick; the currency1 case is symmetric. This violates the existing RangeMath property that lower <= upper, and downstream TickMath calculations cannot use the out-of-domain endpoint. There is no valid strictly-beyond-spot range at the extreme, so the terminal empty-range behavior should be handled consistently. This is a boundary arithmetic defect; no loss-of-funds exploit at the launch price is claimed. Production code and the existing fuzz property were not changed.","line":270,"path":"src/InkLiquidity.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {InkLiquidity} from \"src/InkLiquidity.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {IPoolManager} from \"v4-core/interfaces/IPoolManager.sol\";\nimport {IPositionManager} from \"v4-periphery/src/interfaces/IPositionManager.sol\";\nimport {PoolKey} from \"v4-core/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/types/Currency.sol\";\nimport {IHooks} from \"v4-core/interfaces/IHooks.sol\";\nimport {TickMath} from \"v4-core/libraries/TickMath.sol\";\n\n// Only exposes the production pure-range calculation; no replacement arithmetic.\ncontract RangeEndProofHarness is InkLiquidity {\n    constructor()\n        InkLiquidity(\n            IPoolManager(address(0x100)), IPositionManager(address(0x200)),\n            address(0x300), address(0x400)\n        )\n    {}\n\n    function prime(bool inkIs0) external {\n        poolKey = PoolKey(\n            Currency.wrap(address(0x1000)), Currency.wrap(address(0x2000)),\n            12_500, 60, IHooks(address(0))\n        );\n        ink = IERC20(inkIs0 ? address(0x1000) : address(0x2000));\n        floorTick = 0;\n        premium = 0;\n    }\n\n    function range(int24 tick) external view returns (int24 lower, int24 upper) {\n        return _rangeFrom(tick, 60);\n    }\n}\n\ncontract RangeEndProofTest is Test {\n    function test_terminalUpperTickMustNotProduceAnInvertedRange() public {\n        RangeEndProofHarness h = new RangeEndProofHarness();\n        h.prime(true);\n        (int24 lower, int24 upper) = h.range(887270);\n        // A terminal empty range is already accepted by RangeMathTest; an inverted one is not.\n        assertLe(lower, upper, \"lower must not exceed upper for a valid spot tick\");\n        assertLe(lower, TickMath.maxUsableTick(60));\n        assertLe(upper, TickMath.maxUsableTick(60));\n    }\n\n    function test_terminalLowerTickMustNotProduceAnInvertedRange() public {\n        RangeEndProofHarness h = new RangeEndProofHarness();\n        h.prime(false);\n        (int24 lower, int24 upper) = h.range(-887270);\n        assertLe(lower, upper, \"lower must not exceed upper for a valid spot tick\");\n        assertGe(lower, TickMath.minUsableTick(60));\n        assertGe(upper, TickMath.minUsableTick(60));\n    }\n}","reproduction":"forge test --offline --match-path test/RangeMath.t.sol --fuzz-seed 0x2305 --fuzz-runs 2048 fails testFuzz_rangeNeverRevertsAndStaysUsable with tick=887270, raw width=-1, inkIs0=true (width is bounded by the existing test). A smaller direct input is spacing=60, floorTick=0, premium=0, width=60, tick=887270: expected an ordered clamped range (an empty range at 887220 is already allowed by the existing tests), actual lower=887280 and upper=887220. With inkIs0=false and tick=-887270, actual lower=-887220 and upper=-887280. The self-contained proof was run using forge test --offline --match-path test/scratch/RangeEndProof.t.sol -vv; both tests failed at assertLe(lower, upper) with these exact values.","severity":"low","title":"Terminal tick clamping produces inverted liquidity ranges"}],"hash":"c2ae41c25b2b930c80b4b39d23d99c48d75d2ffb0d96f57a3e87adea56ec7a3c","nodeId":"8842befa-552e-480b-a5ae-c5ee57371dbf","outcome":"completed","summary":"Added failure-path tests and three stateful invariant campaigns. Fresh offline build and verifier-seed tests pass: **162 passed, 28 existing skips**.\n\nA second seed exposed a low-severity range-clamping defect. The reproduced proof is in [.imd-findings.json](/home/seat18/.identitymd/work/aa9f4f0c-c7aa-49a8-b821-b0c189b1457d/8842befa-552e-480b-a5ae-c5ee57371dbf/.imd-findings.json).\n\nExisting formatting failures and five high-severity Slither results remain documented in [HookCoverage.md](/home/seat18/.identitymd/work/aa9f4f0c-c7aa-49a8-b821-b0c189b1457d/8842befa-552e-480b-a5ae-c5ee57371dbf/test/HookCoverage.md). Git staging was blocked by the read-only repository index.","treeHash":"c17d013b1a2a59047f6fa6339c916a36ed8a4884","usage":{"cachedInputTokens":7067264,"inputTokens":177872,"model":"gpt-6-astra","outputTokens":39813,"runtime":"codex","turns":11,"wallClockMs":1130418}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"6ef494db85781eec","findings":[{"citation":"resolved","description":"beforeInitialize ignores the `sender` argument and checks only that one currency is IMD. The hook is single-use (`poolCreated` is set forever on the first initialize), so whoever calls PoolManager.initialize first with a key whose hooks field is this hook decides the pool the hook is bound to: the other currency (native ETH or any token, since `ink` is simply the non-IMD side), the LP fee, the tick spacing and the opening sqrtPrice. If the factory deploys the hook and initializes the INK/IMD pool in two transactions (or the hook address is predictable before the factory's transaction lands), a third party can initialize e.g. {ETH, IMD, fee 0, spacing 1, hook} between them; the factory's own initialize then reverts PoolAlreadyCreated, `ink` is permanently wrong (or is the right INK but with an attacker-chosen fee tier and price, which InkLiquidity.setPool would then record as the floor), and the hook has to be redeployed. No funds are lost; the launch is griefed for gas. The reference manifest pattern (\"beforeInitialize accepts only the launch factory\") checks the sender; this hook does not. If the factory deploys the hook and initializes the pool atomically in one transaction the window does not exist, which the adapter should confirm rather than assume. Fix within the stated design: have beforeInitialize accept only the launch factory as `sender` (constructor argument \"$factory\") and/or only the exact intended key (fee, tickSpacing).","line":125,"path":"src/InkHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {Deployers} from \"v4-core-test/utils/Deployers.sol\";\nimport {MockERC20} from \"solmate/src/test/utils/mocks/MockERC20.sol\";\nimport {IHooks} from \"v4-core/interfaces/IHooks.sol\";\nimport {Hooks} from \"v4-core/libraries/Hooks.sol\";\nimport {TickMath} from \"v4-core/libraries/TickMath.sol\";\nimport {PoolKey} from \"v4-core/types/PoolKey.sol\";\nimport {Currency, CurrencyLibrary} from \"v4-core/types/Currency.sol\";\nimport {InkHook, IAgentPortraits, IInkVault} from \"src/InkHook.sol\";\n\ncontract StubPortraits {\n    function isAgentHolder(address) external pure returns (bool) { return false; }\n}\n\ncontract StubVault {\n    bool public released;\n    address public liquidity;\n}\n\n/// Proof: InkHook.beforeInitialize accepts any caller and any pool paired with IMD. Between the hook's\n/// deployment and the factory's own initialize, a third party can bind the one-pool hook to an ETH/IMD\n/// pool (or an INK/IMD pool with a wrong fee, tick spacing or price). The factory's initialize then\n/// reverts PoolAlreadyCreated and the hook is unusable for the launch.\ncontract HookPoolBindingTest is Test, Deployers {\n    InkHook hook;\n    MockERC20 imd;\n    MockERC20 ink;\n    address factory = makeAddr(\"factory\");\n    address attacker = makeAddr(\"attacker\");\n\n    function setUp() public {\n        deployFreshManagerAndRouters();\n        imd = new MockERC20(\"Identity.md\", \"IMD\", 18);\n        ink = new MockERC20(\"INK\", \"INK\", 18);\n        address hookAddr = address(\n            uint160(\n                Hooks.BEFORE_INITIALIZE_FLAG | Hooks.BEFORE_SWAP_FLAG | Hooks.AFTER_SWAP_FLAG\n                    | Hooks.AFTER_SWAP_RETURNS_DELTA_FLAG\n            ) ^ (0x4444 << 144)\n        );\n        deployCodeTo(\n            \"InkHook.sol:InkHook\",\n            abi.encode(\n                manager,\n                IAgentPortraits(address(new StubPortraits())),\n                IInkVault(address(new StubVault())),\n                Currency.wrap(address(imd)),\n                makeAddr(\"treasury\"),\n                uint16(25)\n            ),\n            hookAddr\n        );\n        hook = InkHook(hookAddr);\n    }\n\n    function _inkKey() internal view returns (PoolKey memory) {\n        bool inkIs0 = address(ink) < address(imd);\n        return PoolKey({\n            currency0: Currency.wrap(inkIs0 ? address(ink) : address(imd)),\n            currency1: Currency.wrap(inkIs0 ? address(imd) : address(ink)),\n            fee: 12_500,\n            tickSpacing: 200,\n            hooks: IHooks(address(hook))\n        });\n    }\n\n    /// A stranger binds the hook to an ETH/IMD pool first; the real INK/IMD initialize is then refused.\n    function test_strangerCanBindHookToWrongPoolBeforeFactory() public {\n        PoolKey memory wrong = PoolKey({\n            currency0: CurrencyLibrary.ADDRESS_ZERO,\n            currency1: Currency.wrap(address(imd)),\n            fee: 0,\n            tickSpacing: 1,\n            hooks: IHooks(address(hook))\n        });\n        vm.prank(attacker, attacker);\n        // Expected: the hook refuses a pool the launch did not create (caller is not the factory / wrong key).\n        vm.expectRevert();\n        manager.initialize(wrong, TickMath.getSqrtPriceAtTick(0));\n\n        // The factory's own initialize must still succeed.\n        vm.prank(factory, factory);\n        manager.initialize(_inkKey(), TickMath.getSqrtPriceAtTick(0));\n        assertTrue(hook.poolCreated());\n        assertEq(Currency.unwrap(hook.ink()), address(ink));\n    }\n\n    /// The same key, but a stranger picks the fee tier and the opening price; the factory's own call then fails.\n    function test_strangerCanFixWrongFeeAndPriceBeforeFactory() public {\n        PoolKey memory k = _inkKey();\n        k.fee = 0;\n        k.tickSpacing = 1;\n        vm.prank(attacker, attacker);\n        vm.expectRevert();\n        manager.initialize(k, TickMath.getSqrtPriceAtTick(TickMath.MAX_TICK - 1));\n\n        vm.prank(factory, factory);\n        manager.initialize(_inkKey(), TickMath.getSqrtPriceAtTick(0));\n        assertTrue(hook.poolCreated());\n    }\n}","reproduction":"State: InkHook deployed at its mined address with imd = IMD. Attacker (any EOA) calls PoolManager.initialize({currency0: 0x0 (ETH), currency1: IMD, fee: 0, tickSpacing: 1, hooks: hook}, sqrtPriceAtTick(0)). Expected: hook reverts (not the launch's pool / not the factory). Actual: beforeInitialize returns its selector, poolCreated = true, ink = ETH, PoolOpened(ETH) is emitted. Factory then calls PoolManager.initialize({INK, IMD, 12500, 200, hook}, price) and it reverts PoolAlreadyCreated. Variant: same key as the launch but fee 0 / tickSpacing 1 / sqrtPriceAtTick(MAX_TICK-1) is accepted and fixes the pool's fee tier and price. See test/scratch/HookPoolBinding.t.sol (2 tests, both fail on current code because the stranger's initialize does not revert).","severity":"medium","snippet":"        if (poolCreated) revert PoolAlreadyCreated();\n        if (address(key.hooks) != address(this)) revert WrongPool();\n        if (key.currency0 == imd) ink = key.currency1;\n        else if (key.currency1 == imd) ink = key.currency0;\n        else revert WrongPool();","title":"InkHook.beforeInitialize lets any caller bind the one-pool hook to a wrong pool (currency, fee tier, tick spacing, price) before the factory does"},{"citation":"resolved","description":"The requester asked for exactly this (no time-based fallback), so this is recorded as the trust assumption it is, with the concrete states that trigger it. `released` can only become true through InkVault.release(), which needs statsRoot set by setup(), which needs setInk(), which reverts NotFunded unless the vault already holds SEATS * PORTRAIT = 800,000,000e18 INK (src/InkVault.sol:164 and :178). The launch draft sends 80% of supply to the vault via remainderTo, so supply must be exactly 1,000,000,000e18 (the figure the tests assume) or more; any smaller supply, a remainderTo that is not the live vault, or the single owner EOA 0x824E…06E3 never calling setInk/setup (or losing its key) leaves released == false permanently. Every swap on the hook's pool then reverts TradingNotOpen forever; the factory-seeded single-sided INK position and any later deposits are untradeable, and the hook has no owner to intervene. There is also no way to re-point the hook at another vault. The adapter should verify the token supply/allocation against the 800M requirement before deploying.","line":146,"path":"src/InkHook.sol","reproduction":"State: hook pool initialized and seeded; vault.released() == false (e.g. INK supply 100,000,000e18 so the vault holds 80,000,000e18 < 800,000,000e18: setInk(ink) reverts NotFunded, so setup() and release() can never be called). Any swap: PoolManager.swap(key, …) → InkHook.beforeSwap → vault.released() false → revert TradingNotOpen. Expected per launch intent: trading opens after the 24-hour claim window. Actual: no path ever opens it; tradingOpen() stays false indefinitely.","severity":"low","snippet":"        if (tradingOpenedAt == 0) {\n            if (!vault.released()) revert TradingNotOpen();","title":"Trading lock has no fallback: if InkVault setup never completes (setInk NotFunded, owner inaction or key loss), the pool and its seeded 10% of supply are locked forever"},{"citation":"resolved","description":"Documented in the hook's own comment, so this is a design tradeoff rather than a defect, but the adapter and reviewers should know its concrete shape. The waiver is meant for \"wallets holding an agent portrait\". Because the check reads tx.origin, (a) a bundler, relayer, aggregator operator or MEV searcher whose sending EOA holds one agent portrait (a 400,000 INK pull, or a marketplace purchase) pays no hook fee on any swap routed through its transactions, including swaps executed for third parties who hold nothing, so the 0.25% fee can be sold as a service; (b) ERC-4337 and multisig wallets that hold agent portraits never get the waiver because tx.origin is the bundler/relayer; (c) the one-block holding requirement (isAgentHolder: agentHolderSince < block.number) prevents same-block borrowing only. Nothing in the hook can be changed later. If the requester wants the waiver to follow the trading wallet, the standard pattern is to use the `sender` passed to afterSwap when it is a known router and the router's msgSender(), or hookData signed by the holder.","line":161,"path":"src/InkHook.sol","reproduction":"State: EOA R holds agent portrait #7 since block N. In block N+1, R sends a transaction to a contract C that performs 50 swaps on the INK/IMD pool on behalf of 50 unrelated users (pulling their IMD via allowances). For each swap: afterSwap → portraits.isAgentHolder(tx.origin = R) → true → return 0 fee. Expected: 0.25% of the unspecified side taken on each of the 50 swaps; actual: 0. Conversely a Safe multisig holding portrait #7 executes a swap via a relayer EOA → tx.origin = relayer → full fee charged.","severity":"low","snippet":"        address swapper = tx.origin;\n\n        if (feeBps == 0 || portraits.isAgentHolder(swapper)) return (IHooks.afterSwap.selector, 0);","title":"Agent-holder fee waiver is keyed to tx.origin: an agent-holder EOA exempts every swap in a transaction it sends for others, and smart-contract wallets can never be exempt"},{"citation":"resolved","description":"INK hook fees are paid by InkHook.collectFees() to vault.liquidity() (InkLiquidity), where they sit until at least MIN_ADD = 400,000 INK has built up (160,000,000 INK of INK-side volume at 25 bps); then anyone may call addLiquidity(), which places all of it single-sided just past the current tick, bounded only by the release floor (floorTick + premium). Nothing prevents the caller from first moving the price with a swap in the same transaction and placing the position where it suits them, then buying the newly listed INK back. The protocol-level effect is that fee INK, which the launch describes as \"added back to the pool as permanent liquidity\", can be listed at a depressed price chosen by the caller instead of the market price; the IMD it earns on the way back goes to the LP positions/treasury, so the loss is to the pool's INK pricing rather than to a specific holder. This was recorded as an accepted design assumption (finding 942e691ce965 in ADAPTATION.md) and is bounded by the floor and the 1.25% + 0.25% round-trip fees; it is restated here because the hook launch is what makes the fee INK flow exist. It cannot be fixed in the live InkLiquidity; the mitigation available to this launch is operational (a keeper calling addLiquidity promptly at quiet moments) or routing hook INK fees elsewhere, which would change the stated design.","line":170,"path":"src/InkLiquidity.sol","reproduction":"State: released; price at tick T well above releaseStartTick S (INK is currency1 in the tests, so lower tick = dearer INK; use the mirrored direction if INK is currency0); InkLiquidity holds 400,000 INK of hook fees. Attacker in one transaction: (1) sells INK until tick = S (cheapest allowed listing); (2) calls liq.addLiquidity() → _rangeFrom(S, 20_000) → 400,000 INK listed in [S-20000, S] right below the depressed price; (3) buys INK back through that fresh position at prices starting at S instead of T. Expected: fee INK joins the pool at the undisturbed market price; actual: at the attacker-chosen floor-adjacent price; attacker profit ≈ (price(T) − price(S)) × INK bought from the new position − 1.5% round-trip fees − slippage of step (1).","severity":"low","snippet":"        (, int24 tick,,) = poolManager.getSlot0(poolKey.toId());\n        int24 start = releaseStartTick();\n        if (_inkIs0() ? tick <= start : tick >= start) return _addLadder(start, amount);\n        (int24 lower, int24 upper) = _rangeFrom(tick, width);\n        positionId = _add(lower, upper, amount);","title":"InkLiquidity.addLiquidity() lists accumulated fee INK at the caller's spot tick, so anyone can push the price down to the release floor and list the hook's INK fees cheaply for themselves"},{"citation":"resolved","description":"Read in full: src/InkHook.sol (the contract this launch deploys), src/InkVault.sol, src/InkLiquidity.sol, src/InkPortraits.sol, src/InkRenderer.sol, src/InkFeeRouter.sol (not part of this launch), test/InkSystem.t.sol setup (launch simulation), test/helpers/ConfiguredLiquidity.sol, launch/*.json, ADAPTATION.md, README.md. Checked against the Pashov guide: v4 callback authentication (onlyPoolManager on every state-changing hook entry and on unlockCallback; permissions match getHookPermissions and are validated in the constructor), swap-delta sign handling for all four exact-input/exact-output × direction cases (specifiedIs0 logic is correct and the fee is taken from the unspecified side), fee claim accounting (mint inside afterSwap offsets the hookDelta credit, net zero; collectFees burns and takes, net zero), overflow/cast bounds, reentrancy (unlock cannot nest; ERC20s are plain), rounding (fee < 400 wei of the unspecified side rounds to 0, dust), access control (no owner on the hook; vault/liquidity/portraits/renderer owners are the stated single EOA with Ownable2Step and guarded renounce), the vault's pull/reveal/admission accounting and the liquidity ladder/range math (no defect found beyond the recorded design assumptions). Reached off-repository: the IMD token source (0xd34a…63b7, Sourcify exact match): a LayerZero OFT over plain OpenZeppelin ERC20 with owner-updatable name/symbol only; no transfer tax, pause or blacklist, so poolManager.take(IMD, treasury) and the fee router paths are not at risk from token behaviour. Not reached within the budget: the IMD launch factory (its atomicity of hook deploy + initialize, the token it mints, its supply and the 10%/10%/80% split that the 800M INK vault requirement depends on), the deployed PoolManager/PositionManager bytecode on mainnet, the identity.md seat contract 0x0000ec93…ec1d, the deployed-bytecode equivalence of the four live contracts (SOURCE_PROVENANCE.json was taken as given), and the full existing test suite (not run; only the scratch proof was executed). No finding above depends on anything unreached except where stated.","line":40,"path":"src/InkHook.sol","reproduction":"Not applicable: coverage statement required by the acceptance criteria.","severity":"info","snippet":"contract InkHook is IHooks, IUnlockCallback {","title":"Coverage: contracts read, checks run, and what could not be reached"},{"citation":"resolved","description":"Facts the manifest step must get right, taken from the code: (1) permissions must be exactly [\"beforeInitialize\",\"beforeSwap\",\"afterSwap\",\"afterSwapReturnDelta\"] (getHookPermissions, validated against the address bits in the constructor; a mismatched salt reverts deployment). (2) constructorArgs has six entries in declaration order: \"$poolManager\", portraits 0x54fa3515848440ddde2aefe45a382bd868cacd15, vault 0x33c85714c7d78fdb253fe1cf761d2d5c3c12d747, imd (must be the pool's paired currency 0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7, which InkLiquidity.MAINNET_IMD also enforces on chain 1), treasury 0xbc10b3662231c830d2ee10bc6359db17bea4b3d4, \"25\". (3) beforeInitialize reverts WrongPool unless one side of the key equals the constructor's imd, so an imd argument that differs from pool.pairedCurrency makes the factory's initialize fail; and it accepts any pool.fee/tickSpacing, so the policy's fee tier (the tests use 12500 / 200) is unconstrained by the hook. (4) feeBps > 100 reverts FeeTooHigh; zero addresses for portraits/vault/treasury revert ZeroAddress; imd is not zero-checked (ETH pairing would be accepted by the constructor but is not this launch). (5) The hook has no owner, no initializer, no delegatecall/selfdestruct, no mint/pause/blacklist: nothing in the forbidden list is present. (6) The token has no constructor arguments and must send 80% of supply (≥ 800,000,000e18) to the vault for setInk to succeed; see the trading-lock finding.","line":93,"path":"src/InkHook.sol","reproduction":"Not applicable: configuration notes. Failing example for (3): hook constructed with imd = X ≠ IMD, factory initializes {INK, IMD, …, hook} → beforeInitialize: currency0 != X and currency1 != X → revert WrongPool.","severity":"info","snippet":"    function getHookPermissions() public pure returns (Hooks.Permissions memory) {","title":"Adapter notes for launch.json: hook permissions, constructor order, and the IMD-currency binding that makes initialize revert if wrong"}],"hash":"c683cfe1ed09f2dd1f39081da9310c3d0e64f03fc0b285d9a7d484c58ecaa424","nodeId":"a83fae29-6b03-42b8-b032-9c2a1789f284","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds six findings. No source file was changed; the only writes are the findings file and one scratch proof test, both ignored by git.\n\n**Contracts read in full:** InkHook, InkVault, InkLiquidity, InkPortraits, InkRenderer, InkFeeRouter, plus the system test's launch simulation and the launch drafts. Off-repository I reached the IMD token's verified source on Sourcify. It is a plain OpenZeppelin ERC20 wrapped as a LayerZero OFT, with no tax, pause or blacklist, so the fee payout paths are safe from token behaviour. **Not reached:** the IMD launch factory (whether hook deploy and pool initialize are atomic, and the token supply it mints), mainnet PoolManager and PositionManager bytecode, the seat NFT contract, and the existing test suite was not run.\n\n**Findings written:**\n\n- **Medium, with proof.** `beforeInitialize` in `src/InkHook.sol:125` ignores the caller and accepts any pool with IMD on one side. Anyone who moves before the factory can bind the single-use hook to an ETH/IMD pool, or to INK/IMD with their own fee tier, tick spacing and opening price. The factory's own initialize then reverts and the hook must be redeployed. The scratch test fails on current code and passes against a copy that restricts the sender to the factory. If the factory deploys and initializes in one transaction the window does not exist, which the adapter should confirm.\n- **Low.** The trading lock has no fallback. Vault setup requires the vault to already hold 800,000,000 INK, so a supply below 1 billion, a wrong remainderTo, or owner inaction leaves every swap reverting forever with the seeded liquidity stuck. The requester chose this design, so it is recorded as a trust assumption with its concrete failing states.\n- **Low.** The fee waiver reads tx.origin. An agent-holder EOA exempts every swap in transactions it sends for others, and smart-contract wallets never qualify. Documented in the hook's comment.\n- **Low.** Hook INK fees flow to InkLiquidity, where anyone can list them at a self-chosen spot tick after pushing the price toward the release floor. Previously accepted as a design assumption; restated because this launch creates the fee flow.\n- **Info.** Coverage statement, and adapter notes for the manifest: the four required permissions, the six constructor arguments in order, and that an `imd` argument differing from the paired currency makes initialize revert.\n\nThe vault's pull, reveal and admission accounting, the liquidity ladder and range math, the v4 delta signs for all four swap cases, and the claim-based fee settlement all checked out with no defect beyond those listed.","treeHash":null,"usage":{"cachedInputTokens":1783782,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":42505,"runtime":"claude","turns":24,"wallClockMs":780870}}],"verification":[{"checks":[{"durationMs":26869,"exitCode":0,"name":"build","output":"Compiling 190 files with Solc 0.8.26\nSolc 0.8.26 finished in 26.57s\nCompiler run successful with warnings:\nWarning (2519): This declaration shadows an existing declaration.\n  --> script/LocalStack.s.sol:95:26:\n   |\n95 |     function _deployHook(InkPortraits portraits, InkVault vault, address treasury)\n   |                          ^^^^^^^^^^^^^^^^^^^^^^\nNote: The shadowed declaration is here:\n  --> script/LocalStack.s.sol:38:5:\n   |\n38 |     InkPortraits portraits;\n   |     ^^^^^^^^^^^^^^^^^^^^^^\n\nWarning (2519): This declaration shadows an existing declaration.\n  --> script/LocalStack.s.sol:95:50:\n   |\n95 |     function _deployHook(InkPortraits portraits, InkVault vault, address treasury)\n   |                                                  ^^^^^^^^^^^^^^\nNote: The shadowed declaration is here:\n  --> script/LocalStack.s.sol:39:5:\n   |\n39 |     InkVault vault;\n   |     ^^^^^^^^^^^^^^\n\nWarning (2519): This declaration shadows an existing declaration.\n   --> script/LocalStack.s.sol:128:20:\n    |\n128 |     function _seed(PoolKey memory key, bool inkIs0, address to, ForkInk ink) internal {\n    |                    ^^^^^^^^^^^^^^^^^^\nNote: The shadowed declaration is here:\n  --> script/LocalStack.s.sol:44:5:\n   |\n44 |     PoolKey key;\n   |     ^^^^^^^^^^^\n\nWarning (2519): This declaration shadows an existing declaration.\n   --> script/LocalStack.s.sol:128:65:\n    |\n128 |     function _seed(PoolKey memory key, bool inkIs0, address to, ForkInk ink) internal {\n    |                                                                 ^^^^^^^^^^^\nNote: The shadowed declaration is here:\n  --> script/LocalStack.s.sol:43:5:\n   |\n43 |     ForkInk ink;\n   |     ^^^^^^^^^^^\n\nWarning (3149): The result type of the shift operation is equal to the type of the first operand (uint128) ignoring the (larger) type of the second operand (uint256) which might be unexpected. Silence this warning by either converting the first or the second operand to the type of the other.\n   --> src/InkLiquidity.sol:196:31:\n    |\n196 |             uint128 unitLiq = uint128(1e24) << k;\n    |                               ^^^^^^^^^^^^^^^^^^\n\n","passed":true},{"durationMs":14968,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/LaunchFeeHandoff.t.sol:LaunchFeeHandoffTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 744.96µs (0.00ns CPU time)\n\nRan 1 test for test/SybilOptions.t.sol:SybilPremium5xTest\n[SKIP] test_fork_sybilScenario() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 916.90µs (26.42µs CPU time)\n\nRan 7 tests for test/MainnetFork.t.sol:MainnetForkInkIs1Test\n[SKIP] test_fork_agentHolderPaysNoHookFee() (gas: 0)\n[SKIP] test_fork_bigBuyAfterRelease() (gas: 0)\n[SKIP] test_fork_fullLifecycle() (gas: 0)\n[SKIP] test_fork_hookFeeExactInBuyIsInkToLiquidity() (gas: 0)\n[SKIP] test_fork_lockedUntilReleaseHoweverLong() (gas: 0)\n[SKIP] test_fork_sellsPayFeesBothWays() (gas: 0)\n[SKIP] test_fork_sybilFirstHour() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 7 skipped; finished in 1.20ms (375.20µs CPU time)\n\nRan 1 test for test/SybilOptions.t.sol:SybilPremium2xTest\n[SKIP] test_fork_sybilScenario() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 1.39ms (27.40µs CPU time)\n\nRan 1 test for test/LaunchDay.t.sol:LaunchDayTest\n[SKIP] test_fork_launchDay() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 1.76ms (22.65µs CPU time)\n\nRan 1 test for test/SybilOptions.t.sol:SybilPremium1xTest\n[SKIP] test_fork_sybilScenario() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 1.90ms (25.38µs CPU time)\n\nRan 7 tests for test/MainnetFork.t.sol:MainnetForkInkIs0Test\n[SKIP] test_fork_agentHolderPaysNoHookFee() (gas: 0)\n[SKIP] test_fork_bigBuyAfterRelease() (gas: 0)\n[SKIP] test_fork_fullLifecycle() (gas: 0)\n[SKIP] test_fork_hookFeeExactInBuyIsInkToLiquidity() (gas: 0)\n[SKIP] test_fork_lockedUntilReleaseHoweverLong() (gas: 0)\n[SKIP] test_fork_sellsPayFeesBothWays() (gas: 0)\n[SKIP] test_fork_sybilFirstHour() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 7 skipped; finished in 2.34ms (144.93µs CPU time)\n\nRan 2 tests for test/SnapshotProofs.t.sol:SnapshotProofsTest\n[PASS] test_realSnapshotProofsClaim() (gas: 3222192)\n[PASS] test_tamperedStatsAreRejected() (gas: 605318)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 7.01ms (7.19ms CPU time)\n\nRan 9 tests for test/SnapshotAllSeats.t.sol:SnapshotAllSeatsTest\n[SKIP] test_claims_0000() (gas: 0)\n[SKIP] test_claims_0250() (gas: 0)\n[SKIP] test_claims_0500() (gas: 0)\n[SKIP] test_claims_0750() (gas: 0)\n[SKIP] test_claims_1000() (gas: 0)\n[SKIP] test_claims_1250() (gas: 0)\n[SKIP] test_claims_1500() (gas: 0)\n[SKIP] test_claims_1750() (gas: 0)\n[SKIP] test_treeHasEverySeatOnce() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 9 skipped; finished in 26.09ms (3.25ms CPU time)\n\nRan 3 tests for test/InkHookLaunch.t.sol:InkHookFactoryLaunchTest\n[PASS] test_constructorsNeedNoLiveDependency() (gas: 69495)\n[PASS] test_factoryBindsThePoolOthersCannot() (gas: 346368)\n[PASS] test_runtimeSizeAndOpcodes() (gas: 2267700)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 51.08ms (10.48ms CPU time)\n\nRan 4 tests for test/SepoliaLaunch.t.sol:SepoliaLaunchTest\n[PASS] test_allFiveRuntimesMeetProtectedFloor() (gas: 18566852)\n[PASS] test_existingOwnerSetupRemainsRequiredAndAvailable() (gas: 441282)\n[PASS] test_factoryDeploymentPreservesOwnersRoyaltiesAndReferences() (gas: 183938)\n[PASS] test_factoryTreasuryAndOutsiderHaveNoAdminRole() (gas: 1701232)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 68.68ms (64.66ms CPU time)\n\nRan 1 test for test/TokenURIPreview.t.sol:TokenURIPreviewTest\n[PASS] test_writeTokenURIs() (gas: 25423286)\nLogs:\n  read-only profile: not written out-preview/agent-280.txt (use FOUNDRY_PROFILE=preview)\n  read-only profile: not written out-preview/pool.txt (use FOUNDRY_PROFILE=preview)\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 69.27ms (68.98ms CPU time)\n\nRan 13 tests for test/InkHookLaunch.t.sol:InkHookLaunchTest\n[PASS] test_agentHolderPaysNoHookFee() (gas: 1467061)\n[PASS] test_callbacksRefuseEveryoneButThePoolManager() (gas: 1143610)\n[PASS] test_collectFeesPaysImdToTreasuryAndInkToLiquidity() (gas: 1503107)\n[PASS] test_factoryInitializesExactlyOnePoolPairedWithImd() (gas: 221940)\n[PASS] test_feeOnBuyExactInput() (gas: 1090718)\n[PASS] test_feeOnBuyExactOutput() (gas: 1089619)\n[PASS] test_feeOnSellExactInput() (gas: 1052158)\n[PASS] test_feeOnSellExactOutput() (gas: 1052991)\n[PASS] test_feeRateAndBindingsAreImmutable() (gas: 62563)\n[PASS] test_noSwapUntilTheVaultReleases() (gas: 1682408)\n[PASS] test_strangerCannotBindHookToWrongPoolBeforeFactory() (gas: 184498)\n[PASS] test_strangerCannotFixWrongFeeAndPriceBeforeFactory() (gas: 161648)\n[PASS] test_tokenIsFixedSupplyInk() (gas: 263361)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 92.85ms (15.53ms CPU time)\n\nRan 3 tests for test/RendererBoundaryProperties.t.sol:RendererBoundaryPropertiesTest\n[PASS] testFuzz_binaryChunksRoundTripAndLock(bytes,bytes) (runs: 1000, μ: 464050, ~: 460611)\n[PASS] test_chunkLimitAndRejectedWritesDoNotDamageUploadedArt() (gas: 5657977)\n[PASS] test_ownershipTransferRevokesOldOwnerAcrossEveryRendererMutation() (gas: 615341)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 95.40ms (95.85ms CPU time)\n\nRan 4 tests for test/RangeMath.t.sol:RangeMathTest\n[PASS] testFuzz_rangeNeverRevertsAndStaysUsable(int24,int24,bool) (runs: 256, μ: 118220, ~: 118357)\n[PASS] test_maxWidthClampsWhenInkIsCurrency0() (gas: 131887)\n[PASS] test_maxWidthClampsWhenInkIsCurrency1() (gas: 115332)\n[PASS] test_ordinaryWidthUnchanged() (gas: 118547)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 275.34ms (274.86ms CPU time)\n\nRan 6 tests for test/InkRenderer.t.sol:InkRendererTest\n[PASS] test_agentTraitsMatchTheScript() (gas: 61685138)\n[PASS] test_noClaimsAfterTheFirstSwarmPortrait() (gas: 1397672)\n[PASS] test_poolTraitsMatchTheScript() (gas: 60520738)\n[PASS] test_scriptStoredInChunksAndLocked() (gas: 12171384)\n[PASS] test_swarmNumbersFillUnclaimedSeats() (gas: 163375864)\n[PASS] test_tokenURIForAnAgentPortrait() (gas: 3733117)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 275.62ms (545.38ms CPU time)\n\nRan 55 tests for test/InkSystem.t.sol:InkSystemTest\n[PASS] test_agentClaimsOwnPortraitFree() (gas: 311177)\n[PASS] test_agentDepositReturnsExactlyItsBacking() (gas: 3516061)\n[PASS] test_agentHoldersPayNoHookFee() (gas: 2734061)\n[PASS] test_agentHoldersRerollForHalf() (gas: 4214951)\n[PASS] test_borrowedAgentPortraitGetsNoWaiver() (gas: 2555036)\n[PASS] test_cantLockAnEmptyRenderer() (gas: 3138116)\n[PASS] test_claimRules() (gas: 465550)\n[PASS] test_collectFeesPaysImdToTreasuryAndKeepsInk() (gas: 3367034)\n[PASS] test_contractURIOwnerOnly() (gas: 94718)\n[PASS] test_contractWalletSeatCanClaim() (gas: 387248)\n[PASS] test_depositMinOut() (gas: 3038333)\n[PASS] test_depositedAgentPortraitIsPullable() (gas: 3344870)\n[PASS] test_feeRouterClaimAndRoute() (gas: 4381579)\n[PASS] test_feeRouterNeedsPoolAndAddresses() (gas: 3872711)\n[PASS] test_feeRouterSplitsAndCompounds() (gas: 4127736)\n[PASS] test_gasAtScale() (gas: 78817084)\nLogs:\n  pull(1) gas: 161004\n  revealNext(1) gas: 221322\n    per reveal: 221322\n  pull(10) gas: 548232\n  revealNext(10) gas: 1140496\n    per reveal: 114049\n  pull(100) gas: 4762670\n  revealNext(100) gas: 10847735\n    per reveal: 108477\n  pull(400) gas: 18812864\n  revealNext(400) gas: 43238119\n    per reveal: 108095\n  deposit(100 portraits) gas: 6950387\n\n[PASS] test_guards() (gas: 12641088)\n[PASS] test_hookFeeImdToTreasury() (gas: 2210755)\n[PASS] test_hookFeeInkGoesToLiquidity() (gas: 2701726)\n[PASS] test_idleSeatClaimsToo() (gas: 227103)\n[PASS] test_inkFeesGoBackIntoThePool() (gas: 6092322)\n[PASS] test_launchSplit() (gas: 67244)\n[PASS] test_liquidityCannotRenounceBeforePoolIsConfigured() (gas: 3071147)\n[PASS] test_liquidityFloorHoldsIfPriceIsDragged() (gas: 6606776)\n[PASS] test_liquidityOwnershipTransferRequiresAcceptance() (gas: 222592)\n[PASS] test_liquidityRefusesUnrelatedNFTsButKeepsPositionCustody() (gas: 2106517)\n[PASS] test_mainnetPoolMustPairWithOfficialIMD() (gas: 3079882)\n[PASS] test_noPerWalletLimitAfterRelease() (gas: 2124588)\n[PASS] test_noPullsUntilRelease() (gas: 2230624)\n[PASS] test_onlyOnePool() (gas: 46381)\n[PASS] test_onlyTheFactoryCanInitialize() (gas: 54112)\n[PASS] test_ownerIsOwnerWalletNotDeployer() (gas: 7990817)\n[PASS] test_permissionlessListingTracksSpotAboveReleaseFloor() (gas: 3372170)\n[PASS] test_poolLockedUntilRelease() (gas: 2618366)\n[PASS] test_poolNeverOpensWithoutRelease() (gas: 2477170)\n[PASS] test_premiumBeforePoolCannotBeFrontRun() (gas: 6049502)\n[PASS] test_premiumCannotBeLockedByListingBeforeRelease() (gas: 6665634)\n[PASS] test_pullRerollPullAgain() (gas: 3918561)\n[PASS] test_releaseLadderShape() (gas: 2319548)\n[PASS] test_releasePremiumListsAboveTheOpeningPrice() (gas: 1925754)\n[PASS] test_releaseTurnsUnclaimedSeatsIntoSlotsAndPoolInk() (gas: 2074111)\n[PASS] test_revealIsNotEarlyAndNotTwice() (gas: 2625250)\n[PASS] test_seatSoldDuringWindowNewOwnerClaims() (gas: 301442)\n[PASS] test_setPoolRejectsHooklessPoolAndRemainsCorrectable() (gas: 3123371)\n[PASS] test_setPoolRejectsInconsistentHookBindings() (gas: 6644412)\n[PASS] test_setPoolRequiresPositionManagerBoundToSameManager() (gas: 3102547)\n[PASS] test_setupRules() (gas: 3137736)\n[PASS] test_setupStartMustBeSoon() (gas: 3127148)\n[PASS] test_traitRerollBeforeReleaseReverts() (gas: 39919)\n[PASS] test_traitRerollExpiredHashClosesWithoutFreeDraw() (gas: 2971955)\n[PASS] test_traitRerollGuards() (gas: 3463065)\n[PASS] test_traitRerollHalfPriceForAgentHolders() (gas: 3130793)\n[PASS] test_traitRerollRepaintsSameToken() (gas: 3032508)\n[PASS] test_vaultStaysFullyBacked() (gas: 3971924)\n[PASS] test_zeroTradingFee() (gas: 29321)\nSuite result: ok. 55 passed; 0 failed; 0 skipped; finished in 275.63ms (238.26ms CPU time)\n\nRan 16 tests for test/LaunchSafety.t.sol:LaunchSafetyTest\n[PASS] testFuzz_directPortraitTransfersCannotBypassDeposit(bool,uint8) (runs: 256, μ: 624869, ~: 407054)\n[PASS] testFuzz_expiredPullChargesPurchaseFeeAndPreservesBacking(uint16,uint16,bool) (runs: 256, μ: 565015, ~: 567933)\n[PASS] test_agentHolderDiscountOnlyOnOwnPullsStillHeld() (gas: 3268385)\n[PASS] test_cannotRenounceBeforeRequiredConfiguration() (gas: 478947)\n[PASS] test_expiredBatchRespectsProcessingLimit() (gas: 691194)\n[PASS] test_expiredHeadCannotStallLaterLivePull() (gas: 800011)\n[PASS] test_expiredTraitRerollConsumesFeeAndRequiresNewPayment() (gas: 977128)\n[PASS] test_nonHolderPaysFullFeeThroughAgentHoldingContract() (gas: 1368938)\n[PASS] test_ownershipTransferRequiresAcceptance() (gas: 640058)\n[PASS] test_portraitsCannotBeSentToLiquidity() (gas: 586110)\n[PASS] test_pullLastAvailableBlockRevealsWithoutExpiryFee() (gas: 484320)\n[PASS] test_setInkRequiresCodeAndFullAllocationBeforeItCommits() (gas: 319403)\n[PASS] test_setupRejectsWrongTokenLiquidityAndRemainsCorrectable() (gas: 1228493)\n[PASS] test_traitRerollLastAvailableBlockUsesOriginalHash() (gas: 688736)\n[PASS] test_traitRerollThroughAgentHoldingContractPaysFullFee() (gas: 1216106)\n[PASS] test_vaultAndRendererBindingFailuresRemainCorrectable() (gas: 433271)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 328.45ms (398.57ms CPU time)\n\nRan 3 tests for test/MainnetContractsLaunch.t.sol:MainnetContractsLaunchTest\n[PASS] test_exactFourContractOrderReferencesAndOwners() (gas: 221680)\n[PASS] test_factoryTreasuryAndOriginHaveNoAdministrativeRights() (gas: 1697915)\n[PASS] test_runtimeSizeAndOpcodesMeetProtectedFloor() (gas: 16755780)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 686.56ms (40.34ms CPU time)\n\nRan 15 tests for test/AuditRegression.t.sol:AuditRegressionTest\n[PASS] testFuzz_admissionCompletesWithBothRevealEntryPoints(uint16,bool) (runs: 256, μ: 23684681, ~: 15072138)\n[PASS] test_directSafeTransfersRejectedForBothOverloadsAndOtherNFTs() (gas: 900066)\n[PASS] test_expiredHeadPullNeverWidensALaterPullsDraw() (gas: 2219191)\n[PASS] test_expiredPullsCloseWithoutWideningDrawsOrRenewing() (gas: 1439598)\n[PASS] test_hashExpiryDuringAdmissionKeepsOriginalDraw() (gas: 47716799)\n[PASS] test_initializedPoolAtZeroTickIsValid() (gas: 4800059)\n[PASS] test_largeAdmissionQueueMakesProgressWithinGasBudget() (gas: 468123702)\nLogs:\n  largest admission/reveal step gas (including call overhead): 3466561\n\n[PASS] test_partialAdmissionPreservesDrawAndExcludesSameBlockAndLaterDeposits() (gas: 25071279)\n[PASS] test_pendingRerollBlocksApprovedOperatorsUntilExpiry() (gas: 1668950)\n[PASS] test_pendingRerollCannotBeSoldWithOldTraits() (gas: 802202)\n[PASS] test_safeTransferToVaultIsRejected() (gas: 531345)\n[PASS] test_sameBlockHandInsDoNotCountForPullsMadeNow() (gas: 275287704)\n[PASS] test_setupRefusesUnconfiguredLiquidityBeforeCommitting() (gas: 4689583)\n[PASS] test_setupRejectsLiquidityWithoutCode() (gas: 291131)\n[PASS] test_uninitializedPoolCannotSetPermanentFloor() (gas: 4894050)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 727.70ms (1.30s CPU time)\n\nRan 4 tests for test/LiquidityCustodyInvariants.t.sol:LiquidityCustodyInk1Test\n[PASS]\nLiquidityCustodyInk1Test invariants:\n[PASS] invariant_configurationAndBackingStayIntact\n[PASS] invariant_conservationAndFeeDestinations\n[PASS] invariant_positionsStayLockedAndFullyLiquid\n LiquidityCustodyInk1Test invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------------------+-------------+-------+---------+----------╮\n| Contract                | Selector    | Calls | Reverts | Discards |\n+====================================================================+\n| LiquidityCustodyHandler | add         | 1137  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | changeWidth | 1177  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | collect     | 1188  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | collectHook | 1177  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | donate      | 1152  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | rejectTheft | 1180  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | trade       | 1181  | 0       | 0        |\n╰-------------------------+-------------+-------+---------+----------╯\n\n[PASS] test_minimumAddBoundaryAndEmptyCollectionRanges() (gas: 4623282)\n[PASS] test_positionManagerFailureRollsBackPrefundingAndCanBeRetried() (gas: 4041920)\n[PASS] test_roundTripCollectAndCompound() (gas: 3039507)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 8.69s (8.70s CPU time)\n\nRan 4 tests for test/LiquidityCustodyInvariants.t.sol:LiquidityCustodyInk0Test\n[PASS]\nLiquidityCustodyInk0Test invariants:\n[PASS] invariant_configurationAndBackingStayIntact\n[PASS] invariant_conservationAndFeeDestinations\n[PASS] invariant_positionsStayLockedAndFullyLiquid\n LiquidityCustodyInk0Test invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------------------+-------------+-------+---------+----------╮\n| Contract                | Selector    | Calls | Reverts | Discards |\n+====================================================================+\n| LiquidityCustodyHandler | add         | 1137  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | changeWidth | 1177  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | collect     | 1188  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | collectHook | 1177  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | donate      | 1152  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | rejectTheft | 1180  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | trade       | 1181  | 0       | 0        |\n╰-------------------------+-------------+-------+---------+----------╯\n\n[PASS] test_minimumAddBoundaryAndEmptyCollectionRanges() (gas: 4633516)\n[PASS] test_positionManagerFailureRollsBackPrefundingAndCanBeRetried() (gas: 4057387)\n[PASS] test_roundTripCollectAndCompound() (gas: 2956058)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 9.08s (9.09s CPU time)\n\nRan 1 test for test/VaultInvariants.t.sol:VaultInvariantsTest\n[PASS]\nVaultInvariantsTest invariants:\n[PASS] invariant_agentBalancesMatch\n[PASS] invariant_capAndSlots\n[PASS] invariant_custody\n[PASS] invariant_feesToLiquidity\n[PASS] invariant_fullyBacked\n[PASS] invariant_inkConserved\n[PASS] invariant_queueCovered\n VaultInvariantsTest invariants (runs: 64, calls: 7680, reverts: 0)\n\n╭--------------+------------------+-------+---------+----------╮\n| Contract     | Selector         | Calls | Reverts | Discards |\n+==============================================================+\n| VaultHandler | claim            | 536   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | deposit          | 502   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | moveSeat         | 580   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | pull             | 556   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | pullAll          | 584   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | pullTooMany      | 543   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | release          | 551   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | rerollTraits     | 565   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | reveal           | 518   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | revealNext       | 522   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | revealTraits     | 567   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | roll             | 540   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | setFees          | 565   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | transferPortrait | 551   | 0       | 0        |\n╰--------------+------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 14.64s (14.63s CPU time)\n\nRan 23 test suites in 14.65s (35.40s CPU time): 134 tests passed, 0 failed, 28 skipped (162 total tests)\n","passed":true},{"durationMs":204,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"InkFeeRouter.claimAndRoute(address,uint64)\",\"InkFeeRouter.route()\",\"InkHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"InkHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"InkHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"InkHook.collectFees()\",\"InkHook.unlockCallback(bytes)\",\"InkLiquidity.acceptOwnership()\",\"InkLiquidity.addLiquidity()\",\"InkLiquidity.collectFees(uint256,uint256)\",\"InkLiquidity.renounceOwnership()\",\"InkLiquidity.setPool((address,address,uint24,int24,address),address)\",\"InkLiquidity.setPremium(int24)\",\"InkLiquidity.setWidth(int24)\",\"InkLiquidity.transferOwnership(address)\",\"InkPortraits.acceptOwnership()\",\"InkPortraits.approve(address,uint256)\",\"InkPortraits.lockRenderer()\",\"InkPortraits.mintAgent(uint256,address,(uint32,uint16,uint32,uint32,bool))\",\"InkPortraits.mintSwarm(address,bytes32)\",\"InkPortraits.renounceOwnership()\",\"InkPortraits.safeTransferFrom(address,address,uint256)\",\"InkPortraits.safeTransferFrom(address,address,uint256,bytes)\",\"InkPortraits.setApprovalForAll(address,bool)\",\"InkPortraits.setContractURI(string)\",\"InkPortraits.setRenderer(address)\",\"InkPortraits.setSwarmSeed(uint256,bytes32)\",\"InkPortraits.setVault(address)\",\"InkPortraits.transferFrom(address,address,uint256)\",\"InkPortraits.transferOwnership(address)\",\"InkRenderer.acceptOwnership()\",\"InkRenderer.addChunk(bytes)\",\"InkRenderer.lock()\",\"InkRenderer.renounceOwnership()\",\"InkRenderer.setPreviewBase(string)\",\"InkRenderer.transferOwnership(address)\",\"InkVault.acceptOwnership()\",\"InkVault.claim(uint256,(uint32,uint16,uint32,uint32,bool),bytes32[])\",\"InkVault.deposit(uint256[],uint256)\",\"InkVault.pull(uint256)\",\"InkVault.release()\",\"InkVault.renounceOwnership()\",\"InkVault.rerollTraits(uint256,uint256)\",\"InkVault.reveal(uint256)\",\"InkVault.revealNext(uint256)\",\"InkVault.revealTraits(uint256)\",\"InkVault.setFees(uint16,uint16)\",\"InkVault.setInk(address)\",\"InkVault.setup(bytes32,uint64,address)\",\"InkVault.transferOwnership(address)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"TestSeats.approve(address,uint256)\",\"TestSeats.mint(uint256)\",\"TestSeats.safeTransferFrom(address,address,uint256)\",\"TestSeats.safeTransferFrom(address,address,uint256,bytes)\",\"TestSeats.setApprovalForAll(address,bool)\",\"TestSeats.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":7,\"ADAPTATION.md\":416,\"AUDIT_BRIEF.md\":71,\"BYTECODE_HASHES.json\":52,\"DEPENDENCIES.md\":10,\"PACKAGING.md\":16,\"README.md\":93,\"SETUP.md\":101,\"SHA256SUMS\":1648,\"SOURCE_PROVENANCE.json\":11,\"VALIDATION.txt\":554,\"art/original-art.js\":418,\"foundry.toml\":33,\"launch.json\":34,\"launch/README.md\":35,\"launch/mainnet-1-contracts.json\":24,\"launch/mainnet-2-hook.json\":31,\"launch/sepolia-1-contracts.json\":24,\"launch/sepolia-2-hook.json\":31,\"remappings.txt\":8,\"script/Configure.s.sol\":63,\"script/InkSteps.sol\":135,\"script/LocalStack.s.sol\":154,\"script/Rehearse.s.sol\":258,\"script/UploadRenderer.s.sol\":33,\"src/InkFeeRouter.sol\":77,\"src/InkHook.sol\":270,\"src/InkLiquidity.sol\":335,\"src/InkPortraits.sol\":271,\"src/InkRenderer.sol\":311,\"src/InkVault.sol\":503,\"src/LaunchToken.sol\":17,\"src/testnet/TestSeats.sol\":13,\"test/AuditRegression.t.sol\":477,\"test/InkHookLaunch.t.sol\":571,\"test/InkRenderer.t.sol\":210,\"test/InkSystem.t.sol\":1429,\"test/LaunchDay.t.sol\":270,\"test/LaunchFeeHandoff.t.sol\":82,\"test/LaunchSafety.t.sol\":478,\"test/LiquidityCustodyInvariants.t.sol\":429,\"test/MainnetContractsLaunch.t.sol\":159,\"test/MainnetFork.t.sol\":549,\"test/RangeMath.t.sol\":80,\"test/RendererBoundaryProperties.t.sol\":137,\"test/SepoliaLaunch.t.sol\":186,\"test/SnapshotAllSeats.t.sol\":159,\"test/SnapshotProofs.t.sol\":91,\"test/SybilOptions.t.sol\":104,\"test/TestingCoverage.md\":42,\"test/TokenURIPreview.t.sol\":79,\"test/VaultInvariants.t.sol\":403,\"test/fixtures/snapshot-sample.json\":98,\"test/fixtures/traits.json\":1,\"test/helpers/ConfiguredLiquidity.sol\":18},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"a064b91757876dc68cf033efdc021e659a7a08ced33bfe299461b983db3af6aa","verifiedTreeHash":"662ab0a4f40be21b22888f28497e0d9c997c5b29","verifierVersion":"0.1.0+a26ee00d"},{"checks":[{"durationMs":13717,"exitCode":0,"name":"build","output":"Compiling 190 files with Solc 0.8.26\nSolc 0.8.26 finished in 13.56s\nCompiler run successful with warnings:\nWarning (2519): This declaration shadows an existing declaration.\n  --> script/LocalStack.s.sol:95:26:\n   |\n95 |     function _deployHook(InkPortraits portraits, InkVault vault, address treasury)\n   |                          ^^^^^^^^^^^^^^^^^^^^^^\nNote: The shadowed declaration is here:\n  --> script/LocalStack.s.sol:38:5:\n   |\n38 |     InkPortraits portraits;\n   |     ^^^^^^^^^^^^^^^^^^^^^^\n\nWarning (2519): This declaration shadows an existing declaration.\n  --> script/LocalStack.s.sol:95:50:\n   |\n95 |     function _deployHook(InkPortraits portraits, InkVault vault, address treasury)\n   |                                                  ^^^^^^^^^^^^^^\nNote: The shadowed declaration is here:\n  --> script/LocalStack.s.sol:39:5:\n   |\n39 |     InkVault vault;\n   |     ^^^^^^^^^^^^^^\n\nWarning (2519): This declaration shadows an existing declaration.\n   --> script/LocalStack.s.sol:128:20:\n    |\n128 |     function _seed(PoolKey memory key, bool inkIs0, address to, ForkInk ink) internal {\n    |                    ^^^^^^^^^^^^^^^^^^\nNote: The shadowed declaration is here:\n  --> script/LocalStack.s.sol:44:5:\n   |\n44 |     PoolKey key;\n   |     ^^^^^^^^^^^\n\nWarning (2519): This declaration shadows an existing declaration.\n   --> script/LocalStack.s.sol:128:65:\n    |\n128 |     function _seed(PoolKey memory key, bool inkIs0, address to, ForkInk ink) internal {\n    |                                                                 ^^^^^^^^^^^\nNote: The shadowed declaration is here:\n  --> script/LocalStack.s.sol:43:5:\n   |\n43 |     ForkInk ink;\n   |     ^^^^^^^^^^^\n\nWarning (3149): The result type of the shift operation is equal to the type of the first operand (uint128) ignoring the (larger) type of the second operand (uint256) which might be unexpected. Silence this warning by either converting the first or the second operand to the type of the other.\n   --> src/InkLiquidity.sol:196:31:\n    |\n196 |             uint128 unitLiq = uint128(1e24) << k;\n    |                               ^^^^^^^^^^^^^^^^^^\n\n","passed":true},{"durationMs":8876,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/SybilOptions.t.sol:SybilPremium2xTest\n[SKIP] test_fork_sybilScenario() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 494.90µs (16.75µs CPU time)\n\nRan 9 tests for test/SnapshotAllSeats.t.sol:SnapshotAllSeatsTest\n[SKIP] test_claims_0000() (gas: 0)\n[SKIP] test_claims_0250() (gas: 0)\n[SKIP] test_claims_0500() (gas: 0)\n[SKIP] test_claims_0750() (gas: 0)\n[SKIP] test_claims_1000() (gas: 0)\n[SKIP] test_claims_1250() (gas: 0)\n[SKIP] test_claims_1500() (gas: 0)\n[SKIP] test_claims_1750() (gas: 0)\n[SKIP] test_treeHasEverySeatOnce() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 9 skipped; finished in 516.66µs (1.21ms CPU time)\n\nRan 1 test for test/SybilOptions.t.sol:SybilPremium1xTest\n[SKIP] test_fork_sybilScenario() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 593.17µs (14.38µs CPU time)\n\nRan 1 test for test/SybilOptions.t.sol:SybilPremium5xTest\n[SKIP] test_fork_sybilScenario() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 732.43µs (18.11µs CPU time)\n\nRan 1 test for test/LaunchFeeHandoff.t.sol:LaunchFeeHandoffTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 123.79µs (0.00ns CPU time)\n\nRan 1 test for test/LaunchDay.t.sol:LaunchDayTest\n[SKIP] test_fork_launchDay() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 978.54µs (24.13µs CPU time)\n\nRan 2 tests for test/SnapshotProofs.t.sol:SnapshotProofsTest\n[PASS] test_realSnapshotProofsClaim() (gas: 3222192)\n[PASS] test_tamperedStatsAreRejected() (gas: 605318)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 7.24ms (9.08ms CPU time)\n\nRan 7 tests for test/MainnetFork.t.sol:MainnetForkInkIs0Test\n[SKIP] test_fork_agentHolderPaysNoHookFee() (gas: 0)\n[SKIP] test_fork_bigBuyAfterRelease() (gas: 0)\n[SKIP] test_fork_fullLifecycle() (gas: 0)\n[SKIP] test_fork_hookFeeExactInBuyIsInkToLiquidity() (gas: 0)\n[SKIP] test_fork_lockedUntilReleaseHoweverLong() (gas: 0)\n[SKIP] test_fork_sellsPayFeesBothWays() (gas: 0)\n[SKIP] test_fork_sybilFirstHour() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 7 skipped; finished in 6.17ms (113.79µs CPU time)\n\nRan 3 tests for test/InkHookLaunch.t.sol:InkHookFactoryLaunchTest\n[PASS] test_constructorsNeedNoLiveDependency() (gas: 69495)\n[PASS] test_factoryBindsThePoolOthersCannot() (gas: 346368)\n[PASS] test_runtimeSizeAndOpcodes() (gas: 2267700)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 20.59ms (3.99ms CPU time)\n\nRan 7 tests for test/MainnetFork.t.sol:MainnetForkInkIs1Test\n[SKIP] test_fork_agentHolderPaysNoHookFee() (gas: 0)\n[SKIP] test_fork_bigBuyAfterRelease() (gas: 0)\n[SKIP] test_fork_fullLifecycle() (gas: 0)\n[SKIP] test_fork_hookFeeExactInBuyIsInkToLiquidity() (gas: 0)\n[SKIP] test_fork_lockedUntilReleaseHoweverLong() (gas: 0)\n[SKIP] test_fork_sellsPayFeesBothWays() (gas: 0)\n[SKIP] test_fork_sybilFirstHour() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 7 skipped; finished in 42.40ms (123.41µs CPU time)\n\nRan 1 test for test/TokenURIPreview.t.sol:TokenURIPreviewTest\n[PASS] test_writeTokenURIs() (gas: 25423286)\nLogs:\n  read-only profile: not written out-preview/agent-280.txt (use FOUNDRY_PROFILE=preview)\n  read-only profile: not written out-preview/pool.txt (use FOUNDRY_PROFILE=preview)\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 53.59ms (53.39ms CPU time)\n\nRan 13 tests for test/InkHookLaunch.t.sol:InkHookLaunchTest\n[PASS] test_agentHolderPaysNoHookFee() (gas: 1467061)\n[PASS] test_callbacksRefuseEveryoneButThePoolManager() (gas: 1143610)\n[PASS] test_collectFeesPaysImdToTreasuryAndInkToLiquidity() (gas: 1503107)\n[PASS] test_factoryInitializesExactlyOnePoolPairedWithImd() (gas: 221940)\n[PASS] test_feeOnBuyExactInput() (gas: 1090718)\n[PASS] test_feeOnBuyExactOutput() (gas: 1089619)\n[PASS] test_feeOnSellExactInput() (gas: 1052158)\n[PASS] test_feeOnSellExactOutput() (gas: 1052991)\n[PASS] test_feeRateAndBindingsAreImmutable() (gas: 62563)\n[PASS] test_noSwapUntilTheVaultReleases() (gas: 1682408)\n[PASS] test_strangerCannotBindHookToWrongPoolBeforeFactory() (gas: 184498)\n[PASS] test_strangerCannotFixWrongFeeAndPriceBeforeFactory() (gas: 161648)\n[PASS] test_tokenIsFixedSupplyInk() (gas: 263361)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 104.16ms (12.93ms CPU time)\n\nRan 3 tests for test/MainnetContractsLaunch.t.sol:MainnetContractsLaunchTest\n[PASS] test_exactFourContractOrderReferencesAndOwners() (gas: 221680)\n[PASS] test_factoryTreasuryAndOriginHaveNoAdministrativeRights() (gas: 1697915)\n[PASS] test_runtimeSizeAndOpcodesMeetProtectedFloor() (gas: 16755780)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 103.08ms (40.78ms CPU time)\n\nRan 16 tests for test/LaunchSafety.t.sol:LaunchSafetyTest\n[PASS] testFuzz_directPortraitTransfersCannotBypassDeposit(bool,uint8) (runs: 256, μ: 624869, ~: 407054)\n[PASS] testFuzz_expiredPullChargesPurchaseFeeAndPreservesBacking(uint16,uint16,bool) (runs: 256, μ: 565015, ~: 567933)\n[PASS] test_agentHolderDiscountOnlyOnOwnPullsStillHeld() (gas: 3268385)\n[PASS] test_cannotRenounceBeforeRequiredConfiguration() (gas: 478947)\n[PASS] test_expiredBatchRespectsProcessingLimit() (gas: 691194)\n[PASS] test_expiredHeadCannotStallLaterLivePull() (gas: 800011)\n[PASS] test_expiredTraitRerollConsumesFeeAndRequiresNewPayment() (gas: 977128)\n[PASS] test_nonHolderPaysFullFeeThroughAgentHoldingContract() (gas: 1368938)\n[PASS] test_ownershipTransferRequiresAcceptance() (gas: 640058)\n[PASS] test_portraitsCannotBeSentToLiquidity() (gas: 586110)\n[PASS] test_pullLastAvailableBlockRevealsWithoutExpiryFee() (gas: 484320)\n[PASS] test_setInkRequiresCodeAndFullAllocationBeforeItCommits() (gas: 319403)\n[PASS] test_setupRejectsWrongTokenLiquidityAndRemainsCorrectable() (gas: 1228493)\n[PASS] test_traitRerollLastAvailableBlockUsesOriginalHash() (gas: 688736)\n[PASS] test_traitRerollThroughAgentHoldingContractPaysFullFee() (gas: 1216106)\n[PASS] test_vaultAndRendererBindingFailuresRemainCorrectable() (gas: 433271)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 103.12ms (145.86ms CPU time)\n\nRan 55 tests for test/InkSystem.t.sol:InkSystemTest\n[PASS] test_agentClaimsOwnPortraitFree() (gas: 311177)\n[PASS] test_agentDepositReturnsExactlyItsBacking() (gas: 3516061)\n[PASS] test_agentHoldersPayNoHookFee() (gas: 2734061)\n[PASS] test_agentHoldersRerollForHalf() (gas: 4214951)\n[PASS] test_borrowedAgentPortraitGetsNoWaiver() (gas: 2555036)\n[PASS] test_cantLockAnEmptyRenderer() (gas: 3138116)\n[PASS] test_claimRules() (gas: 465550)\n[PASS] test_collectFeesPaysImdToTreasuryAndKeepsInk() (gas: 3367034)\n[PASS] test_contractURIOwnerOnly() (gas: 94718)\n[PASS] test_contractWalletSeatCanClaim() (gas: 387248)\n[PASS] test_depositMinOut() (gas: 3038333)\n[PASS] test_depositedAgentPortraitIsPullable() (gas: 3344870)\n[PASS] test_feeRouterClaimAndRoute() (gas: 4381579)\n[PASS] test_feeRouterNeedsPoolAndAddresses() (gas: 3872711)\n[PASS] test_feeRouterSplitsAndCompounds() (gas: 4127736)\n[PASS] test_gasAtScale() (gas: 78817084)\nLogs:\n  pull(1) gas: 161004\n  revealNext(1) gas: 221322\n    per reveal: 221322\n  pull(10) gas: 548232\n  revealNext(10) gas: 1140496\n    per reveal: 114049\n  pull(100) gas: 4762670\n  revealNext(100) gas: 10847735\n    per reveal: 108477\n  pull(400) gas: 18812864\n  revealNext(400) gas: 43238119\n    per reveal: 108095\n  deposit(100 portraits) gas: 6950387\n\n[PASS] test_guards() (gas: 12641088)\n[PASS] test_hookFeeImdToTreasury() (gas: 2210755)\n[PASS] test_hookFeeInkGoesToLiquidity() (gas: 2701726)\n[PASS] test_idleSeatClaimsToo() (gas: 227103)\n[PASS] test_inkFeesGoBackIntoThePool() (gas: 6092322)\n[PASS] test_launchSplit() (gas: 67244)\n[PASS] test_liquidityCannotRenounceBeforePoolIsConfigured() (gas: 3071147)\n[PASS] test_liquidityFloorHoldsIfPriceIsDragged() (gas: 6606776)\n[PASS] test_liquidityOwnershipTransferRequiresAcceptance() (gas: 222592)\n[PASS] test_liquidityRefusesUnrelatedNFTsButKeepsPositionCustody() (gas: 2106517)\n[PASS] test_mainnetPoolMustPairWithOfficialIMD() (gas: 3079882)\n[PASS] test_noPerWalletLimitAfterRelease() (gas: 2124588)\n[PASS] test_noPullsUntilRelease() (gas: 2230624)\n[PASS] test_onlyOnePool() (gas: 46381)\n[PASS] test_onlyTheFactoryCanInitialize() (gas: 54112)\n[PASS] test_ownerIsOwnerWalletNotDeployer() (gas: 7990817)\n[PASS] test_permissionlessListingTracksSpotAboveReleaseFloor() (gas: 3372170)\n[PASS] test_poolLockedUntilRelease() (gas: 2618366)\n[PASS] test_poolNeverOpensWithoutRelease() (gas: 2477170)\n[PASS] test_premiumBeforePoolCannotBeFrontRun() (gas: 6049502)\n[PASS] test_premiumCannotBeLockedByListingBeforeRelease() (gas: 6665634)\n[PASS] test_pullRerollPullAgain() (gas: 3918561)\n[PASS] test_releaseLadderShape() (gas: 2319548)\n[PASS] test_releasePremiumListsAboveTheOpeningPrice() (gas: 1925754)\n[PASS] test_releaseTurnsUnclaimedSeatsIntoSlotsAndPoolInk() (gas: 2074111)\n[PASS] test_revealIsNotEarlyAndNotTwice() (gas: 2625250)\n[PASS] test_seatSoldDuringWindowNewOwnerClaims() (gas: 301442)\n[PASS] test_setPoolRejectsHooklessPoolAndRemainsCorrectable() (gas: 3123371)\n[PASS] test_setPoolRejectsInconsistentHookBindings() (gas: 6644412)\n[PASS] test_setPoolRequiresPositionManagerBoundToSameManager() (gas: 3102547)\n[PASS] test_setupRules() (gas: 3137736)\n[PASS] test_setupStartMustBeSoon() (gas: 3127148)\n[PASS] test_traitRerollBeforeReleaseReverts() (gas: 39919)\n[PASS] test_traitRerollExpiredHashClosesWithoutFreeDraw() (gas: 2971955)\n[PASS] test_traitRerollGuards() (gas: 3463065)\n[PASS] test_traitRerollHalfPriceForAgentHolders() (gas: 3130793)\n[PASS] test_traitRerollRepaintsSameToken() (gas: 3032508)\n[PASS] test_vaultStaysFullyBacked() (gas: 3971924)\n[PASS] test_zeroTradingFee() (gas: 29321)\nSuite result: ok. 55 passed; 0 failed; 0 skipped; finished in 104.36ms (109.64ms CPU time)\n\nRan 6 tests for test/InkRenderer.t.sol:InkRendererTest\n[PASS] test_agentTraitsMatchTheScript() (gas: 61685138)\n[PASS] test_noClaimsAfterTheFirstSwarmPortrait() (gas: 1397672)\n[PASS] test_poolTraitsMatchTheScript() (gas: 60520738)\n[PASS] test_scriptStoredInChunksAndLocked() (gas: 12171384)\n[PASS] test_swarmNumbersFillUnclaimedSeats() (gas: 163375864)\n[PASS] test_tokenURIForAnAgentPortrait() (gas: 3733117)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 125.84ms (287.38ms CPU time)\n\nRan 4 tests for test/RangeMath.t.sol:RangeMathTest\n[PASS] testFuzz_rangeNeverRevertsAndStaysUsable(int24,int24,bool) (runs: 256, μ: 118220, ~: 118357)\n[PASS] test_maxWidthClampsWhenInkIsCurrency0() (gas: 131887)\n[PASS] test_maxWidthClampsWhenInkIsCurrency1() (gas: 115332)\n[PASS] test_ordinaryWidthUnchanged() (gas: 118547)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 124.82ms (124.71ms CPU time)\n\nRan 3 tests for test/RendererBoundaryProperties.t.sol:RendererBoundaryPropertiesTest\n[PASS] testFuzz_binaryChunksRoundTripAndLock(bytes,bytes) (runs: 1000, μ: 464050, ~: 460611)\n[PASS] test_chunkLimitAndRejectedWritesDoNotDamageUploadedArt() (gas: 5657977)\n[PASS] test_ownershipTransferRevokesOldOwnerAcrossEveryRendererMutation() (gas: 615341)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 155.78ms (126.43ms CPU time)\n\nRan 4 tests for test/SepoliaLaunch.t.sol:SepoliaLaunchTest\n[PASS] test_allFiveRuntimesMeetProtectedFloor() (gas: 18566852)\n[PASS] test_existingOwnerSetupRemainsRequiredAndAvailable() (gas: 441282)\n[PASS] test_factoryDeploymentPreservesOwnersRoyaltiesAndReferences() (gas: 183938)\n[PASS] test_factoryTreasuryAndOutsiderHaveNoAdminRole() (gas: 1701232)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 379.79ms (46.43ms CPU time)\n\nRan 15 tests for test/AuditRegression.t.sol:AuditRegressionTest\n[PASS] testFuzz_admissionCompletesWithBothRevealEntryPoints(uint16,bool) (runs: 256, μ: 23684681, ~: 15072138)\n[PASS] test_directSafeTransfersRejectedForBothOverloadsAndOtherNFTs() (gas: 900066)\n[PASS] test_expiredHeadPullNeverWidensALaterPullsDraw() (gas: 2219191)\n[PASS] test_expiredPullsCloseWithoutWideningDrawsOrRenewing() (gas: 1439598)\n[PASS] test_hashExpiryDuringAdmissionKeepsOriginalDraw() (gas: 47716799)\n[PASS] test_initializedPoolAtZeroTickIsValid() (gas: 4800059)\n[PASS] test_largeAdmissionQueueMakesProgressWithinGasBudget() (gas: 468123702)\nLogs:\n  largest admission/reveal step gas (including call overhead): 3466561\n\n[PASS] test_partialAdmissionPreservesDrawAndExcludesSameBlockAndLaterDeposits() (gas: 25071279)\n[PASS] test_pendingRerollBlocksApprovedOperatorsUntilExpiry() (gas: 1668950)\n[PASS] test_pendingRerollCannotBeSoldWithOldTraits() (gas: 802202)\n[PASS] test_safeTransferToVaultIsRejected() (gas: 531345)\n[PASS] test_sameBlockHandInsDoNotCountForPullsMadeNow() (gas: 275287704)\n[PASS] test_setupRefusesUnconfiguredLiquidityBeforeCommitting() (gas: 4689583)\n[PASS] test_setupRejectsLiquidityWithoutCode() (gas: 291131)\n[PASS] test_uninitializedPoolCannotSetPermanentFloor() (gas: 4894050)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 379.97ms (654.82ms CPU time)\n\nRan 4 tests for test/LiquidityCustodyInvariants.t.sol:LiquidityCustodyInk1Test\n[PASS]\nLiquidityCustodyInk1Test invariants:\n[PASS] invariant_configurationAndBackingStayIntact\n[PASS] invariant_conservationAndFeeDestinations\n[PASS] invariant_positionsStayLockedAndFullyLiquid\n LiquidityCustodyInk1Test invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------------------+-------------+-------+---------+----------╮\n| Contract                | Selector    | Calls | Reverts | Discards |\n+====================================================================+\n| LiquidityCustodyHandler | add         | 1137  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | changeWidth | 1177  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | collect     | 1188  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | collectHook | 1177  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | donate      | 1152  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | rejectTheft | 1180  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | trade       | 1181  | 0       | 0        |\n╰-------------------------+-------------+-------+---------+----------╯\n\n[PASS] test_minimumAddBoundaryAndEmptyCollectionRanges() (gas: 4623282)\n[PASS] test_positionManagerFailureRollsBackPrefundingAndCanBeRetried() (gas: 4041920)\n[PASS] test_roundTripCollectAndCompound() (gas: 3039507)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 5.62s (5.63s CPU time)\n\nRan 4 tests for test/LiquidityCustodyInvariants.t.sol:LiquidityCustodyInk0Test\n[PASS]\nLiquidityCustodyInk0Test invariants:\n[PASS] invariant_configurationAndBackingStayIntact\n[PASS] invariant_conservationAndFeeDestinations\n[PASS] invariant_positionsStayLockedAndFullyLiquid\n LiquidityCustodyInk0Test invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------------------+-------------+-------+---------+----------╮\n| Contract                | Selector    | Calls | Reverts | Discards |\n+====================================================================+\n| LiquidityCustodyHandler | add         | 1137  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | changeWidth | 1177  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | collect     | 1188  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | collectHook | 1177  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | donate      | 1152  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | rejectTheft | 1180  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | trade       | 1181  | 0       | 0        |\n╰-------------------------+-------------+-------+---------+----------╯\n\n[PASS] test_minimumAddBoundaryAndEmptyCollectionRanges() (gas: 4633516)\n[PASS] test_positionManagerFailureRollsBackPrefundingAndCanBeRetried() (gas: 4057387)\n[PASS] test_roundTripCollectAndCompound() (gas: 2956058)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 5.71s (5.70s CPU time)\n\nRan 1 test for test/VaultInvariants.t.sol:VaultInvariantsTest\n[PASS]\nVaultInvariantsTest invariants:\n[PASS] invariant_agentBalancesMatch\n[PASS] invariant_capAndSlots\n[PASS] invariant_custody\n[PASS] invariant_feesToLiquidity\n[PASS] invariant_fullyBacked\n[PASS] invariant_inkConserved\n[PASS] invariant_queueCovered\n VaultInvariantsTest invariants (runs: 64, calls: 7680, reverts: 0)\n\n╭--------------+------------------+-------+---------+----------╮\n| Contract     | Selector         | Calls | Reverts | Discards |\n+==============================================================+\n| VaultHandler | claim            | 536   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | deposit          | 502   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | moveSeat         | 580   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | pull             | 556   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | pullAll          | 584   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | pullTooMany      | 543   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | release          | 551   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | rerollTraits     | 565   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | reveal           | 518   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | revealNext       | 522   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | revealTraits     | 567   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | roll             | 540   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | setFees          | 565   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | transferPortrait | 551   | 0       | 0        |\n╰--------------+------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 8.71s (8.71s CPU time)\n\nRan 23 test suites in 8.72s (21.76s CPU time): 134 tests passed, 0 failed, 28 skipped (162 total tests)\n","passed":true},{"durationMs":120,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"InkFeeRouter.claimAndRoute(address,uint64)\",\"InkFeeRouter.route()\",\"InkHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"InkHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"InkHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"InkHook.collectFees()\",\"InkHook.unlockCallback(bytes)\",\"InkLiquidity.acceptOwnership()\",\"InkLiquidity.addLiquidity()\",\"InkLiquidity.collectFees(uint256,uint256)\",\"InkLiquidity.renounceOwnership()\",\"InkLiquidity.setPool((address,address,uint24,int24,address),address)\",\"InkLiquidity.setPremium(int24)\",\"InkLiquidity.setWidth(int24)\",\"InkLiquidity.transferOwnership(address)\",\"InkPortraits.acceptOwnership()\",\"InkPortraits.approve(address,uint256)\",\"InkPortraits.lockRenderer()\",\"InkPortraits.mintAgent(uint256,address,(uint32,uint16,uint32,uint32,bool))\",\"InkPortraits.mintSwarm(address,bytes32)\",\"InkPortraits.renounceOwnership()\",\"InkPortraits.safeTransferFrom(address,address,uint256)\",\"InkPortraits.safeTransferFrom(address,address,uint256,bytes)\",\"InkPortraits.setApprovalForAll(address,bool)\",\"InkPortraits.setContractURI(string)\",\"InkPortraits.setRenderer(address)\",\"InkPortraits.setSwarmSeed(uint256,bytes32)\",\"InkPortraits.setVault(address)\",\"InkPortraits.transferFrom(address,address,uint256)\",\"InkPortraits.transferOwnership(address)\",\"InkRenderer.acceptOwnership()\",\"InkRenderer.addChunk(bytes)\",\"InkRenderer.lock()\",\"InkRenderer.renounceOwnership()\",\"InkRenderer.setPreviewBase(string)\",\"InkRenderer.transferOwnership(address)\",\"InkVault.acceptOwnership()\",\"InkVault.claim(uint256,(uint32,uint16,uint32,uint32,bool),bytes32[])\",\"InkVault.deposit(uint256[],uint256)\",\"InkVault.pull(uint256)\",\"InkVault.release()\",\"InkVault.renounceOwnership()\",\"InkVault.rerollTraits(uint256,uint256)\",\"InkVault.reveal(uint256)\",\"InkVault.revealNext(uint256)\",\"InkVault.revealTraits(uint256)\",\"InkVault.setFees(uint16,uint16)\",\"InkVault.setInk(address)\",\"InkVault.setup(bytes32,uint64,address)\",\"InkVault.transferOwnership(address)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"TestSeats.approve(address,uint256)\",\"TestSeats.mint(uint256)\",\"TestSeats.safeTransferFrom(address,address,uint256)\",\"TestSeats.safeTransferFrom(address,address,uint256,bytes)\",\"TestSeats.setApprovalForAll(address,bool)\",\"TestSeats.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":7,\"ADAPTATION.md\":416,\"AUDIT_BRIEF.md\":71,\"BYTECODE_HASHES.json\":52,\"DEPENDENCIES.md\":10,\"PACKAGING.md\":16,\"README.md\":93,\"SETUP.md\":101,\"SHA256SUMS\":1648,\"SOURCE_PROVENANCE.json\":11,\"VALIDATION.txt\":554,\"art/original-art.js\":418,\"foundry.toml\":33,\"launch/README.md\":35,\"launch/mainnet-1-contracts.json\":24,\"launch/mainnet-2-hook.json\":31,\"launch/sepolia-1-contracts.json\":24,\"launch/sepolia-2-hook.json\":31,\"remappings.txt\":8,\"script/Configure.s.sol\":63,\"script/InkSteps.sol\":135,\"script/LocalStack.s.sol\":154,\"script/Rehearse.s.sol\":258,\"script/UploadRenderer.s.sol\":33,\"src/InkFeeRouter.sol\":77,\"src/InkHook.sol\":270,\"src/InkLiquidity.sol\":335,\"src/InkPortraits.sol\":271,\"src/InkRenderer.sol\":311,\"src/InkVault.sol\":503,\"src/LaunchToken.sol\":17,\"src/testnet/TestSeats.sol\":13,\"test/AuditRegression.t.sol\":477,\"test/InkHookLaunch.t.sol\":571,\"test/InkRenderer.t.sol\":210,\"test/InkSystem.t.sol\":1429,\"test/LaunchDay.t.sol\":270,\"test/LaunchFeeHandoff.t.sol\":82,\"test/LaunchSafety.t.sol\":478,\"test/LiquidityCustodyInvariants.t.sol\":429,\"test/MainnetContractsLaunch.t.sol\":159,\"test/MainnetFork.t.sol\":549,\"test/RangeMath.t.sol\":80,\"test/RendererBoundaryProperties.t.sol\":137,\"test/SepoliaLaunch.t.sol\":186,\"test/SnapshotAllSeats.t.sol\":159,\"test/SnapshotProofs.t.sol\":91,\"test/SybilOptions.t.sol\":104,\"test/TestingCoverage.md\":42,\"test/TokenURIPreview.t.sol\":79,\"test/VaultInvariants.t.sol\":403,\"test/fixtures/snapshot-sample.json\":98,\"test/fixtures/traits.json\":1,\"test/helpers/ConfiguredLiquidity.sol\":18},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":5434,"exitCode":0,"name":"slither","output":"[high/medium] weak-prng at src/InkVault.sol:312: InkVault._revealNext() (src/InkVault.sol#312-360) uses a weak PRNG: \"pick = r % avail (src/InkVault.sol#347)\"\n[medium/medium] divide-before-multiply at src/InkLiquidity.sol:330: InkLiquidity._floor(int24,int24) (src/InkLiquidity.sol#330-334) performs a multiplication on the result of a division:\n[medium/medium] divide-before-multiply at src/InkLiquidity.sol:261: InkLiquidity._rangeFrom(int24,int24) (src/InkLiquidity.sol#261-281) performs a multiplication on the result of a division:\n[medium/high] incorrect-equality at src/InkLiquidity.sol:164: InkLiquidity.addLiquidity() (src/InkLiquidity.sol#164-175) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/InkVault.sol:479: InkVault.waitingBefore() (src/InkVault.sol#479-482) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/InkVault.sol:384: InkVault.deposit(uint256[],uint256) (src/InkVault.sol#384-416) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/InkVault.sol:384: Reentrancy in InkVault.deposit(uint256[],uint256) (src/InkVault.sol#384-416):\n[medium/medium] uninitialized-local at src/InkLiquidity.sol:188: InkLiquidity._addLadder(int24,uint256).total (src/InkLiquidity.sol#188) is a local variable never initialized\n[medium/medium] uninitialized-local at src/InkRenderer.sol:117: InkRenderer.tokenJSON(uint256).extra (src/InkRenderer.sol#117) is a local variable never initialized\n[medium/medium] uninitialized-local at src/InkVault.sol:397: InkVault.deposit(uint256[],uint256).fees (src/InkVault.sol#397) is a local variable never initialized\n[medium/medium] unused-return at src/InkFeeRouter.sol:54: InkFeeRouter.claimAndRoute(ILaunchFeeFactory,uint64) (src/InkFeeRouter.sol#54-61) ignores return value by factory.claimFees(launchNumber) (src/InkFeeRouter.sol#58)\n[medium/medium] unused-return at src/InkLiquidity.sol:164: InkLiquidity.addLiquidity() (src/InkLiquidity.sol#164-175) ignores return value by (None,tick,None,None) = poolManager.getSlot0(poolKey.toId()) (src/InkLiquidity.sol#170)\n[medium/medium] unused-return at src/InkVault.sol:231: InkVault.release() (src/InkVault.sol#231-243) ignores return value by IInkLiquidity(liquidity).addLiquidity() (src/InkVault.sol#240)\n[medium/medium] unused-return at src/InkPortraits.sol:114: InkPortraits.setRenderer(address) (src/InkPortraits.sol#114-122) ignores return value by IInkRendererBinding(renderer_).chunkCount() (src/InkPortraits.sol#118)\n[medium/medium] unused-return at src/InkLiquidity.sol:107: InkLiquidity.setPool(PoolKey,IERC20) (src/InkLiquidity.sol#107-121) ignores return value by (sqrtPriceX96,tick,None,None) = poolManager.getSlot0(key.toId()) (src/InkLiquidity.sol#111)\n[medium/high] write-after-write at src/InkRenderer.sol:243: InkRenderer.swarmLabels(string).u (src/InkRenderer.sol#243) is written in both\n[medium/high] write-after-write at src/InkRenderer.sol:243: InkRenderer.swarmLabels(string).u (src/InkRenderer.sol#243) is written in both\n[low/medium] missing-zero-check at src/InkPortraits.sol:114: InkPortraits.setRenderer(address).renderer_ (src/InkPortraits.sol#114) lacks a zero-check on :\n[low/medium] calls-loop at src/InkVault.sol:384: InkVault.deposit(uint256[],uint256) (src/InkVault.sol#384-416) has external calls inside a loop: portraits.isAgentPortrait(tokenId) (src/InkVault.sol#406-408)\n[low/medium] calls-loop at src/InkVault.sol:312: InkVault._revealNext() (src/InkVault.sol#312-360) has external calls inside a loop: portraits.transferFrom(address(this),p.to,tokenId) (src/InkVault.sol#353)\n[low/medium] calls-loop at src/InkVault.sol:254: InkVault.available() (src/InkVault.sol#254-256) has external calls inside a loop: _inventory.length + swarmSlots() - portraits.swarmMinted() (src/InkVault.sol#255)\n[low/medium] calls-loop at src/InkVault.sol:384: InkVault.deposit(uint256[],uint256) (src/InkVault.sol#384-416) has external calls inside a loop: portraits.transferFrom(msg.sender,address(this),tokenId) (src/InkVault.sol#403)\n[low/medium] calls-loop at src/InkLiquidity.sol:283: InkLiquidity._mintSingleSided(int24,int24,uint256) (src/InkLiquidity.sol#283-319) has external calls inside a loop: positionManager.modifyLiquidities(abi.encode(abi.encodePacked(uint8(Actions.MINT_POSITION),uint8(Actions.SETTLE),uint8(Actions.SWEEP)),params),block.timestamp) (src/InkLiquidity.sol#312-318)\n[low/medium] calls-loop at src/InkVault.sol:312: InkVault._revealNext() (src/InkVault.sol#312-360) has external calls inside a loop: tokenId_scope_0 = portraits.mintSwarm(p.to,keccak256(bytes)(abi.encode(entropy,pullId,seed))) (src/InkVault.sol#356)\n[low/medium] calls-loop at src/InkVault.sol:254: InkVault.available() (src/InkVault.sol#254-256) has external calls inside a loop: _inventory.length + swarmSlots() - portraits.swarmMinted() (src/InkVault.sol#255)\n[low/medium] calls-loop at src/InkVault.sol:312: InkVault._revealNext() (src/InkVault.sol#312-360) has external calls inside a loop: portraits.transferFrom(address(this),p.to,tokenId) (src/InkVault.sol#353)\n[low/medium] calls-loop at src/InkVault.sol:384: InkVault.deposit(uint256[],uint256) (src/InkVault.sol#384-416) has external calls inside a loop: discounted = agentHolder && portraits.heldFromVault(tokenId) (src/InkVault.sol#402)\n[low/medium] calls-loop at src/InkVault.sol:312: InkVault._revealNext() (src/InkVault.sol#312-360) has external calls inside a loop: tokenId_scope_0 = portraits.mintSwarm(p.to,keccak256(bytes)(abi.encode(entropy,pullId,seed))) (src/InkVault.sol#356)\n[low/medium] calls-loop at src/InkLiquidity.sol:283: InkLiquidity._mintSingleSided(int24,int24,uint256) (src/InkLiquidity.sol#283-319) has external calls inside a loop: positionId = positionManager.nextTokenId() (src/InkLiquidity.sol#296)\n[low/medium] calls-loop at src/InkVault.sol:248: InkVault.swarmSlots() (src/InkVault.sol#248-251) has external calls inside a loop: SEATS - portraits.agentMinted() (src/InkVault.sol#250)\n[low/medium] calls-loop at src/InkVault.sol:248: InkVault.swarmSlots() (src/InkVault.sol#248-251) has external calls inside a loop: SEATS - portraits.agentMinted() (src/InkVault.sol#250)\n[low/medium] reentrancy-benign at src/InkLiquidity.sol:216: Reentrancy in InkLiquidity._add(int24,int24,uint256) (src/InkLiquidity.sol#216-220):\n[low/medium] reentrancy-events at src/InkLiquidity.sol:216: Reentrancy in InkLiquidity._add(int24,int24,uint256) (src/InkLiquidity.sol#216-220):\n[low/medium] reentrancy-events at src/InkHook.sol:167: Reentrancy in InkHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/InkHook.sol#167-189):\n[low/medium] reentrancy-events at src/InkFeeRouter.sol:65: Reentrancy in InkFeeRouter.route() (src/InkFeeRouter.sol#65-76):\n[low/medium] reentrancy-events at src/InkFeeRouter.sol:54: Reentrancy in InkFeeRouter.claimAndRoute(ILaunchFeeFactory,uint64) (src/InkFeeRouter.sol#54-61):\n[low/medium] reentrancy-events at src/InkHook.sol:203: Reentrancy in InkHook._payOut(Currency,address) (src/InkHook.sol#203-209):\n[low/medium] reentrancy-events at src/InkFeeRouter.sol:54: Reentrancy in InkFeeRouter.claimAndRoute(ILaunchFeeFactory,uint64) (src/InkFeeRouter.sol#54-61):\n[low/medium] reentrancy-events at src/InkLiquidity.sol:225: Reentrancy in InkLiquidity.collectFees(uint256,uint256) (src/InkLiquidity.sol#225-252):\n[low/medium] timestamp at src/InkVault.sol:231: InkVault.release() (src/InkVault.sol#231-243) uses timestamp for comparisons\n[low/medium] timestamp at src/InkVault.sol:171: InkVault.setup(bytes32,uint64,address) (src/InkVault.sol#171-187) uses timestamp for comparisons\n[low/medium] timestamp at src/InkVault.sol:248: InkVault.swarmSlots() (src/InkVault.sol#248-251) uses timestamp for comparisons\n[low/medium] timestamp at src/InkHook.sol:154: InkHook.beforeSwap(address,PoolKey,SwapParams,bytes) (src/InkHook.sol#154-165) uses timestamp for comparisons\n[low/medium] timestamp at src/InkHook.sol:150: InkHook.tradingOpen() (src/InkHook.sol#150-152) uses timestamp for comparisons\n[low/medium] timestamp at src/InkVault.sol:203: InkVault.claimOpen() (src/InkVault.sol#203-205) uses timestamp for comparisons","passed":true},{"durationMs":842,"exitCode":0,"name":"aderyn","output":"[high] abi-encode-packed-hash-collision at src/InkRenderer.sol:58: `abi.encodePacked()` Hash Collision (4 places)\n[high] reentrancy-state-change at src/InkHook.sol:160: Reentrancy: State change after external call (15 places)\n[high] unsafe-casting at src/InkLiquidity.sol:304: Unsafe Casting of integers (2 places)\n[low] centralization-risk at src/InkLiquidity.sol:35: Centralization Risk (21 places)\n[low] costly-loop at src/InkLiquidity.sol:207: Costly operations inside loop (6 places)\n[low] large-numeric-literal at src/InkHook.sol:182: Large Numeric Literal (7 places)\n[low] literal-instead-of-constant at src/InkPortraits.sol:157: Literal Instead of Constant (36 places)\n[low] require-revert-in-loop at src/InkLiquidity.sol:190: Loop Contains `require`/`revert` (5 places)\n[low] state-no-address-check at src/InkLiquidity.sol:116: Address State Variable Set Without Checks (2 places)\n[low] state-variable-could-be-constant at src/InkRenderer.sol:21: State Variable Could Be Constant\n[low] storage-array-length-not-cached at src/InkRenderer.sol:94: Storage Array Length not Cached\n[low] unchecked-return at src/InkFeeRouter.sol:58: Unchecked Return (6 places)\n[low] uninitialized-local-variable at src/InkLiquidity.sol:207: Uninitialized Local Variable (7 places)\n[low] unsafe-oz-erc721-mint at src/InkPortraits.sol:162: Unsafe `ERC721::_mint()` (3 places)\n[low] unused-public-function at src/InkHook.sol:150: Public Function Not Used Internally","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"b2a585da0ab0e68721f5e33564aa738ff9bc503a004219ce80f66391b01bbeee","verifiedTreeHash":"fb522de1ad023f23119a788d6a03189ad082f61d","verifierVersion":"0.1.0+a26ee00d"},{"checks":[{"durationMs":30712,"exitCode":0,"name":"build","output":"Compiling 194 files with Solc 0.8.26\nSolc 0.8.26 finished in 30.38s\nCompiler run successful with warnings:\nWarning (2519): This declaration shadows an existing declaration.\n  --> script/LocalStack.s.sol:95:26:\n   |\n95 |     function _deployHook(InkPortraits portraits, InkVault vault, address treasury)\n   |                          ^^^^^^^^^^^^^^^^^^^^^^\nNote: The shadowed declaration is here:\n  --> script/LocalStack.s.sol:38:5:\n   |\n38 |     InkPortraits portraits;\n   |     ^^^^^^^^^^^^^^^^^^^^^^\n\nWarning (2519): This declaration shadows an existing declaration.\n  --> script/LocalStack.s.sol:95:50:\n   |\n95 |     function _deployHook(InkPortraits portraits, InkVault vault, address treasury)\n   |                                                  ^^^^^^^^^^^^^^\nNote: The shadowed declaration is here:\n  --> script/LocalStack.s.sol:39:5:\n   |\n39 |     InkVault vault;\n   |     ^^^^^^^^^^^^^^\n\nWarning (2519): This declaration shadows an existing declaration.\n   --> script/LocalStack.s.sol:128:20:\n    |\n128 |     function _seed(PoolKey memory key, bool inkIs0, address to, ForkInk ink) internal {\n    |                    ^^^^^^^^^^^^^^^^^^\nNote: The shadowed declaration is here:\n  --> script/LocalStack.s.sol:44:5:\n   |\n44 |     PoolKey key;\n   |     ^^^^^^^^^^^\n\nWarning (2519): This declaration shadows an existing declaration.\n   --> script/LocalStack.s.sol:128:65:\n    |\n128 |     function _seed(PoolKey memory key, bool inkIs0, address to, ForkInk ink) internal {\n    |                                                                 ^^^^^^^^^^^\nNote: The shadowed declaration is here:\n  --> script/LocalStack.s.sol:43:5:\n   |\n43 |     ForkInk ink;\n   |     ^^^^^^^^^^^\n\nWarning (3149): The result type of the shift operation is equal to the type of the first operand (uint128) ignoring the (larger) type of the second operand (uint256) which might be unexpected. Silence this warning by either converting the first or the second operand to the type of the other.\n   --> src/InkLiquidity.sol:196:31:\n    |\n196 |             uint128 unitLiq = uint128(1e24) << k;\n    |                               ^^^^^^^^^^^^^^^^^^\n\n","passed":true},{"durationMs":18602,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/LaunchFeeHandoff.t.sol:LaunchFeeHandoffTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 278.17µs (0.00ns CPU time)\n\nRan 9 tests for test/SnapshotAllSeats.t.sol:SnapshotAllSeatsTest\n[SKIP] test_claims_0000() (gas: 0)\n[SKIP] test_claims_0250() (gas: 0)\n[SKIP] test_claims_0500() (gas: 0)\n[SKIP] test_claims_0750() (gas: 0)\n[SKIP] test_claims_1000() (gas: 0)\n[SKIP] test_claims_1250() (gas: 0)\n[SKIP] test_claims_1500() (gas: 0)\n[SKIP] test_claims_1750() (gas: 0)\n[SKIP] test_treeHasEverySeatOnce() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 9 skipped; finished in 1.15ms (1.42ms CPU time)\n\nRan 7 tests for test/MainnetFork.t.sol:MainnetForkInkIs1Test\n[SKIP] test_fork_agentHolderPaysNoHookFee() (gas: 0)\n[SKIP] test_fork_bigBuyAfterRelease() (gas: 0)\n[SKIP] test_fork_fullLifecycle() (gas: 0)\n[SKIP] test_fork_hookFeeExactInBuyIsInkToLiquidity() (gas: 0)\n[SKIP] test_fork_lockedUntilReleaseHoweverLong() (gas: 0)\n[SKIP] test_fork_sellsPayFeesBothWays() (gas: 0)\n[SKIP] test_fork_sybilFirstHour() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 7 skipped; finished in 1.33ms (342.68µs CPU time)\n\nRan 1 test for test/SybilOptions.t.sol:SybilPremium2xTest\n[SKIP] test_fork_sybilScenario() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 1.38ms (31.83µs CPU time)\n\nRan 1 test for test/SybilOptions.t.sol:SybilPremium5xTest\n[SKIP] test_fork_sybilScenario() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 1.44ms (32.35µs CPU time)\n\nRan 1 test for test/SybilOptions.t.sol:SybilPremium1xTest\n[SKIP] test_fork_sybilScenario() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 1.60ms (207.76µs CPU time)\n\nRan 1 test for test/LaunchDay.t.sol:LaunchDayTest\n[SKIP] test_fork_launchDay() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 3.16ms (37.30µs CPU time)\n\nRan 7 tests for test/MainnetFork.t.sol:MainnetForkInkIs0Test\n[SKIP] test_fork_agentHolderPaysNoHookFee() (gas: 0)\n[SKIP] test_fork_bigBuyAfterRelease() (gas: 0)\n[SKIP] test_fork_fullLifecycle() (gas: 0)\n[SKIP] test_fork_hookFeeExactInBuyIsInkToLiquidity() (gas: 0)\n[SKIP] test_fork_lockedUntilReleaseHoweverLong() (gas: 0)\n[SKIP] test_fork_sellsPayFeesBothWays() (gas: 0)\n[SKIP] test_fork_sybilFirstHour() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 7 skipped; finished in 3.40ms (184.11µs CPU time)\n\nRan 2 tests for test/SnapshotProofs.t.sol:SnapshotProofsTest\n[PASS] test_realSnapshotProofsClaim() (gas: 3222192)\n[PASS] test_tamperedStatsAreRejected() (gas: 605318)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 5.52ms (4.47ms CPU time)\n\nRan 4 tests for test/RangeMath.t.sol:RangeMathTest\n[PASS] testFuzz_rangeNeverRevertsAndStaysUsable(int24,int24,bool) (runs: 256, μ: 118242, ~: 118357)\n[PASS] test_maxWidthClampsWhenInkIsCurrency0() (gas: 131887)\n[PASS] test_maxWidthClampsWhenInkIsCurrency1() (gas: 115332)\n[PASS] test_ordinaryWidthUnchanged() (gas: 118547)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 10.67ms (9.77ms CPU time)\n\nRan 13 tests for test/InkHookLaunch.t.sol:InkHookLaunchTest\n[PASS] test_agentHolderPaysNoHookFee() (gas: 1467061)\n[PASS] test_callbacksRefuseEveryoneButThePoolManager() (gas: 1143610)\n[PASS] test_collectFeesPaysImdToTreasuryAndInkToLiquidity() (gas: 1503107)\n[PASS] test_factoryInitializesExactlyOnePoolPairedWithImd() (gas: 221940)\n[PASS] test_feeOnBuyExactInput() (gas: 1090718)\n[PASS] test_feeOnBuyExactOutput() (gas: 1089619)\n[PASS] test_feeOnSellExactInput() (gas: 1052158)\n[PASS] test_feeOnSellExactOutput() (gas: 1052991)\n[PASS] test_feeRateAndBindingsAreImmutable() (gas: 62563)\n[PASS] test_noSwapUntilTheVaultReleases() (gas: 1682408)\n[PASS] test_strangerCannotBindHookToWrongPoolBeforeFactory() (gas: 184498)\n[PASS] test_strangerCannotFixWrongFeeAndPriceBeforeFactory() (gas: 161648)\n[PASS] test_tokenIsFixedSupplyInk() (gas: 263361)\nSuite result: ok. 13 passed; 0 failed; 0 skipped; finished in 15.73ms (10.70ms CPU time)\n\nRan 4 tests for test/SepoliaLaunch.t.sol:SepoliaLaunchTest\n[PASS] test_allFiveRuntimesMeetProtectedFloor() (gas: 18566852)\n[PASS] test_existingOwnerSetupRemainsRequiredAndAvailable() (gas: 441282)\n[PASS] test_factoryDeploymentPreservesOwnersRoyaltiesAndReferences() (gas: 183938)\n[PASS] test_factoryTreasuryAndOutsiderHaveNoAdminRole() (gas: 1701232)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 37.57ms (38.24ms CPU time)\n\nRan 3 tests for test/InkHookLaunch.t.sol:InkHookFactoryLaunchTest\n[PASS] test_constructorsNeedNoLiveDependency() (gas: 69495)\n[PASS] test_factoryBindsThePoolOthersCannot() (gas: 346368)\n[PASS] test_runtimeSizeAndOpcodes() (gas: 2267700)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 38.56ms (7.29ms CPU time)\n\nRan 3 tests for test/MainnetContractsLaunch.t.sol:MainnetContractsLaunchTest\n[PASS] test_exactFourContractOrderReferencesAndOwners() (gas: 221680)\n[PASS] test_factoryTreasuryAndOriginHaveNoAdministrativeRights() (gas: 1697915)\n[PASS] test_runtimeSizeAndOpcodesMeetProtectedFloor() (gas: 16755780)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 68.32ms (64.67ms CPU time)\n\nRan 1 test for test/TokenURIPreview.t.sol:TokenURIPreviewTest\n[PASS] test_writeTokenURIs() (gas: 25423286)\nLogs:\n  read-only profile: not written out-preview/agent-280.txt (use FOUNDRY_PROFILE=preview)\n  read-only profile: not written out-preview/pool.txt (use FOUNDRY_PROFILE=preview)\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 96.01ms (95.57ms CPU time)\n\nRan 6 tests for test/InkRenderer.t.sol:InkRendererTest\n[PASS] test_agentTraitsMatchTheScript() (gas: 61685138)\n[PASS] test_noClaimsAfterTheFirstSwarmPortrait() (gas: 1397672)\n[PASS] test_poolTraitsMatchTheScript() (gas: 60520738)\n[PASS] test_scriptStoredInChunksAndLocked() (gas: 12171384)\n[PASS] test_swarmNumbersFillUnclaimedSeats() (gas: 163375864)\n[PASS] test_tokenURIForAnAgentPortrait() (gas: 3733117)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 524.20ms (680.36ms CPU time)\n\nRan 11 tests for test/HookFailurePaths.t.sol:HookFailurePathsInk0Test\n[PASS] testFuzz_constructorRejectsFeesAboveCap(uint16) (runs: 1000, μ: 6917, ~: 6744)\n[PASS] testFuzz_partialFillFeesUseExecutedAmounts(uint8,uint256) (runs: 1000, μ: 1984995, ~: 1982828)\n[PASS] test_constructorRejectsEachRequiredZeroAddress() (gas: 6691)\n[PASS] test_everySwapShapeRemainsLockedLongAfterTheClaimWindow() (gas: 424135)\n[PASS] test_failedInkPayoutRollsBackBothCurrenciesAndCanBeRetried() (gas: 2572005)\n[PASS] test_failedPortraitQueryRollsBackTheFirstTradeAndItsPrice() (gas: 2194938)\n[PASS] test_nestedCollectionCannotConsumeClaimsOrPoisonTheNextCollection() (gas: 2640308)\n[PASS] test_portraitWaiverFollowsOriginAndRequiresAnEarlierBlock() (gas: 3128933)\n[PASS] test_receivingAnotherPortraitDoesNotResetAnExistingHoldingPeriod() (gas: 2020821)\n[PASS] test_releaseBoundaryAndFailedListingAreAtomic() (gas: 2123938)\n[PASS] test_zeroFeeSkipsPortraitLookupAndMaximumFeeIsAccepted() (gas: 129356)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 575.74ms (1.10s CPU time)\n\nRan 8 tests for test/HookFailurePaths.t.sol:HookFailurePathsInk1Test\n[PASS] testFuzz_partialFillFeesUseExecutedAmounts(uint8,uint256) (runs: 1000, μ: 2014840, ~: 2022040)\n[PASS] test_everySwapShapeRemainsLockedLongAfterTheClaimWindow() (gas: 424135)\n[PASS] test_failedInkPayoutRollsBackBothCurrenciesAndCanBeRetried() (gas: 2661756)\n[PASS] test_failedPortraitQueryRollsBackTheFirstTradeAndItsPrice() (gas: 2254294)\n[PASS] test_nestedCollectionCannotConsumeClaimsOrPoisonTheNextCollection() (gas: 2730018)\n[PASS] test_portraitWaiverFollowsOriginAndRequiresAnEarlierBlock() (gas: 3172914)\n[PASS] test_receivingAnotherPortraitDoesNotResetAnExistingHoldingPeriod() (gas: 2061069)\n[PASS] test_releaseBoundaryAndFailedListingAreAtomic() (gas: 2139602)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 576.01ms (589.00ms CPU time)\n\nRan 16 tests for test/LaunchSafety.t.sol:LaunchSafetyTest\n[PASS] testFuzz_directPortraitTransfersCannotBypassDeposit(bool,uint8) (runs: 256, μ: 624869, ~: 407054)\n[PASS] testFuzz_expiredPullChargesPurchaseFeeAndPreservesBacking(uint16,uint16,bool) (runs: 256, μ: 565045, ~: 567933)\n[PASS] test_agentHolderDiscountOnlyOnOwnPullsStillHeld() (gas: 3268385)\n[PASS] test_cannotRenounceBeforeRequiredConfiguration() (gas: 478947)\n[PASS] test_expiredBatchRespectsProcessingLimit() (gas: 691194)\n[PASS] test_expiredHeadCannotStallLaterLivePull() (gas: 800011)\n[PASS] test_expiredTraitRerollConsumesFeeAndRequiresNewPayment() (gas: 977128)\n[PASS] test_nonHolderPaysFullFeeThroughAgentHoldingContract() (gas: 1368938)\n[PASS] test_ownershipTransferRequiresAcceptance() (gas: 640058)\n[PASS] test_portraitsCannotBeSentToLiquidity() (gas: 586110)\n[PASS] test_pullLastAvailableBlockRevealsWithoutExpiryFee() (gas: 484320)\n[PASS] test_setInkRequiresCodeAndFullAllocationBeforeItCommits() (gas: 319403)\n[PASS] test_setupRejectsWrongTokenLiquidityAndRemainsCorrectable() (gas: 1228493)\n[PASS] test_traitRerollLastAvailableBlockUsesOriginalHash() (gas: 688736)\n[PASS] test_traitRerollThroughAgentHoldingContractPaysFullFee() (gas: 1216106)\n[PASS] test_vaultAndRendererBindingFailuresRemainCorrectable() (gas: 433271)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 575.90ms (674.45ms CPU time)\n\nRan 55 tests for test/InkSystem.t.sol:InkSystemTest\n[PASS] test_agentClaimsOwnPortraitFree() (gas: 311177)\n[PASS] test_agentDepositReturnsExactlyItsBacking() (gas: 3516061)\n[PASS] test_agentHoldersPayNoHookFee() (gas: 2734061)\n[PASS] test_agentHoldersRerollForHalf() (gas: 4214951)\n[PASS] test_borrowedAgentPortraitGetsNoWaiver() (gas: 2555036)\n[PASS] test_cantLockAnEmptyRenderer() (gas: 3138116)\n[PASS] test_claimRules() (gas: 465550)\n[PASS] test_collectFeesPaysImdToTreasuryAndKeepsInk() (gas: 3367034)\n[PASS] test_contractURIOwnerOnly() (gas: 94718)\n[PASS] test_contractWalletSeatCanClaim() (gas: 387248)\n[PASS] test_depositMinOut() (gas: 3038333)\n[PASS] test_depositedAgentPortraitIsPullable() (gas: 3344870)\n[PASS] test_feeRouterClaimAndRoute() (gas: 4381579)\n[PASS] test_feeRouterNeedsPoolAndAddresses() (gas: 3872711)\n[PASS] test_feeRouterSplitsAndCompounds() (gas: 4127736)\n[PASS] test_gasAtScale() (gas: 78817084)\nLogs:\n  pull(1) gas: 161004\n  revealNext(1) gas: 221322\n    per reveal: 221322\n  pull(10) gas: 548232\n  revealNext(10) gas: 1140496\n    per reveal: 114049\n  pull(100) gas: 4762670\n  revealNext(100) gas: 10847735\n    per reveal: 108477\n  pull(400) gas: 18812864\n  revealNext(400) gas: 43238119\n    per reveal: 108095\n  deposit(100 portraits) gas: 6950387\n\n[PASS] test_guards() (gas: 12641088)\n[PASS] test_hookFeeImdToTreasury() (gas: 2210755)\n[PASS] test_hookFeeInkGoesToLiquidity() (gas: 2701726)\n[PASS] test_idleSeatClaimsToo() (gas: 227103)\n[PASS] test_inkFeesGoBackIntoThePool() (gas: 6092322)\n[PASS] test_launchSplit() (gas: 67244)\n[PASS] test_liquidityCannotRenounceBeforePoolIsConfigured() (gas: 3071147)\n[PASS] test_liquidityFloorHoldsIfPriceIsDragged() (gas: 6606776)\n[PASS] test_liquidityOwnershipTransferRequiresAcceptance() (gas: 222592)\n[PASS] test_liquidityRefusesUnrelatedNFTsButKeepsPositionCustody() (gas: 2106517)\n[PASS] test_mainnetPoolMustPairWithOfficialIMD() (gas: 3079882)\n[PASS] test_noPerWalletLimitAfterRelease() (gas: 2124588)\n[PASS] test_noPullsUntilRelease() (gas: 2230624)\n[PASS] test_onlyOnePool() (gas: 46381)\n[PASS] test_onlyTheFactoryCanInitialize() (gas: 54112)\n[PASS] test_ownerIsOwnerWalletNotDeployer() (gas: 7990817)\n[PASS] test_permissionlessListingTracksSpotAboveReleaseFloor() (gas: 3372170)\n[PASS] test_poolLockedUntilRelease() (gas: 2618366)\n[PASS] test_poolNeverOpensWithoutRelease() (gas: 2477170)\n[PASS] test_premiumBeforePoolCannotBeFrontRun() (gas: 6049502)\n[PASS] test_premiumCannotBeLockedByListingBeforeRelease() (gas: 6665634)\n[PASS] test_pullRerollPullAgain() (gas: 3918561)\n[PASS] test_releaseLadderShape() (gas: 2319548)\n[PASS] test_releasePremiumListsAboveTheOpeningPrice() (gas: 1925754)\n[PASS] test_releaseTurnsUnclaimedSeatsIntoSlotsAndPoolInk() (gas: 2074111)\n[PASS] test_revealIsNotEarlyAndNotTwice() (gas: 2625250)\n[PASS] test_seatSoldDuringWindowNewOwnerClaims() (gas: 301442)\n[PASS] test_setPoolRejectsHooklessPoolAndRemainsCorrectable() (gas: 3123371)\n[PASS] test_setPoolRejectsInconsistentHookBindings() (gas: 6644412)\n[PASS] test_setPoolRequiresPositionManagerBoundToSameManager() (gas: 3102547)\n[PASS] test_setupRules() (gas: 3137736)\n[PASS] test_setupStartMustBeSoon() (gas: 3127148)\n[PASS] test_traitRerollBeforeReleaseReverts() (gas: 39919)\n[PASS] test_traitRerollExpiredHashClosesWithoutFreeDraw() (gas: 2971955)\n[PASS] test_traitRerollGuards() (gas: 3463065)\n[PASS] test_traitRerollHalfPriceForAgentHolders() (gas: 3130793)\n[PASS] test_traitRerollRepaintsSameToken() (gas: 3032508)\n[PASS] test_vaultStaysFullyBacked() (gas: 3971924)\n[PASS] test_zeroTradingFee() (gas: 29321)\nSuite result: ok. 55 passed; 0 failed; 0 skipped; finished in 575.69ms (273.90ms CPU time)\n\nRan 3 tests for test/RendererBoundaryProperties.t.sol:RendererBoundaryPropertiesTest\n[PASS] testFuzz_binaryChunksRoundTripAndLock(bytes,bytes) (runs: 1000, μ: 464061, ~: 460611)\n[PASS] test_chunkLimitAndRejectedWritesDoNotDamageUploadedArt() (gas: 5657977)\n[PASS] test_ownershipTransferRevokesOldOwnerAcrossEveryRendererMutation() (gas: 615341)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 819.26ms (819.78ms CPU time)\n\nRan 15 tests for test/AuditRegression.t.sol:AuditRegressionTest\n[PASS] testFuzz_admissionCompletesWithBothRevealEntryPoints(uint16,bool) (runs: 256, μ: 23887036, ~: 15187954)\n[PASS] test_directSafeTransfersRejectedForBothOverloadsAndOtherNFTs() (gas: 900066)\n[PASS] test_expiredHeadPullNeverWidensALaterPullsDraw() (gas: 2219191)\n[PASS] test_expiredPullsCloseWithoutWideningDrawsOrRenewing() (gas: 1439598)\n[PASS] test_hashExpiryDuringAdmissionKeepsOriginalDraw() (gas: 47716799)\n[PASS] test_initializedPoolAtZeroTickIsValid() (gas: 4800059)\n[PASS] test_largeAdmissionQueueMakesProgressWithinGasBudget() (gas: 468123702)\nLogs:\n  largest admission/reveal step gas (including call overhead): 3466561\n\n[PASS] test_partialAdmissionPreservesDrawAndExcludesSameBlockAndLaterDeposits() (gas: 25071279)\n[PASS] test_pendingRerollBlocksApprovedOperatorsUntilExpiry() (gas: 1668950)\n[PASS] test_pendingRerollCannotBeSoldWithOldTraits() (gas: 802202)\n[PASS] test_safeTransferToVaultIsRejected() (gas: 531345)\n[PASS] test_sameBlockHandInsDoNotCountForPullsMadeNow() (gas: 275287704)\n[PASS] test_setupRefusesUnconfiguredLiquidityBeforeCommitting() (gas: 4689583)\n[PASS] test_setupRejectsLiquidityWithoutCode() (gas: 291131)\n[PASS] test_uninitializedPoolCannotSetPermanentFloor() (gas: 4894050)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 819.73ms (1.37s CPU time)\n\nRan 4 tests for test/LiquidityCustodyInvariants.t.sol:LiquidityCustodyInk0Test\n[PASS]\nLiquidityCustodyInk0Test invariants:\n[PASS] invariant_configurationAndBackingStayIntact\n[PASS] invariant_conservationAndFeeDestinations\n[PASS] invariant_positionsStayLockedAndFullyLiquid\n LiquidityCustodyInk0Test invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------------------+-------------+-------+---------+----------╮\n| Contract                | Selector    | Calls | Reverts | Discards |\n+====================================================================+\n| LiquidityCustodyHandler | add         | 1137  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | changeWidth | 1177  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | collect     | 1188  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | collectHook | 1177  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | donate      | 1152  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | rejectTheft | 1180  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | trade       | 1181  | 0       | 0        |\n╰-------------------------+-------------+-------+---------+----------╯\n\n[PASS] test_minimumAddBoundaryAndEmptyCollectionRanges() (gas: 4633516)\n[PASS] test_positionManagerFailureRollsBackPrefundingAndCanBeRetried() (gas: 4057387)\n[PASS] test_roundTripCollectAndCompound() (gas: 2956058)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 9.72s (9.73s CPU time)\n\nRan 4 tests for test/LiquidityCustodyInvariants.t.sol:LiquidityCustodyInk1Test\n[PASS]\nLiquidityCustodyInk1Test invariants:\n[PASS] invariant_configurationAndBackingStayIntact\n[PASS] invariant_conservationAndFeeDestinations\n[PASS] invariant_positionsStayLockedAndFullyLiquid\n LiquidityCustodyInk1Test invariants (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------------------+-------------+-------+---------+----------╮\n| Contract                | Selector    | Calls | Reverts | Discards |\n+====================================================================+\n| LiquidityCustodyHandler | add         | 1137  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | changeWidth | 1177  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | collect     | 1188  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | collectHook | 1177  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | donate      | 1152  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | rejectTheft | 1180  | 0       | 0        |\n|-------------------------+-------------+-------+---------+----------|\n| LiquidityCustodyHandler | trade       | 1181  | 0       | 0        |\n╰-------------------------+-------------+-------+---------+----------╯\n\n[PASS] test_minimumAddBoundaryAndEmptyCollectionRanges() (gas: 4623282)\n[PASS] test_positionManagerFailureRollsBackPrefundingAndCanBeRetried() (gas: 4041920)\n[PASS] test_roundTripCollectAndCompound() (gas: 3039507)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 10.01s (10.01s CPU time)\n\nRan 5 tests for test/LaunchTokenAccounting.t.sol:LaunchTokenAccountingTest\n[PASS]\nLaunchTokenAccountingTest invariants:\n[PASS] invariant_allowancesFollowApprovalsAndSuccessfulSpends\n[PASS] invariant_balancesConserveFixedSupply\n LaunchTokenAccountingTest invariants (runs: 256, calls: 32768, reverts: 0)\n\n╭--------------------+----------+-------+---------+----------╮\n| Contract           | Selector | Calls | Reverts | Discards |\n+============================================================+\n| LaunchTokenHandler | approve  | 8186  | 0       | 0        |\n|--------------------+----------+-------+---------+----------|\n| LaunchTokenHandler | reject   | 8274  | 0       | 0        |\n|--------------------+----------+-------+---------+----------|\n| LaunchTokenHandler | spend    | 8127  | 0       | 0        |\n|--------------------+----------+-------+---------+----------|\n| LaunchTokenHandler | transfer | 8181  | 0       | 0        |\n╰--------------------+----------+-------+---------+----------╯\n\n[PASS] testFuzz_roundTripsHaveNoTransferTax(uint256) (runs: 1000, μ: 146470, ~: 146266)\n[PASS] test_finiteInfiniteAndRevokedApprovals() (gas: 1044068)\n[PASS] test_zeroAddressesAndMaximumSpendCannotDestroyTokens() (gas: 253037)\n[PASS] test_zeroOneFullBalanceAndSelfTransfers() (gas: 451744)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 10.89s (11.41s CPU time)\n\nRan 1 test for test/VaultInvariants.t.sol:VaultInvariantsTest\n[PASS]\nVaultInvariantsTest invariants:\n[PASS] invariant_agentBalancesMatch\n[PASS] invariant_capAndSlots\n[PASS] invariant_custody\n[PASS] invariant_feesToLiquidity\n[PASS] invariant_fullyBacked\n[PASS] invariant_inkConserved\n[PASS] invariant_queueCovered\n VaultInvariantsTest invariants (runs: 64, calls: 7680, reverts: 0)\n\n╭--------------+------------------+-------+---------+----------╮\n| Contract     | Selector         | Calls | Reverts | Discards |\n+==============================================================+\n| VaultHandler | claim            | 536   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | deposit          | 502   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | moveSeat         | 580   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | pull             | 556   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | pullAll          | 584   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | pullTooMany      | 543   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | release          | 551   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | rerollTraits     | 565   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | reveal           | 518   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | revealNext       | 522   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | revealTraits     | 567   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | roll             | 540   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | setFees          | 565   | 0       | 0        |\n|--------------+------------------+-------+---------+----------|\n| VaultHandler | transferPortrait | 551   | 0       | 0        |\n╰--------------+------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 15.76s (15.75s CPU time)\n\nRan 2 tests for test/HookAccountingInvariants.t.sol:HookAccountingInk0Test\n[PASS]\nHookAccountingInk0Test invariants:\n[PASS] invariant_accrualEqualsClaimsPlusPayouts\n[PASS] invariant_fixedSupplyAndCustody\n[PASS] invariant_portraitEligibilityMatchesOwnershipHistory\n[PASS] invariant_releaseAndFirstTradeAreMonotonic\n HookAccountingInk0Test invariants (runs: 256, calls: 32768, reverts: 0)\n\n╭-----------------------+--------------+-------+---------+----------╮\n| Contract              | Selector     | Calls | Reverts | Discards |\n+===================================================================+\n| HookAccountingHandler | advance      | 6560  | 0       | 0        |\n|-----------------------+--------------+-------+---------+----------|\n| HookAccountingHandler | collect      | 6452  | 0       | 0        |\n|-----------------------+--------------+-------+---------+----------|\n| HookAccountingHandler | movePortrait | 6718  | 0       | 0        |\n|-----------------------+--------------+-------+---------+----------|\n| HookAccountingHandler | release      | 6551  | 0       | 0        |\n|-----------------------+--------------+-------+---------+----------|\n| HookAccountingHandler | trade        | 6487  | 0       | 0        |\n╰-----------------------+--------------+-------+---------+----------╯\n\n[PASS] test_handlerReachesLocksAllSwapShapesWaiversAndRepeatedCollections() (gas: 5637043)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 17.15s (17.14s CPU time)\n\nRan 2 tests for test/HookAccountingInvariants.t.sol:HookAccountingInk1Test\n[PASS]\nHookAccountingInk1Test invariants:\n[PASS] invariant_accrualEqualsClaimsPlusPayouts\n[PASS] invariant_fixedSupplyAndCustody\n[PASS] invariant_portraitEligibilityMatchesOwnershipHistory\n[PASS] invariant_releaseAndFirstTradeAreMonotonic\n HookAccountingInk1Test invariants (runs: 256, calls: 32768, reverts: 0)\n\n╭-----------------------+--------------+-------+---------+----------╮\n| Contract              | Selector     | Calls | Reverts | Discards |\n+===================================================================+\n| HookAccountingHandler | advance      | 6560  | 0       | 0        |\n|-----------------------+--------------+-------+---------+----------|\n| HookAccountingHandler | collect      | 6452  | 0       | 0        |\n|-----------------------+--------------+-------+---------+----------|\n| HookAccountingHandler | movePortrait | 6718  | 0       | 0        |\n|-----------------------+--------------+-------+---------+----------|\n| HookAccountingHandler | release      | 6551  | 0       | 0        |\n|-----------------------+--------------+-------+---------+----------|\n| HookAccountingHandler | trade        | 6487  | 0       | 0        |\n╰-----------------------+--------------+-------+---------+----------╯\n\n[PASS] test_handlerReachesLocksAllSwapShapesWaiversAndRepeatedCollections() (gas: 5679444)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 18.31s (18.32s CPU time)\n\nRan 28 test suites in 18.33s (86.58s CPU time): 162 tests passed, 0 failed, 28 skipped (190 total tests)\n","passed":true},{"durationMs":237,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"InkFeeRouter.claimAndRoute(address,uint64)\",\"InkFeeRouter.route()\",\"InkHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"InkHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"InkHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"InkHook.collectFees()\",\"InkHook.unlockCallback(bytes)\",\"InkLiquidity.acceptOwnership()\",\"InkLiquidity.addLiquidity()\",\"InkLiquidity.collectFees(uint256,uint256)\",\"InkLiquidity.renounceOwnership()\",\"InkLiquidity.setPool((address,address,uint24,int24,address),address)\",\"InkLiquidity.setPremium(int24)\",\"InkLiquidity.setWidth(int24)\",\"InkLiquidity.transferOwnership(address)\",\"InkPortraits.acceptOwnership()\",\"InkPortraits.approve(address,uint256)\",\"InkPortraits.lockRenderer()\",\"InkPortraits.mintAgent(uint256,address,(uint32,uint16,uint32,uint32,bool))\",\"InkPortraits.mintSwarm(address,bytes32)\",\"InkPortraits.renounceOwnership()\",\"InkPortraits.safeTransferFrom(address,address,uint256)\",\"InkPortraits.safeTransferFrom(address,address,uint256,bytes)\",\"InkPortraits.setApprovalForAll(address,bool)\",\"InkPortraits.setContractURI(string)\",\"InkPortraits.setRenderer(address)\",\"InkPortraits.setSwarmSeed(uint256,bytes32)\",\"InkPortraits.setVault(address)\",\"InkPortraits.transferFrom(address,address,uint256)\",\"InkPortraits.transferOwnership(address)\",\"InkRenderer.acceptOwnership()\",\"InkRenderer.addChunk(bytes)\",\"InkRenderer.lock()\",\"InkRenderer.renounceOwnership()\",\"InkRenderer.setPreviewBase(string)\",\"InkRenderer.transferOwnership(address)\",\"InkVault.acceptOwnership()\",\"InkVault.claim(uint256,(uint32,uint16,uint32,uint32,bool),bytes32[])\",\"InkVault.deposit(uint256[],uint256)\",\"InkVault.pull(uint256)\",\"InkVault.release()\",\"InkVault.renounceOwnership()\",\"InkVault.rerollTraits(uint256,uint256)\",\"InkVault.reveal(uint256)\",\"InkVault.revealNext(uint256)\",\"InkVault.revealTraits(uint256)\",\"InkVault.setFees(uint16,uint16)\",\"InkVault.setInk(address)\",\"InkVault.setup(bytes32,uint64,address)\",\"InkVault.transferOwnership(address)\",\"LaunchToken.approve(address,uint256)\",\"LaunchToken.transfer(address,uint256)\",\"LaunchToken.transferFrom(address,address,uint256)\",\"TestSeats.approve(address,uint256)\",\"TestSeats.mint(uint256)\",\"TestSeats.safeTransferFrom(address,address,uint256)\",\"TestSeats.safeTransferFrom(address,address,uint256,bytes)\",\"TestSeats.setApprovalForAll(address,bool)\",\"TestSeats.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":7,\"ADAPTATION.md\":416,\"AUDIT_BRIEF.md\":71,\"BYTECODE_HASHES.json\":52,\"DEPENDENCIES.md\":10,\"PACKAGING.md\":16,\"README.md\":93,\"SETUP.md\":101,\"SHA256SUMS\":1648,\"SOURCE_PROVENANCE.json\":11,\"VALIDATION.txt\":554,\"art/original-art.js\":418,\"foundry.toml\":33,\"launch/README.md\":35,\"launch/mainnet-1-contracts.json\":24,\"launch/mainnet-2-hook.json\":31,\"launch/sepolia-1-contracts.json\":24,\"launch/sepolia-2-hook.json\":31,\"remappings.txt\":8,\"script/Configure.s.sol\":63,\"script/InkSteps.sol\":135,\"script/LocalStack.s.sol\":154,\"script/Rehearse.s.sol\":258,\"script/UploadRenderer.s.sol\":33,\"src/InkFeeRouter.sol\":77,\"src/InkHook.sol\":270,\"src/InkLiquidity.sol\":335,\"src/InkPortraits.sol\":271,\"src/InkRenderer.sol\":311,\"src/InkVault.sol\":503,\"src/LaunchToken.sol\":17,\"src/testnet/TestSeats.sol\":13,\"test/AuditRegression.t.sol\":477,\"test/HookAccountingInvariants.t.sol\":395,\"test/HookCoverage.md\":138,\"test/HookFailurePaths.t.sol\":357,\"test/InkHookLaunch.t.sol\":571,\"test/InkRenderer.t.sol\":210,\"test/InkSystem.t.sol\":1429,\"test/LaunchDay.t.sol\":270,\"test/LaunchFeeHandoff.t.sol\":82,\"test/LaunchSafety.t.sol\":478,\"test/LaunchTokenAccounting.t.sol\":206,\"test/LiquidityCustodyInvariants.t.sol\":429,\"test/MainnetContractsLaunch.t.sol\":159,\"test/MainnetFork.t.sol\":549,\"test/RangeMath.t.sol\":80,\"test/RendererBoundaryProperties.t.sol\":137,\"test/SepoliaLaunch.t.sol\":186,\"test/SnapshotAllSeats.t.sol\":159,\"test/SnapshotProofs.t.sol\":91,\"test/SybilOptions.t.sol\":104,\"test/TestingCoverage.md\":42,\"test/TokenURIPreview.t.sol\":79,\"test/VaultInvariants.t.sol\":403,\"test/fixtures/snapshot-sample.json\":98,\"test/fixtures/traits.json\":1,\"test/helpers/ConfiguredLiquidity.sol\":18,\"test/helpers/HookAccountingFixture.sol\":187},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"c2ae41c25b2b930c80b4b39d23d99c48d75d2ffb0d96f57a3e87adea56ec7a3c","verifiedTreeHash":"c17d013b1a2a59047f6fa6339c916a36ed8a4884","verifierVersion":"0.1.0+a26ee00d"}]}