{"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":"69561148-b678-420c-bf0b-3b6fb49af48f","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"b081e2a68801e9e5eb56cbf3552473409a594acbe4be7360d395fa1509c4684c","dependsOn":["build_contract_project","write_foundry_tests","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":"8d8a7898679158a944d5352ae1adc68b2482ab1588bdd7c771af9eec5cda3e43","dependsOn":["build_contract_project","write_foundry_tests","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":"e87d879efda9203e7475d829d7b73b81cd2d5475f7eccb94619761db1c4cd75d","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"032e94b9572741badfb4a01ce8c144bf8fc12f6728a8f3d07146102026dcdf43","dependsOn":["build_contract_project","write_foundry_tests","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":"31470919fd46ee3bab1b2203fd6c8460217c7f166a5d2cf6a2d310199e15b5f8","dependsOn":["build_contract_project","write_foundry_tests","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":"db7e512d7d2d06b88bbe07cdd3dd529ecedbe0b8eff2adc6bcadc406c2401df6","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"6e3e328aa196f6965a2821b098b101e9ff09eef55cdaafaec1d6ea18007b96f6","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"6e3e328aa196f6965a2821b098b101e9ff09eef55cdaafaec1d6ea18007b96f6","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"5c231cb9328ce547777c9e88feeb8ad76005d4fe7c59749454f5939b22d6cfda","dependsOn":["build_contract_project","write_foundry_tests"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"74a651ad2c23fcef5955843dfde0738236ab48b62ebbcff4dec923b15b44c427","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","state":"accepted"}],"objective":"Build the Quantum Canary hook: a Uniswap v4 hook whose only purpose is to feed, forever and without any owner, the ETH bounty of the Quantum Canary, an immutable contract already live on Ethereum mainnet at 0x626517225096671868609c1bbf9c1b416a4efd9a. That contract exposes canaryAddress() (the address whose secp256k1 private key nobody knows; ETH sent there can only ever be moved by a quantum computer) and isTripped() (true once that balance has dropped below its threshold).\n\nToken: the standard launch token, name \"Quantum Canary\", symbol \"CANARY\", 1,000,000,000 fixed supply, plain transfers. Pool paired with native ETH.\n\nHook, named QuantumCanaryHook:\n1. Callbacks: use whatever hook permissions the launch's protected checks require (beforeInitialize or afterInitialize for the pool guard) plus beforeSwap and afterSwap with beforeSwapReturnDelta and afterSwapReturnDelta, so that the fee can be taken on the ETH side in all four cases: exact-input and exact-output, buy and sell. The invariant is the economics, not the callback set: every swap pays a flat 1% of the ETH side of the trade (1% of the ETH paid on a buy, 1% of the ETH received on a sell), always in native ETH, never in CANARY. Choose the delta mechanism that makes this hold and document it.\nSettlement: because the PoolManager may not yet hold ETH during the callback (a fresh pool seeded only with CANARY, router settling after the swap), the hook may accrue the fee as ERC-6909 claims on native ETH held by the hook inside the PoolManager. Provide a permissionless payout() that burns the accrued claims and sends the ETH to the canary address, callable by anyone at any time, and attempt that payout automatically at the end of each swap when the PoolManager's ETH balance allows it (never revert the swap because of a payout). The hook must never be able to send ETH or tokens anywhere except the canary address; it has no withdraw function and holds nothing but those claims between transactions.\n2. Constructor arguments: the PoolManager and the Quantum Canary contract address (static addresses). It stores both as immutables and reads canaryAddress() once in the constructor into an immutable.\n3. No owner, no admin, no fee setter, no pause, no upgrade, no selfdestruct, no delegatecall. The fee percentage is a constant.\n4. Retirement: when isTripped() on the Quantum Canary returns true, the hook stops taking any fee and swaps proceed untouched for ever after; the canary has done its job. Read isTripped() in afterSwap with a staticcall guarded so that a revert of the canary never blocks swaps (treat a revert as not tripped).\n5. Callbacks refuse any caller other than the PoolManager; permissions match the declared flags; the hook must work with the launch pool's fee tier and tick spacing chosen by the policy.\n6. Tests: fee goes to the canary address on buys and sells in all four swap modes, exact amounts, after payout; accrued claims can only ever reach the canary address; no fee after a simulated trip (mock canary contract); callbacks reject non-PoolManager callers; fuzz swap sizes.\n7. README: explain in plain language that 100% of the hook fee goes to the Quantum Canary bounty, that no human can redirect or withdraw it, that the requester keeps 0% of the supply, and how anyone can verify the flow on chain.","parentJobId":null,"planHash":"df5511caab4cffc056de5eea29c009fdf08e1f8e6a294a47f26112364af3c931","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"69561148-b678-420c-bf0b-3b6fb49af48f","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1235-src-quantumcanaryhook-sol"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51018","feedbackHash":"ae107096d81df88e854f8db0034e163abceb37b01f120b52b8f6eae4ecf1e710","nodeKey":"audit_economics","submissionHash":"b081e2a68801e9e5eb56cbf3552473409a594acbe4be7360d395fa1509c4684c","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52222","feedbackHash":"5dec189d56df37deca15807bb442884cdf301425280089aa8fb2a4867379cd4f","nodeKey":"audit_flow","submissionHash":"8d8a7898679158a944d5352ae1adc68b2482ab1588bdd7c771af9eec5cda3e43","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52216","feedbackHash":"9985d52df20dc153b1e1647805faed4ad7b86e1a8e0936b56081f8a5a7b3b09a","nodeKey":"audit_judge","submissionHash":"186088b5917555a8bd68d562847cf08dc5389cd7bbcb1b119f2f80e14c18b5c2","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52165","feedbackHash":"21e5b531ce9872b53c440d523e58a1d04e8781b44001a4e2ac89bebf77bcffa0","nodeKey":"audit_judge","submissionHash":"e87d879efda9203e7475d829d7b73b81cd2d5475f7eccb94619761db1c4cd75d","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51558","feedbackHash":"4bd655a216df953418c909eca247f07c20d7f0ea3cbd3d068fdcd957f346424e","nodeKey":"audit_math","submissionHash":"032e94b9572741badfb4a01ce8c144bf8fc12f6728a8f3d07146102026dcdf43","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52210","feedbackHash":"bf3978944cdac65b2ab8b018597600b184a33d6bb757249b20dc983256a7bbca","nodeKey":"audit_permissions","submissionHash":"31470919fd46ee3bab1b2203fd6c8460217c7f166a5d2cf6a2d310199e15b5f8","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51236","feedbackHash":"b61b1efc1e77ace10d9b16fb42ea5343b0749770ab948db043a8524e3328fb60","nodeKey":"build_contract_project","submissionHash":"10d045f022228148f6be7e89d97598812c846cc31d5fa48691bd3f60f56d6630","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51558","feedbackHash":"c4d682dbad1adf0d01864dbeb106c7ce915af4d2c24f1ccfbdc65c8d6110172c","nodeKey":"build_contract_project","submissionHash":"db7e512d7d2d06b88bbe07cdd3dd529ecedbe0b8eff2adc6bcadc406c2401df6","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51135","feedbackHash":"b21022d46da82b8d9633a39a36132045f25e784e63351564c442ccb5d28b66b4","nodeKey":"manifest","submissionHash":"5c231cb9328ce547777c9e88feeb8ad76005d4fe7c59749454f5939b22d6cfda","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51266","feedbackHash":"652bcffc467d296eb40ed0752c734c877d830523b3bea15d77e368210571c4ce","nodeKey":"manifest","submissionHash":"f15c36227295ba96852e22b0bc492099033252c81d7aeb61abe32f00d7756d65","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51200","feedbackHash":"ee98c2250004a5aba2aaf88664a098d656e921fa706d7cc606768cee6966c3cc","nodeKey":"write_foundry_tests","submissionHash":"af08fd4cc8b4c4ffa68c86b785d184d15ea8ed3a161543356de3fb932b4dfdd2","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52557","feedbackHash":"d96c07780d89a0aa01fef35c5e6aaab4a421647c10822215cd112e0c6f420ea3","nodeKey":"write_foundry_tests","submissionHash":"74a651ad2c23fcef5955843dfde0738236ab48b62ebbcff4dec923b15b44c427","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"86bea18f203b6299f6df3bf98109d89a3a44076617d6642ea9355be243f3a55b","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"507b07815bc3bf50","findings":[{"citation":"resolved","description":"Seam: boundary x invariant (Numerical Gap guide) and Boundary guide step 2.4 (return value consumed by caller logic). The live observer at 0x626517225096671868609c1bbf9c1b416a4efd9a (source published by the requester, linked from README.md) implements isTripped() as a LIVE predicate: `canaryAddress.balance < thresholdWei && highWaterMark >= thresholdWei`, deliberately not latched; thresholdWei is 1 ether on mainnet. The hook latches `retired` only when a SWAP callback (beforeSwap/afterSwap -> _observeTrip) sees true. payout() (lines 158-170) and unlockCallback/_redeem never call _observeTrip, yet their only effect is to send ETH to canaryAddress, i.e. to raise exactly the balance the predicate reads. Once the bounty has been drained below the threshold (the event the whole design exists for), a single permissionless payout() with >= 1 ether of pending claims lifts the balance back over thresholdWei, isTripped() returns false again, and the next swap charges the 1% fee as usual and pays it to the address whose key is now known to the attacker. The brief's retirement guarantee ('when isTripped() returns true the hook stops taking any fee ... for ever after') is broken by the hook's own action. Pending claims >= thresholdWei are an ordinary state: every fee-bearing swap whose afterSwap found no ETH in the manager (launch pool seeded with CANARY only, first buys), or found gasleft() < 170_000, or whose payout self-call failed, leaves its fee as claims; the auto-payout in afterSwap reads the trip BEFORE paying (line 132 then 147), but the standalone payout() does not. The attacker holding the key can also trigger payout() themselves right after draining. Minimal fix preserving the design: call _observeTrip() at the top of payout() (and in unlockCallback before _redeem) so a drained bounty is latched before the refill; the ETH still goes only to canaryAddress as the brief requires. Related but outside the hook's control: with a live predicate, a key-holder who leaves >= thresholdWei in the address is never reported as tripped at all.","line":158,"path":"src/QuantumCanaryHook.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 {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {QuantumCanaryHook} from \"src/QuantumCanaryHook.sol\";\nimport {QuantumCanaryToken} from \"src/QuantumCanaryToken.sol\";\nimport {IQuantumCanary} from \"src/IQuantumCanary.sol\";\n\n/// @dev Verbatim logic of the live observer at 0x626517225096671868609c1bBF9c1B416A4eFd9a\n/// (source published by the requester at github.com/identity-md-launches/launch-1213-src-quantumcanary-sol):\n/// isTripped() is a LIVE predicate on canaryAddress.balance, not a latch.\ncontract LiveLogicCanary is IQuantumCanary {\n    address public immutable canaryAddress;\n    uint256 public immutable thresholdWei;\n    uint256 public highWaterMark;\n    bool private tripRecorded;\n    uint256 public trippedAt;\n\n    constructor(address canary, uint256 thresholdWei_) {\n        canaryAddress = canary;\n        thresholdWei = thresholdWei_;\n        highWaterMark = canary.balance;\n    }\n\n    function isTripped() external view returns (bool) {\n        return canaryAddress.balance < thresholdWei && highWaterMark >= thresholdWei;\n    }\n\n    function poke() external {\n        uint256 balance = canaryAddress.balance;\n        uint256 mark = highWaterMark;\n        if (balance > mark) {\n            highWaterMark = balance;\n            mark = balance;\n        }\n        if (!tripRecorded && balance < thresholdWei && mark >= thresholdWei) {\n            tripRecorded = true;\n            trippedAt = block.timestamp;\n        }\n    }\n}\n\ncontract RefillRaceTest is Test {\n    uint160 constant Q96 = 79228162514264337593543950336;\n    IPoolManager manager;\n    QuantumCanaryHook hook;\n    QuantumCanaryToken token;\n    LiveLogicCanary canary;\n    PoolKey key;\n    address bounty;\n\n    receive() external payable {}\n\n    function setUp() public {\n        vm.deal(address(this), 10_000_000 ether);\n        bounty = makeAddr(\"derived secp256k1 bounty address\");\n        token = new QuantumCanaryToken();\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        canary = new LiveLogicCanary(bounty, 1 ether);\n        hook = _mine();\n        key = PoolKey(Currency.wrap(address(0)), Currency.wrap(address(token)), 12_500, 60, IHooks(address(hook)));\n        manager.initialize(key, Q96);\n        // Launch-style seeding: CANARY only, no ETH in the manager.\n        _liquidity(-600, -60, 100_000 ether);\n        assertEq(address(manager).balance, 0);\n    }\n\n    function _mine() internal returns (QuantumCanaryHook deployed) {\n        bytes memory init = abi.encodePacked(type(QuantumCanaryHook).creationCode, abi.encode(manager, canary));\n        bytes32 hash = keccak256(init);\n        for (uint256 i; i < 300_000; ++i) {\n            address predicted =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), hash)))));\n            if (uint160(predicted) & 0x3fff == 0x20cc) {\n                return new QuantumCanaryHook{salt: bytes32(i)}(manager, canary);\n            }\n        }\n        revert(\"no salt\");\n    }\n\n    function _liquidity(int24 lower, int24 upper, int256 amount) internal {\n        manager.unlock(abi.encode(false, ModifyLiquidityParams(lower, upper, amount, bytes32(0)), SwapParams(false, 0, 0)));\n    }\n\n    function _swap(SwapParams memory p) internal returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(true, ModifyLiquidityParams(0, 0, 0, bytes32(0)), p)), (BalanceDelta));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (bool swapping, ModifyLiquidityParams memory lp, SwapParams memory sp) =\n            abi.decode(data, (bool, ModifyLiquidityParams, SwapParams));\n        BalanceDelta delta;\n        if (swapping) delta = manager.swap(key, sp, \"\");\n        else (delta,) = manager.modifyLiquidity(key, lp, \"\");\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) internal {\n        if (amount < 0) {\n            uint256 owed = uint256(-int256(amount));\n            manager.sync(c);\n            if (c.isAddressZero()) manager.settle{value: owed}();\n            else {\n                token.transfer(address(manager), owed);\n                manager.settle();\n            }\n        } else if (amount > 0) {\n            manager.take(c, address(this), uint256(int256(amount)));\n        }\n    }\n\n    /// The bounty is armed (funded >= threshold and poked). The first launch buy accrues 1 ETH of\n    /// claims because the manager held no ETH during the callback. A quantum attacker then drains\n    /// the bounty: isTripped() == true. Before any swap observes it, anyone calls payout(): the\n    /// hook's own 1 ETH refill pushes the balance back over the threshold, isTripped() flips to\n    /// false, and the next swap still charges the bounty fee to the now attacker-controlled address.\n    function test_payoutRefillClearsLiveAlarmBeforeHookObservesIt() public {\n        vm.deal(bounty, 1 ether);\n        canary.poke();\n        assertFalse(canary.isTripped());\n\n        // First buy on the fresh pool: fee 1 ETH accrues as claims (manager had no ETH in afterSwap).\n        _swap(SwapParams(true, -100 ether, TickMath.MIN_SQRT_PRICE + 1));\n        assertEq(hook.accruedFees(), 1 ether, \"claims pending\");\n        assertEq(bounty.balance, 1 ether);\n\n        // Quantum attacker drains the bounty.\n        vm.deal(bounty, 0);\n        assertTrue(canary.isTripped(), \"live alarm is on\");\n        canary.poke();\n        assertGt(canary.trippedAt(), 0, \"observer records the trip permanently\");\n\n        // Anyone (or the attacker) calls the permissionless payout before a swap observes the trip.\n        address anyone = makeAddr(\"anyone\");\n        vm.prank(anyone);\n        assertEq(hook.payout(), 1 ether);\n        assertEq(bounty.balance, 1 ether);\n        assertFalse(canary.isTripped(), \"hook's own refill cleared the live alarm\");\n\n        // Next swap: the hook should have retired; instead it charges the fee again.\n        uint256 before = bounty.balance;\n        _swap(SwapParams(true, -1 ether, TickMath.MIN_SQRT_PRICE + 1));\n        uint256 feeAfterTrip = bounty.balance - before + hook.accruedFees();\n        emit log_named_uint(\"fee charged after recorded trip\", feeAfterTrip);\n        assertTrue(hook.retired(), \"hook must retire after the recorded trip\");\n        assertEq(feeAfterTrip, 0, \"no fee after a trip\");\n    }\n\n    /// Control: the same drain, observed by a swap before any payout, retires the hook.\n    function test_controlSwapBeforePayoutRetires() public {\n        vm.deal(bounty, 1 ether);\n        canary.poke();\n        _swap(SwapParams(true, -100 ether, TickMath.MIN_SQRT_PRICE + 1));\n        vm.deal(bounty, 0);\n        _swap(SwapParams(true, -1 ether, TickMath.MIN_SQRT_PRICE + 1));\n        assertTrue(hook.retired());\n    }\n}","reproduction":"State: fresh ETH/CANARY pool seeded with CANARY only (manager ETH balance 0); observer with thresholdWei = 1 ether, canaryAddress funded with 1 ether and poke()d (highWaterMark = 1 ether). 1) Buy exact-input 100 ether (zeroForOne, amountSpecified = -100e18, wide limit): fee = 1 ether is minted as claims because address(poolManager).balance == 0 during afterSwap; hook.accruedFees() == 1e18. 2) Drain the bounty (vm.deal(canaryAddress, 0) stands in for the quantum key-holder): observer.isTripped() == true; observer.poke() records trippedAt > 0 permanently. 3) Any address calls hook.payout(): returns 1e18, canaryAddress.balance == 1 ether, observer.isTripped() == false. 4) Buy exact-input 1 ether. Expected: hook.retired() == true and fee 0 (bounty tripped). Actual: hook.retired() == false and 0.01 ether (10000000000000000 wei) is charged and paid to the drained address. test/scratch/RefillRace.t.sol fails at 'hook must retire after the recorded trip'; the control test where a swap precedes the payout retires correctly. Verified on a temporary patch: adding _observeTrip() at the top of payout() makes the proof pass and the control still pass.","severity":"medium","snippet":"    function payout() external returns (uint256 paid) {\n        if (_load(PAYING_SLOT) != 0) return 0;\n        uint256 beforeClaims = accruedFees();\n        if (beforeClaims == 0 || address(poolManager).balance == 0) return 0;\n        if (poolManager.isUnlocked()) {","title":"Permissionless payout() refills the canary address without observing the trip, so the hook's own transfer can clear the live isTripped() alarm and fees keep flowing to the compromised address"},{"citation":"resolved","description":"Boundary guide step 2.3 (max input: math overflows before the check) and Math Precision 'overflow intermediates'. For an ETH-specified exact-output sell the hook grosses the request up before quoting: reserved = amountSpecified / 99 and quotedParams.amountSpecified += int256(reserved). For any amountSpecified > type(int256).max * 99 / 100 (about 5.7e76) the checked addition overflows and beforeSwap reverts with Panic(0x11); PoolManager wraps it as WrappedError(hook, beforeSwap selector, Panic(0x11), HookCallFailed). An ordinary v4 pool accepts the same SwapParams and partial-fills to the price limit (core only casts the FILLED amount to int128), and so does this very hook once retired (no quote path). 'Sell as much as the price limit allows' with a saturating amount is a common idiom for swap-to-price routers and arbitrage bots, so the hooked pool rejects a request the fee logic could serve: the fill is bounded by the limit, the quote would report the capacity, and fee = floor(capacity / 100) is well defined. Fix: saturate the quoted request (e.g. cap amountSpecified + reserved at type(int128).max, which is already the largest delta core can return) or compute reserved with unchecked saturation.","line":116,"path":"src/QuantumCanaryHook.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 {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {QuantumCanaryHook} from \"src/QuantumCanaryHook.sol\";\nimport {QuantumCanaryToken} from \"src/QuantumCanaryToken.sol\";\nimport {IQuantumCanary} from \"src/IQuantumCanary.sol\";\n\ncontract ScratchCanary is IQuantumCanary {\n    address public canaryAddress;\n    bool public tripped;\n\n    constructor(address d) {\n        canaryAddress = d;\n    }\n\n    function setTripped(bool v) external {\n        tripped = v;\n    }\n\n    function isTripped() external view returns (bool) {\n        return tripped;\n    }\n}\n\n/// @dev An exact-output sell asking for \"as much ETH as the price limit allows\" with\n/// amountSpecified near type(int256).max is a partial fill on an ordinary v4 pool and on this\n/// hook once retired, but reverts with Panic(0x11) inside beforeSwap while the hook is active:\n/// `quotedParams.amountSpecified += int256(reserved)` overflows before any quote runs.\ncontract ExactOutputSellOverflowTest is Test {\n    uint160 constant Q96 = 79228162514264337593543950336;\n    IPoolManager manager;\n    QuantumCanaryHook hook;\n    QuantumCanaryToken token;\n    ScratchCanary canary;\n    PoolKey key;\n    PoolKey plain;\n\n    receive() external payable {}\n\n    function setUp() public {\n        vm.deal(address(this), 10_000_000 ether);\n        token = new QuantumCanaryToken();\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        canary = new ScratchCanary(makeAddr(\"bounty\"));\n        hook = _mine();\n        key = PoolKey(Currency.wrap(address(0)), Currency.wrap(address(token)), 12_500, 60, IHooks(address(hook)));\n        manager.initialize(key, Q96);\n        plain = key;\n        plain.hooks = IHooks(address(0));\n        manager.initialize(plain, Q96);\n        _liquidity(key, -600, 600, 100_000 ether);\n        _liquidity(plain, -600, 600, 100_000 ether);\n    }\n\n    function _mine() internal returns (QuantumCanaryHook deployed) {\n        bytes memory init = abi.encodePacked(type(QuantumCanaryHook).creationCode, abi.encode(manager, canary));\n        bytes32 hash = keccak256(init);\n        for (uint256 i; i < 300_000; ++i) {\n            address predicted =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), hash)))));\n            if (uint160(predicted) & 0x3fff == 0x20cc) {\n                return new QuantumCanaryHook{salt: bytes32(i)}(manager, canary);\n            }\n        }\n        revert(\"no salt\");\n    }\n\n    function _liquidity(PoolKey memory k, int24 lower, int24 upper, int256 amount) internal {\n        manager.unlock(\n            abi.encode(false, k, ModifyLiquidityParams(lower, upper, amount, bytes32(0)), SwapParams(false, 0, 0))\n        );\n    }\n\n    function _swap(PoolKey memory k, SwapParams memory p) internal returns (BalanceDelta) {\n        return abi.decode(\n            manager.unlock(abi.encode(true, k, ModifyLiquidityParams(0, 0, 0, bytes32(0)), p)), (BalanceDelta)\n        );\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (bool swapping, PoolKey memory k, ModifyLiquidityParams memory lp, SwapParams memory sp) =\n            abi.decode(data, (bool, PoolKey, ModifyLiquidityParams, SwapParams));\n        BalanceDelta delta;\n        if (swapping) delta = manager.swap(k, sp, \"\");\n        else (delta,) = manager.modifyLiquidity(k, lp, \"\");\n        _settle(k.currency0, delta.amount0());\n        _settle(k.currency1, delta.amount1());\n        return abi.encode(delta);\n    }\n\n    function _settle(Currency c, int128 amount) internal {\n        if (amount < 0) {\n            uint256 owed = uint256(-int256(amount));\n            manager.sync(c);\n            if (c.isAddressZero()) manager.settle{value: owed}();\n            else {\n                token.transfer(address(manager), owed);\n                manager.settle();\n            }\n        } else if (amount > 0) {\n            manager.take(c, address(this), uint256(int256(amount)));\n        }\n    }\n\n    function test_exactOutputSellToPriceLimitWithMaxAmountMatchesUnhookedPool() public {\n        uint160 limit = TickMath.getSqrtPriceAtTick(60);\n        // Leaves room for core's own `amountToSwap += hookDeltaSpecified`; only the hook's gross-up overflows.\n        int256 saturating = type(int256).max - 1e30;\n        BalanceDelta plainDelta = _swap(plain, SwapParams(false, saturating, limit));\n        assertGt(plainDelta.amount0(), 0, \"ordinary pool partial-fills to the price limit\");\n\n        // Same request on the hooked pool: must trade (fee on the gross ETH), not revert.\n        BalanceDelta actual = _swap(key, SwapParams(false, saturating, limit));\n        uint256 gross = uint256(int256(plainDelta.amount0()));\n        assertEq(actual.amount0(), int256(gross) - int256(gross / 100), \"1% of gross ETH to the bounty\");\n        assertEq(actual.amount1(), plainDelta.amount1(), \"CANARY input unchanged\");\n    }\n}","reproduction":"Pool ETH/CANARY 12500/60 at sqrtPrice 2^96 with 100_000e18 liquidity on [-600, 600] (same for an unhooked reference pool). SwapParams(zeroForOne=false, amountSpecified=type(int256).max - 1e30, sqrtPriceLimitX96=TickMath.getSqrtPriceAtTick(60)) (the 1e30 margin leaves room for core's own `amountToSwap += hookDeltaSpecified`; any amountSpecified > type(int256).max*99/100 triggers the hook overflow, and type(int256).max itself also reproduces it). Unhooked pool: succeeds, amount0 = +299535495591078093767, amount1 = -304238386100448167492. Hooked pool after a canary trip (retired, no quote): succeeds with the same amount0. Hooked pool while active: manager.swap reverts with WrappedError(hook, 0x575e24b4 (beforeSwap), 0x4e487b71...11 (Panic arithmetic overflow), 0xa9e35b2f (HookCallFailed)) at `quotedParams.amountSpecified += int256(reserved)`. amountSpecified = type(int256).max / 100 * 99 succeeds, locating the overflow at amountSpecified + amountSpecified/99 > type(int256).max. test/scratch/ExactOutputSellOverflow.t.sol fails on this code and passes once the quoted request saturates at type(int128).max (verified on a temporary patch): the hooked pool then partial-fills with amount0 = gross - floor(gross/100).","severity":"low","snippet":"                uint256 reserved = uint256(params.amountSpecified) / 99;\n                quotedParams.amountSpecified += int256(reserved);","title":"Exact-output sell with amountSpecified near type(int256).max reverts with Panic(0x11) in beforeSwap instead of partial-filling like the unhooked pool and the retired hook"}],"hash":"032e94b9572741badfb4a01ce8c144bf8fc12f6728a8f3d07146102026dcdf43","nodeId":"74af6173-edb6-43a8-9479-91daa592bd5a","outcome":"completed","summary":"Review complete. The findings file is written, the two proof tests fail on the current tree and pass on a temporary patch that was reverted, and no tracked file changed.\n\n**Outcome**\n\nThe fee arithmetic itself holds. I hand-proved, for every residue class, that each of the four modes charges exactly floor(1% of the ETH side), including partial fills at a price limit, and that the hook's ETH delta nets to zero on every path. Two defects survived verification, both written to `.imd-findings.json` with proofs under `test/scratch/`.\n\n- **Medium: `payout()` refills the live alarm without observing the trip.** The live observer's `isTripped()` is a non-latched balance predicate. The hook latches retirement only inside swap callbacks, but the permissionless `payout()` sends ETH to the same address the predicate reads. With 1 ETH of pending claims, a drained bounty is \"un-tripped\" by the hook's own transfer, and the next swap still pays 1% to the compromised address. Minimal fix: call the trip observer at the top of `payout()`.\n- **Low: exact-output sell overflow at the int256 boundary.** The gross-up addition in `beforeSwap` panics for any requested output above roughly 0.99 times the int256 maximum. The unhooked pool and the retired hook both partial-fill the same request. Fix: saturate the quoted request at the int128 maximum.\n\n**Leads checked and dropped**\n\n- Auto-payout gas reserve: minimum passing gas with and without a payout attempt differs by about 1.2k, so no out-of-gas window exists.\n- Trip-read gas budget: the live observer's read is one balance lookup plus one storage read, well inside 30k. Source came from the requester's repository, not a bytecode match.\n- All three other int256 extremes partial-fill correctly.\n- Fee rounds down, dust only, non-compounding; not reported.\n\n**Coverage**: all nine entry points have rows, plus five invariant and boundary rows. Unverified against live state: the observer bytecode, and any fork run.","treeHash":null,"usage":{"cachedInputTokens":2906495,"inputTokens":610,"model":"claude-fable-5-1","outputTokens":75379,"runtime":"claude","turns":44,"wallClockMs":1047657}},{"artifacts":[],"attempt":1,"bundleHash":"59426b254e68fe07c7ca8a2f6091871898a7cd114c7ee10b25c7c1713db55590","device":"08261d0cc6850daf","findings":[],"hash":"10d045f022228148f6be7e89d97598812c846cc31d5fa48691bd3f60f56d6630","nodeId":"07ba0542-b044-4dda-a72e-198e766e06cb","outcome":"completed","summary":"Implemented the ownerless hook, fixed-supply token, ETH claim payouts, permanent retirement, and documentation.\n\nVerified: `forge build`, `forge test`, and `forge fmt --check` pass. Offline: 34 tests passed; optional fork suite skipped. Both mainnet fork tests passed separately.\n\nDeployment and permissionless `poke()` monitoring responsibilities are documented in [README.md](/home/imd-worker/.identitymd/work/69561148-b678-420c-bf0b-3b6fb49af48f/07ba0542-b044-4dda-a72e-198e766e06cb/README.md). No transactions were broadcast.","treeHash":"cb5bf435613831a296c90eed0359a46d2b4b536e","usage":{"cachedInputTokens":3915904,"inputTokens":154217,"model":"gpt-6.1-sol","outputTokens":46570,"runtime":"codex","turns":9,"wallClockMs":2249979}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"47f3603854a893a3","findings":[{"citation":"resolved","description":"Merged from audit_flow (high), audit_permissions (low) and audit_economics (info): all three report the same root cause. launch.json writes the observer as the literal 0x626517225096671868609c1bbf9c1b416a4efd9a, so that address is baked into the creation code. Hook.protected.t.sol setUp (lines 43-78) builds a PoolManager in place and, when asked, a token probe. It puts no code at the canary address, then CREATE2-deploys the creation code through deployAtFlags with `require(at != address(0), \"hook deployment reverted\")`. The high-level call to an account with no code reverts, so setUp fails. test_permissionsMatchTheDeclaredFlags, test_runtimeCodeHasNoEscapeHatch, test_callbacksRefuseCallersOtherThanThePoolManager and test_initializesFromTheLaunchFactory then all fail, and the launch cannot be admitted. On mainnet the read succeeds: I checked live that canaryAddress() returns 0x379C0A5704C211f26eadd26e670246E242Af9e7E and isTripped() returns false. So this is a defect against the floor this launch is judged by, not against the factory's mainnet deployment. A fix that stays within the brief: make the one constructor read a low-level staticcall. Use its result when the observer has code and returns 32 bytes holding a nonzero address. Otherwise fall back to a constant equal to the observer's deterministic canaryAddress (0x379C...9e7E, derived from its fixed SEED_PHRASE), or recompute that derivation. On mainnet the destination stays the value read from the observer, and it stays immutable.","line":61,"path":"src/QuantumCanaryHook.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 {QuantumCanaryHook} from \"src/QuantumCanaryHook.sol\";\nimport {IQuantumCanary} from \"src/IQuantumCanary.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\n\n/// @notice Mirrors the protected floor (Hook.protected.t.sol `setUp`/`deployAtFlags`): the attested\n/// creation code, with the manifest's literal canary 0x6265...d9a baked in as the second constructor\n/// argument, is CREATE2-deployed on a chain that has no network and no code at that address.\n/// The hook's constructor does `canary.canaryAddress()` unconditionally, so deployment reverts and\n/// every protected hook check that depends on `hook` fails in setUp.\ncontract ConstructorOnEmptyChainTest is Test {\n    address constant LIVE_CANARY = 0x626517225096671868609c1bBF9c1B416A4eFd9a;\n    uint160 constant DECLARED_FLAGS = 0x20cc;\n\n    function test_attestedCreationCodeDeploysWhereTheProtectedFloorRunsIt() public {\n        // Same precondition as the verifier's local chain: nothing lives at the mainnet canary address.\n        assertEq(LIVE_CANARY.code.length, 0, \"fixture: verifier chain has no canary code\");\n\n        PoolManager manager = new PoolManager(address(this));\n        bytes memory creationCode =\n            abi.encodePacked(type(QuantumCanaryHook).creationCode, abi.encode(IPoolManager(address(manager)), IQuantumCanary(LIVE_CANARY)));\n        bytes32 initCodeHash = keccak256(creationCode);\n\n        address at;\n        for (uint256 i = 0; i < 200_000; i++) {\n            address predicted = address(\n                uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), initCodeHash))))\n            );\n            if (!HookFlags.matches(predicted, DECLARED_FLAGS)) continue;\n            bytes32 salt = bytes32(i);\n            assembly (\"memory-safe\") {\n                at := create2(0, add(creationCode, 0x20), mload(creationCode), salt)\n            }\n            break;\n        }\n        // Protected floor: require(at != address(0), \"hook deployment reverted\")\n        assertTrue(at != address(0), \"hook deployment reverted: constructor read canaryAddress() on an empty account\");\n        assertEq(HookFlags.flagsOf(at), DECLARED_FLAGS);\n    }\n}","reproduction":"State: a local chain with no code at 0x626517225096671868609c1bBF9c1B416A4eFd9a, which is the verifier's offline chain and a default `forge test`. Input: CREATE2 of abi.encodePacked(type(QuantumCanaryHook).creationCode, abi.encode(manager, 0x6265...d9a)) at a salt mined for flags 0x20cc, as deployAtFlags does. Expected: a hook at a 0x20cc address, after which the floor runs. Actual: the constructor reverts at line 61 and create2 returns address(0), so the floor's setUp fails with 'hook deployment reverted'. Ran test/scratch/Proof_e9138cd164a6.t.sol: FAIL 'hook deployment reverted: constructor read canaryAddress() on an empty account'.","severity":"high","snippet":"        address destination = canary.canaryAddress();","title":"Constructor's unguarded canaryAddress() call reverts wherever the observer has no code, so the protected floor cannot deploy the attested creation code and every protected hook check fails in setUp"},{"citation":"resolved","description":"Merged from audit_math (medium) and audit_flow (low): same root cause. The hook sets `retired` only from _observeTrip(), which runs only in beforeSwap and afterSwap. payout() (lines 158-170), unlockCallback and _redeem never read the trip, but the only thing they do is send ETH to canaryAddress. The observer's published source defines `isTripped() = canaryAddress.balance < thresholdWei && highWaterMark >= thresholdWei`, a live check that does not latch (I fetched and checked the source; thresholdWei is 1 ether on mainnet). Claims pile up whenever the automatic payout is skipped: the launch pool is seeded with CANARY only, so the first buys find no ETH in the manager; or a swapper leaves less than 170k gas at line 199. In that state, once the bounty is drained below the threshold, one permissionless payout() pays out at least the shortfall and isTripped() goes back to false. Every later swap then keeps charging 1% and paying it to the address whose key is now known. This breaks requirement 4: the trip happened on chain, but the hook's own transfer hid it. (A key holder who deliberately leaves at least thresholdWei in the address can always avoid the trip. That is a limit of the observer and outside the hook's control.) Fix: call _observeTrip() at the top of payout(), and at the top of unlockCallback and redeemUnlocked, before any burn or take.","line":158,"path":"src/QuantumCanaryHook.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 {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {QuantumCanaryHook} from \"src/QuantumCanaryHook.sol\";\nimport {QuantumCanaryToken} from \"src/QuantumCanaryToken.sol\";\nimport {IQuantumCanary} from \"src/IQuantumCanary.sol\";\n\n/// @dev Verbatim logic of the live observer at 0x626517225096671868609c1bBF9c1B416A4eFd9a\n/// (source published by the requester at github.com/identity-md-launches/launch-1213-src-quantumcanary-sol):\n/// isTripped() is a LIVE predicate on canaryAddress.balance, not a latch.\ncontract LiveLogicCanary is IQuantumCanary {\n    address public immutable canaryAddress;\n    uint256 public immutable thresholdWei;\n    uint256 public highWaterMark;\n    bool private tripRecorded;\n    uint256 public trippedAt;\n\n    constructor(address canary, uint256 thresholdWei_) {\n        canaryAddress = canary;\n        thresholdWei = thresholdWei_;\n        highWaterMark = canary.balance;\n    }\n\n    function isTripped() external view returns (bool) {\n        return canaryAddress.balance < thresholdWei && highWaterMark >= thresholdWei;\n    }\n\n    function poke() external {\n        uint256 balance = canaryAddress.balance;\n        uint256 mark = highWaterMark;\n        if (balance > mark) {\n            highWaterMark = balance;\n            mark = balance;\n        }\n        if (!tripRecorded && balance < thresholdWei && mark >= thresholdWei) {\n            tripRecorded = true;\n            trippedAt = block.timestamp;\n        }\n    }\n}\n\ncontract RefillRaceTest is Test {\n    uint160 constant Q96 = 79228162514264337593543950336;\n    IPoolManager manager;\n    QuantumCanaryHook hook;\n    QuantumCanaryToken token;\n    LiveLogicCanary canary;\n    PoolKey key;\n    address bounty;\n\n    receive() external payable {}\n\n    function setUp() public {\n        vm.deal(address(this), 10_000_000 ether);\n        bounty = makeAddr(\"derived secp256k1 bounty address\");\n        token = new QuantumCanaryToken();\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        canary = new LiveLogicCanary(bounty, 1 ether);\n        hook = _mine();\n        key = PoolKey(Currency.wrap(address(0)), Currency.wrap(address(token)), 12_500, 60, IHooks(address(hook)));\n        manager.initialize(key, Q96);\n        // Launch-style seeding: CANARY only, no ETH in the manager.\n        _liquidity(-600, -60, 100_000 ether);\n        assertEq(address(manager).balance, 0);\n    }\n\n    function _mine() internal returns (QuantumCanaryHook deployed) {\n        bytes memory init = abi.encodePacked(type(QuantumCanaryHook).creationCode, abi.encode(manager, canary));\n        bytes32 hash = keccak256(init);\n        for (uint256 i; i < 300_000; ++i) {\n            address predicted =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), hash)))));\n            if (uint160(predicted) & 0x3fff == 0x20cc) {\n                return new QuantumCanaryHook{salt: bytes32(i)}(manager, canary);\n            }\n        }\n        revert(\"no salt\");\n    }\n\n    function _liquidity(int24 lower, int24 upper, int256 amount) internal {\n        manager.unlock(abi.encode(false, ModifyLiquidityParams(lower, upper, amount, bytes32(0)), SwapParams(false, 0, 0)));\n    }\n\n    function _swap(SwapParams memory p) internal returns (BalanceDelta) {\n        return abi.decode(manager.unlock(abi.encode(true, ModifyLiquidityParams(0, 0, 0, bytes32(0)), p)), (BalanceDelta));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (bool swapping, ModifyLiquidityParams memory lp, SwapParams memory sp) =\n            abi.decode(data, (bool, ModifyLiquidityParams, SwapParams));\n        BalanceDelta delta;\n        if (swapping) delta = manager.swap(key, sp, \"\");\n        else (delta,) = manager.modifyLiquidity(key, lp, \"\");\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) internal {\n        if (amount < 0) {\n            uint256 owed = uint256(-int256(amount));\n            manager.sync(c);\n            if (c.isAddressZero()) manager.settle{value: owed}();\n            else {\n                token.transfer(address(manager), owed);\n                manager.settle();\n            }\n        } else if (amount > 0) {\n            manager.take(c, address(this), uint256(int256(amount)));\n        }\n    }\n\n    /// The bounty is armed (funded >= threshold and poked). The first launch buy accrues 1 ETH of\n    /// claims because the manager held no ETH during the callback. A quantum attacker then drains\n    /// the bounty: isTripped() == true. Before any swap observes it, anyone calls payout(): the\n    /// hook's own 1 ETH refill pushes the balance back over the threshold, isTripped() flips to\n    /// false, and the next swap still charges the bounty fee to the now attacker-controlled address.\n    function test_payoutRefillClearsLiveAlarmBeforeHookObservesIt() public {\n        vm.deal(bounty, 1 ether);\n        canary.poke();\n        assertFalse(canary.isTripped());\n\n        // First buy on the fresh pool: fee 1 ETH accrues as claims (manager had no ETH in afterSwap).\n        _swap(SwapParams(true, -100 ether, TickMath.MIN_SQRT_PRICE + 1));\n        assertEq(hook.accruedFees(), 1 ether, \"claims pending\");\n        assertEq(bounty.balance, 1 ether);\n\n        // Quantum attacker drains the bounty.\n        vm.deal(bounty, 0);\n        assertTrue(canary.isTripped(), \"live alarm is on\");\n        canary.poke();\n        assertGt(canary.trippedAt(), 0, \"observer records the trip permanently\");\n\n        // Anyone (or the attacker) calls the permissionless payout before a swap observes the trip.\n        address anyone = makeAddr(\"anyone\");\n        vm.prank(anyone);\n        assertEq(hook.payout(), 1 ether);\n        assertEq(bounty.balance, 1 ether);\n        assertFalse(canary.isTripped(), \"hook's own refill cleared the live alarm\");\n\n        // Next swap: the hook should have retired; instead it charges the fee again.\n        uint256 before = bounty.balance;\n        _swap(SwapParams(true, -1 ether, TickMath.MIN_SQRT_PRICE + 1));\n        uint256 feeAfterTrip = bounty.balance - before + hook.accruedFees();\n        emit log_named_uint(\"fee charged after recorded trip\", feeAfterTrip);\n        assertTrue(hook.retired(), \"hook must retire after the recorded trip\");\n        assertEq(feeAfterTrip, 0, \"no fee after a trip\");\n    }\n\n    /// Control: the same drain, observed by a swap before any payout, retires the hook.\n    function test_controlSwapBeforePayoutRetires() public {\n        vm.deal(bounty, 1 ether);\n        canary.poke();\n        _swap(SwapParams(true, -100 ether, TickMath.MIN_SQRT_PRICE + 1));\n        vm.deal(bounty, 0);\n        _swap(SwapParams(true, -1 ether, TickMath.MIN_SQRT_PRICE + 1));\n        assertTrue(hook.retired());\n    }\n}","reproduction":"Observer with thresholdWei = 1 ether. Bounty funded to 1 ether and poke() called. Fresh pool seeded with CANARY only (manager ETH balance 0). 1) Exact-input buy of 100 ether: hook.accruedFees() == 1e18. 2) Bounty balance set to 0: isTripped() == true. 3) Anyone calls payout(): it returns 1e18, bounty.balance == 1 ether, isTripped() == false. 4) Exact-input buy of 1 ether. Expected: hook.retired() == true and no fee. Actual: retired() == false and a 0.01 ether fee is charged and paid to the drained address. Ran test/scratch/Proof_0d5ece9e791b.t.sol: it fails 'hook must retire after the recorded trip', and the control (a swap before the payout) retires correctly.","severity":"medium","snippet":"    function payout() external returns (uint256 paid) {","title":"payout() refills the canary address without first observing the trip, so the hook's own payment can clear the observer's live isTripped() alarm and the hook never retires"},{"citation":"resolved","description":"From audit_math. For an exact-output sell, reserved = amountSpecified/99 (line 116). The checked addition on line 117 overflows when amountSpecified + amountSpecified/99 > type(int256).max. A pool without the hook, and this same hook once retired (no quote is taken), accept such a request and fill as far as sqrtPriceLimitX96 allows. Router and arbitrage code uses a saturating amount as 'sell to the price limit', and the active hook rejects it. No funds are at risk; it is a functional gap. Fix: cap the quoted amount at type(int128).max, which is the largest amount core can fill anyway.","line":117,"path":"src/QuantumCanaryHook.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 {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {QuantumCanaryHook} from \"src/QuantumCanaryHook.sol\";\nimport {QuantumCanaryToken} from \"src/QuantumCanaryToken.sol\";\nimport {IQuantumCanary} from \"src/IQuantumCanary.sol\";\n\ncontract ScratchCanary is IQuantumCanary {\n    address public canaryAddress;\n    bool public tripped;\n\n    constructor(address d) {\n        canaryAddress = d;\n    }\n\n    function setTripped(bool v) external {\n        tripped = v;\n    }\n\n    function isTripped() external view returns (bool) {\n        return tripped;\n    }\n}\n\n/// @dev An exact-output sell asking for \"as much ETH as the price limit allows\" with\n/// amountSpecified near type(int256).max is a partial fill on an ordinary v4 pool and on this\n/// hook once retired, but reverts with Panic(0x11) inside beforeSwap while the hook is active:\n/// `quotedParams.amountSpecified += int256(reserved)` overflows before any quote runs.\ncontract ExactOutputSellOverflowTest is Test {\n    uint160 constant Q96 = 79228162514264337593543950336;\n    IPoolManager manager;\n    QuantumCanaryHook hook;\n    QuantumCanaryToken token;\n    ScratchCanary canary;\n    PoolKey key;\n    PoolKey plain;\n\n    receive() external payable {}\n\n    function setUp() public {\n        vm.deal(address(this), 10_000_000 ether);\n        token = new QuantumCanaryToken();\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        canary = new ScratchCanary(makeAddr(\"bounty\"));\n        hook = _mine();\n        key = PoolKey(Currency.wrap(address(0)), Currency.wrap(address(token)), 12_500, 60, IHooks(address(hook)));\n        manager.initialize(key, Q96);\n        plain = key;\n        plain.hooks = IHooks(address(0));\n        manager.initialize(plain, Q96);\n        _liquidity(key, -600, 600, 100_000 ether);\n        _liquidity(plain, -600, 600, 100_000 ether);\n    }\n\n    function _mine() internal returns (QuantumCanaryHook deployed) {\n        bytes memory init = abi.encodePacked(type(QuantumCanaryHook).creationCode, abi.encode(manager, canary));\n        bytes32 hash = keccak256(init);\n        for (uint256 i; i < 300_000; ++i) {\n            address predicted =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), hash)))));\n            if (uint160(predicted) & 0x3fff == 0x20cc) {\n                return new QuantumCanaryHook{salt: bytes32(i)}(manager, canary);\n            }\n        }\n        revert(\"no salt\");\n    }\n\n    function _liquidity(PoolKey memory k, int24 lower, int24 upper, int256 amount) internal {\n        manager.unlock(\n            abi.encode(false, k, ModifyLiquidityParams(lower, upper, amount, bytes32(0)), SwapParams(false, 0, 0))\n        );\n    }\n\n    function _swap(PoolKey memory k, SwapParams memory p) internal returns (BalanceDelta) {\n        return abi.decode(\n            manager.unlock(abi.encode(true, k, ModifyLiquidityParams(0, 0, 0, bytes32(0)), p)), (BalanceDelta)\n        );\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (bool swapping, PoolKey memory k, ModifyLiquidityParams memory lp, SwapParams memory sp) =\n            abi.decode(data, (bool, PoolKey, ModifyLiquidityParams, SwapParams));\n        BalanceDelta delta;\n        if (swapping) delta = manager.swap(k, sp, \"\");\n        else (delta,) = manager.modifyLiquidity(k, lp, \"\");\n        _settle(k.currency0, delta.amount0());\n        _settle(k.currency1, delta.amount1());\n        return abi.encode(delta);\n    }\n\n    function _settle(Currency c, int128 amount) internal {\n        if (amount < 0) {\n            uint256 owed = uint256(-int256(amount));\n            manager.sync(c);\n            if (c.isAddressZero()) manager.settle{value: owed}();\n            else {\n                token.transfer(address(manager), owed);\n                manager.settle();\n            }\n        } else if (amount > 0) {\n            manager.take(c, address(this), uint256(int256(amount)));\n        }\n    }\n\n    function test_exactOutputSellToPriceLimitWithMaxAmountMatchesUnhookedPool() public {\n        uint160 limit = TickMath.getSqrtPriceAtTick(60);\n        // Leaves room for core's own `amountToSwap += hookDeltaSpecified`; only the hook's gross-up overflows.\n        int256 saturating = type(int256).max - 1e30;\n        BalanceDelta plainDelta = _swap(plain, SwapParams(false, saturating, limit));\n        assertGt(plainDelta.amount0(), 0, \"ordinary pool partial-fills to the price limit\");\n\n        // Same request on the hooked pool: must trade (fee on the gross ETH), not revert.\n        BalanceDelta actual = _swap(key, SwapParams(false, saturating, limit));\n        uint256 gross = uint256(int256(plainDelta.amount0()));\n        assertEq(actual.amount0(), int256(gross) - int256(gross / 100), \"1% of gross ETH to the bounty\");\n        assertEq(actual.amount1(), plainDelta.amount1(), \"CANARY input unchanged\");\n    }\n}","reproduction":"ETH/CANARY 12500/60 pool at sqrtPrice 2^96 with 100_000e18 liquidity on [-600,600]. SwapParams(false, type(int256).max - 1e30, getSqrtPriceAtTick(60)). Pool without the hook: succeeds, amount0 = +299535495591078093767. Active hook: reverts WrappedError(hook, 0x575e24b4, Panic(0x11), HookCallFailed). Ran test/scratch/Proof_e25922a17546.t.sol: it fails with exactly that error.","severity":"low","snippet":"                quotedParams.amountSpecified += int256(reserved);","title":"Exact-output ETH sell with amountSpecified above type(int256).max*99/100 overflows in beforeSwap (Panic 0x11) instead of partially filling to the price limit"}],"hash":"186088b5917555a8bd68d562847cf08dc5389cd7bbcb1b119f2f80e14c18b5c2","nodeId":"cbd8df38-d932-4459-8d44-35165864651c","outcome":"completed","summary":"I wrote three findings to `.imd-findings.json`, plus a coverage row for each of the 9 entry points and one for the constructor. The first finding, a high, blocks admission. All three findings carry the specialists' proof tests, which I ran and which fail on the current code for the reasons the findings give. The project's own suite passes: 55 tests, with the mainnet fork test skipped. I changed no project files, and I deleted my scratch copies of the proofs.\n\n**Findings**\n\n1. **High: the hook cannot be deployed on the protected checks' offline chain** (`src/QuantumCanaryHook.sol:61`). Three specialists reported this, at high, low and info; I merged them into one.\n   - **Problem:** the constructor calls `canary.canaryAddress()` directly. The protected test builds the hook on a local chain that has no contract at `0x6265…d9a`, so that call reverts. Setup then stops with \"hook deployment reverted\" and all four protected hook checks fail.\n   - **Mainnet is fine:** there the call works. I checked live: it returns `0x379C0A5704C211f26eadd26e670246E242Af9e7E`, and `isTripped()` is false.\n   - **Suggested fix, within the brief:** make that one read a guarded low-level call. If the observer has no code, fall back to its deterministic canary address.\n\n2. **Medium: `payout()` can stop the hook from ever retiring** (`:158`). Two specialists reported this, at medium and low; I merged them.\n   - **Why:** the observer's published source (which I fetched) shows `isTripped()` is a live balance check that does not latch. `payout()` sends ETH to the canary address without checking the trip first.\n   - **How it plays out:** fees pile up as claims when the automatic payout is skipped, for example on the first buys into a pool seeded only with CANARY. If the bounty is then drained, one payout can push the balance back over the 1 ETH threshold. The alarm clears, and every later swap keeps paying 1% to the compromised address.\n   - **Fix:** call `_observeTrip()` before any burn or transfer in `payout()`, `unlockCallback` and `redeemUnlocked`.\n\n3. **Low: overflow on very large exact-output sells** (`:117`). If `amountSpecified` is above roughly `type(int256).max*99/100`, `beforeSwap` reverts with an arithmetic overflow. A pool without the hook, and this hook once retired, would instead fill up to the price limit. Routers use a saturating amount like this to mean \"sell to the limit\". No funds are at risk.\n\n**Dropped**\n\nThe `initialPrice` note was dropped. It depends on how the deployer opens the pool, and I can't reproduce that from the code.\n\n**My own pass**\n\nI traced the fee in all four swap modes, including partial fills through the hook's reverting quote. I also checked the claim mint/burn/take accounting, the self-call boundaries, caller restrictions and the token, and found nothing beyond the three above. The static-analysis warnings about strict equality are harmless early returns on a zero balance.","treeHash":null,"usage":{"cachedInputTokens":862345,"inputTokens":24,"model":"claude-opus-5-5","outputTokens":14959,"runtime":"claude","turns":12,"wallClockMs":186498}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"dff6c0d3de4aa913","findings":[{"citation":"resolved","description":"The task requires the constructor to read canaryAddress() once into an immutable, and the manifest bakes the literal mainnet observer 0x626517225096671868609c1bbf9c1b416a4efd9a into the creation code. A high-level interface call to an address with no code reverts (empty return data for a function that expects 32 bytes), so the hook cannot be constructed anywhere the observer is absent. The protected floor (.imd/reads/protected/univ4_hook/Hook.protected.t.sol, setUp) deploys IMD_HOOK_CREATION_CODE with CREATE2 on a fresh local chain, etching only the PoolManager at IMD_POOL_MANAGER and optionally the launch token at IMD_TOKEN_PROBE; nothing places code at the canary argument. Its deployAtFlags then hits `require(at != address(0), \"hook deployment reverted\")` and the entire floor (permissions, opcode scan, caller refusal, factory initialization) fails before asserting anything. The code behaves as specified; the gap is between the spec-mandated constructor dependency and the verifier's deployment environment. No in-spec code change removes the dependency: admission needs the floor to run on a mainnet fork (or with the observer's runtime etched at 0x6265...), and that requirement is not recorded in launch.json notes or the README deployment section beyond 'local empty-chain rehearsals must deploy a mock canary first'. Reported so the judge can confirm which environment the floor runs in rather than discover a reverted setUp after review. Also verified live (block 26162086): canaryAddress() = 0x379C0A5704C211f26eadd26e670246E242Af9e7E (no code, 0 balance), isTripped() = false, thresholdWei = 1e18, highWaterMark = 0, so on a fork the constructor succeeds and the hook starts un-retired.","line":61,"path":"src/QuantumCanaryHook.sol","reproduction":"State: any chain where 0x626517225096671868609c1bbf9c1b416a4efd9a has no code (default forge test, the protected floor without a fork). Call: deploy abi.encodePacked(type(QuantumCanaryHook).creationCode, abi.encode(poolManager, 0x626517225096671868609c1bbf9c1b416a4efd9a)) via CREATE2 with a salt whose address carries flags 0x20cc, or `new QuantumCanaryHook(manager, IQuantumCanary(0x6265...))`. Expected by the floor: a hook at a flag-matching address. Actual: the constructor reverts at line 61 (empty return data from an account without code), CREATE2 returns address(0), and HookProtectedTest.setUp fails with 'hook deployment reverted'. Scratch test test/scratch/ConstructorDependency.t.sol reproduces both the CREATE2 address(0) and the direct revert (passes on this tree as a demonstration; it is not a fix-proof).","severity":"low","snippet":"        address destination = canary.canaryAddress();","title":"Constructor's live canaryAddress() read makes the attested creation code revert on any chain without canary code, including the protected floor's empty-chain CREATE2 deploy"}],"hash":"31470919fd46ee3bab1b2203fd6c8460217c7f166a5d2cf6a2d310199e15b5f8","nodeId":"f7be96cf-c7d4-4f24-8d3c-d269fa70156b","outcome":"completed","summary":"Review complete. The findings file is written and validated, and the working tree is clean apart from the ignored scratch test.\n\n**Outcome:** one low-severity finding, no defects in the hook's permission model, trust boundaries, or paired code paths.\n\n**What I checked in my area (Access Control, Trust Gap, Asymmetry):**\n- Every callback and helper refuses callers other than the PoolManager or the hook itself. The unlock callback additionally requires the hook's own transient payout latch.\n- The only outward value path is a `take` to the immutable canary address. The hook never approves, transfers, or sets operators on its ERC-6909 claims, has no receive or fallback, and its quote path always reverts.\n- The simulate/execute pair for ETH-specified swaps is economically consistent. Full fills charge exactly the reserved 1%, and partial fills bind at the same price limit in both runs.\n- The before/after fee branches mirror each other, the transient fee slot is reset on entry so a router-caught revert cannot leak a stale fee, and a retirement latch cannot desynchronise a delta already returned.\n- I read the live observer's source and state from mainnet. Its trip predicate can only turn true if ETH leaves the keyless address, so no permissionless actor can retire the hook. It is currently untripped with a zero high-water mark.\n- Delta accounting under early payout keeps the manager's balance equal to liabilities plus outstanding deltas, so the auto-payout cannot create a shortfall that would not exist without the hook.\n\n**The finding:** the constructor's mandated `canaryAddress()` read reverts wherever the observer has no code. The protected floor deploys the attested creation code on an empty local chain and etches only the PoolManager, so its setUp would fail before any assertion unless it runs on a mainnet fork. This is a spec-versus-environment gap rather than a code bug, reported so the judge can confirm the floor's environment. The scratch test under `test/scratch/` reproduces the revert.\n\n**Not assessed:** the 1:1 `initialPrice` in the manifest and the factory's supply split are outside my area and were not economically evaluated. All 9 listed entry points have coverage rows, plus 5 invariant rows.","treeHash":null,"usage":{"cachedInputTokens":1483176,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":50165,"runtime":"claude","turns":41,"wallClockMs":692938}},{"artifacts":[],"attempt":1,"bundleHash":"8fff93b91cf294e047b85fd328e622f3009a2207bbd0c26c82678d6a2ae6db32","device":"2d9304cc28cc499d","findings":[],"hash":"5c231cb9328ce547777c9e88feeb8ad76005d4fe7c59749454f5939b22d6cfda","nodeId":"a8440166-57a4-41bb-bade-11a9f532624e","outcome":"completed","summary":"Updated only `launch.json`, correcting notes about the constructor fallback and trip checks before payout.\n\nVerified manifest fields, constructor arguments, permissions, and notes length.\n\n`forge build` passed. `forge test`: 67 passed, 0 failed, 1 skipped (mainnet fork).","treeHash":"88b5e918e9a10437a931225d435f40702e7890d7","usage":{"cachedInputTokens":448128,"inputTokens":62738,"model":"gpt-6-astra","outputTokens":3739,"runtime":"codex","turns":3,"wallClockMs":107413}},{"artifacts":[],"attempt":1,"bundleHash":"a278b43cec639b1356421c1c0edcb2132382478fd1718bfd49526252fe3be6e8","device":"7ce44d9c73b2e554","findings":[],"hash":"74a651ad2c23fcef5955843dfde0738236ab48b62ebbcff4dec923b15b44c427","nodeId":"6c1f5775-bebe-48f6-8927-4f50828af435","outcome":"completed","summary":"Confirmed the prior overflow finding is fixed. Added regression, fuzz, deferred-payout, and invariant coverage; updated the constructor error assertion and test documentation. Only `test/` files changed.\n\nValidation: `forge build` passed; `forge test` finished with **67 passed, 0 failed, 1 optional fork suite skipped**. Invariants completed 28,672 randomized calls without reverts.","treeHash":"03ff1b34ceb21eca0eac299e42429724d22ef8c8","usage":{"cachedInputTokens":1271040,"inputTokens":86379,"model":"gpt-6-astra","outputTokens":8256,"runtime":"codex","turns":6,"wallClockMs":250879}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"357c46e3781993d4","findings":[{"citation":"resolved","description":"The hook's constructor performs a high-level external call `canary.canaryAddress()` to its second constructor argument. launch.json binds that argument to the literal mainnet address 0x626517225096671868609c1bbf9c1b416a4efd9a. The protected floor (.imd/reads/protected/univ4_hook/Hook.protected.t.sol, setUp lines 43-78) CREATE2-deploys the attested creation code, with those constructor arguments baked in, on a local chain with no network: it etches a working PoolManager at IMD_POOL_MANAGER and a token at IMD_TOKEN_PROBE, but nothing puts code at the canary address (the harness has no notion of this dependency). On that chain `canary.canaryAddress()` targets an account with no code; Solidity 0.8.26 reverts (extcodesize / return-data-size check), so `create2` returns address(0) and the floor's `require(at != address(0), \"hook deployment reverted\")` fails inside setUp. Consequently test_permissionsMatchTheDeclaredFlags, test_runtimeCodeHasNoEscapeHatch, test_callbacksRefuseCallersOtherThanThePoolManager and test_initializesFromTheLaunchFactory all fail, and the launch cannot be admitted. The project's own README (line 69) and test/QuantumCanaryMainnetFork.t.sol (which skips unless LIVE_CANARY.code.length != 0) acknowledge that 'local empty-chain rehearsals must deploy a mock canary first', but the protected floor is exactly such a rehearsal and cannot be told to. The brief asks the constructor to read canaryAddress() once into an immutable; the implementation must do so in a way that survives a chain where the observer is absent (e.g. a guarded low-level staticcall that falls back to the observer's deterministic derivation of the canary address, which the live contract computes from a fixed seed phrase, or any equivalent that keeps the destination immutable and correct on mainnet while letting the creation code deploy where the floor runs it). This is the same class as the reference's 'a hook that cannot be initialized by the floor cannot launch' blocking finding.","line":61,"path":"src/QuantumCanaryHook.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 {QuantumCanaryHook} from \"src/QuantumCanaryHook.sol\";\nimport {IQuantumCanary} from \"src/IQuantumCanary.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\n\n/// @notice Mirrors the protected floor (Hook.protected.t.sol `setUp`/`deployAtFlags`): the attested\n/// creation code, with the manifest's literal canary 0x6265...d9a baked in as the second constructor\n/// argument, is CREATE2-deployed on a chain that has no network and no code at that address.\n/// The hook's constructor does `canary.canaryAddress()` unconditionally, so deployment reverts and\n/// every protected hook check that depends on `hook` fails in setUp.\ncontract ConstructorOnEmptyChainTest is Test {\n    address constant LIVE_CANARY = 0x626517225096671868609c1bBF9c1B416A4eFd9a;\n    uint160 constant DECLARED_FLAGS = 0x20cc;\n\n    function test_attestedCreationCodeDeploysWhereTheProtectedFloorRunsIt() public {\n        // Same precondition as the verifier's local chain: nothing lives at the mainnet canary address.\n        assertEq(LIVE_CANARY.code.length, 0, \"fixture: verifier chain has no canary code\");\n\n        PoolManager manager = new PoolManager(address(this));\n        bytes memory creationCode =\n            abi.encodePacked(type(QuantumCanaryHook).creationCode, abi.encode(IPoolManager(address(manager)), IQuantumCanary(LIVE_CANARY)));\n        bytes32 initCodeHash = keccak256(creationCode);\n\n        address at;\n        for (uint256 i = 0; i < 200_000; i++) {\n            address predicted = address(\n                uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), initCodeHash))))\n            );\n            if (!HookFlags.matches(predicted, DECLARED_FLAGS)) continue;\n            bytes32 salt = bytes32(i);\n            assembly (\"memory-safe\") {\n                at := create2(0, add(creationCode, 0x20), mload(creationCode), salt)\n            }\n            break;\n        }\n        // Protected floor: require(at != address(0), \"hook deployment reverted\")\n        assertTrue(at != address(0), \"hook deployment reverted: constructor read canaryAddress() on an empty account\");\n        assertEq(HookFlags.flagsOf(at), DECLARED_FLAGS);\n    }\n}","reproduction":"State: any chain where address 0x626517225096671868609c1bBF9c1B416A4eFd9a has no code (the verifier's local chain; also `forge test` with no fork). Call: CREATE2-deploy abi.encodePacked(type(QuantumCanaryHook).creationCode, abi.encode(IPoolManager(manager), IQuantumCanary(0x6265...d9a))) with a salt mined for flags 0x20cc, exactly as Hook.protected.t.sol deployAtFlags does. Expected: hook deployed at an address carrying 0x20cc, floor tests run. Actual: constructor reverts at src/QuantumCanaryHook.sol:61 (call to an account without code), create2 returns address(0), setUp reverts with 'hook deployment reverted' and all four protected hook tests fail. test/scratch/ConstructorOnEmptyChain.t.sol reproduces this: `forge test --match-path test/scratch/ConstructorOnEmptyChain.t.sol` -> FAIL 'hook deployment reverted: constructor read canaryAddress() on an empty account'.","severity":"high","snippet":"        address destination = canary.canaryAddress();","title":"Constructor's unconditional canaryAddress() read makes the attested creation code undeployable on the protected floor's empty local chain, so every protected hook check fails in setUp"},{"citation":"resolved","description":"The hook latches retirement only inside beforeSwap/afterSwap via _observeTrip(), and in both callbacks that read happens before the automatic payout, which is what keeps a payment from hiding a trip. The standalone payout() path has no such read: it burns claims and takes ETH to canaryAddress unconditionally. The live observer at 0x6265...d9a (source linked from README line 60; fetched and compared) defines isTripped() as `canaryAddress.balance < thresholdWei && highWaterMark >= thresholdWei`, deliberately NOT latched, with thresholdWei = 1 ether. Its poke() separately records trippedAt/trippedBlock, which the hook never reads. So if claims worth at least (thresholdWei - current balance) are outstanding when the canary key is broken and its balance swept, a payout() call that lands before the next swap refills the address above the threshold, isTripped() flips back to false, and the hook keeps charging 1% forever: requirement 4 ('when isTripped() returns true, the hook stops taking any fee ... for ever after') is missed although the condition occurred on chain. Outstanding claims of that size arise whenever auto-payout is skipped: a token-only fresh manager (the launch's first buys), or any swap whose caller leaves less than AUTO_PAYOUT_GAS + 50_000 gas at afterSwap (line 199), which a swapper controls. Fix that stays within the brief: call _observeTrip() at the top of payout() (and of unlockCallback/redeemUnlocked) before burning, and/or also treat the observer's recorded trippedAt() != 0 as tripped, so the hook cannot un-trip the observer with its own money.","line":158,"path":"src/QuantumCanaryHook.sol","reproduction":"State: observer armed per README (bounty funded to 1 ETH, poke() called, highWaterMark = 1 ETH). Fresh token-only manager; one exact-input buy of 100 ETH leaves hook.accruedFees() == 1 ether (no ETH in the manager during afterSwap). Then the canary address balance drops to 0 (vm.deal(bounty, 0) standing in for a quantum sweep): observer.isTripped() == true. Before any swap, anyone calls hook.payout(). Expected: the hook observes the trip (retires) and/or the trip remains observable afterwards. Actual: payout() sends 1 ETH to the drained address, observer.isTripped() becomes false, the next 1 ETH buy charges 0.01 ETH fee, hook.retired() stays false, bounty.balance == 1.01 ether. Reproduced by test/scratch/PayoutClearsLiveAlarm.t.sol (`forge test --match-path test/scratch/PayoutClearsLiveAlarm.t.sol`, which PASSES by asserting the defective sequence; the observer logic in it is copied verbatim from the mainnet contract's isTripped()/poke()).","severity":"low","snippet":"    function payout() external returns (uint256 paid) {\n        if (_load(PAYING_SLOT) != 0) return 0;\n        uint256 beforeClaims = accruedFees();\n        if (beforeClaims == 0 || address(poolManager).balance == 0) return 0;","title":"payout() pays the bounty without observing the trip, so the hook's own permissionless payment can clear the observer's non-latched live alarm before any swap sees it, and the hook never retires"}],"hash":"8d8a7898679158a944d5352ae1adc68b2482ab1588bdd7c771af9eec5cda3e43","nodeId":"f8490209-f4a6-48aa-8b11-671c9f7e0d0c","outcome":"completed","summary":"The review is complete. No repository files were changed. The findings file is at `.imd-findings.json` with two findings and 14 coverage rows.\n\n**Findings**\n\n1. **High. The hook's constructor cannot run where the protected floor deploys it.** The constructor calls `canaryAddress()` on the literal mainnet observer address baked into the attested creation code. The protected hook floor deploys that creation code on a local chain with no network and no code at that address, so the call reverts, CREATE2 returns zero, and the floor's `setUp` fails. Every protected hook check then fails and the launch cannot be admitted. The proof test in `test/scratch/ConstructorOnEmptyChain.t.sol` mirrors the floor's deployment path and fails on the current code.\n\n2. **Low. `payout()` pays without observing the trip.** Both swap callbacks read `isTripped()` before paying, but the standalone `payout()` does not. The live observer's alarm is deliberately not latched, so a payout of at least 1 ETH of outstanding claims into a drained canary address clears the live alarm before any swap sees it, and the hook keeps charging fees although the canary tripped. Reproduced with the observer's own logic in `test/scratch/PayoutClearsLiveAlarm.t.sol`. Fix within the brief: observe the trip inside `payout()` and consider the observer's recorded `trippedAt()` as well.\n\n**What I verified and found sound**\n\n- Fee arithmetic in all four modes, including partial fills and floor rounding at the 99 and 100 boundaries, against the v4 core delta rules. The reverting self-quote runs on identical state and the real swap consumes exactly the quoted amount.\n- Hook deltas net to zero on every path. Claims can only leave through one `take` to an immutable address.\n- The live observer's ABI matches, its `isTripped()` costs about 5k gas against a 30k budget, and the auto-payout self-call fits its 120k budget.\n\n**One operational note, not a defect:** the live observer is currently unarmed. Its high-water mark and the bounty balance are both zero, so retirement cannot trigger until someone funds it past 1 ETH and calls `poke()`.\n\n**Dropped lead:** the auto-payout can starve a later ETH `take` in the same unlock, but only when the entire manager holds less ETH than one swap's output, which cannot happen on the mainnet manager, so it was not reported.","treeHash":null,"usage":{"cachedInputTokens":2319852,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":64676,"runtime":"claude","turns":45,"wallClockMs":930004}},{"artifacts":[],"attempt":1,"bundleHash":"a5cba4cdac760498e9e369a9f1e5199a566ac685bee09aef87beeded06a59e5a","device":"2468e053049c5c80","findings":[{"description":"beforeSwap computes uint256(-params.amountSpecified) in checked signed arithmetic. Negating type(int256).min panics before the hook can quote the actual fill. The real Uniswap v4 PoolManager accepts this exact-input budget with a price limit and settles only the finite amount actually traded. Consequently, adding this hook rejects an otherwise valid price-limited buy. This affects the extreme signed input boundary, does not lose funds, and does not affect ordinary-size trades. Compute the negative input magnitude without overflowing signed arithmetic, preserving the fee based on the actual fill.","line":109,"path":"src/QuantumCanaryHook.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 {Hooks} from \"v4-core/src/libraries/Hooks.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {StateLibrary} from \"v4-core/src/libraries/StateLibrary.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 {SwapParams, ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {QuantumCanaryHook} from \"src/QuantumCanaryHook.sol\";\nimport {QuantumCanaryToken} from \"src/QuantumCanaryToken.sol\";\nimport {IQuantumCanary} from \"src/IQuantumCanary.sol\";\n\ncontract MinimumInputCanary is IQuantumCanary {\n    address public immutable override canaryAddress;\n    constructor(address destination) { canaryAddress = destination; }\n    function isTripped() external pure returns (bool) { return false; }\n}\n\ncontract QuantumCanaryMinimumInputProof is Test, IUnlockCallback {\n    using StateLibrary for IPoolManager;\n    IPoolManager internal manager;\n    QuantumCanaryHook internal hook;\n    QuantumCanaryToken internal token;\n    PoolKey internal hooked;\n    PoolKey internal control;\n    address internal bounty;\n\n    receive() external payable {}\n\n    function setUp() public {\n        vm.deal(address(this), 100_000 ether);\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        token = new QuantumCanaryToken();\n        bounty = makeAddr(\"minimum-input proof bounty\");\n        MinimumInputCanary canary = new MinimumInputCanary(bounty);\n        bytes32 initHash = keccak256(abi.encodePacked(type(QuantumCanaryHook).creationCode, abi.encode(manager, canary)));\n        for (uint256 salt; salt < 200_000; ++salt) {\n            address predicted = address(uint160(uint256(keccak256(\n                abi.encodePacked(bytes1(0xff), address(this), bytes32(salt), initHash)\n            ))));\n            if ((uint160(predicted) & Hooks.ALL_HOOK_MASK) == 0x20cc) {\n                hook = new QuantumCanaryHook{salt: bytes32(salt)}(manager, canary);\n                break;\n            }\n        }\n        require(address(hook) != address(0), \"salt not found\");\n        hooked = PoolKey(Currency.wrap(address(0)), Currency.wrap(address(token)), 12_500, 60, IHooks(address(hook)));\n        control = PoolKey(Currency.wrap(address(0)), Currency.wrap(address(token)), 12_500, 60, IHooks(address(0)));\n        manager.initialize(hooked, uint160(1 << 96));\n        manager.initialize(control, uint160(1 << 96));\n        SwapParams memory unused;\n        manager.unlock(abi.encode(true, hooked, unused));\n        manager.unlock(abi.encode(true, control, unused));\n    }\n\n    function test_minimumIntBudgetBuyPaysForOnlyItsActualFill() public {\n        uint160 priceLimit = TickMath.getSqrtPriceAtTick(-60);\n        SwapParams memory params = SwapParams(true, type(int256).min, priceLimit);\n        // v4 accepts the requested maximum budget; the price limit bounds actual settlement\n        // to about 304 ETH, so no impossible wallet balance is required.\n        BalanceDelta referenceDelta = abi.decode(manager.unlock(abi.encode(false, control, params)), (BalanceDelta));\n        assertLt(referenceDelta.amount0(), 0);\n        assertGt(referenceDelta.amount1(), 0);\n        assertLt(uint256(-int256(referenceDelta.amount0())), 1000 ether);\n\n        // CURRENT FAILURE: beforeSwap negates int256.min in checked arithmetic and panics.\n        BalanceDelta actual = abi.decode(manager.unlock(abi.encode(false, hooked, params)), (BalanceDelta));\n        uint256 fee = bounty.balance;\n        uint256 totalPaid = uint256(-int256(actual.amount0()));\n        assertEq(actual.amount1(), referenceDelta.amount1(), \"same partial CANARY fill\");\n        assertEq(actual.amount0(), int256(referenceDelta.amount0()) - int256(fee));\n        assertEq(fee, totalPaid / 100, \"1% of actual ETH payment, never the requested budget\");\n        assertGt(fee, 0);\n        assertEq(hook.accruedFees(), 0);\n        (uint160 price,,,) = manager.getSlot0(hooked.toId());\n        assertEq(price, priceLimit);\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"only manager\");\n        (bool seed, PoolKey memory key, SwapParams memory params) = abi.decode(data, (bool, PoolKey, SwapParams));\n        BalanceDelta delta;\n        if (seed) {\n            (delta,) = manager.modifyLiquidity(key, ModifyLiquidityParams(-600, 600, 100_000 ether, bytes32(0)), \"\");\n        } else {\n            delta = manager.swap(key, params, \"\");\n        }\n        _settle(key.currency0, delta.amount0());\n        _settle(key.currency1, delta.amount1());\n        return abi.encode(delta);\n    }\n\n    function _settle(Currency currency, int128 delta) internal {\n        if (delta < 0) {\n            uint256 owed = uint256(-int256(delta));\n            manager.sync(currency);\n            if (currency.isAddressZero()) manager.settle{value: owed}();\n            else {\n                token.transfer(address(manager), owed);\n                manager.settle();\n            }\n        } else if (delta > 0) {\n            manager.take(currency, address(this), uint256(int256(delta)));\n        }\n    }\n}","reproduction":"Save proof as test/scratch/QuantumCanaryMinimumInputProof.t.sol and run forge test --out /tmp/quantum-canary-out --cache-path /tmp/quantum-canary-cache --match-path test/scratch/QuantumCanaryMinimumInputProof.t.sol -vv. The proof deploys the real manager, actual token, a correctly mined hook and an untripped mock observer; it seeds matched hooked/unhooked native-ETH pools at sqrtPriceX96 = 2^96 with fee 12500, tickSpacing 60 and liquidity 100000 ether over [-600,600]. Submit SwapParams(true, type(int256).min, TickMath.getSqrtPriceAtTick(-60)). The unhooked control succeeds, paying 304238386100448167492 wei for 299535495591078093767 token units. Expected: the hooked trade reaches the same price limit and token fill, with a 1% bounty fee on its actual ETH payment. Actual: the hooked call reverts with Hooks.WrappedError containing Panic(0x11) from beforeSwap at line 109. The supplied test was run and failed for that reason; it contains no expectRevert, fork, ffi, skipped tests, or imports from other tests.","severity":"low","title":"Minimum signed exact-input budget overflows before a valid partial-fill buy"}],"hash":"af08fd4cc8b4c4ffa68c86b785d184d15ea8ed3a161543356de3fb932b4dfdd2","nodeId":"6c1f5775-bebe-48f6-8927-4f50828af435","outcome":"completed","summary":"Added adversarial, differential, and invariant tests, including a 20,480-call random sequence campaign.\n\n`forge build` passes. `forge test`: **55 passed, 0 failed, 1 skipped**—the existing mainnet rehearsal skips offline.\n\nReported one low-severity `int256.min` overflow in `.imd-findings.json`, with a self-contained failing proof. Implementation and configuration files remain unchanged.","treeHash":"e010ba977e039c2c121c7ce5e2c87393887a3d19","usage":{"cachedInputTokens":1827328,"inputTokens":120433,"model":"gpt-6-astra","outputTokens":29948,"runtime":"codex","turns":8,"wallClockMs":821714}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"30a6c1a419ef4f9c","findings":[{"citation":"resolved","description":"The brief requires reading canaryAddress() once in the constructor, and the implementation does exactly that with a high-level call. On a chain where the manifest's literal canary argument 0x626517225096671868609c1bbf9c1b416a4efd9a has no code (a fresh local chain, which is where .imd/reads/protected/univ4_hook/Hook.protected.t.sol deploys the attested creation code in setUp via deployAtFlags), the call returns no data, ABI decoding reverts, and the CREATE2 deployment fails with 'hook deployment reverted' before any protected test runs. This is not a hook defect under the brief (the read is mandated and the README documents that local rehearsals must deploy a mock canary first), but it is a concrete launch-pipeline state the judge should know about: the protected floor passes only if the verifier runs it against a mainnet fork or etches the observer's runtime at that address. No code change is owed if the verifier provides the canary; if it does not, admission is blocked for a reason the author cannot fix without violating the brief.","line":61,"path":"src/QuantumCanaryHook.sol","reproduction":"State: any EVM with no code at 0x626517225096671868609c1bBF9c1B416A4eFd9a. Call: new QuantumCanaryHook(IPoolManager(manager), IQuantumCanary(0x6265...9a)) (or create2 of the attested creation code with those encoded arguments). Expected by the protected harness: a deployed hook at an address carrying flags 0x20cc. Actual: the constructor reverts at line 61 (empty returndata from a no-code account), create2 returns address(0), and HookProtectedTest.setUp fails with 'hook deployment reverted'. Verified with a scratch Foundry test deploying a real PoolManager and asserting create() returns address(0) on a chain where live.code.length == 0.","severity":"info","snippet":"        address destination = canary.canaryAddress();","title":"Constructor's live canaryAddress() read means the hook cannot be built on any chain without code at 0x6265...9a (offline protected suite, local rehearsals)"},{"citation":"resolved","description":"pool.initialPrice is 2^96, i.e. sqrt(1) in Q64.96: one wei of ETH per one wei of CANARY. Both the manifest notes and README state that this is a 'reference' value and that the factory derives the real opening price from its 10 ETH opening-market-cap policy, but nothing in the repository or the supplied references demonstrates that the deployer ignores pool.initialPrice for a univ4_hook launch. The economic consequence if it is honoured is total: 90% of a 1e9 supply seeded as single-sided CANARY liquidity at or above 1 ETH per token means no rational buy ever executes, so the hook accrues no fee and the Quantum Canary bounty is never fed. The hook code itself is price-agnostic (its checks are on currency0 == ETH, fee 12500, spacing 60, hooks == self) and works at any opening price. Recorded as info because the field is schema-valid, matches the reference example manifest, and its effect depends on deployer behaviour the reviewer cannot observe; the judge should confirm with the deployer which price the factory initializes with before freezing the launch.","line":27,"path":"launch.json","reproduction":"State: factory initializes the pool with the manifest's initialPrice 79228162514264337593543950336 and adds the policy's token-only position above the current tick. Input: any exact-input ETH buy with sqrtPriceLimitX96 = MIN_SQRT_PRICE+1 and amountSpecified = -1 ether. Expected (launch intent): the buyer receives roughly (1 ETH / 10 ETH market cap) * supply worth of CANARY and 0.01 ETH flows to the canary. Actual at a 1:1 opening price: the AMM returns under 1 CANARY (minus 1.25% LP fee) for 1 ETH, so no buyer trades, no fee accrues, hook.accruedFees() stays 0 and the canary balance never grows. Conversely, if the deployer substitutes the policy price, there is no defect; the test fixtures in test/QuantumCanaryHook.t.sol use the same 1:1 price and prove only that the hook works at that price.","severity":"info","snippet":"    \"initialPrice\": \"79228162514264337593543950336\"","title":"launch.json records a 1:1 sqrtPriceX96 as initialPrice; if the deployer honours it, the pool opens at 1 ETH per CANARY (a 1,000,000,000 ETH fully-diluted valuation) and the bounty receives nothing"}],"hash":"b081e2a68801e9e5eb56cbf3552473409a594acbe4be7360d395fa1509c4684c","nodeId":"4544af70-42bc-4819-815c-41832deff504","outcome":"completed","summary":"The review is complete. The findings file is written, the scratch test is removed, and the only untracked change in the tree is `.imd-findings.json`.\n\n**Outcome: no economic defect found in the hook.** The report at `.imd-findings.json` holds two informational findings and a 16-row coverage record answering all nine listed entry points plus the constructor, `beforeInitialize`, the manifest, and four economic invariants.\n\n**What I verified in the Economic Security, Invariant and Flow Gap areas**\n\n- **Fee arithmetic in all four modes.** I traced the v4 core delta mapping for every `(zeroForOne, sign)` case. The exact-input buy and exact-output sell use a positive specified delta from `beforeSwap`; the other two use a positive unspecified delta from `afterSwap`. The `/99` gross-up and `/100` fee agree for every residue class, so the fee equals floor(1% of the total ETH side) on full fills and floor(1% of the filled ETH) on limit-bound partial fills. Route divergence between exact-in and exact-out is at most 1 wei at one boundary, which is dust and not reported.\n- **Reverting quote.** The self-call skips hook callbacks, always reverts, and re-raises core price-limit errors unchanged. I proved the quoted amount and the real amount are identical on full fills, and that on partial fills both stop at the same price limit.\n- **Settlement and conservation.** Every claim mint is matched by an equal core credit in the same swap. Burn and take are atomic, the only take recipient is the immutable canary address, and v4's ERC-6909 gives no operator or allowance path to move the hook's claims. The two stateful campaigns were re-run and pass.\n- **Retirement dependency.** I fetched the live observer's source. Its `isTripped()` is two storage reads and a balance check, far under the 30k-gas staticcall budget. The latch is write-once and the trip read precedes the payout in `afterSwap`.\n- **Token and manifest.** The token is unmodified OpenZeppelin ERC-20 with a one-time mint to the deployer. The manifest's keys, address case, argument order, permissions, fee tier and notes length are valid.\n\n**The two informational findings**\n\n1. **Construction requires live canary code.** The brief mandates reading `canaryAddress()` in the constructor, so on any chain with no code at the canary address the deployment reverts. I confirmed this with a scratch test. The offline protected suite runs in exactly that state, so admission depends on the verifier etching the observer or running on a fork. No code change is owed under the brief.\n2. **Manifest opening price.** `initialPrice` is 1:1, meaning 1 ETH per CANARY if the deployer honours it, which would mean no buys and no bounty income. The README and notes say the factory substitutes a policy-derived price. I could not verify that, so the judge should confirm it with the deployer.\n\n**Not reached:** the fork rehearsal at mainnet block 26,161,866 cannot be re-run here without network RPC, so the live-observer integration rests on the code read and the author's recorded run.","treeHash":null,"usage":{"cachedInputTokens":1253370,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":42952,"runtime":"claude","turns":34,"wallClockMs":622633}},{"artifacts":[],"attempt":1,"bundleHash":"d06e70c71f67fd80ef86a7a2961d186d011ba23cd80abae2858d51d6966c57d8","device":"507b07815bc3bf50","findings":[],"hash":"db7e512d7d2d06b88bbe07cdd3dd529ecedbe0b8eff2adc6bcadc406c2401df6","nodeId":"07ba0542-b044-4dda-a72e-198e766e06cb","outcome":"completed","summary":"All four reports reproduced and are fixed. The two proofs the work is held to now pass, along with the full suite, build, and format check.\n\n**What changed in the hook** (`src/QuantumCanaryHook.sol`):\n\n- **Constructor on an empty chain.** The one `canaryAddress()` read is now a guarded staticcall. With code at the observer, the answer must be a valid nonzero address or deployment reverts. With no code, and only for the launch observer at 0x6265…d9a, the hook falls back to the constant 0x379C…9e7E. Any other codeless address is refused, so a mistyped observer cannot deploy silently. I verified the constant live against mainnet, and it is also checked offline: a verbatim copy of the observer's published source is now a test fixture, and a test deploys it and asserts its seed-phrase derivation equals the constant.\n- **Payout refill race.** The single ETH-moving path reads the trip before any burn or take. That covers the automatic in-swap payout, a standalone call, and a router's in-unlock call, so the hook's own refill can no longer hide a drained bounty. The isolated self-call budget rose to cover the added bounded read.\n- **Signed boundaries (advisory, both real).** The exact-input magnitude is computed unchecked, and both quote requests are capped at core's int128 fill limit. A `type(int256).min` buy and a near-saturating exact-output sell now fill to the price limit and pay 1% of the ETH actually traded.\n\n**Tests added** to the existing suite: empty-chain CREATE2 deploy mirroring the protected floor, derivation check against the vendored observer, constructor refusals for unknown, broken, silent, and zero-answer observers, the refill race on both the standalone and in-unlock payout paths, and the two boundary swaps.\n\n**Docs** in README and SECURITY_REVIEW describe the guarded read, the fallback and how to verify it, the trip-before-payout ordering, and the boundary handling. Answers to all four findings are in `.imd-responses.json`.\n\nResults of the final run:\n\n| Check | Result |\n| --- | --- |\n| forge build | passes, same lint classes as before |\n| forge test | 46 passed, 0 failed, 1 fork test skipped |\n| forge fmt --check | clean |\n\nOne limit remains outside the hook's control and is documented: a refill by a third party before any hook action can still clear the observer's live alarm.","treeHash":"1d78abc110c028ddb217301912b9572c9b392b41","usage":{"cachedInputTokens":1826051,"inputTokens":514,"model":"claude-fable-5-1","outputTokens":41488,"runtime":"claude","turns":53,"wallClockMs":576611}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"e12f98dda6acc55f","findings":[],"hash":"e87d879efda9203e7475d829d7b73b81cd2d5475f7eccb94619761db1c4cd75d","nodeId":"cbd8df38-d932-4459-8d44-35165864651c","outcome":"completed","summary":"All three earlier findings are fixed and nothing new reproduced. The report is written to `.imd-findings.json` with an empty findings list and a 13-row coverage record.\n\n**Settled findings**\n\n- **High, constructor read on a codeless observer: fixed.** The constructor now falls back to the derived canary address only when the argument is the launch observer and it has no code, and refuses any other codeless address. Both attached proofs pass. I ran the protected floor itself against the attested creation code with the manifest's literal observer baked in and no code at that address. All four hook floor tests pass, plus the token floor. The live observer on mainnet still returns the same address the constant holds, with the alarm untripped.\n- **Medium, payout refilling the bounty before observing the trip: fixed.** The one ETH-moving routine now observes the trip before any burn or take, and every payout route goes through it. Both attached proofs pass. My own probe confirmed a trip latched through a standalone payout survives a later third-party refill, and all four swap modes then accrue nothing.\n- **Low, exact-output sell overflow near the int256 maximum: fixed.** The quote request is summed in uint256 and capped at core's int128 limit. The attached proof passes. The remaining failure right at the int256 boundary is core's own checked addition, not the hook's, and is documented.\n\n**Fresh pass**\n\nThe static-analysis leads on strict balance equality and the ignored unlock return are skip guards and intentional, not defects. The full suite, fuzz runs and both invariant suites pass, and the fork test skips cleanly offline. The manifest uses only schema keys, with constructor arguments and permissions matching the implementation.","treeHash":null,"usage":{"cachedInputTokens":940465,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":18144,"runtime":"claude","turns":27,"wallClockMs":255871}},{"artifacts":[],"attempt":1,"bundleHash":"64150f6540cb5a43a65ec1e5a3d3f1d271a29889e39f86bd2562f71183aa93ae","device":"fdeac05268aded13","findings":[],"hash":"f15c36227295ba96852e22b0bc492099033252c81d7aeb61abe32f00d7756d65","nodeId":"a8440166-57a4-41bb-bade-11a9f532624e","outcome":"completed","summary":"Created [launch.json](/root/.identitymd/work/69561148-b678-420c-bf0b-3b6fb49af48f/a8440166-57a4-41bb-bade-11a9f532624e/launch.json) matching the accepted contracts, constructor arguments, permissions, and required pool values.\n\nOnly the manifest is changed for submission.\n\nValidation: `forge build` succeeded; `forge test` reported **55 passed, 0 failed, 1 skipped** (optional mainnet fork suite).","treeHash":"ceed7559e0f428966270251a8bda77d11dece6a3","usage":{"cachedInputTokens":1134976,"inputTokens":76092,"model":"gpt-6-astra","outputTokens":6829,"runtime":"codex","turns":6,"wallClockMs":400773}}],"verification":[{"checks":[{"durationMs":2444,"exitCode":0,"name":"build","output":"Compiling 84 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.24s\nCompiler run successful!\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/QuantumCanaryHook.sol:247:13\n    │\n247 │             emit Retired();\n    │             ━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:109:34\n    │\n109 │                 uint256 budget = uint256(-params.amountSpecified);\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:111:49\n    │\n111 │                 quotedParams.amountSpecified += int256(reserved);\n    │                                                 ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:112:32\n    │\n112 │                 uint256 used = uint256(-int256(_quoteETH(key, quotedParams)));\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:116:36\n    │\n116 │                 uint256 reserved = uint256(params.amountSpecified) / 99;\n    │                                    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:117:49\n    │\n117 │                 quotedParams.amountSpecified += int256(reserved);\n    │                                                 ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:118:33\n    │\n118 │                 uint256 gross = uint256(int256(_quoteETH(key, quotedParams)));\n    │                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:123:63\n    │\n123 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int256(fee).toInt128(), 0), 0);\n    │                                                               ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/QuantumCanaryHook.sol:145:13\n    │\n145 │             emit FeeAccrued(PoolId.unwrap(key.toId()), fee);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:138:39\n    │\n138 │             fee = params.zeroForOne ? uint256(-int256(ethDelta)) / 99 : uint256(int256(ethDelta)) / 100;\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:138:73\n    │\n138 │             fee = params.zeroForOne ? uint256(-int256(ethDelta)) / 99 : uint256(int256(ethDelta)) / 100;\n    │                                                                         ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:139:27\n    │\n139 │             returnDelta = int256(fee).toInt128();\n    │                           ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/QuantumCanaryHook.sol:161:34\n    │\n161 │         if (beforeClaims == 0 || address(poolManager).balance == 0) return 0;\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/QuantumCanaryHook.sol:193:9\n    │\n193 │         emit BountyPaid(canaryAddress, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:188:31\n    │\n188 │         amount = _min(amount, uint256(uint128(type(int128).max)));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:188:39\n    │\n188 │         amount = _min(amount, uint256(uint128(type(int128).max)));\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/QuantumCanaryHook.sol:197:13\n    │\n197 │         if (address(poolManager).balance == 0 || accruedFees() == 0) return;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:220:40\n    │\n220 │             if (reason.length == 36 && bytes4(reason) == QuotedETH.selector) {\n    │                                        ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'bytes4' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\n","passed":true},{"durationMs":907,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/QuantumCanaryMainnetFork.t.sol:QuantumCanaryMainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 380.66µs (0.00ns CPU time)\n\nRan 5 tests for test/QuantumCanaryToken.t.sol:QuantumCanaryTokenTest\n[PASS] testFuzz_plainTransfersConserveFixedSupply(uint256) (runs: 1000, μ: 173390, ~: 173830)\n[PASS] test_delegatedTransferHonorsAllowanceAndHasNoFee() (gas: 201231)\n[PASS] test_exactLaunchSupplyBelongsEntirelyToDeployer() (gas: 61252)\n[PASS] test_insufficientBalanceAndInvalidRecipientRevert() (gas: 61672)\n[PASS] test_noMintOwnerPauseOrUpgradeInterface() (gas: 120850)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 11.06ms (11.22ms CPU time)\n\nRan 1 test for test/MineQuantumCanary.t.sol:MineQuantumCanaryTest\n[PASS] test_minerPredictsTheActuallyDeployedHook() (gas: 33980125)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 83.54ms (51.54ms CPU time)\n\nRan 27 tests for test/QuantumCanaryHook.t.sol:QuantumCanaryHookTest\n[PASS] testFuzz_allFourModesConserveValue(uint256,bool,bool) (runs: 1000, μ: 410701, ~: 384892)\n[PASS] testFuzz_partialFills(uint256,bool,bool,uint24) (runs: 1000, μ: 368879, ~: 348147)\n[PASS] test_badPriceLimitRevertsWithoutAccruingFees() (gas: 90199)\n[PASS] test_badTripReadsNeverBlockSwapsAndDoNotRetire() (gas: 2014966)\n[PASS] test_callbacksAndSelfHelpersRejectUnauthorizedCallers() (gas: 186521)\n[PASS] test_claimsCannotBeTransferredByAnyoneElse() (gas: 24996720)\n[PASS] test_declaredPermissionsExactlyMatchMinedAddress() (gas: 14181)\n[PASS] test_exactInputBuyPaysExactlyOnePercent() (gas: 418993)\n[PASS] test_exactInputSellPaysExactlyOnePercent() (gas: 349887)\n[PASS] test_exactOutputBuyPaysExactlyOnePercent() (gas: 350116)\n[PASS] test_exactOutputSellPreservesNetOutput() (gas: 401214)\n[PASS] test_failedPayoutDoesNotRevertAnySwapMode() (gas: 10785521)\n[PASS] test_freshTokenOnlyPoolAccruesThenAnyonePays() (gas: 24923907)\n[PASS] test_freshTokenOnlyPoolSupportsAllFourModes() (gas: 25817783)\n[PASS] test_gasExhaustingRecipientDoesNotBlockSwap() (gas: 9825823)\n[PASS] test_immutableDestinationEvenIfMockChangesItsAnswer() (gas: 451805)\n[PASS] test_laterSwapAutomaticallyPaysEarlierClaims() (gas: 25162472)\n[PASS] test_noAdministrativeOrWithdrawalInterfaceExists() (gas: 233659)\n[PASS] test_partialFillsAllFourModesChargeOnlyTheFilledETH() (gas: 1563826)\n[PASS] test_partialPayoutPreservesRemainingClaims() (gas: 24981556)\n[PASS] test_payoutInsideRouterUnlockAfterSettlement() (gas: 24755588)\n[PASS] test_policyPoolInitializesAndBadPoolsAreRefused() (gas: 140225)\n[PASS] test_recipientCannotReenterPayout() (gas: 9616169)\n[PASS] test_retirementIsPermanentAllFourModesAreUntouched() (gas: 1493003)\n[PASS] test_runtimeHasNoDelegatecallSelfdestructOrCallcode() (gas: 1974703)\n[PASS] test_tripDoesNotStrandPreviouslyAccruedFees() (gas: 25196079)\n[PASS] test_weiRoundingBoundariesDoNotOvercharge() (gas: 9962379)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 104.31ms (525.45ms CPU time)\n\nRan 1 test for test/QuantumCanaryInvariant.t.sol:QuantumCanaryInvariantTest\n[PASS] invariant_feesCanOnlyBeClaimsOrETHAtTheImmutableBounty() (runs: 128, calls: 8192, reverts: 0)\n\n╭---------------+--------------------+-------+---------+----------╮\n| Contract      | Selector           | Calls | Reverts | Discards |\n+=================================================================+\n| CanaryHandler | canaryReadBehavior | 1634  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | recipientBehavior  | 1689  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | retryPayout        | 1645  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | swap               | 1647  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | trip               | 1577  | 0       | 0        |\n╰---------------+--------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 818.17ms (772.26ms CPU time)\n\nRan 5 test suites in 820.18ms (1.02s CPU time): 34 tests passed, 0 failed, 1 skipped (35 total tests)\n","passed":true},{"durationMs":48,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"QuantumCanaryHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"QuantumCanaryHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"QuantumCanaryHook.payout()\",\"QuantumCanaryHook.quotePoolSwap((address,address,uint24,int24,address),(bool,int256,uint160))\",\"QuantumCanaryHook.redeemUnlocked()\",\"QuantumCanaryHook.unlockCallback(bytes)\",\"QuantumCanaryToken.approve(address,uint256)\",\"QuantumCanaryToken.transfer(address,uint256)\",\"QuantumCanaryToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":5,\"LICENSE\":21,\"README.md\":107,\"SECURITY_REVIEW.md\":40,\"foundry.toml\":11,\"remappings.txt\":5,\"script/MineQuantumCanary.s.sol\":49,\"src/HookFlags.sol\":30,\"src/IQuantumCanary.sol\":7,\"src/QuantumCanaryHook.sol\":277,\"src/QuantumCanaryToken.sol\":12,\"test/MineQuantumCanary.t.sol\":13,\"test/QuantumCanaryHook.t.sol\":492,\"test/QuantumCanaryInvariant.t.sol\":113,\"test/QuantumCanaryMainnetFork.t.sol\":66,\"test/QuantumCanaryToken.t.sol\":76,\"test/mocks/MockCanary.sol\":66,\"test/mocks/MockERC20.sol\":10},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1027,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/QuantumCanaryHook.sol:186: QuantumCanaryHook._redeem() (src/QuantumCanaryHook.sol#186-194) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/QuantumCanaryHook.sol:196: QuantumCanaryHook._tryPayout() (src/QuantumCanaryHook.sol#196-207) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/QuantumCanaryHook.sol:158: QuantumCanaryHook.payout() (src/QuantumCanaryHook.sol#158-170) uses a dangerous strict equality:\n[medium/medium] unused-return at src/QuantumCanaryHook.sol:158: QuantumCanaryHook.payout() (src/QuantumCanaryHook.sol#158-170) ignores return value by poolManager.unlock() (src/QuantumCanaryHook.sol#166)\n[low/medium] reentrancy-events at src/QuantumCanaryHook.sol:126: Reentrancy in QuantumCanaryHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/QuantumCanaryHook.sol#126-149):\n[low/medium] reentrancy-events at src/QuantumCanaryHook.sol:186: Reentrancy in QuantumCanaryHook._redeem() (src/QuantumCanaryHook.sol#186-194):","passed":true},{"durationMs":440,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/QuantumCanaryHook.sol:61: Reentrancy: State change after external call\n[high] strict-equality-contract-balance at src/QuantumCanaryHook.sol:161: Dangerous strict equality checks on contract balances (2 places)\n[low] internal-function-used-once at src/HookFlags.sol:22: Internal Function Used Only Once\n[low] large-numeric-literal at src/QuantumCanaryHook.sol:30: Large Numeric Literal (5 places)\n[low] literal-instead-of-constant at src/QuantumCanaryHook.sol:110: Literal Instead of Constant (6 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"10d045f022228148f6be7e89d97598812c846cc31d5fa48691bd3f60f56d6630","verifiedTreeHash":"cb5bf435613831a296c90eed0359a46d2b4b536e","verifierVersion":"0.1.0+fdeb4d4a"},{"checks":[{"durationMs":4148,"exitCode":0,"name":"build","output":"Compiling 89 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.78s\nCompiler run successful!\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/QuantumCanaryHook.sol:78:55\n   │\n78 │             if (!ok || answer.length != 32 || uint256(bytes32(answer)) > type(uint160).max) revert InvalidAddress();\n   │                                                       ━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes32' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/QuantumCanaryHook.sol:79:51\n   │\n79 │             destination = address(uint160(uint256(bytes32(answer))));\n   │                                                   ━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes32' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/QuantumCanaryHook.sol:275:13\n    │\n275 │             emit Retired();\n    │             ━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:132:30\n    │\n132 │                     budget = uint256(-params.amountSpecified); // type(int256).min wraps to 2**255\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:135:49\n    │\n135 │                 quotedParams.amountSpecified = -int256(_min(budget - reserved, MAX_CORE_AMOUNT));\n    │                                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:136:32\n    │\n136 │                 uint256 used = uint256(-int256(_quoteETH(key, quotedParams)));\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:140:37\n    │\n140 │                 uint256 requested = uint256(params.amountSpecified);\n    │                                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:142:48\n    │\n142 │                 quotedParams.amountSpecified = int256(_min(requested + reserved, MAX_CORE_AMOUNT));\n    │                                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:143:33\n    │\n143 │                 uint256 gross = uint256(int256(_quoteETH(key, quotedParams)));\n    │                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:148:63\n    │\n148 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int256(fee).toInt128(), 0), 0);\n    │                                                               ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/QuantumCanaryHook.sol:170:13\n    │\n170 │             emit FeeAccrued(PoolId.unwrap(key.toId()), fee);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:163:39\n    │\n163 │             fee = params.zeroForOne ? uint256(-int256(ethDelta)) / 99 : uint256(int256(ethDelta)) / 100;\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:163:73\n    │\n163 │             fee = params.zeroForOne ? uint256(-int256(ethDelta)) / 99 : uint256(int256(ethDelta)) / 100;\n    │                                                                         ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:164:27\n    │\n164 │             returnDelta = int256(fee).toInt128();\n    │                           ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/QuantumCanaryHook.sol:186:34\n    │\n186 │         if (beforeClaims == 0 || address(poolManager).balance == 0) return 0;\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/QuantumCanaryHook.sol:221:9\n    │\n221 │         emit BountyPaid(canaryAddress, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/QuantumCanaryHook.sol:225:13\n    │\n225 │         if (address(poolManager).balance == 0 || accruedFees() == 0) return;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:248:40\n    │\n248 │             if (reason.length == 36 && bytes4(reason) == QuotedETH.selector) {\n    │                                        ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'bytes4' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\n","passed":true},{"durationMs":4950,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/QuantumCanaryMainnetFork.t.sol:QuantumCanaryMainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 428.33µs (0.00ns CPU time)\n\nRan 1 test for test/MineQuantumCanary.t.sol:MineQuantumCanaryTest\n[PASS] test_minerPredictsTheActuallyDeployedHook() (gas: 6485949)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 18.70ms (7.78ms CPU time)\n\nRan 5 tests for test/QuantumCanaryToken.t.sol:QuantumCanaryTokenTest\n[PASS] testFuzz_plainTransfersConserveFixedSupply(uint256) (runs: 1000, μ: 173703, ~: 173878)\n[PASS] test_delegatedTransferHonorsAllowanceAndHasNoFee() (gas: 201231)\n[PASS] test_exactLaunchSupplyBelongsEntirelyToDeployer() (gas: 61252)\n[PASS] test_insufficientBalanceAndInvalidRecipientRevert() (gas: 61672)\n[PASS] test_noMintOwnerPauseOrUpgradeInterface() (gas: 120850)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 332.03ms (332.05ms CPU time)\n\nRan 9 tests for test/QuantumCanaryDifferential.t.sol:QuantumCanaryDifferentialTest\n[PASS] testFuzz_largeSignedBuyBudgetsPreserveCoreFills(uint256,uint24,uint16) (runs: 1000, μ: 1062320, ~: 955997)\n[PASS] testFuzz_quotesAndFeesMatchUnhookedPool(uint256,bool,bool,uint24,uint16) (runs: 1000, μ: 898367, ~: 875226)\n[PASS] testFuzz_retiredSwapsExactlyMatchUnhookedPool(uint256,bool,bool) (runs: 1000, μ: 1791402, ~: 1634772)\n[PASS] test_exhaustionAcrossLiquidityGapsInEveryMode() (gas: 4384742)\n[PASS] test_largeBuyBudgetsAroundTheQuoteCapHaveIdenticalFills() (gas: 8958575)\n[PASS] test_minimumSignedBuyMatchesTheOriginalUnhookedReproduction() (gas: 824977)\n[PASS] test_minimumSignedBuysRemainUntouchedAfterRetirement() (gas: 1481009)\n[PASS] test_noLiquidityMeansNoTradeAndNoBountyFee() (gas: 2968243)\n[PASS] test_roundTripsAcrossTickBoundariesDoNotLeakQuoteState() (gas: 7397354)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 348.12ms (1.07s CPU time)\n\nRan 16 tests for test/QuantumCanaryAdversarial.t.sol:QuantumCanaryAdversarialTest\n[PASS] testFuzz_spoofedManagerSenderDoesNotAuthorizeCallbacks(address,bytes32) (runs: 1000, μ: 323319, ~: 323603)\n[PASS] testFuzz_transientFeesDoNotLeakBetweenBatchModes(uint256,bytes32) (runs: 1000, μ: 742048, ~: 742984)\n[PASS] test_allEnabledCallbacksEnforceEveryPoolGuard() (gas: 906960)\n[PASS] test_allFourModesCanBeNettedInOneFreshManagerUnlock() (gas: 721150)\n[PASS] test_claimBurnCannotBeUsedToStealBountyDuringUnlock() (gas: 1002905)\n[PASS] test_constructorRefusesAnAddressWithWrongPermissionBits() (gas: 229006)\n[PASS] test_constructorRejectsAnUnavailableDestination() (gas: 217241)\n[PASS] test_constructorRejectsMissingDependenciesAndUnpayableDestinations() (gas: 1226023)\n[PASS] test_failedCanaryReadsKeepAllFourModesLiveAndFeeBearing() (gas: 8138508)\n[PASS] test_failedSecondSwapRollsBackFirstAndClearsTransientAccounting() (gas: 1208311)\n[PASS] test_minimumSignedBuyOnFreshManagerAccruesRedeemableClaims() (gas: 836204)\n[PASS] test_revertingRecipientPreservesClaimsAcrossLockedAndUnlockedPayouts() (gas: 2634577)\n[PASS] test_tripEmitsRetirementAndOracleIsNeverReadAgain() (gas: 1965275)\n[PASS] test_unsettledSwapRollsBackBountyPaymentAndPoolState() (gas: 1299827)\n[PASS] test_unsettledTripObservationDoesNotPersistOrDestroyOldClaims() (gas: 1149331)\n[PASS] test_zeroAmountNeverAccruesOrRetires() (gas: 872455)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 348.17ms (339.59ms CPU time)\n\nRan 34 tests for test/QuantumCanaryHook.t.sol:QuantumCanaryHookTest\n[PASS] testFuzz_allFourModesConserveValue(uint256,bool,bool) (runs: 1000, μ: 412218, ~: 386438)\n[PASS] testFuzz_partialFills(uint256,bool,bool,uint24) (runs: 1000, μ: 370937, ~: 349973)\n[PASS] test_attestedCreationCodeDeploysWithNoCodeAtTheLaunchObserver() (gas: 19501977)\n[PASS] test_badPriceLimitRevertsWithoutAccruingFees() (gas: 90253)\n[PASS] test_badTripReadsNeverBlockSwapsAndDoNotRetire() (gas: 2066318)\n[PASS] test_callbacksAndSelfHelpersRejectUnauthorizedCallers() (gas: 186587)\n[PASS] test_claimsCannotBeTransferredByAnyoneElse() (gas: 53026514)\n[PASS] test_constructorRejectsUnknownEmptyOrBrokenObservers() (gas: 7600)\n[PASS] test_declaredPermissionsExactlyMatchMinedAddress() (gas: 14225)\n[PASS] test_exactInputBuyPaysExactlyOnePercent() (gas: 420574)\n[PASS] test_exactInputSellPaysExactlyOnePercent() (gas: 351414)\n[PASS] test_exactOutputBuyPaysExactlyOnePercent() (gas: 351665)\n[PASS] test_exactOutputSellPreservesNetOutput() (gas: 402770)\n[PASS] test_failedPayoutDoesNotRevertAnySwapMode() (gas: 131550701)\n[PASS] test_freshTokenOnlyPoolAccruesThenAnyonePays() (gas: 52900188)\n[PASS] test_freshTokenOnlyPoolSupportsAllFourModes() (gas: 53807229)\n[PASS] test_gasExhaustingRecipientDoesNotBlockSwap() (gas: 130524734)\n[PASS] test_immutableDestinationEvenIfMockChangesItsAnswer() (gas: 453386)\n[PASS] test_laterSwapAutomaticallyPaysEarlierClaims() (gas: 53135194)\n[PASS] test_launchDestinationMatchesTheObserverSourceDerivation() (gas: 1434443)\n[PASS] test_minimumSignedExactInputBuyFillsToThePriceLimit() (gas: 294041)\n[PASS] test_noAdministrativeOrWithdrawalInterfaceExists() (gas: 233725)\n[PASS] test_partialFillsAllFourModesChargeOnlyTheFilledETH() (gas: 1570065)\n[PASS] test_partialPayoutPreservesRemainingClaims() (gas: 52966540)\n[PASS] test_payoutInsideRouterUnlockAfterSettlement() (gas: 52721311)\n[PASS] test_payoutInsideRouterUnlockAlsoLatchesTheTripFirst() (gas: 6398480)\n[PASS] test_payoutLatchesTheTripBeforeItsRefillClearsTheLiveAlarm() (gas: 6437025)\n[PASS] test_policyPoolInitializesAndBadPoolsAreRefused() (gas: 140225)\n[PASS] test_recipientCannotReenterPayout() (gas: 130276758)\n[PASS] test_retirementIsPermanentAllFourModesAreUntouched() (gas: 1493223)\n[PASS] test_runtimeHasNoDelegatecallSelfdestructOrCallcode() (gas: 1974434)\n[PASS] test_saturatingExactOutputSellFillsToThePriceLimit() (gas: 286300)\n[PASS] test_tripDoesNotStrandPreviouslyAccruedFees() (gas: 53167682)\n[PASS] test_weiRoundingBoundariesDoNotOvercharge() (gas: 9995440)\nSuite result: ok. 34 passed; 0 failed; 0 skipped; finished in 348.15ms (997.19ms CPU time)\n\nRan 1 test for test/QuantumCanaryInvariant.t.sol:QuantumCanaryInvariantTest\n[PASS] invariant_feesCanOnlyBeClaimsOrETHAtTheImmutableBounty() (runs: 128, calls: 8192, reverts: 0)\n\n╭---------------+--------------------+-------+---------+----------╮\n| Contract      | Selector           | Calls | Reverts | Discards |\n+=================================================================+\n| CanaryHandler | canaryReadBehavior | 1653  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | recipientBehavior  | 1621  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | retryPayout        | 1622  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | swap               | 1671  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | trip               | 1625  | 0       | 0        |\n╰---------------+--------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 847.82ms (829.80ms CPU time)\n\nRan 1 test for test/QuantumCanarySettlementInvariant.t.sol:QuantumCanarySettlementInvariantTest\n[PASS] invariant_feesRemainBackedAndExclusiveToTheCachedBounty() (runs: 256, calls: 20480, reverts: 0)\n\n╭-------------------------+---------------------------+-------+---------+----------╮\n| Contract                | Selector                  | Calls | Reverts | Discards |\n+==================================================================================+\n| CanarySettlementHandler | attemptClaimTheft         | 3465  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| CanarySettlementHandler | batch                     | 3435  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| CanarySettlementHandler | canaryReadBehavior        | 3343  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| CanarySettlementHandler | changeReportedDestination | 3365  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| CanarySettlementHandler | payout                    | 3456  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| CanarySettlementHandler | recipientBehavior         | 3416  | 0       | 0        |\n╰-------------------------+---------------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.85s (4.81s CPU time)\n\nRan 8 test suites in 4.85s (7.09s CPU time): 67 tests passed, 0 failed, 1 skipped (68 total tests)\n","passed":true},{"durationMs":55,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"QuantumCanaryHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"QuantumCanaryHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"QuantumCanaryHook.payout()\",\"QuantumCanaryHook.quotePoolSwap((address,address,uint24,int24,address),(bool,int256,uint160))\",\"QuantumCanaryHook.redeemUnlocked()\",\"QuantumCanaryHook.unlockCallback(bytes)\",\"QuantumCanaryToken.approve(address,uint256)\",\"QuantumCanaryToken.transfer(address,uint256)\",\"QuantumCanaryToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":5,\"LICENSE\":21,\"README.md\":113,\"SECURITY_REVIEW.md\":46,\"foundry.toml\":11,\"launch.json\":30,\"remappings.txt\":5,\"script/MineQuantumCanary.s.sol\":49,\"src/HookFlags.sol\":30,\"src/IQuantumCanary.sol\":7,\"src/QuantumCanaryHook.sol\":305,\"src/QuantumCanaryToken.sol\":12,\"test/MineQuantumCanary.t.sol\":13,\"test/QuantumCanaryAdversarial.t.sol\":292,\"test/QuantumCanaryDifferential.t.sol\":302,\"test/QuantumCanaryHook.t.sol\":619,\"test/QuantumCanaryInvariant.t.sol\":113,\"test/QuantumCanaryMainnetFork.t.sol\":66,\"test/QuantumCanarySettlementInvariant.t.sol\":167,\"test/QuantumCanaryToken.t.sol\":76,\"test/README.md\":49,\"test/helpers/CanaryScenario.sol\":189,\"test/mocks/MockCanary.sol\":66,\"test/mocks/MockERC20.sol\":10,\"test/mocks/QuantumCanaryObserver.sol\":119},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"5c231cb9328ce547777c9e88feeb8ad76005d4fe7c59749454f5939b22d6cfda","verifiedTreeHash":"88b5e918e9a10437a931225d435f40702e7890d7","verifierVersion":"0.1.0+fdeb4d4a"},{"checks":[{"durationMs":4120,"exitCode":0,"name":"build","output":"Compiling 89 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.75s\nCompiler run successful!\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/QuantumCanaryHook.sol:78:55\n   │\n78 │             if (!ok || answer.length != 32 || uint256(bytes32(answer)) > type(uint160).max) revert InvalidAddress();\n   │                                                       ━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes32' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/QuantumCanaryHook.sol:79:51\n   │\n79 │             destination = address(uint160(uint256(bytes32(answer))));\n   │                                                   ━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes32' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/QuantumCanaryHook.sol:275:13\n    │\n275 │             emit Retired();\n    │             ━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:132:30\n    │\n132 │                     budget = uint256(-params.amountSpecified); // type(int256).min wraps to 2**255\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:135:49\n    │\n135 │                 quotedParams.amountSpecified = -int256(_min(budget - reserved, MAX_CORE_AMOUNT));\n    │                                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:136:32\n    │\n136 │                 uint256 used = uint256(-int256(_quoteETH(key, quotedParams)));\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:140:37\n    │\n140 │                 uint256 requested = uint256(params.amountSpecified);\n    │                                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:142:48\n    │\n142 │                 quotedParams.amountSpecified = int256(_min(requested + reserved, MAX_CORE_AMOUNT));\n    │                                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:143:33\n    │\n143 │                 uint256 gross = uint256(int256(_quoteETH(key, quotedParams)));\n    │                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:148:63\n    │\n148 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int256(fee).toInt128(), 0), 0);\n    │                                                               ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/QuantumCanaryHook.sol:170:13\n    │\n170 │             emit FeeAccrued(PoolId.unwrap(key.toId()), fee);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:163:39\n    │\n163 │             fee = params.zeroForOne ? uint256(-int256(ethDelta)) / 99 : uint256(int256(ethDelta)) / 100;\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:163:73\n    │\n163 │             fee = params.zeroForOne ? uint256(-int256(ethDelta)) / 99 : uint256(int256(ethDelta)) / 100;\n    │                                                                         ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:164:27\n    │\n164 │             returnDelta = int256(fee).toInt128();\n    │                           ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/QuantumCanaryHook.sol:186:34\n    │\n186 │         if (beforeClaims == 0 || address(poolManager).balance == 0) return 0;\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/QuantumCanaryHook.sol:221:9\n    │\n221 │         emit BountyPaid(canaryAddress, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/QuantumCanaryHook.sol:225:13\n    │\n225 │         if (address(poolManager).balance == 0 || accruedFees() == 0) return;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:248:40\n    │\n248 │             if (reason.length == 36 && bytes4(reason) == QuotedETH.selector) {\n    │                                        ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'bytes4' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\n","passed":true},{"durationMs":5027,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/QuantumCanaryMainnetFork.t.sol:QuantumCanaryMainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 408.29µs (0.00ns CPU time)\n\nRan 1 test for test/MineQuantumCanary.t.sol:MineQuantumCanaryTest\n[PASS] test_minerPredictsTheActuallyDeployedHook() (gas: 6485949)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 15.15ms (6.83ms CPU time)\n\nRan 16 tests for test/QuantumCanaryAdversarial.t.sol:QuantumCanaryAdversarialTest\n[PASS] testFuzz_spoofedManagerSenderDoesNotAuthorizeCallbacks(address,bytes32) (runs: 1000, μ: 323286, ~: 323603)\n[PASS] testFuzz_transientFeesDoNotLeakBetweenBatchModes(uint256,bytes32) (runs: 1000, μ: 741875, ~: 740095)\n[PASS] test_allEnabledCallbacksEnforceEveryPoolGuard() (gas: 906960)\n[PASS] test_allFourModesCanBeNettedInOneFreshManagerUnlock() (gas: 721150)\n[PASS] test_claimBurnCannotBeUsedToStealBountyDuringUnlock() (gas: 1002905)\n[PASS] test_constructorRefusesAnAddressWithWrongPermissionBits() (gas: 229006)\n[PASS] test_constructorRejectsAnUnavailableDestination() (gas: 217241)\n[PASS] test_constructorRejectsMissingDependenciesAndUnpayableDestinations() (gas: 1226023)\n[PASS] test_failedCanaryReadsKeepAllFourModesLiveAndFeeBearing() (gas: 8138508)\n[PASS] test_failedSecondSwapRollsBackFirstAndClearsTransientAccounting() (gas: 1208311)\n[PASS] test_minimumSignedBuyOnFreshManagerAccruesRedeemableClaims() (gas: 836204)\n[PASS] test_revertingRecipientPreservesClaimsAcrossLockedAndUnlockedPayouts() (gas: 2634577)\n[PASS] test_tripEmitsRetirementAndOracleIsNeverReadAgain() (gas: 1965275)\n[PASS] test_unsettledSwapRollsBackBountyPaymentAndPoolState() (gas: 1299827)\n[PASS] test_unsettledTripObservationDoesNotPersistOrDestroyOldClaims() (gas: 1149331)\n[PASS] test_zeroAmountNeverAccruesOrRetires() (gas: 872455)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 374.92ms (684.16ms CPU time)\n\nRan 9 tests for test/QuantumCanaryDifferential.t.sol:QuantumCanaryDifferentialTest\n[PASS] testFuzz_largeSignedBuyBudgetsPreserveCoreFills(uint256,uint24,uint16) (runs: 1000, μ: 1059239, ~: 955941)\n[PASS] testFuzz_quotesAndFeesMatchUnhookedPool(uint256,bool,bool,uint24,uint16) (runs: 1000, μ: 906444, ~: 884656)\n[PASS] testFuzz_retiredSwapsExactlyMatchUnhookedPool(uint256,bool,bool) (runs: 1000, μ: 1801732, ~: 1634799)\n[PASS] test_exhaustionAcrossLiquidityGapsInEveryMode() (gas: 4384742)\n[PASS] test_largeBuyBudgetsAroundTheQuoteCapHaveIdenticalFills() (gas: 8958575)\n[PASS] test_minimumSignedBuyMatchesTheOriginalUnhookedReproduction() (gas: 824977)\n[PASS] test_minimumSignedBuysRemainUntouchedAfterRetirement() (gas: 1481009)\n[PASS] test_noLiquidityMeansNoTradeAndNoBountyFee() (gas: 2968243)\n[PASS] test_roundTripsAcrossTickBoundariesDoNotLeakQuoteState() (gas: 7397354)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 374.99ms (1.09s CPU time)\n\nRan 5 tests for test/QuantumCanaryToken.t.sol:QuantumCanaryTokenTest\n[PASS] testFuzz_plainTransfersConserveFixedSupply(uint256) (runs: 1000, μ: 173367, ~: 173830)\n[PASS] test_delegatedTransferHonorsAllowanceAndHasNoFee() (gas: 201231)\n[PASS] test_exactLaunchSupplyBelongsEntirelyToDeployer() (gas: 61252)\n[PASS] test_insufficientBalanceAndInvalidRecipientRevert() (gas: 61672)\n[PASS] test_noMintOwnerPauseOrUpgradeInterface() (gas: 120850)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 374.95ms (375.08ms CPU time)\n\nRan 34 tests for test/QuantumCanaryHook.t.sol:QuantumCanaryHookTest\n[PASS] testFuzz_allFourModesConserveValue(uint256,bool,bool) (runs: 1000, μ: 412120, ~: 386441)\n[PASS] testFuzz_partialFills(uint256,bool,bool,uint24) (runs: 1000, μ: 368847, ~: 349612)\n[PASS] test_attestedCreationCodeDeploysWithNoCodeAtTheLaunchObserver() (gas: 19501977)\n[PASS] test_badPriceLimitRevertsWithoutAccruingFees() (gas: 90253)\n[PASS] test_badTripReadsNeverBlockSwapsAndDoNotRetire() (gas: 2066318)\n[PASS] test_callbacksAndSelfHelpersRejectUnauthorizedCallers() (gas: 186587)\n[PASS] test_claimsCannotBeTransferredByAnyoneElse() (gas: 53026514)\n[PASS] test_constructorRejectsUnknownEmptyOrBrokenObservers() (gas: 7600)\n[PASS] test_declaredPermissionsExactlyMatchMinedAddress() (gas: 14225)\n[PASS] test_exactInputBuyPaysExactlyOnePercent() (gas: 420574)\n[PASS] test_exactInputSellPaysExactlyOnePercent() (gas: 351414)\n[PASS] test_exactOutputBuyPaysExactlyOnePercent() (gas: 351665)\n[PASS] test_exactOutputSellPreservesNetOutput() (gas: 402770)\n[PASS] test_failedPayoutDoesNotRevertAnySwapMode() (gas: 131550701)\n[PASS] test_freshTokenOnlyPoolAccruesThenAnyonePays() (gas: 52900188)\n[PASS] test_freshTokenOnlyPoolSupportsAllFourModes() (gas: 53807229)\n[PASS] test_gasExhaustingRecipientDoesNotBlockSwap() (gas: 130524734)\n[PASS] test_immutableDestinationEvenIfMockChangesItsAnswer() (gas: 453386)\n[PASS] test_laterSwapAutomaticallyPaysEarlierClaims() (gas: 53135194)\n[PASS] test_launchDestinationMatchesTheObserverSourceDerivation() (gas: 1434443)\n[PASS] test_minimumSignedExactInputBuyFillsToThePriceLimit() (gas: 294041)\n[PASS] test_noAdministrativeOrWithdrawalInterfaceExists() (gas: 233725)\n[PASS] test_partialFillsAllFourModesChargeOnlyTheFilledETH() (gas: 1570065)\n[PASS] test_partialPayoutPreservesRemainingClaims() (gas: 52966540)\n[PASS] test_payoutInsideRouterUnlockAfterSettlement() (gas: 52721311)\n[PASS] test_payoutInsideRouterUnlockAlsoLatchesTheTripFirst() (gas: 6398480)\n[PASS] test_payoutLatchesTheTripBeforeItsRefillClearsTheLiveAlarm() (gas: 6437025)\n[PASS] test_policyPoolInitializesAndBadPoolsAreRefused() (gas: 140225)\n[PASS] test_recipientCannotReenterPayout() (gas: 130276758)\n[PASS] test_retirementIsPermanentAllFourModesAreUntouched() (gas: 1493223)\n[PASS] test_runtimeHasNoDelegatecallSelfdestructOrCallcode() (gas: 1974434)\n[PASS] test_saturatingExactOutputSellFillsToThePriceLimit() (gas: 286300)\n[PASS] test_tripDoesNotStrandPreviouslyAccruedFees() (gas: 53167682)\n[PASS] test_weiRoundingBoundariesDoNotOvercharge() (gas: 9995440)\nSuite result: ok. 34 passed; 0 failed; 0 skipped; finished in 375.12ms (1.32s CPU time)\n\nRan 1 test for test/QuantumCanaryInvariant.t.sol:QuantumCanaryInvariantTest\n[PASS] invariant_feesCanOnlyBeClaimsOrETHAtTheImmutableBounty() (runs: 128, calls: 8192, reverts: 0)\n\n╭---------------+--------------------+-------+---------+----------╮\n| Contract      | Selector           | Calls | Reverts | Discards |\n+=================================================================+\n| CanaryHandler | canaryReadBehavior | 1567  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | recipientBehavior  | 1621  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | retryPayout        | 1686  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | swap               | 1709  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | trip               | 1609  | 0       | 0        |\n╰---------------+--------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 896.83ms (878.42ms CPU time)\n\nRan 1 test for test/QuantumCanarySettlementInvariant.t.sol:QuantumCanarySettlementInvariantTest\n[PASS] invariant_feesRemainBackedAndExclusiveToTheCachedBounty() (runs: 256, calls: 20480, reverts: 0)\n\n╭-------------------------+---------------------------+-------+---------+----------╮\n| Contract                | Selector                  | Calls | Reverts | Discards |\n+==================================================================================+\n| CanarySettlementHandler | attemptClaimTheft         | 3487  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| CanarySettlementHandler | batch                     | 3437  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| CanarySettlementHandler | canaryReadBehavior        | 3419  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| CanarySettlementHandler | changeReportedDestination | 3426  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| CanarySettlementHandler | payout                    | 3351  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| CanarySettlementHandler | recipientBehavior         | 3360  | 0       | 0        |\n╰-------------------------+---------------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.93s (4.90s CPU time)\n\nRan 8 test suites in 4.93s (7.34s CPU time): 67 tests passed, 0 failed, 1 skipped (68 total tests)\n","passed":true},{"durationMs":54,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"QuantumCanaryHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"QuantumCanaryHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"QuantumCanaryHook.payout()\",\"QuantumCanaryHook.quotePoolSwap((address,address,uint24,int24,address),(bool,int256,uint160))\",\"QuantumCanaryHook.redeemUnlocked()\",\"QuantumCanaryHook.unlockCallback(bytes)\",\"QuantumCanaryToken.approve(address,uint256)\",\"QuantumCanaryToken.transfer(address,uint256)\",\"QuantumCanaryToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":5,\"LICENSE\":21,\"README.md\":113,\"SECURITY_REVIEW.md\":46,\"foundry.toml\":11,\"remappings.txt\":5,\"script/MineQuantumCanary.s.sol\":49,\"src/HookFlags.sol\":30,\"src/IQuantumCanary.sol\":7,\"src/QuantumCanaryHook.sol\":305,\"src/QuantumCanaryToken.sol\":12,\"test/MineQuantumCanary.t.sol\":13,\"test/QuantumCanaryAdversarial.t.sol\":292,\"test/QuantumCanaryDifferential.t.sol\":302,\"test/QuantumCanaryHook.t.sol\":619,\"test/QuantumCanaryInvariant.t.sol\":113,\"test/QuantumCanaryMainnetFork.t.sol\":66,\"test/QuantumCanarySettlementInvariant.t.sol\":167,\"test/QuantumCanaryToken.t.sol\":76,\"test/README.md\":49,\"test/helpers/CanaryScenario.sol\":189,\"test/mocks/MockCanary.sol\":66,\"test/mocks/MockERC20.sol\":10,\"test/mocks/QuantumCanaryObserver.sol\":119},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"74a651ad2c23fcef5955843dfde0738236ab48b62ebbcff4dec923b15b44c427","verifiedTreeHash":"03ff1b34ceb21eca0eac299e42429724d22ef8c8","verifierVersion":"0.1.0+fdeb4d4a"},{"checks":[{"durationMs":4093,"exitCode":0,"name":"build","output":"Compiling 88 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.76s\nCompiler run successful!\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/QuantumCanaryHook.sol:247:13\n    │\n247 │             emit Retired();\n    │             ━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:109:34\n    │\n109 │                 uint256 budget = uint256(-params.amountSpecified);\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:111:49\n    │\n111 │                 quotedParams.amountSpecified += int256(reserved);\n    │                                                 ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:112:32\n    │\n112 │                 uint256 used = uint256(-int256(_quoteETH(key, quotedParams)));\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:116:36\n    │\n116 │                 uint256 reserved = uint256(params.amountSpecified) / 99;\n    │                                    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:117:49\n    │\n117 │                 quotedParams.amountSpecified += int256(reserved);\n    │                                                 ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:118:33\n    │\n118 │                 uint256 gross = uint256(int256(_quoteETH(key, quotedParams)));\n    │                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:123:63\n    │\n123 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int256(fee).toInt128(), 0), 0);\n    │                                                               ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/QuantumCanaryHook.sol:145:13\n    │\n145 │             emit FeeAccrued(PoolId.unwrap(key.toId()), fee);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:138:39\n    │\n138 │             fee = params.zeroForOne ? uint256(-int256(ethDelta)) / 99 : uint256(int256(ethDelta)) / 100;\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:138:73\n    │\n138 │             fee = params.zeroForOne ? uint256(-int256(ethDelta)) / 99 : uint256(int256(ethDelta)) / 100;\n    │                                                                         ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:139:27\n    │\n139 │             returnDelta = int256(fee).toInt128();\n    │                           ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/QuantumCanaryHook.sol:161:34\n    │\n161 │         if (beforeClaims == 0 || address(poolManager).balance == 0) return 0;\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/QuantumCanaryHook.sol:193:9\n    │\n193 │         emit BountyPaid(canaryAddress, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:188:31\n    │\n188 │         amount = _min(amount, uint256(uint128(type(int128).max)));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:188:39\n    │\n188 │         amount = _min(amount, uint256(uint128(type(int128).max)));\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/QuantumCanaryHook.sol:197:13\n    │\n197 │         if (address(poolManager).balance == 0 || accruedFees() == 0) return;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:220:40\n    │\n220 │             if (reason.length == 36 && bytes4(reason) == QuotedETH.selector) {\n    │                                        ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'bytes4' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\n","passed":true},{"durationMs":4887,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/QuantumCanaryMainnetFork.t.sol:QuantumCanaryMainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 397.28µs (0.00ns CPU time)\n\nRan 5 tests for test/QuantumCanaryToken.t.sol:QuantumCanaryTokenTest\n[PASS] testFuzz_plainTransfersConserveFixedSupply(uint256) (runs: 1000, μ: 173500, ~: 173842)\n[PASS] test_delegatedTransferHonorsAllowanceAndHasNoFee() (gas: 201231)\n[PASS] test_exactLaunchSupplyBelongsEntirelyToDeployer() (gas: 61252)\n[PASS] test_insufficientBalanceAndInvalidRecipientRevert() (gas: 61672)\n[PASS] test_noMintOwnerPauseOrUpgradeInterface() (gas: 120850)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 11.74ms (12.21ms CPU time)\n\nRan 1 test for test/MineQuantumCanary.t.sol:MineQuantumCanaryTest\n[PASS] test_minerPredictsTheActuallyDeployedHook() (gas: 33980125)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 88.25ms (59.26ms CPU time)\n\nRan 5 tests for test/QuantumCanaryDifferential.t.sol:QuantumCanaryDifferentialTest\n[PASS] testFuzz_quotesAndFeesMatchUnhookedPool(uint256,bool,bool,uint24,uint16) (runs: 1000, μ: 901217, ~: 876206)\n[PASS] testFuzz_retiredSwapsExactlyMatchUnhookedPool(uint256,bool,bool) (runs: 1000, μ: 1788468, ~: 1634010)\n[PASS] test_exhaustionAcrossLiquidityGapsInEveryMode() (gas: 4378635)\n[PASS] test_noLiquidityMeansNoTradeAndNoBountyFee() (gas: 2968024)\n[PASS] test_roundTripsAcrossTickBoundariesDoNotLeakQuoteState() (gas: 7385074)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 333.73ms (619.25ms CPU time)\n\nRan 15 tests for test/QuantumCanaryAdversarial.t.sol:QuantumCanaryAdversarialTest\n[PASS] testFuzz_spoofedManagerSenderDoesNotAuthorizeCallbacks(address,bytes32) (runs: 1000, μ: 323206, ~: 323513)\n[PASS] testFuzz_transientFeesDoNotLeakBetweenBatchModes(uint256,bytes32) (runs: 1000, μ: 731760, ~: 729938)\n[PASS] test_allEnabledCallbacksEnforceEveryPoolGuard() (gas: 905379)\n[PASS] test_allFourModesCanBeNettedInOneFreshManagerUnlock() (gas: 719484)\n[PASS] test_claimBurnCannotBeUsedToStealBountyDuringUnlock() (gas: 992821)\n[PASS] test_constructorBubblesMissingDestinationInsteadOfInventingOne() (gas: 212291)\n[PASS] test_constructorRefusesAnAddressWithWrongPermissionBits() (gas: 223811)\n[PASS] test_constructorRejectsMissingDependenciesAndUnpayableDestinations() (gas: 1201156)\n[PASS] test_failedCanaryReadsKeepAllFourModesLiveAndFeeBearing() (gas: 7932491)\n[PASS] test_failedSecondSwapRollsBackFirstAndClearsTransientAccounting() (gas: 1205257)\n[PASS] test_revertingRecipientPreservesClaimsAcrossLockedAndUnlockedPayouts() (gas: 2564970)\n[PASS] test_tripEmitsRetirementAndOracleIsNeverReadAgain() (gas: 1964929)\n[PASS] test_unsettledSwapRollsBackBountyPaymentAndPoolState() (gas: 1286645)\n[PASS] test_unsettledTripObservationDoesNotPersistOrDestroyOldClaims() (gas: 1148889)\n[PASS] test_zeroAmountNeverAccruesOrRetires() (gas: 872337)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 335.24ms (566.87ms CPU time)\n\nRan 27 tests for test/QuantumCanaryHook.t.sol:QuantumCanaryHookTest\n[PASS] testFuzz_allFourModesConserveValue(uint256,bool,bool) (runs: 1000, μ: 411146, ~: 384892)\n[PASS] testFuzz_partialFills(uint256,bool,bool,uint24) (runs: 1000, μ: 368453, ~: 348437)\n[PASS] test_badPriceLimitRevertsWithoutAccruingFees() (gas: 90199)\n[PASS] test_badTripReadsNeverBlockSwapsAndDoNotRetire() (gas: 2014966)\n[PASS] test_callbacksAndSelfHelpersRejectUnauthorizedCallers() (gas: 186521)\n[PASS] test_claimsCannotBeTransferredByAnyoneElse() (gas: 24996720)\n[PASS] test_declaredPermissionsExactlyMatchMinedAddress() (gas: 14181)\n[PASS] test_exactInputBuyPaysExactlyOnePercent() (gas: 418993)\n[PASS] test_exactInputSellPaysExactlyOnePercent() (gas: 349887)\n[PASS] test_exactOutputBuyPaysExactlyOnePercent() (gas: 350116)\n[PASS] test_exactOutputSellPreservesNetOutput() (gas: 401214)\n[PASS] test_failedPayoutDoesNotRevertAnySwapMode() (gas: 10785521)\n[PASS] test_freshTokenOnlyPoolAccruesThenAnyonePays() (gas: 24923907)\n[PASS] test_freshTokenOnlyPoolSupportsAllFourModes() (gas: 25817783)\n[PASS] test_gasExhaustingRecipientDoesNotBlockSwap() (gas: 9825823)\n[PASS] test_immutableDestinationEvenIfMockChangesItsAnswer() (gas: 451805)\n[PASS] test_laterSwapAutomaticallyPaysEarlierClaims() (gas: 25162472)\n[PASS] test_noAdministrativeOrWithdrawalInterfaceExists() (gas: 233659)\n[PASS] test_partialFillsAllFourModesChargeOnlyTheFilledETH() (gas: 1563826)\n[PASS] test_partialPayoutPreservesRemainingClaims() (gas: 24981556)\n[PASS] test_payoutInsideRouterUnlockAfterSettlement() (gas: 24755588)\n[PASS] test_policyPoolInitializesAndBadPoolsAreRefused() (gas: 140225)\n[PASS] test_recipientCannotReenterPayout() (gas: 9616169)\n[PASS] test_retirementIsPermanentAllFourModesAreUntouched() (gas: 1493003)\n[PASS] test_runtimeHasNoDelegatecallSelfdestructOrCallcode() (gas: 1974703)\n[PASS] test_tripDoesNotStrandPreviouslyAccruedFees() (gas: 25196079)\n[PASS] test_weiRoundingBoundariesDoNotOvercharge() (gas: 9962379)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 335.27ms (623.30ms CPU time)\n\nRan 1 test for test/QuantumCanaryInvariant.t.sol:QuantumCanaryInvariantTest\n[PASS] invariant_feesCanOnlyBeClaimsOrETHAtTheImmutableBounty() (runs: 128, calls: 8192, reverts: 0)\n\n╭---------------+--------------------+-------+---------+----------╮\n| Contract      | Selector           | Calls | Reverts | Discards |\n+=================================================================+\n| CanaryHandler | canaryReadBehavior | 1609  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | recipientBehavior  | 1703  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | retryPayout        | 1611  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | swap               | 1587  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | trip               | 1682  | 0       | 0        |\n╰---------------+--------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 905.65ms (854.74ms CPU time)\n\nRan 1 test for test/QuantumCanarySettlementInvariant.t.sol:QuantumCanarySettlementInvariantTest\n[PASS] invariant_feesRemainBackedAndExclusiveToTheCachedBounty() (runs: 256, calls: 20480, reverts: 0)\n\n╭-------------------------+---------------------------+-------+---------+----------╮\n| Contract                | Selector                  | Calls | Reverts | Discards |\n+==================================================================================+\n| CanarySettlementHandler | attemptClaimTheft         | 3408  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| CanarySettlementHandler | batch                     | 3417  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| CanarySettlementHandler | canaryReadBehavior        | 3372  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| CanarySettlementHandler | changeReportedDestination | 3397  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| CanarySettlementHandler | payout                    | 3466  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| CanarySettlementHandler | recipientBehavior         | 3420  | 0       | 0        |\n╰-------------------------+---------------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.77s (4.74s CPU time)\n\nRan 8 test suites in 4.77s (6.78s CPU time): 55 tests passed, 0 failed, 1 skipped (56 total tests)\n","passed":true},{"durationMs":52,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"QuantumCanaryHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"QuantumCanaryHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"QuantumCanaryHook.payout()\",\"QuantumCanaryHook.quotePoolSwap((address,address,uint24,int24,address),(bool,int256,uint160))\",\"QuantumCanaryHook.redeemUnlocked()\",\"QuantumCanaryHook.unlockCallback(bytes)\",\"QuantumCanaryToken.approve(address,uint256)\",\"QuantumCanaryToken.transfer(address,uint256)\",\"QuantumCanaryToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":5,\"LICENSE\":21,\"README.md\":107,\"SECURITY_REVIEW.md\":40,\"foundry.toml\":11,\"remappings.txt\":5,\"script/MineQuantumCanary.s.sol\":49,\"src/HookFlags.sol\":30,\"src/IQuantumCanary.sol\":7,\"src/QuantumCanaryHook.sol\":277,\"src/QuantumCanaryToken.sol\":12,\"test/MineQuantumCanary.t.sol\":13,\"test/QuantumCanaryAdversarial.t.sol\":266,\"test/QuantumCanaryDifferential.t.sol\":213,\"test/QuantumCanaryHook.t.sol\":492,\"test/QuantumCanaryInvariant.t.sol\":113,\"test/QuantumCanaryMainnetFork.t.sol\":66,\"test/QuantumCanarySettlementInvariant.t.sol\":165,\"test/QuantumCanaryToken.t.sol\":76,\"test/README.md\":41,\"test/helpers/CanaryScenario.sol\":189,\"test/mocks/MockCanary.sol\":66,\"test/mocks/MockERC20.sol\":10},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"af08fd4cc8b4c4ffa68c86b785d184d15ea8ed3a161543356de3fb932b4dfdd2","verifiedTreeHash":"e010ba977e039c2c121c7ce5e2c87393887a3d19","verifierVersion":"0.1.0+fdeb4d4a"},{"checks":[{"durationMs":2538,"exitCode":0,"name":"build","output":"Compiling 85 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.34s\nCompiler run successful!\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/QuantumCanaryHook.sol:78:55\n   │\n78 │             if (!ok || answer.length != 32 || uint256(bytes32(answer)) > type(uint160).max) revert InvalidAddress();\n   │                                                       ━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes32' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n   ╭▸ src/QuantumCanaryHook.sol:79:51\n   │\n79 │             destination = address(uint160(uint256(bytes32(answer))));\n   │                                                   ━━━━━━━━━━━━━━━\n   │\n   ├ note: consider disabling this lint if you're certain the cast is safe\n   │       \n   │       // casting to 'bytes32' is safe because [explain why]\n   │       // forge-lint: disable-next-line(unsafe-typecast)\n   │       \n   │       \n   ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/QuantumCanaryHook.sol:275:13\n    │\n275 │             emit Retired();\n    │             ━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:132:30\n    │\n132 │                     budget = uint256(-params.amountSpecified); // type(int256).min wraps to 2**255\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:135:49\n    │\n135 │                 quotedParams.amountSpecified = -int256(_min(budget - reserved, MAX_CORE_AMOUNT));\n    │                                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:136:32\n    │\n136 │                 uint256 used = uint256(-int256(_quoteETH(key, quotedParams)));\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:140:37\n    │\n140 │                 uint256 requested = uint256(params.amountSpecified);\n    │                                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:142:48\n    │\n142 │                 quotedParams.amountSpecified = int256(_min(requested + reserved, MAX_CORE_AMOUNT));\n    │                                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:143:33\n    │\n143 │                 uint256 gross = uint256(int256(_quoteETH(key, quotedParams)));\n    │                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:148:63\n    │\n148 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int256(fee).toInt128(), 0), 0);\n    │                                                               ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/QuantumCanaryHook.sol:170:13\n    │\n170 │             emit FeeAccrued(PoolId.unwrap(key.toId()), fee);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:163:39\n    │\n163 │             fee = params.zeroForOne ? uint256(-int256(ethDelta)) / 99 : uint256(int256(ethDelta)) / 100;\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:163:73\n    │\n163 │             fee = params.zeroForOne ? uint256(-int256(ethDelta)) / 99 : uint256(int256(ethDelta)) / 100;\n    │                                                                         ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:164:27\n    │\n164 │             returnDelta = int256(fee).toInt128();\n    │                           ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/QuantumCanaryHook.sol:186:34\n    │\n186 │         if (beforeClaims == 0 || address(poolManager).balance == 0) return 0;\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/QuantumCanaryHook.sol:221:9\n    │\n221 │         emit BountyPaid(canaryAddress, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/QuantumCanaryHook.sol:225:13\n    │\n225 │         if (address(poolManager).balance == 0 || accruedFees() == 0) return;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:248:40\n    │\n248 │             if (reason.length == 36 && bytes4(reason) == QuotedETH.selector) {\n    │                                        ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'bytes4' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\n","passed":true},{"durationMs":907,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/QuantumCanaryMainnetFork.t.sol:QuantumCanaryMainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 333.83µs (0.00ns CPU time)\n\nRan 1 test for test/MineQuantumCanary.t.sol:MineQuantumCanaryTest\n[PASS] test_minerPredictsTheActuallyDeployedHook() (gas: 6485949)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 12.80ms (6.09ms CPU time)\n\nRan 5 tests for test/QuantumCanaryToken.t.sol:QuantumCanaryTokenTest\n[PASS] testFuzz_plainTransfersConserveFixedSupply(uint256) (runs: 1000, μ: 173352, ~: 173830)\n[PASS] test_delegatedTransferHonorsAllowanceAndHasNoFee() (gas: 201231)\n[PASS] test_exactLaunchSupplyBelongsEntirelyToDeployer() (gas: 61252)\n[PASS] test_insufficientBalanceAndInvalidRecipientRevert() (gas: 61672)\n[PASS] test_noMintOwnerPauseOrUpgradeInterface() (gas: 120850)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 104.82ms (104.93ms CPU time)\n\nRan 34 tests for test/QuantumCanaryHook.t.sol:QuantumCanaryHookTest\n[PASS] testFuzz_allFourModesConserveValue(uint256,bool,bool) (runs: 1000, μ: 412755, ~: 436784)\n[PASS] testFuzz_partialFills(uint256,bool,bool,uint24) (runs: 1000, μ: 368623, ~: 349612)\n[PASS] test_attestedCreationCodeDeploysWithNoCodeAtTheLaunchObserver() (gas: 19501977)\n[PASS] test_badPriceLimitRevertsWithoutAccruingFees() (gas: 90253)\n[PASS] test_badTripReadsNeverBlockSwapsAndDoNotRetire() (gas: 2066318)\n[PASS] test_callbacksAndSelfHelpersRejectUnauthorizedCallers() (gas: 186587)\n[PASS] test_claimsCannotBeTransferredByAnyoneElse() (gas: 53026514)\n[PASS] test_constructorRejectsUnknownEmptyOrBrokenObservers() (gas: 7600)\n[PASS] test_declaredPermissionsExactlyMatchMinedAddress() (gas: 14225)\n[PASS] test_exactInputBuyPaysExactlyOnePercent() (gas: 420574)\n[PASS] test_exactInputSellPaysExactlyOnePercent() (gas: 351414)\n[PASS] test_exactOutputBuyPaysExactlyOnePercent() (gas: 351665)\n[PASS] test_exactOutputSellPreservesNetOutput() (gas: 402770)\n[PASS] test_failedPayoutDoesNotRevertAnySwapMode() (gas: 131550701)\n[PASS] test_freshTokenOnlyPoolAccruesThenAnyonePays() (gas: 52900188)\n[PASS] test_freshTokenOnlyPoolSupportsAllFourModes() (gas: 53807229)\n[PASS] test_gasExhaustingRecipientDoesNotBlockSwap() (gas: 130524734)\n[PASS] test_immutableDestinationEvenIfMockChangesItsAnswer() (gas: 453386)\n[PASS] test_laterSwapAutomaticallyPaysEarlierClaims() (gas: 53135194)\n[PASS] test_launchDestinationMatchesTheObserverSourceDerivation() (gas: 1434443)\n[PASS] test_minimumSignedExactInputBuyFillsToThePriceLimit() (gas: 294041)\n[PASS] test_noAdministrativeOrWithdrawalInterfaceExists() (gas: 233725)\n[PASS] test_partialFillsAllFourModesChargeOnlyTheFilledETH() (gas: 1570065)\n[PASS] test_partialPayoutPreservesRemainingClaims() (gas: 52966540)\n[PASS] test_payoutInsideRouterUnlockAfterSettlement() (gas: 52721311)\n[PASS] test_payoutInsideRouterUnlockAlsoLatchesTheTripFirst() (gas: 6398480)\n[PASS] test_payoutLatchesTheTripBeforeItsRefillClearsTheLiveAlarm() (gas: 6437025)\n[PASS] test_policyPoolInitializesAndBadPoolsAreRefused() (gas: 140225)\n[PASS] test_recipientCannotReenterPayout() (gas: 130276758)\n[PASS] test_retirementIsPermanentAllFourModesAreUntouched() (gas: 1493223)\n[PASS] test_runtimeHasNoDelegatecallSelfdestructOrCallcode() (gas: 1974434)\n[PASS] test_saturatingExactOutputSellFillsToThePriceLimit() (gas: 286300)\n[PASS] test_tripDoesNotStrandPreviouslyAccruedFees() (gas: 53167682)\n[PASS] test_weiRoundingBoundariesDoNotOvercharge() (gas: 9995440)\nSuite result: ok. 34 passed; 0 failed; 0 skipped; finished in 120.86ms (906.77ms CPU time)\n\nRan 1 test for test/QuantumCanaryInvariant.t.sol:QuantumCanaryInvariantTest\n[PASS] invariant_feesCanOnlyBeClaimsOrETHAtTheImmutableBounty() (runs: 128, calls: 8192, reverts: 0)\n\n╭---------------+--------------------+-------+---------+----------╮\n| Contract      | Selector           | Calls | Reverts | Discards |\n+=================================================================+\n| CanaryHandler | canaryReadBehavior | 1576  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | recipientBehavior  | 1604  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | retryPayout        | 1674  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | swap               | 1707  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | trip               | 1631  | 0       | 0        |\n╰---------------+--------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 813.41ms (781.44ms CPU time)\n\nRan 5 test suites in 814.69ms (1.05s CPU time): 41 tests passed, 0 failed, 1 skipped (42 total tests)\n","passed":true},{"durationMs":52,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"QuantumCanaryHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"QuantumCanaryHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"QuantumCanaryHook.payout()\",\"QuantumCanaryHook.quotePoolSwap((address,address,uint24,int24,address),(bool,int256,uint160))\",\"QuantumCanaryHook.redeemUnlocked()\",\"QuantumCanaryHook.unlockCallback(bytes)\",\"QuantumCanaryToken.approve(address,uint256)\",\"QuantumCanaryToken.transfer(address,uint256)\",\"QuantumCanaryToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":5,\"LICENSE\":21,\"README.md\":113,\"SECURITY_REVIEW.md\":46,\"foundry.toml\":11,\"remappings.txt\":5,\"script/MineQuantumCanary.s.sol\":49,\"src/HookFlags.sol\":30,\"src/IQuantumCanary.sol\":7,\"src/QuantumCanaryHook.sol\":305,\"src/QuantumCanaryToken.sol\":12,\"test/MineQuantumCanary.t.sol\":13,\"test/QuantumCanaryHook.t.sol\":619,\"test/QuantumCanaryInvariant.t.sol\":113,\"test/QuantumCanaryMainnetFork.t.sol\":66,\"test/QuantumCanaryToken.t.sol\":76,\"test/mocks/MockCanary.sol\":66,\"test/mocks/MockERC20.sol\":10,\"test/mocks/QuantumCanaryObserver.sol\":119},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1028,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/QuantumCanaryHook.sol:183: QuantumCanaryHook.payout() (src/QuantumCanaryHook.sol#183-195) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/QuantumCanaryHook.sol:211: QuantumCanaryHook._redeem() (src/QuantumCanaryHook.sol#211-222) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/QuantumCanaryHook.sol:224: QuantumCanaryHook._tryPayout() (src/QuantumCanaryHook.sol#224-235) uses a dangerous strict equality:\n[medium/medium] unused-return at src/QuantumCanaryHook.sol:183: QuantumCanaryHook.payout() (src/QuantumCanaryHook.sol#183-195) ignores return value by poolManager.unlock() (src/QuantumCanaryHook.sol#191)\n[low/medium] reentrancy-events at src/QuantumCanaryHook.sol:151: Reentrancy in QuantumCanaryHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/QuantumCanaryHook.sol#151-174):\n[low/medium] reentrancy-events at src/QuantumCanaryHook.sol:211: Reentrancy in QuantumCanaryHook._redeem() (src/QuantumCanaryHook.sol#211-222):","passed":true},{"durationMs":305,"exitCode":0,"name":"aderyn","output":"[high] strict-equality-contract-balance at src/QuantumCanaryHook.sol:186: Dangerous strict equality checks on contract balances (2 places)\n[low] internal-function-used-once at src/HookFlags.sol:22: Internal Function Used Only Once\n[low] large-numeric-literal at src/QuantumCanaryHook.sol:30: Large Numeric Literal (5 places)\n[low] literal-instead-of-constant at src/QuantumCanaryHook.sol:134: Literal Instead of Constant (6 places)","passed":true},{"durationMs":1751,"exitCode":0,"name":"proof bf8b7d5d470d","output":"Compiling 78 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.22s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-3111f065/Proof_bf8b7d5d470d.t.sol:ConstructorOnEmptyChainTest\n[PASS] test_attestedCreationCodeDeploysWhereTheProtectedFloorRunsIt() (gas: 5979699)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 951.07µs (751.29µs CPU time)\n\nRan 1 test suite in 3.17ms (951.07µs CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true},{"durationMs":1582,"exitCode":0,"name":"proof 9ef084504729","output":"2026-10-10T13:03:58.508141Z ERROR foundry_compilers_artifacts_solc::sources: error=\"/tmp/imd-verify-o7LaKY/repo/test/imd-proof-3111f065/Proof_bf8b7d5d470d.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.06s\nCompiler run successful!\n\nRan 2 tests for test/imd-proof-f8277315/Proof_9ef084504729.t.sol:RefillRaceTest\n[PASS] test_controlSwapBeforePayoutRetires() (gas: 547365)\n[PASS] test_payoutRefillClearsLiveAlarmBeforeHookObservesIt() (gas: 738821)\nLogs:\n  fee charged after recorded trip: 0\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 6.37ms (1.44ms CPU time)\n\nRan 1 test suite in 7.08ms (6.37ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"db7e512d7d2d06b88bbe07cdd3dd529ecedbe0b8eff2adc6bcadc406c2401df6","verifiedTreeHash":"1d78abc110c028ddb217301912b9572c9b392b41","verifierVersion":"0.1.0+fdeb4d4a"},{"checks":[{"durationMs":4241,"exitCode":0,"name":"build","output":"Compiling 88 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.92s\nCompiler run successful!\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/QuantumCanaryHook.sol:247:13\n    │\n247 │             emit Retired();\n    │             ━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:109:34\n    │\n109 │                 uint256 budget = uint256(-params.amountSpecified);\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:111:49\n    │\n111 │                 quotedParams.amountSpecified += int256(reserved);\n    │                                                 ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:112:32\n    │\n112 │                 uint256 used = uint256(-int256(_quoteETH(key, quotedParams)));\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:116:36\n    │\n116 │                 uint256 reserved = uint256(params.amountSpecified) / 99;\n    │                                    ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:117:49\n    │\n117 │                 quotedParams.amountSpecified += int256(reserved);\n    │                                                 ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:118:33\n    │\n118 │                 uint256 gross = uint256(int256(_quoteETH(key, quotedParams)));\n    │                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:123:63\n    │\n123 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int256(fee).toInt128(), 0), 0);\n    │                                                               ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/QuantumCanaryHook.sol:145:13\n    │\n145 │             emit FeeAccrued(PoolId.unwrap(key.toId()), fee);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:138:39\n    │\n138 │             fee = params.zeroForOne ? uint256(-int256(ethDelta)) / 99 : uint256(int256(ethDelta)) / 100;\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:138:73\n    │\n138 │             fee = params.zeroForOne ? uint256(-int256(ethDelta)) / 99 : uint256(int256(ethDelta)) / 100;\n    │                                                                         ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:139:27\n    │\n139 │             returnDelta = int256(fee).toInt128();\n    │                           ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/QuantumCanaryHook.sol:161:34\n    │\n161 │         if (beforeClaims == 0 || address(poolManager).balance == 0) return 0;\n    │                                  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/QuantumCanaryHook.sol:193:9\n    │\n193 │         emit BountyPaid(canaryAddress, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:188:31\n    │\n188 │         amount = _min(amount, uint256(uint128(type(int128).max)));\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:188:39\n    │\n188 │         amount = _min(amount, uint256(uint128(type(int128).max)));\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[incorrect-strict-equality]: dangerous strict equality check on an externally-influenced value\n    ╭▸ src/QuantumCanaryHook.sol:197:13\n    │\n197 │         if (address(poolManager).balance == 0 || accruedFees() == 0) return;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/incorrect-strict-equality\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/QuantumCanaryHook.sol:220:40\n    │\n220 │             if (reason.length == 36 && bytes4(reason) == QuotedETH.selector) {\n    │                                        ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'bytes4' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\n","passed":true},{"durationMs":4461,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/QuantumCanaryMainnetFork.t.sol:QuantumCanaryMainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 348.77µs (0.00ns CPU time)\n\nRan 5 tests for test/QuantumCanaryToken.t.sol:QuantumCanaryTokenTest\n[PASS] testFuzz_plainTransfersConserveFixedSupply(uint256) (runs: 1000, μ: 173628, ~: 173830)\n[PASS] test_delegatedTransferHonorsAllowanceAndHasNoFee() (gas: 201231)\n[PASS] test_exactLaunchSupplyBelongsEntirelyToDeployer() (gas: 61252)\n[PASS] test_insufficientBalanceAndInvalidRecipientRevert() (gas: 61672)\n[PASS] test_noMintOwnerPauseOrUpgradeInterface() (gas: 120850)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 12.03ms (12.17ms CPU time)\n\nRan 1 test for test/MineQuantumCanary.t.sol:MineQuantumCanaryTest\n[PASS] test_minerPredictsTheActuallyDeployedHook() (gas: 33980125)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 63.79ms (38.84ms CPU time)\n\nRan 27 tests for test/QuantumCanaryHook.t.sol:QuantumCanaryHookTest\n[PASS] testFuzz_allFourModesConserveValue(uint256,bool,bool) (runs: 1000, μ: 412601, ~: 435218)\n[PASS] testFuzz_partialFills(uint256,bool,bool,uint24) (runs: 1000, μ: 367952, ~: 348091)\n[PASS] test_badPriceLimitRevertsWithoutAccruingFees() (gas: 90199)\n[PASS] test_badTripReadsNeverBlockSwapsAndDoNotRetire() (gas: 2014966)\n[PASS] test_callbacksAndSelfHelpersRejectUnauthorizedCallers() (gas: 186521)\n[PASS] test_claimsCannotBeTransferredByAnyoneElse() (gas: 24996720)\n[PASS] test_declaredPermissionsExactlyMatchMinedAddress() (gas: 14181)\n[PASS] test_exactInputBuyPaysExactlyOnePercent() (gas: 418993)\n[PASS] test_exactInputSellPaysExactlyOnePercent() (gas: 349887)\n[PASS] test_exactOutputBuyPaysExactlyOnePercent() (gas: 350116)\n[PASS] test_exactOutputSellPreservesNetOutput() (gas: 401214)\n[PASS] test_failedPayoutDoesNotRevertAnySwapMode() (gas: 10785521)\n[PASS] test_freshTokenOnlyPoolAccruesThenAnyonePays() (gas: 24923907)\n[PASS] test_freshTokenOnlyPoolSupportsAllFourModes() (gas: 25817783)\n[PASS] test_gasExhaustingRecipientDoesNotBlockSwap() (gas: 9825823)\n[PASS] test_immutableDestinationEvenIfMockChangesItsAnswer() (gas: 451805)\n[PASS] test_laterSwapAutomaticallyPaysEarlierClaims() (gas: 25162472)\n[PASS] test_noAdministrativeOrWithdrawalInterfaceExists() (gas: 233659)\n[PASS] test_partialFillsAllFourModesChargeOnlyTheFilledETH() (gas: 1563826)\n[PASS] test_partialPayoutPreservesRemainingClaims() (gas: 24981556)\n[PASS] test_payoutInsideRouterUnlockAfterSettlement() (gas: 24755588)\n[PASS] test_policyPoolInitializesAndBadPoolsAreRefused() (gas: 140225)\n[PASS] test_recipientCannotReenterPayout() (gas: 9616169)\n[PASS] test_retirementIsPermanentAllFourModesAreUntouched() (gas: 1493003)\n[PASS] test_runtimeHasNoDelegatecallSelfdestructOrCallcode() (gas: 1974703)\n[PASS] test_tripDoesNotStrandPreviouslyAccruedFees() (gas: 25196079)\n[PASS] test_weiRoundingBoundariesDoNotOvercharge() (gas: 9962379)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 282.09ms (506.44ms CPU time)\n\nRan 5 tests for test/QuantumCanaryDifferential.t.sol:QuantumCanaryDifferentialTest\n[PASS] testFuzz_quotesAndFeesMatchUnhookedPool(uint256,bool,bool,uint24,uint16) (runs: 1000, μ: 901011, ~: 883091)\n[PASS] testFuzz_retiredSwapsExactlyMatchUnhookedPool(uint256,bool,bool) (runs: 1000, μ: 1812026, ~: 1634756)\n[PASS] test_exhaustionAcrossLiquidityGapsInEveryMode() (gas: 4378635)\n[PASS] test_noLiquidityMeansNoTradeAndNoBountyFee() (gas: 2968024)\n[PASS] test_roundTripsAcrossTickBoundariesDoNotLeakQuoteState() (gas: 7385074)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 305.10ms (598.46ms CPU time)\n\nRan 15 tests for test/QuantumCanaryAdversarial.t.sol:QuantumCanaryAdversarialTest\n[PASS] testFuzz_spoofedManagerSenderDoesNotAuthorizeCallbacks(address,bytes32) (runs: 1000, μ: 323182, ~: 323513)\n[PASS] testFuzz_transientFeesDoNotLeakBetweenBatchModes(uint256,bytes32) (runs: 1000, μ: 731805, ~: 729938)\n[PASS] test_allEnabledCallbacksEnforceEveryPoolGuard() (gas: 905379)\n[PASS] test_allFourModesCanBeNettedInOneFreshManagerUnlock() (gas: 719484)\n[PASS] test_claimBurnCannotBeUsedToStealBountyDuringUnlock() (gas: 992821)\n[PASS] test_constructorBubblesMissingDestinationInsteadOfInventingOne() (gas: 212291)\n[PASS] test_constructorRefusesAnAddressWithWrongPermissionBits() (gas: 223811)\n[PASS] test_constructorRejectsMissingDependenciesAndUnpayableDestinations() (gas: 1201156)\n[PASS] test_failedCanaryReadsKeepAllFourModesLiveAndFeeBearing() (gas: 7932491)\n[PASS] test_failedSecondSwapRollsBackFirstAndClearsTransientAccounting() (gas: 1205257)\n[PASS] test_revertingRecipientPreservesClaimsAcrossLockedAndUnlockedPayouts() (gas: 2564970)\n[PASS] test_tripEmitsRetirementAndOracleIsNeverReadAgain() (gas: 1964929)\n[PASS] test_unsettledSwapRollsBackBountyPaymentAndPoolState() (gas: 1286645)\n[PASS] test_unsettledTripObservationDoesNotPersistOrDestroyOldClaims() (gas: 1148889)\n[PASS] test_zeroAmountNeverAccruesOrRetires() (gas: 872337)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 305.40ms (313.61ms CPU time)\n\nRan 1 test for test/QuantumCanaryInvariant.t.sol:QuantumCanaryInvariantTest\n[PASS] invariant_feesCanOnlyBeClaimsOrETHAtTheImmutableBounty() (runs: 128, calls: 8192, reverts: 0)\n\n╭---------------+--------------------+-------+---------+----------╮\n| Contract      | Selector           | Calls | Reverts | Discards |\n+=================================================================+\n| CanaryHandler | canaryReadBehavior | 1578  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | recipientBehavior  | 1707  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | retryPayout        | 1649  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | swap               | 1653  | 0       | 0        |\n|---------------+--------------------+-------+---------+----------|\n| CanaryHandler | trip               | 1605  | 0       | 0        |\n╰---------------+--------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 860.19ms (829.96ms CPU time)\n\nRan 1 test for test/QuantumCanarySettlementInvariant.t.sol:QuantumCanarySettlementInvariantTest\n[PASS] invariant_feesRemainBackedAndExclusiveToTheCachedBounty() (runs: 256, calls: 20480, reverts: 0)\n\n╭-------------------------+---------------------------+-------+---------+----------╮\n| Contract                | Selector                  | Calls | Reverts | Discards |\n+==================================================================================+\n| CanarySettlementHandler | attemptClaimTheft         | 3524  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| CanarySettlementHandler | batch                     | 3459  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| CanarySettlementHandler | canaryReadBehavior        | 3405  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| CanarySettlementHandler | changeReportedDestination | 3434  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| CanarySettlementHandler | payout                    | 3362  | 0       | 0        |\n|-------------------------+---------------------------+-------+---------+----------|\n| CanarySettlementHandler | recipientBehavior         | 3296  | 0       | 0        |\n╰-------------------------+---------------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 4.36s (4.33s CPU time)\n\nRan 8 test suites in 4.36s (6.19s CPU time): 55 tests passed, 0 failed, 1 skipped (56 total tests)\n","passed":true},{"durationMs":45,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"QuantumCanaryHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"QuantumCanaryHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"QuantumCanaryHook.payout()\",\"QuantumCanaryHook.quotePoolSwap((address,address,uint24,int24,address),(bool,int256,uint160))\",\"QuantumCanaryHook.redeemUnlocked()\",\"QuantumCanaryHook.unlockCallback(bytes)\",\"QuantumCanaryToken.approve(address,uint256)\",\"QuantumCanaryToken.transfer(address,uint256)\",\"QuantumCanaryToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":5,\"LICENSE\":21,\"README.md\":107,\"SECURITY_REVIEW.md\":40,\"foundry.toml\":11,\"launch.json\":30,\"remappings.txt\":5,\"script/MineQuantumCanary.s.sol\":49,\"src/HookFlags.sol\":30,\"src/IQuantumCanary.sol\":7,\"src/QuantumCanaryHook.sol\":277,\"src/QuantumCanaryToken.sol\":12,\"test/MineQuantumCanary.t.sol\":13,\"test/QuantumCanaryAdversarial.t.sol\":266,\"test/QuantumCanaryDifferential.t.sol\":213,\"test/QuantumCanaryHook.t.sol\":492,\"test/QuantumCanaryInvariant.t.sol\":113,\"test/QuantumCanaryMainnetFork.t.sol\":66,\"test/QuantumCanarySettlementInvariant.t.sol\":165,\"test/QuantumCanaryToken.t.sol\":76,\"test/README.md\":41,\"test/helpers/CanaryScenario.sol\":189,\"test/mocks/MockCanary.sol\":66,\"test/mocks/MockERC20.sol\":10},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"f15c36227295ba96852e22b0bc492099033252c81d7aeb61abe32f00d7756d65","verifiedTreeHash":"ceed7559e0f428966270251a8bda77d11dece6a3","verifierVersion":"0.1.0+fdeb4d4a"}]}