{"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":"7400cf72-96af-4acb-bf4b-3ca2208b3e58","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"7ef1cb8cea95e66622a903fc599c4c55c20cf19cd4049bf57eb84ae0b6b20f6f","dependsOn":["build_contract_project","write_foundry_tests","frontend_for_contract","manifest"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"cbb3d3078cc826b7d164130bcd10fda3b275390dd97137a9f882aac64de1a295","dependsOn":["build_contract_project","write_foundry_tests","frontend_for_contract","manifest"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"925578bca7505102b9fcaf1592ae9c128304055a910b1419641cefade5c3c567","dependsOn":["build_contract_project","write_foundry_tests","frontend_for_contract","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":"51b9a68c9b8328ed318c1ebbf9b63b41ac8a4ce03d37599367f98b139d97907d","dependsOn":["build_contract_project","write_foundry_tests","frontend_for_contract","manifest"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"603189a668954176ffc281714723410db4d53f64c9ad53b76889400b0a394c3d","dependsOn":["build_contract_project","write_foundry_tests","frontend_for_contract","manifest"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"847456388a693943951806383def512c8109a7d70a83fe7f634daec76c0b883b","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"d59561da3b378461b7c749f6bd5084477d44993aa94972ddea90f542535751df","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"d59561da3b378461b7c749f6bd5084477d44993aa94972ddea90f542535751df","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"1dd9ebc144b163265c4f0f8be09a78b06d37d813cc701884d89d4924bc550410","dependsOn":["write_foundry_tests"],"execution":{"mustProduce":["dist/index.html"],"network":true,"optionalTools":["browser"],"profile":"foundry","requires":["network"],"skillHash":"7a8712ac362fd9796a2d33fcee6ee3ad1adaefb41673944a34b3d34ef78b3e37","skillId":"frontend-for-contract","tools":[]},"key":"frontend_for_contract","kind":"code","role":"implement","skillHash":"7a8712ac362fd9796a2d33fcee6ee3ad1adaefb41673944a34b3d34ef78b3e37","skillId":"frontend-for-contract","state":"accepted"},{"acceptedSubmissionHash":"98bb3ddba0fa0e608dbd8122c3c4215eeb034eb8dc42a992f57d03cf2b5a78f4","dependsOn":["build_contract_project","write_foundry_tests","frontend_for_contract"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"e3598d208c22804d1897f0757b987fcb9bbdbb3590004d56f0bc6e73a175dbc1","dependsOn":["build_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":"CROWN - a per-ticker crown registry and Uniswap v4 fee hook. Build the full system, deploy it on Robinhood Chain (chainId 4663), and deliver a working end-to-end demo.\n\nCONCEPT: One canonical token per ticker - the king. A registry contract maps ticker -> {kingToken, beneficiary, reignStartedAt}. A Uniswap v4 hook attached to participating pools skims a cut of every swap and pays it, inside the same transaction, to the current king`s beneficiary wallet (pay-at-source: fees must never be stored in the hook or pooled for claiming). Anyone can challenge the king with a bonded ETH stake-vote; if the challenger out-stakes the king within the window and quorum is met, the registry flips after a timelock and every subsequent swap pays the new king.\n\nCONTRACT 1 - CrownRegistry:\n- ticker is bytes32 = keccak256 of the uppercase symbol string.\n- registerKing(ticker, token, beneficiary): the first registration for a ticker crowns that token.\n- openChallenge(ticker, challengerToken) payable: bond 0.01 ETH; one live challenge per ticker; a king is challengeable only after a 7-day minimum reign; 3-day cooldown per ticker after a failed challenge.\n- stake(challengeId, side) payable in ETH during a 72-hour window (side = king or challenger).\n- finalize(challengeId) after the window plus a 24-hour timelock: the challenger wins iff challenger stake > king stake AND total staked >= 0.05 ETH quorum. On a flip, the registry points to the challenger token and its beneficiary. Losing side: 80% refundable to its stakers (pull pattern), 20% slashed - half of the slash pro-rata to winning-side stakers (pull), half to treasury 0xf19F11Dee16a341671E6Ed3e209b704C1Bbc3E57. Winning side`s stake is fully refundable (pull).\n- Events: Crowned, ChallengeOpened, Staked, Dethroned, ChallengeFailed. Public per-ticker totals.\n- No admin functions after deployment beyond the constructor`s parameter init; resolution is purely rule-based.\n\nCONTRACT 2 - CrownHook (Uniswap v4; afterSwap and afterSwapReturnsDelta permissions):\n- afterInitialize records poolId -> ticker; the deployer registers each pool`s ticker explicitly.\n- On every swap: compute the cut (per-pool bps set at init, default 100, range 0-500) from the swap amount, read registry.king(ticker).beneficiary, take the cut as the hook`s delta and transfer it to the beneficiary within the same settlement. Maintain a public counter crownPaid[ticker][currency] (cumulative).\n- The hook must never revert or block a swap: if the registry read or the transfer fails, the swap proceeds without the cut.\n\nDEMO LAUNCH (this launch`s token): name \"Crown Demo\", symbol CRWN, paired with native ETH on Robinhood Chain, pool wearing CrownHook, registered as genesis king of ticker CRWN with beneficiary = the launch payer wallet. Additionally deploy a second, distinct plain ERC-20 with the same name and symbol CRWN (a deploy script is fine) as the standing challenger candidate, so a full dethrone (challenge, stake, finalize, recipient flip) can be executed end-to-end after launch.\n\nSITE: the canonical ticker page - current king per ticker, crownPaid totals per ticker, live challenge state (window, both stakes, time remaining), stake and challenge actions wired to the contracts, and a plain-English how-it-works section.\n\nTESTS (Foundry, in the repo): registry and challenge math unit tests (bond, quorum, slash split exact to the wei) and a Uniswap v4 integration test executing buy -> challenge -> stake -> finalize -> buy, asserting the fee recipient changed and crownPaid matches the transfers. Contracts must compile with the repo`s pinned settings and be Sourcify-verifiable; record the compiler settings in the README.","parentJobId":null,"planHash":"1b2626f19a5c2bc729292594aa84a9feebde2a5abec49161a1c432857aefba61","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"7400cf72-96af-4acb-bf4b-3ca2208b3e58","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1867-crown-per-ticker-crown"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"52289","feedbackHash":"5829c57dd347ad3f83f302222489b0b5aba189f8053084409707f3eafb617666","nodeKey":"audit_economics","submissionHash":"7ef1cb8cea95e66622a903fc599c4c55c20cf19cd4049bf57eb84ae0b6b20f6f","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51104","feedbackHash":"c92c837e287b0295af4b54cb9d2383a3ccfa35d86a3992c491cdb5a367badadc","nodeKey":"audit_flow","submissionHash":"cbb3d3078cc826b7d164130bcd10fda3b275390dd97137a9f882aac64de1a295","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"53929","feedbackHash":"c316e8b8f50ac5d9ab881a5afeb61710c061c6803c8c101df23d1ac34a970115","nodeKey":"audit_judge","submissionHash":"6cc651d425aa90c959ac6c4ffb2512fc5bdc39008d85c2d3290d2dc85ec809da","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52152","feedbackHash":"ad56e281d886aa62c71f429adc69502e8df1dfc9feae179550c303d390df51fa","nodeKey":"audit_judge","submissionHash":"925578bca7505102b9fcaf1592ae9c128304055a910b1419641cefade5c3c567","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51515","feedbackHash":"5da44d2eb91aea10bb3934a111865ff9902c30a5d72c57e3d64470a7244e7331","nodeKey":"audit_judge","submissionHash":"64a792de656d4f5bb98e5d5d2a6914e09b8df1233060d94cafd2b46933ca7657","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51420","feedbackHash":"cec5b68df1376ecf0501d4c48b6f2f5b5afad09800f600af2f78f84c7e24ae7b","nodeKey":"audit_math","submissionHash":"51b9a68c9b8328ed318c1ebbf9b63b41ac8a4ce03d37599367f98b139d97907d","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51027","feedbackHash":"33542df1fb223871494a02b9d9439ae2d3e30593771a8057381985c68d0a2a81","nodeKey":"audit_permissions","submissionHash":"603189a668954176ffc281714723410db4d53f64c9ad53b76889400b0a394c3d","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52585","feedbackHash":"f6e28a2ef3462e67611ebf4450ade04ce8ad66193ca72f16c809f6dc952da14d","nodeKey":"build_contract_project","submissionHash":"847456388a693943951806383def512c8109a7d70a83fe7f634daec76c0b883b","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51021","feedbackHash":"11d5ac3ef420a3e63c7738b36a5809b0fe87fed3781cb3b954b86d13dfbdce2e","nodeKey":"frontend_for_contract","submissionHash":"1dd9ebc144b163265c4f0f8be09a78b06d37d813cc701884d89d4924bc550410","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52091","feedbackHash":"30e07b535dc1d8b4aeb33908c4b61826533351d6d020dc7be434ff61f9d837ab","nodeKey":"frontend_for_contract","submissionHash":"f17066ebcac860b64c1c72b38cef7c5a071ca380777a549b4928217758ac6cac","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51025","feedbackHash":"1c5cfa7f7d702f12ecbe0a6c8769e1b676e7082fc0c90086908068cd50c84301","nodeKey":"frontend_for_contract","submissionHash":"8ecfa7f330507ededa4c6f234595e018c4259898d1285471506bce7f714c6e81","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52167","feedbackHash":"f1ab198e70722cd73fdd003d86d15be4f056aa93335bceff571e63bf92b6cabb","nodeKey":"manifest","submissionHash":"98bb3ddba0fa0e608dbd8122c3c4215eeb034eb8dc42a992f57d03cf2b5a78f4","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51728","feedbackHash":"3be7e65f6f52a050bcf6aa27ab22ad80e5817448c315f90d54692547b826d295","nodeKey":"manifest","submissionHash":"5fc989a14e4c5b19b9a0330a94f1b7a4fbd706e20eb2632e63d6d9517592e6c1","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"50989","feedbackHash":"a80effc8a766dbe47957cc6db8d7e3c299439517801e584402a746eb1130ea5d","nodeKey":"manifest","submissionHash":"34252beeee3b44aefc7960f06ee0dd4c7f259e4c98e750bc45cdec8a4a51553c","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51317","feedbackHash":"c39736f457d750c406714d935016aab3a29123c1e619405e975c8f76402694ac","nodeKey":"write_foundry_tests","submissionHash":"e3598d208c22804d1897f0757b987fcb9bbdbb3590004d56f0bc6e73a175dbc1","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"ece84ed8e72d5417f634ad1325126c932dd0c70704ba330c2510396741fd57e0","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"1826613e327030815f274bfa7b0f7e0a57ea3f9f6050aea11d93b195ca4c58d0","device":"72ae9b5bbd1a54b6","findings":[],"hash":"1dd9ebc144b163265c4f0f8be09a78b06d37d813cc701884d89d4924bc550410","nodeId":"fb3ed2b0-b2f9-4643-9652-dc72c670dd60","outcome":"completed","summary":"The responses file is in place at the repository root, excluded from the diff by the harness's own exclude rule, as in the previous round. The work is complete.\n\n**Finding 641d732f (sybil self-challenge with a one-wei defence): fixed.** The reviewer's proof reproduced on the starting tree with `ReignTooShort()` and passes on the result.\n\n**Cause.** `finalize` restarted the reign for any nonzero king stake, and the 20% slash of one wei rounds to zero, so the incumbent's second wallet could re-crown it for free and lock honest challengers out for seven days.\n\n**Fix in `src/CrownRegistry.sol`.** The reign clock and the challenge gate are now separate values. A flip always restarts `reignStartedAt`, which the proof requires. The new `challengeableAt` moves seven days ahead only when the king side was defended with at least the new constant `MIN_DEFENCE`, equal to the 0.01 ETH bond. A losing defence of that size is slashed 20%, half to the treasury, so protection always costs at least 0.001 ETH that leaves the defenders' hands. A smaller defence passes the crown with the protection untouched, and an outsider can challenge as soon as the throwaway challenge ends.\n\n**Verification run on this worker.**\n- Reviewer proof: fails on the starting tree, passes on the result.\n- Foundry with the verifier's fuzz seed: 85 tests pass, including five new registry tests covering the sybil scenario, both sides of the `MIN_DEFENCE` boundary with the exact 0.008 / 0.001 ETH split, and the registration window.\n- Protected hook, owners and token suites against the new hook creation code: all pass, with the two manifest-dependent token tests skipped as before.\n- Site: ABI regenerated, typecheck and production build pass, 49 interaction assertions pass, design probe 16/16, and the 390px first screen was inspected again.\n- Slither was not run, as it is not installed here.\n\n**Site and docs.** The page now reads `challengeableAt` for the \"Challenge opens\" fact, the eligibility gate and the countdown, instead of deriving them from the reign clock. The how-it-works note states the 0.01 ETH rule. README, VALIDATION.md, REVIEW.md and the site README record the rule, the constant, the check results and the remaining residuals: the 0.001 ETH floor fixed by the brief's slash economics, and the gas-only race to re-occupy the challenge slot.\n\nCommits were left to the harness, which authored the previous rounds' commits. The old export assets are replaced by the rebuilt ones in `dist/`.","treeHash":"df9132bd083d1b57feb84664b7575e766a5bf471","usage":{"cachedInputTokens":2832183,"inputTokens":642,"model":"claude-fable-5-1","outputTokens":46324,"runtime":"claude","turns":81,"wallClockMs":1060211}},{"artifacts":[],"attempt":1,"bundleHash":"833a58e4beacffd85d669cc12276ed8a902dfcc7a6c328d31c01194bca5b47fc","device":"e36579e0223ff908","findings":[],"hash":"34252beeee3b44aefc7960f06ee0dd4c7f259e4c98e750bc45cdec8a4a51553c","nodeId":"4aeaee3c-d5e8-48fd-baf1-e5f909776f71","outcome":"completed","summary":"Done. Only `launch.json` changed, and only its notes field.\n\n**What I verified against the tree**\n\n- Constructor args `$poolManager`, `$factory`, `$owner` match the hook's declaration order in `src/CrownHook.sol:100`.\n- Declared permissions match getHookPermissions exactly: afterInitialize, beforeSwap, afterSwap, beforeSwapReturnDelta, afterSwapReturnDelta. beforeInitialize is correctly absent.\n- Token contract CrownToken, name \"Crown Demo\", symbol CRWN, 18 decimals. Pool fields are the launch reference values. afterInitialize checks no fee tier, so it accepts 12500.\n- The first key is `\"kind\": \"univ4_hook\"`; all addresses are lowercase; notes are 3491 characters.\n- `forge build` passes, and `forge test` with the verifier's fuzz seed passes 80 of 80 tests.\n\n**What I changed**\n\nThe notes described the pre-revision hook. The accepted revision round added behavior the manifest must disclose, so the notes now lead with the two deviations from the brief: the ERC-6909 claim fallback for the beneficiary payout, and the PartialFill and AmountTooLarge refusals on cut-bearing pools. They also record the self-challenge rule, the undefended-flip reign clock, the TickerTaken and ZeroTicker pool-opening refusals, and the treasury credit retry via payTreasury.\n\n**Not asserted**\n\nNo live deployment, live dethrone demo or Sourcify verification is claimed in the manifest. The site still awaits deployment addresses.","treeHash":"2858f4553475c116fc9394febea90d47272208d9","usage":{"cachedInputTokens":416061,"inputTokens":226,"model":"claude-fable-5-1","outputTokens":5262,"runtime":"claude","turns":8,"wallClockMs":199611}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"e382bd4d2b3e471f","findings":[{"citation":"resolved","description":"Math Precision / Boundary (break downcasts, max input). _crown() treats an amount above type(uint128).max as 'nothing to charge' and returns 0 instead of refusing the swap. In the beforeSwap path (currency0 specified: exact-input zeroForOne buys and exact-output oneForZero sells) `amount` is |params.amountSpecified|, an int256 the swapper chooses freely. v4 does not bound amountSpecified to int128; it bounds what the pool *consumes* by sqrtPriceLimitX96 and casts only the consumed amount with toInt128(). So a swapper passes amountSpecified = -(2^128) together with a price limit one tick spacing below spot: the hook early-returns 0, beforeSwap returns ZERO_DELTA, the pool swaps ~3.04 ETH until the limit, the swapper settles ~3.04 ETH and receives tokens, and the king's beneficiary receives nothing; crownPaid stays 0. The identical trade with amountSpecified = -(2^128 - 1) is charged (companion test), so the only difference is the early return. The 'never revert a swap' rule was meant for registry/transfer failures, not for giving the cut away; every swapper can use this to trade on every CrownHook pool fee-free, which defeats the hook's single purpose (fees paid at source to the king). Fix: do not silently zero the cut on an oversized amount. Either revert (`if (amount > type(uint128).max) revert AmountTooLarge();`, which the pool would need anyway since such a fill cannot be settled) or clamp the charged amount: `uint256 cut = (amount > type(uint128).max ? type(uint128).max : amount) * cfg.bps / BPS;` and let the subsequent delta accounting fail the absurd swap. Regression: assert a buy with amountSpecified = -(2^128) and a tick-limited fill either reverts or pays a cut > 0.","line":188,"path":"src/CrownHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {CrownHook} from \"src/CrownHook.sol\";\nimport {CrownToken} from \"src/CrownToken.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\n\n/// @dev Minimal v4 router that lets the test choose amountSpecified and sqrtPriceLimitX96 freely.\ncontract LimitRouter is IUnlockCallback {\n    IPoolManager public immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    function swap(PoolKey memory key, SwapParams memory p) external returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(uint8(0), key, p, ModifyLiquidityParams(0, 0, 0, 0))), (BalanceDelta));\n    }\n\n    function addLiquidity(PoolKey memory key, int24 lo, int24 hi, int256 liq) external returns (BalanceDelta) {\n        return abi.decode(\n            manager.unlock(abi.encode(uint8(1), key, SwapParams(false, 0, 0), ModifyLiquidityParams(lo, hi, liq, 0))),\n            (BalanceDelta)\n        );\n    }\n\n    function unlockCallback(bytes calldata raw) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"not manager\");\n        (uint8 action, PoolKey memory key, SwapParams memory sp, ModifyLiquidityParams memory mp) =\n            abi.decode(raw, (uint8, PoolKey, SwapParams, ModifyLiquidityParams));\n        BalanceDelta delta;\n        if (action == 0) delta = manager.swap(key, sp, \"\");\n        else (delta,) = manager.modifyLiquidity(key, mp, \"\");\n        _settle(key.currency0, delta.amount0());\n        _settle(key.currency1, delta.amount1());\n        return abi.encode(delta);\n    }\n\n    function _settle(Currency c, int128 amount) private {\n        if (amount < 0) {\n            uint256 owed = uint256(uint128(-amount));\n            manager.sync(c);\n            if (c.isAddressZero()) manager.settle{value: owed}();\n            else {\n                IERC20(Currency.unwrap(c)).transfer(address(manager), owed);\n                manager.settle();\n            }\n        } else if (amount > 0) {\n            manager.take(c, address(this), uint256(uint128(amount)));\n        }\n    }\n\n    receive() external payable {}\n}\n\ncontract CrownCutBypassProofTest is Test {\n    uint160 internal constant SQRT_PRICE_1_1 = 79228162514264337593543950336;\n    bytes32 internal constant CRWN = keccak256(\"CRWN\");\n    Currency internal constant ETH = Currency.wrap(address(0));\n\n    address internal factory = makeAddr(\"factory\");\n    address internal payer = makeAddr(\"payer\");\n\n    PoolManager internal manager;\n    CrownHook internal hook;\n    CrownToken internal token;\n    PoolKey internal key;\n    LimitRouter internal lp;\n    LimitRouter internal trader;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        address at = address((uint160(0xC0FFEE) << 100) | HookFlags.CROWN_HOOK);\n        deployCodeTo(\"CrownHook.sol:CrownHook\", abi.encode(manager, factory, payer), at);\n        hook = CrownHook(at);\n        token = new CrownToken();\n        key = PoolKey(ETH, Currency.wrap(address(token)), 12_500, 60, IHooks(at));\n        vm.prank(factory);\n        manager.initialize(key, SQRT_PRICE_1_1);\n\n        lp = new LimitRouter(manager);\n        trader = new LimitRouter(manager);\n        vm.deal(address(lp), 1_000_000 ether);\n        vm.deal(address(trader), 1_000_000 ether);\n        token.transfer(address(lp), 100_000_000 ether);\n        token.transfer(address(trader), 100_000_000 ether);\n        lp.addLiquidity(key, TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 1_000 ether);\n    }\n\n    /// @dev Finding: an exact-input buy whose amountSpecified exceeds uint128.max pays no cut at all, although the\n    /// pool executes a normal-sized trade bounded by the price limit. _crown() early-returns 0 on\n    /// `amount > type(uint128).max`, so beforeSwap returns a zero delta and the beneficiary gets nothing.\n    function test_oversizedAmountSpecified_skipsTheCut() public {\n        uint256 beneficiaryBefore = payer.balance;\n        uint160 limit = TickMath.getSqrtPriceAtTick(-60); // one spacing below the current price\n        SwapParams memory p = SwapParams({\n            zeroForOne: true,\n            amountSpecified: -int256(uint256(type(uint128).max) + 1),\n            sqrtPriceLimitX96: limit\n        });\n\n        uint256 ethBefore = address(trader).balance;\n        try trader.swap(key, p) returns (BalanceDelta d) {\n            uint256 ethIn = ethBefore - address(trader).balance;\n            // the pool actually took a few ETH from the trader\n            assertGt(ethIn, 1 ether, \"a real trade executed\");\n            assertEq(uint256(-int256(d.amount0())), ethIn);\n            // ...so the king must have been paid its 1% cut on it, as on any other buy\n            uint256 paid = hook.crownPaid(CRWN, ETH)\n                + (payer.balance - beneficiaryBefore)\n                + manager.balanceOf(payer, 0);\n            assertGt(paid, 0, \"DEFECT: trade executed and the crown cut was skipped\");\n        } catch {\n            // A fix may instead refuse the oversized amount; that is acceptable.\n        }\n    }\n\n    /// @dev Companion: the same trade with amountSpecified = uint128.max (one wei less) IS charged, proving the\n    /// price limit, not the amount, bounds what the pool consumes, and the only difference is the early return.\n    function test_sameTradeOneWeiSmaller_isCharged() public {\n        uint160 limit = TickMath.getSqrtPriceAtTick(-60);\n        SwapParams memory p = SwapParams({\n            zeroForOne: true,\n            amountSpecified: -int256(uint256(type(uint128).max)),\n            sqrtPriceLimitX96: limit\n        });\n        uint256 ethBefore = address(trader).balance;\n        // the cut on uint128.max input is ~1.7e36 ETH; the manager cannot pay it, so a claim is minted and the\n        // trader must fund it: settlement fails, the swap reverts, and no free trade happens.\n        vm.expectRevert();\n        trader.swap(key, p);\n        assertEq(ethBefore, address(trader).balance);\n    }\n}","reproduction":"State: launch pool ETH/CRWN at 1:1, full-range liquidity 1000e18, CRWN crowned (beneficiary = owner), bps = 100. Call: router unlock -> poolManager.swap(key, SwapParams({zeroForOne: true, amountSpecified: -int256(uint256(type(uint128).max) + 1), sqrtPriceLimitX96: TickMath.getSqrtPriceAtTick(-60)})). Expected: cut = 1% of the ~3.04e18 wei of ETH actually swapped (~3.04e16 wei) paid to the beneficiary, or the oversized request refused. Actual: beforeSwap -> _crown(key, 2^128) hits `amount > type(uint128).max` and returns 0; swap executes with amount0 = -3042383861004481675 (3.04 ETH in), crownPaid[CRWN][ETH] == 0, beneficiary balance and ERC-6909 claim unchanged. Same call with amountSpecified = -(2^128 - 1) reverts (cut charged, cannot be settled), proving the gap is the early return. Run: forge test --match-path test/scratch/CrownCutBypassProof.t.sol (fails on current code).","severity":"high","snippet":"        if (cfg.ticker == bytes32(0) || cfg.bps == 0 || amount > type(uint128).max) return 0;","title":"Exact-input buy or exact-output sell with amountSpecified above uint128.max executes a normal-sized trade and pays no crown cut"},{"citation":"resolved","description":"Numerical Gap seam boundary x invariant (and Boundary: partial-fill edge). The two currency0-specified swap shapes are charged in beforeSwap from params.amountSpecified, before the pool knows how much will execute, while the two currency0-unspecified shapes are charged in afterSwap from the real delta. v4 swaps stop at sqrtPriceLimitX96, so the executed amount can be any fraction of the request. The cut is taken by poolManager.take at once and credited back to the hook through the BeforeSwapDelta, which v4 subtracts from the swapper's delta in full regardless of fill. Concrete numbers (pool 1:1, L = 1000e18, bps = 100): (a) exact-input buy of 100 ETH with limit at tick -1: pool consumes 0.0506 ETH, hook takes 1 ETH; the swapper pays 1.0506 ETH for 0.0506 ETH worth of tokens, a 20x fee. (b) exact-output sell asking 100 ETH out with limit at tick +10: beforeSwap takes 1 ETH, the pool delivers only 0.50015 ETH before the limit, and v4 computes swapDelta.amount0 = +0.50015 - 1 = -0.49985 ETH: the swapper pays 0.50643 CRWN AND pays 0.49985 ETH and receives nothing; the king receives 1 ETH funded by the swapper. The invariant the brief states ('a cut of every swap ... from the swap amount') holds only for full fills; at the partial-fill boundary the swap sign itself flips for exact-output. The afterSwap path is not affected because it reads delta.amount0(). Fix options (design decision needed since afterSwapReturnDelta cannot move the specified currency): charge currency0-specified shapes in afterSwap on the *unspecified* side (currency1) from the real delta; or keep beforeSwap but refuse fills that do not consume the request (compare delta.amount0() to the requested amount in afterSwap and revert when short); or at minimum cap the cut at MAX_BPS of the executed amount in afterSwap by returning a negative unspecified delta refund. Regression: the two tests in the proof (tick-limited exact-input buy must not be charged more than 5% of executed input; tick-limited exact-output sell must not decrease the swapper's ETH).","line":162,"path":"src/CrownHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {CrownHook} from \"src/CrownHook.sol\";\nimport {CrownToken} from \"src/CrownToken.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\n\n/// @dev Minimal v4 router that lets the test choose amountSpecified and sqrtPriceLimitX96 freely.\ncontract LimitRouter is IUnlockCallback {\n    IPoolManager public immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    function swap(PoolKey memory key, SwapParams memory p) external returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(uint8(0), key, p, ModifyLiquidityParams(0, 0, 0, 0))), (BalanceDelta));\n    }\n\n    function addLiquidity(PoolKey memory key, int24 lo, int24 hi, int256 liq) external returns (BalanceDelta) {\n        return abi.decode(\n            manager.unlock(abi.encode(uint8(1), key, SwapParams(false, 0, 0), ModifyLiquidityParams(lo, hi, liq, 0))),\n            (BalanceDelta)\n        );\n    }\n\n    function unlockCallback(bytes calldata raw) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"not manager\");\n        (uint8 action, PoolKey memory key, SwapParams memory sp, ModifyLiquidityParams memory mp) =\n            abi.decode(raw, (uint8, PoolKey, SwapParams, ModifyLiquidityParams));\n        BalanceDelta delta;\n        if (action == 0) delta = manager.swap(key, sp, \"\");\n        else (delta,) = manager.modifyLiquidity(key, mp, \"\");\n        _settle(key.currency0, delta.amount0());\n        _settle(key.currency1, delta.amount1());\n        return abi.encode(delta);\n    }\n\n    function _settle(Currency c, int128 amount) private {\n        if (amount < 0) {\n            uint256 owed = uint256(uint128(-amount));\n            manager.sync(c);\n            if (c.isAddressZero()) manager.settle{value: owed}();\n            else {\n                IERC20(Currency.unwrap(c)).transfer(address(manager), owed);\n                manager.settle();\n            }\n        } else if (amount > 0) {\n            manager.take(c, address(this), uint256(uint128(amount)));\n        }\n    }\n\n    receive() external payable {}\n}\n\ncontract CrownPartialFillProofTest is Test {\n    uint160 internal constant SQRT_PRICE_1_1 = 79228162514264337593543950336;\n    bytes32 internal constant CRWN = keccak256(\"CRWN\");\n    Currency internal constant ETH = Currency.wrap(address(0));\n\n    address internal factory = makeAddr(\"factory\");\n    address internal payer = makeAddr(\"payer\");\n\n    PoolManager internal manager;\n    CrownHook internal hook;\n    CrownToken internal token;\n    PoolKey internal key;\n    LimitRouter internal lp;\n    LimitRouter internal trader;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        address at = address((uint160(0xC0FFEE) << 100) | HookFlags.CROWN_HOOK);\n        deployCodeTo(\"CrownHook.sol:CrownHook\", abi.encode(manager, factory, payer), at);\n        hook = CrownHook(at);\n        token = new CrownToken();\n        key = PoolKey(ETH, Currency.wrap(address(token)), 12_500, 60, IHooks(at));\n        vm.prank(factory);\n        manager.initialize(key, SQRT_PRICE_1_1);\n\n        lp = new LimitRouter(manager);\n        trader = new LimitRouter(manager);\n        vm.deal(address(lp), 1_000_000 ether);\n        vm.deal(address(trader), 1_000_000 ether);\n        token.transfer(address(lp), 100_000_000 ether);\n        token.transfer(address(trader), 100_000_000 ether);\n        lp.addLiquidity(key, TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 1_000 ether);\n    }\n\n    /// @dev Exact-output sell: tokens in, exactly 100 ETH requested out, price limit ten ticks up. The pool can\n    /// deliver only ~0.5 ETH before the limit, but beforeSwap already took a 1 ETH cut on the 100 ETH *requested*\n    /// (CrownHook.sol:162-163). The swapper ends the swap paying ~0.506 CRWN AND ~0.5 ETH, receiving no ETH.\n    /// On fixed code the swap either reverts (partial fill refused) or the swapper's ETH balance does not go down.\n    function test_exactOutputSell_partialFill_mustNotChargeSwapperEth() public {\n        uint160 limit = TickMath.getSqrtPriceAtTick(10);\n        SwapParams memory p = SwapParams({zeroForOne: false, amountSpecified: 100 ether, sqrtPriceLimitX96: limit});\n        uint256 ethBefore = address(trader).balance;\n        try trader.swap(key, p) returns (BalanceDelta d) {\n            emit log_named_int(\"amount0 (ETH) delta seen by swapper\", d.amount0());\n            emit log_named_uint(\"cut paid to king\", hook.crownPaid(CRWN, ETH));\n            assertGe(\n                address(trader).balance,\n                ethBefore,\n                \"DEFECT: swapper asked for ETH out and ended up paying ETH (cut charged on the unfilled request)\"\n            );\n        } catch {\n            // refusing the partial fill is an acceptable fix\n        }\n    }\n\n    /// @dev Exact-input buy of 100 ETH limited to one tick: ~0.05 ETH executes but the 1 ETH cut on the 100 ETH\n    /// specified is still charged, 20x the executed amount. Fixed code charges at most the cut on what executed\n    /// (plus rounding) or refuses the partial fill.\n    function test_exactInputBuy_partialFill_cutBoundedByExecutedAmount() public {\n        uint160 limit = TickMath.getSqrtPriceAtTick(-1);\n        SwapParams memory p = SwapParams({zeroForOne: true, amountSpecified: -100 ether, sqrtPriceLimitX96: limit});\n        uint256 ethBefore = address(trader).balance;\n        try trader.swap(key, p) {\n            uint256 ethIn = ethBefore - address(trader).balance;\n            uint256 cut = hook.crownPaid(CRWN, ETH);\n            uint256 executed = ethIn - cut;\n            emit log_named_uint(\"ETH actually swapped\", executed);\n            emit log_named_uint(\"cut\", cut);\n            assertLe(cut, executed * 500 / 10_000 + 1, \"DEFECT: cut exceeds the maximum 5% of the executed input\");\n        } catch {}\n    }\n}","reproduction":"State: launch pool ETH/CRWN at 1:1, full-range liquidity 1000e18, CRWN crowned, bps 100. Call A: poolManager.swap(key, SwapParams(true, -100 ether, TickMath.getSqrtPriceAtTick(-1))). Expected: cut proportional to the 50631645632907438 wei consumed (~5.06e14 wei at 1%). Actual: crownPaid = 1e18, swapper's ETH balance down 1050631645632907438. Call B: poolManager.swap(key, SwapParams(false, 100 ether, TickMath.getSqrtPriceAtTick(10))). Expected: swapper receives ETH (at most 100 ETH, less a cut on what was delivered) and pays CRWN. Actual: BalanceDelta amount0 = -500149965006998741 (swapper PAYS 0.5 ETH), amount1 = -506430389873924062 (and pays 0.506 CRWN), crownPaid = 1e18. Run: forge test --match-path test/scratch/CrownPartialFillProof.t.sol (both tests fail on current code).","severity":"medium","snippet":"        uint256 amount = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n        uint256 paid = _crown(key, amount);","title":"beforeSwap charges the cut on the requested amount, not the executed one: partially filled swaps overpay up to the whole cut and an exact-output sell can make the swapper pay ETH instead of receiving "},{"citation":"resolved","description":"Boundary guide, external call corner case 'non-standard token / blacklist / pausable'. The registry read and both payment paths are wrapped in try/catch with gas caps so that 'the hook must never revert or block a swap', but the balance probe before them is a plain external call. For the launch pool currency0 is native ETH (address.balance, cannot fail), so the launch is unaffected. For any further pool the owner configures where currency0 is an ERC-20 (the repo's own test_erc20PairPool_paysTheCutInCurrency0 shows this is a supported shape), a token whose balanceOf reverts (paused, upgraded, holder blocklisted, non-standard return) makes beforeSwap/afterSwap revert, which reverts the whole swap for as long as that state lasts. Scratch test: a pool (ALPH, CRWN) with ALPH as currency0 swaps normally; after ALPH.setPaused(true) the same exact-input swap reverts inside the hook. Fix: probe the balance with a try/catch (or staticcall with a gas cap) and fall through to the mint path when it fails, e.g. `try IERC20(Currency.unwrap(currency)).balanceOf{gas: 30_000}(address(poolManager)) returns (uint256 bal) { enough = bal >= cut; } catch {}` for non-native currencies.","line":204,"path":"src/CrownHook.sol","reproduction":"State: owner-configured pool PoolKey(ALPH, CRWN, 12500, 60, hook) where ALPH < CRWN by address and ALPH.balanceOf reverts while paused; full-range liquidity. Call: poolManager.swap(key, SwapParams(true, -1 ether, MIN_SQRT_PRICE + 1)) after ALPH.setPaused(true). Expected: swap proceeds without the cut (CrownSkipped). Actual: beforeSwap -> _crown -> currency.balanceOf reverts 'paused' -> swap reverts. Scratch: test/scratch/CrownBoundary.t.sol::test_currency0BalanceOfReverting_blocksEverySwap passes (asserts the revert).","severity":"low","snippet":"        if (currency.balanceOf(address(poolManager)) >= cut) {","title":"_crown calls currency0.balanceOf unguarded, so a reverting balanceOf on a non-ETH currency0 reverts every swap in that pool instead of skipping the cut"},{"citation":"resolved","description":"Boundary guide, return-value handling. The brief defines the ticker as keccak256 of the uppercase symbol string, and registerKing/openChallenge require Ticker.ofToken(token) == ticker. ofToken requires an ABI string return (data.length >= 64, offset 0x20, 1..64 bytes). An MKR-style token whose symbol() returns bytes32 produces 32 bytes of return data and is read as 'no ticker', and a token with a symbol longer than 64 bytes is rejected by the len > 64 branch on line 35. Those tokens can neither be crowned nor used to challenge, and a ticker whose only on-chain claimant is such a token stays uncrownable (afterInitialize also records ticker 0 for such a pool, so its swaps never pay a cut). This is a boundary limitation rather than a loss, and arguably an acceptable scope decision for CRWN, but it is undocumented. Fix: either document that only ABI-string symbol() tokens are eligible, or decode a 32-byte return as a right-padded bytes32 symbol (strip trailing zeros) before hashing.","line":25,"path":"src/libraries/Ticker.sol","reproduction":"State: fresh CrownRegistry; contract T with `function symbol() external pure returns (bytes32) { return \"CRWN\"; }`. Call: Ticker.ofToken(address(T)) -> expected keccak256(\"CRWN\"), actual bytes32(0). registerKing(keccak256(\"CRWN\"), address(T), beneficiary) -> expected Crowned, actual revert TickerMismatch. Scratch: test/scratch/CrownBoundary.t.sol::test_bytes32Symbol_cannotBeKingOrChallenger passes (asserts the revert).","severity":"low","snippet":"        if (!ok || data.length < 64) return bytes32(0);","title":"Ticker.ofToken returns zero for tokens whose symbol() is bytes32-encoded or longer than 64 bytes, so such tokens can never be registered as king or challenger"}],"hash":"51b9a68c9b8328ed318c1ebbf9b63b41ac8a4ce03d37599367f98b139d97907d","nodeId":"63e2a66b-dccb-4ee0-8b1b-c4ec7ab52bae","outcome":"completed","summary":"Review complete. The findings file is written and every snippet matches the tree. No tracked file was changed; only `test/scratch/` (ignored) and `.imd-findings.json` were created.\n\n**What I found in the assigned math area (4 findings, 2 with failing proofs):**\n\n- **High, `src/CrownHook.sol:188`.** A buy with `amountSpecified` above uint128.max pays no cut. The early return treats an oversized request as \"nothing to charge\" while the pool still executes a normal, price-limit-bounded trade. A 3.04 ETH buy executed with crownPaid at zero; the same trade one wei smaller is charged. Proof: `test/scratch/CrownCutBypassProof.t.sol` fails on current code.\n- **Medium, `src/CrownHook.sol:162`.** The beforeSwap path charges on the requested amount, not the executed one. A 100 ETH exact-input buy limited to one tick executes 0.05 ETH but pays a 1 ETH cut. Worse, an exact-output sell that partially fills makes the swapper pay 0.5 ETH and 0.5 CRWN while receiving nothing. Proof: `test/scratch/CrownPartialFillProof.t.sol`, both tests fail now.\n- **Low, `src/CrownHook.sol:204`.** The balanceOf probe on currency0 is unguarded. On a non-ETH pool a paused or reverting token blocks every swap, contradicting the never-revert rule. Verified in a scratch test.\n- **Low, `src/libraries/Ticker.sol:25`.** Tokens with a bytes32-style `symbol()` or a symbol over 64 bytes read as no ticker and can never be king or challenger. Verified in a scratch test.\n\n**What holds.** The registry's slash split, quorum and tie boundaries, pro-rata remainder payout, treasury push and reentrancy guards all check out by hand and under the repo's fuzz and invariant suites. The afterSwap path is correct because it reads the real delta. The slither reentrancy lead on the treasury push is not exploitable.\n\n**Coverage.** All 20 entry points have a row, plus two invariant rows. Two entry points carry findings, the rest hold, none unreached.","treeHash":null,"usage":{"cachedInputTokens":1298915,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":51242,"runtime":"claude","turns":30,"wallClockMs":888782}},{"artifacts":[],"attempt":1,"bundleHash":"51e03f1d41ffdc95035926d8a365ef9ecebac88595e0054511f4360639871930","device":"d6415252dcd668d9","findings":[],"hash":"5fc989a14e4c5b19b9a0330a94f1b7a4fbd706e20eb2632e63d6d9517592e6c1","nodeId":"4aeaee3c-d5e8-48fd-baf1-e5f909776f71","outcome":"completed","summary":"Created [launch.json](/home/imd/.identitymd-3/work/7400cf72-96af-4acb-bf4b-3ca2208b3e58/4aeaee3c-d5e8-48fd-baf1-e5f909776f71/launch.json), matching constructor arguments, permissions, and required pool settings.\n\n`forge build` passed; all 67 tests passed with the supplied fuzz seed. Only the manifest changed outside permitted scratch files.\n\nNotes disclose the existing claim fallback’s conflict with immediate-payment requirements and that live deployment remains pending.","treeHash":"b3d2931a72e08fd15350468ef6b47a52af217893","usage":{"cachedInputTokens":492160,"inputTokens":70051,"model":"gpt-6-astra","outputTokens":5150,"runtime":"codex","turns":4,"wallClockMs":179778}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"61b4050710026370","findings":[{"citation":"resolved","description":"Trust gap (access x economics). openChallenge is open to everyone, including the current king's own beneficiary, and only one challenge may be live per ticker (src/CrownRegistry.sol:182 `if (t.activeChallengeId != 0) revert ChallengeLive();`). The incumbent deploys a second same-symbol token, opens the challenge the instant `reignStartedAt + MIN_REIGN` passes, and stakes 0.04 ETH more on its own challenger side. With zero king-side stake the challenge 'wins': losers = 0 so slash = 0, the whole 0.05 ETH is refunded via claim, the beneficiary is unchanged, and finalize writes `k.reignStartedAt = block.timestamp`, granting a fresh 7-day ReignTooShort window. Every real challenger gets ChallengeLive during the 96 hours the self-challenge is live and ReignTooShort for 7 days after it. Repeating this each cycle keeps the fee stream with the incumbent indefinitely; the only counter is winning the same-block ordering race to call openChallenge first. If an outsider instead stakes on the king side to defeat the self-challenge, the result is still the incumbent as king plus a 3-day cooldown, after which the race repeats. The 'anyone can challenge the king after a 7-day reign' guarantee therefore does not hold. Suggested minimal fix preserving the design: do not restart the reign (and do not count a dethrone) when the winning challenge had no opposing stake or when the beneficiary is unchanged, or let a king-side staker inherit the slot by allowing a second challenge once the live one is unopposed; any fix must keep the slash split and pull-payment rules intact.","line":247,"path":"src/CrownRegistry.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {CrownRegistry} from \"src/CrownRegistry.sol\";\nimport {CrownToken} from \"src/CrownToken.sol\";\nimport {CrownChallengerToken} from \"src/CrownChallengerToken.sol\";\n\n/// @notice The incumbent beneficiary occupies the only challenge slot the moment it opens, \"wins\" against zero\n/// opposition, pays no slash, and leaves with a fresh 7-day MIN_REIGN that locks every real challenger out again.\ncontract SelfChallengeTest is Test {\n    bytes32 constant CRWN = keccak256(\"CRWN\");\n\n    CrownRegistry registry;\n    CrownToken king;\n    address incumbent = makeAddr(\"incumbent\");\n    address rivalOpener = makeAddr(\"rivalOpener\");\n\n    function setUp() public {\n        registry = new CrownRegistry();\n        king = new CrownToken();\n        registry.registerKing(CRWN, address(king), incumbent);\n        vm.deal(incumbent, 1 ether);\n        vm.deal(rivalOpener, 1 ether);\n    }\n\n    function test_incumbentSelfChallengeCannotResetReignForFreeAndLockOutRivals() public {\n        // A real rival prepares a same-symbol token and waits for the minimum reign to pass.\n        CrownChallengerToken rivalToken = new CrownChallengerToken(rivalOpener);\n        // The incumbent's shadow token: another same-symbol ERC-20 it controls.\n        CrownChallengerToken shadow = new CrownChallengerToken(incumbent);\n\n        (,, uint64 reign0) = registry.king(CRWN);\n        vm.warp(uint256(reign0) + registry.MIN_REIGN());\n\n        // The incumbent takes the slot first (same block as the rival's attempt).\n        vm.prank(incumbent);\n        uint256 id = registry.openChallenge{value: 0.01 ether}(CRWN, address(shadow));\n        vm.prank(incumbent);\n        registry.stake{value: 0.04 ether}(id, CrownRegistry.Side.Challenger); // reaches QUORUM alone\n\n        // The rival is shut out: one live challenge per ticker.\n        vm.prank(rivalOpener);\n        vm.expectRevert(CrownRegistry.ChallengeLive.selector);\n        registry.openChallenge{value: 0.01 ether}(CRWN, address(rivalToken));\n\n        vm.warp(block.timestamp + registry.STAKING_WINDOW() + registry.TIMELOCK());\n        registry.finalize(id);\n\n        // Nothing changed for anyone except the clock: same beneficiary, zero slash, full refund.\n        (address kingToken, address beneficiary, uint64 reign1) = registry.king(CRWN);\n        assertEq(kingToken, address(shadow));\n        assertEq(beneficiary, incumbent, \"the fee stream never moved\");\n        uint256 before = incumbent.balance;\n        vm.prank(incumbent);\n        registry.claim(id);\n        assertEq(incumbent.balance - before, 0.05 ether, \"the self-challenge cost nothing\");\n\n        // Expected: a ticker whose beneficiary did not change, against zero opposition, is still challengeable.\n        // Actual: reignStartedAt was reset, so the rival is locked out for another MIN_REIGN.\n        assertEq(reign1, reign0, \"uncontested self-flip must not restart the minimum reign\");\n        vm.prank(rivalOpener);\n        registry.openChallenge{value: 0.01 ether}(CRWN, address(rivalToken));\n    }\n}","reproduction":"State: registerKing(keccak256('CRWN'), CrownToken, incumbent) at t0. Inputs: at t0 + 7 days the incumbent calls openChallenge{value: 0.01 ether}(CRWN, shadowCrwnToken) then stake{value: 0.04 ether}(id, Challenger); a rival's openChallenge in the same block reverts ChallengeLive. At t0 + 7d + 96h anyone calls finalize(id). Expected: an uncontested flip to the same wallet leaves the ticker challengeable. Actual: king(CRWN).reignStartedAt == t0 + 7d + 96h, incumbent.claim(id) returns the full 0.05 ETH (slash 0), and the rival's openChallenge reverts ReignTooShort until t0 + 14d + 96h. See test/scratch/SelfChallenge.t.sol.","severity":"medium","snippet":"            k.token = c.challengerToken;\n            k.beneficiary = c.challengerBeneficiary;\n            k.reignStartedAt = uint64(block.timestamp);","title":"Incumbent self-challenge resets MIN_REIGN at zero cost and monopolises the single challenge slot"},{"citation":"resolved","description":"Branch asymmetry between the two payment paths. When currency0 is the unspecified side the cut is computed in afterSwap from the realized `delta.amount0()` (src/CrownHook.sol:175-177). When currency0 is the specified side it is computed in beforeSwap from `params.amountSpecified`, which is only an upper bound: a swap stops at `sqrtPriceLimitX96` (or when range liquidity runs out) and consumes less. The hook has already paid `amountSpecified * bps / 10000` to the beneficiary via take/mint and returns that as its specified delta, so the PoolManager charges the trader the full cut on volume that never traded. Exact-input buy of 100 ETH with a limit one tick below spot fills 3.04 ETH yet pays a 1 ETH cut (33% of the trade instead of 1%). Exact-output sell asking 100 ETH out with a limit ten ticks above spot: the pool delivers ~0.5 ETH, the hook takes the 1 ETH cut from the manager, and the swapper's currency0 delta becomes -0.5 ETH: the seller hands over tokens and pays ~0.5 ETH on top. The beneficiary is paid the overcharge in both cases, so it is a transfer from traders to whoever holds the crown. The README (lines 139-143) describes the cut as a percentage of the swap, which does not hold on these inputs. A fix that keeps the ETH-side design: in beforeSwap return the reservation only, and in afterSwap (which is already called for every swap) pay the beneficiary `bps` of the realized currency0 amount from `delta` and return the surplus of the reserved cut to `sender` with poolManager.take (or mint it as a claim), so the trader is charged exactly bps of what filled.","line":162,"path":"src/CrownHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {CrownHook} from \"src/CrownHook.sol\";\nimport {CrownToken} from \"src/CrownToken.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\n\n/// @dev Minimal router: swaps or adds liquidity from its own balance and settles afterwards.\ncontract Router is IUnlockCallback {\n    IPoolManager immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    function swap(PoolKey memory key, SwapParams memory p) external returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(uint8(0), abi.encode(key, p))), (BalanceDelta));\n    }\n\n    function addLiquidity(PoolKey memory key, int24 lo, int24 hi, int256 liq) external returns (BalanceDelta) {\n        return abi.decode(\n            manager.unlock(abi.encode(uint8(1), abi.encode(key, ModifyLiquidityParams(lo, hi, liq, bytes32(0))))),\n            (BalanceDelta)\n        );\n    }\n\n    function unlockCallback(bytes calldata raw) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (uint8 action, bytes memory data) = abi.decode(raw, (uint8, bytes));\n        BalanceDelta delta;\n        PoolKey memory key;\n        if (action == 0) {\n            SwapParams memory p;\n            (key, p) = abi.decode(data, (PoolKey, SwapParams));\n            delta = manager.swap(key, p, \"\");\n        } else {\n            ModifyLiquidityParams memory p;\n            (key, p) = abi.decode(data, (PoolKey, ModifyLiquidityParams));\n            (delta,) = manager.modifyLiquidity(key, p, \"\");\n        }\n        _settle(key.currency0, delta.amount0());\n        _settle(key.currency1, delta.amount1());\n        return abi.encode(delta);\n    }\n\n    function _settle(Currency c, int128 a) private {\n        if (a < 0) {\n            uint256 owed = uint256(uint128(-a));\n            manager.sync(c);\n            if (c.isAddressZero()) manager.settle{value: owed}();\n            else {\n                IERC20(Currency.unwrap(c)).transfer(address(manager), owed);\n                manager.settle();\n            }\n        } else if (a > 0) {\n            manager.take(c, address(this), uint256(uint128(a)));\n        }\n    }\n\n    receive() external payable {}\n}\n\n/// @notice The beforeSwap path charges the cut on the amount the trader *asked* for, the afterSwap path on what the\n/// pool *actually* moved. A partially filled exact-input buy pays 1% of the unfilled amount too.\ncontract PartialFillCutTest is Test {\n    uint160 constant SQRT_PRICE_1_1 = 79228162514264337593543950336;\n    bytes32 constant CRWN = keccak256(\"CRWN\");\n\n    address factory = makeAddr(\"factory\");\n    address payer = makeAddr(\"payer\");\n    PoolManager manager;\n    CrownHook hook;\n    CrownToken token;\n    PoolKey key;\n    Router lp;\n    Router trader;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        address at = address((uint160(0xC0FFEE) << 100) | HookFlags.CROWN_HOOK);\n        deployCodeTo(\"CrownHook.sol:CrownHook\", abi.encode(manager, factory, payer), at);\n        hook = CrownHook(at);\n        token = new CrownToken();\n        key = PoolKey(Currency.wrap(address(0)), Currency.wrap(address(token)), 12_500, 60, IHooks(at));\n        vm.prank(factory);\n        manager.initialize(key, SQRT_PRICE_1_1);\n        lp = new Router(manager);\n        trader = new Router(manager);\n        vm.deal(address(lp), 1_000_000 ether);\n        vm.deal(address(trader), 1_000_000 ether);\n        token.transfer(address(lp), 400_000_000 ether);\n        token.transfer(address(trader), 400_000_000 ether);\n        lp.addLiquidity(key, TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 1_000 ether);\n    }\n\n    function test_exactInputBuy_partialFill_cutMatchesTheFilledAmount() public {\n        // Ask to spend 100 ETH but stop one tick below the current price: only ~3 ETH can fill.\n        uint160 limit = TickMath.getSqrtPriceAtTick(-60);\n        uint256 before = payer.balance;\n        BalanceDelta d = trader.swap(key, SwapParams(true, -100 ether, limit));\n        uint256 cut = payer.balance - before;\n        uint256 traderPaid = uint256(-int256(d.amount0()));\n        uint256 filled = traderPaid - cut; // what the pool actually received\n        assertLt(filled, 10 ether, \"the fill was partial\");\n        // Expected: the cut is 1% of what was swapped. Actual: 1 ETH, 1% of the 100 ETH that was never traded.\n        assertEq(cut, filled / 100, \"cut charged on the unfilled amount\");\n    }\n\n    function test_exactOutputSell_partialFill_doesNotMakeTheSellerPayEth() public {\n        // Ask for exactly 100 ETH out, stopping ten ticks above the current price: only ~0.5 ETH can be delivered.\n        uint160 limit = TickMath.getSqrtPriceAtTick(10);\n        uint256 before = payer.balance;\n        BalanceDelta d = trader.swap(key, SwapParams(false, 100 ether, limit));\n        uint256 cut = payer.balance - before;\n        int256 received = int256(d.amount0());\n        assertGt(received, 0, \"seller of tokens must receive ETH, not pay it\");\n        assertEq(cut, (uint256(received) + cut) / 100, \"cut charged on the undelivered amount\");\n    }\n}","reproduction":"Pool: ETH/CRWN at 1:1 with 1000e18 full-range liquidity, default 100 bps. Input A: swap(zeroForOne=true, amountSpecified=-100 ether, sqrtPriceLimitX96=getSqrtPriceAtTick(-60)). Expected: beneficiary receives 1% of the ~3.04 ETH that filled (0.0304 ETH). Actual: beneficiary receives 1.000 ETH and the trader's amount0 delta is -(3.04 + 1.0) ETH. Input B: swap(zeroForOne=false, amountSpecified=+100 ether, sqrtPriceLimitX96=getSqrtPriceAtTick(10)). Expected: seller receives ~0.5 ETH minus 1%. Actual: trader's amount0 delta is -0.50015 ETH (the seller pays ETH), beneficiary receives 1 ETH. See test/scratch/PartialFillCut.t.sol.","severity":"medium","snippet":"        uint256 amount = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n        uint256 paid = _crown(key, amount);\n        return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(paid)), 0), 0);","title":"beforeSwap path charges the cut on the requested amount, not the filled amount: partial fills overpay the king and an exact-output sell can cost the seller ETH"},{"citation":"resolved","description":"Access x asymmetry. registerKing is permissionless and first-come, and afterInitialize crowns the pool's own token for the owner only if nobody holds the ticker yet. The IMD factory deploys the hook and opens the launch pool in one transaction, so the launch itself is safe (README lines 129-132). script/DeployCrown.s.sol (line 44, the CREATE2 call) and every further pool the owner opens are not: the registry exists from the hook's constructor, and the pool is initialized in a later transaction. In that window anyone can call registerKing(ticker, anySameSymbolContract, attackerWallet). afterInitialize then skips the genesis crown, every swap on the owner's pool pays the attacker, and the owner cannot even open a challenge for 7 days (ReignTooShort), after which it must out-stake the squatter and wait 96 more hours. There is no owner-side fix after the fact; the design accepts this for arbitrary tickers but the README's 'no window for front-running' claim applies only to the factory path. Suggested fix: have the script initialize the pool (or call registerKing) in the same broadcast as the hook deployment, or let configurePool pre-register the ticker's king atomically with the configuration.","line":145,"path":"src/CrownHook.sol","reproduction":"tx1: deployCodeTo CrownHook(manager, factory, owner) at a flagged address (what script/DeployCrown.s.sol does). tx2: attacker deploys CrownChallengerToken (symbol CRWN) and calls registry.registerKing(keccak256('CRWN'), junk, attacker). tx3: owner calls manager.initialize(ETH/CrownToken, 12500, 60, hook). Expected: king(CRWN) == (CrownToken, owner). Actual: king(CRWN) == (junk, attacker); owner's openChallenge reverts ReignTooShort for 7 days; every buy on the pool pays the attacker 1%. See test/scratch/GenesisFrontrun.t.sol.","severity":"low","snippet":"        if (cfg.ticker != bytes32(0) && registry.beneficiaryOf(cfg.ticker) == address(0)) {\n            try registry.registerKing(cfg.ticker, token, owner) {} catch {}\n        }","title":"Non-atomic deployment (DeployCrown script) leaves the genesis crown of every pool open to ticker squatting"}],"hash":"603189a668954176ffc281714723410db4d53f64c9ad53b76889400b0a394c3d","nodeId":"a064e88d-917a-469e-9698-a7e53d144611","outcome":"completed","summary":"The review is complete. The findings file is written at `.imd-findings.json`, only that file and `test/scratch/` were touched (git shows the tree otherwise clean because both are gitignored), and the repository's own 67 tests still pass.\n\n**Findings (3), all reproduced with Foundry tests under `test/scratch/`:**\n\n1. **Medium, trust gap: incumbent self-challenge resets the reign clock for free.** `openChallenge` is open to the king's own beneficiary and only one challenge may be live per ticker. The incumbent opens a challenge with a second same-symbol token the instant the 7-day reign ends, stakes 0.05 ETH on its own side, wins against zero opposition, is slashed nothing, and `finalize` writes a fresh `reignStartedAt`. Real challengers get `ChallengeLive` for 96 hours, then `ReignTooShort` for 7 more days. The proof test fails on the current code at the reign-reset assertion.\n\n2. **Medium, branch asymmetry in the hook's cut.** The afterSwap path charges on the realized `amount0`; the beforeSwap path charges on `amountSpecified`, an upper bound. A 100 ETH exact-input buy stopped one tick below spot fills 3.04 ETH yet pays a 1 ETH cut. An exact-output sell for 100 ETH stopped ten ticks above spot delivers 0.5 ETH, the hook takes 1 ETH, and the seller ends up paying 0.5 ETH on top of its tokens. Proof test fails on both inputs.\n\n3. **Low, access × asymmetry: ticker squatting on non-atomic deployment.** The IMD factory path is atomic, but `script/DeployCrown.s.sol` and every further owner-opened pool leave a window between the registry existing and the pool opening. Anyone registers the ticker with a same-symbol contract and their wallet, `afterInitialize` skips the genesis crown, and the owner's pool pays the squatter for at least 7 days.\n\n**Coverage:** all 20 listed entry points have a row; 23 of 24 rows are answered. The hook's callbacks, self-call guard, claim redemption, registry reentrancy guards, treasury isolation, and the slash split all hold under the Access Control, Trust Gap and Asymmetry checks. The owner's power to set 0–500 bps on future pools is recorded as a documented trust assumption, not a bypass. The site's transaction wiring is the one area marked unreached, beyond a spot check that it exposes `payTreasury` rather than the self-only helper.","treeHash":null,"usage":{"cachedInputTokens":1884158,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":49917,"runtime":"claude","turns":49,"wallClockMs":705047}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ea89e16822824c6f","findings":[{"citation":"resolved","description":"beforeSwap passes |params.amountSpecified| (an int256 the swapper chooses) to _crown(), and _crown() treats an amount above type(uint128).max as nothing to charge and returns 0 instead of refusing. v4 does not bound amountSpecified to int128; it bounds what the pool consumes by sqrtPriceLimitX96 and only casts the consumed amount. So a swapper sets amountSpecified = -(2^128) with a price limit one tick spacing below spot: beforeSwap returns ZERO_DELTA, the pool swaps ~3.04 ETH up to the limit, the swapper settles that and receives tokens, and the king's beneficiary receives nothing. The same trade with amountSpecified = -(2^128 - 1) is charged (the cut on it cannot be settled, so it reverts), proving the early return is the only difference. Both currency0-specified shapes (exact-input zeroForOne buys and exact-output oneForZero sells) are affected. This is a complete bypass of the hook's single purpose, available to every trader on every CrownHook pool, so the fee stream the king and the launch rely on can be avoided at will. Reported by audit_math; reproduced with its proof. Fix: revert on amount > type(uint128).max (such a request can never be fully settled anyway) or clamp the amount charged to type(uint128).max; do not fall through to a zero cut.","line":188,"path":"src/CrownHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {CrownHook} from \"src/CrownHook.sol\";\nimport {CrownToken} from \"src/CrownToken.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\n\n/// @dev Minimal v4 router that lets the test choose amountSpecified and sqrtPriceLimitX96 freely.\ncontract LimitRouter is IUnlockCallback {\n    IPoolManager public immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    function swap(PoolKey memory key, SwapParams memory p) external returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(uint8(0), key, p, ModifyLiquidityParams(0, 0, 0, 0))), (BalanceDelta));\n    }\n\n    function addLiquidity(PoolKey memory key, int24 lo, int24 hi, int256 liq) external returns (BalanceDelta) {\n        return abi.decode(\n            manager.unlock(abi.encode(uint8(1), key, SwapParams(false, 0, 0), ModifyLiquidityParams(lo, hi, liq, 0))),\n            (BalanceDelta)\n        );\n    }\n\n    function unlockCallback(bytes calldata raw) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"not manager\");\n        (uint8 action, PoolKey memory key, SwapParams memory sp, ModifyLiquidityParams memory mp) =\n            abi.decode(raw, (uint8, PoolKey, SwapParams, ModifyLiquidityParams));\n        BalanceDelta delta;\n        if (action == 0) delta = manager.swap(key, sp, \"\");\n        else (delta,) = manager.modifyLiquidity(key, mp, \"\");\n        _settle(key.currency0, delta.amount0());\n        _settle(key.currency1, delta.amount1());\n        return abi.encode(delta);\n    }\n\n    function _settle(Currency c, int128 amount) private {\n        if (amount < 0) {\n            uint256 owed = uint256(uint128(-amount));\n            manager.sync(c);\n            if (c.isAddressZero()) manager.settle{value: owed}();\n            else {\n                IERC20(Currency.unwrap(c)).transfer(address(manager), owed);\n                manager.settle();\n            }\n        } else if (amount > 0) {\n            manager.take(c, address(this), uint256(uint128(amount)));\n        }\n    }\n\n    receive() external payable {}\n}\n\ncontract CrownCutBypassProofTest is Test {\n    uint160 internal constant SQRT_PRICE_1_1 = 79228162514264337593543950336;\n    bytes32 internal constant CRWN = keccak256(\"CRWN\");\n    Currency internal constant ETH = Currency.wrap(address(0));\n\n    address internal factory = makeAddr(\"factory\");\n    address internal payer = makeAddr(\"payer\");\n\n    PoolManager internal manager;\n    CrownHook internal hook;\n    CrownToken internal token;\n    PoolKey internal key;\n    LimitRouter internal lp;\n    LimitRouter internal trader;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        address at = address((uint160(0xC0FFEE) << 100) | HookFlags.CROWN_HOOK);\n        deployCodeTo(\"CrownHook.sol:CrownHook\", abi.encode(manager, factory, payer), at);\n        hook = CrownHook(at);\n        token = new CrownToken();\n        key = PoolKey(ETH, Currency.wrap(address(token)), 12_500, 60, IHooks(at));\n        vm.prank(factory);\n        manager.initialize(key, SQRT_PRICE_1_1);\n\n        lp = new LimitRouter(manager);\n        trader = new LimitRouter(manager);\n        vm.deal(address(lp), 1_000_000 ether);\n        vm.deal(address(trader), 1_000_000 ether);\n        token.transfer(address(lp), 100_000_000 ether);\n        token.transfer(address(trader), 100_000_000 ether);\n        lp.addLiquidity(key, TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 1_000 ether);\n    }\n\n    /// @dev Finding: an exact-input buy whose amountSpecified exceeds uint128.max pays no cut at all, although the\n    /// pool executes a normal-sized trade bounded by the price limit. _crown() early-returns 0 on\n    /// `amount > type(uint128).max`, so beforeSwap returns a zero delta and the beneficiary gets nothing.\n    function test_oversizedAmountSpecified_skipsTheCut() public {\n        uint256 beneficiaryBefore = payer.balance;\n        uint160 limit = TickMath.getSqrtPriceAtTick(-60); // one spacing below the current price\n        SwapParams memory p = SwapParams({\n            zeroForOne: true,\n            amountSpecified: -int256(uint256(type(uint128).max) + 1),\n            sqrtPriceLimitX96: limit\n        });\n\n        uint256 ethBefore = address(trader).balance;\n        try trader.swap(key, p) returns (BalanceDelta d) {\n            uint256 ethIn = ethBefore - address(trader).balance;\n            // the pool actually took a few ETH from the trader\n            assertGt(ethIn, 1 ether, \"a real trade executed\");\n            assertEq(uint256(-int256(d.amount0())), ethIn);\n            // ...so the king must have been paid its 1% cut on it, as on any other buy\n            uint256 paid = hook.crownPaid(CRWN, ETH)\n                + (payer.balance - beneficiaryBefore)\n                + manager.balanceOf(payer, 0);\n            assertGt(paid, 0, \"DEFECT: trade executed and the crown cut was skipped\");\n        } catch {\n            // A fix may instead refuse the oversized amount; that is acceptable.\n        }\n    }\n\n    /// @dev Companion: the same trade with amountSpecified = uint128.max (one wei less) IS charged, proving the\n    /// price limit, not the amount, bounds what the pool consumes, and the only difference is the early return.\n    function test_sameTradeOneWeiSmaller_isCharged() public {\n        uint160 limit = TickMath.getSqrtPriceAtTick(-60);\n        SwapParams memory p = SwapParams({\n            zeroForOne: true,\n            amountSpecified: -int256(uint256(type(uint128).max)),\n            sqrtPriceLimitX96: limit\n        });\n        uint256 ethBefore = address(trader).balance;\n        // the cut on uint128.max input is ~1.7e36 ETH; the manager cannot pay it, so a claim is minted and the\n        // trader must fund it: settlement fails, the swap reverts, and no free trade happens.\n        vm.expectRevert();\n        trader.swap(key, p);\n        assertEq(ethBefore, address(trader).balance);\n    }\n}","reproduction":"State: fresh PoolManager, CrownHook at a flagged address with owner = payer, ETH/CRWN pool at 1:1 with 1000e18 full-range liquidity, CRWN crowned by afterInitialize, bps = 100. Call: router unlock -> poolManager.swap(key, SwapParams(zeroForOne: true, amountSpecified: -int256(uint256(type(uint128).max) + 1), sqrtPriceLimitX96: TickMath.getSqrtPriceAtTick(-60))). Expected: the oversized request is refused, or the ~3.04e18 wei actually swapped pays its 1% cut (~3.04e16 wei) to the beneficiary. Actual: the swap succeeds, trader's amount0 = -3042383861004481675, crownPaid[CRWN][ETH] == 0, payer.balance and payer's ERC-6909 ETH claim unchanged. Companion: amountSpecified = -(2^128 - 1) reverts, so the only difference is the early return at line 188. Run: forge test --match-path test/scratch/Proof_a1c066895729.t.sol -> test_oversizedAmountSpecified_skipsTheCut fails with 'DEFECT: trade executed and the crown cut was skipped: 0 <= 0'.","severity":"high","snippet":"        if (cfg.ticker == bytes32(0) || cfg.bps == 0 || amount > type(uint128).max) return 0;","title":"Any swapper trades fee-free: amountSpecified above uint128.max makes _crown() return 0 while a price-limited trade still executes"},{"citation":"resolved","description":"For the two swap shapes where currency0 is the specified side (exact-input zeroForOne buy, exact-output oneForZero sell) the cut is computed in beforeSwap from params.amountSpecified and taken with poolManager.take / minted before the pool runs. params.amountSpecified is only an upper bound: v4 stops at sqrtPriceLimitX96 or when range liquidity runs out. The hook never reconciles: afterSwap returns 0 for these shapes (line 174), and v4 applies the BeforeSwapDelta of +cut to the swapper in full. The two other shapes are charged in afterSwap from the realised delta and are correct, so the same trade is charged differently depending on which side is specified. Exact-input buy of 100 ETH with a limit one tick below spot: the pool consumes 0.0506 ETH, the hook pays 1 ETH to the king, the trader pays 1.0506 ETH (a ~2000% effective fee). Exact-output sell asking 100 ETH out with a limit ten ticks up: beforeSwap takes 1 ETH, the pool delivers 0.50015 ETH, v4 computes amount0 = +0.50015 - 1 = -0.49985 ETH, so the seller pays 0.506 CRWN AND 0.49985 ETH and receives nothing while the king receives 1 ETH out of the seller's pocket. Routers that settle any negative delta (PoolSwapTest-style, the repo's own Swapper) lose the ETH; routers that TAKE_ALL revert, so the swap is blocked, which the brief forbids. Partial fills happen without any price limit whenever a request exceeds the liquidity in range (a launch pool seeded one-sided with tokens is such a pool), and the king's beneficiary can profit by front-running large limited orders to push them into partial fills. Reported independently by audit_math, audit_flow, audit_economics and audit_permissions; merged here at the severity the loss of funds deserves. Fix (design decision): compute the cut for these shapes in afterSwap from the realised delta.amount0() and refund the surplus of any beforeSwap reservation, or refuse fills that do not consume the request (compare delta.amount0() to the request in afterSwap).","line":162,"path":"src/CrownHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {CrownHook} from \"src/CrownHook.sol\";\nimport {CrownToken} from \"src/CrownToken.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\n\n/// @dev Minimal v4 router that lets the test choose amountSpecified and sqrtPriceLimitX96 freely.\ncontract LimitRouter is IUnlockCallback {\n    IPoolManager public immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    function swap(PoolKey memory key, SwapParams memory p) external returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(uint8(0), key, p, ModifyLiquidityParams(0, 0, 0, 0))), (BalanceDelta));\n    }\n\n    function addLiquidity(PoolKey memory key, int24 lo, int24 hi, int256 liq) external returns (BalanceDelta) {\n        return abi.decode(\n            manager.unlock(abi.encode(uint8(1), key, SwapParams(false, 0, 0), ModifyLiquidityParams(lo, hi, liq, 0))),\n            (BalanceDelta)\n        );\n    }\n\n    function unlockCallback(bytes calldata raw) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"not manager\");\n        (uint8 action, PoolKey memory key, SwapParams memory sp, ModifyLiquidityParams memory mp) =\n            abi.decode(raw, (uint8, PoolKey, SwapParams, ModifyLiquidityParams));\n        BalanceDelta delta;\n        if (action == 0) delta = manager.swap(key, sp, \"\");\n        else (delta,) = manager.modifyLiquidity(key, mp, \"\");\n        _settle(key.currency0, delta.amount0());\n        _settle(key.currency1, delta.amount1());\n        return abi.encode(delta);\n    }\n\n    function _settle(Currency c, int128 amount) private {\n        if (amount < 0) {\n            uint256 owed = uint256(uint128(-amount));\n            manager.sync(c);\n            if (c.isAddressZero()) manager.settle{value: owed}();\n            else {\n                IERC20(Currency.unwrap(c)).transfer(address(manager), owed);\n                manager.settle();\n            }\n        } else if (amount > 0) {\n            manager.take(c, address(this), uint256(uint128(amount)));\n        }\n    }\n\n    receive() external payable {}\n}\n\ncontract CrownPartialFillProofTest is Test {\n    uint160 internal constant SQRT_PRICE_1_1 = 79228162514264337593543950336;\n    bytes32 internal constant CRWN = keccak256(\"CRWN\");\n    Currency internal constant ETH = Currency.wrap(address(0));\n\n    address internal factory = makeAddr(\"factory\");\n    address internal payer = makeAddr(\"payer\");\n\n    PoolManager internal manager;\n    CrownHook internal hook;\n    CrownToken internal token;\n    PoolKey internal key;\n    LimitRouter internal lp;\n    LimitRouter internal trader;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        address at = address((uint160(0xC0FFEE) << 100) | HookFlags.CROWN_HOOK);\n        deployCodeTo(\"CrownHook.sol:CrownHook\", abi.encode(manager, factory, payer), at);\n        hook = CrownHook(at);\n        token = new CrownToken();\n        key = PoolKey(ETH, Currency.wrap(address(token)), 12_500, 60, IHooks(at));\n        vm.prank(factory);\n        manager.initialize(key, SQRT_PRICE_1_1);\n\n        lp = new LimitRouter(manager);\n        trader = new LimitRouter(manager);\n        vm.deal(address(lp), 1_000_000 ether);\n        vm.deal(address(trader), 1_000_000 ether);\n        token.transfer(address(lp), 100_000_000 ether);\n        token.transfer(address(trader), 100_000_000 ether);\n        lp.addLiquidity(key, TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 1_000 ether);\n    }\n\n    /// @dev Exact-output sell: tokens in, exactly 100 ETH requested out, price limit ten ticks up. The pool can\n    /// deliver only ~0.5 ETH before the limit, but beforeSwap already took a 1 ETH cut on the 100 ETH *requested*\n    /// (CrownHook.sol:162-163). The swapper ends the swap paying ~0.506 CRWN AND ~0.5 ETH, receiving no ETH.\n    /// On fixed code the swap either reverts (partial fill refused) or the swapper's ETH balance does not go down.\n    function test_exactOutputSell_partialFill_mustNotChargeSwapperEth() public {\n        uint160 limit = TickMath.getSqrtPriceAtTick(10);\n        SwapParams memory p = SwapParams({zeroForOne: false, amountSpecified: 100 ether, sqrtPriceLimitX96: limit});\n        uint256 ethBefore = address(trader).balance;\n        try trader.swap(key, p) returns (BalanceDelta d) {\n            emit log_named_int(\"amount0 (ETH) delta seen by swapper\", d.amount0());\n            emit log_named_uint(\"cut paid to king\", hook.crownPaid(CRWN, ETH));\n            assertGe(\n                address(trader).balance,\n                ethBefore,\n                \"DEFECT: swapper asked for ETH out and ended up paying ETH (cut charged on the unfilled request)\"\n            );\n        } catch {\n            // refusing the partial fill is an acceptable fix\n        }\n    }\n\n    /// @dev Exact-input buy of 100 ETH limited to one tick: ~0.05 ETH executes but the 1 ETH cut on the 100 ETH\n    /// specified is still charged, 20x the executed amount. Fixed code charges at most the cut on what executed\n    /// (plus rounding) or refuses the partial fill.\n    function test_exactInputBuy_partialFill_cutBoundedByExecutedAmount() public {\n        uint160 limit = TickMath.getSqrtPriceAtTick(-1);\n        SwapParams memory p = SwapParams({zeroForOne: true, amountSpecified: -100 ether, sqrtPriceLimitX96: limit});\n        uint256 ethBefore = address(trader).balance;\n        try trader.swap(key, p) {\n            uint256 ethIn = ethBefore - address(trader).balance;\n            uint256 cut = hook.crownPaid(CRWN, ETH);\n            uint256 executed = ethIn - cut;\n            emit log_named_uint(\"ETH actually swapped\", executed);\n            emit log_named_uint(\"cut\", cut);\n            assertLe(cut, executed * 500 / 10_000 + 1, \"DEFECT: cut exceeds the maximum 5% of the executed input\");\n        } catch {}\n    }\n}","reproduction":"State: fresh PoolManager, CrownHook with owner = payer, ETH/CRWN pool at 1:1, 1000e18 full-range liquidity, bps 100. Call A: poolManager.swap(key, SwapParams(true, -100 ether, TickMath.getSqrtPriceAtTick(-1))). Expected: cut <= 5% of the 50631645632907438 wei consumed. Actual: crownPaid[CRWN][ETH] = 1e18, trader's ETH down 1050631645632907438. Call B: poolManager.swap(key, SwapParams(false, 100 ether, TickMath.getSqrtPriceAtTick(10))). Expected: the seller's ETH balance does not fall (it asked for ETH out). Actual: BalanceDelta amount0 = -500149965006998741 (seller pays ~0.5 ETH), amount1 = -506430389873924062 (and pays 0.506 CRWN), crownPaid = 1e18. Run: forge test --match-path test/scratch/Proof_54f558ce703d.t.sol -> both tests fail ('cut exceeds the maximum 5% of the executed input: 1e18 > 2531582281645372' and 'swapper asked for ETH out and ended up paying ETH').","severity":"high","snippet":"        uint256 amount = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);","title":"beforeSwap charges the cut on the requested amount, not the filled one: partial fills overpay the king and an exact-output sell makes the seller pay ETH"},{"citation":"resolved","description":"payCrown pays the beneficiary with poolManager.take, whose native transfer gives the beneficiary ~90k gas while the manager is unlocked, and then checks only the hook's currencyDelta and getNonzeroDeltaCount. PoolManager.sync is callable by anyone, is not gated by the unlock, and changes neither, so a beneficiary whose receive() calls sync(...) passes the checks and the payment is kept. Two router patterns then fail inside the same unlock: (a) a router that synced and pre-transferred its ERC-20 input before swap (sync -> transfer -> swap -> settle): the beneficiary's sync(address(0)) resets the synced currency, settle() credits msg.value = 0 to native, the token debt stays open and the unlock reverts CurrencyNotSettled; (b) a router that settles native ETH after the swap with settle{value} and no preceding sync (the pattern of v4-core's CurrencySettler helper): the beneficiary's sync(token) during the beforeSwap payout leaves token as the synced currency, and settle{value} reverts NonzeroNativeValue, so every ETH buy through such a router is blocked. The beneficiary is whoever wins a challenge (0.05 ETH quorum, out-stake the king), so this is reachable by an outsider. The brief requires that the hook never block a swap, and payCrown's own comment claims a beneficiary cannot make the swapper's unlock revert. v4's _settle comment says integrators 'should still call sync first', so routers that do are unaffected (the Universal Router syncs inside SETTLE), which keeps this at medium. Reported by audit_flow; reproduced with its proof and with test/scratch/MirrorSync.t.sol for case (b). Fix: in payCrown snapshot getSyncedCurrency()/getSyncedReserves() (TransientStateLibrary) before take and revert DeltaMismatch when either changed, so the payment falls back to the ERC-6909 claim; or pay contract beneficiaries only as claims.","line":232,"path":"src/CrownHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {CrownHook} from \"src/CrownHook.sol\";\nimport {CrownRegistry} from \"src/CrownRegistry.sol\";\nimport {CrownToken} from \"src/CrownToken.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\n\ncontract Rival is ERC20 {\n    constructor() ERC20(\"Crown Demo\", \"CRWN\") {\n        _mint(msg.sender, 1e24);\n    }\n}\n\n/// @dev A king beneficiary that, while being paid, resets the manager's synced currency. sync() is callable by anyone\n/// at any time and changes neither the hook's delta nor the nonzero-delta count, so payCrown's checks pass.\ncontract SyncResetter {\n    IPoolManager immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    receive() external payable {\n        manager.sync(Currency.wrap(address(0)));\n    }\n}\n\n/// @dev Router that pays its ERC-20 input up front: sync, transfer, swap, settle (all inside one unlock).\ncontract PrepaySeller is IUnlockCallback {\n    IPoolManager public immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    function sell(PoolKey memory key, uint256 amountIn) external returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(key, amountIn)), (BalanceDelta));\n    }\n\n    function unlockCallback(bytes calldata raw) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"not manager\");\n        (PoolKey memory key, uint256 amountIn) = abi.decode(raw, (PoolKey, uint256));\n        manager.sync(key.currency1);\n        IERC20(Currency.unwrap(key.currency1)).transfer(address(manager), amountIn);\n        BalanceDelta d = manager.swap(key, SwapParams(false, -int256(amountIn), TickMath.MAX_SQRT_PRICE - 1), \"\");\n        manager.settle();\n        if (d.amount0() > 0) manager.take(key.currency0, address(this), uint256(uint128(d.amount0())));\n        return abi.encode(d);\n    }\n\n    receive() external payable {}\n}\n\ncontract LpRouter is IUnlockCallback {\n    IPoolManager public immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    function add(PoolKey memory key, int256 liq) external {\n        manager.unlock(abi.encode(key, liq));\n    }\n\n    function unlockCallback(bytes calldata raw) external returns (bytes memory) {\n        (PoolKey memory key, int256 liq) = abi.decode(raw, (PoolKey, int256));\n        (BalanceDelta d,) = manager.modifyLiquidity(\n            key, ModifyLiquidityParams(TickMath.minUsableTick(60), TickMath.maxUsableTick(60), liq, 0), \"\"\n        );\n        manager.sync(key.currency0);\n        manager.settle{value: uint256(uint128(-d.amount0()))}();\n        manager.sync(key.currency1);\n        IERC20(Currency.unwrap(key.currency1)).transfer(address(manager), uint256(uint128(-d.amount1())));\n        manager.settle();\n        return \"\";\n    }\n\n    receive() external payable {}\n}\n\ncontract SyncGriefTest is Test {\n    uint160 constant SQRT_1_1 = 79228162514264337593543950336;\n    bytes32 constant CRWN = keccak256(\"CRWN\");\n    address factory = makeAddr(\"factory\");\n    address payer = makeAddr(\"payer\");\n\n    PoolManager manager;\n    CrownHook hook;\n    CrownRegistry registry;\n    CrownToken token;\n    PoolKey key;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        address at = address((uint160(0xC0FFEE) << 100) | HookFlags.CROWN_HOOK);\n        deployCodeTo(\"CrownHook.sol:CrownHook\", abi.encode(manager, factory, payer), at);\n        hook = CrownHook(at);\n        registry = hook.registry();\n        token = new CrownToken();\n        key = PoolKey(Currency.wrap(address(0)), Currency.wrap(address(token)), 12_500, 60, IHooks(at));\n        vm.prank(factory);\n        manager.initialize(key, SQRT_1_1);\n        LpRouter lp = new LpRouter(manager);\n        vm.deal(address(lp), 10_000 ether);\n        token.transfer(address(lp), 10_000 ether);\n        lp.add(key, 1_000 ether);\n    }\n\n    function _crownHostile() internal {\n        // the hostile party wins CRWN with a rival token; its beneficiary is a contract\n        SyncResetter hostile = new SyncResetter(manager);\n        vm.warp(block.timestamp + 7 days);\n        Rival rival = new Rival();\n        vm.deal(address(hostile), 1 ether);\n        vm.prank(address(hostile));\n        uint256 id = registry.openChallenge{value: 0.01 ether}(CRWN, address(rival));\n        vm.prank(address(hostile));\n        registry.stake{value: 0.05 ether}(id, CrownRegistry.Side.Challenger);\n        vm.warp(block.timestamp + 96 hours);\n        registry.finalize(id);\n        assertEq(registry.beneficiaryOf(CRWN), address(hostile));\n    }\n\n    function test_prepayRouterSell_worksWithHonestKing() public {\n        PrepaySeller r = new PrepaySeller(manager);\n        token.transfer(address(r), 10 ether);\n        BalanceDelta d = r.sell(key, 1 ether);\n        assertGt(d.amount0(), 0);\n    }\n\n    function test_prepayRouterSell_blockedByHostileKing() public {\n        _crownHostile();\n        PrepaySeller r = new PrepaySeller(manager);\n        token.transfer(address(r), 10 ether);\n        // the hook promises never to block a swap; the king's beneficiary makes this router's unlock revert\n        r.sell(key, 1 ether);\n    }\n}","reproduction":"State: ETH/CRWN pool with the hook, 1000e18 liquidity. A contract SyncResetter whose receive() calls poolManager.sync(Currency.wrap(address(0))) wins CRWN: warp 7 days, openChallenge{0.01 ETH}(CRWN, rivalWithSymbolCRWN), stake{0.05 ETH}(id, Challenger), warp 96 h, finalize(id); registry.beneficiaryOf(CRWN) == SyncResetter. Router PrepaySeller.unlockCallback: sync(token); token.transfer(manager, 1e18); swap(key, {false, -1e18, MAX_SQRT_PRICE-1}); settle(); take(ETH). Call PrepaySeller.sell(key, 1 ether). Expected: completes (it does with the genesis EOA king). Actual: reverts CurrencyNotSettled(). Run: forge test --match-path test/scratch/Proof_891063337919.t.sol -> test_prepayRouterSell_blockedByHostileKing fails with CurrencyNotSettled(). Mirror: beneficiary receive() calls sync(token); a router doing swap(true, -1e18) then settle{value}() without sync reverts NonzeroNativeValue() (test/scratch/MirrorSync.t.sol::test_noSyncNativeBuyer_blockedByHostileKing) while the honest-king companion passes.","severity":"medium","snippet":"        poolManager.take(currency, to, amount);","title":"A hostile king beneficiary can block swaps from routers that rely on the synced currency: payCrown's delta checks do not cover PoolManager.sync"},{"citation":"resolved","description":"openChallenge is open to everyone including the current king's beneficiary, refuses only the exact current king token address, allows one live challenge per ticker (line 182), and finalize restarts the reign clock on every flip (line 249: k.reignStartedAt = uint64(block.timestamp)) even when the beneficiary does not change. The incumbent deploys a throwaway token reporting the same symbol, opens the challenge the second reignStartedAt + MIN_REIGN passes, stakes 0.04 ETH more on its own challenger side, and nobody opposes. finalize: challengerStake 0.05 > kingStake 0, total >= QUORUM -> won; losers = 0 so slash = 0 and treasury gets 0; beneficiary stays the same wallet; reignStartedAt = now; claim refunds the whole 0.05 ETH. During the 96 h every honest openChallenge reverts ChallengeLive, and for 7 days after it ReignTooShort. Repeated each cycle (an in-block ordering race the incumbent's bot can win) this keeps the 1%-of-volume fee stream with the incumbent for gas only. If an outsider defends the king side to defeat the self-challenge the incumbent loses 20% of 0.05 ETH and a 3-day cooldown follows, but the slot is still closed for the cycle. The brief's guarantee that a king is challengeable after a 7-day reign therefore does not hold. Reported by audit_economics and audit_permissions; merged. Fix (design decision): refuse openChallenge when msg.sender == current beneficiary and/or do not restart reignStartedAt (or count a dethrone) when the winning challenge's beneficiary equals the sitting one or had no opposing stake.","line":185,"path":"src/CrownRegistry.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {CrownRegistry} from \"src/CrownRegistry.sol\";\n\ncontract SymbolToken is ERC20 {\n    constructor() ERC20(\"Crown Demo\", \"CRWN\") {}\n}\n\n/// @dev The incumbent beneficiary challenges its own crown with a throwaway same-symbol token, stakes to quorum on\n/// its own side, wins uncontested, keeps the beneficiary, has its whole stake refunded and gets a fresh 7-day\n/// MIN_REIGN. An honest challenger is locked out by ChallengeLive during the 96 h and ReignTooShort after it.\n/// Expected (brief): a king that has reigned 7 days is challengeable by anyone. The test passes once a fix either\n/// refuses the self-challenge or stops an uncontested / same-beneficiary flip from restarting the reign clock.\ncontract SelfChallengeLockoutTest is Test {\n    bytes32 constant CRWN = keccak256(\"CRWN\");\n    CrownRegistry registry;\n    SymbolToken kingToken;\n    SymbolToken shadow;\n    SymbolToken honestToken;\n    address incumbent = makeAddr(\"incumbent\");\n    address honest = makeAddr(\"honest\");\n\n    function setUp() public {\n        registry = new CrownRegistry();\n        kingToken = new SymbolToken();\n        shadow = new SymbolToken();\n        honestToken = new SymbolToken();\n        registry.registerKing(CRWN, address(kingToken), incumbent);\n        vm.deal(incumbent, 1 ether);\n        vm.deal(honest, 1 ether);\n    }\n\n    function test_incumbentSelfChallenge_locksOutHonestChallengerForAnotherReign() public {\n        vm.warp(block.timestamp + 7 days);\n        uint256 incumbentBefore = incumbent.balance;\n\n        // Incumbent challenges itself the second it becomes challengeable.\n        vm.prank(incumbent);\n        try registry.openChallenge{value: 0.01 ether}(CRWN, address(shadow)) returns (uint256 id) {\n            vm.prank(incumbent);\n            registry.stake{value: 0.04 ether}(id, CrownRegistry.Side.Challenger);\n\n            // Honest challenger is locked out while the self-challenge is live.\n            vm.prank(honest);\n            vm.expectRevert(CrownRegistry.ChallengeLive.selector);\n            registry.openChallenge{value: 0.01 ether}(CRWN, address(honestToken));\n\n            vm.warp(block.timestamp + 96 hours);\n            registry.finalize(id);\n            (, address beneficiary,) = registry.king(CRWN);\n            assertEq(beneficiary, incumbent, \"beneficiary unchanged\");\n\n            vm.prank(incumbent);\n            registry.claim(id);\n            assertEq(incumbent.balance, incumbentBefore, \"self-challenge cost the incumbent nothing\");\n        } catch {\n            // A fix that refuses the self-challenge is acceptable.\n        }\n\n        // The king has reigned well over 7 days under the same beneficiary: an outsider must be able to challenge.\n        vm.prank(honest);\n        registry.openChallenge{value: 0.01 ether}(CRWN, address(honestToken));\n    }\n}","reproduction":"State: registerKing(keccak256('CRWN'), T1, incumbent) at t0. At t0 + 7 days incumbent: openChallenge{0.01 ether}(CRWN, D1) with D1 a fresh ERC-20 whose symbol() is 'CRWN' -> id; stake{0.04 ether}(id, Challenger). Honest H: openChallenge{0.01 ether}(CRWN, H1) -> reverts ChallengeLive. At t0 + 7d + 96h: finalize(id) -> king(CRWN).beneficiary == incumbent, reignStartedAt == t0 + 7d + 96h; incumbent.claim(id) returns 0.05 ETH (balance equals the pre-challenge balance). H: openChallenge -> reverts ReignTooShort until t0 + 14d + 96h. Expected: H can challenge a king that has reigned 7 days. Run: forge test --match-path test/scratch/SelfChallengeLockout.t.sol -> fails with ReignTooShort().","severity":"medium","snippet":"        if (challengerToken == k.token) revert SameToken();","title":"Incumbent beneficiary can self-challenge at zero cost, restarting MIN_REIGN and holding the single challenge slot so the king is never challengeable"},{"citation":"resolved","description":"registerKing is permissionless and first-come, and afterInitialize crowns the pool's own token for the owner only when the ticker has no beneficiary yet, silently proceeding otherwise. The IMD factory deploys the hook (and its registry) and initializes the launch pool in one transaction, so the launch itself is safe. script/DeployCrown.s.sol deploys the hook in its own transaction and leaves initialization to a later call, and every later pool the owner opens (configurePool + initialize) has the same window: anyone deploys a token whose symbol() is the ticker and calls registerKing first. The pool then goes live paying 100% of its cut to the squatter, the owner cannot openChallenge for 7 days (ReignTooShort counts from the squat) and must then win a bonded stake-vote for its own ticker. The failure is not surfaced: afterInitialize returns its selector and emits PoolRegistered. Reported by audit_flow, audit_economics and audit_permissions; merged. Fix: let configurePool (owner-only) register the king for the configured ticker atomically, or have afterInitialize refuse to go live when the ticker's king token differs from the pool token and no explicit configuration named that ticker.","line":145,"path":"src/CrownHook.sol","reproduction":"tx1: deploy CrownHook(manager, factory, payer) at a flagged address (what DeployCrown does); no pool yet. tx2 (squatter): deploy Rival ERC-20 with symbol 'CRWN'; registry.registerKing(keccak256('CRWN'), rival, squatter) succeeds. tx3 (owner): manager.initialize(PoolKey(ETH, CrownToken, 12500, 60, hook), 1:1). Actual: hook.poolConfig(id) == (CRWN, 100, true), registry.king(CRWN) == (rival, squatter); after 1000e18 liquidity, trader exact-input buy of 1 ETH pays 0.01 ETH to squatter; owner's openChallenge{0.01 ether}(CRWN, CrownToken) reverts ReignTooShort. Expected: CrownToken crowned with owner as beneficiary, or the open refused. test/scratch/LowChecks.t.sol::test_genesisFrontrun_onNonAtomicDeploy asserts the actual behaviour and passes on this code.","severity":"low","snippet":"        if (cfg.ticker != bytes32(0) && registry.beneficiaryOf(cfg.ticker) == address(0)) {","title":"Genesis crown of any pool opened outside the one-transaction factory path can be front-run by a ticker squatter"},{"citation":"resolved","description":"The brief states fees must never be pooled for claiming and that if the transfer fails the swap proceeds without the cut. The hook instead mints an ERC-6909 claim in the PoolManager to the beneficiary whenever the manager's balance is short or the direct transfer fails, charges the swapper the cut anyway, and increments crownPaid[ticker][currency] by an amount that was not transferred, so crownPaid no longer 'matches the transfers' as the brief's integration test requires. A beneficiary that cannot run setOperator + redeemClaim (a contract that rejects ETH, a multisig without arbitrary calls) accumulates value it cannot pull except by ERC-6909 transfer. The hook also enables beforeSwap and beforeSwapReturnDelta beyond the two permissions the brief lists. launch.json's notes acknowledge the deviation, and the task's own hook reference recommends the claim fallback for a token-only seeded pool, so this is recorded as a requirement conflict the requester must accept explicitly rather than a surprise. Reported by audit_flow.","line":210,"path":"src/CrownHook.sol","reproduction":"State: pool configured at 200 bps whose king beneficiary is a contract whose receive() reverts (RejectingBeneficiary in test/CrownHook.t.sol). trader.swap(key, true, 1 ether). Actual: swapper pays the full cut, manager.balanceOf(beneficiary, 0) == 0.02 ether as a claim, crownPaid[ticker][ETH] == 0.02 ether, beneficiary.balance unchanged. Expected per brief: the swap proceeds without the cut, crownPaid unchanged, no claim. The existing test test_beneficiaryRejectingEther_getsClaimAndSwapProceeds asserts the implemented behaviour.","severity":"low","snippet":"            try poolManager.mint{gas: MINT_GAS}(beneficiary, currency.toId(), cut) {","title":"Transfer failure mints an ERC-6909 claim, charges the swapper and counts it in crownPaid, contrary to the brief's pay-at-source and skip-on-failure rules"},{"citation":"resolved","description":"The registry read and both payment paths are wrapped in try/catch with gas caps so the hook never reverts a swap, but the balance probe before them is a plain external call. The launch pool's currency0 is native ETH (address.balance, cannot fail), so the launch is unaffected. For any owner-configured pool whose currency0 is an ERC-20 (a shape the repo's own test_erc20PairPool_paysTheCutInCurrency0 supports), a token whose balanceOf reverts (paused, upgraded, non-standard) makes beforeSwap/afterSwap revert and with them the whole swap for as long as that state lasts. Reported by audit_math. Fix: probe non-native balances with a gas-capped try/catch or staticcall and fall through to the mint path on failure.","line":204,"path":"src/CrownHook.sol","reproduction":"State: owner-configured pool PoolKey(ALPH, CRWN, 12500, 60, hook) with ALPH < CRWN by address, ALPH.balanceOf reverting 'paused' once setPaused(true), full-range liquidity. trader.swap(k, true, 1 ether) succeeds while unpaused; after ALPH.setPaused(true) the same call reverts inside _crown at the balanceOf probe. Expected: the swap proceeds without the cut (CrownSkipped). test/scratch/LowChecks.t.sol::test_erc20Currency0_revertingBalanceOf_blocksSwap asserts the revert.","severity":"low","snippet":"        if (currency.balanceOf(address(poolManager)) >= cut) {","title":"_crown probes currency0.balanceOf unguarded, so a reverting balanceOf on an ERC-20 currency0 reverts every swap in that pool"},{"citation":"resolved","description":"When no pending configuration exists, afterInitialize derives the ticker from currency1.symbol() via Ticker.ofToken, which returns bytes32(0) for a token whose symbol() returns bytes32 (MKR-style), an empty string, a string longer than 64 bytes or no code. The zero ticker is stored with set = true, _crown returns 0 for ticker 0 so the pool never pays a cut, and configurePool reverts PoolAlreadyLive for that pool forever. The failure is silent: the callback returns its selector and emits PoolRegistered with ticker 0. Reachable only by the owner or factory initializing without configurePool, hence low, but there is no recovery short of a new pool. Reported by audit_flow. Fix: revert in afterInitialize when the derived ticker is zero, or leave the pool configurable in that case.","line":140,"path":"src/CrownHook.sol","reproduction":"State: token T whose symbol() returns bytes32('MKR'). Owner: manager.initialize(PoolKey(ETH, T, 12500, 60, hook), 1:1) without configurePool. Actual: hook.poolConfig(id) == (bytes32(0), 100, true); hook.configurePool(k, keccak256('MKR'), 100) reverts PoolAlreadyLive. Expected: the initialization is refused or the pool stays configurable. test/scratch/LowChecks.t.sol::test_afterInitialize_zeroTickerIsPermanent asserts the actual behaviour.","severity":"low","snippet":"        if (!cfg.set) cfg = PoolConfig(Ticker.ofToken(token), DEFAULT_BPS, true);","title":"afterInitialize permanently records ticker 0 for a currency1 without a readable string symbol, leaving the pool cut-less and unconfigurable"},{"citation":"resolved","description":"registerKing and openChallenge require Ticker.ofToken(token) == ticker, and ofToken accepts only an ABI-encoded string return of 1..64 bytes (data.length >= 64, offset 0x20, len <= 64 at line 35). A token whose symbol() returns bytes32 produces 32 bytes of return data and reads as no ticker; a symbol longer than 64 bytes is rejected. Such tokens can neither be crowned nor challenge, so a ticker whose only on-chain claimant is one of them stays uncrownable. A boundary limitation rather than a loss, and arguably an acceptable scope decision, but undocumented. Reported by audit_math. Fix: document that only string-symbol tokens are eligible, or decode a 32-byte return as a right-padded bytes32 symbol.","line":25,"path":"src/libraries/Ticker.sol","reproduction":"State: fresh CrownRegistry; contract T with `function symbol() external pure returns (bytes32) { return 'MKR'; }`. Ticker.ofToken(address(T)) -> expected keccak256('MKR'), actual bytes32(0). registerKing(keccak256('MKR'), address(T), beneficiary) -> expected Crowned, actual revert TickerMismatch. test/scratch/LowChecks.t.sol::test_bytes32Symbol_isZeroTicker_andCannotBeKing asserts the actual behaviour.","severity":"low","snippet":"        if (!ok || data.length < 64) return bytes32(0);","title":"Ticker.ofToken returns zero for tokens whose symbol() is bytes32-encoded or longer than 64 bytes, so they can never be king or challenger"}],"hash":"64a792de656d4f5bb98e5d5d2a6914e09b8df1233060d94cafd2b46933ca7657","nodeId":"ffb157f5-2473-43d1-ab48-15fbf7ee6c11","outcome":"completed","summary":"The findings file is complete and validated: 9 findings, every snippet matches its line in the tree, 4 proofs attached, and all 20 entry points answered.\n\n**Outcome of the review**\n\nEvery specialist finding reproduced against the code. I ran the four attached proofs plus three scratch files of my own, merged the duplicates, and recalibrated severities. The report is in `.imd-findings.json`.\n\n**Findings kept, with reproductions run**\n\n1. **High. Fee bypass via oversized amountSpecified.** `_crown()` returns 0 above uint128.max, so a price-limited swap of 2^128 executes a normal trade with no cut. Specialist proof fails on current code. Raised to high because every trader on every pool can use it.\n2. **High. Partial fills charge the cut on the requested amount.** Merged from four specialists at a higher severity. An exact-output sell with a partial fill leaves the seller paying ETH and tokens while receiving nothing. Both proof tests fail on current code.\n3. **Medium. Hostile beneficiary blocks swaps via sync().** The payCrown delta checks miss PoolManager.sync. The specialist proof fails with CurrencyNotSettled. My extra scratch test shows the mirror case, where routers that settle native ETH without syncing fail with NonzeroNativeValue.\n4. **Medium. Incumbent self-challenge lockout.** Merged from two specialists. My scratch test fails with ReignTooShort after an uncontested self-challenge refunds the incumbent in full and restarts the reign clock.\n5. **Low. Genesis crown front-run** on the non-atomic deploy script and on every later pool, merged from three specialists.\n6. **Low. Claim-mint fallback deviates from the brief** and inflates crownPaid. Documented in the manifest notes, kept as a requirement conflict.\n7. **Low. Unguarded balanceOf** reverts every swap on an ERC-20 currency0 pool if the token's balanceOf reverts.\n8. **Low. afterInitialize stores a zero ticker permanently** for a token without a string symbol.\n9. **Low. Ticker.ofToken rejects bytes32 symbols**, so such tokens can never be crowned.\n\n**Checks that held**\n\nContract sizes are within the deployment limits. The manifest's constructor arguments and permissions match the hook. The treasury payout, claim math, reentrancy guards, redeemClaim and unlockCallback all traced clean. The existing 67-test suite passes.\n\nThe static-analysis leads either reproduced as part of the above or did not hold: the reentrancy-eth lead on the treasury path is covered by the nonReentrant guard and the self-call boundary.","treeHash":null,"usage":{"cachedInputTokens":1941559,"inputTokens":514,"model":"claude-fable-5-1","outputTokens":51762,"runtime":"claude","turns":35,"wallClockMs":938487}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"23617eab0c5242d6","findings":[{"citation":"resolved","description":"The fix refuses openChallenge only when msg.sender is the sitting beneficiary (line 184), and it restarts the reign clock whenever kingStake != 0. The incumbent opens the challenge from a second wallet it controls, so SelfChallenge does not trigger. It then stakes 1 wei on the king side from any wallet. finalize sees kingStake == 1 != 0 and restarts reignStartedAt. slash = 1 * 2000 / 10000 = 0, so neither the treasury nor the winners take anything, and every wei is claimed back. The crown stays with the incumbent's wallet, which keeps the fee stream. Honest challengers get ChallengeLive for the 96 h and then ReignTooShort for another 7 days. That is exactly the lockout the original finding described, at gas cost only. The README's claim that a sybil king-side stake costs 'the treasury's half of its slash' does not hold for a dust stake, because the slash rounds to 0. Fix: require a minimum king-side stake (for example >= BOND, or a share of the challenger stake) before a flip restarts the clock. Alternatively, never restart the clock when the new beneficiary is the old one or its stake counterparty, or charge a non-refundable portion of the bond.","line":255,"path":"src/CrownRegistry.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {CrownRegistry} from \"src/CrownRegistry.sol\";\n\ncontract SymbolToken is ERC20 {\n    constructor() ERC20(\"Crown Demo\", \"CRWN\") {}\n}\n\n/// @dev The incumbent challenges its own crown from a second wallet (SelfChallenge only checks the sitting\n/// beneficiary) and defends the king side with 1 wei. kingStake != 0 so finalize restarts the reign clock, and the\n/// 20% slash of 1 wei rounds to zero, so the incumbent recovers every wei. An honest challenger is then refused\n/// ReignTooShort for another 7 days. Expected: a king that has reigned 7 days stays challengeable once the\n/// incumbent's own throwaway challenge ends.\ncontract SelfChallengeSybilTest is Test {\n    bytes32 constant CRWN = keccak256(\"CRWN\");\n    CrownRegistry registry;\n    SymbolToken kingToken;\n    SymbolToken shadow;\n    SymbolToken honestToken;\n    address incumbent = makeAddr(\"incumbent\");\n    address incumbentAlt = makeAddr(\"incumbentAlt\");\n    address honest = makeAddr(\"honest\");\n\n    function setUp() public {\n        registry = new CrownRegistry();\n        kingToken = new SymbolToken();\n        shadow = new SymbolToken();\n        honestToken = new SymbolToken();\n        registry.registerKing(CRWN, address(kingToken), incumbent);\n        vm.deal(incumbent, 1 ether);\n        vm.deal(incumbentAlt, 1 ether);\n        vm.deal(honest, 1 ether);\n    }\n\n    function test_selfChallengeFromSecondWallet_withOneWeiDefence_restartsReignForFree() public {\n        vm.warp(block.timestamp + 7 days);\n        uint256 before = incumbent.balance + incumbentAlt.balance;\n\n        vm.prank(incumbentAlt);\n        uint256 id = registry.openChallenge{value: 0.01 ether}(CRWN, address(shadow));\n        vm.prank(incumbentAlt);\n        registry.stake{value: 0.04 ether}(id, CrownRegistry.Side.Challenger);\n        vm.prank(incumbent);\n        registry.stake{value: 1}(id, CrownRegistry.Side.King);\n\n        vm.warp(block.timestamp + 96 hours);\n        registry.finalize(id);\n        (, address beneficiary, uint64 reignStartedAt) = registry.king(CRWN);\n        assertEq(beneficiary, incumbentAlt, \"crown still with the incumbent's wallets\");\n        assertEq(reignStartedAt, block.timestamp, \"reign clock restarted\");\n\n        vm.prank(incumbentAlt);\n        registry.claim(id);\n        vm.prank(incumbent);\n        registry.claim(id);\n        assertEq(incumbent.balance + incumbentAlt.balance, before, \"cost the incumbent nothing\");\n\n        // The king has reigned over 7 days; the incumbent's throwaway challenge is over. An outsider must be able\n        // to challenge now. Actual: ReignTooShort for another 7 days.\n        vm.prank(honest);\n        registry.openChallenge{value: 0.01 ether}(CRWN, address(honestToken));\n    }\n}","reproduction":"State: registerKing(keccak256('CRWN'), T1, incumbent) at t0. At t0+7d incumbentAlt calls openChallenge{0.01 ether}(CRWN, D1) (D1 has symbol CRWN), then stake{0.04 ether}(id, Challenger). incumbent calls stake{1 wei}(id, King). At +96h finalize(id): beneficiary == incumbentAlt and reignStartedAt == now. Both claims return the full 0.05 ETH + 1 wei, so the combined balance is unchanged. Honest openChallenge{0.01 ether}(CRWN, H1) is expected to succeed (the king has reigned >7d and the incumbent's challenge is over) but actually reverts ReignTooShort. forge test --match-path test/scratch/SelfChallengeSybil.t.sol fails with ReignTooShort() after every cost/beneficiary assertion passes.","severity":"medium","snippet":"            if (kingStake != 0) k.reignStartedAt = uint64(block.timestamp);","title":"Self-challenge fix is bypassed: incumbent opens from a second wallet and defends with 1 wei, restarting MIN_REIGN for free (re-raise of 0d8da1f1)"}],"hash":"6cc651d425aa90c959ac6c4ffb2512fc5bdc39008d85c2d3290d2dc85ec809da","nodeId":"ffb157f5-2473-43d1-ab48-15fbf7ee6c11","outcome":"completed","summary":"Of the four blocking findings from last round, three are fixed. The fourth, the self-challenge lockout, is still open: the fix can be got around cheaply. It's the only finding in `.imd-findings.json`, at medium, with a proof that fails on the current code. The coverage record answers all 20 entry points: 17 hold and 3 point to that finding.\n\n**Blocking findings from last round**\n- **Oversized `amountSpecified` (high): fixed.** `beforeSwap` now refuses amounts above `uint128.max` with `AmountTooLarge` on pools that charge a cut. The original proof passes.\n- **Cut charged on the requested amount, not the filled one (high): fixed.** `beforeSwap` records the amount the pool must fill and `afterSwap` checks the pool's actual delta against it, reverting `PartialFill` on a short fill. One specialist's proof (Proof_062bb…) still fails, but only because it expects a partial fill to go through. The design now refuses partial fills, which is one of the fixes the finding proposed. The other two partial-fill proofs pass.\n- **Hostile beneficiary resetting `sync` (medium): fixed.** `payCrown` now snapshots the synced currency and its reserves before paying and falls back to an ERC-6909 claim if either changed. Both proofs pass.\n- **Self-challenge lockout (medium): not fixed.**\n  - The new `SelfChallenge` check only blocks the wallet that currently receives the fees. The incumbent can open the challenge from a second wallet instead.\n  - The reign clock now restarts only when someone staked on the king's side (`src/CrownRegistry.sol:255`). The incumbent can meet that with a 1-wei stake.\n  - The 20% slash of 1 wei rounds to 0, so the incumbent gets every wei back.\n  - Result: honest challengers are refused with `ReignTooShort` for another 7 days, for the cost of gas alone. The README's claim that this costs the treasury's half of the slash doesn't hold for a 1-wei stake.\n  - `test/scratch/SelfChallengeSybil.t.sol` passes every cost and beneficiary assertion, then fails with `ReignTooShort()` when the honest challenger tries to open.\n\n**Advisory findings from last round**\n- **Fixed:** the ticker squatting before a pool opens (now refused with `TickerTaken`), the unguarded `balanceOf` check, and the permanent zero ticker (now refused with `ZeroTicker`).\n- **Dropped:**\n  - The ERC-6909 claim fallback: the task's own hook reference asks for it, and the author documented it in the README.\n  - The `bytes32`-symbol limitation: it's a scope decision and is now documented.\n\nThe repo's own test suite passes. The scratch proofs were deleted afterwards; the new proof's full source is in `.imd-findings.json`. Nothing else in the repo was changed.","treeHash":null,"usage":{"cachedInputTokens":577208,"inputTokens":14,"model":"claude-opus-5-5","outputTokens":10365,"runtime":"claude","turns":8,"wallClockMs":158750}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ee9fbaf2480d1034","findings":[{"citation":"resolved","description":"openChallenge allows one live challenge per ticker, makes the opener (msg.sender) the challenger's beneficiary, only refuses the exact current king token (SameToken), and a winning challenge restarts the 7-day MIN_REIGN. Nothing stops the current beneficiary from challenging its own crown with a throwaway token that reports the same symbol. The self-challenge occupies the slot for 72h + 24h; if nobody opposes it, it wins (0.05 ETH challenger stake > 0, quorum met), the beneficiary stays the same wallet, the reign clock resets to now (7 more days of ReignTooShort), the slash is 0 and the incumbent's whole stake is refunded via claim. Repeating the cycle at the first eligible second (an in-block race the incumbent's bot can run every 7 days) keeps honest challengers out for gas only. If outsiders instead defend the king side to make the self-challenge fail, the incumbent loses 20% of 0.05 ETH = 0.01 ETH per cycle and a 3-day cooldown follows, so the slot is still closed for 7 days per cycle. Economic incentive: the beneficiary receives 1% of every swap on the pool, so it pays ~0 to defend a stream it would otherwise have to out-stake a challenger to keep. This defeats the brief's guarantee that a king is challengeable after a 7-day reign and that resolution is purely rule-based. Minimal fix (needs a design decision): refuse openChallenge when msg.sender == current beneficiary and/or when challengerToken's symbol-hash is already the ticker's current king token's hash via a different address (e.g. track a per-ticker set), or do not reset reignStartedAt / allow a second challenge from a different opener when the live challenge's opener is the incumbent beneficiary.","line":182,"path":"src/CrownRegistry.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {CrownRegistry} from \"src/CrownRegistry.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\ncontract SymToken is ERC20 {\n    constructor(string memory s) ERC20(\"Crown Demo\", s) {}\n}\n\n/// @notice The incumbent beneficiary monopolises the single challenge slot of its ticker at zero cost: it opens a\n/// challenge against itself with a throwaway same-symbol token and stakes to quorum on the challenger side. Honest\n/// challengers are refused (ChallengeLive) for the whole 4-day challenge; the self-challenge then \"wins\", the\n/// beneficiary is unchanged, the reign clock restarts (7 more days of ReignTooShort) and the incumbent's full stake\n/// is refunded. Repeated every 7 days, this holds the crown for gas only. The brief guarantees that anyone can\n/// challenge a king once it has reigned 7 days; this test asserts that guarantee against a self-challenging king.\n/// The incumbent's own calls are made low-level so that a fix refusing them does not break the test.\ncontract SelfChallengeLockoutTest is Test {\n    bytes32 constant TICKER = keccak256(\"CRWN\");\n    CrownRegistry registry;\n    address incumbent = makeAddr(\"incumbent\");\n    address honest = makeAddr(\"honest\");\n    SymToken kingToken;\n    SymToken honestToken;\n\n    function setUp() public {\n        vm.warp(1_700_000_000);\n        registry = new CrownRegistry();\n        kingToken = new SymToken(\"CRWN\");\n        honestToken = new SymToken(\"CRWN\");\n        registry.registerKing(TICKER, address(kingToken), incumbent);\n        vm.deal(incumbent, 1 ether);\n        vm.deal(honest, 1 ether);\n        vm.warp(block.timestamp + 7 days);\n    }\n\n    /// @dev The incumbent opens a challenge against its own crown with a fresh throwaway token and stakes to quorum.\n    function selfChallenge() internal returns (bool opened, uint256 id) {\n        vm.startPrank(incumbent);\n        SymToken dummy = new SymToken(\"CRWN\");\n        bytes memory ret;\n        (opened, ret) = address(registry).call{value: 0.01 ether}(\n            abi.encodeCall(CrownRegistry.openChallenge, (TICKER, address(dummy)))\n        );\n        if (opened) {\n            id = abi.decode(ret, (uint256));\n            address(registry).call{value: 0.04 ether}(abi.encodeCall(CrownRegistry.stake, (id, CrownRegistry.Side.Challenger)));\n        }\n        vm.stopPrank();\n    }\n\n    function test_honestChallengerCanChallengeDespiteIncumbentSelfChallenge() public {\n        uint256 incumbentBefore = incumbent.balance;\n\n        // Cycle 1: self-challenge, let it resolve.\n        (bool opened, uint256 id) = selfChallenge();\n        vm.warp(block.timestamp + 96 hours);\n        if (opened) {\n            registry.finalize(id);\n            // On the current code the self-challenge wins: same beneficiary, reign clock reset, full refund.\n            assertEq(registry.beneficiaryOf(TICKER), incumbent, \"beneficiary unchanged\");\n            vm.prank(incumbent);\n            registry.claim(id);\n            assertEq(incumbent.balance, incumbentBefore, \"lockout cost nothing\");\n            assertEq(registry.TREASURY().balance, 0, \"treasury earned nothing\");\n        }\n\n        // Cycle 2: at the first eligible second the incumbent self-challenges again, ahead of the honest challenger.\n        vm.warp(block.timestamp + 7 days);\n        selfChallenge();\n\n        // Brief: a king is challengeable by anyone once it has reigned 7 days. This wallet has held the crown for\n        // 11 days and no outside challenge has ever been resolved against it. This call must succeed.\n        vm.prank(honest);\n        registry.openChallenge{value: 0.01 ether}(TICKER, address(honestToken));\n    }\n}","reproduction":"State: registry with CRWN king token T1, beneficiary B, reign >= 7 days. 1) B deploys dummy ERC-20 D1 with symbol \"CRWN\". 2) B calls openChallenge{value: 0.01 ether}(keccak256(\"CRWN\"), D1) -> id. 3) B calls stake{value: 0.04 ether}(id, Challenger). 4) Honest H calls openChallenge{value: 0.01 ether}(CRWN, H1) at any time in the next 96h -> reverts ChallengeLive. 5) After 96h anyone calls finalize(id): kingStake 0 < challengerStake 0.05, total 0.05 >= QUORUM -> won; king = D1, beneficiary still B, reignStartedAt = now, slash = 0, treasury gets 0. 6) B calls claim(id) -> receives 0.05 ETH back in full. 7) H calls openChallenge -> reverts ReignTooShort for 7 days. 8) At reignStartedAt + 7 days B repeats from step 1 in the first eligible block. Expected per brief: H can challenge B's crown once it has reigned 7 days. Actual: H is refused indefinitely while B pays nothing. The scratch test test/scratch/SelfChallengeLockout.t.sol fails on the current code with ChallengeLive().","severity":"medium","snippet":"        if (t.activeChallengeId != 0) revert ChallengeLive();\n        if (block.timestamp < uint256(k.reignStartedAt) + MIN_REIGN) revert ReignTooShort();\n        if (block.timestamp < t.cooldownUntil) revert CoolingDown();\n        if (challengerToken == k.token) revert SameToken();","title":"Incumbent beneficiary can hold the ticker's only challenge slot indefinitely at zero cost by self-challenging"},{"citation":"resolved","description":"For exact-input zeroForOne buys and exact-output oneForZero sells the cut is computed and paid in beforeSwap from params.amountSpecified, before the pool has executed anything. When params.sqrtPriceLimitX96 truncates the swap, the pool consumes only part of the specified input (or delivers only part of the specified output) but the hook has already taken 1% of the full specified amount and reports it as its delta, which the swapper pays. The afterSwap path (the other two swap shapes) computes the cut from the executed BalanceDelta, so the same trade is charged differently depending on which side is specified (path divergence). Economics: the brief says the cut is 'from the swap amount'; here a trader who places a 100 ETH exact-input order with a tight limit and gets 0.05 ETH filled pays a 1 ETH cut, a ~2000% effective fee on the executed amount, transferred to the king's beneficiary. No attacker can force this on others, but any router that uses price limits for partial fills (v4 supports them deliberately) overpays the king on every truncated order. Fix: compute the beforeSwap-path cut in afterSwap from delta.amount0() like the other path (take from the unspecified/specified side after execution), or cap the cut to 1% of the executed amount and refund the difference.","line":162,"path":"src/CrownHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {PoolId} from \"v4-core/src/types/PoolId.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {StateLibrary} from \"v4-core/src/libraries/StateLibrary.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {CrownHook} from \"src/CrownHook.sol\";\nimport {CrownToken} from \"src/CrownToken.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\n\ncontract Router is IUnlockCallback {\n    IPoolManager immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    function swap(PoolKey memory key, SwapParams memory p) external returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(uint8(0), abi.encode(key, p))), (BalanceDelta));\n    }\n\n    function addLiquidity(PoolKey memory key, int24 lo, int24 hi, int256 liq) external returns (BalanceDelta) {\n        return abi.decode(\n            manager.unlock(abi.encode(uint8(1), abi.encode(key, ModifyLiquidityParams(lo, hi, liq, bytes32(0))))),\n            (BalanceDelta)\n        );\n    }\n\n    function unlockCallback(bytes calldata raw) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (uint8 action, bytes memory data) = abi.decode(raw, (uint8, bytes));\n        BalanceDelta delta;\n        PoolKey memory key;\n        if (action == 0) {\n            SwapParams memory p;\n            (key, p) = abi.decode(data, (PoolKey, SwapParams));\n            delta = manager.swap(key, p, \"\");\n        } else {\n            ModifyLiquidityParams memory p;\n            (key, p) = abi.decode(data, (PoolKey, ModifyLiquidityParams));\n            (delta,) = manager.modifyLiquidity(key, p, \"\");\n        }\n        _settle(key.currency0, delta.amount0());\n        _settle(key.currency1, delta.amount1());\n        return abi.encode(delta);\n    }\n\n    function _settle(Currency c, int128 a) internal {\n        if (a < 0) {\n            uint256 owed = uint256(uint128(-a));\n            manager.sync(c);\n            if (c.isAddressZero()) manager.settle{value: owed}();\n            else {\n                IERC20(Currency.unwrap(c)).transfer(address(manager), owed);\n                manager.settle();\n            }\n        } else if (a > 0) {\n            manager.take(c, address(this), uint256(uint128(a)));\n        }\n    }\n\n    receive() external payable {}\n}\n\n/// @notice The cut taken in beforeSwap is 1% of the amount the trader *specified*, not of the amount the pool\n/// *executed*. When a price limit truncates an exact-input buy, the trader pays the full cut on ETH that never\n/// traded. Expected: cut == 1% of the ETH the pool actually consumed (as the afterSwap path does).\ncontract PartialFillCutTest is Test {\n    using StateLibrary for IPoolManager;\n\n    address factory = makeAddr(\"factory\");\n    address payer = makeAddr(\"payer\");\n    PoolManager manager;\n    CrownHook hook;\n    CrownToken token;\n    PoolKey key;\n    Router lp;\n    Router trader;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        address at = address((uint160(0xC0FFEE) << 100) | HookFlags.CROWN_HOOK);\n        deployCodeTo(\"CrownHook.sol:CrownHook\", abi.encode(manager, factory, payer), at);\n        hook = CrownHook(at);\n        token = new CrownToken();\n        key = PoolKey(Currency.wrap(address(0)), Currency.wrap(address(token)), 12_500, 60, IHooks(at));\n        vm.prank(factory);\n        manager.initialize(key, 79228162514264337593543950336);\n        lp = new Router(manager);\n        trader = new Router(manager);\n        vm.deal(address(lp), 1_000_000 ether);\n        vm.deal(address(trader), 1_000_000 ether);\n        token.transfer(address(lp), 500_000_000 ether);\n        token.transfer(address(trader), 500_000_000 ether);\n        lp.addLiquidity(key, TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 1_000 ether);\n    }\n\n    function test_cutIsChargedOnTheSpecifiedAmountNotTheExecutedAmount() public {\n        // A limit one tick below the current price: the pool can only consume a sliver of a 100 ETH order.\n        (, int24 tick,,) = IPoolManager(address(manager)).getSlot0(key.toId());\n        uint160 limit = TickMath.getSqrtPriceAtTick(tick - 1);\n        uint256 before = payer.balance;\n        BalanceDelta d = trader.swap(key, SwapParams(true, -100 ether, limit));\n        uint256 cut = payer.balance - before;\n        uint256 paidByTrader = uint256(-int256(d.amount0()));\n        uint256 consumedByPool = paidByTrader - cut;\n        emit log_named_uint(\"ETH consumed by pool\", consumedByPool);\n        emit log_named_uint(\"cut paid to king\", cut);\n        // The trader traded ~0.05 ETH but paid the 1 ETH cut of a 100 ETH order: a >1000% effective fee.\n        assertEq(cut, consumedByPool / 100, \"cut should be 1% of the ETH that actually traded\");\n    }\n}","reproduction":"State: ETH/CRWN pool at price 1:1 with 1000 liquidity units over the full range. Trader calls swap(key, SwapParams(zeroForOne=true, amountSpecified=-100 ether, sqrtPriceLimitX96 = sqrtPriceAtTick(currentTick - 1))). Pool consumes 0.050631645632907438 ETH; the hook transfers 1.000000000000000000 ETH to the beneficiary in beforeSwap; the trader's delta is -1.050631645632907438 ETH. Expected: cut == 1% of executed input == 0.000506316456329074 ETH. Actual: 1 ETH. test/scratch/PartialFillCut.t.sol fails with 'cut should be 1% of the ETH that actually traded: 1000000000000000000 != 506316456329074'.","severity":"low","snippet":"        uint256 amount = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n        uint256 paid = _crown(key, amount);\n        return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(paid)), 0), 0);","title":"beforeSwap cut is charged on the specified amount, not the executed amount: a price-limited partial fill pays the cut on ETH that never traded"},{"citation":"resolved","description":"registerKing is permissionless and first-come, and afterInitialize only crowns the pool's token for the owner if the ticker is still empty, silently proceeding otherwise. For the launch pool this is safe because the hook (and its registry) is deployed and the pool initialized in one factory transaction. For every later pool the owner opens (configurePool + initialize, or initialize with the default ticker of currency1), a squatter who sees the transaction can call registerKing(keccak256(symbol), fakeTokenWithThatSymbol, squatter) first. The pool then goes live paying 100% of the cut to the squatter, the owner's token is not king, and the owner cannot even open a challenge for 7 days (MIN_REIGN counts from the squat), after which it must out-stake the squatter in a stake war. Economics: the squatter's cost is one token deployment and one registerKing; the gain is the pool's entire cut (default 1% of currency0 volume) for at least 7 days plus whatever the stake war yields. The brief's rule that the first registration crowns is honoured, but the hook's pool-opening flow gives the owner no way to bind the crown to the pool atomically and does not surface the failure. Fix: have afterInitialize revert (or refuse to go live / set bps to 0) when the ticker already has a king whose token differs from the pool's currency1 and no explicit configurePool named that ticker, or let configurePool pre-register the king in the same call so the open is atomic.","line":145,"path":"src/CrownHook.sol","reproduction":"State: hook deployed, owner = payer. 1) Squatter deploys ERC-20 F with symbol \"OTHR\" and calls registry.registerKing(keccak256(\"OTHR\"), F, squatter). 2) Owner calls manager.initialize(PoolKey(ETH, OTHR, 12500, 60, hook), 1:1): afterInitialize finds beneficiaryOf(OTHR) != 0 and skips registerKing; pool is live with ticker OTHR at 100 bps. 3) LP adds liquidity; a trader buys with 1 ETH exact input. Actual: squatter.balance == 0.01 ETH, owner receives 0. 4) Owner calls openChallenge(OTHR, realOTHR) -> reverts ReignTooShort for 7 days. Expected: the owner's pool pays the owner (its documented genesis beneficiary), or the open is refused so the owner notices. test/scratch/TickerSquat.t.sol reproduces this and passes on the current code.","severity":"low","snippet":"        if (cfg.ticker != bytes32(0) && registry.beneficiaryOf(cfg.ticker) == address(0)) {\n            try registry.registerKing(cfg.ticker, token, owner) {} catch {}\n        }","title":"Genesis crown of any later pool can be front-run: a squatter registers the pool token's ticker first and collects that pool's fees"}],"hash":"7ef1cb8cea95e66622a903fc599c4c55c20cf19cd4049bf57eb84ae0b6b20f6f","nodeId":"6eb1c4d3-b545-4110-89d8-59c0a8bc1d23","outcome":"completed","summary":"The review is complete. Findings are in `.imd-findings.json` with a 22-row coverage record, and the working tree is unchanged apart from the scratch tests and the findings file (git shows nothing tracked as modified).\n\n**What I found in the Economic Security, Invariant and Flow Gap area**\n\n1. **Medium: an incumbent beneficiary can hold the ticker's only challenge slot forever at zero cost.** The king's wallet deploys a throwaway token with the same symbol, challenges its own crown, and stakes to quorum on the challenger side. Honest challengers get `ChallengeLive` for four days, the self-challenge wins, the beneficiary is unchanged, the reign clock resets for seven more days, and the whole stake is refunded. The proof test in `test/scratch/SelfChallengeLockout.t.sol` fails on the current code with `ChallengeLive()`.\n\n2. **Low: the beforeSwap cut is charged on the specified amount, not the executed amount.** A price-limited exact-input buy of 100 ETH that fills 0.05 ETH still pays a 1 ETH cut to the king. The other two swap shapes compute from the executed delta, so the same trade is priced differently by path. The proof test in `test/scratch/PartialFillCut.t.sol` fails with the exact numbers.\n\n3. **Low: later pools' genesis crown can be front-run.** Because registration is first-come and afterInitialize silently yields when the ticker already has a king, a squatter who registers the pool token's symbol first collects that pool's full cut and cannot be challenged for seven days. Reproduced in `test/scratch/TickerSquat.t.sol`.\n\n**What held**\n\n- Registry math is exact to the wei: pools conserve `winners + losers`, pro-rata claims give the remainder to the last claimer, winners never get less than their stake, and the view matches the write path.\n- Finalize cannot be blocked by the treasury, and claim failures only affect the caller.\n- The hook's take or mint is offset exactly by its returned delta on every path, including batched unlocks, so it never holds funds.\n- A beneficiary that trades on the hooked pool inside its receive callback runs out of the 100k gas envelope, so the in-swap MEV path does not stand. I recorded this on the `payCrown` coverage row rather than as a finding.\n\n**Not reached**\n\nNothing in the listed entry points was left unreached. I did not test the site or deployment scripts, which are outside this area.","treeHash":null,"usage":{"cachedInputTokens":2216271,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":53993,"runtime":"claude","turns":43,"wallClockMs":998177}},{"artifacts":[],"attempt":1,"bundleHash":"2be0b89534c5c7af616690b4fcb37c88eff5d2ae36856db6dec0e0195d33d8fd","device":"3c1226d190150ef0","findings":[],"hash":"847456388a693943951806383def512c8109a7d70a83fe7f634daec76c0b883b","nodeId":"6040de00-bc97-4ca0-a77e-41a654cd9017","outcome":"completed","summary":"I've built CrownRegistry and CrownHook as specified, plus the CRWN launch token, the standing challenger token, deploy scripts, the ticker site and the tests. Everything is committed (`80af607`), including the dependencies as plain files with no submodules. `forge build`, `forge test` (with the verifier's fuzz seed) and `forge fmt --check` all pass: 44 tests. I also ran the three protected floor test files against the built bytecode with launch-like settings (Robinhood Chain, ETH pair, fee 12500, spacing 60), and all 13 pass. Nothing was deployed: the launch pipeline deploys the token and the hook, so the brief's \"deploy on Robinhood Chain\" is left to that step.\n\n**CrownRegistry** (`src/CrownRegistry.sol`)\n- **Ticker:** keccak256 of the uppercased symbol. A token can only register or challenge for a ticker its own `symbol()` produces.\n- **Rules:** first registration wins. A challenge needs a 0.01 ETH bond, there is one live challenge per ticker, the king must have reigned 7 days, and a failed challenge starts a 3-day cooldown. Staking runs 72 hours, followed by a 24-hour timelock. The challenger wins only with more stake than the king and at least 0.05 ETH staked in total.\n- **Payouts:** the losing side gets 80% back and 20% is slashed, half to the winning stakers and half to the treasury. All stakers claim their own share.\n- **Exact to the wei:** each claim is paid pro rata over the stake not yet claimed, so the last claimer gets the exact remainder. A fuzz test checks that every wei staked is paid out and that winners get at least their stake back.\n- **Treasury and admin:** the treasury share is sent at finalize. If that fails it is kept and anyone can retry it, so the treasury can't block a result. There are no admin functions.\n\n**CrownHook** (`src/CrownHook.sol`)\n- **Where the cut goes:** the cut (default 1%, range 0–5% per pool) goes to the current king's beneficiary inside the swap. The hook never holds funds. `crownPaid[ticker][currency]` is public.\n- **Two extra permissions:** besides `afterSwap` and `afterSwapReturnDelta`, I added `beforeSwap` and `beforeSwapReturnDelta`. The launch rules require the fee in ETH for both exact-input and exact-output swaps, and the after-swap callbacks can't reach ETH when it is the specified side.\n- **Swaps never revert because of the hook:**\n  - The registry read and the transfer are gas-capped and wrapped so a failure is caught.\n  - If the manager has too little ETH (for example a fresh pool seeded with tokens only), or the beneficiary rejects ETH or burns gas, the cut is minted as a Uniswap claim to the beneficiary instead. They redeem it with `redeemClaim`.\n  - If the beneficiary tries to leave an unpaid debt in the pool manager while being paid, the transfer is undone and they get a claim instead.\n- **Liquidity:** there are no add/remove-liquidity callbacks, so the launch factory can always collect its fees.\n\n**Design choices I made where the brief was open** (all listed in the README):\n- **The hook creates its own registry.** The launch can only deploy the token and the hook. The hook also crowns CRWN with the owner wallet as beneficiary when the pool opens, so nobody can register the ticker first.\n- **Who can open pools:** only the launch factory or the owner. The owner sets a pool's ticker and cut before opening it; the launch pool defaults to its token's ticker at 1%.\n- **The bond counts as the challenger's stake.** Whoever opens the challenge becomes the new beneficiary if it wins, because `openChallenge` has no beneficiary argument.\n- **Failed challenges:** a challenge that loses on quorum or a tie slashes the challenger side.\n\n**Still to do after launch** (the README has `cast` commands for each step):\n- Set the hook address in `site/config.js`; the site shows a \"not configured\" message until then.\n- Deploy the challenger token with `script/DeployChallenger.s.sol`.\n- Run the on-chain dethrone demo. It needs real time: 7 days of reign, then 72 hours of staking and the 24-hou","treeHash":"dacef5728579741df89029c5005b66e9bbf7a6d6","usage":{"cachedInputTokens":5331216,"inputTokens":86,"model":"claude-opus-5-5","outputTokens":93584,"runtime":"claude","turns":46,"wallClockMs":1050966}},{"artifacts":[],"attempt":1,"bundleHash":"889e937aad010950c5a822249343e14c92369b25815dc6f0540ec87b3cd2b85a","device":"b273d407784470b4","findings":[],"hash":"8ecfa7f330507ededa4c6f234595e018c4259898d1285471506bce7f714c6e81","nodeId":"fb3ed2b0-b2f9-4643-9652-dc72c670dd60","outcome":"completed","summary":"All four reopened findings reproduced and are fixed, every reviewer proof now passes, and the frontend export is rebuilt against the new ABIs. The work is complete and verified.\n\n**Findings and what changed**\n\n- **Oversized amount traded fee-free (high):** `beforeSwap` now reverts with `AmountTooLarge` on a cut-bearing pool when the specified amount exceeds the largest fill any pool can make. Uncharged pools keep plain v4 behaviour.\n- **Cut charged on the request, not the fill (high):** a refund of a beforeSwap reservation is not expressible in v4, so the hook takes the finding's second option. `beforeSwap` stores the net amount the pool must fill in a transient slot, and `afterSwap` reverts with `PartialFill` when the pool's raw delta differs. Nothing is charged on a refused fill, and pools with no king or zero bps let partial fills through unchanged.\n- **Hostile beneficiary re-syncing the manager (medium):** `payCrown` snapshots the synced currency and reserves around the transfer and falls back to the claim path when either changed. Both the prepaying ERC-20 router and a native-settling router complete their swaps in the new tests.\n- **Self-challenge lockout (medium):** the sitting beneficiary can no longer open a challenge, and an undefended flip no longer restarts the reign clock. The residual sybil race and its cost are documented in the README.\n- **Advisory items:** squatted-ticker and unreadable-symbol pools are now refused on the unconfigured path, and the ERC-20 balance probe is gas-capped. The claim fallback and the string-only symbol rule stay as documented scope decisions and are answered as disputed.\n\n**Verification**\n\n| Check | Result |\n| --- | --- |\n| Repository Foundry tests, verifier seed | 80 passed |\n| Reviewer proofs copied under test/scratch | 7 passed |\n| Protected hook floor suite, new bytecode | 4 passed |\n| Site typecheck, build, interaction tests | pass, 49 assertions |\n| Design probe at 320, 390, 1440 | 16 of 16 |\n\nSlither is not installed on this worker, so no static-analysis claim is made for the revised code. The responses file answers all nine findings, and the README, validation and review documents record the new rules, the test results and that limitation.","treeHash":"e52771b9e0328ecf13732b52463bccfe37648737","usage":{"cachedInputTokens":4689407,"inputTokens":706,"model":"claude-fable-5-1","outputTokens":84214,"runtime":"claude","turns":85,"wallClockMs":1864927}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"7c748c02cd2ee98f","findings":[{"citation":"resolved","description":"The fix for finding 641d732f (sybil self-challenge lockout) is confirmed: the proof passes on this tree, a one-wei or absent defence no longer moves challengeableAt, and the sybil cycle now forfeits at least 0.001 ETH to the treasury per 7 days. This note records the trade-off the fix introduces, not a blocking defect. The brief says 'a king is challengeable only after a 7-day minimum reign'. On this tree a flip pushes challengeableAt forward only when the losing king side staked at least MIN_DEFENCE (0.01 ETH). An honest challenger that wins because nobody defended the king therefore becomes king with challengeableAt still in the past: the dethroned incumbent (or anyone) can open a new challenge against it in the very block of finalize, and the new king must out-stake every challenger from the first second, with 3-day cooldowns after each failed challenge the only pause. The author documents this in README and the manifest notes, the alternative (restart on every flip) is exactly the lockout vector that was fixed, and the economics are still rule-based (each failed challenge costs the challenger 20% of its stake, half of it to the king's defenders), so I recommend accepting it as a documented deviation rather than reworking it. If the author wants the brief's literal guarantee, the only options within the brief's parameters are a non-refundable bond or a reign restart tied to the winner rather than the loser, both of which re-open the sybil vector.","line":272,"path":"src/CrownRegistry.sol","reproduction":"State: registerKing(keccak256('CRWN'), T1, incumbent) at t0. At t0+7d honest calls openChallenge{0.01 ether}(CRWN, T2) then stake{0.04 ether}(id, Challenger); nobody stakes on the king side. At +96h finalize(id): king(CRWN) == (T2, honest, now) and challengeableAt(CRWN) == t0+7d (< now). incumbent calls openChallenge{0.01 ether}(CRWN, T1) in the same block. Expected per the brief's wording: revert ReignTooShort for 7 days. Actual: succeeds with challengeId 2. test/scratch/UndefendedWinner.t.sol passes on this tree (it asserts the actual behaviour).","severity":"info","snippet":"            if (kingStake >= MIN_DEFENCE) k.challengeableAt = uint64(block.timestamp + MIN_REIGN);","title":"Residual of the MIN_DEFENCE fix: a challenger that wins an undefended challenge gets no 7-day minimum reign and can be re-challenged in the same block"}],"hash":"925578bca7505102b9fcaf1592ae9c128304055a910b1419641cefade5c3c567","nodeId":"ffb157f5-2473-43d1-ab48-15fbf7ee6c11","outcome":"completed","summary":"The review is complete. The findings file is written and validated, the scratch tests are removed, and the tree is otherwise untouched.\n\n**Outcome: the one outstanding finding is fixed, and nothing new blocks the launch.**\n\n**Settled finding (641d732f, medium, sybil self-challenge lockout):** confirmed fixed. My proof now passes on the revised code. The registry gates challenges on a new `challengeableAt` field, which a flip pushes 7 days ahead only when the losing king side staked at least 0.01 ETH. A one-wei or absent defence leaves the gate untouched, so an honest challenger can open the moment a throwaway challenge ends. The repository adds tests for the sybil scenario and both MIN_DEFENCE boundaries. The remaining cost of a sybil cycle is 0.001 ETH to the treasury per 7 days, which the README and manifest notes document as a residual. That floor is set by the brief's 20% slash, so I accept it.\n\n**Verification run on this tree:**\n- Repository suite: 84 tests pass.\n- Protected floor suites against the current hook and token creation code: Hook 4/4, Owners 2/2, Token 6/6 pass, with the factory probe, chain 4663 and the manifest's pool terms. Initcode is 14,883 bytes.\n- Earlier-round proofs: four of five pass. The fifth fails only because the hook now refuses partial fills by design, a deviation settled in a prior round.\n- launch.json: constructor arguments and the five permissions match the implementation, notes are under 4,000 characters, fee is 12500.\n\n**One info note, not blocking:** a challenger who wins an undefended challenge becomes king with no 7-day protection and can be re-challenged in the same block. This is the direct trade-off of the fix I asked for, it is documented, and I recommend accepting it. The reproduction is in the findings file.\n\nCoverage answers all 20 entry points as `holds`, plus three invariant rows for the settled finding, the earlier proofs and the floor suites.","treeHash":null,"usage":{"cachedInputTokens":1214316,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":18492,"runtime":"claude","turns":22,"wallClockMs":465576}},{"artifacts":[],"attempt":1,"bundleHash":"3fbbf0e42ba1e35daebb88213a168baa2e3e75f64f7a18d8cbe89c6ef1ebbd45","device":"1ca477e8d9b58040","findings":[],"hash":"98bb3ddba0fa0e608dbd8122c3c4215eeb034eb8dc42a992f57d03cf2b5a78f4","nodeId":"4aeaee3c-d5e8-48fd-baf1-e5f909776f71","outcome":"completed","summary":"Build and tests pass. The only remaining check is that no file other than the manifest changed, which the earlier status already showed.\n\nUpdated launch.json for the CROWN launch. The manifest already matched the accepted hook, so this revision touched one sentence in the notes.\n\n**What changed**\n\n- The notes said a flip against an undefended king does not restart `reignStartedAt`. The second revision round in the accepted tree replaced that rule. Now every flip restarts the reign clock, and only `challengeableAt` moves 7 days ahead, and only when the king side was defended with at least 0.01 ETH. The notes now state the current rule.\n\n**What was verified against the tree**\n\n- The first field is `\"kind\": \"univ4_hook\"`, and the manifest uses only schema keys.\n- Permissions match `getHookPermissions` in src/CrownHook.sol exactly: afterInitialize, beforeSwap, afterSwap, beforeSwapReturnDelta, afterSwapReturnDelta.\n- Constructor arguments match the declaration order of pool manager, factory, owner, written as `$poolManager`, `$factory`, `$owner`.\n- Token contract CrownToken, name Crown Demo, symbol CRWN, 18 decimals.\n- Pool fields are the launch reference values: native ETH pair, fee 12500, tick spacing 60, the given initial price.\n- Notes are 3679 characters, under the 4000 cap.\n- The compiler settings the notes cite match the README table and foundry.toml.\n\n**Checks run**\n\n- `forge build` passes.\n- `forge test` with the verifier's fuzz seed: 84 tests passed, 0 failed.\n- `git status` shows launch.json as the only modified file.","treeHash":"765a3e10dd7af1a203031b7e2a03689a7ddca155","usage":{"cachedInputTokens":330000,"inputTokens":194,"model":"claude-fable-5-1","outputTokens":3292,"runtime":"claude","turns":7,"wallClockMs":87245}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"8a38929b31c716ff","findings":[],"hash":"bb71ab9eba51a4a3d180680af1bf98996fefb559da1ae3b254909e1266c9ebdc","nodeId":"734e1c36-275a-474e-b519-330b7b5dc905","outcome":"failed","summary":"This content was flagged for possible cybersecurity risk. If this seems wrong, try rephrasing your request. If you’re doing authorized security work that requires more cyber permissive safeguards, apply for Daybreak access via https://platform.openai.com/settings/organization/status-and-access before retrying.","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":"gpt-6-astra","outputTokens":0,"runtime":"codex","turns":5,"wallClockMs":1217795}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"e67cc6ec4009b226","findings":[{"citation":"resolved","description":"For the two swap shapes where currency0 (ETH) is the specified side, the cut is computed in beforeSwap from params.amountSpecified and taken from the PoolManager before the pool runs. v4 fills exact-input and exact-output swaps only up to sqrtPriceLimitX96 or until liquidity runs out, so the executed amount can be far smaller than the request. The hook never reconciles: in afterSwap it returns 0 for these shapes (line 174), and the beforeSwap specified delta of +cut is applied to the swapper in Hooks.afterSwap regardless of fill. Exact-input buy: the swapper pays 1% of the requested ETH on top of the few ETH the pool actually consumed (an effective fee of ~100% in the reproduction). Exact-output sell (oneForZero, amountSpecified > 0): the hook takes 1% of the requested ETH out of the manager's pooled ETH, the pool produces less than that, and swapDelta.amount0 = actual - cut goes negative: a user who sold tokens to receive ETH ends up paying tokens AND ETH and receiving nothing, with the king's beneficiary paid out of the seller's pocket and out of other pools' ETH held by the manager. Routers that settle any negative delta (PoolSwapTest-style, the repo's Swapper) lose the ETH; the Universal Router's TAKE_ALL reverts on the negative delta, so the swap is blocked instead, which the brief forbids. The afterSwap paths (lines 175-178) are correct because they use the realised delta.","line":162,"path":"src/CrownHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {CrownHook} from \"src/CrownHook.sol\";\nimport {CrownRegistry} from \"src/CrownRegistry.sol\";\nimport {CrownToken} from \"src/CrownToken.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\n\n/// @dev Router that settles after the action, with a caller-chosen price limit.\ncontract LimitSwapper is IUnlockCallback {\n    IPoolManager public immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    function swap(PoolKey memory key, bool zeroForOne, int256 amountSpecified, uint160 limit)\n        external\n        returns (BalanceDelta)\n    {\n        return abi.decode(manager.unlock(abi.encode(uint8(0), key, SwapParams(zeroForOne, amountSpecified, limit))), (BalanceDelta));\n    }\n\n    function modifyLiquidity(PoolKey memory key, int24 lo, int24 hi, int256 liq) external returns (BalanceDelta) {\n        return abi.decode(\n            manager.unlock(abi.encode(uint8(1), key, SwapParams(false, 0, 0), lo, hi, liq)), (BalanceDelta)\n        );\n    }\n\n    function unlockCallback(bytes calldata raw) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"not manager\");\n        uint8 action = abi.decode(raw[:32], (uint8));\n        BalanceDelta delta;\n        PoolKey memory key;\n        if (action == 0) {\n            SwapParams memory p;\n            (, key, p) = abi.decode(raw, (uint8, PoolKey, SwapParams));\n            delta = manager.swap(key, p, \"\");\n        } else {\n            int24 lo;\n            int24 hi;\n            int256 liq;\n            (, key,, lo, hi, liq) = abi.decode(raw, (uint8, PoolKey, SwapParams, int24, int24, int256));\n            (delta,) = manager.modifyLiquidity(key, ModifyLiquidityParams(lo, hi, liq, 0), \"\");\n        }\n        _settle(key.currency0, delta.amount0());\n        _settle(key.currency1, delta.amount1());\n        return abi.encode(delta);\n    }\n\n    function _settle(Currency c, int128 amount) private {\n        if (amount < 0) {\n            uint256 owed = uint256(uint128(-amount));\n            manager.sync(c);\n            if (c.isAddressZero()) manager.settle{value: owed}();\n            else {\n                IERC20(Currency.unwrap(c)).transfer(address(manager), owed);\n                manager.settle();\n            }\n        } else if (amount > 0) {\n            manager.take(c, address(this), uint256(uint128(amount)));\n        }\n    }\n\n    receive() external payable {}\n}\n\ncontract PartialFillTest is Test {\n    uint160 constant SQRT_1_1 = 79228162514264337593543950336;\n    bytes32 constant CRWN = keccak256(\"CRWN\");\n    address factory = makeAddr(\"factory\");\n    address payer = makeAddr(\"payer\");\n\n    PoolManager manager;\n    CrownHook hook;\n    CrownToken token;\n    PoolKey key;\n    LimitSwapper lp;\n    LimitSwapper trader;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        address at = address((uint160(0xC0FFEE) << 100) | HookFlags.CROWN_HOOK);\n        deployCodeTo(\"CrownHook.sol:CrownHook\", abi.encode(manager, factory, payer), at);\n        hook = CrownHook(at);\n        token = new CrownToken();\n        key = PoolKey(Currency.wrap(address(0)), Currency.wrap(address(token)), 12_500, 60, IHooks(at));\n        vm.prank(factory);\n        manager.initialize(key, SQRT_1_1);\n        lp = new LimitSwapper(manager);\n        trader = new LimitSwapper(manager);\n        vm.deal(address(lp), 1_000_000 ether);\n        vm.deal(address(trader), 1_000_000 ether);\n        token.transfer(address(lp), 400_000_000 ether);\n        token.transfer(address(trader), 400_000_000 ether);\n        lp.modifyLiquidity(key, TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 1_000 ether);\n    }\n\n    /// Exact-input buy stopped early by a price limit: the hook charges 1% of the 100 ETH asked, not of what the pool took.\n    function test_exactInputBuy_partialFill_cutOnRequestedNotExecuted() public {\n        uint256 before = payer.balance;\n        uint160 limit = uint160(uint256(SQRT_1_1) * 999 / 1000);\n        BalanceDelta d = trader.swap(key, true, -100 ether, limit);\n        uint256 cut = payer.balance - before;\n        uint256 paidTotal = uint256(-int256(d.amount0()));\n        uint256 consumed = paidTotal - cut;\n        emit log_named_uint(\"consumed by pool (wei)\", consumed);\n        emit log_named_uint(\"cut (wei)\", cut);\n        // What a 1% cut on the executed amount would be, versus what was charged.\n        assertLe(cut, consumed / 100 + 1, \"cut should be ~1% of executed input\");\n    }\n\n    /// Exact-output sell stopped early: the hook takes 1% of the 100 ETH requested before the pool produces ~0.5 ETH,\n    /// so the seller's ETH delta is negative: it pays tokens AND ETH and receives nothing.\n    function test_exactOutputSell_partialFill_sellerPaysEth() public {\n        uint256 beforePayer = payer.balance;\n        uint256 beforeTrader = address(trader).balance;\n        // ~0.5 ETH of output before the limit is hit\n        uint160 limit = uint160(uint256(SQRT_1_1) * 10005 / 10000);\n        BalanceDelta d = trader.swap(key, false, int256(100 ether), limit);\n        emit log_named_int(\"trader amount0 (ETH delta)\", d.amount0());\n        emit log_named_int(\"trader amount1 (token delta)\", d.amount1());\n        emit log_named_uint(\"cut paid to king\", payer.balance - beforePayer);\n        assertGe(address(trader).balance, beforeTrader, \"a seller of tokens must not end up paying ETH\");\n    }\n}","reproduction":"Fixture: fresh PoolManager, CrownHook at flagged address, CrownToken/ETH pool at price 1:1 with 1000e18 full-range liquidity, genesis king = payer. (a) Exact-input buy: swap(zeroForOne=true, amountSpecified=-100 ether, sqrtPriceLimitX96 = SQRT_1_1*999/1000). Pool consumes 1.0137 ETH; the hook pays 1.000 ETH to the king; trader's amount0 = -2.0137 ETH. Expected cut ~0.0101 ETH (1% of executed). (b) Exact-output sell: swap(zeroForOne=false, amountSpecified=+100 ether, sqrtPriceLimitX96 = SQRT_1_1*10005/10000). Hook takes 1 ETH before the swap; pool produces 0.5002 ETH; trader's delta is amount0 = -0.50025 ETH, amount1 = -0.50633 tokens: the seller pays both currencies and receives nothing, king receives 1 ETH. Expected: the seller never has a negative ETH delta; the cut is at most 1% of what the pool actually produced. The same state is reached with no price limit when the pool's liquidity is exhausted by a request larger than the pool. Run test/scratch/PartialFill.t.sol: both tests fail on the current code. Suggested fix: compute the beforeSwap-path cut in afterSwap from the realised amount0 of the swap (via a transient flag set in beforeSwap) and refund any over-collection, or cap the exact-output cut so it never exceeds the pool's actual output; at minimum document that these two shapes must only be routed with full-fill price limits.","severity":"medium","snippet":"        uint256 amount = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n        uint256 paid = _crown(key, amount);\n        return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(paid)), 0), 0);","title":"beforeSwap charges the cut on the requested amount, not the executed one: a partially filled exact-output sell makes the seller pay ETH"},{"citation":"resolved","description":"payCrown pays the beneficiary with poolManager.take, whose native transfer hands the beneficiary up to ~90k gas while the manager is unlocked. payCrown guards only the hook's currencyDelta and getNonzeroDeltaCount (lines 230-236). PoolManager.sync is callable by anyone at any time, is not gated by the unlock, and changes neither value, so a beneficiary that calls poolManager.sync(address(0)) (or sync(someToken)) inside receive() passes both checks and the payment is kept. Any router that synced and pre-transferred its ERC-20 input before calling swap (sync -> transfer -> swap -> settle) then has its settle() credit 0 for the wrong currency: its deltas stay open and the unlock reverts with CurrencyNotSettled, so the whole swap fails. The mirror case: sync(token) during the payout makes a router that pays native with settle{value} without a preceding sync revert with NonzeroNativeValue. The brief requires that the hook never block a swap; the hook's own doc comment on payCrown claims the self-call boundary prevents a beneficiary from making the swapper's unlock revert, which is false for this path. The beneficiary is any address that wins a challenge (0.05 ETH quorum, out-stake the king), so this is reachable by an outside party, not only the owner. The Universal Router syncs inside its own SETTLE after the swap and is not affected; in-house, aggregator or pre-pay routers are.","line":232,"path":"src/CrownHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {CrownHook} from \"src/CrownHook.sol\";\nimport {CrownRegistry} from \"src/CrownRegistry.sol\";\nimport {CrownToken} from \"src/CrownToken.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\n\ncontract Rival is ERC20 {\n    constructor() ERC20(\"Crown Demo\", \"CRWN\") {\n        _mint(msg.sender, 1e24);\n    }\n}\n\n/// @dev A king beneficiary that, while being paid, resets the manager's synced currency. sync() is callable by anyone\n/// at any time and changes neither the hook's delta nor the nonzero-delta count, so payCrown's checks pass.\ncontract SyncResetter {\n    IPoolManager immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    receive() external payable {\n        manager.sync(Currency.wrap(address(0)));\n    }\n}\n\n/// @dev Router that pays its ERC-20 input up front: sync, transfer, swap, settle (all inside one unlock).\ncontract PrepaySeller is IUnlockCallback {\n    IPoolManager public immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    function sell(PoolKey memory key, uint256 amountIn) external returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(key, amountIn)), (BalanceDelta));\n    }\n\n    function unlockCallback(bytes calldata raw) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"not manager\");\n        (PoolKey memory key, uint256 amountIn) = abi.decode(raw, (PoolKey, uint256));\n        manager.sync(key.currency1);\n        IERC20(Currency.unwrap(key.currency1)).transfer(address(manager), amountIn);\n        BalanceDelta d = manager.swap(key, SwapParams(false, -int256(amountIn), TickMath.MAX_SQRT_PRICE - 1), \"\");\n        manager.settle();\n        if (d.amount0() > 0) manager.take(key.currency0, address(this), uint256(uint128(d.amount0())));\n        return abi.encode(d);\n    }\n\n    receive() external payable {}\n}\n\ncontract LpRouter is IUnlockCallback {\n    IPoolManager public immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    function add(PoolKey memory key, int256 liq) external {\n        manager.unlock(abi.encode(key, liq));\n    }\n\n    function unlockCallback(bytes calldata raw) external returns (bytes memory) {\n        (PoolKey memory key, int256 liq) = abi.decode(raw, (PoolKey, int256));\n        (BalanceDelta d,) = manager.modifyLiquidity(\n            key, ModifyLiquidityParams(TickMath.minUsableTick(60), TickMath.maxUsableTick(60), liq, 0), \"\"\n        );\n        manager.sync(key.currency0);\n        manager.settle{value: uint256(uint128(-d.amount0()))}();\n        manager.sync(key.currency1);\n        IERC20(Currency.unwrap(key.currency1)).transfer(address(manager), uint256(uint128(-d.amount1())));\n        manager.settle();\n        return \"\";\n    }\n\n    receive() external payable {}\n}\n\ncontract SyncGriefTest is Test {\n    uint160 constant SQRT_1_1 = 79228162514264337593543950336;\n    bytes32 constant CRWN = keccak256(\"CRWN\");\n    address factory = makeAddr(\"factory\");\n    address payer = makeAddr(\"payer\");\n\n    PoolManager manager;\n    CrownHook hook;\n    CrownRegistry registry;\n    CrownToken token;\n    PoolKey key;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        address at = address((uint160(0xC0FFEE) << 100) | HookFlags.CROWN_HOOK);\n        deployCodeTo(\"CrownHook.sol:CrownHook\", abi.encode(manager, factory, payer), at);\n        hook = CrownHook(at);\n        registry = hook.registry();\n        token = new CrownToken();\n        key = PoolKey(Currency.wrap(address(0)), Currency.wrap(address(token)), 12_500, 60, IHooks(at));\n        vm.prank(factory);\n        manager.initialize(key, SQRT_1_1);\n        LpRouter lp = new LpRouter(manager);\n        vm.deal(address(lp), 10_000 ether);\n        token.transfer(address(lp), 10_000 ether);\n        lp.add(key, 1_000 ether);\n    }\n\n    function _crownHostile() internal {\n        // the hostile party wins CRWN with a rival token; its beneficiary is a contract\n        SyncResetter hostile = new SyncResetter(manager);\n        vm.warp(block.timestamp + 7 days);\n        Rival rival = new Rival();\n        vm.deal(address(hostile), 1 ether);\n        vm.prank(address(hostile));\n        uint256 id = registry.openChallenge{value: 0.01 ether}(CRWN, address(rival));\n        vm.prank(address(hostile));\n        registry.stake{value: 0.05 ether}(id, CrownRegistry.Side.Challenger);\n        vm.warp(block.timestamp + 96 hours);\n        registry.finalize(id);\n        assertEq(registry.beneficiaryOf(CRWN), address(hostile));\n    }\n\n    function test_prepayRouterSell_worksWithHonestKing() public {\n        PrepaySeller r = new PrepaySeller(manager);\n        token.transfer(address(r), 10 ether);\n        BalanceDelta d = r.sell(key, 1 ether);\n        assertGt(d.amount0(), 0);\n    }\n\n    function test_prepayRouterSell_blockedByHostileKing() public {\n        _crownHostile();\n        PrepaySeller r = new PrepaySeller(manager);\n        token.transfer(address(r), 10 ether);\n        // the hook promises never to block a swap; the king's beneficiary makes this router's unlock revert\n        r.sell(key, 1 ether);\n    }\n}","reproduction":"State: CrownToken/ETH pool with the hook; a contract SyncResetter whose receive() calls poolManager.sync(Currency.wrap(address(0))) wins CRWN: warp 7 days, openChallenge{0.01 ETH}(CRWN, rivalTokenWithSymbolCRWN), stake{0.05 ETH}(id, Challenger), warp 96 h, finalize(id) -> beneficiaryOf(CRWN) == SyncResetter. Router PrepaySeller.unlockCallback does sync(token); token.transfer(manager, 1e18); swap(key, {zeroForOne:false, amountSpecified:-1e18, limit: MAX-1}); settle(); take(ETH). Call PrepaySeller.sell(key, 1 ether). Actual: the unlock reverts with CurrencyNotSettled() (the hook's afterSwap paid the cut, the beneficiary's sync(0) reset the synced currency, the router's settle credited msg.value=0 to native). Expected: the swap completes (the same call succeeds with the genesis EOA king, see test_prepayRouterSell_worksWithHonestKing). Run test/scratch/SyncGrief.t.sol. Suggested fix: in payCrown, snapshot poolManager.getSyncedCurrency() and getSyncedReserves() (TransientStateLibrary) before take and revert DeltaMismatch if either changed, so the payment falls back to the ERC-6909 claim; or pay contracts only via claims.","severity":"medium","snippet":"        poolManager.take(currency, to, amount);","title":"A hostile king beneficiary can block swaps from routers that sync before swapping: payCrown's delta checks do not cover PoolManager.sync"},{"citation":"resolved","description":"The brief states fees must never be pooled for claiming and that if the transfer fails the swap proceeds without the cut. The implementation instead mints an ERC-6909 claim in the PoolManager to the beneficiary whenever the manager's balance is short or the direct transfer fails, charges the swapper the cut anyway, and increments crownPaid[ticker][currency] by an amount that was not transferred. The launch.json notes acknowledge the deviation, so this is recorded as a requirement conflict for the judge rather than a surprise. Consequences the author should accept explicitly: crownPaid no longer 'matches the transfers' (it includes unredeemed claims); a beneficiary that cannot run setOperator + redeemClaim (a plain multisig without arbitrary-call UI, or a contract that cannot receive ETH) accumulates value it cannot pull except by ERC-6909 transfer; and the hook also enables beforeSwap/beforeSwapReturnDelta beyond the two permissions the brief lists.","line":210,"path":"src/CrownHook.sol","reproduction":"State: a king whose beneficiary is a contract whose receive() reverts (RejectingBeneficiary in test/CrownHook.t.sol), pool configured at 200 bps. trader.swap(k, true, 1 ether). Actual: swapper pays the full 1 ether plus nothing refunded; manager.balanceOf(beneficiary, 0) == 0.02 ether (claim), crownPaid == 0.02 ether, beneficiary.balance unchanged. Expected per brief: the swap proceeds without the cut, crownPaid unchanged, no claim. Existing test test_beneficiaryRejectingEther_getsClaimAndSwapProceeds asserts the implemented behaviour.","severity":"low","snippet":"            try poolManager.mint{gas: MINT_GAS}(beneficiary, currency.toId(), cut) {","title":"Transfer failure mints an ERC-6909 claim and still counts it in crownPaid, contrary to the brief's pay-at-source and skip-on-failure rules"},{"citation":"resolved","description":"afterInitialize crowns the pool's token only if the ticker has no king yet; CrownRegistry.registerKing is permissionless and first-come. The IMD factory deploys the hook and initializes the pool in one transaction, which closes the window for the launch itself. script/DeployCrown.s.sol, offered for rehearsals and additional deployments, deploys the hook (and thus a fresh public registry) in its own transaction and leaves initialization to a later call by the owner. Any observer can then deploy a token whose symbol() returns 'CRWN' and call registerKing(keccak256('CRWN'), theirToken, them). The launch pool still opens (the registerKing in afterInitialize is in a try/catch and is skipped because a beneficiary exists), but every swap pays the squatter, and the owner cannot challenge for MIN_REIGN = 7 days, then must win a bonded stake-vote to take back its own ticker. Other tickers the owner later wants to run pools for are squattable the same way at any time.","line":145,"path":"src/CrownHook.sol","reproduction":"Tx1: run DeployCrown (hook + registry deployed, no pool). Tx2 (attacker): deploy Rival ERC-20 with symbol 'CRWN'; registry.registerKing(keccak256('CRWN'), rival, attacker) succeeds (ticker had no king). Tx3 (owner): manager.initialize(ETH/CrownToken key). Actual: poolConfig.ticker == keccak256('CRWN'), registry.king(CRWN) == (rival, attacker); trader.swap(key, true, 1 ether) pays 0.01 ETH to attacker; owner's openChallenge reverts ReignTooShort for 7 days. Expected: the launch token is crowned with the owner as beneficiary. Suggested fix: have the hook register the genesis king for the configured ticker from configurePool (owner-only) or let the hook constructor pre-register the launch token, so the first registration is not a public race.","severity":"low","snippet":"        if (cfg.ticker != bytes32(0) && registry.beneficiaryOf(cfg.ticker) == address(0)) {","title":"Genesis crowning can be front-run on the manual deployment path: anyone may registerKing(CRWN) between hook deployment and pool initialization"},{"citation":"resolved","description":"When no pending configuration exists, afterInitialize derives the ticker from currency1.symbol(). Ticker.ofToken returns bytes32(0) for a token whose symbol() returns bytes32 (MKR-style), an empty string, a string longer than 64 bytes, or no code. The zero ticker is still stored with set = true, _crown returns 0 for ticker 0 (line 188) so the pool never pays a cut, and configurePool reverts PoolAlreadyLive for that pool forever. The failure is silent: afterInitialize returns its selector and emits PoolRegistered with ticker 0 instead of reverting or refusing the unconfigured pool. Reachable only by the owner or factory initializing without configurePool, so low severity, but it is a permanent misconfiguration with no recovery short of opening a new pool.","line":140,"path":"src/CrownHook.sol","reproduction":"State: a token whose symbol() returns bytes32 (abi.encode(bytes32('MKR')) -> 32 bytes, Ticker.ofToken returns 0). Owner calls manager.initialize(PoolKey(ETH, thatToken, 12500, 60, hook), SQRT_1_1) without configurePool. Actual: hook.poolConfig(id) == (bytes32(0), 100, true); swaps pay no cut (CrownSkipped is not even emitted since _crown returns at line 188); hook.configurePool(key, keccak256('MKR'), 100) reverts PoolAlreadyLive. Expected: afterInitialize reverts (refusing the pool) or leaves the pool configurable when no ticker could be derived.","severity":"low","snippet":"        if (!cfg.set) cfg = PoolConfig(Ticker.ofToken(token), DEFAULT_BPS, true);","title":"afterInitialize permanently records ticker 0 for a currency1 without a readable symbol, leaving the pool cut-less and unconfigurable"}],"hash":"cbb3d3078cc826b7d164130bcd10fda3b275390dd97137a9f882aac64de1a295","nodeId":"ce4a48d3-0921-467d-ab42-3f9bf20fdf78","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds five findings and a 24-row coverage record. The repository's own 67 tests all pass, so none of the defects below are caught by the existing suite.\n\n**Confirmed defects (each reproduced with a Foundry test under test/scratch/)**\n\n- **Medium, cut charged on the requested amount in beforeSwap.** For the two swap shapes where ETH is the specified side, `CrownHook.beforeSwap` takes 1% of `amountSpecified` before the pool runs and never reconciles against the fill. A price-limited 100 ETH exact-input buy consumed about 1.01 ETH but paid a 1 ETH cut. A price-limited exact-output sell took a 1 ETH cut while the pool produced 0.5 ETH, so the seller paid tokens and 0.5 ETH and received nothing. The same state is reached without a price limit when a request exceeds pool liquidity. Proof attached.\n- **Medium, hostile king beneficiary can block pre-pay routers.** `payCrown` guards the hook's delta and the nonzero-delta count, but a beneficiary that calls `PoolManager.sync` inside its receive passes both checks. A router that syncs and transfers its token input before swapping then reverts with `CurrencyNotSettled`. Any challenge winner can become the beneficiary, and the brief forbids the hook blocking swaps. Proof attached. Suggested fix is to snapshot the synced currency and reserves around `take` and fall back to the claim path if they change.\n- **Low, claim fallback deviates from the brief.** Transfer failures mint ERC-6909 claims and count them in `crownPaid`, instead of skipping the cut. Acknowledged in the manifest notes, recorded for the judge.\n- **Low, genesis squat on the manual deploy path.** The rehearsal script deploys the hook in one transaction and initializes later, during which anyone can `registerKing` for CRWN with a same-symbol token and collect the launch pool's fees for at least seven days.\n- **Low, silent ticker-zero pool.** Initializing without `configurePool` for a token whose `symbol()` returns bytes32 stores ticker zero with `set = true`, so the pool never pays a cut and can never be reconfigured. Confirmed by test.\n\n**What held.** The registry's challenge, stake, finalize, claim and treasury paths, the hook's afterSwap accounting and delta balancing including batched unlocks and reentrant beneficiaries, claim redemption, flag and manifest agreement, and the Ticker library bounds all traced cleanly. The frontend bundle, challenger deploy script runtime and Sourcify verifiability were not reached and are marked `unreached` in the coverage record.","treeHash":null,"usage":{"cachedInputTokens":1763244,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":58034,"runtime":"claude","turns":32,"wallClockMs":913934}},{"artifacts":[],"attempt":2,"bundleHash":"afa4acdd60020e228931ce46233babb8f47453a252566da158cdcef533d7f667","device":"0476c44a80aa9574","findings":[{"description":"openChallenge allows one live challenge per ticker and finalize starts a 3-day cooldown after every failed challenge. Anyone, including the sitting king's beneficiary, can open a sham challenge with a throwaway token that reports the ticker's symbol, stake nothing, let it fail below quorum 96 hours later, and reopen the instant the cooldown ends. Every honest challenger then has to win a same-block race at cooldown expiry, and the registry's own rules give the incumbent no cost beyond the slashed 20% of the 0.01 ETH bond (0.002 ETH per 7-day cycle, half of it to the treasury). The brief's rules are implemented exactly as written, so this cannot be asserted as a defect in a passing test; it is a design-level liveness weakness of the one-live-challenge plus cooldown scheme that the requester should decide on (for example: no cooldown when the failed challenge was unopposed, a cooldown keyed to the challenger address rather than the ticker, or an escalating bond).","line":181,"path":"src/CrownRegistry.sol","reproduction":"Registry with king K for ticker T, reign older than 7 days. Attacker A deploys token X with symbol matching T. t0: A calls openChallenge{value: 0.01 ether}(T, X); nobody stakes. t0 + 96h: anyone calls finalize -> ChallengeFailed, cooldownUntil = t0 + 96h + 3 days, A's claim returns 0.008 ETH. At cooldownUntil, A immediately calls openChallenge again (ChallengeLive before it, CoolingDown until it). Expected by an honest challenger: a window in which a real challenge can be opened. Actual: openChallenge from anyone else reverts with ChallengeLive at every moment except the single cooldown-expiry instant, which A can front-run, for a net cost to A of 0.002 ETH per 7-day cycle.","severity":"low","title":"A sham challenge every cooldown can keep a ticker unchallengeable indefinitely (liveness griefing)"}],"hash":"e3598d208c22804d1897f0757b987fcb9bbdbb3590004d56f0bc6e73a175dbc1","nodeId":"734e1c36-275a-474e-b519-330b7b5dc905","outcome":"completed","summary":"Done. The suite now has 66 tests, all passing under the verifier's fuzz seed, and the new work is committed on top of the earlier contributors' tests.\n\n**What I added** (all under `test/`):\n\n- **Registry invariants** in `test/CrownRegistry.invariant.t.sol`. A handler drives six actors over two tickers through open, stake, warp, finalize, claim, treasury retry, and a treasury that toggles between accepting and rejecting ETH. Invariants: the registry's balance equals its obligations to the wei, every wei deposited is in the registry, at the treasury, or claimed, winners are always made whole, losers never exceed 80 percent plus dust, `claimable` agrees with `claim`, outcomes match the stake-and-quorum rule, per-ticker totals match the sums, a finished challenge never reopens, and the king always reports its ticker.\n- **Hook invariants** in `test/CrownHook.invariant.t.sol`. Random swaps of all four shapes interleave with challenges, stakes, dethrones, a beneficiary that refuses ETH, and claim redemptions. Invariants: the hook never holds ETH, tokens, or claims, `crownPaid` equals transfers plus minted claims, every swap paid exactly 1 percent of its ETH leg to whoever was king at that moment, the trader's specified amount is never altered, and claims are backed by manager ETH. A scratch test confirmed the handler reaches a flip, a claim mint, and a redemption.\n- **Wei-exact math and failure paths** in `test/CrownMath.t.sol`. Fuzzed slash split with five random stakers and random claim order against expectations computed from the brief's rule, quorum one wei short versus exactly met, ties versus one extra wei, any non-bond value refused, dust stakes where the last claimer takes the exact remainder, staking during the timelock, an invalid side, a dethroned king challenging back, and a probe showing no admin selectors or stray-ETH receive.\n- **Hook edges** in `test/CrownHookEdge.t.sol` plus a batching router mock in `test/mocks/MultiSwapper.sol`. Sub-100-wei swaps pay nothing, a router doing three swaps in one unlock pays each cut, fuzzed exact-output buys and both sell shapes, an ERC-20/ERC-20 pool paying in currency0, a second pool on the launch token sharing the king without recrowning, and claim redemption refused for a non-holder or beyond what is held.\n\n**Finding reported** in `.imd-findings.json` (the file is gitignored by the repo, so it sits in the working tree). It is low severity and design-level: a sham self-challenge with a throwaway token, left unopposed and reopened the instant each 3-day cooldown ends, keeps a ticker unchallengeable for about 0.002 ETH per week. The code implements the brief's rules exactly, so I did not assert it as a defect in a test. No contract defect was found in the registry arithmetic or the hook's delta accounting.\n\n**Still owed**: no fork run against Robinhood Chain was possible offline. The integration is covered by a local PoolManager only.","treeHash":"f43d948340589261f7ecab7c50f4bdc4118f3751","usage":{"cachedInputTokens":2958788,"inputTokens":610,"model":"claude-fable-5-1","outputTokens":66526,"runtime":"claude","turns":48,"wallClockMs":1154896}},{"artifacts":[],"attempt":1,"bundleHash":"4e3a8a2090dc539cf8a0e3392c0fc89fe6cfe21aa374cc8f0b0eb694a3048403","device":"79606c6a6e3585fb","findings":[],"hash":"f17066ebcac860b64c1c72b38cef7c5a071ca380777a549b4928217758ac6cac","nodeId":"fb3ed2b0-b2f9-4643-9652-dc72c670dd60","outcome":"completed","summary":"Built the CROWN frontend and static export, with wallet connection, live reads, challenge/staking controls, refunds, and fee claims.\n\nPassed 49 browser assertions, 16 design checks, and 67 Foundry tests with both fuzz seeds. Resolved the treasury-payment Slither finding; zero High findings remain.\n\nNo deployment handoff was supplied, so live transactions await real contract addresses. Details: [VALIDATION.md](/home/seat24/.identitymd/work/7400cf72-96af-4acb-bf4b-3ca2208b3e58/fb3ed2b0-b2f9-4643-9652-dc72c670dd60/VALIDATION.md).","treeHash":"20dbfd8c681b5379cf3742ccc921a78e07dabff8","usage":{"cachedInputTokens":10829824,"inputTokens":248065,"model":"gpt-6-astra","outputTokens":87408,"runtime":"codex","turns":10,"wallClockMs":2865645}}],"verification":[{"checks":[{"durationMs":50716,"exitCode":0,"name":"build","output":"Compiling 92 files with Solc 0.8.26\nSolc 0.8.26 finished in 50.62s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to view\n   --> test/CrownRegistry.t.sol:294:5:\n    |\n294 |     function test_registerKing_setsReignAndProtection() public {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n","passed":true},{"durationMs":541,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/CrownToken.t.sol:CrownTokenTest\n[PASS] test_challengerScript_deploysDistinctSameTickerToken() (gas: 1709782)\n[PASS] test_challengerToken_rejectsZeroHolder() (gas: 4233)\n[PASS] test_deployScript_minesFlagAddress() (gas: 7202636)\n[PASS] test_launchToken_fixedSupplyToDeployer() (gas: 100279)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 41.81ms (2.70ms CPU time)\n\nRan 9 tests for test/CrownHookEdge.t.sol:CrownHookEdgeTest\n[PASS] testFuzz_buyExactOutput(uint256) (runs: 256, μ: 221732, ~: 228153)\n[PASS] testFuzz_sellExactInput(uint256) (runs: 256, μ: 217917, ~: 225712)\n[PASS] testFuzz_sellExactOutput(uint256) (runs: 256, μ: 219851, ~: 227011)\n[PASS] test_batchedRouter_paysTheCutOnEverySwapInOneUnlock() (gas: 1345765)\n[PASS] test_configurePool_canBeRevisedUntilThePoolOpens() (gas: 748348)\n[PASS] test_erc20PairPool_paysTheCutInCurrency0() (gas: 3317573)\n[PASS] test_redeemClaim_onlyTheHolderAndOnlyWhatItHolds() (gas: 2349848)\n[PASS] test_secondPoolOnTheLaunchToken_sharesTheKingAndDoesNotRecrown() (gas: 604390)\n[PASS] test_swapBelowOneHundredWei_paysNoCutAndStillSettles() (gas: 356897)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 46.19ms (128.24ms CPU time)\n\nRan 20 tests for test/CrownHook.t.sol:CrownHookTest\n[PASS] testFuzz_buyExactInput(uint256) (runs: 256, μ: 221796, ~: 229577)\n[PASS] test_afterInitialize_refusesOtherInitializers() (gas: 787508)\n[PASS] test_afterInitialize_registersPoolAndCrownsGenesisKing() (gas: 40694)\n[PASS] test_beneficiaryOpeningADebt_isUndoneAndPaidAsClaim() (gas: 2352680)\n[PASS] test_beneficiaryRejectingEther_getsClaimAndSwapProceeds() (gas: 2290141)\n[PASS] test_buyExactInput_paysOnePercentOfInput() (gas: 232052)\n[PASS] test_buyExactOutput_paysOnePercentOnTopOfInput() (gas: 228333)\n[PASS] test_callbacksRefuseNonManager() (gas: 165930)\n[PASS] test_configurePool_rules() (gas: 1719285)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 8847)\n[PASS] test_endToEnd_buyChallengeStakeFinalizeBuy() (gas: 1112661)\n[PASS] test_feeCollectWithZeroLiquidityChange_isNotBlocked() (gas: 515682)\n[PASS] test_freshManagerSeededWithTokensOnly_mintsClaimThenRedeems() (gas: 8456612)\n[PASS] test_gasBurningBeneficiary_cannotBlockTheSwap() (gas: 2351444)\n[PASS] test_permissionsMatchAddress() (gas: 10154)\n[PASS] test_redeemClaim_requiresOperatorApproval() (gas: 45594)\n[PASS] test_sellExactInput_paysOnePercentOfOutput() (gas: 225548)\n[PASS] test_sellExactOutput_paysOnePercentOfOutput() (gas: 226850)\n[PASS] test_tickerWithoutKing_skipsTheCut() (gas: 1306782)\n[PASS] test_zeroBpsPool_takesNothing() (gas: 1284506)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 46.15ms (62.85ms CPU time)\n\nRan 27 tests for test/CrownRegistry.t.sol:CrownRegistryTest\n[PASS] testFuzz_conservation(uint64[3],uint64[3]) (runs: 256, μ: 1607995, ~: 1603451)\n[PASS] test_claim_failurePaths() (gas: 808049)\n[PASS] test_claim_reentrancyCannotDoublePay() (gas: 1037966)\n[PASS] test_dethrone_slashSplitExactToTheWei() (gas: 1110700)\n[PASS] test_dustDefence_grantsNoNewProtection() (gas: 925482)\n[PASS] test_failedChallenge_belowQuorumSlashesChallengersAndCoolsDown() (gas: 1184317)\n[PASS] test_finalize_timelockBoundary() (gas: 594149)\n[PASS] test_minDefence_grantsProtection_andForfeitsASlash() (gas: 825247)\n[PASS] test_newKingMustReignSevenDays() (gas: 948110)\n[PASS] test_openChallenge_minimumReignBoundary() (gas: 326452)\n[PASS] test_openChallenge_oneLiveChallengePerTicker() (gas: 845526)\n[PASS] test_openChallenge_refusesTheSittingBeneficiary() (gas: 736893)\n[PASS] test_openChallenge_rejectsSameTokenWrongTickerAndNoKing() (gas: 657086)\n[PASS] test_openChallenge_requiresExactBond() (gas: 354136)\n[PASS] test_registerKing_crownsFirstRegistration() (gas: 20596)\n[PASS] test_registerKing_rejectsSecondRegistration() (gas: 38319)\n[PASS] test_registerKing_rejectsWrongSymbolZeroAndCodeless() (gas: 618436)\n[PASS] test_registerKing_setsReignAndProtection() (gas: 22087)\n[PASS] test_stake_windowBoundaryAndZero() (gas: 502445)\n[PASS] test_stakerOnBothSides_isPaidForBoth() (gas: 863721)\n[PASS] test_sybilSelfChallenge_withDustDefence_buysNoImmunity() (gas: 1532319)\n[PASS] test_tie_keepsTheKing() (gas: 680803)\n[PASS] test_treasuryIsolatedPayment_isSelfOnlyAndFailedRetriesPreserveCredit() (gas: 775423)\n[PASS] test_treasuryThatRejectsEther_doesNotBlockFinalize() (gas: 625361)\n[PASS] test_uncontestedFlip_grantsNoNewProtection() (gas: 743866)\n[PASS] test_unopposedChallengeAtQuorum_dethronesAndRefundsInFull() (gas: 659404)\n[PASS] test_unopposedChallengeBelowQuorum_sendsWholeSlashToTreasury() (gas: 528916)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 72.85ms (81.21ms CPU time)\n\nRan 11 tests for test/CrownRevision.t.sol:CrownRevisionTest\n[PASS] testFuzz_exactInputBuy_withLimit_neverOvercharges(uint256,int24) (runs: 256, μ: 211264, ~: 226319)\n[PASS] test_afterInitialize_refusesAPoolWhoseTickerIsHeldByAnotherToken() (gas: 1494247)\n[PASS] test_afterInitialize_refusesATokenWithoutAReadableStringSymbol() (gas: 371898)\n[PASS] test_beneficiaryResettingTheSync_isPaidAsClaimAndThePrepayRouterSettles() (gas: 1903108)\n[PASS] test_beneficiarySyncingTheToken_isPaidAsClaimAndTheNativeBuyerSettles() (gas: 1346474)\n[PASS] test_erc20Currency0_revertingBalanceOf_paysAsClaimInsteadOfBlocking() (gas: 3431880)\n[PASS] test_exactInputBuy_limitReachedExactlyAtTheRequest_isNotAPartialFill() (gas: 1527269)\n[PASS] test_exactInputBuy_partialFill_isRefused() (gas: 425577)\n[PASS] test_exactOutputSell_partialFill_isRefused() (gas: 415095)\n[PASS] test_oversizedExactInput_isRefusedOnACrownedPool() (gas: 130450)\n[PASS] test_partialFillsAndOversizedRequests_proceedWhenNothingIsCharged() (gas: 2586833)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 75.57ms (58.37ms CPU time)\n\nRan 11 tests for test/CrownMath.t.sol:CrownMathTest\n[PASS] testFuzz_quorumBoundary(uint256) (runs: 256, μ: 1230144, ~: 1233765)\n[PASS] testFuzz_slashSplit_exactToTheWei(uint96[5],uint8,uint8) (runs: 512, μ: 1445738, ~: 1444662)\n[PASS] testFuzz_tieKeepsKing_oneWeiMoreFlips(uint256) (runs: 256, μ: 1253132, ~: 1253148)\n[PASS] testFuzz_wrongBond(uint256) (runs: 256, μ: 44099, ~: 44247)\n[PASS] test_dethronedKing_canChallengeBackAfterMinReign() (gas: 1079768)\n[PASS] test_dustStakes_lastClaimerTakesExactRemainder() (gas: 1185016)\n[PASS] test_finalize_isPermissionlessAndNotRepeatable() (gas: 513127)\n[PASS] test_noAdminFunctions() (gas: 219415)\n[PASS] test_registerKing_rejectsZeroTickerAndCrownsOtherTickersIndependently() (gas: 702841)\n[PASS] test_stake_invalidSideIsRefused() (gas: 338945)\n[PASS] test_timelock_windowClosedButNotFinalized_isStillLive() (gas: 976704)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 75.68ms (183.63ms CPU time)\n\nRan 1 test for test/CrownHook.invariant.t.sol:CrownHookInvariantTest\n[PASS]\nCrownHookInvariantTest invariants:\n[PASS] invariant_claimsAreBacked\n[PASS] invariant_crownPaidMatchesReceipts\n[PASS] invariant_everyCutExact\n[PASS] invariant_hookHoldsNothing\n[PASS] invariant_kingAlwaysSet\n CrownHookInvariantTest invariants (runs: 32, calls: 1280, reverts: 0)\n\n╭-------------+---------------+-------+---------+----------╮\n| Contract    | Selector      | Calls | Reverts | Discards |\n+==========================================================+\n| HookHandler | buyExactIn    | 134   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | buyExactOut   | 131   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | challenge     | 133   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | finalize      | 108   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | redeem        | 138   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | sellExactIn   | 134   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | sellExactOut  | 123   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | stake         | 123   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | toggleRejects | 120   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | warp          | 136   | 0       | 0        |\n╰-------------+---------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 284.24ms (280.79ms CPU time)\n\nRan 1 test for test/CrownRegistry.invariant.t.sol:CrownRegistryInvariantTest\n[PASS]\nCrownRegistryInvariantTest invariants:\n[PASS] invariant_balanceEqualsObligations\n[PASS] invariant_claimRulesHold\n[PASS] invariant_ethIsConserved\n[PASS] invariant_resolutionMatchesRule\n[PASS] invariant_tickerTotalsAndKings\n[PASS] invariant_treasuryArrearsOnlyWhileRejecting\n CrownRegistryInvariantTest invariants (runs: 48, calls: 2880, reverts: 0)\n\n╭-----------------+----------------+-------+---------+----------╮\n| Contract        | Selector       | Calls | Reverts | Discards |\n+===============================================================+\n| RegistryHandler | claim          | 418   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | finalize       | 401   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | openChallenge  | 408   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | payTreasury    | 408   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | stake          | 420   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | toggleTreasury | 390   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | warp           | 435   | 0       | 0        |\n╰-----------------+----------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 427.06ms (424.47ms CPU time)\n\nRan 8 test suites in 429.99ms (1.07s CPU time): 84 tests passed, 0 failed, 0 skipped (84 total tests)\n","passed":true},{"durationMs":61,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"CrownChallengerToken.approve(address,uint256)\",\"CrownChallengerToken.transfer(address,uint256)\",\"CrownChallengerToken.transferFrom(address,address,uint256)\",\"CrownHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"CrownHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"CrownHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"CrownHook.configurePool((address,address,uint24,int24,address),bytes32,uint16)\",\"CrownHook.payCrown(address,address,uint256)\",\"CrownHook.redeemClaim(address,uint256)\",\"CrownHook.unlockCallback(bytes)\",\"CrownRegistry.claim(uint256)\",\"CrownRegistry.finalize(uint256)\",\"CrownRegistry.openChallenge(bytes32,address)\",\"CrownRegistry.payTreasury()\",\"CrownRegistry.payTreasuryIsolated()\",\"CrownRegistry.registerKing(bytes32,address,address)\",\"CrownRegistry.stake(uint256,uint8)\",\"CrownToken.approve(address,uint256)\",\"CrownToken.transfer(address,uint256)\",\"CrownToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"DESIGN.md\":83,\"README.md\":332,\"REVIEW.md\":33,\"VALIDATION.md\":175,\"dist/abi/CrownChallengerToken.json\":1,\"dist/abi/CrownHook.json\":1,\"dist/abi/CrownRegistry.json\":1,\"dist/abi/CrownToken.json\":1,\"dist/abi/PoolManager.json\":1,\"dist/assets/ccip-DosuAWIe.js\":1,\"dist/assets/dist-DCPwh1G5.js\":3,\"dist/assets/index-DK2JiM0w.css\":1,\"dist/assets/index-PcoSA_Mu.js\":47,\"dist/crown-config.json\":16,\"dist/favicon.svg\":1,\"dist/index.html\":21,\"dist/social.png\":99,\"dist/wallets/browser.svg\":1,\"dist/wallets/coinbase.svg\":1,\"dist/wallets/metamask.svg\":1,\"dist/wallets/phantom.svg\":1,\"dist/wallets/rabby.svg\":1,\"foundry.toml\":19,\"remappings.txt\":4,\"script/DeployChallenger.s.sol\":21,\"script/DeployCrown.s.sol\":49,\"site/README.md\":83,\"site/WALLET-ICONS-LICENSE.txt\":9,\"site/design/social.svg\":1,\"site/index.html\":20,\"site/package-lock.json\":1718,\"site/package.json\":16,\"site/public/abi/CrownChallengerToken.json\":1,\"site/public/abi/CrownHook.json\":1,\"site/public/abi/CrownRegistry.json\":1,\"site/public/abi/CrownToken.json\":1,\"site/public/abi/PoolManager.json\":1,\"site/public/crown-config.json\":16,\"site/public/favicon.svg\":1,\"site/public/social.png\":99,\"site/public/wallets/browser.svg\":1,\"site/public/wallets/coinbase.svg\":1,\"site/public/wallets/metamask.svg\":1,\"site/public/wallets/phantom.svg\":1,\"site/public/wallets/rabby.svg\":1,\"site/scripts/abis.mjs\":13,\"site/scripts/export.mjs\":60,\"site/scripts/social.mjs\":13,\"site/src/App.tsx\":173,\"site/src/OwnerPanel.tsx\":26,\"site/src/Transaction.tsx\":108,\"site/src/Wallet.tsx\":73,\"site/src/config.ts\":78,\"site/src/domain.ts\":74,\"site/src/main.tsx\":13,\"site/src/styles.css\":188,\"site/src/useCrown.ts\":69,\"site/tests/export.mjs\":41,\"site/tests/interaction.mjs\":285,\"site/tsconfig.json\":10,\"site/vite.config.ts\":8,\"src/CrownChallengerToken.sol\":19,\"src/CrownHook.sol\":386,\"src/CrownRegistry.sol\":363,\"src/CrownToken.sol\":15,\"src/HookFlags.sol\":34,\"src/libraries/Ticker.sol\":42,\"test/CrownHook.invariant.t.sol\":289,\"test/CrownHook.t.sol\":356,\"test/CrownHookEdge.t.sol\":207,\"test/CrownMath.t.sol\":325,\"test/CrownRegistry.invariant.t.sol\":319,\"test/CrownRegistry.t.sol\":509,\"test/CrownRevision.t.sol\":320,\"test/CrownToken.t.sol\":67,\"test/floor/OwnerChecks.sol\":23,\"test/mocks/MockERC20.sol\":11,\"test/mocks/MultiSwapper.sol\":65,\"test/mocks/Swapper.sol\":84,\"test/utils/CrownFixture.sol\":64},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":3017,"exitCode":0,"name":"slither","output":"[medium/medium] uninitialized-local at src/CrownHook.sol:233: CrownHook._crown(PoolKey,uint256).beneficiary (src/CrownHook.sol#233) is a local variable never initialized\n[medium/medium] uninitialized-local at src/CrownHook.sol:244: CrownHook._crown(PoolKey,uint256).paid (src/CrownHook.sol#244) is a local variable never initialized\n[medium/medium] uninitialized-local at src/CrownHook.sol:243: CrownHook._crown(PoolKey,uint256).asClaim (src/CrownHook.sol#243) is a local variable never initialized\n[medium/medium] unused-return at src/CrownHook.sol:312: CrownHook.redeemClaim(Currency,uint256) (src/CrownHook.sol#312-314) ignores return value by poolManager.unlock(abi.encode(msg.sender,currency,amount)) (src/CrownHook.sol#313)\n[medium/medium] unused-return at src/CrownHook.sol:146: CrownHook.afterInitialize(address,PoolKey,uint160,int24) (src/CrownHook.sol#146-176) ignores return value by (kingToken,None,None) = registry.king(cfg.ticker) (src/CrownHook.sol#171)\n[low/medium] reentrancy-benign at src/CrownHook.sol:226: Reentrancy in CrownHook._crown(PoolKey,uint256) (src/CrownHook.sol#226-263):\n[low/medium] reentrancy-events at src/CrownRegistry.sol:324: Reentrancy in CrownRegistry.payTreasuryIsolated() (src/CrownRegistry.sol#324-331):\n[low/medium] reentrancy-events at src/CrownHook.sol:226: Reentrancy in CrownHook._crown(PoolKey,uint256) (src/CrownHook.sol#226-263):\n[low/medium] reentrancy-events at src/CrownHook.sol:146: Reentrancy in CrownHook.afterInitialize(address,PoolKey,uint160,int24) (src/CrownHook.sol#146-176):\n[low/medium] return-bomb at src/CrownHook.sol:267: CrownHook._managerHolds(Currency,uint256) (src/CrownHook.sol#267-273) tries to limit the gas of an external call that controls implicit decoding\n[low/medium] timestamp at src/CrownRegistry.sol:225: CrownRegistry.stake(uint256,CrownRegistry.Side) (src/CrownRegistry.sol#225-230) uses timestamp for comparisons\n[low/medium] timestamp at src/CrownRegistry.sol:182: CrownRegistry.registerKing(bytes32,address,address) (src/CrownRegistry.sol#182-188) uses timestamp for comparisons\n[low/medium] timestamp at src/CrownRegistry.sol:194: CrownRegistry.openChallenge(bytes32,address) (src/CrownRegistry.sol#194-222) uses timestamp for comparisons\n[low/medium] timestamp at src/CrownRegistry.sol:240: CrownRegistry.finalize(uint256) (src/CrownRegistry.sol#240-289) uses timestamp for comparisons","passed":true},{"durationMs":402,"exitCode":0,"name":"aderyn","output":"[high] eth-send-unchecked-address at src/CrownRegistry.sol:292: ETH transferred without address checks\n[low] internal-function-used-once at src/HookFlags.sol:27: Internal Function Used Only Once (2 places)\n[low] large-numeric-literal at src/CrownChallengerToken.sol:11: Large Numeric Literal (9 places)\n[low] literal-instead-of-constant at src/libraries/Ticker.sol:25: Literal Instead of Constant (3 places)\n[low] local-variable-shadowing at src/CrownRegistry.sol:49: Local Variable Shadows State Variable\n[low] state-change-without-event at src/CrownRegistry.sol:318: State Change Without Event\n[low] unchecked-return at src/CrownHook.sol:313: Unchecked Return\n[low] unused-public-function at src/CrownHook.sol:110: Public Function Not Used Internally\n[low] unused-state-variable at src/CrownHook.sol:43: Unused State Variable (4 places)","passed":true},{"durationMs":6510,"exitCode":0,"name":"proof 641d732fd538","output":"Compiling 82 files with Solc 0.8.26\nSolc 0.8.26 finished in 5.91s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-5669a016/Proof_641d732fd538.t.sol:SelfChallengeSybilTest\n[PASS] test_selfChallengeFromSecondWallet_withOneWeiDefence_restartsReignForFree() (gas: 990958)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 894.03µs (208.90µs CPU time)\n\nRan 1 test suite in 3.75ms (894.03µs CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"1dd9ebc144b163265c4f0f8be09a78b06d37d813cc701884d89d4924bc550410","verifiedTreeHash":"df9132bd083d1b57feb84664b7575e766a5bf471","verifierVersion":"0.1.0+8fbf00a5"},{"checks":[{"durationMs":50635,"exitCode":0,"name":"build","output":"Compiling 92 files with Solc 0.8.26\nSolc 0.8.26 finished in 50.54s\nCompiler run successful!\n","passed":true},{"durationMs":529,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 11 tests for test/CrownRevision.t.sol:CrownRevisionTest\n[PASS] testFuzz_exactInputBuy_withLimit_neverOvercharges(uint256,int24) (runs: 256, μ: 211264, ~: 226333)\n[PASS] test_afterInitialize_refusesAPoolWhoseTickerIsHeldByAnotherToken() (gas: 1471978)\n[PASS] test_afterInitialize_refusesATokenWithoutAReadableStringSymbol() (gas: 371898)\n[PASS] test_beneficiaryResettingTheSync_isPaidAsClaimAndThePrepayRouterSettles() (gas: 1900959)\n[PASS] test_beneficiarySyncingTheToken_isPaidAsClaimAndTheNativeBuyerSettles() (gas: 1344325)\n[PASS] test_erc20Currency0_revertingBalanceOf_paysAsClaimInsteadOfBlocking() (gas: 3409611)\n[PASS] test_exactInputBuy_limitReachedExactlyAtTheRequest_isNotAPartialFill() (gas: 1527225)\n[PASS] test_exactInputBuy_partialFill_isRefused() (gas: 425577)\n[PASS] test_exactOutputSell_partialFill_isRefused() (gas: 415095)\n[PASS] test_oversizedExactInput_isRefusedOnACrownedPool() (gas: 130450)\n[PASS] test_partialFillsAndOversizedRequests_proceedWhenNothingIsCharged() (gas: 2586789)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 38.22ms (47.15ms CPU time)\n\nRan 9 tests for test/CrownHookEdge.t.sol:CrownHookEdgeTest\n[PASS] testFuzz_buyExactOutput(uint256) (runs: 256, μ: 222048, ~: 228143)\n[PASS] testFuzz_sellExactInput(uint256) (runs: 256, μ: 217916, ~: 225678)\n[PASS] testFuzz_sellExactOutput(uint256) (runs: 256, μ: 219850, ~: 227000)\n[PASS] test_batchedRouter_paysTheCutOnEverySwapInOneUnlock() (gas: 1345765)\n[PASS] test_configurePool_canBeRevisedUntilThePoolOpens() (gas: 748348)\n[PASS] test_erc20PairPool_paysTheCutInCurrency0() (gas: 3295304)\n[PASS] test_redeemClaim_onlyTheHolderAndOnlyWhatItHolds() (gas: 2327579)\n[PASS] test_secondPoolOnTheLaunchToken_sharesTheKingAndDoesNotRecrown() (gas: 604390)\n[PASS] test_swapBelowOneHundredWei_paysNoCutAndStillSettles() (gas: 356897)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 55.99ms (123.50ms CPU time)\n\nRan 4 tests for test/CrownToken.t.sol:CrownTokenTest\n[PASS] test_challengerScript_deploysDistinctSameTickerToken() (gas: 1685367)\n[PASS] test_challengerToken_rejectsZeroHolder() (gas: 4233)\n[PASS] test_deployScript_minesFlagAddress() (gas: 8420680)\n[PASS] test_launchToken_fixedSupplyToDeployer() (gas: 100279)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 56.48ms (12.40ms CPU time)\n\nRan 20 tests for test/CrownHook.t.sol:CrownHookTest\n[PASS] testFuzz_buyExactInput(uint256) (runs: 256, μ: 221791, ~: 229569)\n[PASS] test_afterInitialize_refusesOtherInitializers() (gas: 765239)\n[PASS] test_afterInitialize_registersPoolAndCrownsGenesisKing() (gas: 40694)\n[PASS] test_beneficiaryOpeningADebt_isUndoneAndPaidAsClaim() (gas: 2330411)\n[PASS] test_beneficiaryRejectingEther_getsClaimAndSwapProceeds() (gas: 2267872)\n[PASS] test_buyExactInput_paysOnePercentOfInput() (gas: 232052)\n[PASS] test_buyExactOutput_paysOnePercentOnTopOfInput() (gas: 228333)\n[PASS] test_callbacksRefuseNonManager() (gas: 165930)\n[PASS] test_configurePool_rules() (gas: 1719285)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 8847)\n[PASS] test_endToEnd_buyChallengeStakeFinalizeBuy() (gas: 1105542)\n[PASS] test_feeCollectWithZeroLiquidityChange_isNotBlocked() (gas: 515682)\n[PASS] test_freshManagerSeededWithTokensOnly_mintsClaimThenRedeems() (gas: 8392569)\n[PASS] test_gasBurningBeneficiary_cannotBlockTheSwap() (gas: 2329175)\n[PASS] test_permissionsMatchAddress() (gas: 10154)\n[PASS] test_redeemClaim_requiresOperatorApproval() (gas: 45594)\n[PASS] test_sellExactInput_paysOnePercentOfOutput() (gas: 225548)\n[PASS] test_sellExactOutput_paysOnePercentOfOutput() (gas: 226850)\n[PASS] test_tickerWithoutKing_skipsTheCut() (gas: 1306738)\n[PASS] test_zeroBpsPool_takesNothing() (gas: 1284506)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 56.46ms (54.13ms CPU time)\n\nRan 23 tests for test/CrownRegistry.t.sol:CrownRegistryTest\n[PASS] testFuzz_conservation(uint64[3],uint64[3]) (runs: 256, μ: 1604694, ~: 1600527)\n[PASS] test_claim_failurePaths() (gas: 805841)\n[PASS] test_claim_reentrancyCannotDoublePay() (gas: 1035723)\n[PASS] test_dethrone_slashSplitExactToTheWei() (gas: 1103458)\n[PASS] test_failedChallenge_belowQuorumSlashesChallengersAndCoolsDown() (gas: 1177816)\n[PASS] test_finalize_timelockBoundary() (gas: 592012)\n[PASS] test_newKingMustReignSevenDays() (gas: 936787)\n[PASS] test_openChallenge_minimumReignBoundary() (gas: 322226)\n[PASS] test_openChallenge_oneLiveChallengePerTicker() (gas: 843388)\n[PASS] test_openChallenge_refusesTheSittingBeneficiary() (gas: 732628)\n[PASS] test_openChallenge_rejectsSameTokenWrongTickerAndNoKing() (gas: 652865)\n[PASS] test_openChallenge_requiresExactBond() (gas: 351902)\n[PASS] test_registerKing_crownsFirstRegistration() (gas: 20554)\n[PASS] test_registerKing_rejectsSecondRegistration() (gas: 38275)\n[PASS] test_registerKing_rejectsWrongSymbolZeroAndCodeless() (gas: 618282)\n[PASS] test_stake_windowBoundaryAndZero() (gas: 500255)\n[PASS] test_stakerOnBothSides_isPaidForBoth() (gas: 856457)\n[PASS] test_tie_keepsTheKing() (gas: 678613)\n[PASS] test_treasuryIsolatedPayment_isSelfOnlyAndFailedRetriesPreserveCredit() (gas: 772798)\n[PASS] test_treasuryThatRejectsEther_doesNotBlockFinalize() (gas: 622957)\n[PASS] test_uncontestedFlip_doesNotRestartTheReignClock() (gas: 735094)\n[PASS] test_unopposedChallengeAtQuorum_dethronesAndRefundsInFull() (gas: 657121)\n[PASS] test_unopposedChallengeBelowQuorum_sendsWholeSlashToTreasury() (gas: 526663)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 67.84ms (75.09ms CPU time)\n\nRan 11 tests for test/CrownMath.t.sol:CrownMathTest\n[PASS] testFuzz_quorumBoundary(uint256) (runs: 256, μ: 1224816, ~: 1229533)\n[PASS] testFuzz_slashSplit_exactToTheWei(uint96[5],uint8,uint8) (runs: 512, μ: 1443671, ~: 1442302)\n[PASS] testFuzz_tieKeepsKing_oneWeiMoreFlips(uint256) (runs: 256, μ: 1244783, ~: 1244798)\n[PASS] testFuzz_wrongBond(uint256) (runs: 256, μ: 44051, ~: 44049)\n[PASS] test_dethronedKing_canChallengeBackAfterMinReign() (gas: 1066282)\n[PASS] test_dustStakes_lastClaimerTakesExactRemainder() (gas: 1182923)\n[PASS] test_finalize_isPermissionlessAndNotRepeatable() (gas: 510968)\n[PASS] test_noAdminFunctions() (gas: 219063)\n[PASS] test_registerKing_rejectsZeroTickerAndCrownsOtherTickersIndependently() (gas: 680484)\n[PASS] test_stake_invalidSideIsRefused() (gas: 336799)\n[PASS] test_timelock_windowClosedButNotFinalized_isStillLive() (gas: 974544)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 71.55ms (165.01ms CPU time)\n\nRan 1 test for test/CrownHook.invariant.t.sol:CrownHookInvariantTest\n[PASS]\nCrownHookInvariantTest invariants:\n[PASS] invariant_claimsAreBacked\n[PASS] invariant_crownPaidMatchesReceipts\n[PASS] invariant_everyCutExact\n[PASS] invariant_hookHoldsNothing\n[PASS] invariant_kingAlwaysSet\n CrownHookInvariantTest invariants (runs: 32, calls: 1280, reverts: 0)\n\n╭-------------+---------------+-------+---------+----------╮\n| Contract    | Selector      | Calls | Reverts | Discards |\n+==========================================================+\n| HookHandler | buyExactIn    | 134   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | buyExactOut   | 131   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | challenge     | 133   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | finalize      | 108   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | redeem        | 138   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | sellExactIn   | 134   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | sellExactOut  | 123   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | stake         | 123   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | toggleRejects | 120   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | warp          | 136   | 0       | 0        |\n╰-------------+---------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 280.38ms (272.50ms CPU time)\n\nRan 1 test for test/CrownRegistry.invariant.t.sol:CrownRegistryInvariantTest\n[PASS]\nCrownRegistryInvariantTest invariants:\n[PASS] invariant_balanceEqualsObligations\n[PASS] invariant_claimRulesHold\n[PASS] invariant_ethIsConserved\n[PASS] invariant_resolutionMatchesRule\n[PASS] invariant_tickerTotalsAndKings\n[PASS] invariant_treasuryArrearsOnlyWhileRejecting\n CrownRegistryInvariantTest invariants (runs: 48, calls: 2880, reverts: 0)\n\n╭-----------------+----------------+-------+---------+----------╮\n| Contract        | Selector       | Calls | Reverts | Discards |\n+===============================================================+\n| RegistryHandler | claim          | 418   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | finalize       | 401   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | openChallenge  | 408   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | payTreasury    | 408   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | stake          | 420   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | toggleTreasury | 390   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | warp           | 435   | 0       | 0        |\n╰-----------------+----------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 423.49ms (419.45ms CPU time)\n\nRan 8 test suites in 426.14ms (1.05s CPU time): 80 tests passed, 0 failed, 0 skipped (80 total tests)\n","passed":true},{"durationMs":70,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"CrownChallengerToken.approve(address,uint256)\",\"CrownChallengerToken.transfer(address,uint256)\",\"CrownChallengerToken.transferFrom(address,address,uint256)\",\"CrownHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"CrownHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"CrownHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"CrownHook.configurePool((address,address,uint24,int24,address),bytes32,uint16)\",\"CrownHook.payCrown(address,address,uint256)\",\"CrownHook.redeemClaim(address,uint256)\",\"CrownHook.unlockCallback(bytes)\",\"CrownRegistry.claim(uint256)\",\"CrownRegistry.finalize(uint256)\",\"CrownRegistry.openChallenge(bytes32,address)\",\"CrownRegistry.payTreasury()\",\"CrownRegistry.payTreasuryIsolated()\",\"CrownRegistry.registerKing(bytes32,address,address)\",\"CrownRegistry.stake(uint256,uint8)\",\"CrownToken.approve(address,uint256)\",\"CrownToken.transfer(address,uint256)\",\"CrownToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"DESIGN.md\":83,\"README.md\":318,\"REVIEW.md\":33,\"VALIDATION.md\":147,\"dist/abi/CrownChallengerToken.json\":1,\"dist/abi/CrownHook.json\":1,\"dist/abi/CrownRegistry.json\":1,\"dist/abi/CrownToken.json\":1,\"dist/abi/PoolManager.json\":1,\"dist/assets/ccip-DHQZXRDH.js\":1,\"dist/assets/dist-D4rI3O-X.js\":3,\"dist/assets/index-BsIC3gU9.js\":47,\"dist/assets/index-DK2JiM0w.css\":1,\"dist/crown-config.json\":16,\"dist/favicon.svg\":1,\"dist/index.html\":21,\"dist/social.png\":99,\"dist/wallets/browser.svg\":1,\"dist/wallets/coinbase.svg\":1,\"dist/wallets/metamask.svg\":1,\"dist/wallets/phantom.svg\":1,\"dist/wallets/rabby.svg\":1,\"foundry.toml\":19,\"launch.json\":31,\"remappings.txt\":4,\"script/DeployChallenger.s.sol\":21,\"script/DeployCrown.s.sol\":49,\"site/README.md\":83,\"site/WALLET-ICONS-LICENSE.txt\":9,\"site/design/social.svg\":1,\"site/index.html\":20,\"site/package-lock.json\":1718,\"site/package.json\":16,\"site/public/abi/CrownChallengerToken.json\":1,\"site/public/abi/CrownHook.json\":1,\"site/public/abi/CrownRegistry.json\":1,\"site/public/abi/CrownToken.json\":1,\"site/public/abi/PoolManager.json\":1,\"site/public/crown-config.json\":16,\"site/public/favicon.svg\":1,\"site/public/social.png\":99,\"site/public/wallets/browser.svg\":1,\"site/public/wallets/coinbase.svg\":1,\"site/public/wallets/metamask.svg\":1,\"site/public/wallets/phantom.svg\":1,\"site/public/wallets/rabby.svg\":1,\"site/scripts/abis.mjs\":13,\"site/scripts/export.mjs\":60,\"site/scripts/social.mjs\":13,\"site/src/App.tsx\":173,\"site/src/OwnerPanel.tsx\":26,\"site/src/Transaction.tsx\":108,\"site/src/Wallet.tsx\":73,\"site/src/config.ts\":78,\"site/src/domain.ts\":73,\"site/src/main.tsx\":13,\"site/src/styles.css\":188,\"site/src/useCrown.ts\":69,\"site/tests/export.mjs\":41,\"site/tests/interaction.mjs\":284,\"site/tsconfig.json\":10,\"site/vite.config.ts\":8,\"src/CrownChallengerToken.sol\":19,\"src/CrownHook.sol\":386,\"src/CrownRegistry.sol\":346,\"src/CrownToken.sol\":15,\"src/HookFlags.sol\":34,\"src/libraries/Ticker.sol\":42,\"test/CrownHook.invariant.t.sol\":289,\"test/CrownHook.t.sol\":356,\"test/CrownHookEdge.t.sol\":207,\"test/CrownMath.t.sol\":325,\"test/CrownRegistry.invariant.t.sol\":319,\"test/CrownRegistry.t.sol\":438,\"test/CrownRevision.t.sol\":320,\"test/CrownToken.t.sol\":67,\"test/floor/OwnerChecks.sol\":23,\"test/mocks/MockERC20.sol\":11,\"test/mocks/MultiSwapper.sol\":65,\"test/mocks/Swapper.sol\":84,\"test/utils/CrownFixture.sol\":64},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"34252beeee3b44aefc7960f06ee0dd4c7f259e4c98e750bc45cdec8a4a51553c","verifiedTreeHash":"2858f4553475c116fc9394febea90d47272208d9","verifierVersion":"0.1.0+8fbf00a5"},{"checks":[{"durationMs":41806,"exitCode":0,"name":"build","output":"Compiling 91 files with Solc 0.8.26\nSolc 0.8.26 finished in 41.70s\nCompiler run successful!\n","passed":true},{"durationMs":577,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 20 tests for test/CrownHook.t.sol:CrownHookTest\n[PASS] testFuzz_buyExactInput(uint256) (runs: 256, μ: 215754, ~: 223326)\n[PASS] test_afterInitialize_refusesOtherInitializers() (gas: 763966)\n[PASS] test_afterInitialize_registersPoolAndCrownsGenesisKing() (gas: 40694)\n[PASS] test_beneficiaryOpeningADebt_isUndoneAndPaidAsClaim() (gas: 2326904)\n[PASS] test_beneficiaryRejectingEther_getsClaimAndSwapProceeds() (gas: 2264409)\n[PASS] test_buyExactInput_paysOnePercentOfInput() (gas: 225781)\n[PASS] test_buyExactOutput_paysOnePercentOnTopOfInput() (gas: 222687)\n[PASS] test_callbacksRefuseNonManager() (gas: 165934)\n[PASS] test_configurePool_rules() (gas: 1712971)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 8847)\n[PASS] test_endToEnd_buyChallengeStakeFinalizeBuy() (gas: 1092723)\n[PASS] test_feeCollectWithZeroLiquidityChange_isNotBlocked() (gas: 503721)\n[PASS] test_freshManagerSeededWithTokensOnly_mintsClaimThenRedeems() (gas: 8321500)\n[PASS] test_gasBurningBeneficiary_cannotBlockTheSwap() (gas: 2328354)\n[PASS] test_permissionsMatchAddress() (gas: 10154)\n[PASS] test_redeemClaim_requiresOperatorApproval() (gas: 45594)\n[PASS] test_sellExactInput_paysOnePercentOfOutput() (gas: 219880)\n[PASS] test_sellExactOutput_paysOnePercentOfOutput() (gas: 220606)\n[PASS] test_tickerWithoutKing_skipsTheCut() (gas: 1306246)\n[PASS] test_zeroBpsPool_takesNothing() (gas: 1283979)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 61.16ms (42.62ms CPU time)\n\nRan 9 tests for test/CrownHookEdge.t.sol:CrownHookEdgeTest\n[PASS] testFuzz_buyExactOutput(uint256) (runs: 256, μ: 216851, ~: 222549)\n[PASS] testFuzz_sellExactInput(uint256) (runs: 256, μ: 212201, ~: 220100)\n[PASS] testFuzz_sellExactOutput(uint256) (runs: 256, μ: 213838, ~: 220798)\n[PASS] test_batchedRouter_paysTheCutOnEverySwapInOneUnlock() (gas: 1327693)\n[PASS] test_configurePool_canBeRevisedUntilThePoolOpens() (gas: 748327)\n[PASS] test_erc20PairPool_paysTheCutInCurrency0() (gas: 3282740)\n[PASS] test_redeemClaim_onlyTheHolderAndOnlyWhatItHolds() (gas: 2324116)\n[PASS] test_secondPoolOnTheLaunchToken_sharesTheKingAndDoesNotRecrown() (gas: 594819)\n[PASS] test_swapBelowOneHundredWei_paysNoCutAndStillSettles() (gas: 350165)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 64.64ms (133.11ms CPU time)\n\nRan 21 tests for test/CrownRegistry.t.sol:CrownRegistryTest\n[PASS] testFuzz_conservation(uint64[3],uint64[3]) (runs: 256, μ: 1605410, ~: 1600720)\n[PASS] test_claim_failurePaths() (gas: 805772)\n[PASS] test_claim_reentrancyCannotDoublePay() (gas: 1035676)\n[PASS] test_dethrone_slashSplitExactToTheWei() (gas: 1103005)\n[PASS] test_failedChallenge_belowQuorumSlashesChallengersAndCoolsDown() (gas: 1177631)\n[PASS] test_finalize_timelockBoundary() (gas: 591921)\n[PASS] test_newKingMustReignSevenDays() (gas: 732817)\n[PASS] test_openChallenge_minimumReignBoundary() (gas: 322088)\n[PASS] test_openChallenge_oneLiveChallengePerTicker() (gas: 841188)\n[PASS] test_openChallenge_rejectsSameTokenWrongTickerAndNoKing() (gas: 652744)\n[PASS] test_openChallenge_requiresExactBond() (gas: 351811)\n[PASS] test_registerKing_crownsFirstRegistration() (gas: 20532)\n[PASS] test_registerKing_rejectsSecondRegistration() (gas: 38231)\n[PASS] test_registerKing_rejectsWrongSymbolZeroAndCodeless() (gas: 618238)\n[PASS] test_stake_windowBoundaryAndZero() (gas: 500164)\n[PASS] test_stakerOnBothSides_isPaidForBoth() (gas: 856136)\n[PASS] test_tie_keepsTheKing() (gas: 678566)\n[PASS] test_treasuryIsolatedPayment_isSelfOnlyAndFailedRetriesPreserveCredit() (gas: 772707)\n[PASS] test_treasuryThatRejectsEther_doesNotBlockFinalize() (gas: 622866)\n[PASS] test_unopposedChallengeAtQuorum_dethronesAndRefundsInFull() (gas: 657058)\n[PASS] test_unopposedChallengeBelowQuorum_sendsWholeSlashToTreasury() (gas: 526572)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 70.65ms (72.98ms CPU time)\n\nRan 4 tests for test/CrownToken.t.sol:CrownTokenTest\n[PASS] test_challengerScript_deploysDistinctSameTickerToken() (gas: 1683593)\n[PASS] test_challengerToken_rejectsZeroHolder() (gas: 4233)\n[PASS] test_deployScript_minesFlagAddress() (gas: 6557397)\n[PASS] test_launchToken_fixedSupplyToDeployer() (gas: 100279)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 79.51ms (3.52ms CPU time)\n\nRan 11 tests for test/CrownMath.t.sol:CrownMathTest\n[PASS] testFuzz_quorumBoundary(uint256) (runs: 256, μ: 1224546, ~: 1229329)\n[PASS] testFuzz_slashSplit_exactToTheWei(uint96[5],uint8,uint8) (runs: 512, μ: 1443482, ~: 1442255)\n[PASS] testFuzz_tieKeepsKing_oneWeiMoreFlips(uint256) (runs: 256, μ: 1244503, ~: 1244520)\n[PASS] testFuzz_wrongBond(uint256) (runs: 256, μ: 44056, ~: 44203)\n[PASS] test_dethronedKing_canChallengeBackAfterMinReign() (gas: 799477)\n[PASS] test_dustStakes_lastClaimerTakesExactRemainder() (gas: 1182646)\n[PASS] test_finalize_isPermissionlessAndNotRepeatable() (gas: 510921)\n[PASS] test_noAdminFunctions() (gas: 219063)\n[PASS] test_registerKing_rejectsZeroTickerAndCrownsOtherTickersIndependently() (gas: 680484)\n[PASS] test_stake_invalidSideIsRefused() (gas: 336752)\n[PASS] test_timelock_windowClosedButNotFinalized_isStillLive() (gas: 972344)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 79.56ms (169.52ms CPU time)\n\nRan 1 test for test/CrownHook.invariant.t.sol:CrownHookInvariantTest\n[PASS]\nCrownHookInvariantTest invariants:\n[PASS] invariant_claimsAreBacked\n[PASS] invariant_crownPaidMatchesReceipts\n[PASS] invariant_everyCutExact\n[PASS] invariant_hookHoldsNothing\n[PASS] invariant_kingAlwaysSet\n CrownHookInvariantTest invariants (runs: 32, calls: 1280, reverts: 0)\n\n╭-------------+---------------+-------+---------+----------╮\n| Contract    | Selector      | Calls | Reverts | Discards |\n+==========================================================+\n| HookHandler | buyExactIn    | 134   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | buyExactOut   | 131   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | challenge     | 133   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | finalize      | 108   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | redeem        | 138   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | sellExactIn   | 134   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | sellExactOut  | 123   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | stake         | 123   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | toggleRejects | 120   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | warp          | 136   | 0       | 0        |\n╰-------------+---------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 264.53ms (261.40ms CPU time)\n\nRan 1 test for test/CrownRegistry.invariant.t.sol:CrownRegistryInvariantTest\n[PASS]\nCrownRegistryInvariantTest invariants:\n[PASS] invariant_balanceEqualsObligations\n[PASS] invariant_claimRulesHold\n[PASS] invariant_ethIsConserved\n[PASS] invariant_resolutionMatchesRule\n[PASS] invariant_tickerTotalsAndKings\n[PASS] invariant_treasuryArrearsOnlyWhileRejecting\n CrownRegistryInvariantTest invariants (runs: 48, calls: 2880, reverts: 0)\n\n╭-----------------+----------------+-------+---------+----------╮\n| Contract        | Selector       | Calls | Reverts | Discards |\n+===============================================================+\n| RegistryHandler | claim          | 418   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | finalize       | 401   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | openChallenge  | 408   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | payTreasury    | 408   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | stake          | 420   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | toggleTreasury | 390   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | warp           | 435   | 0       | 0        |\n╰-----------------+----------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 464.73ms (462.11ms CPU time)\n\nRan 7 test suites in 467.59ms (1.08s CPU time): 67 tests passed, 0 failed, 0 skipped (67 total tests)\n","passed":true},{"durationMs":77,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"CrownChallengerToken.approve(address,uint256)\",\"CrownChallengerToken.transfer(address,uint256)\",\"CrownChallengerToken.transferFrom(address,address,uint256)\",\"CrownHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"CrownHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"CrownHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"CrownHook.configurePool((address,address,uint24,int24,address),bytes32,uint16)\",\"CrownHook.payCrown(address,address,uint256)\",\"CrownHook.redeemClaim(address,uint256)\",\"CrownHook.unlockCallback(bytes)\",\"CrownRegistry.claim(uint256)\",\"CrownRegistry.finalize(uint256)\",\"CrownRegistry.openChallenge(bytes32,address)\",\"CrownRegistry.payTreasury()\",\"CrownRegistry.payTreasuryIsolated()\",\"CrownRegistry.registerKing(bytes32,address,address)\",\"CrownRegistry.stake(uint256,uint8)\",\"CrownToken.approve(address,uint256)\",\"CrownToken.transfer(address,uint256)\",\"CrownToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"DESIGN.md\":83,\"README.md\":273,\"REVIEW.md\":31,\"VALIDATION.md\":116,\"dist/abi/CrownChallengerToken.json\":1,\"dist/abi/CrownHook.json\":1,\"dist/abi/CrownRegistry.json\":1,\"dist/abi/CrownToken.json\":1,\"dist/abi/PoolManager.json\":1,\"dist/assets/ccip-uVUi-UK6.js\":1,\"dist/assets/dist-iVAoPyTN.js\":3,\"dist/assets/index-DK2JiM0w.css\":1,\"dist/assets/index-pUUlmf3L.js\":47,\"dist/crown-config.json\":16,\"dist/favicon.svg\":1,\"dist/index.html\":21,\"dist/social.png\":99,\"dist/wallets/browser.svg\":1,\"dist/wallets/coinbase.svg\":1,\"dist/wallets/metamask.svg\":1,\"dist/wallets/phantom.svg\":1,\"dist/wallets/rabby.svg\":1,\"foundry.toml\":19,\"launch.json\":31,\"remappings.txt\":4,\"script/DeployChallenger.s.sol\":21,\"script/DeployCrown.s.sol\":49,\"site/README.md\":83,\"site/WALLET-ICONS-LICENSE.txt\":9,\"site/design/social.svg\":1,\"site/index.html\":20,\"site/package-lock.json\":1718,\"site/package.json\":16,\"site/public/abi/CrownChallengerToken.json\":1,\"site/public/abi/CrownHook.json\":1,\"site/public/abi/CrownRegistry.json\":1,\"site/public/abi/CrownToken.json\":1,\"site/public/abi/PoolManager.json\":1,\"site/public/crown-config.json\":16,\"site/public/favicon.svg\":1,\"site/public/social.png\":99,\"site/public/wallets/browser.svg\":1,\"site/public/wallets/coinbase.svg\":1,\"site/public/wallets/metamask.svg\":1,\"site/public/wallets/phantom.svg\":1,\"site/public/wallets/rabby.svg\":1,\"site/scripts/abis.mjs\":13,\"site/scripts/export.mjs\":60,\"site/scripts/social.mjs\":13,\"site/src/App.tsx\":173,\"site/src/OwnerPanel.tsx\":26,\"site/src/Transaction.tsx\":108,\"site/src/Wallet.tsx\":73,\"site/src/config.ts\":78,\"site/src/domain.ts\":70,\"site/src/main.tsx\":13,\"site/src/styles.css\":188,\"site/src/useCrown.ts\":69,\"site/tests/export.mjs\":41,\"site/tests/interaction.mjs\":281,\"site/tsconfig.json\":10,\"site/vite.config.ts\":8,\"src/CrownChallengerToken.sol\":19,\"src/CrownHook.sol\":315,\"src/CrownRegistry.sol\":340,\"src/CrownToken.sol\":15,\"src/HookFlags.sol\":34,\"src/libraries/Ticker.sol\":42,\"test/CrownHook.invariant.t.sol\":289,\"test/CrownHook.t.sol\":356,\"test/CrownHookEdge.t.sol\":207,\"test/CrownMath.t.sol\":322,\"test/CrownRegistry.invariant.t.sol\":319,\"test/CrownRegistry.t.sol\":405,\"test/CrownToken.t.sol\":65,\"test/floor/OwnerChecks.sol\":23,\"test/mocks/MockERC20.sol\":11,\"test/mocks/MultiSwapper.sol\":65,\"test/mocks/Swapper.sol\":79,\"test/utils/CrownFixture.sol\":64},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"5fc989a14e4c5b19b9a0330a94f1b7a4fbd706e20eb2632e63d6d9517592e6c1","verifiedTreeHash":"b3d2931a72e08fd15350468ef6b47a52af217893","verifierVersion":"0.1.0+8fbf00a5"},{"checks":[{"durationMs":24054,"exitCode":0,"name":"build","output":"Compiling 86 files with Solc 0.8.26\nSolc 0.8.26 finished in 23.94s\nCompiler run successful!\n","passed":true},{"durationMs":158,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 20 tests for test/CrownHook.t.sol:CrownHookTest\n[PASS] testFuzz_buyExactInput(uint256) (runs: 256, μ: 215827, ~: 223298)\n[PASS] test_afterInitialize_refusesOtherInitializers() (gas: 763944)\n[PASS] test_afterInitialize_registersPoolAndCrownsGenesisKing() (gas: 40694)\n[PASS] test_beneficiaryOpeningADebt_isUndoneAndPaidAsClaim() (gas: 2326882)\n[PASS] test_beneficiaryRejectingEther_getsClaimAndSwapProceeds() (gas: 2264387)\n[PASS] test_buyExactInput_paysOnePercentOfInput() (gas: 225781)\n[PASS] test_buyExactOutput_paysOnePercentOnTopOfInput() (gas: 222687)\n[PASS] test_callbacksRefuseNonManager() (gas: 165934)\n[PASS] test_configurePool_rules() (gas: 1712971)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 8847)\n[PASS] test_endToEnd_buyChallengeStakeFinalizeBuy() (gas: 1091601)\n[PASS] test_feeCollectWithZeroLiquidityChange_isNotBlocked() (gas: 503721)\n[PASS] test_freshManagerSeededWithTokensOnly_mintsClaimThenRedeems() (gas: 8299919)\n[PASS] test_gasBurningBeneficiary_cannotBlockTheSwap() (gas: 2328332)\n[PASS] test_permissionsMatchAddress() (gas: 10154)\n[PASS] test_redeemClaim_requiresOperatorApproval() (gas: 45594)\n[PASS] test_sellExactInput_paysOnePercentOfOutput() (gas: 219880)\n[PASS] test_sellExactOutput_paysOnePercentOfOutput() (gas: 220606)\n[PASS] test_tickerWithoutKing_skipsTheCut() (gas: 1306224)\n[PASS] test_zeroBpsPool_takesNothing() (gas: 1283979)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 24.28ms (35.59ms CPU time)\n\nRan 4 tests for test/CrownToken.t.sol:CrownTokenTest\n[PASS] test_challengerScript_deploysDistinctSameTickerToken() (gas: 1683571)\n[PASS] test_challengerToken_rejectsZeroHolder() (gas: 4233)\n[PASS] test_deployScript_minesFlagAddress() (gas: 6849201)\n[PASS] test_launchToken_fixedSupplyToDeployer() (gas: 100279)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 42.06ms (4.58ms CPU time)\n\nRan 20 tests for test/CrownRegistry.t.sol:CrownRegistryTest\n[PASS] testFuzz_conservation(uint64[3],uint64[3]) (runs: 256, μ: 1603256, ~: 1599199)\n[PASS] test_claim_failurePaths() (gas: 804615)\n[PASS] test_claim_reentrancyCannotDoublePay() (gas: 1034519)\n[PASS] test_dethrone_slashSplitExactToTheWei() (gas: 1101721)\n[PASS] test_failedChallenge_belowQuorumSlashesChallengersAndCoolsDown() (gas: 1176382)\n[PASS] test_finalize_timelockBoundary() (gas: 591023)\n[PASS] test_newKingMustReignSevenDays() (gas: 732795)\n[PASS] test_openChallenge_minimumReignBoundary() (gas: 322066)\n[PASS] test_openChallenge_oneLiveChallengePerTicker() (gas: 841188)\n[PASS] test_openChallenge_rejectsSameTokenWrongTickerAndNoKing() (gas: 652744)\n[PASS] test_openChallenge_requiresExactBond() (gas: 351767)\n[PASS] test_registerKing_crownsFirstRegistration() (gas: 20532)\n[PASS] test_registerKing_rejectsSecondRegistration() (gas: 38187)\n[PASS] test_registerKing_rejectsWrongSymbolZeroAndCodeless() (gas: 618150)\n[PASS] test_stake_windowBoundaryAndZero() (gas: 500142)\n[PASS] test_stakerOnBothSides_isPaidForBoth() (gas: 854922)\n[PASS] test_tie_keepsTheKing() (gas: 677444)\n[PASS] test_treasuryThatRejectsEther_doesNotBlockFinalize() (gas: 626504)\n[PASS] test_unopposedChallengeAtQuorum_dethronesAndRefundsInFull() (gas: 656966)\n[PASS] test_unopposedChallengeBelowQuorum_sendsWholeSlashToTreasury() (gas: 525617)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 52.00ms (57.43ms CPU time)\n\nRan 3 test suites in 52.89ms (118.34ms CPU time): 44 tests passed, 0 failed, 0 skipped (44 total tests)\n","passed":true},{"durationMs":65,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"CrownChallengerToken.approve(address,uint256)\",\"CrownChallengerToken.transfer(address,uint256)\",\"CrownChallengerToken.transferFrom(address,address,uint256)\",\"CrownHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"CrownHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"CrownHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"CrownHook.configurePool((address,address,uint24,int24,address),bytes32,uint16)\",\"CrownHook.payCrown(address,address,uint256)\",\"CrownHook.redeemClaim(address,uint256)\",\"CrownHook.unlockCallback(bytes)\",\"CrownRegistry.claim(uint256)\",\"CrownRegistry.finalize(uint256)\",\"CrownRegistry.openChallenge(bytes32,address)\",\"CrownRegistry.payTreasury()\",\"CrownRegistry.registerKing(bytes32,address,address)\",\"CrownRegistry.stake(uint256,uint8)\",\"CrownToken.approve(address,uint256)\",\"CrownToken.transfer(address,uint256)\",\"CrownToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":265,\"foundry.toml\":19,\"remappings.txt\":4,\"script/DeployChallenger.s.sol\":21,\"script/DeployCrown.s.sol\":49,\"site/app.js\":164,\"site/config.js\":9,\"site/index.html\":120,\"src/CrownChallengerToken.sol\":19,\"src/CrownHook.sol\":315,\"src/CrownRegistry.sol\":332,\"src/CrownToken.sol\":15,\"src/HookFlags.sol\":34,\"src/libraries/Ticker.sol\":42,\"test/CrownHook.t.sol\":356,\"test/CrownRegistry.t.sol\":384,\"test/CrownToken.t.sol\":65,\"test/floor/OwnerChecks.sol\":23,\"test/mocks/MockERC20.sol\":11,\"test/mocks/Swapper.sol\":79,\"test/utils/CrownFixture.sol\":64},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":3926,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-eth at src/CrownRegistry.sol:324: Reentrancy in CrownRegistry._payTreasury() (src/CrownRegistry.sol#324-331):\n[medium/medium] uninitialized-local at src/CrownHook.sol:192: CrownHook._crown(PoolKey,uint256).beneficiary (src/CrownHook.sol#192) is a local variable never initialized\n[medium/medium] uninitialized-local at src/CrownHook.sol:203: CrownHook._crown(PoolKey,uint256).paid (src/CrownHook.sol#203) is a local variable never initialized\n[medium/medium] uninitialized-local at src/CrownHook.sol:202: CrownHook._crown(PoolKey,uint256).asClaim (src/CrownHook.sol#202) is a local variable never initialized\n[medium/medium] unused-return at src/CrownHook.sol:241: CrownHook.redeemClaim(Currency,uint256) (src/CrownHook.sol#241-243) ignores return value by poolManager.unlock(abi.encode(msg.sender,currency,amount)) (src/CrownHook.sol#242)\n[low/medium] reentrancy-benign at src/CrownHook.sol:185: Reentrancy in CrownHook._crown(PoolKey,uint256) (src/CrownHook.sol#185-222):\n[low/medium] reentrancy-events at src/CrownHook.sol:185: Reentrancy in CrownHook._crown(PoolKey,uint256) (src/CrownHook.sol#185-222):\n[low/medium] reentrancy-events at src/CrownHook.sol:131: Reentrancy in CrownHook.afterInitialize(address,PoolKey,uint160,int24) (src/CrownHook.sol#131-150):\n[low/medium] timestamp at src/CrownRegistry.sol:176: CrownRegistry.openChallenge(bytes32,address) (src/CrownRegistry.sol#176-203) uses timestamp for comparisons\n[low/medium] timestamp at src/CrownRegistry.sol:166: CrownRegistry.registerKing(bytes32,address,address) (src/CrownRegistry.sol#166-172) uses timestamp for comparisons\n[low/medium] timestamp at src/CrownRegistry.sol:217: CrownRegistry.finalize(uint256) (src/CrownRegistry.sol#217-265) uses timestamp for comparisons\n[low/medium] timestamp at src/CrownRegistry.sol:206: CrownRegistry.stake(uint256,CrownRegistry.Side) (src/CrownRegistry.sol#206-211) uses timestamp for comparisons","passed":true},{"durationMs":477,"exitCode":0,"name":"aderyn","output":"[high] eth-send-unchecked-address at src/CrownRegistry.sol:217: ETH transferred without address checks (3 places)\n[low] internal-function-used-once at src/HookFlags.sol:27: Internal Function Used Only Once (2 places)\n[low] large-numeric-literal at src/CrownChallengerToken.sol:11: Large Numeric Literal (8 places)\n[low] literal-instead-of-constant at src/libraries/Ticker.sol:25: Literal Instead of Constant (3 places)\n[low] local-variable-shadowing at src/CrownRegistry.sol:46: Local Variable Shadows State Variable\n[low] unchecked-return at src/CrownHook.sol:242: Unchecked Return\n[low] unused-public-function at src/CrownHook.sol:95: Public Function Not Used Internally\n[low] unused-state-variable at src/CrownHook.sol:39: Unused State Variable (3 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"847456388a693943951806383def512c8109a7d70a83fe7f634daec76c0b883b","verifiedTreeHash":"dacef5728579741df89029c5005b66e9bbf7a6d6","verifierVersion":"0.1.0+8fbf00a5"},{"checks":[{"durationMs":64169,"exitCode":0,"name":"build","output":"Compiling 92 files with Solc 0.8.26\nSolc 0.8.26 finished in 64.06s\nCompiler run successful!\n","passed":true},{"durationMs":711,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/CrownToken.t.sol:CrownTokenTest\n[PASS] test_challengerScript_deploysDistinctSameTickerToken() (gas: 1685367)\n[PASS] test_challengerToken_rejectsZeroHolder() (gas: 4233)\n[PASS] test_deployScript_minesFlagAddress() (gas: 8420680)\n[PASS] test_launchToken_fixedSupplyToDeployer() (gas: 100279)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 69.03ms (7.08ms CPU time)\n\nRan 23 tests for test/CrownRegistry.t.sol:CrownRegistryTest\n[PASS] testFuzz_conservation(uint64[3],uint64[3]) (runs: 256, μ: 1604694, ~: 1600527)\n[PASS] test_claim_failurePaths() (gas: 805841)\n[PASS] test_claim_reentrancyCannotDoublePay() (gas: 1035723)\n[PASS] test_dethrone_slashSplitExactToTheWei() (gas: 1103458)\n[PASS] test_failedChallenge_belowQuorumSlashesChallengersAndCoolsDown() (gas: 1177816)\n[PASS] test_finalize_timelockBoundary() (gas: 592012)\n[PASS] test_newKingMustReignSevenDays() (gas: 936787)\n[PASS] test_openChallenge_minimumReignBoundary() (gas: 322226)\n[PASS] test_openChallenge_oneLiveChallengePerTicker() (gas: 843388)\n[PASS] test_openChallenge_refusesTheSittingBeneficiary() (gas: 732628)\n[PASS] test_openChallenge_rejectsSameTokenWrongTickerAndNoKing() (gas: 652865)\n[PASS] test_openChallenge_requiresExactBond() (gas: 351902)\n[PASS] test_registerKing_crownsFirstRegistration() (gas: 20554)\n[PASS] test_registerKing_rejectsSecondRegistration() (gas: 38275)\n[PASS] test_registerKing_rejectsWrongSymbolZeroAndCodeless() (gas: 618282)\n[PASS] test_stake_windowBoundaryAndZero() (gas: 500255)\n[PASS] test_stakerOnBothSides_isPaidForBoth() (gas: 856457)\n[PASS] test_tie_keepsTheKing() (gas: 678613)\n[PASS] test_treasuryIsolatedPayment_isSelfOnlyAndFailedRetriesPreserveCredit() (gas: 772798)\n[PASS] test_treasuryThatRejectsEther_doesNotBlockFinalize() (gas: 622957)\n[PASS] test_uncontestedFlip_doesNotRestartTheReignClock() (gas: 735094)\n[PASS] test_unopposedChallengeAtQuorum_dethronesAndRefundsInFull() (gas: 657121)\n[PASS] test_unopposedChallengeBelowQuorum_sendsWholeSlashToTreasury() (gas: 526663)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 78.75ms (86.78ms CPU time)\n\nRan 20 tests for test/CrownHook.t.sol:CrownHookTest\n[PASS] testFuzz_buyExactInput(uint256) (runs: 256, μ: 221791, ~: 229569)\n[PASS] test_afterInitialize_refusesOtherInitializers() (gas: 765239)\n[PASS] test_afterInitialize_registersPoolAndCrownsGenesisKing() (gas: 40694)\n[PASS] test_beneficiaryOpeningADebt_isUndoneAndPaidAsClaim() (gas: 2330411)\n[PASS] test_beneficiaryRejectingEther_getsClaimAndSwapProceeds() (gas: 2267872)\n[PASS] test_buyExactInput_paysOnePercentOfInput() (gas: 232052)\n[PASS] test_buyExactOutput_paysOnePercentOnTopOfInput() (gas: 228333)\n[PASS] test_callbacksRefuseNonManager() (gas: 165930)\n[PASS] test_configurePool_rules() (gas: 1719285)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 8847)\n[PASS] test_endToEnd_buyChallengeStakeFinalizeBuy() (gas: 1105542)\n[PASS] test_feeCollectWithZeroLiquidityChange_isNotBlocked() (gas: 515682)\n[PASS] test_freshManagerSeededWithTokensOnly_mintsClaimThenRedeems() (gas: 8392569)\n[PASS] test_gasBurningBeneficiary_cannotBlockTheSwap() (gas: 2329175)\n[PASS] test_permissionsMatchAddress() (gas: 10154)\n[PASS] test_redeemClaim_requiresOperatorApproval() (gas: 45594)\n[PASS] test_sellExactInput_paysOnePercentOfOutput() (gas: 225548)\n[PASS] test_sellExactOutput_paysOnePercentOfOutput() (gas: 226850)\n[PASS] test_tickerWithoutKing_skipsTheCut() (gas: 1306738)\n[PASS] test_zeroBpsPool_takesNothing() (gas: 1284506)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 124.11ms (134.96ms CPU time)\n\nRan 9 tests for test/CrownHookEdge.t.sol:CrownHookEdgeTest\n[PASS] testFuzz_buyExactOutput(uint256) (runs: 256, μ: 222048, ~: 228143)\n[PASS] testFuzz_sellExactInput(uint256) (runs: 256, μ: 217916, ~: 225678)\n[PASS] testFuzz_sellExactOutput(uint256) (runs: 256, μ: 219850, ~: 227000)\n[PASS] test_batchedRouter_paysTheCutOnEverySwapInOneUnlock() (gas: 1345765)\n[PASS] test_configurePool_canBeRevisedUntilThePoolOpens() (gas: 748348)\n[PASS] test_erc20PairPool_paysTheCutInCurrency0() (gas: 3295304)\n[PASS] test_redeemClaim_onlyTheHolderAndOnlyWhatItHolds() (gas: 2327579)\n[PASS] test_secondPoolOnTheLaunchToken_sharesTheKingAndDoesNotRecrown() (gas: 604390)\n[PASS] test_swapBelowOneHundredWei_paysNoCutAndStillSettles() (gas: 356897)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 124.90ms (177.97ms CPU time)\n\nRan 11 tests for test/CrownRevision.t.sol:CrownRevisionTest\n[PASS] testFuzz_exactInputBuy_withLimit_neverOvercharges(uint256,int24) (runs: 256, μ: 211264, ~: 226333)\n[PASS] test_afterInitialize_refusesAPoolWhoseTickerIsHeldByAnotherToken() (gas: 1471978)\n[PASS] test_afterInitialize_refusesATokenWithoutAReadableStringSymbol() (gas: 371898)\n[PASS] test_beneficiaryResettingTheSync_isPaidAsClaimAndThePrepayRouterSettles() (gas: 1900959)\n[PASS] test_beneficiarySyncingTheToken_isPaidAsClaimAndTheNativeBuyerSettles() (gas: 1344325)\n[PASS] test_erc20Currency0_revertingBalanceOf_paysAsClaimInsteadOfBlocking() (gas: 3409611)\n[PASS] test_exactInputBuy_limitReachedExactlyAtTheRequest_isNotAPartialFill() (gas: 1527225)\n[PASS] test_exactInputBuy_partialFill_isRefused() (gas: 425577)\n[PASS] test_exactOutputSell_partialFill_isRefused() (gas: 415095)\n[PASS] test_oversizedExactInput_isRefusedOnACrownedPool() (gas: 130450)\n[PASS] test_partialFillsAndOversizedRequests_proceedWhenNothingIsCharged() (gas: 2586789)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 125.26ms (75.96ms CPU time)\n\nRan 11 tests for test/CrownMath.t.sol:CrownMathTest\n[PASS] testFuzz_quorumBoundary(uint256) (runs: 256, μ: 1224816, ~: 1229533)\n[PASS] testFuzz_slashSplit_exactToTheWei(uint96[5],uint8,uint8) (runs: 512, μ: 1443671, ~: 1442302)\n[PASS] testFuzz_tieKeepsKing_oneWeiMoreFlips(uint256) (runs: 256, μ: 1244783, ~: 1244798)\n[PASS] testFuzz_wrongBond(uint256) (runs: 256, μ: 44051, ~: 44049)\n[PASS] test_dethronedKing_canChallengeBackAfterMinReign() (gas: 1066282)\n[PASS] test_dustStakes_lastClaimerTakesExactRemainder() (gas: 1182923)\n[PASS] test_finalize_isPermissionlessAndNotRepeatable() (gas: 510968)\n[PASS] test_noAdminFunctions() (gas: 219063)\n[PASS] test_registerKing_rejectsZeroTickerAndCrownsOtherTickersIndependently() (gas: 680484)\n[PASS] test_stake_invalidSideIsRefused() (gas: 336799)\n[PASS] test_timelock_windowClosedButNotFinalized_isStillLive() (gas: 974544)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 126.88ms (256.62ms CPU time)\n\nRan 1 test for test/CrownHook.invariant.t.sol:CrownHookInvariantTest\n[PASS]\nCrownHookInvariantTest invariants:\n[PASS] invariant_claimsAreBacked\n[PASS] invariant_crownPaidMatchesReceipts\n[PASS] invariant_everyCutExact\n[PASS] invariant_hookHoldsNothing\n[PASS] invariant_kingAlwaysSet\n CrownHookInvariantTest invariants (runs: 32, calls: 1280, reverts: 0)\n\n╭-------------+---------------+-------+---------+----------╮\n| Contract    | Selector      | Calls | Reverts | Discards |\n+==========================================================+\n| HookHandler | buyExactIn    | 134   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | buyExactOut   | 131   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | challenge     | 133   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | finalize      | 108   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | redeem        | 138   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | sellExactIn   | 134   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | sellExactOut  | 123   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | stake         | 123   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | toggleRejects | 120   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | warp          | 136   | 0       | 0        |\n╰-------------+---------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 400.43ms (393.28ms CPU time)\n\nRan 1 test for test/CrownRegistry.invariant.t.sol:CrownRegistryInvariantTest\n[PASS]\nCrownRegistryInvariantTest invariants:\n[PASS] invariant_balanceEqualsObligations\n[PASS] invariant_claimRulesHold\n[PASS] invariant_ethIsConserved\n[PASS] invariant_resolutionMatchesRule\n[PASS] invariant_tickerTotalsAndKings\n[PASS] invariant_treasuryArrearsOnlyWhileRejecting\n CrownRegistryInvariantTest invariants (runs: 48, calls: 2880, reverts: 0)\n\n╭-----------------+----------------+-------+---------+----------╮\n| Contract        | Selector       | Calls | Reverts | Discards |\n+===============================================================+\n| RegistryHandler | claim          | 418   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | finalize       | 401   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | openChallenge  | 408   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | payTreasury    | 408   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | stake          | 420   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | toggleTreasury | 390   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | warp           | 435   | 0       | 0        |\n╰-----------------+----------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 589.46ms (583.28ms CPU time)\n\nRan 8 test suites in 592.68ms (1.64s CPU time): 80 tests passed, 0 failed, 0 skipped (80 total tests)\n","passed":true},{"durationMs":89,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"CrownChallengerToken.approve(address,uint256)\",\"CrownChallengerToken.transfer(address,uint256)\",\"CrownChallengerToken.transferFrom(address,address,uint256)\",\"CrownHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"CrownHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"CrownHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"CrownHook.configurePool((address,address,uint24,int24,address),bytes32,uint16)\",\"CrownHook.payCrown(address,address,uint256)\",\"CrownHook.redeemClaim(address,uint256)\",\"CrownHook.unlockCallback(bytes)\",\"CrownRegistry.claim(uint256)\",\"CrownRegistry.finalize(uint256)\",\"CrownRegistry.openChallenge(bytes32,address)\",\"CrownRegistry.payTreasury()\",\"CrownRegistry.payTreasuryIsolated()\",\"CrownRegistry.registerKing(bytes32,address,address)\",\"CrownRegistry.stake(uint256,uint8)\",\"CrownToken.approve(address,uint256)\",\"CrownToken.transfer(address,uint256)\",\"CrownToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"DESIGN.md\":83,\"README.md\":318,\"REVIEW.md\":33,\"VALIDATION.md\":147,\"dist/abi/CrownChallengerToken.json\":1,\"dist/abi/CrownHook.json\":1,\"dist/abi/CrownRegistry.json\":1,\"dist/abi/CrownToken.json\":1,\"dist/abi/PoolManager.json\":1,\"dist/assets/ccip-DHQZXRDH.js\":1,\"dist/assets/dist-D4rI3O-X.js\":3,\"dist/assets/index-BsIC3gU9.js\":47,\"dist/assets/index-DK2JiM0w.css\":1,\"dist/crown-config.json\":16,\"dist/favicon.svg\":1,\"dist/index.html\":21,\"dist/social.png\":99,\"dist/wallets/browser.svg\":1,\"dist/wallets/coinbase.svg\":1,\"dist/wallets/metamask.svg\":1,\"dist/wallets/phantom.svg\":1,\"dist/wallets/rabby.svg\":1,\"foundry.toml\":19,\"remappings.txt\":4,\"script/DeployChallenger.s.sol\":21,\"script/DeployCrown.s.sol\":49,\"site/README.md\":83,\"site/WALLET-ICONS-LICENSE.txt\":9,\"site/design/social.svg\":1,\"site/index.html\":20,\"site/package-lock.json\":1718,\"site/package.json\":16,\"site/public/abi/CrownChallengerToken.json\":1,\"site/public/abi/CrownHook.json\":1,\"site/public/abi/CrownRegistry.json\":1,\"site/public/abi/CrownToken.json\":1,\"site/public/abi/PoolManager.json\":1,\"site/public/crown-config.json\":16,\"site/public/favicon.svg\":1,\"site/public/social.png\":99,\"site/public/wallets/browser.svg\":1,\"site/public/wallets/coinbase.svg\":1,\"site/public/wallets/metamask.svg\":1,\"site/public/wallets/phantom.svg\":1,\"site/public/wallets/rabby.svg\":1,\"site/scripts/abis.mjs\":13,\"site/scripts/export.mjs\":60,\"site/scripts/social.mjs\":13,\"site/src/App.tsx\":173,\"site/src/OwnerPanel.tsx\":26,\"site/src/Transaction.tsx\":108,\"site/src/Wallet.tsx\":73,\"site/src/config.ts\":78,\"site/src/domain.ts\":73,\"site/src/main.tsx\":13,\"site/src/styles.css\":188,\"site/src/useCrown.ts\":69,\"site/tests/export.mjs\":41,\"site/tests/interaction.mjs\":284,\"site/tsconfig.json\":10,\"site/vite.config.ts\":8,\"src/CrownChallengerToken.sol\":19,\"src/CrownHook.sol\":386,\"src/CrownRegistry.sol\":346,\"src/CrownToken.sol\":15,\"src/HookFlags.sol\":34,\"src/libraries/Ticker.sol\":42,\"test/CrownHook.invariant.t.sol\":289,\"test/CrownHook.t.sol\":356,\"test/CrownHookEdge.t.sol\":207,\"test/CrownMath.t.sol\":325,\"test/CrownRegistry.invariant.t.sol\":319,\"test/CrownRegistry.t.sol\":438,\"test/CrownRevision.t.sol\":320,\"test/CrownToken.t.sol\":67,\"test/floor/OwnerChecks.sol\":23,\"test/mocks/MockERC20.sol\":11,\"test/mocks/MultiSwapper.sol\":65,\"test/mocks/Swapper.sol\":84,\"test/utils/CrownFixture.sol\":64},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":3790,"exitCode":0,"name":"slither","output":"[medium/medium] uninitialized-local at src/CrownHook.sol:233: CrownHook._crown(PoolKey,uint256).beneficiary (src/CrownHook.sol#233) is a local variable never initialized\n[medium/medium] uninitialized-local at src/CrownHook.sol:244: CrownHook._crown(PoolKey,uint256).paid (src/CrownHook.sol#244) is a local variable never initialized\n[medium/medium] uninitialized-local at src/CrownHook.sol:243: CrownHook._crown(PoolKey,uint256).asClaim (src/CrownHook.sol#243) is a local variable never initialized\n[medium/medium] unused-return at src/CrownHook.sol:312: CrownHook.redeemClaim(Currency,uint256) (src/CrownHook.sol#312-314) ignores return value by poolManager.unlock(abi.encode(msg.sender,currency,amount)) (src/CrownHook.sol#313)\n[medium/medium] unused-return at src/CrownHook.sol:146: CrownHook.afterInitialize(address,PoolKey,uint160,int24) (src/CrownHook.sol#146-176) ignores return value by (kingToken,None,None) = registry.king(cfg.ticker) (src/CrownHook.sol#171)\n[low/medium] reentrancy-benign at src/CrownHook.sol:226: Reentrancy in CrownHook._crown(PoolKey,uint256) (src/CrownHook.sol#226-263):\n[low/medium] reentrancy-events at src/CrownRegistry.sol:307: Reentrancy in CrownRegistry.payTreasuryIsolated() (src/CrownRegistry.sol#307-314):\n[low/medium] reentrancy-events at src/CrownHook.sol:226: Reentrancy in CrownHook._crown(PoolKey,uint256) (src/CrownHook.sol#226-263):\n[low/medium] reentrancy-events at src/CrownHook.sol:146: Reentrancy in CrownHook.afterInitialize(address,PoolKey,uint160,int24) (src/CrownHook.sol#146-176):\n[low/medium] return-bomb at src/CrownHook.sol:267: CrownHook._managerHolds(Currency,uint256) (src/CrownHook.sol#267-273) tries to limit the gas of an external call that controls implicit decoding\n[low/medium] timestamp at src/CrownRegistry.sol:224: CrownRegistry.finalize(uint256) (src/CrownRegistry.sol#224-272) uses timestamp for comparisons\n[low/medium] timestamp at src/CrownRegistry.sol:168: CrownRegistry.registerKing(bytes32,address,address) (src/CrownRegistry.sol#168-174) uses timestamp for comparisons\n[low/medium] timestamp at src/CrownRegistry.sol:211: CrownRegistry.stake(uint256,CrownRegistry.Side) (src/CrownRegistry.sol#211-216) uses timestamp for comparisons\n[low/medium] timestamp at src/CrownRegistry.sol:180: CrownRegistry.openChallenge(bytes32,address) (src/CrownRegistry.sol#180-208) uses timestamp for comparisons","passed":true},{"durationMs":440,"exitCode":0,"name":"aderyn","output":"[high] eth-send-unchecked-address at src/CrownRegistry.sol:275: ETH transferred without address checks\n[low] internal-function-used-once at src/HookFlags.sol:27: Internal Function Used Only Once (2 places)\n[low] large-numeric-literal at src/CrownChallengerToken.sol:11: Large Numeric Literal (9 places)\n[low] literal-instead-of-constant at src/libraries/Ticker.sol:25: Literal Instead of Constant (3 places)\n[low] local-variable-shadowing at src/CrownRegistry.sol:46: Local Variable Shadows State Variable\n[low] state-change-without-event at src/CrownRegistry.sol:301: State Change Without Event\n[low] unchecked-return at src/CrownHook.sol:313: Unchecked Return\n[low] unused-public-function at src/CrownHook.sol:110: Public Function Not Used Internally\n[low] unused-state-variable at src/CrownHook.sol:43: Unused State Variable (4 places)","passed":true},{"durationMs":10266,"exitCode":0,"name":"proof 32a8dd2b7bec","output":"Compiling 82 files with Solc 0.8.26\nSolc 0.8.26 finished in 9.50s\nCompiler run successful!\n\nRan 2 tests for test/imd-proof-a1d0f542/Proof_32a8dd2b7bec.t.sol:CrownCutBypassProofTest\n[PASS] test_oversizedAmountSpecified_skipsTheCut() (gas: 62185)\n[PASS] test_sameTradeOneWeiSmaller_isCharged() (gas: 167421)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.51ms (623.11µs CPU time)\n\nRan 1 test suite in 6.29ms (2.51ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)\n","passed":true},{"durationMs":9075,"exitCode":0,"name":"proof e4ce3d9e5119","output":"2026-10-15T19:03:18.922282Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-WCmsia/repo/test/imd-proof-a1d0f542/Proof_32a8dd2b7bec.t.sol\": No such file or directory (os error 2)\nCompiling 1 files with Solc 0.8.26\nSolc 0.8.26 finished in 8.44s\nCompiler run successful!\n\nRan 2 tests for test/imd-proof-29cd77b8/Proof_e4ce3d9e5119.t.sol:CrownPartialFillProofTest\n[PASS] test_exactInputBuy_partialFill_cutBoundedByExecutedAmount() (gas: 185420)\n[PASS] test_exactOutputSell_partialFill_mustNotChargeSwapperEth() (gas: 179858)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.57ms (621.00µs CPU time)\n\nRan 1 test suite in 4.70ms (1.57ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)\n","passed":true},{"durationMs":9676,"exitCode":0,"name":"proof a6a04375a56a","output":"2026-10-15T19:03:28.014099Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-WCmsia/repo/test/imd-proof-29cd77b8/Proof_e4ce3d9e5119.t.sol\": No such file or directory (os error 2)\nCompiling 1 files with Solc 0.8.26\nSolc 0.8.26 finished in 9.02s\nCompiler run successful!\n\nRan 2 tests for test/imd-proof-4da71579/Proof_a6a04375a56a.t.sol:SyncGriefTest\n[PASS] test_prepayRouterSell_blockedByHostileKing() (gas: 1870093)\n[PASS] test_prepayRouterSell_worksWithHonestKing() (gas: 778086)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.83ms (937.28µs CPU time)\n\nRan 1 test suite in 9.25ms (1.83ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)\n","passed":true},{"durationMs":2518,"exitCode":0,"name":"proof 0d8da1f11423","output":"2026-10-15T19:03:37.692967Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-WCmsia/repo/test/imd-proof-4da71579/Proof_a6a04375a56a.t.sol\": No such file or directory (os error 2)\nCompiling 1 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.92s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-b65d3fc2/Proof_0d8da1f11423.t.sol:SelfChallengeLockoutTest\n[PASS] test_incumbentSelfChallenge_locksOutHonestChallengerForAnotherReign() (gas: 324355)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 669.61µs (98.30µs CPU time)\n\nRan 1 test suite in 3.98ms (669.61µs CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"8ecfa7f330507ededa4c6f234595e018c4259898d1285471506bce7f714c6e81","verifiedTreeHash":"e52771b9e0328ecf13732b52463bccfe37648737","verifierVersion":"0.1.0+8fbf00a5"},{"checks":[{"durationMs":51551,"exitCode":0,"name":"build","output":"Compiling 92 files with Solc 0.8.26\nSolc 0.8.26 finished in 51.43s\nCompiler run successful with warnings:\nWarning (2018): Function state mutability can be restricted to view\n   --> test/CrownRegistry.t.sol:294:5:\n    |\n294 |     function test_registerKing_setsReignAndProtection() public {\n    |     ^ (Relevant source part starts here and spans across multiple lines).\n\n","passed":true},{"durationMs":534,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/CrownToken.t.sol:CrownTokenTest\n[PASS] test_challengerScript_deploysDistinctSameTickerToken() (gas: 1709782)\n[PASS] test_challengerToken_rejectsZeroHolder() (gas: 4233)\n[PASS] test_deployScript_minesFlagAddress() (gas: 7202636)\n[PASS] test_launchToken_fixedSupplyToDeployer() (gas: 100279)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 31.01ms (5.13ms CPU time)\n\nRan 11 tests for test/CrownRevision.t.sol:CrownRevisionTest\n[PASS] testFuzz_exactInputBuy_withLimit_neverOvercharges(uint256,int24) (runs: 256, μ: 211264, ~: 226319)\n[PASS] test_afterInitialize_refusesAPoolWhoseTickerIsHeldByAnotherToken() (gas: 1494247)\n[PASS] test_afterInitialize_refusesATokenWithoutAReadableStringSymbol() (gas: 371898)\n[PASS] test_beneficiaryResettingTheSync_isPaidAsClaimAndThePrepayRouterSettles() (gas: 1903108)\n[PASS] test_beneficiarySyncingTheToken_isPaidAsClaimAndTheNativeBuyerSettles() (gas: 1346474)\n[PASS] test_erc20Currency0_revertingBalanceOf_paysAsClaimInsteadOfBlocking() (gas: 3431880)\n[PASS] test_exactInputBuy_limitReachedExactlyAtTheRequest_isNotAPartialFill() (gas: 1527269)\n[PASS] test_exactInputBuy_partialFill_isRefused() (gas: 425577)\n[PASS] test_exactOutputSell_partialFill_isRefused() (gas: 415095)\n[PASS] test_oversizedExactInput_isRefusedOnACrownedPool() (gas: 130450)\n[PASS] test_partialFillsAndOversizedRequests_proceedWhenNothingIsCharged() (gas: 2586833)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 44.38ms (49.19ms CPU time)\n\nRan 9 tests for test/CrownHookEdge.t.sol:CrownHookEdgeTest\n[PASS] testFuzz_buyExactOutput(uint256) (runs: 256, μ: 221732, ~: 228153)\n[PASS] testFuzz_sellExactInput(uint256) (runs: 256, μ: 217917, ~: 225712)\n[PASS] testFuzz_sellExactOutput(uint256) (runs: 256, μ: 219851, ~: 227011)\n[PASS] test_batchedRouter_paysTheCutOnEverySwapInOneUnlock() (gas: 1345765)\n[PASS] test_configurePool_canBeRevisedUntilThePoolOpens() (gas: 748348)\n[PASS] test_erc20PairPool_paysTheCutInCurrency0() (gas: 3317573)\n[PASS] test_redeemClaim_onlyTheHolderAndOnlyWhatItHolds() (gas: 2349848)\n[PASS] test_secondPoolOnTheLaunchToken_sharesTheKingAndDoesNotRecrown() (gas: 604390)\n[PASS] test_swapBelowOneHundredWei_paysNoCutAndStillSettles() (gas: 356897)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 58.16ms (106.41ms CPU time)\n\nRan 20 tests for test/CrownHook.t.sol:CrownHookTest\n[PASS] testFuzz_buyExactInput(uint256) (runs: 256, μ: 221796, ~: 229577)\n[PASS] test_afterInitialize_refusesOtherInitializers() (gas: 787508)\n[PASS] test_afterInitialize_registersPoolAndCrownsGenesisKing() (gas: 40694)\n[PASS] test_beneficiaryOpeningADebt_isUndoneAndPaidAsClaim() (gas: 2352680)\n[PASS] test_beneficiaryRejectingEther_getsClaimAndSwapProceeds() (gas: 2290141)\n[PASS] test_buyExactInput_paysOnePercentOfInput() (gas: 232052)\n[PASS] test_buyExactOutput_paysOnePercentOnTopOfInput() (gas: 228333)\n[PASS] test_callbacksRefuseNonManager() (gas: 165930)\n[PASS] test_configurePool_rules() (gas: 1719285)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 8847)\n[PASS] test_endToEnd_buyChallengeStakeFinalizeBuy() (gas: 1112661)\n[PASS] test_feeCollectWithZeroLiquidityChange_isNotBlocked() (gas: 515682)\n[PASS] test_freshManagerSeededWithTokensOnly_mintsClaimThenRedeems() (gas: 8456612)\n[PASS] test_gasBurningBeneficiary_cannotBlockTheSwap() (gas: 2351444)\n[PASS] test_permissionsMatchAddress() (gas: 10154)\n[PASS] test_redeemClaim_requiresOperatorApproval() (gas: 45594)\n[PASS] test_sellExactInput_paysOnePercentOfOutput() (gas: 225548)\n[PASS] test_sellExactOutput_paysOnePercentOfOutput() (gas: 226850)\n[PASS] test_tickerWithoutKing_skipsTheCut() (gas: 1306782)\n[PASS] test_zeroBpsPool_takesNothing() (gas: 1284506)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 70.50ms (45.11ms CPU time)\n\nRan 27 tests for test/CrownRegistry.t.sol:CrownRegistryTest\n[PASS] testFuzz_conservation(uint64[3],uint64[3]) (runs: 256, μ: 1607995, ~: 1603451)\n[PASS] test_claim_failurePaths() (gas: 808049)\n[PASS] test_claim_reentrancyCannotDoublePay() (gas: 1037966)\n[PASS] test_dethrone_slashSplitExactToTheWei() (gas: 1110700)\n[PASS] test_dustDefence_grantsNoNewProtection() (gas: 925482)\n[PASS] test_failedChallenge_belowQuorumSlashesChallengersAndCoolsDown() (gas: 1184317)\n[PASS] test_finalize_timelockBoundary() (gas: 594149)\n[PASS] test_minDefence_grantsProtection_andForfeitsASlash() (gas: 825247)\n[PASS] test_newKingMustReignSevenDays() (gas: 948110)\n[PASS] test_openChallenge_minimumReignBoundary() (gas: 326452)\n[PASS] test_openChallenge_oneLiveChallengePerTicker() (gas: 845526)\n[PASS] test_openChallenge_refusesTheSittingBeneficiary() (gas: 736893)\n[PASS] test_openChallenge_rejectsSameTokenWrongTickerAndNoKing() (gas: 657086)\n[PASS] test_openChallenge_requiresExactBond() (gas: 354136)\n[PASS] test_registerKing_crownsFirstRegistration() (gas: 20596)\n[PASS] test_registerKing_rejectsSecondRegistration() (gas: 38319)\n[PASS] test_registerKing_rejectsWrongSymbolZeroAndCodeless() (gas: 618436)\n[PASS] test_registerKing_setsReignAndProtection() (gas: 22087)\n[PASS] test_stake_windowBoundaryAndZero() (gas: 502445)\n[PASS] test_stakerOnBothSides_isPaidForBoth() (gas: 863721)\n[PASS] test_sybilSelfChallenge_withDustDefence_buysNoImmunity() (gas: 1532319)\n[PASS] test_tie_keepsTheKing() (gas: 680803)\n[PASS] test_treasuryIsolatedPayment_isSelfOnlyAndFailedRetriesPreserveCredit() (gas: 775423)\n[PASS] test_treasuryThatRejectsEther_doesNotBlockFinalize() (gas: 625361)\n[PASS] test_uncontestedFlip_grantsNoNewProtection() (gas: 743866)\n[PASS] test_unopposedChallengeAtQuorum_dethronesAndRefundsInFull() (gas: 659404)\n[PASS] test_unopposedChallengeBelowQuorum_sendsWholeSlashToTreasury() (gas: 528916)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 70.52ms (60.65ms CPU time)\n\nRan 11 tests for test/CrownMath.t.sol:CrownMathTest\n[PASS] testFuzz_quorumBoundary(uint256) (runs: 256, μ: 1230144, ~: 1233765)\n[PASS] testFuzz_slashSplit_exactToTheWei(uint96[5],uint8,uint8) (runs: 512, μ: 1445738, ~: 1444662)\n[PASS] testFuzz_tieKeepsKing_oneWeiMoreFlips(uint256) (runs: 256, μ: 1253132, ~: 1253148)\n[PASS] testFuzz_wrongBond(uint256) (runs: 256, μ: 44099, ~: 44247)\n[PASS] test_dethronedKing_canChallengeBackAfterMinReign() (gas: 1079768)\n[PASS] test_dustStakes_lastClaimerTakesExactRemainder() (gas: 1185016)\n[PASS] test_finalize_isPermissionlessAndNotRepeatable() (gas: 513127)\n[PASS] test_noAdminFunctions() (gas: 219415)\n[PASS] test_registerKing_rejectsZeroTickerAndCrownsOtherTickersIndependently() (gas: 702841)\n[PASS] test_stake_invalidSideIsRefused() (gas: 338945)\n[PASS] test_timelock_windowClosedButNotFinalized_isStillLive() (gas: 976704)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 76.51ms (214.91ms CPU time)\n\nRan 1 test for test/CrownHook.invariant.t.sol:CrownHookInvariantTest\n[PASS]\nCrownHookInvariantTest invariants:\n[PASS] invariant_claimsAreBacked\n[PASS] invariant_crownPaidMatchesReceipts\n[PASS] invariant_everyCutExact\n[PASS] invariant_hookHoldsNothing\n[PASS] invariant_kingAlwaysSet\n CrownHookInvariantTest invariants (runs: 32, calls: 1280, reverts: 0)\n\n╭-------------+---------------+-------+---------+----------╮\n| Contract    | Selector      | Calls | Reverts | Discards |\n+==========================================================+\n| HookHandler | buyExactIn    | 134   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | buyExactOut   | 131   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | challenge     | 133   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | finalize      | 108   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | redeem        | 138   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | sellExactIn   | 134   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | sellExactOut  | 123   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | stake         | 123   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | toggleRejects | 120   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | warp          | 136   | 0       | 0        |\n╰-------------+---------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 272.74ms (267.60ms CPU time)\n\nRan 1 test for test/CrownRegistry.invariant.t.sol:CrownRegistryInvariantTest\n[PASS]\nCrownRegistryInvariantTest invariants:\n[PASS] invariant_balanceEqualsObligations\n[PASS] invariant_claimRulesHold\n[PASS] invariant_ethIsConserved\n[PASS] invariant_resolutionMatchesRule\n[PASS] invariant_tickerTotalsAndKings\n[PASS] invariant_treasuryArrearsOnlyWhileRejecting\n CrownRegistryInvariantTest invariants (runs: 48, calls: 2880, reverts: 0)\n\n╭-----------------+----------------+-------+---------+----------╮\n| Contract        | Selector       | Calls | Reverts | Discards |\n+===============================================================+\n| RegistryHandler | claim          | 418   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | finalize       | 401   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | openChallenge  | 408   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | payTreasury    | 408   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | stake          | 420   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | toggleTreasury | 390   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | warp           | 435   | 0       | 0        |\n╰-----------------+----------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 434.15ms (430.69ms CPU time)\n\nRan 8 test suites in 436.49ms (1.06s CPU time): 84 tests passed, 0 failed, 0 skipped (84 total tests)\n","passed":true},{"durationMs":72,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"CrownChallengerToken.approve(address,uint256)\",\"CrownChallengerToken.transfer(address,uint256)\",\"CrownChallengerToken.transferFrom(address,address,uint256)\",\"CrownHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"CrownHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"CrownHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"CrownHook.configurePool((address,address,uint24,int24,address),bytes32,uint16)\",\"CrownHook.payCrown(address,address,uint256)\",\"CrownHook.redeemClaim(address,uint256)\",\"CrownHook.unlockCallback(bytes)\",\"CrownRegistry.claim(uint256)\",\"CrownRegistry.finalize(uint256)\",\"CrownRegistry.openChallenge(bytes32,address)\",\"CrownRegistry.payTreasury()\",\"CrownRegistry.payTreasuryIsolated()\",\"CrownRegistry.registerKing(bytes32,address,address)\",\"CrownRegistry.stake(uint256,uint8)\",\"CrownToken.approve(address,uint256)\",\"CrownToken.transfer(address,uint256)\",\"CrownToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"DESIGN.md\":83,\"README.md\":332,\"REVIEW.md\":33,\"VALIDATION.md\":175,\"dist/abi/CrownChallengerToken.json\":1,\"dist/abi/CrownHook.json\":1,\"dist/abi/CrownRegistry.json\":1,\"dist/abi/CrownToken.json\":1,\"dist/abi/PoolManager.json\":1,\"dist/assets/ccip-DosuAWIe.js\":1,\"dist/assets/dist-DCPwh1G5.js\":3,\"dist/assets/index-DK2JiM0w.css\":1,\"dist/assets/index-PcoSA_Mu.js\":47,\"dist/crown-config.json\":16,\"dist/favicon.svg\":1,\"dist/index.html\":21,\"dist/social.png\":99,\"dist/wallets/browser.svg\":1,\"dist/wallets/coinbase.svg\":1,\"dist/wallets/metamask.svg\":1,\"dist/wallets/phantom.svg\":1,\"dist/wallets/rabby.svg\":1,\"foundry.toml\":19,\"launch.json\":31,\"remappings.txt\":4,\"script/DeployChallenger.s.sol\":21,\"script/DeployCrown.s.sol\":49,\"site/README.md\":83,\"site/WALLET-ICONS-LICENSE.txt\":9,\"site/design/social.svg\":1,\"site/index.html\":20,\"site/package-lock.json\":1718,\"site/package.json\":16,\"site/public/abi/CrownChallengerToken.json\":1,\"site/public/abi/CrownHook.json\":1,\"site/public/abi/CrownRegistry.json\":1,\"site/public/abi/CrownToken.json\":1,\"site/public/abi/PoolManager.json\":1,\"site/public/crown-config.json\":16,\"site/public/favicon.svg\":1,\"site/public/social.png\":99,\"site/public/wallets/browser.svg\":1,\"site/public/wallets/coinbase.svg\":1,\"site/public/wallets/metamask.svg\":1,\"site/public/wallets/phantom.svg\":1,\"site/public/wallets/rabby.svg\":1,\"site/scripts/abis.mjs\":13,\"site/scripts/export.mjs\":60,\"site/scripts/social.mjs\":13,\"site/src/App.tsx\":173,\"site/src/OwnerPanel.tsx\":26,\"site/src/Transaction.tsx\":108,\"site/src/Wallet.tsx\":73,\"site/src/config.ts\":78,\"site/src/domain.ts\":74,\"site/src/main.tsx\":13,\"site/src/styles.css\":188,\"site/src/useCrown.ts\":69,\"site/tests/export.mjs\":41,\"site/tests/interaction.mjs\":285,\"site/tsconfig.json\":10,\"site/vite.config.ts\":8,\"src/CrownChallengerToken.sol\":19,\"src/CrownHook.sol\":386,\"src/CrownRegistry.sol\":363,\"src/CrownToken.sol\":15,\"src/HookFlags.sol\":34,\"src/libraries/Ticker.sol\":42,\"test/CrownHook.invariant.t.sol\":289,\"test/CrownHook.t.sol\":356,\"test/CrownHookEdge.t.sol\":207,\"test/CrownMath.t.sol\":325,\"test/CrownRegistry.invariant.t.sol\":319,\"test/CrownRegistry.t.sol\":509,\"test/CrownRevision.t.sol\":320,\"test/CrownToken.t.sol\":67,\"test/floor/OwnerChecks.sol\":23,\"test/mocks/MockERC20.sol\":11,\"test/mocks/MultiSwapper.sol\":65,\"test/mocks/Swapper.sol\":84,\"test/utils/CrownFixture.sol\":64},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"98bb3ddba0fa0e608dbd8122c3c4215eeb034eb8dc42a992f57d03cf2b5a78f4","verifiedTreeHash":"765a3e10dd7af1a203031b7e2a03689a7ddca155","verifierVersion":"0.1.0+8fbf00a5"},{"checks":[{"durationMs":42186,"exitCode":0,"name":"build","output":"Compiling 91 files with Solc 0.8.26\nSolc 0.8.26 finished in 42.08s\nCompiler run successful!\n","passed":true},{"durationMs":599,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 20 tests for test/CrownHook.t.sol:CrownHookTest\n[PASS] testFuzz_buyExactInput(uint256) (runs: 256, μ: 215752, ~: 223314)\n[PASS] test_afterInitialize_refusesOtherInitializers() (gas: 763944)\n[PASS] test_afterInitialize_registersPoolAndCrownsGenesisKing() (gas: 40694)\n[PASS] test_beneficiaryOpeningADebt_isUndoneAndPaidAsClaim() (gas: 2326882)\n[PASS] test_beneficiaryRejectingEther_getsClaimAndSwapProceeds() (gas: 2264387)\n[PASS] test_buyExactInput_paysOnePercentOfInput() (gas: 225781)\n[PASS] test_buyExactOutput_paysOnePercentOnTopOfInput() (gas: 222687)\n[PASS] test_callbacksRefuseNonManager() (gas: 165934)\n[PASS] test_configurePool_rules() (gas: 1712971)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 8847)\n[PASS] test_endToEnd_buyChallengeStakeFinalizeBuy() (gas: 1091601)\n[PASS] test_feeCollectWithZeroLiquidityChange_isNotBlocked() (gas: 503721)\n[PASS] test_freshManagerSeededWithTokensOnly_mintsClaimThenRedeems() (gas: 8299919)\n[PASS] test_gasBurningBeneficiary_cannotBlockTheSwap() (gas: 2328332)\n[PASS] test_permissionsMatchAddress() (gas: 10154)\n[PASS] test_redeemClaim_requiresOperatorApproval() (gas: 45594)\n[PASS] test_sellExactInput_paysOnePercentOfOutput() (gas: 219880)\n[PASS] test_sellExactOutput_paysOnePercentOfOutput() (gas: 220606)\n[PASS] test_tickerWithoutKing_skipsTheCut() (gas: 1306224)\n[PASS] test_zeroBpsPool_takesNothing() (gas: 1283979)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 32.02ms (44.78ms CPU time)\n\nRan 4 tests for test/CrownToken.t.sol:CrownTokenTest\n[PASS] test_challengerScript_deploysDistinctSameTickerToken() (gas: 1683571)\n[PASS] test_challengerToken_rejectsZeroHolder() (gas: 4233)\n[PASS] test_deployScript_minesFlagAddress() (gas: 6849201)\n[PASS] test_launchToken_fixedSupplyToDeployer() (gas: 100279)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 45.67ms (4.14ms CPU time)\n\nRan 20 tests for test/CrownRegistry.t.sol:CrownRegistryTest\n[PASS] testFuzz_conservation(uint64[3],uint64[3]) (runs: 256, μ: 1603861, ~: 1599229)\n[PASS] test_claim_failurePaths() (gas: 804615)\n[PASS] test_claim_reentrancyCannotDoublePay() (gas: 1034519)\n[PASS] test_dethrone_slashSplitExactToTheWei() (gas: 1101721)\n[PASS] test_failedChallenge_belowQuorumSlashesChallengersAndCoolsDown() (gas: 1176382)\n[PASS] test_finalize_timelockBoundary() (gas: 591023)\n[PASS] test_newKingMustReignSevenDays() (gas: 732795)\n[PASS] test_openChallenge_minimumReignBoundary() (gas: 322066)\n[PASS] test_openChallenge_oneLiveChallengePerTicker() (gas: 841188)\n[PASS] test_openChallenge_rejectsSameTokenWrongTickerAndNoKing() (gas: 652744)\n[PASS] test_openChallenge_requiresExactBond() (gas: 351767)\n[PASS] test_registerKing_crownsFirstRegistration() (gas: 20532)\n[PASS] test_registerKing_rejectsSecondRegistration() (gas: 38187)\n[PASS] test_registerKing_rejectsWrongSymbolZeroAndCodeless() (gas: 618150)\n[PASS] test_stake_windowBoundaryAndZero() (gas: 500142)\n[PASS] test_stakerOnBothSides_isPaidForBoth() (gas: 854922)\n[PASS] test_tie_keepsTheKing() (gas: 677444)\n[PASS] test_treasuryThatRejectsEther_doesNotBlockFinalize() (gas: 626504)\n[PASS] test_unopposedChallengeAtQuorum_dethronesAndRefundsInFull() (gas: 656966)\n[PASS] test_unopposedChallengeBelowQuorum_sendsWholeSlashToTreasury() (gas: 525617)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 65.35ms (70.47ms CPU time)\n\nRan 9 tests for test/CrownHookEdge.t.sol:CrownHookEdgeTest\n[PASS] testFuzz_buyExactOutput(uint256) (runs: 256, μ: 216847, ~: 222543)\n[PASS] testFuzz_sellExactInput(uint256) (runs: 256, μ: 212196, ~: 220084)\n[PASS] testFuzz_sellExactOutput(uint256) (runs: 256, μ: 213834, ~: 220782)\n[PASS] test_batchedRouter_paysTheCutOnEverySwapInOneUnlock() (gas: 1327693)\n[PASS] test_configurePool_canBeRevisedUntilThePoolOpens() (gas: 748327)\n[PASS] test_erc20PairPool_paysTheCutInCurrency0() (gas: 3282718)\n[PASS] test_redeemClaim_onlyTheHolderAndOnlyWhatItHolds() (gas: 2324094)\n[PASS] test_secondPoolOnTheLaunchToken_sharesTheKingAndDoesNotRecrown() (gas: 594819)\n[PASS] test_swapBelowOneHundredWei_paysNoCutAndStillSettles() (gas: 350165)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 71.78ms (173.71ms CPU time)\n\nRan 11 tests for test/CrownMath.t.sol:CrownMathTest\n[PASS] testFuzz_quorumBoundary(uint256) (runs: 256, μ: 1222561, ~: 1227309)\n[PASS] testFuzz_slashSplit_exactToTheWei(uint96[5],uint8,uint8) (runs: 512, μ: 1442269, ~: 1441133)\n[PASS] testFuzz_tieKeepsKing_oneWeiMoreFlips(uint256) (runs: 256, μ: 1242483, ~: 1242500)\n[PASS] testFuzz_wrongBond(uint256) (runs: 256, μ: 44056, ~: 44203)\n[PASS] test_dethronedKing_canChallengeBackAfterMinReign() (gas: 799477)\n[PASS] test_dustStakes_lastClaimerTakesExactRemainder() (gas: 1181524)\n[PASS] test_finalize_isPermissionlessAndNotRepeatable() (gas: 510023)\n[PASS] test_noAdminFunctions() (gas: 218887)\n[PASS] test_registerKing_rejectsZeroTickerAndCrownsOtherTickersIndependently() (gas: 680418)\n[PASS] test_stake_invalidSideIsRefused() (gas: 336752)\n[PASS] test_timelock_windowClosedButNotFinalized_isStillLive() (gas: 972344)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 72.13ms (154.13ms CPU time)\n\nRan 1 test for test/CrownHook.invariant.t.sol:CrownHookInvariantTest\n[PASS]\nCrownHookInvariantTest invariants:\n[PASS] invariant_claimsAreBacked\n[PASS] invariant_crownPaidMatchesReceipts\n[PASS] invariant_everyCutExact\n[PASS] invariant_hookHoldsNothing\n[PASS] invariant_kingAlwaysSet\n CrownHookInvariantTest invariants (runs: 32, calls: 1280, reverts: 0)\n\n╭-------------+---------------+-------+---------+----------╮\n| Contract    | Selector      | Calls | Reverts | Discards |\n+==========================================================+\n| HookHandler | buyExactIn    | 134   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | buyExactOut   | 131   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | challenge     | 133   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | finalize      | 108   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | redeem        | 138   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | sellExactIn   | 134   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | sellExactOut  | 123   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | stake         | 123   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | toggleRejects | 120   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | warp          | 136   | 0       | 0        |\n╰-------------+---------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 316.53ms (306.79ms CPU time)\n\nRan 1 test for test/CrownRegistry.invariant.t.sol:CrownRegistryInvariantTest\n[PASS]\nCrownRegistryInvariantTest invariants:\n[PASS] invariant_balanceEqualsObligations\n[PASS] invariant_claimRulesHold\n[PASS] invariant_ethIsConserved\n[PASS] invariant_resolutionMatchesRule\n[PASS] invariant_tickerTotalsAndKings\n[PASS] invariant_treasuryArrearsOnlyWhileRejecting\n CrownRegistryInvariantTest invariants (runs: 48, calls: 2880, reverts: 0)\n\n╭-----------------+----------------+-------+---------+----------╮\n| Contract        | Selector       | Calls | Reverts | Discards |\n+===============================================================+\n| RegistryHandler | claim          | 418   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | finalize       | 401   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | openChallenge  | 408   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | payTreasury    | 408   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | stake          | 420   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | toggleTreasury | 390   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | warp           | 435   | 0       | 0        |\n╰-----------------+----------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 468.75ms (465.25ms CPU time)\n\nRan 7 test suites in 472.34ms (1.07s CPU time): 66 tests passed, 0 failed, 0 skipped (66 total tests)\n","passed":true},{"durationMs":55,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"CrownChallengerToken.approve(address,uint256)\",\"CrownChallengerToken.transfer(address,uint256)\",\"CrownChallengerToken.transferFrom(address,address,uint256)\",\"CrownHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"CrownHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"CrownHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"CrownHook.configurePool((address,address,uint24,int24,address),bytes32,uint16)\",\"CrownHook.payCrown(address,address,uint256)\",\"CrownHook.redeemClaim(address,uint256)\",\"CrownHook.unlockCallback(bytes)\",\"CrownRegistry.claim(uint256)\",\"CrownRegistry.finalize(uint256)\",\"CrownRegistry.openChallenge(bytes32,address)\",\"CrownRegistry.payTreasury()\",\"CrownRegistry.registerKing(bytes32,address,address)\",\"CrownRegistry.stake(uint256,uint8)\",\"CrownToken.approve(address,uint256)\",\"CrownToken.transfer(address,uint256)\",\"CrownToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":265,\"foundry.toml\":19,\"remappings.txt\":4,\"script/DeployChallenger.s.sol\":21,\"script/DeployCrown.s.sol\":49,\"site/app.js\":164,\"site/config.js\":9,\"site/index.html\":120,\"src/CrownChallengerToken.sol\":19,\"src/CrownHook.sol\":315,\"src/CrownRegistry.sol\":332,\"src/CrownToken.sol\":15,\"src/HookFlags.sol\":34,\"src/libraries/Ticker.sol\":42,\"test/CrownHook.invariant.t.sol\":289,\"test/CrownHook.t.sol\":356,\"test/CrownHookEdge.t.sol\":207,\"test/CrownMath.t.sol\":322,\"test/CrownRegistry.invariant.t.sol\":319,\"test/CrownRegistry.t.sol\":384,\"test/CrownToken.t.sol\":65,\"test/floor/OwnerChecks.sol\":23,\"test/mocks/MockERC20.sol\":11,\"test/mocks/MultiSwapper.sol\":65,\"test/mocks/Swapper.sol\":79,\"test/utils/CrownFixture.sol\":64},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"e3598d208c22804d1897f0757b987fcb9bbdbb3590004d56f0bc6e73a175dbc1","verifiedTreeHash":"f43d948340589261f7ecab7c50f4bdc4118f3751","verifierVersion":"0.1.0+8fbf00a5"},{"checks":[{"durationMs":42198,"exitCode":0,"name":"build","output":"Compiling 91 files with Solc 0.8.26\nSolc 0.8.26 finished in 42.10s\nCompiler run successful!\n","passed":true},{"durationMs":581,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 4 tests for test/CrownToken.t.sol:CrownTokenTest\n[PASS] test_challengerScript_deploysDistinctSameTickerToken() (gas: 1683593)\n[PASS] test_challengerToken_rejectsZeroHolder() (gas: 4233)\n[PASS] test_deployScript_minesFlagAddress() (gas: 6557397)\n[PASS] test_launchToken_fixedSupplyToDeployer() (gas: 100279)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 15.81ms (3.13ms CPU time)\n\nRan 20 tests for test/CrownHook.t.sol:CrownHookTest\n[PASS] testFuzz_buyExactInput(uint256) (runs: 256, μ: 215754, ~: 223326)\n[PASS] test_afterInitialize_refusesOtherInitializers() (gas: 763966)\n[PASS] test_afterInitialize_registersPoolAndCrownsGenesisKing() (gas: 40694)\n[PASS] test_beneficiaryOpeningADebt_isUndoneAndPaidAsClaim() (gas: 2326904)\n[PASS] test_beneficiaryRejectingEther_getsClaimAndSwapProceeds() (gas: 2264409)\n[PASS] test_buyExactInput_paysOnePercentOfInput() (gas: 225781)\n[PASS] test_buyExactOutput_paysOnePercentOnTopOfInput() (gas: 222687)\n[PASS] test_callbacksRefuseNonManager() (gas: 165934)\n[PASS] test_configurePool_rules() (gas: 1712971)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 8847)\n[PASS] test_endToEnd_buyChallengeStakeFinalizeBuy() (gas: 1092723)\n[PASS] test_feeCollectWithZeroLiquidityChange_isNotBlocked() (gas: 503721)\n[PASS] test_freshManagerSeededWithTokensOnly_mintsClaimThenRedeems() (gas: 8321500)\n[PASS] test_gasBurningBeneficiary_cannotBlockTheSwap() (gas: 2328354)\n[PASS] test_permissionsMatchAddress() (gas: 10154)\n[PASS] test_redeemClaim_requiresOperatorApproval() (gas: 45594)\n[PASS] test_sellExactInput_paysOnePercentOfOutput() (gas: 219880)\n[PASS] test_sellExactOutput_paysOnePercentOfOutput() (gas: 220606)\n[PASS] test_tickerWithoutKing_skipsTheCut() (gas: 1306246)\n[PASS] test_zeroBpsPool_takesNothing() (gas: 1283979)\nSuite result: ok. 20 passed; 0 failed; 0 skipped; finished in 49.62ms (51.52ms CPU time)\n\nRan 9 tests for test/CrownHookEdge.t.sol:CrownHookEdgeTest\n[PASS] testFuzz_buyExactOutput(uint256) (runs: 256, μ: 216851, ~: 222549)\n[PASS] testFuzz_sellExactInput(uint256) (runs: 256, μ: 212201, ~: 220100)\n[PASS] testFuzz_sellExactOutput(uint256) (runs: 256, μ: 213838, ~: 220798)\n[PASS] test_batchedRouter_paysTheCutOnEverySwapInOneUnlock() (gas: 1327693)\n[PASS] test_configurePool_canBeRevisedUntilThePoolOpens() (gas: 748327)\n[PASS] test_erc20PairPool_paysTheCutInCurrency0() (gas: 3282740)\n[PASS] test_redeemClaim_onlyTheHolderAndOnlyWhatItHolds() (gas: 2324116)\n[PASS] test_secondPoolOnTheLaunchToken_sharesTheKingAndDoesNotRecrown() (gas: 594819)\n[PASS] test_swapBelowOneHundredWei_paysNoCutAndStillSettles() (gas: 350165)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 56.27ms (129.06ms CPU time)\n\nRan 11 tests for test/CrownMath.t.sol:CrownMathTest\n[PASS] testFuzz_quorumBoundary(uint256) (runs: 256, μ: 1224546, ~: 1229329)\n[PASS] testFuzz_slashSplit_exactToTheWei(uint96[5],uint8,uint8) (runs: 512, μ: 1443482, ~: 1442255)\n[PASS] testFuzz_tieKeepsKing_oneWeiMoreFlips(uint256) (runs: 256, μ: 1244503, ~: 1244520)\n[PASS] testFuzz_wrongBond(uint256) (runs: 256, μ: 44056, ~: 44203)\n[PASS] test_dethronedKing_canChallengeBackAfterMinReign() (gas: 799477)\n[PASS] test_dustStakes_lastClaimerTakesExactRemainder() (gas: 1182646)\n[PASS] test_finalize_isPermissionlessAndNotRepeatable() (gas: 510921)\n[PASS] test_noAdminFunctions() (gas: 219063)\n[PASS] test_registerKing_rejectsZeroTickerAndCrownsOtherTickersIndependently() (gas: 680484)\n[PASS] test_stake_invalidSideIsRefused() (gas: 336752)\n[PASS] test_timelock_windowClosedButNotFinalized_isStillLive() (gas: 972344)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 69.20ms (190.42ms CPU time)\n\nRan 21 tests for test/CrownRegistry.t.sol:CrownRegistryTest\n[PASS] testFuzz_conservation(uint64[3],uint64[3]) (runs: 256, μ: 1605410, ~: 1600720)\n[PASS] test_claim_failurePaths() (gas: 805772)\n[PASS] test_claim_reentrancyCannotDoublePay() (gas: 1035676)\n[PASS] test_dethrone_slashSplitExactToTheWei() (gas: 1103005)\n[PASS] test_failedChallenge_belowQuorumSlashesChallengersAndCoolsDown() (gas: 1177631)\n[PASS] test_finalize_timelockBoundary() (gas: 591921)\n[PASS] test_newKingMustReignSevenDays() (gas: 732817)\n[PASS] test_openChallenge_minimumReignBoundary() (gas: 322088)\n[PASS] test_openChallenge_oneLiveChallengePerTicker() (gas: 841188)\n[PASS] test_openChallenge_rejectsSameTokenWrongTickerAndNoKing() (gas: 652744)\n[PASS] test_openChallenge_requiresExactBond() (gas: 351811)\n[PASS] test_registerKing_crownsFirstRegistration() (gas: 20532)\n[PASS] test_registerKing_rejectsSecondRegistration() (gas: 38231)\n[PASS] test_registerKing_rejectsWrongSymbolZeroAndCodeless() (gas: 618238)\n[PASS] test_stake_windowBoundaryAndZero() (gas: 500164)\n[PASS] test_stakerOnBothSides_isPaidForBoth() (gas: 856136)\n[PASS] test_tie_keepsTheKing() (gas: 678566)\n[PASS] test_treasuryIsolatedPayment_isSelfOnlyAndFailedRetriesPreserveCredit() (gas: 772707)\n[PASS] test_treasuryThatRejectsEther_doesNotBlockFinalize() (gas: 622866)\n[PASS] test_unopposedChallengeAtQuorum_dethronesAndRefundsInFull() (gas: 657058)\n[PASS] test_unopposedChallengeBelowQuorum_sendsWholeSlashToTreasury() (gas: 526572)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 77.61ms (84.16ms CPU time)\n\nRan 1 test for test/CrownHook.invariant.t.sol:CrownHookInvariantTest\n[PASS]\nCrownHookInvariantTest invariants:\n[PASS] invariant_claimsAreBacked\n[PASS] invariant_crownPaidMatchesReceipts\n[PASS] invariant_everyCutExact\n[PASS] invariant_hookHoldsNothing\n[PASS] invariant_kingAlwaysSet\n CrownHookInvariantTest invariants (runs: 32, calls: 1280, reverts: 0)\n\n╭-------------+---------------+-------+---------+----------╮\n| Contract    | Selector      | Calls | Reverts | Discards |\n+==========================================================+\n| HookHandler | buyExactIn    | 134   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | buyExactOut   | 131   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | challenge     | 133   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | finalize      | 108   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | redeem        | 138   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | sellExactIn   | 134   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | sellExactOut  | 123   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | stake         | 123   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | toggleRejects | 120   | 0       | 0        |\n|-------------+---------------+-------+---------+----------|\n| HookHandler | warp          | 136   | 0       | 0        |\n╰-------------+---------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 286.40ms (281.60ms CPU time)\n\nRan 1 test for test/CrownRegistry.invariant.t.sol:CrownRegistryInvariantTest\n[PASS]\nCrownRegistryInvariantTest invariants:\n[PASS] invariant_balanceEqualsObligations\n[PASS] invariant_claimRulesHold\n[PASS] invariant_ethIsConserved\n[PASS] invariant_resolutionMatchesRule\n[PASS] invariant_tickerTotalsAndKings\n[PASS] invariant_treasuryArrearsOnlyWhileRejecting\n CrownRegistryInvariantTest invariants (runs: 48, calls: 2880, reverts: 0)\n\n╭-----------------+----------------+-------+---------+----------╮\n| Contract        | Selector       | Calls | Reverts | Discards |\n+===============================================================+\n| RegistryHandler | claim          | 418   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | finalize       | 401   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | openChallenge  | 408   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | payTreasury    | 408   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | stake          | 420   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | toggleTreasury | 390   | 0       | 0        |\n|-----------------+----------------+-------+---------+----------|\n| RegistryHandler | warp           | 435   | 0       | 0        |\n╰-----------------+----------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 465.95ms (461.53ms CPU time)\n\nRan 7 test suites in 468.65ms (1.02s CPU time): 67 tests passed, 0 failed, 0 skipped (67 total tests)\n","passed":true},{"durationMs":66,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"CrownChallengerToken.approve(address,uint256)\",\"CrownChallengerToken.transfer(address,uint256)\",\"CrownChallengerToken.transferFrom(address,address,uint256)\",\"CrownHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"CrownHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"CrownHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"CrownHook.configurePool((address,address,uint24,int24,address),bytes32,uint16)\",\"CrownHook.payCrown(address,address,uint256)\",\"CrownHook.redeemClaim(address,uint256)\",\"CrownHook.unlockCallback(bytes)\",\"CrownRegistry.claim(uint256)\",\"CrownRegistry.finalize(uint256)\",\"CrownRegistry.openChallenge(bytes32,address)\",\"CrownRegistry.payTreasury()\",\"CrownRegistry.payTreasuryIsolated()\",\"CrownRegistry.registerKing(bytes32,address,address)\",\"CrownRegistry.stake(uint256,uint8)\",\"CrownToken.approve(address,uint256)\",\"CrownToken.transfer(address,uint256)\",\"CrownToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"DESIGN.md\":83,\"README.md\":273,\"REVIEW.md\":31,\"VALIDATION.md\":116,\"dist/abi/CrownChallengerToken.json\":1,\"dist/abi/CrownHook.json\":1,\"dist/abi/CrownRegistry.json\":1,\"dist/abi/CrownToken.json\":1,\"dist/abi/PoolManager.json\":1,\"dist/assets/ccip-uVUi-UK6.js\":1,\"dist/assets/dist-iVAoPyTN.js\":3,\"dist/assets/index-DK2JiM0w.css\":1,\"dist/assets/index-pUUlmf3L.js\":47,\"dist/crown-config.json\":16,\"dist/favicon.svg\":1,\"dist/index.html\":21,\"dist/social.png\":99,\"dist/wallets/browser.svg\":1,\"dist/wallets/coinbase.svg\":1,\"dist/wallets/metamask.svg\":1,\"dist/wallets/phantom.svg\":1,\"dist/wallets/rabby.svg\":1,\"foundry.toml\":19,\"remappings.txt\":4,\"script/DeployChallenger.s.sol\":21,\"script/DeployCrown.s.sol\":49,\"site/README.md\":83,\"site/WALLET-ICONS-LICENSE.txt\":9,\"site/design/social.svg\":1,\"site/index.html\":20,\"site/package-lock.json\":1718,\"site/package.json\":16,\"site/public/abi/CrownChallengerToken.json\":1,\"site/public/abi/CrownHook.json\":1,\"site/public/abi/CrownRegistry.json\":1,\"site/public/abi/CrownToken.json\":1,\"site/public/abi/PoolManager.json\":1,\"site/public/crown-config.json\":16,\"site/public/favicon.svg\":1,\"site/public/social.png\":99,\"site/public/wallets/browser.svg\":1,\"site/public/wallets/coinbase.svg\":1,\"site/public/wallets/metamask.svg\":1,\"site/public/wallets/phantom.svg\":1,\"site/public/wallets/rabby.svg\":1,\"site/scripts/abis.mjs\":13,\"site/scripts/export.mjs\":60,\"site/scripts/social.mjs\":13,\"site/src/App.tsx\":173,\"site/src/OwnerPanel.tsx\":26,\"site/src/Transaction.tsx\":108,\"site/src/Wallet.tsx\":73,\"site/src/config.ts\":78,\"site/src/domain.ts\":70,\"site/src/main.tsx\":13,\"site/src/styles.css\":188,\"site/src/useCrown.ts\":69,\"site/tests/export.mjs\":41,\"site/tests/interaction.mjs\":281,\"site/tsconfig.json\":10,\"site/vite.config.ts\":8,\"src/CrownChallengerToken.sol\":19,\"src/CrownHook.sol\":315,\"src/CrownRegistry.sol\":340,\"src/CrownToken.sol\":15,\"src/HookFlags.sol\":34,\"src/libraries/Ticker.sol\":42,\"test/CrownHook.invariant.t.sol\":289,\"test/CrownHook.t.sol\":356,\"test/CrownHookEdge.t.sol\":207,\"test/CrownMath.t.sol\":322,\"test/CrownRegistry.invariant.t.sol\":319,\"test/CrownRegistry.t.sol\":405,\"test/CrownToken.t.sol\":65,\"test/floor/OwnerChecks.sol\":23,\"test/mocks/MockERC20.sol\":11,\"test/mocks/MultiSwapper.sol\":65,\"test/mocks/Swapper.sol\":79,\"test/utils/CrownFixture.sol\":64},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":3106,"exitCode":0,"name":"slither","output":"[medium/medium] uninitialized-local at src/CrownHook.sol:192: CrownHook._crown(PoolKey,uint256).beneficiary (src/CrownHook.sol#192) is a local variable never initialized\n[medium/medium] uninitialized-local at src/CrownHook.sol:203: CrownHook._crown(PoolKey,uint256).paid (src/CrownHook.sol#203) is a local variable never initialized\n[medium/medium] uninitialized-local at src/CrownHook.sol:202: CrownHook._crown(PoolKey,uint256).asClaim (src/CrownHook.sol#202) is a local variable never initialized\n[medium/medium] unused-return at src/CrownHook.sol:241: CrownHook.redeemClaim(Currency,uint256) (src/CrownHook.sol#241-243) ignores return value by poolManager.unlock(abi.encode(msg.sender,currency,amount)) (src/CrownHook.sol#242)\n[low/medium] reentrancy-benign at src/CrownHook.sol:185: Reentrancy in CrownHook._crown(PoolKey,uint256) (src/CrownHook.sol#185-222):\n[low/medium] reentrancy-events at src/CrownHook.sol:185: Reentrancy in CrownHook._crown(PoolKey,uint256) (src/CrownHook.sol#185-222):\n[low/medium] reentrancy-events at src/CrownRegistry.sol:301: Reentrancy in CrownRegistry.payTreasuryIsolated() (src/CrownRegistry.sol#301-308):\n[low/medium] reentrancy-events at src/CrownHook.sol:131: Reentrancy in CrownHook.afterInitialize(address,PoolKey,uint160,int24) (src/CrownHook.sol#131-150):\n[low/medium] timestamp at src/CrownRegistry.sol:177: CrownRegistry.openChallenge(bytes32,address) (src/CrownRegistry.sol#177-204) uses timestamp for comparisons\n[low/medium] timestamp at src/CrownRegistry.sol:167: CrownRegistry.registerKing(bytes32,address,address) (src/CrownRegistry.sol#167-173) uses timestamp for comparisons\n[low/medium] timestamp at src/CrownRegistry.sol:218: CrownRegistry.finalize(uint256) (src/CrownRegistry.sol#218-266) uses timestamp for comparisons\n[low/medium] timestamp at src/CrownRegistry.sol:207: CrownRegistry.stake(uint256,CrownRegistry.Side) (src/CrownRegistry.sol#207-212) uses timestamp for comparisons","passed":true},{"durationMs":557,"exitCode":0,"name":"aderyn","output":"[high] eth-send-unchecked-address at src/CrownRegistry.sol:269: ETH transferred without address checks\n[low] internal-function-used-once at src/HookFlags.sol:27: Internal Function Used Only Once (2 places)\n[low] large-numeric-literal at src/CrownChallengerToken.sol:11: Large Numeric Literal (8 places)\n[low] literal-instead-of-constant at src/libraries/Ticker.sol:25: Literal Instead of Constant (3 places)\n[low] local-variable-shadowing at src/CrownRegistry.sol:46: Local Variable Shadows State Variable\n[low] state-change-without-event at src/CrownRegistry.sol:295: State Change Without Event\n[low] unchecked-return at src/CrownHook.sol:242: Unchecked Return\n[low] unused-public-function at src/CrownHook.sol:95: Public Function Not Used Internally\n[low] unused-state-variable at src/CrownHook.sol:39: Unused State Variable (3 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"f17066ebcac860b64c1c72b38cef7c5a071ca380777a549b4928217758ac6cac","verifiedTreeHash":"20dbfd8c681b5379cf3742ccc921a78e07dabff8","verifierVersion":"0.1.0+8fbf00a5"}]}