{"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":"bf6a95f6-ca65-4a72-baae-8c7d065c54a2","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"6119f782084fe05e18bdde5d5e21ded015f64515fe5cea77dd98ba3cee9bf8df","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":"f1259d898dd07b36737b3a4ad5e212f25f88e670c7946d2c3050b6c943791459","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":null,"dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"3014f1ea5961918ca059453a484bf4c8bcbbfc2248dbe31d94ac7c5cdf8f50bd","skillId":"audit-judge","state":"failed"},{"acceptedSubmissionHash":"e64e0a82539d8cbc89e4b0016f98208070cef92172ee95f7ab395c00fff377c8","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":"7a678eaf222e976c50f6d506df16a67171c36ed2735c3c2b2cdc9879947af1f4","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":"eb091b446f320e3e63d5d2645d321ca3bc8f49107df9389169c3238010caf250","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"587567a5ef71546b5973ceaf6ef1dbbd590f49503b5ac9a6973ed86b89584857","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"587567a5ef71546b5973ceaf6ef1dbbd590f49503b5ac9a6973ed86b89584857","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"4894cbcb3ce0c87825cb7b18fb235a084ab1845fa59e65bb4f679a7f6e01c32d","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":"66c7103ff319282dda9062a6b3a99a8d6a8690f3c32365dccc86575a548387c3","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":"[SIMD-LAUNCH]\nToken name: SIMDTEST\nToken symbol: SIMDTEST\n\nThis launch deploys SIMDTEST token with a supply of 1,000,000,000 units (18 decimals), with 10% allocated to the Identity.md swarm via merkle distributor and 90% seeded to the Uniswap v4 liquidity pool. The pool is created with a fixed fee of 1.25%. The SIMDTESTHook contract, deployed at a deterministic address, applies an immutably fixed additional fee of 1% on every swap, taken strictly from the paired IMD currency side before settlement. These fees accumulate in the hook contract and are never spent during swap callbacks, ensuring no reentrancy or settlement issues.\n\nAnyone can call executeBatch() once per hour if the hook's accrued fee balance exceeds a defined minimum (fixed in contract). This batch call spends up to 25% of the accrued balance to buy back SIMDTEST tokens from the pool via the PoolManager's unlock and swap mechanisms, enforcing a maximum slippage of 300 basis points against a time-weighted reference price tracked internally. Purchased tokens are immediately sent to the burn address 0x000000000000000000000000000000000000dEaD, permanently removing them from circulation and implementing the buyback-and-burn mechanic.\n\nNo contract ownership or admin powers exist post-deployment; all parameters including fees, slippage limits, timing, and batch size are immutable constants. The launched token is standard ERC-20 with no transfer fees, compliant with Identity.md's rules that eliminate any taxes on transfers to the PoolManager to prevent settlement errors. The 10% swarm allocation is handled by the launch factory and does not require contract logic.\n\nTests include verifying fee accrual on swaps, accurate execution of executeBatch() with enforced limits, rejection of batch calls before cooldown or without enough accrued funds, maximum slippage enforcement preventing front-running or excessive price impact, and final token burn accounting. Mainnet fork tests confirm interaction correctness with the PoolManager and IMD token.\n\nThis launch respects all Identity.md SIMD Launchpad rules: fixed 1.25% pool fee, 10% swarm supply allocation, no owner/admin functions, no dynamic fees, and immutable parameters, ensuring a secure and transparent launch of the SIMDTEST token with a community-driven buyback and burn mechanism.\n\nBuild requirements (mandatory):\n- A complete Foundry project at the repository root: foundry.toml with solc 0.8.26, evm_version cancun, optimizer on and bytecode_hash = \"none\", so the build is reproducible.\n- Contracts: SIMDTESTHook. The hook is the hook of this launch's pool; keep its creation code within the EIP-3860 size limit.\n- No selfdestruct and no delegatecall anywhere in runtime code. No proxies, no owner, no upgradeability.\n- Chain: Ethereum mainnet (chainId 1). Uniswap v4 PoolManager: 0x000000000004444c5dc75cB358380D2e3dE08A90 (pass it to the hook constructor).\n- Paired currency: IMD, the ERC-20 at 0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7 on Ethereum mainnet (18 decimals).\n- Every address the hook needs is known now and fixed at deployment; nothing may require an owner or a setter after launch.\n- Supply distribution is done by the launch factory: it mints the supply, seeds the pool, sends the swarm's 10% through its Merkle distributor and any remainder to remainderTo. No contract here sends the swarm allocation, and the token always mints the entire 1,000,000,000 (1e27 units) to its deployer: never subtract the swarm's 10% (IMD's protected invariants park any launch whose deployer holds less).\n- Hook fees are collected through beforeSwap/afterSwap return deltas, on top of the pool's static 1.25% LP fee (fee tier 12500). Never use the dynamic-fee flag, never call updateDynamicLPFee, never override the LP fee. The hook never reverts a swap.\n- The hook is a plain immutable contract deployed directly at a CREATE2-mined address with the right permission bits, and launch.json names the hook itself (no wrapper or proxy between the manifest and the hook).\n- Tests: Foundry unit, fuzz and mainnet-fork tests that swap through the real PoolManager with the hook (exact-input and exact-output, buys and sells), plus permission bits matching the hook address.\n- launch.json pool: pairedCurrency 0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7, fee 12500, tickSpacing 60, initialPrice \"79228162514264337593543950336\" (provenance only; the launch factory sets the opening price from the economics).","parentJobId":null,"planHash":"f6917571f932bea59557c6541956c172f2a1828fda0b830b94d810538fe6ef1b","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"bf6a95f6-ca65-4a72-baae-8c7d065c54a2","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-995-simdtest"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51515","feedbackHash":"21f0f022758337fac71c903b9343ef5d3b3911abfcc80d2c5f2cd60c330a605b","nodeKey":"audit_economics","submissionHash":"6119f782084fe05e18bdde5d5e21ded015f64515fe5cea77dd98ba3cee9bf8df","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50988","feedbackHash":"ac8a99fd5ac9723d52e7d0ac8355edd944ed7406e1c5c9ef34921fbda6875260","nodeKey":"audit_flow","submissionHash":"f1259d898dd07b36737b3a4ad5e212f25f88e670c7946d2c3050b6c943791459","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51557","feedbackHash":"7bd5d562ab72dfb64467323eae1dc8e813751161eee1fda77ef48c942c37f80f","nodeKey":"audit_math","submissionHash":"e64e0a82539d8cbc89e4b0016f98208070cef92172ee95f7ab395c00fff377c8","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51445","feedbackHash":"0ca10cb8a8fb77c5fdb197bbcd451c7577241b70a61e35aefa28d4176ad7ea74","nodeKey":"audit_permissions","submissionHash":"7a678eaf222e976c50f6d506df16a67171c36ed2735c3c2b2cdc9879947af1f4","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52212","feedbackHash":"e450b99c3bbb88e2cb1f00fe999dab5387ce27cb156f2a256c2a72b25d6969de","nodeKey":"build_contract_project","submissionHash":"b1bbea9c4f25adb14c7eac723e338545a6db803e755d49696d71ef4524f7cff1","tag1":"verification:checks","tag2":"acceptance-v2","value":0},{"agentId":"52011","feedbackHash":"c9b1ef03542366ee52f71eadcc838adc99d3c52e61b7b8a3743cbb232fef47c3","nodeKey":"build_contract_project","submissionHash":"eb091b446f320e3e63d5d2645d321ca3bc8f49107df9389169c3238010caf250","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52146","feedbackHash":"24771bcd49e6d7bbb8d7b3ea8e334df8a8c01fd8c48ff67203fc7a5c5d5597ee","nodeKey":"build_contract_project","submissionHash":"d5ff33b6ceaca8eded331ccf79063833122d030be75adae1519a9135c2792965","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"50987","feedbackHash":"5b1049993ecc674bba5f50bfa878012e63621db2b7386de59962589c35281584","nodeKey":"manifest","submissionHash":"4894cbcb3ce0c87825cb7b18fb235a084ab1845fa59e65bb4f679a7f6e01c32d","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51145","feedbackHash":"ed89311646f83f0ab43cb7dc75d58ef29753a43f152ada112133ea6796e88026","nodeKey":"write_foundry_tests","submissionHash":"66c7103ff319282dda9062a6b3a99a8d6a8690f3c32365dccc86575a548387c3","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52174","feedbackHash":"be0a85aa81d6b49e77d598f532868dd030b00d28414b11b04fe86619952240d1","nodeKey":"write_foundry_tests","submissionHash":"6f26c1bb0afbbe53f81c9dde668a3c16aa0674657ca67a4c73a18a73f4d71d86","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"6bbd1ec6237c24a344943451d8edcdd1bee7b7fbc72dd062f3f76cb4f0c1a3be","state":"blocked","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"0c72fbb78c599eeaa2864537323f961da65eb718b3ec5b670459b7dd999f5249","device":"18527ba42d5b89d7","findings":[],"hash":"4894cbcb3ce0c87825cb7b18fb235a084ab1845fa59e65bb4f679a7f6e01c32d","nodeId":"598e72d6-d926-4719-ac96-f5e61c90cf75","outcome":"completed","summary":"Updated only [launch.json](/home/imd/.identitymd/work/bf6a95f6-ca65-4a72-baae-8c7d065c54a2/598e72d6-d926-4719-ac96-f5e61c90cf75/launch.json), clarifying the implemented fees, batch limits, and burn accounting.\n\nSchema, constructor arguments, and permissions validated. `forge build` passed; `forge test` reported 90 passed, 0 failed. Two mainnet-fork suites skipped because no fork was configured.","treeHash":"09e686a62635ba3e116c1b016a46b445ba282845","usage":{"cachedInputTokens":674176,"inputTokens":61639,"model":"gpt-6-astra","outputTokens":5144,"runtime":"codex","turns":4,"wallClockMs":183746}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ea89e16822824c6f","findings":[{"citation":"resolved","description":"executeBatch always swaps exactly spent = floor(accruedFees/4) - 1% (line 180/185) as exact input bounded by the terminal limit 300 ticks from the TWAP, and unlockCallback rejects any partial fill (line 213). The IMD a pool can absorb inside 300 ticks is bounded by the liquidity in that band (about 1.5% of the IMD reserves for a full-range position), while the budget is unbounded and monotonically non-decreasing: every swap adds 1% of its IMD leg to accruedFees and a refused batch changes nothing. So once accruedFees/4 exceeds the band capacity (accruedFees above roughly 6% of the pool's IMD reserves, e.g. an hour or a day of volume above ~6x reserves, or simply fees left uncalled because keepers earn nothing), every executeBatch reverts with SlippageExceeded, and every further trade only enlarges the budget. There is no smaller batch, no owner, and no other path that spends the claims: the buyback-and-burn, which is the only purpose of the fee, stops indefinitely while the hook keeps charging 1% on every swap. The only exits are third parties adding in-band liquidity or a >3% favourable price move large enough to open room, neither of which the contract controls. Invariant broken: 'anyone can call executeBatch once per hour if accrued fees exceed the minimum' (brief). The brief says the batch spends 'up to 25%'; filling what the band allows (accept the partial fill, spend inputDelta, keep the rest accrued) or capping spent at the fillable amount restores liveness without changing any parameter.","line":213,"path":"src/SIMDTESTHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SIMDTEST} from \"src/SIMDTEST.sol\";\nimport {SIMDTESTHook} from \"src/SIMDTESTHook.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {StateLibrary} from \"v4-core/src/libraries/StateLibrary.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\n\ncontract IMDStandIn is ERC20 {\n    constructor() ERC20(\"Identity\", \"IMD\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\n/// @notice The batch budget is a fixed 24.75% of accrued fees and the batch refuses any partial\n/// fill. Once accrued fees outgrow the liquidity inside the 300-tick band around the TWAP, every\n/// executeBatch reverts with SlippageExceeded, and every further trade only enlarges the budget,\n/// so the stall is self-reinforcing: the fees can never be spent by the hook itself.\ncontract Proof_BatchStall is Test, IUnlockCallback {\n    using StateLibrary for IPoolManager;\n\n    address internal constant IMD = 0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7;\n    uint160 internal constant Q96 = 79228162514264337593543950336;\n\n    IPoolManager internal manager;\n    SIMDTEST internal token;\n    SIMDTESTHook internal hook;\n    PoolKey internal key;\n\n    function setUp() public {\n        vm.warp(10_000);\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        vm.etch(IMD, address(new IMDStandIn()).code);\n        IMDStandIn(IMD).mint(address(this), 10_000_000_000 ether);\n        token = new SIMDTEST();\n        bytes memory creation = abi.encodePacked(type(SIMDTESTHook).creationCode, abi.encode(manager, address(token)));\n        bytes32 initHash = keccak256(creation);\n        for (uint256 i; i < 500_000; i++) {\n            address predicted =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), initHash)))));\n            if (uint160(predicted) & ((1 << 14) - 1) == 0x10cc) {\n                hook = new SIMDTESTHook{salt: bytes32(i)}(manager, address(token));\n                break;\n            }\n        }\n        require(address(hook) != address(0), \"no salt\");\n        key = hook.poolKey();\n        manager.initialize(key, Q96);\n        // Same depth as the project's own harness: 10M liquidity units over the full range.\n        _liquidity(10_000_000 ether, -887220, 887220);\n    }\n\n    /// @dev Ordinary trading (wash-neutral, so the TWAP sits on the spot) accrues fees until the\n    /// batch budget exceeds the IMD the pool can absorb within 300 ticks (~151k IMD at this depth).\n    /// Expected: executeBatch spends at most a quarter of the fees and burns something.\n    /// Actual: SlippageExceeded on every attempt, forever, and every further trade makes it worse.\n    function test_batchExecutesWhenBudgetExceedsBandLiquidity() public {\n        for (uint256 i; i < 45; i++) {\n            vm.warp(block.timestamp + 60);\n            BalanceDelta d = _swap(true, true, 1_000_000 ether); // buy 1M IMD exact input\n            uint256 tokensBought = uint256(int256(_tokenDelta(d)));\n            vm.warp(block.timestamp + 60);\n            _swap(false, true, tokensBought); // sell the same tokens back: price returns near 1\n        }\n        vm.warp(block.timestamp + 1 hours);\n        uint256 accrued = hook.accruedFees();\n        emit log_named_decimal_uint(\"accrued IMD fees\", accrued, 18);\n        emit log_named_decimal_uint(\"batch budget\", accrued / 4, 18);\n        emit log_named_decimal_uint(\"IMD absorbable within 300 ticks\", _bandCapacity(), 18);\n        assertGt(accrued, hook.MIN_ACCRUED_FEES());\n        int24 ref = hook.consult();\n        (, int24 spot,,) = manager.getSlot0(hook.poolId());\n        emit log_named_int(\"reference tick\", ref);\n        emit log_named_int(\"spot tick\", spot);\n        // Spot is on the favourable side of (or at) the reference, so nothing but the budget blocks.\n        assertTrue(hook.imdIsCurrency0() ? spot >= ref - 5 : spot <= ref + 5, \"spot not near reference\");\n\n        (uint256 spent, uint256 burned) = hook.executeBatch();\n        assertLe(spent, accrued / 4, \"spent more than a quarter\");\n        assertGt(burned, 0, \"nothing burned\");\n        assertEq(token.balanceOf(hook.BURN_ADDRESS()), burned);\n    }\n\n    /// @dev IMD the pool absorbs from the current price down to the 300-tick limit, for a swap that\n    /// buys SIMDTEST with IMD, at the full-range depth seeded above (ignoring the LP fee).\n    function _bandCapacity() internal view returns (uint256) {\n        (uint160 sqrtP, int24 spot,,) = manager.getSlot0(hook.poolId());\n        uint128 L = manager.getLiquidity(hook.poolId());\n        if (hook.imdIsCurrency0()) {\n            uint160 sqrtLimit = TickMath.getSqrtPriceAtTick(spot - 300);\n            // amount0 = L * (1/sqrtLimit - 1/sqrtP)\n            return uint256(L) * (1 << 96) / sqrtLimit - uint256(L) * (1 << 96) / sqrtP;\n        } else {\n            uint160 sqrtLimit = TickMath.getSqrtPriceAtTick(spot + 300);\n            // amount1 = L * (sqrtLimit - sqrtP)\n            return uint256(L) * (sqrtLimit - sqrtP) / (1 << 96);\n        }\n    }\n\n    function _liquidity(int256 amount, int24 lower, int24 upper) internal {\n        manager.unlock(abi.encode(uint8(0), abi.encode(ModifyLiquidityParams(lower, upper, amount, bytes32(0)))));\n    }\n\n    function _swap(bool buy, bool exactInput, uint256 amount) internal returns (BalanceDelta) {\n        bool zeroForOne = buy == hook.imdIsCurrency0();\n        SwapParams memory p = SwapParams(\n            zeroForOne,\n            exactInput ? -int256(amount) : int256(amount),\n            zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1\n        );\n        return abi.decode(manager.unlock(abi.encode(uint8(1), abi.encode(p))), (BalanceDelta));\n    }\n\n    function _tokenDelta(BalanceDelta delta) internal view returns (int256) {\n        return hook.imdIsCurrency0() ? int256(delta.amount1()) : int256(delta.amount0());\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"manager only\");\n        (uint8 action, bytes memory payload) = abi.decode(data, (uint8, bytes));\n        BalanceDelta delta;\n        if (action == 0) {\n            (delta,) = manager.modifyLiquidity(key, abi.decode(payload, (ModifyLiquidityParams)), \"\");\n        } else {\n            delta = manager.swap(key, abi.decode(payload, (SwapParams)), \"\");\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) private {\n        if (delta < 0) {\n            manager.sync(currency);\n            IERC20(Currency.unwrap(currency)).transfer(address(manager), uint256(-int256(delta)));\n            manager.settle();\n        } else if (delta > 0) {\n            manager.take(currency, address(this), uint128(delta));\n        }\n    }\n}","reproduction":"State: PoolManager with the hook's pool at price 1 and 10M liquidity units full-range (the project's own harness depth). Trade wash-neutrally for 90 minutes: 45 rounds of {buy with 1,000,000 IMD exact input; sell the received tokens back}, then wait one hour. Result: accruedFees = 884,905 IMD, budget = 221,226 IMD, spent = 219,014 IMD, reference tick == spot tick (980 == 980), yet the pool can absorb only 158,338 IMD between spot and spot-300 ticks. executeBatch() -> SlippageExceeded from unlockCallback (partial fill). Keep trading a further day (10 more rounds of 500k, batch attempted hourly): accruedFees grows to 983,202 IMD and every attempt still reverts. A single 150,000 IMD exact-input swap with the same terminal limit fills completely (tick ends at 1367 = ref+300 side), showing the band had room for a smaller batch. Expected: a batch executes spending at most a quarter of the fees and burns tokens. Actual: no batch can ever execute; see test/scratch/Proof_BatchStall.t.sol (fails on current code with SlippageExceeded).","severity":"medium","snippet":"        if (inputDelta != -int256(amount) || outputDelta < int256(minimumOut)) revert SlippageExceeded();","title":"Batch budget outgrows the liquidity inside the 300-tick band; executeBatch then reverts forever and every trade makes it worse (fees unspendable)"},{"citation":"resolved","description":"For exact-input buys and exact-output sells (IMD is the specified currency) the fee is computed in beforeSwap from params.amountSpecified and accrued immediately; afterSwap never compares it with the IMD the pool actually moved. When the swap stops early at the caller's sqrtPriceLimitX96, the fee stays at 1% (or 1/99) of the request. The brief promises 'a fixed additional fee of 1% on every swap'; here the effective fee is unbounded relative to the fill, up to the whole request when the fill is zero. The trigger is not only self-inflicted: anyone watching the mempool can move the pool to exactly one wei short of the victim's limit (v4 reverts PriceLimitAlreadyExceeded only when the limit is already reached, so a swap starting one wei inside it runs, fills a dust amount and stops), after which the victim's swap succeeds with zero output and the full reserved fee. For an exact-output sell the seller's IMD delta becomes negative: they hand over tokens AND pay IMD. A router-level minimum-output check prevents the zero-fill case but not the partial-fill case (e.g. a loose amountOutMinimum of 50% lets a 60% fill through and the user pays 1,000 IMD on a 60,000 IMD fill: 1.67%). The README documents the behaviour and says there is no in-contract remedy, but there is, and it was checked against the vendored PoolManager in a sketch hook: in afterSwap, when the swap was IMD-specified, compare the reserved fee with 1% (1/99) of the actual IMD pool delta (the BalanceDelta argument) and return the excess to `sender` by burning the hook's own claim and minting an ERC-6909 IMD claim to sender (or poolManager.take to sender when the manager holds the IMD), decrementing accruedFees/totalFeesAccrued by the refund. The hook's net delta stays zero and the swapper's unlock settles: a 100,000 IMD exact-input request that filled 15,200 IMD paid a net fee of 152 IMD with 848 IMD refunded as a claim. Loss is bounded by 1% of any price-limited request; fees go to the buyback, so the attacker's gain is indirect (griefing, or as a token holder).","line":140,"path":"src/SIMDTESTHook.sol","reproduction":"State: hook pool at price 1 with 10M full-range liquidity, victim and attacker are separate routers. (a) Attacker: exact-input buy of 10,000 IMD with sqrtPriceLimitX96 = (price at tick -1 for IMD=currency0, +1 otherwise) + 1 wei toward the current price, i.e. one wei short of the victim's limit. Victim: exact-input buy of 100,000 IMD with sqrtPriceLimitX96 at tick -1/+1. Actual: victim receives 0 tokens, pays 1,000.000000000000000002 IMD, of which 1,000 IMD is the hook fee (accruedFees rises by 1000e18). Expected: fee <= 1% of IMD the pool moved (2 wei). (b) Same with an exact-output sell: attacker sells to one wei short, victim requests 100,000 IMD out with the same limit. Actual: victim token delta -2 wei, victim IMD change -1,010.101010101010101010 (the seller pays IMD), hook fee 1,010.10 IMD. Expected: seller receives IMD >= 0 and fee <= 1% of IMD paid out. Both are test/scratch/Proof_FeeOnZeroFill.t.sol, failing on current code; the project's own test_priceLimitedExactOutputSellPaysFeeOnTheRequest pins the self-inflicted variant.","severity":"medium","snippet":"        uint256 fee = exactInput ? magnitude / 100 : magnitude / 99;\n        if (fee > uint256(uint128(type(int128).max))) fee = uint256(uint128(type(int128).max));\n        _accrue(fee);\n        return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);","title":"IMD-specified swaps are charged 1% of the requested amount even when the pool fills nothing: a front-runner can make a victim pay 1,000 IMD for zero output, and a seller pay 1,010 IMD on a sell"},{"citation":"resolved","description":"The spot-versus-TWAP gate is what protects the batch from sandwiches (checked: a plain sandwich and a JIT-liquidity sandwich both lose or net at most +80 IMD on a 102k IMD batch before gas). Its flip side is that anyone can refuse a batch in a given block by moving spot past the 300-tick limit immediately before it, at a cost of the two fees on the push and its unwind. No loss of funds; the batch is retried next block. Combined with the absence of any keeper reward (README: 'receive no bounty'), fees may remain uncalled for long periods, which is the precondition of finding 1 (the budget grows while the band capacity does not). Not a defect against the brief; recorded so the judge sees the liveness assumptions.","line":194,"path":"src/SIMDTESTHook.sol","reproduction":"Harness depth (10M full-range liquidity, price 1): after a 100,000 IMD buy and a one-hour wait, a same-block exact-input buy of 500,000 IMD moves spot more than 300 ticks from the reference (project test test_spotManipulationCannotRewriteTWAPOrSpendFees); executeBatch() reverts SlippageExceeded with ledger and clock unchanged. Moving exactly 300 ticks needs about 153,000 IMD at this depth; hook fee plus LP fee on the push and on the unwind cost the griefer about 6,900 IMD per blocked block. Expected and actual agree (refusal); this row documents cost and liveness only.","severity":"info","snippet":"        if (imdIsCurrency0 ? spot <= limit : spot >= limit) revert SlippageExceeded();","title":"Batch liveness can be denied per block by a same-block push beyond 300 ticks; keepers have no incentive, so fees may sit idle and feed finding 1"}],"hash":"6119f782084fe05e18bdde5d5e21ded015f64515fe5cea77dd98ba3cee9bf8df","nodeId":"29afc057-11e5-42a6-bb17-27e0404594d8","outcome":"completed","summary":"The review is complete. The findings file is written at the repository root, the scratch directory is removed, and no tracked file under `src`, `test`, `lib`, `script`, `launch.json` or `foundry.toml` changed.\n\n**Outcome: two reproducible medium findings in the batch and fee economics, one info note, and a 15-row coverage record.** Both medium findings carry a self-contained Foundry proof that fails on the current tree.\n\n- **Finding 1 (medium, proof): the buyback can stall permanently.** The batch always swaps exactly 24.75% of accrued fees and rejects any partial fill, while the IMD a pool can absorb inside the 300-tick band is bounded by in-band liquidity. Once fees outgrow that band the batch reverts on every call, and every further swap only enlarges the budget. In the project's own harness depth, 90 minutes of wash-neutral trading accrues 884,905 IMD against 158,338 IMD of band capacity, with spot sitting on the reference tick. Filling what the band allows, which the brief's \"up to 25%\" permits, restores liveness.\n\n- **Finding 2 (medium, proof): IMD-specified swaps pay 1% of the request, not the fill.** A front-runner can park the price one wei short of a victim's price limit so the victim's swap fills nothing yet pays the full reserved fee: 1,000 IMD on a 100,000 IMD buy, and 1,010 IMD paid out of pocket on an exact-output sell. The README calls this unfixable in-contract. I verified a remedy against the vendored PoolManager: reconcile in afterSwap and refund the excess as an ERC-6909 claim to the sender, which settles cleanly.\n\n- **Info: liveness denial and keeper incentive.** Anyone can refuse a batch in a given block by pushing spot past the band at a cost of about 6,900 IMD per blocked block at harness depth. Combined with no keeper reward, this feeds the precondition of finding 1.\n\n**What held.** Fee ledger conservation, claims backing, TWAP immunity to same-block manipulation, the once-per-hour and strict-threshold gates, batch rollback, reentrancy barriers, and the sandwich bound. A JIT-liquidity sandwich at five push depths nets at most +80 IMD on a 102k IMD batch before gas. The static-analysis leads resolved to false positives or to the line already reported in finding 1.\n\n**Not reached.** The real IMD token's behaviour on a mainnet fork, since this environment has no network. The hook never transfers IMD itself, so IMD-side controls affect swappers' settlement rather than batches.","treeHash":null,"usage":{"cachedInputTokens":3832220,"inputTokens":738,"model":"claude-fable-5-1","outputTokens":79876,"runtime":"claude","turns":53,"wallClockMs":1147593}},{"artifacts":[],"attempt":1,"bundleHash":"7da81a0634f91a7589b49851ad6eca4f2e0ab38f7d9aaccb78adc815de3ed54e","device":"3b9f88f55251764d","findings":[{"description":"For an exact-output sell (IMD specified as output) beforeSwap reserves amountSpecified / 99 as the hook fee before the pool runs and returns it as the specified-side BeforeSwapDelta. v4 cannot revise a specified-side hook delta after the pool has executed, so when the caller's sqrtPriceLimitX96 stops the pool after a fraction of the requested output, the fee stays sized to the request. The seller's IMD delta becomes pool output minus the reserved fee, which turns negative once the fill is below about 1% of the request: the seller pays IMD and tokens in the same swap and the hook collects more than 1% of what the pool actually produced. The implementation documents this in README and pins it in test_priceLimitedExactOutputSellPaysFeeOnTheRequest. Because the brief fixes the fee to the IMD side and forbids reverting swaps, there is no in-contract remedy under the current design short of charging exact-output sells on the unspecified (token) side or refusing them; the hazard is therefore reported for the requester's and routers' attention rather than asserted as correct. A router that settles blindly after an exact-output sell with a protective price limit loses user IMD; one that checks the signed IMD delta reverts the transaction.","line":140,"path":"src/SIMDTESTHook.sol","reproduction":"Local PoolManager, IMD and SIMDTEST at price 1:1 with 10,000,000e18 full-range liquidity (test/helpers/PoolHarness.sol _localSetup(true)). Swap exact-output sell: zeroForOne = !imdIsCurrency0, amountSpecified = +100_000e18 (IMD out), sqrtPriceLimitX96 = TickMath.getSqrtPriceAtTick(imdIsCurrency0 ? 1 : -1). Expected (a 1% fee on the executed IMD output of about 505.3e18): seller IMD delta about +500.2e18, hook fee about 5.05e18. Actual: hook accruedFees = 1010101010101010101010 (1,010.10 IMD), seller IMD delta = -510138506976283513903 (pays 510.14 IMD), seller token delta = -506316456329074369859 (also pays 506.3 tokens). The fee is about 200 times the fee due on the fill.","severity":"low","title":"Price-limited exact-output sell charges the fee on the requested amount, so a small fill makes the seller pay IMD as well as tokens"}],"hash":"66c7103ff319282dda9062a6b3a99a8d6a8690f3c32365dccc86575a548387c3","nodeId":"889fa6a7-c59d-47ca-aec1-cacde22b25ee","outcome":"completed","summary":"Work is complete. Final state:\n\n**Outcome.** The tree already held my accepted adversarial, invariant and fork suites, and the implementer afterwards changed the batch slippage reference from the gross budget to the IMD the pool actually prices. Everything compiled and passed, so the earlier rejection was the aborted attempt itself, not a failing test. This revision aligns my suites with the new semantics and adds coverage of them. The full offline suite passes with 90 tests and the 2 intentional fork skips, and both fork suites pass on mainnet at the README's recorded block and at a recent block.\n\n**Changes, all inside `test/`:**\n- `test/HookInvariant.t.sol`: the handler's output floor now encodes the hook's actual promise, 97% of the reference quote for 99% of the budget net of the 1.25% LP fee. The old gross-budget floor was stricter than the contract and would have failed spuriously under adverse drift. It also checks that every executed batch's output is no worse than the quote at the 300-tick terminal limit.\n- `test/HookAdversarial.t.sol`: the batch fuzz uses the same floor. Two new fuzz tests: one pushes spot off the hour's mean by a same-block trade and requires execute inside the tolerance with output within 300 bps of the reference, refusal only at the edge or beyond with state untouched, and no other outcome. The other sandwiches the batch and shows the attacker always ends with less IMD. A scratch probe confirmed the drift fuzz reaches both branches.\n- `test/MainnetForkAdversarial.t.sol`: the keeper-burn test's floor uses the hook's formula.\n\n**Finding reported** in `.imd-findings.json` as low severity. An exact-output sell that a price limit stops after a small fill still pays the fee on the requested amount, so the seller pays IMD and tokens in one swap. Concrete numbers: a 100,000 IMD request stopped one tick in reserved 1,010.1 IMD, and the seller paid about 510 IMD plus 506 tokens. The implementer documents and pins this, and the brief's constraints leave no in-contract remedy, so it is reported rather than asserted correct. I probed a thin-liquidity batch stall as a second candidate but batches resumed once the price level let the budget fit, so that stays documented economic behaviour and is not reported.\n\n`git status` shows only the three modified test files. The findings file is excluded by the repository's own `.git/info/exclude`, which is where the harness expects it. No scratch files remain and nothing was committed.","treeHash":"e19c9a7208da1042c7f2cdfa8b126630294efc20","usage":{"cachedInputTokens":1744950,"inputTokens":482,"model":"claude-fable-5-1","outputTokens":39635,"runtime":"claude","turns":39,"wallClockMs":600350}},{"artifacts":[],"attempt":2,"bundleHash":"0bd7ca37da0fb150e4578ceb887a796fe2520f48ae3c9293d6cad42a6ed706b2","device":"b4f6137e7c93a2b3","findings":[{"description":"The brief asks for 'a maximum slippage of 300 basis points against a time-weighted reference price'. executeBatch() computes minimumOut = 97% of quoteAtTick(referenceTick, budget), i.e. 97% of what the GROSS budget would buy at the mean price with no fees, but the swap it then performs spends only `spent = budget - budget/100` (the retained 1% self-fee) and pays the pool's 1.25% LP fee. Those two fees alone cost 1 - 0.99*0.9875 = 2.24% of the quoted amount, so the output check can only tolerate an adverse difference between spot and the mean of 0.97/(0.99*0.9875) = 0.9922, about 78 ticks (0.78%), not 300. Any market in which the token has been drifting up during the trailing hour (which a buyback-and-burn token is designed to do) leaves spot more than 78 ticks above the lagging mean and every executeBatch() call reverts with SlippageExceeded even though the executed price is within 1-2% of the reference and the pool is deep enough that the batch itself has negligible impact. Fees keep accruing as claims and nothing is lost, but the buyback mechanic the launch advertises stalls for as long as the trend lasts; a steady 1%/hour rise blocks it indefinitely. The separate terminal-price check (_priceLimit at 300 ticks) is correctly sized; the output check is the one that is mis-scaled. Expected: a batch whose execution price is within 300 bps of the TWAP executes. Suggested direction: measure the slippage against the reference as the brief says, e.g. minimumOut = quoteAtTick(referenceTick, spent) net of the LP fee times 0.97, or quote on `spent` and keep the terminal-price bound as the hard cap; the README's 'both fees count within the 3%' sentence then needs updating.","line":186,"path":"src/SIMDTESTHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SIMDTEST} from \"src/SIMDTEST.sol\";\nimport {SIMDTESTHook} from \"src/SIMDTESTHook.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {StateLibrary} from \"v4-core/src/libraries/StateLibrary.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\n\ncontract MintableERC20 is ERC20 {\n    constructor() ERC20(\"Identity\", \"IMD\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\n/// @notice executeBatch() refuses a buyback whose execution price is well inside 300 bps of the\n/// one-hour TWAP, because the 3% tolerance is applied to the gross budget's quote while the\n/// swap is executed with 99% of the budget and pays the 1.25% LP fee. Those two fees consume\n/// 2.21 of the 3 points, leaving ~78 ticks (0.78%) for drift between the mean and spot.\n///\n/// Fails on the current code with SlippageExceeded. Passes once the slippage check compares the\n/// executed price with the reference price (the brief's \"maximum slippage of 300 basis points\n/// against a time-weighted reference price\") instead of folding both fees into the tolerance.\ncontract BatchStarvationProofTest is Test, IUnlockCallback {\n    using StateLibrary for IPoolManager;\n\n    address constant IMD = 0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7;\n    uint160 constant Q96 = 79228162514264337593543950336;\n\n    IPoolManager manager;\n    SIMDTEST token;\n    SIMDTESTHook hook;\n    PoolKey key;\n\n    function setUp() public {\n        vm.warp(10_000);\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        vm.etch(IMD, address(new MintableERC20()).code);\n        MintableERC20(IMD).mint(address(this), 1_000_000_000 ether);\n        token = new SIMDTEST();\n\n        bytes32 initHash = keccak256(abi.encodePacked(type(SIMDTESTHook).creationCode, abi.encode(manager, address(token))));\n        bytes32 salt;\n        for (uint256 i;; i++) {\n            salt = bytes32(i);\n            address predicted = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, initHash)))));\n            if (HookFlags.matches(predicted, 0x10cc)) break;\n        }\n        hook = new SIMDTESTHook{salt: salt}(manager, address(token));\n        key = hook.poolKey();\n        manager.initialize(key, Q96);\n        manager.unlock(abi.encode(uint8(0), abi.encode(ModifyLiquidityParams(-887220, 887220, 10_000_000 ether, bytes32(0)))));\n    }\n\n    function test_batchWithinThreePercentOfTheTWAPExecutes() public {\n        // Accrue fees, then let buyers lift the price gently for an hour: the mean lags spot.\n        _buy(200_000 ether);\n        for (uint256 i; i < 12; i++) {\n            vm.warp(block.timestamp + 300);\n            _buy(10_000 ether);\n        }\n        vm.warp(block.timestamp + 1);\n\n        int24 mean = hook.consult();\n        (, int24 spot,,) = manager.getSlot0(hook.poolId());\n        uint256 adverseTicks = uint256(int256(hook.imdIsCurrency0() ? mean - spot : spot - mean));\n        // Preconditions: the move is real but inside the brief's 300 bps, and the pool is so deep\n        // that the batch itself moves the price by far less than a basis point.\n        assertGt(adverseTicks, 100, \"drift too small to be meaningful\");\n        assertLt(adverseTicks, 300, \"drift outside the stated tolerance\");\n        uint256 budget = hook.accruedFees() / 4;\n        assertLt(budget * 10_000 / IERC20(IMD).balanceOf(address(manager)), 2, \"budget is not negligible against reserves\");\n\n        // Expected: a buyback within tolerance goes through. Actual: SlippageExceeded.\n        (uint256 spent, uint256 burned) = hook.executeBatch();\n        assertGt(spent, 0);\n        assertGt(burned, 0);\n        assertEq(token.balanceOf(hook.BURN_ADDRESS()), burned);\n    }\n\n    function _buy(uint256 imdIn) internal {\n        bool zeroForOne = hook.imdIsCurrency0();\n        manager.unlock(\n            abi.encode(\n                uint8(1),\n                abi.encode(SwapParams(zeroForOne, -int256(imdIn), zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1))\n            )\n        );\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (uint8 action, bytes memory payload) = abi.decode(data, (uint8, bytes));\n        BalanceDelta delta;\n        if (action == 0) (delta,) = manager.modifyLiquidity(key, abi.decode(payload, (ModifyLiquidityParams)), \"\");\n        else delta = manager.swap(key, abi.decode(payload, (SwapParams)), \"\");\n        _settle(key.currency0, delta.amount0());\n        _settle(key.currency1, delta.amount1());\n        return \"\";\n    }\n\n    function _settle(Currency currency, int128 delta) private {\n        if (delta < 0) {\n            manager.sync(currency);\n            IERC20(Currency.unwrap(currency)).transfer(address(manager), uint256(-int256(delta)));\n            manager.settle();\n        } else if (delta > 0) {\n            manager.take(currency, address(this), uint128(delta));\n        }\n    }\n}","reproduction":"Fresh PoolManager, hook at a mined 0x10cc address, pool initialised at sqrtPrice 2^96 with 10,000,000e18 full-range liquidity (either currency ordering). (1) Exact-input buy with 200,000 IMD (accrues 2,000 IMD of fees). (2) Twelve exact-input buys of 10,000 IMD each, 300 seconds apart, over the following hour. (3) Warp +1s. consult() = mean tick -494, spot tick -617 (IMD = currency0 ordering): adverse drift of 123 ticks = 1.22%, inside the 300-tick limit; budget = 800 IMD against ~1e25 IMD of reserves (impact < 0.01%). Expected: executeBatch() returns (spent ≈ 792e18, burned > 0). Actual: revert SlippageExceeded() (0x8199f5f3). Replacing minimumOut with FullMath.mulDiv(quoteAtTick(referenceTick, spent) * 98_750 / 100_000, 9700, 10_000) makes the same sequence succeed (verified on a scratch copy of the hook).","severity":"medium","title":"executeBatch() folds its own 1% fee and the 1.25% LP fee into the 300 bps slippage tolerance, leaving ~78 ticks for drift: buybacks are refused while spot is well inside 3% of the TWAP"},{"description":"For swaps where IMD is the specified currency (exact-input buys and exact-output sells) the fee is charged in beforeSwap as a BeforeSwapDelta of magnitude/100 or magnitude/99 of the amount the caller asked for, and v4 cannot revise a specified-side hook delta after the pool has run. If the caller's sqrtPriceLimitX96 stops the pool early, the pool moves far less IMD than requested but the hook still takes the fee sized to the request. For an exact-output sell the consequence is worse than an over-charge: the seller's IMD delta becomes (pool output - fee), which goes negative, so they hand over tokens AND pay IMD, and the hook's 'fee' exceeds the IMD the pool actually produced. The README documents the behaviour ('Price-limited partial fills still pay this specified-amount fee') and routers using exact-input sells with MIN/MAX limits are unaffected, and the hook has no way to refund the specified side without touching IMD inside the callback (which the brief forbids), so this is reported for the record rather than as something the implementer overlooked. It is nevertheless a trap for any integrator that uses sqrtPriceLimitX96 as the protective bound v4 intends it to be, and the effective fee can exceed 100% of the pool's output. Not asserted in the delivered suite; the suite asserts only the documented fee formula (fee = N/99, seller receives exactly N on a full fill).","line":140,"path":"src/SIMDTESTHook.sol","reproduction":"Local harness (test/helpers/PoolHarness.sol, IMD = currency0, pool at 1:1 with 10,000,000e18 full-range liquidity). Exact-output SELL: zeroForOne = false (token in, IMD out), amountSpecified = +100,000e18, sqrtPriceLimitX96 = TickMath.getSqrtPriceAtTick(1) (one tick above spot). Result: hook.accruedFees() = 1,010.10 IMD (= 100,000e18 / 99); pool moved only ~500 IMD to the price limit; the caller's BalanceDelta is IMD -510.14e18 and token -506.32e18, i.e. the seller paid on both legs. Expected by a user: receive IMD for the tokens sold, with a fee of at most 1% of what the pool actually produced (~5 IMD).","severity":"low","title":"Exact-output sells reserve the 1% fee from the REQUESTED IMD amount before the swap; a user price limit that only lets a small fill happen leaves the seller paying IMD as well as tokens"}],"hash":"6f26c1bb0afbbe53f81c9dde668a3c16aa0674657ca67a4c73a18a73f4d71d86","nodeId":"889fa6a7-c59d-47ca-aec1-cacde22b25ee","outcome":"completed","summary":"The harness itself excludes `.imd-findings.json` from git via `.git/info/exclude` (it's read from the working tree), so that's expected. Work is complete.\n\n## Summary\n\n**Starting point.** The accepted implementation (`SIMDTESTHook`, `SIMDTEST`) came with a solid 39-test suite. I read the hook adversarially, probed it with scratch tests, and built on the existing harness rather than rewriting it.\n\n**Delivered (all under `test/`, 82 pass offline + 2 fork suites that skip cleanly without `--fork-url`):**\n\n- `test/HookAdversarial.t.sol` — 21 tests × both currency orderings. Failure paths the original suite didn't reach: constructor rejects codeless manager/token, IMD-as-token, and an un-mined address (`HookAddressNotValid`); un-initialized hook refuses `executeBatch`/`consult`; `afterInitialize` replay from the manager; foreign-token pools; the exact 100 IMD threshold vs. +1 wei; ring overflow → `OracleNotReady` blocks batches but not trades, then recovers; sub-rounding amounts pay zero and still settle; `int256.min`/`int128.max` swaps revert in the manager with no fee left behind; untrusted router + stranger keeper (earns nothing); gifted ERC-6909 claims and stray ERC-20s are neither counted nor spent; LP add/remove/donate bypass the hook; event contents; factory-style CREATE2 deploy + atomic initialize. Fuzz: TWAP vs. an independent reference over random schedules, `quoteAtTick` vs. sqrt-price arithmetic across the full tick range, batch budget/slippage/burn bounds.\n- `test/HookInvariant.t.sol` — handler with 3 independent actors trading, LPing, donating, gifting claims, sending strays, and attempting batches with time warps; 6 invariants (ledger conservation, claims = ledger + gifts and backed by manager IMD, burn ledger = dEaD balance, fixed supply, hook holds only strays, batch cadence ≤ hours elapsed, immutable params, no unexpected reverts). 128 runs × depth 48.\n- `test/MainnetForkAdversarial.t.sol` — 6 tests on the real PoolManager + real IMD: exact IMD settlement both directions (no tax), batch via claims moves zero IMD ERC-20, refusals leave state untouched, fuzz over all four modes. **Verified live at block 26145475: 7/7 fork tests pass** (both fork suites).\n- Helpers: `ThirdPartyRouter.sol`, `LaunchFactorySim.sol`.\n\n**Findings reported in `.imd-findings.json` (not tested around):**\n\n1. **Medium** — `executeBatch()` quotes 97% of the *gross* budget at the TWAP but swaps only 99% of it and pays the 1.25% LP fee, so the effective drift tolerance is ~78 ticks, not 300 bps. A gentle 1.2% rise over the trailing hour (deep pool, negligible impact) makes every batch revert `SlippageExceeded`. Proof attached: fails on current code, passes on a corrected scratch copy.\n2. **Low** — exact-output sells reserve `N/99` from the *requested* amount; with a tight `sqrtPriceLimit` the seller ends up paying IMD *and* tokens (repro: 100k requested → 1,010 IMD fee on a ~500 IMD fill). Documented by the implementer and inherent to v4's specified-side delta, so recorded rather than blessed.\n\n**Notes:** README's recorded fork block 26145294 is no longer served by the public RPC (historical state pruned) — a documentation staleness outside my write scope. Inline `forge-config` fuzz runs inside the abstract base aren't applied by forge (the toml's 1000 runs apply instead), which is strictly more runs.","treeHash":"91aa434993acf09e318d5758d707af0afbbb76c4","usage":{"cachedInputTokens":6277675,"inputTokens":95,"model":"claude-fable-5-1","outputTokens":78487,"runtime":"claude","turns":50,"wallClockMs":1402681}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"fa8fc4653a9e883d","findings":[{"citation":"resolved","description":"Asymmetry between the two fee paths. When IMD is the unspecified currency, afterSwap charges 1% of the IMD the pool actually moved (line 156-158). When IMD is the specified currency, beforeSwap reserves 1% (or 1/99) of params.amountSpecified before the pool runs and returns it as a fixed specified-side delta. v4 cannot revise that delta after the pool fills, so any swap whose sqrtPriceLimitX96 (a standard router slippage parameter, used exactly like Uniswap v3's) or whose exhausted liquidity stops the pool early is charged on IMD that never traded. For an exact-input buy the buyer pays 1% of the whole budget for a dust fill. For an exact-output sell the reserved fee is deducted from the pool's partial IMD output, so the seller's IMD delta goes negative: they hand over tokens AND IMD and receive nothing. The brief promises 'a fixed additional fee of 1% on every swap'; here the effective fee is 198% (buy) and more than 200% (sell) of the IMD leg, with no cap other than the request size. The README documents this as a 'trap' routers must avoid; that does not make the swapper's loss go away, and the hook holds the overcharge as claims it then spends on buybacks. A fix that keeps the fee on the IMD side: in afterSwap for IMD-specified swaps, compute due = |actual IMD pool delta| / 100 (or /99 grossed up), burn the surplus claim (reserved - due) and mint it back to the swap's sender as an IMD claim (or take it to the sender when the manager holds enough IMD), decrementing accruedFees; alternatively charge the fee in afterSwap on the unspecified side only when the fill is partial. The proof credits any claim refund to the swapper so either refund form passes it.","line":140,"path":"src/SIMDTESTHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SIMDTEST} from \"src/SIMDTEST.sol\";\nimport {SIMDTESTHook} from \"src/SIMDTESTHook.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {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 {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\ncontract ProofIMD is ERC20 {\n    constructor() ERC20(\"Identity\", \"IMD\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\n/// @notice A price-limited swap that specifies IMD is charged the hook fee on the whole request,\n/// not on the IMD that actually moved. The swapper pays far more than 1% of the trade.\ncontract PartialFillFeeProof is Test, IUnlockCallback {\n    address constant IMD = 0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7;\n    uint160 constant Q96 = 79228162514264337593543950336;\n\n    IPoolManager manager;\n    SIMDTEST token;\n    SIMDTESTHook hook;\n    PoolKey key;\n\n    function setUp() public {\n        vm.warp(10_000);\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        vm.etch(IMD, address(new ProofIMD()).code);\n        ProofIMD(IMD).mint(address(this), 1_000_000_000 ether);\n        // Either currency ordering reproduces; the test reads the ordering from the hook.\n        token = new SIMDTEST();\n\n        bytes32 initHash =\n            keccak256(abi.encodePacked(type(SIMDTESTHook).creationCode, abi.encode(manager, address(token))));\n        bytes32 salt;\n        for (uint256 i;; i++) {\n            salt = bytes32(i);\n            address predicted =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), salt, initHash)))));\n            if (uint160(predicted) & ((1 << 14) - 1) == 0x10cc) break;\n        }\n        hook = new SIMDTESTHook{salt: salt}(manager, address(token));\n        key = hook.poolKey();\n        manager.initialize(key, Q96);\n        manager.unlock(abi.encode(uint8(0), abi.encode(ModifyLiquidityParams(-887220, 887220, 10_000_000 ether, bytes32(0)))));\n    }\n\n    /// @dev Seller asks for 100,000 IMD out, protected by a price limit one tick away. The pool\n    /// fills only a few hundred IMD, but the hook keeps 100_000e18 / 99 of IMD. The seller leaves\n    /// with tokens gone AND an IMD debt. Expected: the fee is at most 1% of the IMD the pool moved.\n    function test_exactOutputSellWithPriceLimitIsChargedOnTheRequest() public {\n        bool imdFirst = hook.imdIsCurrency0();\n        uint160 limit = TickMath.getSqrtPriceAtTick(imdFirst ? int24(1) : int24(-1));\n        uint256 imdBefore = _imdHeld();\n        BalanceDelta delta = _swapLimit(!imdFirst, int256(100_000 ether), limit);\n        int256 imd = imdFirst ? int256(delta.amount0()) : int256(delta.amount1());\n        // Gross IMD the pool paid out = seller's net delta + the hook's fee (the net can be negative).\n        int256 poolMoved = imd + int256(hook.accruedFees());\n        emit log_named_uint(\"fee charged (IMD wei)\", hook.accruedFees());\n        emit log_named_int(\"gross IMD the pool paid\", poolMoved);\n        emit log_named_int(\"seller's net IMD delta\", imd);\n        emit log_named_int(\"tokens the seller gave\", imdFirst ? int256(delta.amount1()) : int256(delta.amount0()));\n        assertGe(_imdHeld(), imdBefore, \"a seller of tokens paid IMD\");\n        assertLe(int256(hook.accruedFees()), poolMoved / 100 + 1, \"hook fee exceeds 1% of the IMD leg\");\n    }\n\n    /// @dev ERC-20 IMD plus any PoolManager IMD claim credited to this swapper (a refund path a fix may use).\n    function _imdHeld() internal view returns (uint256) {\n        return IERC20(IMD).balanceOf(address(this)) + manager.balanceOf(address(this), uint256(uint160(IMD)));\n    }\n\n    /// @dev Buyer offers up to 100,000 IMD with a limit one tick away; the pool consumes a few\n    /// hundred IMD yet the hook keeps 1,000 IMD. Expected: at most 1% of the IMD consumed.\n    function test_exactInputBuyWithPriceLimitIsChargedOnTheRequest() public {\n        bool imdFirst = hook.imdIsCurrency0();\n        uint160 limit = TickMath.getSqrtPriceAtTick(imdFirst ? int24(-1) : int24(1));\n        BalanceDelta delta = _swapLimit(imdFirst, -int256(100_000 ether), limit);\n        int256 imd = imdFirst ? int256(delta.amount0()) : int256(delta.amount1());\n        // Swapper's gross IMD outlay minus what the hook kept is what the pool consumed.\n        uint256 kept = hook.accruedFees() - manager.balanceOf(address(this), uint256(uint160(IMD)));\n        uint256 consumed = uint256(-imd) - kept;\n        emit log_named_uint(\"fee kept by hook (IMD wei)\", kept);\n        emit log_named_uint(\"IMD the pool actually consumed\", consumed);\n        assertLe(kept, consumed / 100 + 1, \"hook fee exceeds 1% of the IMD leg\");\n    }\n\n    function _swapLimit(bool zeroForOne, int256 amount, uint160 limit) internal returns (BalanceDelta) {\n        return abi.decode(\n            manager.unlock(abi.encode(uint8(1), abi.encode(SwapParams(zeroForOne, amount, limit)))), (BalanceDelta)\n        );\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (uint8 action, bytes memory payload) = abi.decode(data, (uint8, bytes));\n        BalanceDelta delta;\n        if (action == 0) {\n            (delta,) = manager.modifyLiquidity(key, abi.decode(payload, (ModifyLiquidityParams)), \"\");\n        } else {\n            delta = manager.swap(key, abi.decode(payload, (SwapParams)), \"\");\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) private {\n        if (delta < 0) {\n            manager.sync(currency);\n            IERC20(Currency.unwrap(currency)).transfer(address(manager), uint256(-int256(delta)));\n            manager.settle();\n        } else if (delta > 0) {\n            manager.take(currency, address(this), uint128(delta));\n        }\n    }\n}","reproduction":"Local PoolManager, IMD stand-in at 0xD34a...63B7, hook at a 0x10cc address, pool initialised at sqrtPrice 2^96 with 10,000,000e18 full-range liquidity. (1) Exact-output sell: swap(zeroForOne = !imdIsCurrency0, amountSpecified = +100_000e18, sqrtPriceLimitX96 = price at tick +1/-1 towards the trade). Expected: fee <= 1% of IMD paid out, seller's IMD balance does not decrease. Actual: accruedFees = 1010.101e18; pool paid 499.96e18 IMD gross; seller's IMD delta = -510.14e18 and token delta = -506.32e18 (seller loses both legs). (2) Exact-input buy: swap(zeroForOne = imdIsCurrency0, amountSpecified = -100_000e18, limit at one tick). Expected: fee <= 1% of IMD consumed (5.06e18). Actual: fee kept = 1000e18 while the pool consumed 506.32e18 IMD (198% fee). Same outcome whenever a concentrated-liquidity pool runs out of liquidity before the specified amount is filled. test/scratch/PartialFillFee.t.sol fails on both.","severity":"high","snippet":"        uint256 fee = exactInput ? magnitude / 100 : magnitude / 99;\n        if (fee > uint256(uint128(type(int128).max))) fee = uint256(uint128(type(int128).max));\n        _accrue(fee);\n        return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);","title":"IMD-specified swaps are charged 1% of the requested amount, not the filled amount: a price-limited exact-output sell leaves the seller paying IMD on top of the tokens"},{"citation":"resolved","description":"Trust gap (access x economics). Anyone may call executeBatch; its only protections are a terminal price limit 300 ticks adverse of the one-hour mean tick and a minimum output of 97% of the mean-tick quote. Both are anchored to the reference, not to the pre-batch spot. The README argues a sandwich cannot pay because the batch concedes at most 300 bps while the attacker pays two 1% hook fees and two 1.25% LP fees; that holds only while spot is at or adverse to the mean. When spot sits favourably below the mean (a seller dumped in the current block, or the price fell during the hour), the batch may move the price from spot all the way down to ref-300 ticks and still clear minimumOut, so its own impact can be 8% or more. A searcher who front-runs the keeper's batch with a buy and back-runs with a sell captures that impact; the burn receives fewer tokens for the same IMD. The attacker can be the keeper itself, bundling front-run, executeBatch and back-run. The brief's literal '300 bps against a time-weighted reference' is honoured, but the buyback's purpose (maximise tokens burned per IMD, resist front-running) is not. Fix: also bound the swap against the pre-batch spot, e.g. use the adverse-most of (ref-300 ticks, spot-300 ticks) as the sqrtPriceLimitX96 and require output >= 97% of the quote at min(spot, ref), so the batch's own impact is capped at 3% and the 4.5% round-trip fee argument holds in every state.","line":192,"path":"src/SIMDTESTHook.sol","reproduction":"test/scratch/FavourableSandwich.t.sol (uses the project's PoolHarness/ThirdPartyRouter): IMD-first ordering, 10,000,000e18 full-range liquidity, exact-input buy of 2,000,000e18 IMD so accruedFees = 20,000e18; warp +1h; remove liquidity down to 100,000e18; in the same block a third party sells 4,500e18 tokens so spot is 1,035 ticks (about 10.9%) favourable to the batch while consult() is unchanged. Attacker (router): buy with 1,200e18 IMD, call executeBatch() (spent 4,967.9e18 IMD, burned 3,576.3e18 tokens, batch moved spot by 837 ticks), sell the tokens bought back. Expected: attacker IMD balance <= before (sandwich unprofitable, as the suite's testFuzz_sandwichingTheBatchLosesIMD assumes). Actual: attacker ends +46.73e18 IMD (3.9% on 1,200 IMD of capital) after paying all four fees; the fuzz variant finds profit across front-runs of 100-5,000 IMD and dumps of 2,000-20,000 tokens (example: drift 1,051 ticks, front-run 1,219.7e18, profit 47.56e18).","severity":"medium","snippet":"        uint160 limit = _priceLimit(referenceTick);\n        (uint160 spot,,,) = poolManager.getSlot0(poolId);\n        if (imdIsCurrency0 ? spot <= limit : spot >= limit) revert SlippageExceeded();","title":"Batch slippage is bounded only against the TWAP: when spot has drifted in the batch's favour its own price impact is unbounded and a sandwich of the permissionless executeBatch is profitable"},{"citation":"resolved","description":"Asymmetry between paired branches. Sells are symmetric: exact-input charges 1% of the pool's gross IMD output (afterSwap, P/100) and exact-output grosses up N/99 so the fee is again 1% of gross output. Buys are not: an exact-input buy charges 1% of the gross IMD the buyer sends (beforeSwap, B/100; the pool receives 0.99B), while an exact-output buy charges 1% of the pool's input P (afterSwap), i.e. 1/101 of the buyer's gross outlay. For the same gross spend an exact-output buyer pays about 1% less fee; for the same pool input an exact-input buyer pays Q/99 against Q/100. Routers that choose the mode can pick the cheaper side. Dust-level per trade, but it contradicts 'fixed 1% on every swap' and the documented table. Fix: charge magnitude / 99 for exact-output buys in afterSwap (mirroring the sell gross-up) or charge B/101 on exact-input buys, so every mode is 1% of the same basis.","line":158,"path":"src/SIMDTESTHook.sol","reproduction":"test/scratch/FeeBasis.t.sol: IMD-first pool with 10,000,000e18 full-range liquidity at price 1. (a) Exact-input buy of 10,100e18 IMD: accruedFees = 101e18, tokens received T. Revert state. (b) Exact-output buy of exactly T tokens: buyer pays 10,098.99e18 IMD gross, accruedFees = 99.99e18. Expected: equal fees for the same trade (both 101e18 or both ~100e18). Actual: 101e18 vs 99.99e18.","severity":"low","snippet":"        uint256 fee = magnitude / 100;","title":"Buy-side fee basis differs between exact-input and exact-output: the same trade pays 101 IMD one way and 99.99 IMD the other"}],"hash":"7a678eaf222e976c50f6d506df16a67171c36ed2735c3c2b2cdc9879947af1f4","nodeId":"7fa9f5c5-028b-4ca5-9842-dbdf0258b419","outcome":"completed","summary":"The review is complete. Findings and coverage are written to `.imd-findings.json` at the repository root, and every snippet, line and proof was validated against the tree.\n\n**Outcome.** The permission model itself is sound: every callback is gated to the PoolManager, the batch callback also needs the in-flight flag, no owner or setter exists, the manifest's constructor order and 0x10cc permission bits match the implementation, and the single-pool binding in `afterInitialize` closes the pre-deployment initialization window. The project's own 90-test suite passes offline. The defects I found live in the asymmetries and trust gaps the guides describe, not in missing guards.\n\n**Findings reported (three):**\n\n- **High, with proof.** Swaps that specify IMD are charged 1% of the *requested* amount in `beforeSwap`, while swaps that leave IMD unspecified are charged on the *actual* pool delta in `afterSwap`. A price-limited exact-output sell of 100,000 IMD filled only 500 IMD, yet the hook kept 1,010 IMD. The seller handed over 506 tokens and still paid 510 IMD net, receiving nothing. The exact-input buy case kept a 1,000 IMD fee on a 506 IMD fill. The README calls this a documented trap, but it is a loss of funds for anyone who uses the standard price-limit slippage parameter. The proof file in `test/scratch/PartialFillFee.t.sol` fails on both cases now and accepts a claim-based refund as a fix.\n- **Medium.** The batch's terminal limit and minimum output are anchored only to the hour's mean tick. When spot has drifted in the batch's favour, the batch may move the price by far more than 300 bps and still clear both checks, so sandwiching the permissionless `executeBatch` becomes profitable. With spot 1,035 ticks favourable and the pool thinned, a 1,200 IMD front-run around a 4,968 IMD batch netted the attacker 46.7 IMD after all four fees. The suite's sandwich fuzz never exercises this state.\n- **Low.** Exact-input and exact-output buys use different fee bases, so the same trade pays 101 IMD one way and 99.99 IMD the other. Sells are symmetric.\n\n**Coverage.** All eight listed entry points have rows: five hold, three carry finding references. Two invariant rows record claim conservation and the no-admin property. Fork runs against live mainnet state were not possible here and remain owed. The scratch tests under `test/scratch/` are review material and are not part of the delivered suite.","treeHash":null,"usage":{"cachedInputTokens":2292608,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":54270,"runtime":"claude","turns":37,"wallClockMs":762353}},{"artifacts":[],"attempt":1,"bundleHash":"453a742b54630febf3f2ca2bae353b5e642e3d97c4425c598002858417de63d8","device":"68fdf4cb027a7a1f","findings":[],"hash":"b1bbea9c4f25adb14c7eac723e338545a6db803e755d49696d71ef4524f7cff1","nodeId":"51641a9e-3ca6-4ce4-bc33-98d10399087c","outcome":"completed","summary":"Implemented the token, immutable fee/buyback hook, CREATE2 miner, launch manifest, vendored dependencies, tests, and documentation.\n\nValidation passed: `forge build`, `forge fmt --check`, and 55 tests with zero failures.\n\nMainnet-fork validation remains pending: public RPC requests returned HTTP 403, so that suite skipped cleanly.\n\nDeployment details and assumptions are in [README.md](/root/.identitymd/work/bf6a95f6-ca65-4a72-baae-8c7d065c54a2/51641a9e-3ca6-4ce4-bc33-98d10399087c/README.md).","treeHash":"326492745ff7804bf557192efe4f525bef915f38","usage":{"cachedInputTokens":2217472,"inputTokens":122580,"model":"gpt-6-astra","outputTokens":36781,"runtime":"codex","turns":7,"wallClockMs":1162725}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"943650541094145e","findings":[],"hash":"ca5bcb9db523803f08458b3ecbd1194a244771385c0939840e8dc510f83ceea9","nodeId":"889fa6a7-c59d-47ca-aec1-cacde22b25ee","outcome":"failed","summary":"This content was flagged for possible cybersecurity risk. If this seems wrong, try rephrasing your request. If you’re doing authorized security work that requires more cyber permissive safeguards, apply for Daybreak access via https://platform.openai.com/settings/organization/status-and-access before retrying.","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":"gpt-6-astra","outputTokens":0,"runtime":"codex","turns":3,"wallClockMs":358942}},{"artifacts":[],"attempt":2,"bundleHash":"8ec13d1902ef4753000d984f442cc774b7b35437c22016363550c0ce1c473b37","device":"e58e34e801414475","findings":[],"hash":"d5ff33b6ceaca8eded331ccf79063833122d030be75adae1519a9135c2792965","nodeId":"51641a9e-3ca6-4ce4-bc33-98d10399087c","outcome":"completed","summary":"Implemented the Foundry project and corrected `launch.json` to `kind: \"univ4_hook\"`.\n\nVerified:\n- Build and formatting pass.\n- 39 local tests and 11 protected checks pass.\n- Mainnet fork swaps and buyback/burn pass.\n\nDeployment assumptions and the requested-amount fee treatment for partial fills are documented in `README.md`; results are recorded in `VALIDATION.md`.","treeHash":"44db0d73505a9939834b2b529e8512d088682771","usage":{"cachedInputTokens":2157952,"inputTokens":116441,"model":"gpt-6-astra","outputTokens":36732,"runtime":"codex","turns":8,"wallClockMs":1213769}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"559cfaaab2c0d013","findings":[{"citation":"resolved","description":"Seam: boundary x invariant. The spend is always floor(accruedFees/4) less 1%, and accruedFees is monotone non-decreasing except through a successful batch. unlockCallback (line 213) rejects any partial fill (`inputDelta != -int256(amount)`), so a batch succeeds only if the pool can absorb the whole spend between spot and the terminal limit 300 ticks from the TWAP. The IMD the pool can absorb in that band is bounded by liquidity (about 1.5% of the IMD-side depth for a full-range position at the current price), while the spend grows with every swap. Nothing reduces the spend when it does not fit: the batch does not scale down, does not retry a smaller fraction, and the revert leaves accruedFees unchanged, so the next attempt faces the same or a larger budget. Because keepers earn nothing (README), fees routinely accumulate for hours or days before anyone calls executeBatch, and batches are also refused during any hour in which the token trends up by more than 300 ticks, which is exactly when volume (and so fee accrual) is highest. Once accruedFees/4 x 0.99 exceeds the absorbable amount, the buyback-and-burn mechanic halts indefinitely: it resumes only if third parties add liquidity or the spot drifts favourably against the TWAP by more than the excess impact within one hour. The same deadlock is reached earlier if LPs withdraw depth (nothing locks liquidity). The brief asks the batch to spend 'up to 25%'; spending the amount that fits inside the slippage bound (a partial fill up to the terminal limit still executes every unit at a price no worse than the limit, so the 300 bps guarantee is kept) or halving the spend on a slippage failure would satisfy it without changing any constant.","line":180,"path":"src/SIMDTESTHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SIMDTEST} from \"src/SIMDTEST.sol\";\nimport {SIMDTESTHook} from \"src/SIMDTESTHook.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {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 {StateLibrary} from \"v4-core/src/libraries/StateLibrary.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\ncontract ProofIMD is ERC20 {\n    constructor() ERC20(\"Identity\", \"IMD\") {}\n\n    function mint(address to, uint256 amount) external {\n        _mint(to, amount);\n    }\n}\n\n/// @notice The batch budget is a fixed quarter of a balance that only a successful batch can lower.\n/// Once that quarter exceeds the IMD the pool can absorb inside the 300-tick terminal limit, every\n/// batch reverts with SlippageExceeded, and trading only enlarges the budget. Nobody is paid to call\n/// executeBatch, so fees accumulating for a day before the first call is an ordinary launch.\n/// Fails on the current code (the batch never executes); passes once the batch sizes its spend\n/// to what the pool can fill inside the limit (or otherwise lets a smaller spend proceed).\ncontract ProofBatchDeadlockTest is Test, IUnlockCallback {\n    using StateLibrary for IPoolManager;\n\n    address constant IMD = 0xD34a99Bc0f67aE1bbd63C660e6d0b0dd03E263B7;\n    uint160 constant Q96 = 79228162514264337593543950336;\n\n    IPoolManager manager;\n    SIMDTEST token;\n    SIMDTESTHook hook;\n    PoolKey key;\n\n    function setUp() public {\n        vm.warp(10_000);\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        vm.etch(IMD, address(new ProofIMD()).code);\n        ProofIMD(IMD).mint(address(this), 2_000_000_000 ether);\n        address tokenAddress = address(uint160(IMD) + 1);\n        deployCodeTo(\"SIMDTEST.sol:SIMDTEST\", tokenAddress);\n        token = SIMDTEST(tokenAddress);\n\n        bytes32 initHash =\n            keccak256(abi.encodePacked(type(SIMDTESTHook).creationCode, abi.encode(manager, address(token))));\n        bytes32 salt;\n        for (uint256 i;; i++) {\n            address predicted =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), initHash)))));\n            if (uint160(predicted) & ((1 << 14) - 1) == 0x10cc) {\n                salt = bytes32(i);\n                break;\n            }\n        }\n        hook = new SIMDTESTHook{salt: salt}(manager, address(token));\n        key = hook.poolKey();\n        manager.initialize(key, Q96);\n        // 10M IMD / 10M SIMDTEST of full-range depth, price 1:1.\n        manager.unlock(abi.encode(uint8(0), abi.encode(ModifyLiquidityParams(-887220, 887220, 10_000_000 ether, 0))));\n    }\n\n    function test_batchStillExecutesAfterADayOfVolumeWithoutAKeeper() public {\n        // Twelve rounds: buy 5M IMD of SIMDTEST, sell it all back. About 120M IMD of volume on a\n        // 10M IMD-deep pool, price back near the start. No one calls executeBatch meanwhile.\n        for (uint256 i; i < 12; i++) {\n            vm.warp(block.timestamp + 600);\n            BalanceDelta d = _swap(true, 5_000_000 ether);\n            vm.warp(block.timestamp + 600);\n            _swap(false, uint256(int256(d.amount1())));\n        }\n        // Two quiet hours: the one-hour mean tick equals the spot tick exactly.\n        vm.warp(block.timestamp + 2 hours);\n        (, int24 spot,,) = manager.getSlot0(hook.poolId());\n        assertEq(hook.consult(), spot, \"reference must equal spot for this proof\");\n        assertGt(hook.accruedFees(), hook.MIN_ACCRUED_FEES());\n\n        // A calm pool, reference == spot, more than the minimum accrued, cooldown long over:\n        // the brief's buyback must be able to spend \"up to 25%\" here.\n        (uint256 spent, uint256 burned) = hook.executeBatch();\n        assertGt(burned, 0);\n        assertLe(spent, hook.totalFeesAccrued() / 4);\n    }\n\n    function _swap(bool buy, uint256 exactIn) internal returns (BalanceDelta) {\n        bool zeroForOne = buy == hook.imdIsCurrency0();\n        SwapParams memory p =\n            SwapParams(zeroForOne, -int256(exactIn), zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1);\n        return abi.decode(manager.unlock(abi.encode(uint8(1), abi.encode(p))), (BalanceDelta));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (uint8 action, bytes memory payload) = abi.decode(data, (uint8, bytes));\n        BalanceDelta delta;\n        if (action == 0) {\n            (delta,) = manager.modifyLiquidity(key, abi.decode(payload, (ModifyLiquidityParams)), \"\");\n        } else {\n            delta = manager.swap(key, abi.decode(payload, (SwapParams)), \"\");\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) private {\n        if (delta < 0) {\n            manager.sync(currency);\n            ERC20(Currency.unwrap(currency)).transfer(address(manager), uint256(-int256(delta)));\n            manager.settle();\n        } else if (delta > 0) {\n            manager.take(currency, address(this), uint128(delta));\n        }\n    }\n}","reproduction":"Local PoolManager, SIMDTEST/IMD pool at 1:1 with 10,000,000 ether of full-range liquidity (about 10M IMD and 10M SIMDTEST of depth), no keeper calls during the day. (1) Twelve rounds, 10 minutes apart: exact-input buy of 5,000,000 IMD, then exact-input sell of all tokens received (about 120M IMD volume, price returns to tick -973). (2) Warp 2 hours with no trades so consult() == spot tick (-973). State: accruedFees = 1,181,593 IMD (> MIN_ACCRUED_FEES), budget = 295,398 IMD, spent = 292,444 IMD. The IMD the pool can take before sqrtPrice reaches getSqrtPriceAtTick(ref - 300) is L*(2^96/limit - 2^96/sqrtP) = 159,055 IMD. (3) executeBatch() -> expected: a batch spending at most 25% executes on a calm pool with reference == spot; actual: revert SlippageExceeded (pool stops at the limit, inputDelta != -amount). (4) For the next 7 days, every hour: a 1,000 IMD buy and sell-back, then executeBatch(): all 168 calls revert; accruedFees rises to 1,184,895 IMD; totalIMDSpent stays 0 and nothing is ever burned. Run with the harness-based probe test/scratch/Probe.t.sol::test_probe_budgetExceedsDepthDeadlock or the self-contained proof below (fails now with SlippageExceeded).","severity":"medium","snippet":"        uint256 budget = accruedFees / 4;\n        // Keep the PoolManager swap amount safely within its signed delta domain.\n        if (budget > uint256(uint128(type(int128).max))) budget = uint256(uint128(type(int128).max));","title":"executeBatch budget is a fixed quarter of a balance only a batch can lower: once it exceeds the depth inside the 300-tick limit, every batch reverts and fees are stuck"},{"citation":"resolved","description":"Seam: boundary x invariant. The brief's invariant is a fee of 1% of each swap's IMD leg. For the two swaps where IMD is the specified currency (exact-input buy, exact-output sell) the fee is fixed in beforeSwap from params.amountSpecified before the pool runs, and v4 cannot revise a specified-side hook delta afterwards. Any swap that fills partially because of the caller's sqrtPriceLimitX96 therefore pays 1% (or 1/99) of the request, however little IMD actually moves. In the exact-output sell case the seller's IMD delta is pool output minus the reserved fee and turns negative, so the seller delivers tokens and pays IMD. The README and test_priceLimitedExactOutputSellPaysFeeOnTheRequest document this as an accepted trap and tell routers to size requests to the intended fill; it is reported here because the deviation from '1% on every swap' is unbounded in relative terms and no on-chain guard (for example a maximum ratio of fee to actual IMD delta, or charging only when the fill is complete) limits it. With the IMD-only fee constraint the only in-contract remedies change the design (fee from the token side for exact-output sells, or reverting partial specified fills, which the brief forbids), so this is left to the author as a documented decision rather than a required fix.","line":140,"path":"src/SIMDTESTHook.sol","reproduction":"Local pool at 1:1 with 10,000,000 ether full-range liquidity. Exact-output sell: swap(zeroForOne = !imdIsCurrency0, amountSpecified = +100,000e18 IMD, sqrtPriceLimitX96 = getSqrtPriceAtTick(imdIsCurrency0 ? 1 : -1)). Expected (brief): the seller receives IMD and pays a 1% fee on the IMD leg. Actual: hook fee reserved = 100,000e18/99 = 1,010.101 IMD (accruedFees); pool output = 499.96 IMD; seller IMD delta = -510.14 IMD (the seller pays IMD) and token delta = -506.32 SIMDTEST; the fee is 202% of the IMD the pool produced. Exact-input buy with the same tight limit: swap(zeroForOne = imdIsCurrency0, amountSpecified = -100,000e18, limit = getSqrtPriceAtTick(imdIsCurrency0 ? -1 : 1)): fee = 1,000 IMD while the pool only priced 1,012.6 IMD (buyer IMD delta -2,012.6, token delta +999.95), a 99% fee on the executed amount. See test/scratch/Probe.t.sol::test_probe_partialFillFee for the logged values.","severity":"low","snippet":"        uint256 fee = exactInput ? magnitude / 100 : magnitude / 99;","title":"Specified-side fee is 1% of the requested amount, not of the IMD actually swapped: a price-limited exact-output sell pays a fee larger than the pool's whole IMD output and nets the seller negative IMD"},{"citation":"resolved","description":"Math Precision: fees should round up; here beforeSwap uses magnitude/100 and magnitude/99 and afterSwap uses magnitude/100, all floored, so each swap under-collects by up to 1 wei and swaps whose IMD leg is below 100 wei (99 wei for an exact-output sell) pay nothing. With 18-decimal IMD the leak is at most 1e-18 IMD per swap and a fee-free swap moves under 1e-16 IMD, so there is no economic impact; recorded so the rounding direction is a conscious choice. The suite's testFuzz_amountsBelowTheRoundingThresholdPayNothingAndStillSettle pins the current behaviour.","line":158,"path":"src/SIMDTESTHook.sol","reproduction":"Exact-input buy with amountSpecified = -99: fee = 99/100 = 0, accruedFees stays 0, the swap settles. Exact-output sell with amountSpecified = +98: fee = 98/99 = 0. Exact-input sell whose pool IMD output is 199 wei: fee = 199/100 = 1 wei instead of the rounded-up 2 wei.","severity":"info","snippet":"        uint256 fee = magnitude / 100;","title":"All hook fees round down and truncate to zero below 100 wei of IMD (99 wei for exact-output sells)"}],"hash":"e64e0a82539d8cbc89e4b0016f98208070cef92172ee95f7ab395c00fff377c8","nodeId":"5768cc32-b532-43af-a423-10a4842f1e19","outcome":"completed","summary":"The review is complete. The findings file is at `.imd-findings.json` with three findings and twelve coverage rows, and no tracked file was changed.\n\n**Outcome.** The arithmetic in this hook is largely sound. The fee formulas, the saturating casts, the exact cumulative-tick interpolation in the oracle, the 512-bit quote math in both currency orders, and the relationship between the 300-tick terminal limit and the 97% output floor all hold under the Math Precision, Boundary and Numerical Gap checks. The suite of 90 local tests passes. I found one defect worth fixing and two documented behaviours worth a conscious decision.\n\n**Findings**\n\n- **Medium, batch deadlock against pool depth.** The spend is always a quarter of the accrued balance, the batch refuses any partial fill, and nothing lowers the budget when it does not fit inside the 300-tick band. With no keeper bounty, fees accumulate before anyone calls. In the reproduction a day of two-way volume on a 10M IMD-deep pool leaves 1.18M IMD accrued, a 292k IMD spend, and only 159k IMD absorbable inside the limit. Every batch for the following week reverts with `SlippageExceeded` and nothing is ever burned. A self-contained proof under `test/scratch/` fails on the current code. Spending what fits inside the limit would keep the 300 bps guarantee and satisfy \"up to 25%\".\n- **Low, specified-side fee charged on the request.** A price-limited exact-output sell of 100,000 IMD reserves 1,010 IMD of fee against 500 IMD of pool output, so the seller pays 510 IMD net while also delivering tokens. The README documents this and no IMD-only remedy exists inside v4, so it is recorded as a design decision for the author.\n- **Info, fees floor to zero below 100 wei.** No economic impact at 18 decimals.\n\n**Coverage.** All eight entry points have a verdict. The only unreached item is the deployed IMD token's behaviour on a mainnet fork, which needs network access this environment lacks. The hook never transfers IMD directly, so that gap affects router settlement rather than the hook's own math.","treeHash":null,"usage":{"cachedInputTokens":1348876,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":51496,"runtime":"claude","turns":31,"wallClockMs":729274}},{"artifacts":[],"attempt":1,"bundleHash":"663d1cbffae29e5d5bbcdc97e681c247a0bbc5d94d33c7b1c75f9214eed93f10","device":"a406deaac63a93b0","findings":[],"hash":"eb091b446f320e3e63d5d2645d321ca3bc8f49107df9389169c3238010caf250","nodeId":"51641a9e-3ca6-4ce4-bc33-98d10399087c","outcome":"completed","summary":"Both findings are answered and the medium one is fixed. The reviewer's proof now passes, and the launch manifest was left as the accepted `univ4_hook` version.\n\n**Medium finding, fixed.** The proof reproduced on the starting tree with `SlippageExceeded`. The cause was the output check in `executeBatch()` quoting the gross budget while the swap spends 99% of it and the pool prices that net of the 1.25% LP fee, leaving only about 78 ticks of drift tolerance. The minimum output is now 97% of the reference quote for the IMD the pool actually prices (spent, net of the LP fee), so slippage is measured on the executed price against the TWAP and neither fee eats into the 300 basis points. The 300-tick terminal price bound is unchanged. In `test/Hook.t.sol` the test that asserted the old behaviour was replaced by one that executes a batch with 100 to 300 ticks of adverse drift and one that still refuses a batch just past 300 ticks, in both currency orderings.\n\n**Low finding, disputed as by design.** The exact-output sell partial-fill case reproduces exactly as described, but v4 offers no way to revise a specified-side hook delta after the pool runs, and the fee must come from IMD only. No code changed. A new test pins the behaviour, and the README partial-fill section now calls out the exact-output sell case and the router guidance.\n\n**Docs and manifest.** README parameter table and batch section, the `launch.json` notes sentence about fees counting within the tolerance, and `VALIDATION.md` were updated. Responses are in `.imd-responses.json`.\n\nVerification results:\n\n| Check | Result |\n| --- | --- |\n| Reviewer proof on revised tree | passes |\n| `forge test` under `env -i` | 43 passed, 1 fork test skipped |\n| Pinned protected hook and token checks | 11 passed |\n| `forge fmt --check`, `forge build`, manifest script | pass |\n\nStill owed, as before: a live mainnet fork run and an independent adversarial review before a funded launch.","treeHash":"698f291a09b141939fd26dfc39ad5f65fd729a04","usage":{"cachedInputTokens":1179533,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":24598,"runtime":"claude","turns":31,"wallClockMs":393320}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"fa2b0b9c7ebc1547","findings":[{"citation":"resolved","description":"Execution trace, beforeSwap -> Pool.swap -> afterSwap. When IMD is the specified currency (exact-input buy, exact-output sell) the hook computes the fee in beforeSwap from params.amountSpecified, mints the matching ERC-6909 claim immediately, and returns it as the specified-side BeforeSwapDelta. Pool.swap then runs with the caller's sqrtPriceLimitX96 and may stop early (partial fill). afterSwap returns 0 for these swaps and never compares the reserved fee with the IMD the pool actually moved, and v4 offers no way to revise the specified-side delta after the pool ran. The brief specifies 'a fixed additional fee of 1% on every swap'; on a partial fill the fee is 1% (buy) or 1.0101% (sell) of the request, which can be any multiple of the executed amount. In the exact-output-sell case the reserved fee exceeds the pool's gross output, so the swapper's IMD delta turns negative: they deliver tokens and also pay IMD, receiving nothing. The repository README and test_priceLimitedExactOutputSellPaysFeeOnTheRequest document this as a known trap and shift the burden to routers; it is reported here because it is a concrete deviation from the requested fee rule that any integrator who uses sqrtPriceLimitX96 as a protective bound (the Uniswap v3 SwapRouter convention) will hit, and because a partial mitigation exists: in afterSwap, for IMD-specified swaps, recompute the fee from the actual IMD leg of the swap delta, burn the over-reserved claim and credit the excess back to `sender` (poolManager.mint of an IMD claim to the router, or poolManager.take when the manager holds the balance), or at minimum make the gross-up visible to routers by exposing a quote helper. The default v4 periphery router does not set price limits, which is why this is low rather than medium.","line":140,"path":"src/SIMDTESTHook.sol","reproduction":"Local PoolManager, IMD is currency0 (test/helpers/PoolHarness.sol _localSetup(true)), pool initialized at sqrtPriceX96 = 2^96 (tick 0) with 10,000,000e18 full-range liquidity. Case A, exact-output sell with a 1-tick protective limit: unlock and call manager.swap(key, SwapParams({zeroForOne: false, amountSpecified: +100_000e18, sqrtPriceLimitX96: TickMath.getSqrtPriceAtTick(1)}), \"\"). Expected under a 1%-of-executed rule: seller gives ~506 tokens, receives ~495 IMD net of a ~5 IMD fee. Actual: hook.accruedFees() == 1010101010101010101010 (100_000e18/99), the pool only produces ~500 IMD before the limit, and the returned BalanceDelta is amount0 (IMD) = -510138506976283513903 and amount1 (token) = -506316456329074369859: the seller pays 510.14 IMD and 506.32 tokens and receives nothing. Case B, exact-input buy with a limit one unit below spot: read (spot,,,) = manager.getSlot0(hook.poolId()) and call manager.swap(key, SwapParams({zeroForOne: true, amountSpecified: -100_000e18, sqrtPriceLimitX96: spot - 1}), \"\"). Expected: a fee of 1% of the (near-zero) fill. Actual: hook.accruedFees() == 1000e18, buyer's IMD delta = -1000000000000000000002 and token delta = 0, i.e. 1000 IMD paid for no tokens. Both sequences run in test/scratch (removed) and the second mirrors test_priceLimitedExactOutputSellPaysFeeOnTheRequest in test/Hook.t.sol.","severity":"low","snippet":"        uint256 fee = exactInput ? magnitude / 100 : magnitude / 99;\n        if (fee > uint256(uint128(type(int128).max))) fee = uint256(uint128(type(int128).max));\n        _accrue(fee);\n        return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);","title":"IMD-specified swaps pay the 1% fee on the requested amount, so a price-limited partial fill pays far more than 1% of the executed trade and an exact-output seller can end up paying IMD"},{"citation":"resolved","description":"Periphery x first principles. The batch's minimum output is derived from the budget net of a compile-time POOL_FEE of 12500 pips, on the stated premise (README 'Batches and reference price', and the inline comment) that a full fill inside the 300-tick terminal limit always satisfies the 97% check because 1.0001^-300 = 0.97045 > 0.97. That margin is only 0.045%. The vendored and mainnet PoolManager let the protocol fee controller set a protocol fee of up to 1000 pips (0.1%) per direction on any pool (ProtocolFees.setProtocolFee, ProtocolFeeLibrary.MAX_PROTOCOL_FEE); Pool.swap then charges swapFee = calculateSwapFee(protocolFee, lpFee) = 13487 pips instead of 12500, which lowers the pool's priced input by ~0.1% and erases the margin. The hook reads neither slot0.protocolFee nor slot0.lpFee here, so once such a fee is set, every batch whose spot sits roughly 295-300 ticks adverse of the reference reverts with SlippageExceeded even though the swap would have filled completely inside the terminal limit the brief's 300 bps tolerance is meant to express. Impact is a spurious refusal (cooldown is not consumed, fees are not lost), not a loss, and it needs a Uniswap-governance action on this pool, so it is low. Fix: compute `priced` from the actual swap fee, e.g. read (, , protocolFee, lpFee) = poolManager.getSlot0(poolId), take the direction's protocol fee with ProtocolFeeLibrary.getZeroForOneFee/getOneForZeroFee and use ProtocolFeeLibrary.calculateSwapFee(protocolFee, lpFee) in place of POOL_FEE; or drop the duplicate floor and rely on the terminal limit alone, updating the README claim either way.","line":189,"path":"src/SIMDTESTHook.sol","reproduction":"Local PoolManager owned by the test (new PoolManager(address(this))), IMD is currency0, pool at tick 0 with 10,000,000e18 full-range liquidity (PoolHarness._localSetup(true)). 1) Exact-input buy of 50_000e18 IMD at initialization time. 2) vm.warp(initialTime + 1 hours); ref = hook.consult(). 3) Exact-input buy of 153496442906543961726127 IMD wei, which leaves spot 296 ticks adverse of ref (manager.getSlot0 tick = ref - 296; hook.consult() is unchanged in the same block). 4) hook.executeBatch() with protocolFee == 0: succeeds, spent = 503653696193696305272, burned = 478120763737054493234 against minimumOut = 477732724075093865984. 5) Instead, before step 4: manager.setProtocolFeeController(address(this)); manager.setProtocolFee(key, uint24(1000) | (uint24(1000) << 12)) (getSlot0 now reports protocolFee 4097000, lpFee 12500). hook.executeBatch() now reverts with SlippageExceeded although spot is 296 < 300 ticks adverse and the swap fills completely inside the terminal limit; the pool's output for the same input drops to ~477.6 tokens, below the 477.73 floor computed with POOL_FEE. Expected: the batch executes whenever the full fill stays inside the 300-tick limit, as the README states.","severity":"low","snippet":"        uint256 priced = spent - FullMath.mulDiv(spent, POOL_FEE, 1_000_000);\n        uint256 minimumOut = FullMath.mulDiv(quoteAtTick(referenceTick, priced), 10_000 - MAX_SLIPPAGE_BPS, 10_000);","title":"executeBatch's 97%-of-reference floor hardcodes the 1.25% LP fee and ignores the PoolManager protocol fee, so with a governance-set protocol fee batches inside the 300-tick terminal limit are refused"}],"hash":"f1259d898dd07b36737b3a4ad5e212f25f88e670c7946d2c3050b6c943791459","nodeId":"cffba57a-1ae8-42a3-919b-51e7daa31cd6","outcome":"completed","summary":"Review complete. The findings file is written at the repository root and no project files were changed.\n\n**Outcome: two low-severity findings, no critical/high/medium.** The hook's control flow holds up under the Execution Trace, Periphery and First Principles guides. I traced every entry point the verifier listed, both currency orderings, all four swap modes, the batch's unlock path, and the ring-buffer oracle, and cross-checked each against the vendored v4-core swap, fee and settlement code. The existing suite passes (90 tests, 2 fork tests skipped offline), and the manifest validates.\n\n**Finding 1 (low), `beforeSwap`, line 140.** When IMD is the specified currency the 1% fee is fixed on the requested amount before the pool runs and never reconciled afterwards. With a protective price limit and a partial fill, the fee can be any multiple of the executed trade. Reproduced on a tick-0 pool: an exact-output sell of 100,000 IMD with a one-tick limit charges 1,010 IMD of fee, and the seller ends up paying 510 IMD plus 506 tokens for nothing. An exact-input buy with a limit one unit below spot pays 1,000 IMD for zero tokens. The author documents this as a known trap. I kept it low because the standard v4 router sets no price limit and v4 cannot revise the specified-side delta, but I described a claim-refund mitigation in `afterSwap`.\n\n**Finding 2 (low), `executeBatch`, line 189.** The 97%-of-reference floor hardcodes the 1.25% LP fee. Uniswap governance can set a protocol fee of up to 0.1% on the pool, which erases the 0.045% margin between the 300-tick terminal limit and the 97% check. Reproduced: with spot 296 ticks adverse the batch executes at zero protocol fee and reverts with `SlippageExceeded` at the maximum protocol fee, contradicting the README claim that the floor is only a backstop. No funds are lost and the cooldown is not consumed.\n\n**Coverage.** All 8 listed entry points have rows: 6 hold, `beforeSwap` and `executeBatch` carry the findings. I added rows for the claims-conservation invariant, the TWAP invariant and the manifest. Nothing was left unreached. Areas I did not independently cover in depth: the economic manipulation surface of the TWAP (another specialist's lane, though the sandwich tests pass) and the live-mainnet IMD token behaviour, which only the fork run exercises.","treeHash":null,"usage":{"cachedInputTokens":2320658,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":60085,"runtime":"claude","turns":42,"wallClockMs":782974}}],"verification":[{"checks":[{"durationMs":4444,"exitCode":0,"name":"build","output":"Compiling 91 files with Solc 0.8.26\nSolc 0.8.26 finished in 4.31s\nCompiler run successful!\nwarning[missing-events-arithmetic]: `lastBatchAt` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:115:9\n    │\n115 │         lastBatchAt = block.timestamp;\n    │         ━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastTick` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:116:9\n    │\n116 │         lastTick = tick;\n    │         ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `observationIndex` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:231:13\n    │\n231 │             observationIndex = next;\n    │             ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `observationCount` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:232:58\n    │\n232 │             if (observationCount < OBSERVATION_CAPACITY) observationCount++;\n    │                                                          ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastTick` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:234:9\n    │\n234 │         lastTick = tick;\n    │         ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTHook.sol:74:40\n   │\n74 │     constructor(IPoolManager manager_, address token_) {\n   │                                        ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:117:39\n    │\n117 │         observations[0] = Observation(uint64(block.timestamp), 0);\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/SIMDTESTHook.sol:136:38\n    │\n136 │             magnitude = exactInput ? uint256(-(params.amountSpecified + 1)) + 1 : 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/SIMDTESTHook.sol:136:83\n    │\n136 │             magnitude = exactInput ? uint256(-(params.amountSpecified + 1)) + 1 : 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/SIMDTESTHook.sol:141:19\n    │\n141 │         if (fee > uint256(uint128(type(int128).max))) fee = 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/SIMDTESTHook.sol:141:27\n    │\n141 │         if (fee > uint256(uint128(type(int128).max))) fee = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:141:61\n    │\n141 │         if (fee > uint256(uint128(type(int128).max))) fee = 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/SIMDTESTHook.sol:141:69\n    │\n141 │         if (fee > uint256(uint128(type(int128).max))) fee = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:143:63\n    │\n143 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\n    │                                                               ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/SIMDTESTHook.sol:143:70\n    │\n143 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\n    │                                                                      ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:157:29\n    │\n157 │         uint256 magnitude = uint256(imdDelta < 0 ? -imdDelta : imdDelta);\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/SIMDTESTHook.sol:160:44\n    │\n160 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/SIMDTESTHook.sol:160:51\n    │\n160 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                                   ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:168:9\n    │\n168 │         emit FeesAccrued(fee);\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `executing` is updated\n    ╭▸ src/SIMDTESTHook.sol:200:29\n    │\n200 │         burned = abi.decode(poolManager.unlock(abi.encode(spent, minimumOut, limit)), (uint256));\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:177:13\n    │\n177 │         if (block.timestamp < lastBatchAt + COOLDOWN) revert CooldownActive();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:203:9\n    │\n203 │         emit BatchExecuted(budget, spent, burned, referenceTick);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:182:22\n    │\n182 │         if (budget > uint256(uint128(type(int128).max))) budget = 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/SIMDTESTHook.sol:182:30\n    │\n182 │         if (budget > uint256(uint128(type(int128).max))) budget = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:182:67\n    │\n182 │         if (budget > uint256(uint128(type(int128).max))) budget = 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/SIMDTESTHook.sol:182:75\n    │\n182 │         if (budget > uint256(uint128(type(int128).max))) budget = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:209:86\n    │\n209 │         BalanceDelta delta = poolManager.swap(poolKey(), SwapParams(imdIsCurrency0, -int256(amount), limit), bytes(\"\"));\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/SIMDTESTHook.sol:213:28\n    │\n213 │         if (inputDelta != -int256(amount) || outputDelta < int256(minimumOut)) revert SlippageExceeded();\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/SIMDTESTHook.sol:213:60\n    │\n213 │         if (inputDelta != -int256(amount) || outputDelta < int256(minimumOut)) revert SlippageExceeded();\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/SIMDTESTHook.sol:215:26\n    │\n215 │         uint256 output = uint256(outputDelta);\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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:226:13\n    │\n226 │         if (block.timestamp > latest.timestamp) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:228:41\n    │\n228 │                 latest.tickCumulative + int128(int256(lastTick) * int256(block.timestamp - latest.timestamp));\n    │                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/SIMDTESTHook.sol:228:67\n    │\n228 │                 latest.tickCumulative + int128(int256(lastTick) * int256(block.timestamp - latest.timestamp));\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/SIMDTESTHook.sol:230:46\n    │\n230 │             observations[next] = Observation(uint64(block.timestamp), cumulative);\n    │                                              ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/SIMDTESTHook.sol:253:27\n    │\n253 │             while (high - low > 1) {\n    │                           ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:240:29\n    │\n240 │         if (!initialized || block.timestamp < TWAP_WINDOW) revert OracleNotReady();\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:243:13\n    │\n243 │         if (observations[oldest].timestamp > target) revert OracleNotReady();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:248:13\n    │\n248 │         if (target >= latest.timestamp) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:255:21\n    │\n255 │                 if (observations[(oldest + mid) % OBSERVATION_CAPACITY].timestamp <= target) low = mid;\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:246:64\n    │\n246 │             int256(latest.tickCumulative) + int256(lastTick) * int256(block.timestamp - latest.timestamp);\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/SIMDTESTHook.sol:249:81\n    │\n249 │             cumulativeThen = int256(latest.tickCumulative) + int256(lastTick) * int256(target - latest.timestamp);\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/SIMDTESTHook.sol:261:19\n    │\n261 │                 * int256(target - a.timestamp) / int256(uint256(b.timestamp - a.timestamp));\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/SIMDTESTHook.sol:264:29\n    │\n264 │         int256 mean = sum / int256(TWAP_WINDOW);\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/SIMDTESTHook.sol:265:30\n    │\n265 │         if (sum < 0 && sum % int256(TWAP_WINDOW) != 0) mean--;\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/SIMDTESTHook.sol:266:16\n    │\n266 │         return int24(mean);\n    │                ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:110:38\n    │\n110 │         assertEq(hook.lastBatchAt(), block.timestamp);\n    │                                      ━━━━━━━━━━━━━━━\n    ‡\n115 │         vm.warp(block.timestamp + 3599);\n    │         ─────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:115:17\n    │\n115 │         vm.warp(block.timestamp + 3599);\n    │         ────────━━━━━━━━━━━━━━━──────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:167:21\n    │\n167 │             vm.warp(block.timestamp + 300);\n    │             ────────━━━━━━━━━━━━━━━─────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:277:21\n    │\n277 │             vm.warp(block.timestamp + 12);\n    │             ────────━━━━━━━━━━━━━━━────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:316:21\n    │\n316 │             vm.warp(block.timestamp + 600);\n    │             ────────━━━━━━━━━━━━━━━─────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/HookAdversarial.t.sol:140:21\n    │\n140 │             vm.warp(block.timestamp + 3);\n    │             ────────━━━━━━━━━━━━━━━───── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/HookAdversarial.t.sol:277:17\n    │\n277 │         vm.warp(block.timestamp + 1800);\n    │         ────────━━━━━━━━━━━━━━━──────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/HookAdversarial.t.sol:280:21\n    │\n280 │             vm.warp(block.timestamp + 300);\n    │             ────────━━━━━━━━━━━━━━━─────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/HookAdversarial.t.sol:439:21\n    │\n439 │             vm.warp(block.timestamp + seed % 1801);\n    │             ────────━━━━━━━━━━━━━━━─────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/HookAdversarial.t.sol:441:30\n    │\n439 │             vm.warp(block.timestamp + seed % 1801);\n    │             ────────────────────────────────────── `vm.warp` changes this environment here\n440 │             _swap((seed >> 8) & 1 == 1, (seed >> 9) & 1 == 1, 100 ether + (seed >> 16) % 50_000 ether);\n441 │             if (ts[n - 1] == block.timestamp) {\n    │                              ━━━━━━━━━━━━━━━\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/HookAdversarial.t.sol:444:25\n    │\n439 │             vm.warp(block.timestamp + seed % 1801);\n    │             ────────────────────────────────────── `vm.warp` changes this environment here\n    ‡\n444 │                 ts[n] = block.timestamp;\n    │                         ━━━━━━━━━━━━━━━\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/HookInvariant.t.sol:85:17\n   │\n85 │         vm.warp(block.timestamp + bound(elapsed, 0, 5400));\n   │         ────────━━━━━━━━━━━━━━━─────────────────────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/MainnetForkAdversarial.t.sol:150:17\n    │\n150 │         vm.warp(block.timestamp + 1 hours);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":1378,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 286.87µs (0.00ns CPU time)\n\nRan 1 test for test/MainnetForkAdversarial.t.sol:MainnetForkAdversarialTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 467.54µs (0.00ns CPU time)\n\nRan 1 test for test/Invariant.t.sol:FeeInvariantTest\n[PASS] invariant_feeClaimsAndBurnAccountingAreConserved() (runs: 64, calls: 2048, reverts: 0)\n\n╭------------------+----------+-------+---------+----------╮\n| Contract         | Selector | Calls | Reverts | Discards |\n+==========================================================+\n| FeeInvariantTest | batch    | 1054  | 0       | 0        |\n|------------------+----------+-------+---------+----------|\n| FeeInvariantTest | trade    | 994   | 0       | 0        |\n╰------------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 415.54ms (397.94ms CPU time)\n\nRan 18 tests for test/Hook.t.sol:HookIMDSecondTest\n[PASS] testFuzz_allSwapModesAccrueOnlyIMD(bool,bool,uint256) (runs: 1000, μ: 393433, ~: 392754)\n[PASS] testFuzz_swapBatchSequenceConservesClaims(uint256) (runs: 1000, μ: 3934469, ~: 3934680)\n[PASS] test_batchJustBeyondToleranceIsRejected() (gas: 644963)\n[PASS] test_batchPriceImpactAndPartialFillRollBackEverything() (gas: 658607)\n[PASS] test_batchWithinToleranceExecutesAgainstReferenceNetOfFees() (gas: 3044616)\n[PASS] test_cooldownNeverRestrictsTrading() (gas: 903003)\n[PASS] test_everyCallbackRejectsUnauthorizedCaller() (gas: 152162)\n[PASS] test_executeBatchBurnsWithinBudgetAndEnforcesCooldown() (gas: 895068)\n[PASS] test_freshManagerWithOnlyLaunchTokenLiquidityCanCollectFees() (gas: 689956)\n[PASS] test_nestedBatchCannotUnlockManagerOrLoseFees() (gas: 885220)\n[PASS] test_noFundsAndThresholdAreRejected() (gas: 790906)\n[PASS] test_oracleRingWrapsWithoutAffectingSwaps() (gas: 208750678)\n[PASS] test_oracleWeightsElapsedTimeAndRoundsNegativeDown() (gas: 574542)\n[PASS] test_permissionsStaticFeeAndDirectDeployment() (gas: 2902527)\n[PASS] test_priceLimitedExactOutputSellPaysFeeOnTheRequest() (gas: 373964)\n[PASS] test_priceLimitedSpecifiedFeeIsReservedUpfront() (gas: 351777)\n[PASS] test_rejectsForeignPoolAtInitialization() (gas: 135993)\n[PASS] test_spotManipulationCannotRewriteTWAPOrSpendFees() (gas: 648543)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 821.06ms (1.01s CPU time)\n\nRan 18 tests for test/Hook.t.sol:HookIMDFirstTest\n[PASS] testFuzz_allSwapModesAccrueOnlyIMD(bool,bool,uint256) (runs: 1000, μ: 393842, ~: 396628)\n[PASS] testFuzz_swapBatchSequenceConservesClaims(uint256) (runs: 1000, μ: 3935591, ~: 3935434)\n[PASS] test_batchJustBeyondToleranceIsRejected() (gas: 651650)\n[PASS] test_batchPriceImpactAndPartialFillRollBackEverything() (gas: 664903)\n[PASS] test_batchWithinToleranceExecutesAgainstReferenceNetOfFees() (gas: 3055158)\n[PASS] test_cooldownNeverRestrictsTrading() (gas: 908931)\n[PASS] test_everyCallbackRejectsUnauthorizedCaller() (gas: 152162)\n[PASS] test_executeBatchBurnsWithinBudgetAndEnforcesCooldown() (gas: 902322)\n[PASS] test_freshManagerWithOnlyLaunchTokenLiquidityCanCollectFees() (gas: 702042)\n[PASS] test_nestedBatchCannotUnlockManagerOrLoseFees() (gas: 890867)\n[PASS] test_noFundsAndThresholdAreRejected() (gas: 798226)\n[PASS] test_oracleRingWrapsWithoutAffectingSwaps() (gas: 208771209)\n[PASS] test_oracleWeightsElapsedTimeAndRoundsNegativeDown() (gas: 581102)\n[PASS] test_permissionsStaticFeeAndDirectDeployment() (gas: 2902527)\n[PASS] test_priceLimitedExactOutputSellPaysFeeOnTheRequest() (gas: 368033)\n[PASS] test_priceLimitedSpecifiedFeeIsReservedUpfront() (gas: 357779)\n[PASS] test_rejectsForeignPoolAtInitialization() (gas: 135993)\n[PASS] test_spotManipulationCannotRewriteTWAPOrSpendFees() (gas: 655308)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 836.66ms (1.04s CPU time)\n\nRan 23 tests for test/HookAdversarial.t.sol:HookAdversarialIMDFirstTest\n[PASS] testFuzz_adverseDriftExecutesInsideTheToleranceAndIsRefusedBeyondIt(uint256) (runs: 1000, μ: 769063, ~: 812907)\n[PASS] testFuzz_amountsBelowTheRoundingThresholdPayNothingAndStillSettle(uint256,bool) (runs: 1000, μ: 284964, ~: 280973)\n[PASS] testFuzz_batchRespectsBudgetSlippageAndBurnsOnlyToDead(uint256,uint256) (runs: 1000, μ: 645590, ~: 645795)\n[PASS] testFuzz_exactOutputSellGrossesUpSoTheFeeIsOnePercentOfPoolOutput(uint256) (runs: 1000, μ: 346749, ~: 347770)\n[PASS] testFuzz_quoteAtTickMatchesSqrtPriceArithmetic(int24,uint256) (runs: 1000, μ: 20962, ~: 20840)\n[PASS] testFuzz_sandwichingTheBatchLosesIMD(uint256) (runs: 1000, μ: 879607, ~: 941019)\n[PASS] testFuzz_twapMatchesAnIndependentReferenceOverRandomSchedules(uint256) (runs: 1000, μ: 1398028, ~: 1397686)\n[PASS] test_afterInitializeCannotBeReplayedEvenByTheManager() (gas: 49297)\n[PASS] test_batchEventsReportWhatWasSpentAndBurned() (gas: 502828)\n[PASS] test_constructorRefusesAnAddressWithoutTheMinedPermissionBits() (gas: 17408)\n[PASS] test_constructorRejectsCodelessArgumentsAndIMDAsLaunchToken() (gas: 7556)\n[PASS] test_enormousSpecifiedAmountsRevertInTheManagerWithoutAccruingAnything() (gas: 1834223)\n[PASS] test_factoryStyleCreate2DeployAndAtomicInitializeIsAccepted() (gas: 23507601)\n[PASS] test_favourableDriftLetsTheBatchBurnAboveTheTWAPQuote() (gas: 1828739)\n[PASS] test_giftedClaimsAndStrayTokensAreNeitherCountedNorSpent() (gas: 721401)\n[PASS] test_idleHourMeansOpeningTickAndBatchStillNeedsFees() (gas: 61357)\n[PASS] test_initializationRejectsAnotherTokenPairedWithTheHook() (gas: 684220)\n[PASS] test_insufficientFeesIsCheckedBeforeTheOracle() (gas: 319094)\n[PASS] test_liquidityAndPoolDonationsBypassTheHookEntirely() (gas: 920897)\n[PASS] test_oracleWithoutAFullHourRefusesBatchesButNotTrades() (gas: 222705096)\n[PASS] test_thresholdIsStrictlyGreaterThanOneWeiAbove() (gas: 792530)\n[PASS] test_unInitializedHookRefusesBatchesAndOracleReads() (gas: 7712940)\n[PASS] test_untrustedRouterPaysTheSameFeeAndTheKeeperEarnsNothing() (gas: 652491)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 836.63ms (4.59s CPU time)\n\nRan 23 tests for test/HookAdversarial.t.sol:HookAdversarialIMDSecondTest\n[PASS] testFuzz_adverseDriftExecutesInsideTheToleranceAndIsRefusedBeyondIt(uint256) (runs: 1000, μ: 761099, ~: 805386)\n[PASS] testFuzz_amountsBelowTheRoundingThresholdPayNothingAndStillSettle(uint256,bool) (runs: 1000, μ: 283763, ~: 280470)\n[PASS] testFuzz_batchRespectsBudgetSlippageAndBurnsOnlyToDead(uint256,uint256) (runs: 1000, μ: 638781, ~: 638980)\n[PASS] testFuzz_exactOutputSellGrossesUpSoTheFeeIsOnePercentOfPoolOutput(uint256) (runs: 1000, μ: 353040, ~: 352866)\n[PASS] testFuzz_quoteAtTickMatchesSqrtPriceArithmetic(int24,uint256) (runs: 1000, μ: 20985, ~: 20874)\n[PASS] testFuzz_sandwichingTheBatchLosesIMD(uint256) (runs: 1000, μ: 873601, ~: 934339)\n[PASS] testFuzz_twapMatchesAnIndependentReferenceOverRandomSchedules(uint256) (runs: 1000, μ: 1398171, ~: 1397590)\n[PASS] test_afterInitializeCannotBeReplayedEvenByTheManager() (gas: 49297)\n[PASS] test_batchEventsReportWhatWasSpentAndBurned() (gas: 495981)\n[PASS] test_constructorRefusesAnAddressWithoutTheMinedPermissionBits() (gas: 17408)\n[PASS] test_constructorRejectsCodelessArgumentsAndIMDAsLaunchToken() (gas: 7556)\n[PASS] test_enormousSpecifiedAmountsRevertInTheManagerWithoutAccruingAnything() (gas: 1833327)\n[PASS] test_factoryStyleCreate2DeployAndAtomicInitializeIsAccepted() (gas: 34696334)\n[PASS] test_favourableDriftLetsTheBatchBurnAboveTheTWAPQuote() (gas: 1823833)\n[PASS] test_giftedClaimsAndStrayTokensAreNeitherCountedNorSpent() (gas: 714621)\n[PASS] test_idleHourMeansOpeningTickAndBatchStillNeedsFees() (gas: 61368)\n[PASS] test_initializationRejectsAnotherTokenPairedWithTheHook() (gas: 684220)\n[PASS] test_insufficientFeesIsCheckedBeforeTheOracle() (gas: 312770)\n[PASS] test_liquidityAndPoolDonationsBypassTheHookEntirely() (gas: 914571)\n[PASS] test_oracleWithoutAFullHourRefusesBatchesButNotTrades() (gas: 222733608)\n[PASS] test_thresholdIsStrictlyGreaterThanOneWeiAbove() (gas: 785210)\n[PASS] test_unInitializedHookRefusesBatchesAndOracleReads() (gas: 8283186)\n[PASS] test_untrustedRouterPaysTheSameFeeAndTheKeeperEarnsNothing() (gas: 645691)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 836.67ms (5.41s CPU time)\n\nRan 6 tests for test/Token.t.sol:TokenTest\n[PASS] testFuzz_transferAndAllowanceDoNotTaxOrChangeSupply(uint256) (runs: 1000, μ: 136635, ~: 137051)\n[PASS] test_entirePolicySupplyBelongsToDeployer() (gas: 44079)\n[PASS] test_invalidTransferAndAllowanceFailWithoutStateChange() (gas: 64549)\n[PASS] test_noMintOrAdminSurfaceEvenForDeployer() (gas: 167859)\n[PASS] test_runtimeHasNoEscapeOpcodes() (gas: 464168)\n[PASS] test_transfersToPoolManagerHaveNoTax() (gas: 78880)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 836.71ms (837.99ms CPU time)\n\nRan 1 test for test/HookInvariant.t.sol:HookInvariantTest\n[PASS]\nHookInvariantTest invariants:\n[PASS] invariant_batchCadenceAndParametersAreImmutable\n[PASS] invariant_claimsEqualLedgerPlusGiftsAndAreBacked\n[PASS] invariant_everyPurchasedTokenIsBurnedAndSupplyIsFixed\n[PASS] invariant_feeLedgerIsConserved\n[PASS] invariant_hookHoldsNothingButStrays\n[PASS] invariant_noActionRevertsUnexpectedly\n HookInvariantTest invariants (runs: 128, calls: 6144, reverts: 0)\n\n╭-------------+------------------+-------+---------+----------╮\n| Contract    | Selector         | Calls | Reverts | Discards |\n+=============================================================+\n| HookHandler | addLiquidity     | 759   | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| HookHandler | batch            | 788   | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| HookHandler | donateToPool     | 736   | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| HookHandler | giftClaimsToHook | 728   | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| HookHandler | removeLiquidity  | 775   | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| HookHandler | strayTransfer    | 772   | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| HookHandler | trade            | 1586  | 0       | 0        |\n╰-------------+------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.29s (1.27s CPU time)\n\nRan 9 test suites in 1.29s (5.88s CPU time): 90 tests passed, 0 failed, 2 skipped (92 total tests)\n","passed":true},{"durationMs":45,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SIMDTEST.approve(address,uint256)\",\"SIMDTEST.transfer(address,uint256)\",\"SIMDTEST.transferFrom(address,address,uint256)\",\"SIMDTESTHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"SIMDTESTHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SIMDTESTHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SIMDTESTHook.executeBatch()\",\"SIMDTESTHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":95,\"SECURITY.md\":15,\"VALIDATION.md\":32,\"foundry.toml\":25,\"launch.json\":30,\"script/MineHook.s.sol\":24,\"script/check_manifest.py\":34,\"src/HookFlags.sol\":29,\"src/SIMDTEST.sol\":11,\"src/SIMDTESTHook.sol\":290,\"test/Hook.t.sol\":335,\"test/HookAdversarial.t.sol\":500,\"test/HookInvariant.t.sol\":251,\"test/Invariant.t.sol\":37,\"test/MainnetFork.t.sol\":45,\"test/MainnetForkAdversarial.t.sol\":182,\"test/Token.t.sol\":79,\"test/helpers/LaunchFactorySim.sol\":18,\"test/helpers/PoolHarness.sol\":131,\"test/helpers/ThirdPartyRouter.sol\":82,\"test/mocks/MockERC20.sol\":14},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"4894cbcb3ce0c87825cb7b18fb235a084ab1845fa59e65bb4f679a7f6e01c32d","verifiedTreeHash":"09e686a62635ba3e116c1b016a46b445ba282845","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":5617,"exitCode":0,"name":"build","output":"Compiling 91 files with Solc 0.8.26\nSolc 0.8.26 finished in 5.44s\nCompiler run successful!\nwarning[missing-events-arithmetic]: `lastBatchAt` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:115:9\n    │\n115 │         lastBatchAt = block.timestamp;\n    │         ━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastTick` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:116:9\n    │\n116 │         lastTick = tick;\n    │         ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `observationIndex` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:231:13\n    │\n231 │             observationIndex = next;\n    │             ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `observationCount` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:232:58\n    │\n232 │             if (observationCount < OBSERVATION_CAPACITY) observationCount++;\n    │                                                          ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastTick` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:234:9\n    │\n234 │         lastTick = tick;\n    │         ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTHook.sol:74:40\n   │\n74 │     constructor(IPoolManager manager_, address token_) {\n   │                                        ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:117:39\n    │\n117 │         observations[0] = Observation(uint64(block.timestamp), 0);\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/SIMDTESTHook.sol:136:38\n    │\n136 │             magnitude = exactInput ? uint256(-(params.amountSpecified + 1)) + 1 : 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/SIMDTESTHook.sol:136:83\n    │\n136 │             magnitude = exactInput ? uint256(-(params.amountSpecified + 1)) + 1 : 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/SIMDTESTHook.sol:141:19\n    │\n141 │         if (fee > uint256(uint128(type(int128).max))) fee = 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/SIMDTESTHook.sol:141:27\n    │\n141 │         if (fee > uint256(uint128(type(int128).max))) fee = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:141:61\n    │\n141 │         if (fee > uint256(uint128(type(int128).max))) fee = 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/SIMDTESTHook.sol:141:69\n    │\n141 │         if (fee > uint256(uint128(type(int128).max))) fee = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:143:63\n    │\n143 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\n    │                                                               ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/SIMDTESTHook.sol:143:70\n    │\n143 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\n    │                                                                      ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:157:29\n    │\n157 │         uint256 magnitude = uint256(imdDelta < 0 ? -imdDelta : imdDelta);\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/SIMDTESTHook.sol:160:44\n    │\n160 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/SIMDTESTHook.sol:160:51\n    │\n160 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                                   ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:168:9\n    │\n168 │         emit FeesAccrued(fee);\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `executing` is updated\n    ╭▸ src/SIMDTESTHook.sol:200:29\n    │\n200 │         burned = abi.decode(poolManager.unlock(abi.encode(spent, minimumOut, limit)), (uint256));\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:177:13\n    │\n177 │         if (block.timestamp < lastBatchAt + COOLDOWN) revert CooldownActive();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:203:9\n    │\n203 │         emit BatchExecuted(budget, spent, burned, referenceTick);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:182:22\n    │\n182 │         if (budget > uint256(uint128(type(int128).max))) budget = 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/SIMDTESTHook.sol:182:30\n    │\n182 │         if (budget > uint256(uint128(type(int128).max))) budget = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:182:67\n    │\n182 │         if (budget > uint256(uint128(type(int128).max))) budget = 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/SIMDTESTHook.sol:182:75\n    │\n182 │         if (budget > uint256(uint128(type(int128).max))) budget = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:209:86\n    │\n209 │         BalanceDelta delta = poolManager.swap(poolKey(), SwapParams(imdIsCurrency0, -int256(amount), limit), bytes(\"\"));\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/SIMDTESTHook.sol:213:28\n    │\n213 │         if (inputDelta != -int256(amount) || outputDelta < int256(minimumOut)) revert SlippageExceeded();\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/SIMDTESTHook.sol:213:60\n    │\n213 │         if (inputDelta != -int256(amount) || outputDelta < int256(minimumOut)) revert SlippageExceeded();\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/SIMDTESTHook.sol:215:26\n    │\n215 │         uint256 output = uint256(outputDelta);\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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:226:13\n    │\n226 │         if (block.timestamp > latest.timestamp) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:228:41\n    │\n228 │                 latest.tickCumulative + int128(int256(lastTick) * int256(block.timestamp - latest.timestamp));\n    │                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/SIMDTESTHook.sol:228:67\n    │\n228 │                 latest.tickCumulative + int128(int256(lastTick) * int256(block.timestamp - latest.timestamp));\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/SIMDTESTHook.sol:230:46\n    │\n230 │             observations[next] = Observation(uint64(block.timestamp), cumulative);\n    │                                              ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/SIMDTESTHook.sol:253:27\n    │\n253 │             while (high - low > 1) {\n    │                           ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:240:29\n    │\n240 │         if (!initialized || block.timestamp < TWAP_WINDOW) revert OracleNotReady();\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:243:13\n    │\n243 │         if (observations[oldest].timestamp > target) revert OracleNotReady();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:248:13\n    │\n248 │         if (target >= latest.timestamp) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:255:21\n    │\n255 │                 if (observations[(oldest + mid) % OBSERVATION_CAPACITY].timestamp <= target) low = mid;\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:246:64\n    │\n246 │             int256(latest.tickCumulative) + int256(lastTick) * int256(block.timestamp - latest.timestamp);\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/SIMDTESTHook.sol:249:81\n    │\n249 │             cumulativeThen = int256(latest.tickCumulative) + int256(lastTick) * int256(target - latest.timestamp);\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/SIMDTESTHook.sol:261:19\n    │\n261 │                 * int256(target - a.timestamp) / int256(uint256(b.timestamp - a.timestamp));\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/SIMDTESTHook.sol:264:29\n    │\n264 │         int256 mean = sum / int256(TWAP_WINDOW);\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/SIMDTESTHook.sol:265:30\n    │\n265 │         if (sum < 0 && sum % int256(TWAP_WINDOW) != 0) mean--;\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/SIMDTESTHook.sol:266:16\n    │\n266 │         return int24(mean);\n    │                ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:110:38\n    │\n110 │         assertEq(hook.lastBatchAt(), block.timestamp);\n    │                                      ━━━━━━━━━━━━━━━\n    ‡\n115 │         vm.warp(block.timestamp + 3599);\n    │         ─────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:115:17\n    │\n115 │         vm.warp(block.timestamp + 3599);\n    │         ────────━━━━━━━━━━━━━━━──────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:167:21\n    │\n167 │             vm.warp(block.timestamp + 300);\n    │             ────────━━━━━━━━━━━━━━━─────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:277:21\n    │\n277 │             vm.warp(block.timestamp + 12);\n    │             ────────━━━━━━━━━━━━━━━────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:316:21\n    │\n316 │             vm.warp(block.timestamp + 600);\n    │             ────────━━━━━━━━━━━━━━━─────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/HookAdversarial.t.sol:140:21\n    │\n140 │             vm.warp(block.timestamp + 3);\n    │             ────────━━━━━━━━━━━━━━━───── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/HookAdversarial.t.sol:277:17\n    │\n277 │         vm.warp(block.timestamp + 1800);\n    │         ────────━━━━━━━━━━━━━━━──────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/HookAdversarial.t.sol:280:21\n    │\n280 │             vm.warp(block.timestamp + 300);\n    │             ────────━━━━━━━━━━━━━━━─────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/HookAdversarial.t.sol:439:21\n    │\n439 │             vm.warp(block.timestamp + seed % 1801);\n    │             ────────━━━━━━━━━━━━━━━─────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/HookAdversarial.t.sol:441:30\n    │\n439 │             vm.warp(block.timestamp + seed % 1801);\n    │             ────────────────────────────────────── `vm.warp` changes this environment here\n440 │             _swap((seed >> 8) & 1 == 1, (seed >> 9) & 1 == 1, 100 ether + (seed >> 16) % 50_000 ether);\n441 │             if (ts[n - 1] == block.timestamp) {\n    │                              ━━━━━━━━━━━━━━━\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/HookAdversarial.t.sol:444:25\n    │\n439 │             vm.warp(block.timestamp + seed % 1801);\n    │             ────────────────────────────────────── `vm.warp` changes this environment here\n    ‡\n444 │                 ts[n] = block.timestamp;\n    │                         ━━━━━━━━━━━━━━━\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/HookInvariant.t.sol:85:17\n   │\n85 │         vm.warp(block.timestamp + bound(elapsed, 0, 5400));\n   │         ────────━━━━━━━━━━━━━━━─────────────────────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/MainnetForkAdversarial.t.sol:150:17\n    │\n150 │         vm.warp(block.timestamp + 1 hours);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":1890,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 490.75µs (0.00ns CPU time)\n\nRan 1 test for test/MainnetForkAdversarial.t.sol:MainnetForkAdversarialTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 853.77µs (0.00ns CPU time)\n\nRan 1 test for test/Invariant.t.sol:FeeInvariantTest\n[PASS] invariant_feeClaimsAndBurnAccountingAreConserved() (runs: 64, calls: 2048, reverts: 0)\n\n╭------------------+----------+-------+---------+----------╮\n| Contract         | Selector | Calls | Reverts | Discards |\n+==========================================================+\n| FeeInvariantTest | batch    | 1018  | 0       | 0        |\n|------------------+----------+-------+---------+----------|\n| FeeInvariantTest | trade    | 1030  | 0       | 0        |\n╰------------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 713.76ms (682.52ms CPU time)\n\nRan 18 tests for test/Hook.t.sol:HookIMDSecondTest\n[PASS] testFuzz_allSwapModesAccrueOnlyIMD(bool,bool,uint256) (runs: 1000, μ: 393417, ~: 392767)\n[PASS] testFuzz_swapBatchSequenceConservesClaims(uint256) (runs: 1000, μ: 3934319, ~: 3934680)\n[PASS] test_batchJustBeyondToleranceIsRejected() (gas: 644963)\n[PASS] test_batchPriceImpactAndPartialFillRollBackEverything() (gas: 658607)\n[PASS] test_batchWithinToleranceExecutesAgainstReferenceNetOfFees() (gas: 3044616)\n[PASS] test_cooldownNeverRestrictsTrading() (gas: 903003)\n[PASS] test_everyCallbackRejectsUnauthorizedCaller() (gas: 152162)\n[PASS] test_executeBatchBurnsWithinBudgetAndEnforcesCooldown() (gas: 895068)\n[PASS] test_freshManagerWithOnlyLaunchTokenLiquidityCanCollectFees() (gas: 689956)\n[PASS] test_nestedBatchCannotUnlockManagerOrLoseFees() (gas: 885220)\n[PASS] test_noFundsAndThresholdAreRejected() (gas: 790906)\n[PASS] test_oracleRingWrapsWithoutAffectingSwaps() (gas: 208750678)\n[PASS] test_oracleWeightsElapsedTimeAndRoundsNegativeDown() (gas: 574542)\n[PASS] test_permissionsStaticFeeAndDirectDeployment() (gas: 2902527)\n[PASS] test_priceLimitedExactOutputSellPaysFeeOnTheRequest() (gas: 373964)\n[PASS] test_priceLimitedSpecifiedFeeIsReservedUpfront() (gas: 351777)\n[PASS] test_rejectsForeignPoolAtInitialization() (gas: 135993)\n[PASS] test_spotManipulationCannotRewriteTWAPOrSpendFees() (gas: 648543)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 903.63ms (1.90s CPU time)\n\nRan 6 tests for test/Token.t.sol:TokenTest\n[PASS] testFuzz_transferAndAllowanceDoNotTaxOrChangeSupply(uint256) (runs: 1000, μ: 136712, ~: 137051)\n[PASS] test_entirePolicySupplyBelongsToDeployer() (gas: 44079)\n[PASS] test_invalidTransferAndAllowanceFailWithoutStateChange() (gas: 64549)\n[PASS] test_noMintOrAdminSurfaceEvenForDeployer() (gas: 167859)\n[PASS] test_runtimeHasNoEscapeOpcodes() (gas: 464168)\n[PASS] test_transfersToPoolManagerHaveNoTax() (gas: 78880)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 923.57ms (925.16ms CPU time)\n\nRan 23 tests for test/HookAdversarial.t.sol:HookAdversarialIMDFirstTest\n[PASS] testFuzz_adverseDriftExecutesInsideTheToleranceAndIsRefusedBeyondIt(uint256) (runs: 1000, μ: 769294, ~: 812907)\n[PASS] testFuzz_amountsBelowTheRoundingThresholdPayNothingAndStillSettle(uint256,bool) (runs: 1000, μ: 284630, ~: 280973)\n[PASS] testFuzz_batchRespectsBudgetSlippageAndBurnsOnlyToDead(uint256,uint256) (runs: 1000, μ: 645576, ~: 645791)\n[PASS] testFuzz_exactOutputSellGrossesUpSoTheFeeIsOnePercentOfPoolOutput(uint256) (runs: 1000, μ: 346827, ~: 347782)\n[PASS] testFuzz_quoteAtTickMatchesSqrtPriceArithmetic(int24,uint256) (runs: 1000, μ: 20942, ~: 20772)\n[PASS] testFuzz_sandwichingTheBatchLosesIMD(uint256) (runs: 1000, μ: 882203, ~: 941057)\n[PASS] testFuzz_twapMatchesAnIndependentReferenceOverRandomSchedules(uint256) (runs: 1000, μ: 1397769, ~: 1397420)\n[PASS] test_afterInitializeCannotBeReplayedEvenByTheManager() (gas: 49297)\n[PASS] test_batchEventsReportWhatWasSpentAndBurned() (gas: 502828)\n[PASS] test_constructorRefusesAnAddressWithoutTheMinedPermissionBits() (gas: 17408)\n[PASS] test_constructorRejectsCodelessArgumentsAndIMDAsLaunchToken() (gas: 7556)\n[PASS] test_enormousSpecifiedAmountsRevertInTheManagerWithoutAccruingAnything() (gas: 1834223)\n[PASS] test_factoryStyleCreate2DeployAndAtomicInitializeIsAccepted() (gas: 23507601)\n[PASS] test_favourableDriftLetsTheBatchBurnAboveTheTWAPQuote() (gas: 1828739)\n[PASS] test_giftedClaimsAndStrayTokensAreNeitherCountedNorSpent() (gas: 721401)\n[PASS] test_idleHourMeansOpeningTickAndBatchStillNeedsFees() (gas: 61357)\n[PASS] test_initializationRejectsAnotherTokenPairedWithTheHook() (gas: 684220)\n[PASS] test_insufficientFeesIsCheckedBeforeTheOracle() (gas: 319094)\n[PASS] test_liquidityAndPoolDonationsBypassTheHookEntirely() (gas: 920897)\n[PASS] test_oracleWithoutAFullHourRefusesBatchesButNotTrades() (gas: 222705096)\n[PASS] test_thresholdIsStrictlyGreaterThanOneWeiAbove() (gas: 792530)\n[PASS] test_unInitializedHookRefusesBatchesAndOracleReads() (gas: 7712940)\n[PASS] test_untrustedRouterPaysTheSameFeeAndTheKeeperEarnsNothing() (gas: 652491)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 923.84ms (3.66s CPU time)\n\nRan 18 tests for test/Hook.t.sol:HookIMDFirstTest\n[PASS] testFuzz_allSwapModesAccrueOnlyIMD(bool,bool,uint256) (runs: 1000, μ: 393799, ~: 396628)\n[PASS] testFuzz_swapBatchSequenceConservesClaims(uint256) (runs: 1000, μ: 3935430, ~: 3935620)\n[PASS] test_batchJustBeyondToleranceIsRejected() (gas: 651650)\n[PASS] test_batchPriceImpactAndPartialFillRollBackEverything() (gas: 664903)\n[PASS] test_batchWithinToleranceExecutesAgainstReferenceNetOfFees() (gas: 3055158)\n[PASS] test_cooldownNeverRestrictsTrading() (gas: 908931)\n[PASS] test_everyCallbackRejectsUnauthorizedCaller() (gas: 152162)\n[PASS] test_executeBatchBurnsWithinBudgetAndEnforcesCooldown() (gas: 902322)\n[PASS] test_freshManagerWithOnlyLaunchTokenLiquidityCanCollectFees() (gas: 702042)\n[PASS] test_nestedBatchCannotUnlockManagerOrLoseFees() (gas: 890867)\n[PASS] test_noFundsAndThresholdAreRejected() (gas: 798226)\n[PASS] test_oracleRingWrapsWithoutAffectingSwaps() (gas: 208771209)\n[PASS] test_oracleWeightsElapsedTimeAndRoundsNegativeDown() (gas: 581102)\n[PASS] test_permissionsStaticFeeAndDirectDeployment() (gas: 2902527)\n[PASS] test_priceLimitedExactOutputSellPaysFeeOnTheRequest() (gas: 368033)\n[PASS] test_priceLimitedSpecifiedFeeIsReservedUpfront() (gas: 357779)\n[PASS] test_rejectsForeignPoolAtInitialization() (gas: 135993)\n[PASS] test_spotManipulationCannotRewriteTWAPOrSpendFees() (gas: 655308)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 924.49ms (2.23s CPU time)\n\nRan 23 tests for test/HookAdversarial.t.sol:HookAdversarialIMDSecondTest\n[PASS] testFuzz_adverseDriftExecutesInsideTheToleranceAndIsRefusedBeyondIt(uint256) (runs: 1000, μ: 762626, ~: 805386)\n[PASS] testFuzz_amountsBelowTheRoundingThresholdPayNothingAndStillSettle(uint256,bool) (runs: 1000, μ: 283772, ~: 287832)\n[PASS] testFuzz_batchRespectsBudgetSlippageAndBurnsOnlyToDead(uint256,uint256) (runs: 1000, μ: 638756, ~: 638966)\n[PASS] testFuzz_exactOutputSellGrossesUpSoTheFeeIsOnePercentOfPoolOutput(uint256) (runs: 1000, μ: 353061, ~: 352910)\n[PASS] testFuzz_quoteAtTickMatchesSqrtPriceArithmetic(int24,uint256) (runs: 1000, μ: 20968, ~: 20822)\n[PASS] testFuzz_sandwichingTheBatchLosesIMD(uint256) (runs: 1000, μ: 875354, ~: 934360)\n[PASS] testFuzz_twapMatchesAnIndependentReferenceOverRandomSchedules(uint256) (runs: 1000, μ: 1398070, ~: 1397728)\n[PASS] test_afterInitializeCannotBeReplayedEvenByTheManager() (gas: 49297)\n[PASS] test_batchEventsReportWhatWasSpentAndBurned() (gas: 495981)\n[PASS] test_constructorRefusesAnAddressWithoutTheMinedPermissionBits() (gas: 17408)\n[PASS] test_constructorRejectsCodelessArgumentsAndIMDAsLaunchToken() (gas: 7556)\n[PASS] test_enormousSpecifiedAmountsRevertInTheManagerWithoutAccruingAnything() (gas: 1833327)\n[PASS] test_factoryStyleCreate2DeployAndAtomicInitializeIsAccepted() (gas: 34696334)\n[PASS] test_favourableDriftLetsTheBatchBurnAboveTheTWAPQuote() (gas: 1823833)\n[PASS] test_giftedClaimsAndStrayTokensAreNeitherCountedNorSpent() (gas: 714621)\n[PASS] test_idleHourMeansOpeningTickAndBatchStillNeedsFees() (gas: 61368)\n[PASS] test_initializationRejectsAnotherTokenPairedWithTheHook() (gas: 684220)\n[PASS] test_insufficientFeesIsCheckedBeforeTheOracle() (gas: 312770)\n[PASS] test_liquidityAndPoolDonationsBypassTheHookEntirely() (gas: 914571)\n[PASS] test_oracleWithoutAFullHourRefusesBatchesButNotTrades() (gas: 222733608)\n[PASS] test_thresholdIsStrictlyGreaterThanOneWeiAbove() (gas: 785210)\n[PASS] test_unInitializedHookRefusesBatchesAndOracleReads() (gas: 8283186)\n[PASS] test_untrustedRouterPaysTheSameFeeAndTheKeeperEarnsNothing() (gas: 645691)\nSuite result: ok. 23 passed; 0 failed; 0 skipped; finished in 924.40ms (3.74s CPU time)\n\nRan 1 test for test/HookInvariant.t.sol:HookInvariantTest\n[PASS]\nHookInvariantTest invariants:\n[PASS] invariant_batchCadenceAndParametersAreImmutable\n[PASS] invariant_claimsEqualLedgerPlusGiftsAndAreBacked\n[PASS] invariant_everyPurchasedTokenIsBurnedAndSupplyIsFixed\n[PASS] invariant_feeLedgerIsConserved\n[PASS] invariant_hookHoldsNothingButStrays\n[PASS] invariant_noActionRevertsUnexpectedly\n HookInvariantTest invariants (runs: 128, calls: 6144, reverts: 0)\n\n╭-------------+------------------+-------+---------+----------╮\n| Contract    | Selector         | Calls | Reverts | Discards |\n+=============================================================+\n| HookHandler | addLiquidity     | 775   | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| HookHandler | batch            | 769   | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| HookHandler | donateToPool     | 763   | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| HookHandler | giftClaimsToHook | 758   | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| HookHandler | removeLiquidity  | 751   | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| HookHandler | strayTransfer    | 767   | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| HookHandler | trade            | 1561  | 0       | 0        |\n╰-------------+------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.76s (1.72s CPU time)\n\nRan 9 test suites in 1.76s (7.07s CPU time): 90 tests passed, 0 failed, 2 skipped (92 total tests)\n","passed":true},{"durationMs":78,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SIMDTEST.approve(address,uint256)\",\"SIMDTEST.transfer(address,uint256)\",\"SIMDTEST.transferFrom(address,address,uint256)\",\"SIMDTESTHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"SIMDTESTHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SIMDTESTHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SIMDTESTHook.executeBatch()\",\"SIMDTESTHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":95,\"SECURITY.md\":15,\"VALIDATION.md\":32,\"foundry.toml\":25,\"launch.json\":30,\"script/MineHook.s.sol\":24,\"script/check_manifest.py\":34,\"src/HookFlags.sol\":29,\"src/SIMDTEST.sol\":11,\"src/SIMDTESTHook.sol\":290,\"test/Hook.t.sol\":335,\"test/HookAdversarial.t.sol\":500,\"test/HookInvariant.t.sol\":251,\"test/Invariant.t.sol\":37,\"test/MainnetFork.t.sol\":45,\"test/MainnetForkAdversarial.t.sol\":182,\"test/Token.t.sol\":79,\"test/helpers/LaunchFactorySim.sol\":18,\"test/helpers/PoolHarness.sol\":131,\"test/helpers/ThirdPartyRouter.sol\":82,\"test/mocks/MockERC20.sol\":14},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"66c7103ff319282dda9062a6b3a99a8d6a8690f3c32365dccc86575a548387c3","verifiedTreeHash":"e19c9a7208da1042c7f2cdfa8b126630294efc20","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":4249,"exitCode":0,"name":"build","output":"Compiling 91 files with Solc 0.8.26\nSolc 0.8.26 finished in 4.12s\nCompiler run successful!\nwarning[missing-events-arithmetic]: `lastBatchAt` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:115:9\n    │\n115 │         lastBatchAt = block.timestamp;\n    │         ━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastTick` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:116:9\n    │\n116 │         lastTick = tick;\n    │         ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `observationIndex` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:227:13\n    │\n227 │             observationIndex = next;\n    │             ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `observationCount` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:228:58\n    │\n228 │             if (observationCount < OBSERVATION_CAPACITY) observationCount++;\n    │                                                          ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastTick` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:230:9\n    │\n230 │         lastTick = tick;\n    │         ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTHook.sol:74:40\n   │\n74 │     constructor(IPoolManager manager_, address token_) {\n   │                                        ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:117:39\n    │\n117 │         observations[0] = Observation(uint64(block.timestamp), 0);\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/SIMDTESTHook.sol:136:38\n    │\n136 │             magnitude = exactInput ? uint256(-(params.amountSpecified + 1)) + 1 : 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/SIMDTESTHook.sol:136:83\n    │\n136 │             magnitude = exactInput ? uint256(-(params.amountSpecified + 1)) + 1 : 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/SIMDTESTHook.sol:141:19\n    │\n141 │         if (fee > uint256(uint128(type(int128).max))) fee = 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/SIMDTESTHook.sol:141:27\n    │\n141 │         if (fee > uint256(uint128(type(int128).max))) fee = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:141:61\n    │\n141 │         if (fee > uint256(uint128(type(int128).max))) fee = 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/SIMDTESTHook.sol:141:69\n    │\n141 │         if (fee > uint256(uint128(type(int128).max))) fee = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:143:63\n    │\n143 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\n    │                                                               ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/SIMDTESTHook.sol:143:70\n    │\n143 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\n    │                                                                      ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:157:29\n    │\n157 │         uint256 magnitude = uint256(imdDelta < 0 ? -imdDelta : imdDelta);\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/SIMDTESTHook.sol:160:44\n    │\n160 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/SIMDTESTHook.sol:160:51\n    │\n160 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                                   ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:168:9\n    │\n168 │         emit FeesAccrued(fee);\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `executing` is updated\n    ╭▸ src/SIMDTESTHook.sol:196:29\n    │\n196 │         burned = abi.decode(poolManager.unlock(abi.encode(spent, minimumOut, limit)), (uint256));\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:177:13\n    │\n177 │         if (block.timestamp < lastBatchAt + COOLDOWN) revert CooldownActive();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:199:9\n    │\n199 │         emit BatchExecuted(budget, spent, burned, referenceTick);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:182:22\n    │\n182 │         if (budget > uint256(uint128(type(int128).max))) budget = 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/SIMDTESTHook.sol:182:30\n    │\n182 │         if (budget > uint256(uint128(type(int128).max))) budget = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:182:67\n    │\n182 │         if (budget > uint256(uint128(type(int128).max))) budget = 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/SIMDTESTHook.sol:182:75\n    │\n182 │         if (budget > uint256(uint128(type(int128).max))) budget = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:205:86\n    │\n205 │         BalanceDelta delta = poolManager.swap(poolKey(), SwapParams(imdIsCurrency0, -int256(amount), limit), bytes(\"\"));\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/SIMDTESTHook.sol:209:28\n    │\n209 │         if (inputDelta != -int256(amount) || outputDelta < int256(minimumOut)) revert SlippageExceeded();\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/SIMDTESTHook.sol:209:60\n    │\n209 │         if (inputDelta != -int256(amount) || outputDelta < int256(minimumOut)) revert SlippageExceeded();\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/SIMDTESTHook.sol:211:26\n    │\n211 │         uint256 output = uint256(outputDelta);\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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:222:13\n    │\n222 │         if (block.timestamp > latest.timestamp) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:224:41\n    │\n224 │                 latest.tickCumulative + int128(int256(lastTick) * int256(block.timestamp - latest.timestamp));\n    │                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/SIMDTESTHook.sol:224:67\n    │\n224 │                 latest.tickCumulative + int128(int256(lastTick) * int256(block.timestamp - latest.timestamp));\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/SIMDTESTHook.sol:226:46\n    │\n226 │             observations[next] = Observation(uint64(block.timestamp), cumulative);\n    │                                              ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/SIMDTESTHook.sol:249:27\n    │\n249 │             while (high - low > 1) {\n    │                           ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:236:29\n    │\n236 │         if (!initialized || block.timestamp < TWAP_WINDOW) revert OracleNotReady();\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:239:13\n    │\n239 │         if (observations[oldest].timestamp > target) revert OracleNotReady();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:244:13\n    │\n244 │         if (target >= latest.timestamp) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:251:21\n    │\n251 │                 if (observations[(oldest + mid) % OBSERVATION_CAPACITY].timestamp <= target) low = mid;\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:242:64\n    │\n242 │             int256(latest.tickCumulative) + int256(lastTick) * int256(block.timestamp - latest.timestamp);\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/SIMDTESTHook.sol:245:81\n    │\n245 │             cumulativeThen = int256(latest.tickCumulative) + int256(lastTick) * int256(target - latest.timestamp);\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/SIMDTESTHook.sol:257:19\n    │\n257 │                 * int256(target - a.timestamp) / int256(uint256(b.timestamp - a.timestamp));\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/SIMDTESTHook.sol:260:29\n    │\n260 │         int256 mean = sum / int256(TWAP_WINDOW);\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/SIMDTESTHook.sol:261:30\n    │\n261 │         if (sum < 0 && sum % int256(TWAP_WINDOW) != 0) mean--;\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/SIMDTESTHook.sol:262:16\n    │\n262 │         return int24(mean);\n    │                ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:110:38\n    │\n110 │         assertEq(hook.lastBatchAt(), block.timestamp);\n    │                                      ━━━━━━━━━━━━━━━\n    ‡\n115 │         vm.warp(block.timestamp + 3599);\n    │         ─────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:115:17\n    │\n115 │         vm.warp(block.timestamp + 3599);\n    │         ────────━━━━━━━━━━━━━━━──────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:237:21\n    │\n237 │             vm.warp(block.timestamp + 12);\n    │             ────────━━━━━━━━━━━━━━━────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:261:21\n    │\n261 │             vm.warp(block.timestamp + 600);\n    │             ────────━━━━━━━━━━━━━━━─────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/HookAdversarial.t.sol:140:21\n    │\n140 │             vm.warp(block.timestamp + 3);\n    │             ────────━━━━━━━━━━━━━━━───── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/HookAdversarial.t.sol:277:17\n    │\n277 │         vm.warp(block.timestamp + 1800);\n    │         ────────━━━━━━━━━━━━━━━──────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/HookAdversarial.t.sol:280:21\n    │\n280 │             vm.warp(block.timestamp + 300);\n    │             ────────━━━━━━━━━━━━━━━─────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/HookAdversarial.t.sol:354:21\n    │\n354 │             vm.warp(block.timestamp + seed % 1801);\n    │             ────────━━━━━━━━━━━━━━━─────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/HookAdversarial.t.sol:356:30\n    │\n354 │             vm.warp(block.timestamp + seed % 1801);\n    │             ────────────────────────────────────── `vm.warp` changes this environment here\n355 │             _swap((seed >> 8) & 1 == 1, (seed >> 9) & 1 == 1, 100 ether + (seed >> 16) % 50_000 ether);\n356 │             if (ts[n - 1] == block.timestamp) {\n    │                              ━━━━━━━━━━━━━━━\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/HookAdversarial.t.sol:359:25\n    │\n354 │             vm.warp(block.timestamp + seed % 1801);\n    │             ────────────────────────────────────── `vm.warp` changes this environment here\n    ‡\n359 │                 ts[n] = block.timestamp;\n    │                         ━━━━━━━━━━━━━━━\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n   ╭▸ test/HookInvariant.t.sol:85:17\n   │\n85 │         vm.warp(block.timestamp + bound(elapsed, 0, 5400));\n   │         ────────━━━━━━━━━━━━━━━─────────────────────────── `vm.warp` changes this environment here\n   │\n   ├ help: capture it with `vm.getBlockTimestamp()` instead\n   ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/MainnetForkAdversarial.t.sol:148:17\n    │\n148 │         vm.warp(block.timestamp + 1 hours);\n    │         ────────━━━━━━━━━━━━━━━─────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":1403,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 367.53µs (0.00ns CPU time)\n\nRan 1 test for test/MainnetForkAdversarial.t.sol:MainnetForkAdversarialTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 503.05µs (0.00ns CPU time)\n\nRan 1 test for test/Invariant.t.sol:FeeInvariantTest\n[PASS] invariant_feeClaimsAndBurnAccountingAreConserved() (runs: 64, calls: 2048, reverts: 0)\n\n╭------------------+----------+-------+---------+----------╮\n| Contract         | Selector | Calls | Reverts | Discards |\n+==========================================================+\n| FeeInvariantTest | batch    | 1033  | 0       | 0        |\n|------------------+----------+-------+---------+----------|\n| FeeInvariantTest | trade    | 1015  | 0       | 0        |\n╰------------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 569.52ms (520.75ms CPU time)\n\nRan 21 tests for test/HookAdversarial.t.sol:HookAdversarialIMDFirstTest\n[PASS] testFuzz_amountsBelowTheRoundingThresholdPayNothingAndStillSettle(uint256,bool) (runs: 1000, μ: 284669, ~: 280925)\n[PASS] testFuzz_batchRespectsBudgetSlippageAndBurnsOnlyToDead(uint256,uint256) (runs: 1000, μ: 644764, ~: 644966)\n[PASS] testFuzz_exactOutputSellGrossesUpSoTheFeeIsOnePercentOfPoolOutput(uint256) (runs: 1000, μ: 346744, ~: 347712)\n[PASS] testFuzz_quoteAtTickMatchesSqrtPriceArithmetic(int24,uint256) (runs: 1000, μ: 20958, ~: 20776)\n[PASS] testFuzz_twapMatchesAnIndependentReferenceOverRandomSchedules(uint256) (runs: 1000, μ: 1397748, ~: 1397044)\n[PASS] test_afterInitializeCannotBeReplayedEvenByTheManager() (gas: 49297)\n[PASS] test_batchEventsReportWhatWasSpentAndBurned() (gas: 502442)\n[PASS] test_constructorRefusesAnAddressWithoutTheMinedPermissionBits() (gas: 17386)\n[PASS] test_constructorRejectsCodelessArgumentsAndIMDAsLaunchToken() (gas: 7556)\n[PASS] test_enormousSpecifiedAmountsRevertInTheManagerWithoutAccruingAnything() (gas: 1834031)\n[PASS] test_factoryStyleCreate2DeployAndAtomicInitializeIsAccepted() (gas: 12714175)\n[PASS] test_favourableDriftLetsTheBatchBurnAboveTheTWAPQuote() (gas: 1828065)\n[PASS] test_giftedClaimsAndStrayTokensAreNeitherCountedNorSpent() (gas: 721015)\n[PASS] test_idleHourMeansOpeningTickAndBatchStillNeedsFees() (gas: 61357)\n[PASS] test_initializationRejectsAnotherTokenPairedWithTheHook() (gas: 684220)\n[PASS] test_insufficientFeesIsCheckedBeforeTheOracle() (gas: 319046)\n[PASS] test_liquidityAndPoolDonationsBypassTheHookEntirely() (gas: 920849)\n[PASS] test_oracleWithoutAFullHourRefusesBatchesButNotTrades() (gas: 222651814)\n[PASS] test_thresholdIsStrictlyGreaterThanOneWeiAbove() (gas: 792096)\n[PASS] test_unInitializedHookRefusesBatchesAndOracleReads() (gas: 12235438)\n[PASS] test_untrustedRouterPaysTheSameFeeAndTheKeeperEarnsNothing() (gas: 652114)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 676.42ms (2.65s CPU time)\n\nRan 21 tests for test/HookAdversarial.t.sol:HookAdversarialIMDSecondTest\n[PASS] testFuzz_amountsBelowTheRoundingThresholdPayNothingAndStillSettle(uint256,bool) (runs: 1000, μ: 284128, ~: 287784)\n[PASS] testFuzz_batchRespectsBudgetSlippageAndBurnsOnlyToDead(uint256,uint256) (runs: 1000, μ: 637943, ~: 638137)\n[PASS] testFuzz_exactOutputSellGrossesUpSoTheFeeIsOnePercentOfPoolOutput(uint256) (runs: 1000, μ: 352992, ~: 352831)\n[PASS] testFuzz_quoteAtTickMatchesSqrtPriceArithmetic(int24,uint256) (runs: 1000, μ: 20969, ~: 20794)\n[PASS] testFuzz_twapMatchesAnIndependentReferenceOverRandomSchedules(uint256) (runs: 1000, μ: 1397815, ~: 1397091)\n[PASS] test_afterInitializeCannotBeReplayedEvenByTheManager() (gas: 49297)\n[PASS] test_batchEventsReportWhatWasSpentAndBurned() (gas: 495595)\n[PASS] test_constructorRefusesAnAddressWithoutTheMinedPermissionBits() (gas: 17386)\n[PASS] test_constructorRejectsCodelessArgumentsAndIMDAsLaunchToken() (gas: 7556)\n[PASS] test_enormousSpecifiedAmountsRevertInTheManagerWithoutAccruingAnything() (gas: 1833135)\n[PASS] test_factoryStyleCreate2DeployAndAtomicInitializeIsAccepted() (gas: 16762696)\n[PASS] test_favourableDriftLetsTheBatchBurnAboveTheTWAPQuote() (gas: 1823159)\n[PASS] test_giftedClaimsAndStrayTokensAreNeitherCountedNorSpent() (gas: 714235)\n[PASS] test_idleHourMeansOpeningTickAndBatchStillNeedsFees() (gas: 61368)\n[PASS] test_initializationRejectsAnotherTokenPairedWithTheHook() (gas: 684220)\n[PASS] test_insufficientFeesIsCheckedBeforeTheOracle() (gas: 312722)\n[PASS] test_liquidityAndPoolDonationsBypassTheHookEntirely() (gas: 914523)\n[PASS] test_oracleWithoutAFullHourRefusesBatchesButNotTrades() (gas: 222680326)\n[PASS] test_thresholdIsStrictlyGreaterThanOneWeiAbove() (gas: 784776)\n[PASS] test_unInitializedHookRefusesBatchesAndOracleReads() (gas: 35863027)\n[PASS] test_untrustedRouterPaysTheSameFeeAndTheKeeperEarnsNothing() (gas: 645314)\nSuite result: ok. 21 passed; 0 failed; 0 skipped; finished in 676.39ms (2.53s CPU time)\n\nRan 6 tests for test/Token.t.sol:TokenTest\n[PASS] testFuzz_transferAndAllowanceDoNotTaxOrChangeSupply(uint256) (runs: 1000, μ: 136653, ~: 137051)\n[PASS] test_entirePolicySupplyBelongsToDeployer() (gas: 44079)\n[PASS] test_invalidTransferAndAllowanceFailWithoutStateChange() (gas: 64549)\n[PASS] test_noMintOrAdminSurfaceEvenForDeployer() (gas: 167859)\n[PASS] test_runtimeHasNoEscapeOpcodes() (gas: 464168)\n[PASS] test_transfersToPoolManagerHaveNoTax() (gas: 78880)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 684.84ms (686.64ms CPU time)\n\nRan 16 tests for test/Hook.t.sol:HookIMDFirstTest\n[PASS] testFuzz_allSwapModesAccrueOnlyIMD(bool,bool,uint256) (runs: 1000, μ: 393810, ~: 392782)\n[PASS] testFuzz_swapBatchSequenceConservesClaims(uint256) (runs: 1000, μ: 3932034, ~: 3934244)\n[PASS] test_batchPriceImpactAndPartialFillRollBackEverything() (gas: 664633)\n[PASS] test_cooldownNeverRestrictsTrading() (gas: 909019)\n[PASS] test_everyCallbackRejectsUnauthorizedCaller() (gas: 152184)\n[PASS] test_executeBatchBurnsWithinBudgetAndEnforcesCooldown() (gas: 901285)\n[PASS] test_freshManagerWithOnlyLaunchTokenLiquidityCanCollectFees() (gas: 702126)\n[PASS] test_minimumOutputIncludesBothFees() (gas: 686480)\n[PASS] test_nestedBatchCannotUnlockManagerOrLoseFees() (gas: 890237)\n[PASS] test_noFundsAndThresholdAreRejected() (gas: 797954)\n[PASS] test_oracleRingWrapsWithoutAffectingSwaps() (gas: 208793913)\n[PASS] test_oracleWeightsElapsedTimeAndRoundsNegativeDown() (gas: 581168)\n[PASS] test_permissionsStaticFeeAndDirectDeployment() (gas: 2891790)\n[PASS] test_priceLimitedSpecifiedFeeIsReservedUpfront() (gas: 357801)\n[PASS] test_rejectsForeignPoolAtInitialization() (gas: 136015)\n[PASS] test_spotManipulationCannotRewriteTWAPOrSpendFees() (gas: 654950)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 720.48ms (1.37s CPU time)\n\nRan 16 tests for test/Hook.t.sol:HookIMDSecondTest\n[PASS] testFuzz_allSwapModesAccrueOnlyIMD(bool,bool,uint256) (runs: 1000, μ: 393716, ~: 392804)\n[PASS] testFuzz_swapBatchSequenceConservesClaims(uint256) (runs: 1000, μ: 3930411, ~: 3933035)\n[PASS] test_batchPriceImpactAndPartialFillRollBackEverything() (gas: 658337)\n[PASS] test_cooldownNeverRestrictsTrading() (gas: 903091)\n[PASS] test_everyCallbackRejectsUnauthorizedCaller() (gas: 152184)\n[PASS] test_executeBatchBurnsWithinBudgetAndEnforcesCooldown() (gas: 894031)\n[PASS] test_freshManagerWithOnlyLaunchTokenLiquidityCanCollectFees() (gas: 690040)\n[PASS] test_minimumOutputIncludesBothFees() (gas: 679330)\n[PASS] test_nestedBatchCannotUnlockManagerOrLoseFees() (gas: 884590)\n[PASS] test_noFundsAndThresholdAreRejected() (gas: 790634)\n[PASS] test_oracleRingWrapsWithoutAffectingSwaps() (gas: 208773382)\n[PASS] test_oracleWeightsElapsedTimeAndRoundsNegativeDown() (gas: 574608)\n[PASS] test_permissionsStaticFeeAndDirectDeployment() (gas: 2891790)\n[PASS] test_priceLimitedSpecifiedFeeIsReservedUpfront() (gas: 351799)\n[PASS] test_rejectsForeignPoolAtInitialization() (gas: 136015)\n[PASS] test_spotManipulationCannotRewriteTWAPOrSpendFees() (gas: 648185)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 720.52ms (1.77s CPU time)\n\nRan 1 test for test/HookInvariant.t.sol:HookInvariantTest\n[PASS]\nHookInvariantTest invariants:\n[PASS] invariant_batchCadenceAndParametersAreImmutable\n[PASS] invariant_claimsEqualLedgerPlusGiftsAndAreBacked\n[PASS] invariant_everyPurchasedTokenIsBurnedAndSupplyIsFixed\n[PASS] invariant_feeLedgerIsConserved\n[PASS] invariant_hookHoldsNothingButStrays\n[PASS] invariant_noActionRevertsUnexpectedly\n HookInvariantTest invariants (runs: 128, calls: 6144, reverts: 0)\n\n╭-------------+------------------+-------+---------+----------╮\n| Contract    | Selector         | Calls | Reverts | Discards |\n+=============================================================+\n| HookHandler | addLiquidity     | 778   | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| HookHandler | batch            | 842   | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| HookHandler | donateToPool     | 781   | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| HookHandler | giftClaimsToHook | 759   | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| HookHandler | removeLiquidity  | 733   | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| HookHandler | strayTransfer    | 777   | 0       | 0        |\n|-------------+------------------+-------+---------+----------|\n| HookHandler | trade            | 1474  | 0       | 0        |\n╰-------------+------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.32s (1.27s CPU time)\n\nRan 9 test suites in 1.32s (5.37s CPU time): 82 tests passed, 0 failed, 2 skipped (84 total tests)\n","passed":true},{"durationMs":40,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SIMDTEST.approve(address,uint256)\",\"SIMDTEST.transfer(address,uint256)\",\"SIMDTEST.transferFrom(address,address,uint256)\",\"SIMDTESTHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"SIMDTESTHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SIMDTESTHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SIMDTESTHook.executeBatch()\",\"SIMDTESTHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":93,\"SECURITY.md\":15,\"VALIDATION.md\":31,\"foundry.toml\":25,\"launch.json\":27,\"script/MineHook.s.sol\":24,\"script/check_manifest.py\":34,\"src/HookFlags.sol\":29,\"src/SIMDTEST.sol\":11,\"src/SIMDTESTHook.sol\":286,\"test/Hook.t.sol\":280,\"test/HookAdversarial.t.sol\":415,\"test/HookInvariant.t.sol\":240,\"test/Invariant.t.sol\":37,\"test/MainnetFork.t.sol\":45,\"test/MainnetForkAdversarial.t.sol\":180,\"test/Token.t.sol\":79,\"test/helpers/LaunchFactorySim.sol\":18,\"test/helpers/PoolHarness.sol\":131,\"test/helpers/ThirdPartyRouter.sol\":82,\"test/mocks/MockERC20.sol\":14},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"6f26c1bb0afbbe53f81c9dde668a3c16aa0674657ca67a4c73a18a73f4d71d86","verifiedTreeHash":"91aa434993acf09e318d5758d707af0afbbb76c4","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":3884,"exitCode":0,"name":"build","output":"Compiling 81 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.47s\nCompiler run successful!\nwarning[missing-events-arithmetic]: `lastBatch` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:123:9\n    │\n123 │         lastBatch = block.timestamp;\n    │         ━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastObservationTime` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:124:9\n    │\n124 │         lastObservationTime = uint64(block.timestamp);\n    │         ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastTick` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:125:9\n    │\n125 │         lastTick = TickMath.getTickAtSqrtPrice(sqrtPriceX96);\n    │         ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `tickCumulative` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:322:9\n    │\n322 │         tickCumulative += int192(int256(lastTick) * int256(uint256(now_ - lastObservationTime)));\n    │         ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastObservationTime` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:323:9\n    │\n323 │         lastObservationTime = now_;\n    │         ━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastTick` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:324:9\n    │\n324 │         lastTick = tick;\n    │         ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTHook.sol:81:40\n   │\n81 │     constructor(IPoolManager manager_, address token_) {\n   │                                        ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:124:31\n    │\n124 │         lastObservationTime = uint64(block.timestamp);\n    │                               ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/SIMDTESTHook.sol:126:39\n    │\n126 │         observations[0] = Observation(uint64(block.timestamp), 0);\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/SIMDTESTHook.sol:146:77\n    │\n146 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(fee)), 0), 0);\n    │                                                                             ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:215:9\n    │\n215 │         emit FeeAccrued(fee);\n    │         ━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:146:63\n    │\n146 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(fee)), 0), 0);\n    │                                                               ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/SIMDTESTHook.sol:146:70\n    │\n146 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(int256(fee)), 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[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/SIMDTESTHook.sol:164:58\n    │\n164 │         return (IHooks.afterSwap.selector, int128(int256(fee)));\n    │                                                          ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:159:19\n    │\n159 │             fee = uint256(pairDelta < 0 ? -pairDelta : pairDelta) / 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/SIMDTESTHook.sol:164:44\n    │\n164 │         return (IHooks.afterSwap.selector, int128(int256(fee)));\n    │                                            ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/SIMDTESTHook.sol:164:51\n    │\n164 │         return (IHooks.afterSwap.selector, int128(int256(fee)));\n    │                                                   ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:179:22\n    │\n179 │             amount = uint256(exactInput ? -params.amountSpecified : params.amountSpecified);\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:183:24\n    │\n183 │         if (reserved > uint256(uint128(type(int128).max))) reserved = 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/SIMDTESTHook.sol:183:32\n    │\n183 │         if (reserved > uint256(uint128(type(int128).max))) reserved = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:183:71\n    │\n183 │         if (reserved > uint256(uint128(type(int128).max))) reserved = 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/SIMDTESTHook.sol:183:79\n    │\n183 │         if (reserved > uint256(uint128(type(int128).max))) reserved = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:186:38\n    │\n186 │             quote.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/SIMDTESTHook.sol:188:32\n    │\n188 │             uint256 headroom = uint256(type(int256).max) - amount;\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/SIMDTESTHook.sol:189:38\n    │\n189 │             quote.amountSpecified += int256(reserved < headroom ? reserved : headroom);\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/SIMDTESTHook.sol:195:40\n    │\n195 │             if (reason.length != 36 || bytes4(reason) != QuotedIMD.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\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:205:30\n    │\n205 │             uint256 filled = uint256(pairDelta < 0 ? -pairDelta : pairDelta);\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[reentrancy-no-eth]: external call can be reentered before `batchActive` is updated\n    ╭▸ src/SIMDTESTHook.sol:237:38\n    │\n237 │         (spent, burned) = abi.decode(poolManager.unlock(abi.encode(input, referenceTick)), (uint256, uint256));\n    │                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:227:13\n    │\n227 │         if (block.timestamp < lastBatch + COOLDOWN) revert Cooldown();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:241:9\n    │\n241 │         emit BatchExecuted(msg.sender, spent, burned, referenceTick);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:234:21\n    │\n234 │         if (input > uint256(uint128(type(int128).max))) input = 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/SIMDTESTHook.sol:234:29\n    │\n234 │         if (input > uint256(uint128(type(int128).max))) input = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:234:65\n    │\n234 │         if (input > uint256(uint128(type(int128).max))) input = 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/SIMDTESTHook.sol:234:73\n    │\n234 │         if (input > uint256(uint128(type(int128).max))) input = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:251:53\n    │\n251 │         uint160 limit = TickMath.getSqrtPriceAtTick(int24(limitTick));\n    │                                                     ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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/SIMDTESTHook.sol:257:72\n    │\n257 │             poolManager.swap(getPoolKey(), SwapParams(imdIsCurrency0, -int256(input), limit), \"\");\n    │                                                                        ━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:261:25\n    │\n261 │         uint256 spent = uint256(-pairDelta);\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/SIMDTESTHook.sol:262:26\n    │\n262 │         uint256 burned = uint256(tokenDelta);\n    │                          ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/SIMDTESTHook.sol:301:46\n    │\n301 │             if (obs.timestamp <= target && (!found || obs.timestamp > anchor.timestamp)) {\n    │                                              ━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:295:13\n    │\n295 │         if (block.timestamp < ORACLE_WINDOW) revert OracleNotReady();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:301:17\n    │\n301 │             if (obs.timestamp <= target && (!found || obs.timestamp > anchor.timestamp)) {\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:308:57\n    │\n308 │             int256(tickCumulative) + int256(lastTick) * int256(block.timestamp - lastObservationTime);\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/SIMDTESTHook.sol:309:27\n    │\n309 │         int256 duration = int256(block.timestamp - anchor.timestamp);\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/SIMDTESTHook.sol:317:20\n    │\n317 │         meanTick = int24(average);\n    │                    ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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/SIMDTESTHook.sol:321:23\n    │\n321 │         uint64 now_ = uint64(block.timestamp);\n    │                       ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/SIMDTESTHook.sol:326:32\n    │\n326 │             observationIndex = uint8((uint256(observationIndex) + 1) % OBSERVATION_COUNT);\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint8' 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":2276,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 402.94µs (0.00ns CPU time)\n\nRan 1 test for test/Reentrancy.t.sol:ReentrancyTest\n[PASS] test_burnTransferCannotReenterBatch() (gas: 1278808)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 8.57ms (630.19µs CPU time)\n\nRan 3 tests for test/SIMDTEST.t.sol:SIMDTESTTest\n[PASS] testFuzz_transferFromConservesSupply(uint256) (runs: 1000, μ: 148010, ~: 148366)\n[PASS] test_factoryReceivesEntireSupplyAndCanSplitWithoutTax() (gas: 196523)\n[PASS] test_noMintOrAdminEvenForDeployer() (gas: 152240)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 186.21ms (186.55ms CPU time)\n\nRan 25 tests for test/SIMDTESTHook.t.sol:IMDIsCurrency1Test\n[PASS] testFuzz_batchConservesFeesAndBurns(uint96) (runs: 1000, μ: 626284, ~: 626466)\n[PASS] testFuzz_partialFillFee(uint96,uint16,bool) (runs: 1000, μ: 350595, ~: 347225)\n[PASS] testFuzz_swapAccounting(uint96,bool,bool) (runs: 1000, μ: 423407, ~: 441021)\n[PASS] test_batchBurnAndCooldownBoundaries() (gas: 920771)\n[PASS] test_batchWithoutFees() (gas: 42377)\n[PASS] test_buyExactInput() (gas: 355475)\n[PASS] test_buyExactOutput() (gas: 306337)\n[PASS] test_buyWorksWhenManagerHasNoIMDOrETHBeforeSettlement() (gas: 988612)\n[PASS] test_create2PermissionsInitializationAndSize() (gas: 3057363)\n[PASS] test_emptyLiquiditySwapsHaveNoFee() (gas: 1007900)\n[PASS] test_minimumIsStrictAndDirectTokenDonationsDoNotCount() (gas: 960374)\n[PASS] test_netOutputCheckRejectsWithinSpotLimitButExcessTotalSlippage() (gas: 760937)\n[PASS] test_noLiquidityBatchRollsBack() (gas: 527510)\n[PASS] test_oracleNeedsAnHourAndWeightsElapsedPrices() (gas: 333682)\n[PASS] test_partialExactInputBuyChargesOnlyFilledVolume() (gas: 362177)\n[PASS] test_partialExactOutputSellDoesNotChargeUnfilledOutput() (gas: 356514)\n[PASS] test_quoteSimulationDoesNotDuplicatePoolFees() (gas: 775652)\n[PASS] test_rejectsDifferentPool() (gas: 142654)\n[PASS] test_ringWrapAndSwapBurstRetainOldHistory() (gas: 11188006)\n[PASS] test_sameTimestampSwapsCannotRewriteReference() (gas: 787774)\n[PASS] test_sellExactInput() (gas: 312119)\n[PASS] test_sellExactOutput() (gas: 369423)\n[PASS] test_thinLiquidityBatchStopsAtPriceLimit() (gas: 751792)\n[PASS] test_tinySwapRoundsFeeDown() (gas: 283471)\n[PASS] test_unauthorizedCallbacks() (gas: 175265)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 186.17ms (571.36ms CPU time)\n\nRan 25 tests for test/SIMDTESTHook.t.sol:IMDIsCurrency0Test\n[PASS] testFuzz_batchConservesFeesAndBurns(uint96) (runs: 1000, μ: 639486, ~: 639651)\n[PASS] testFuzz_partialFillFee(uint96,uint16,bool) (runs: 1000, μ: 352511, ~: 358162)\n[PASS] testFuzz_swapAccounting(uint96,bool,bool) (runs: 1000, μ: 423577, ~: 442637)\n[PASS] test_batchBurnAndCooldownBoundaries() (gas: 934411)\n[PASS] test_batchWithoutFees() (gas: 42377)\n[PASS] test_buyExactInput() (gas: 368249)\n[PASS] test_buyExactOutput() (gas: 311319)\n[PASS] test_buyWorksWhenManagerHasNoIMDOrETHBeforeSettlement() (gas: 1007330)\n[PASS] test_create2PermissionsInitializationAndSize() (gas: 3057363)\n[PASS] test_emptyLiquiditySwapsHaveNoFee() (gas: 1021002)\n[PASS] test_minimumIsStrictAndDirectTokenDonationsDoNotCount() (gas: 974976)\n[PASS] test_netOutputCheckRejectsWithinSpotLimitButExcessTotalSlippage() (gas: 774883)\n[PASS] test_noLiquidityBatchRollsBack() (gas: 540241)\n[PASS] test_oracleNeedsAnHourAndWeightsElapsedPrices() (gas: 346382)\n[PASS] test_partialExactInputBuyChargesOnlyFilledVolume() (gas: 374186)\n[PASS] test_partialExactOutputSellDoesNotChargeUnfilledOutput() (gas: 344685)\n[PASS] test_quoteSimulationDoesNotDuplicatePoolFees() (gas: 794754)\n[PASS] test_rejectsDifferentPool() (gas: 142654)\n[PASS] test_ringWrapAndSwapBurstRetainOldHistory() (gas: 11254346)\n[PASS] test_sameTimestampSwapsCannotRewriteReference() (gas: 800975)\n[PASS] test_sellExactInput() (gas: 305770)\n[PASS] test_sellExactOutput() (gas: 359153)\n[PASS] test_thinLiquidityBatchStopsAtPriceLimit() (gas: 764475)\n[PASS] test_tinySwapRoundsFeeDown() (gas: 299691)\n[PASS] test_unauthorizedCallbacks() (gas: 175265)\nSuite result: ok. 25 passed; 0 failed; 0 skipped; finished in 186.23ms (394.79ms CPU time)\n\nRan 1 test for test/AccountingInvariant.t.sol:AccountingInvariantTest\n[PASS] invariant_claimsBurnsAndSupplyAreConserved() (runs: 128, calls: 8192, reverts: 0)\n\n╭-------------------+----------+-------+---------+----------╮\n| Contract          | Selector | Calls | Reverts | Discards |\n+===========================================================+\n| AccountingHandler | batch    | 4118  | 0       | 0        |\n|-------------------+----------+-------+---------+----------|\n| AccountingHandler | trade    | 4074  | 0       | 0        |\n╰-------------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 2.17s (2.16s CPU time)\n\nRan 6 test suites in 2.17s (2.74s CPU time): 55 tests passed, 0 failed, 1 skipped (56 total tests)\n","passed":true},{"durationMs":47,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SIMDTEST.approve(address,uint256)\",\"SIMDTEST.transfer(address,uint256)\",\"SIMDTEST.transferFrom(address,address,uint256)\",\"SIMDTESTHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SIMDTESTHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"SIMDTESTHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SIMDTESTHook.executeBatch()\",\"SIMDTESTHook.quoteIMDFill((bool,int256,uint160))\",\"SIMDTESTHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"LICENSE\":23,\"README.md\":194,\"SECURITY_REVIEW.md\":33,\"foundry.toml\":22,\"launch.json\":22,\"script/MineHook.s.sol\":27,\"src/SIMDTEST.sol\":11,\"src/SIMDTESTHook.sol\":331,\"test/AccountingInvariant.t.sol\":107,\"test/MainnetFork.t.sol\":67,\"test/Reentrancy.t.sol\":52,\"test/SIMDTEST.t.sol\":59,\"test/SIMDTESTHook.t.sol\":399,\"test/helpers/Fixture.sol\":96,\"test/helpers/PoolActor.sol\":57},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1977,"exitCode":0,"name":"slither","output":"[high/medium] weak-prng at src/SIMDTESTHook.sol:291: SIMDTESTHook.consult() (src/SIMDTESTHook.sol#291-318) uses a weak PRNG: \"change % duration != 0 (src/SIMDTESTHook.sol#313)\"\n[medium/medium] reentrancy-no-eth at src/SIMDTESTHook.sol:224: Reentrancy in SIMDTESTHook.executeBatch() (src/SIMDTESTHook.sol#224-242):\n[medium/medium] uninitialized-local at src/SIMDTESTHook.sol:138: SIMDTESTHook.beforeSwap(address,PoolKey,SwapParams,bytes).fee (src/SIMDTESTHook.sol#138) is a local variable never initialized\n[medium/medium] uninitialized-local at src/SIMDTESTHook.sol:297: SIMDTESTHook.consult().anchor (src/SIMDTESTHook.sol#297) is a local variable never initialized\n[medium/medium] uninitialized-local at src/SIMDTESTHook.sol:156: SIMDTESTHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes).fee (src/SIMDTESTHook.sol#156) is a local variable never initialized\n[medium/medium] uninitialized-local at src/SIMDTESTHook.sol:298: SIMDTESTHook.consult().found (src/SIMDTESTHook.sol#298) is a local variable never initialized\n[medium/medium] unused-return at src/SIMDTESTHook.sol:244: SIMDTESTHook.unlockCallback(bytes) (src/SIMDTESTHook.sol#244-272) ignores return value by (None,tick,None,None) = poolManager.getSlot0(poolId) (src/SIMDTESTHook.sol#269)\n[medium/medium] unused-return at src/SIMDTESTHook.sol:244: SIMDTESTHook.unlockCallback(bytes) (src/SIMDTESTHook.sol#244-272) ignores return value by (spot,None,None,None) = poolManager.getSlot0(poolId) (src/SIMDTESTHook.sol#254)\n[medium/medium] unused-return at src/SIMDTESTHook.sol:149: SIMDTESTHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/SIMDTESTHook.sol#149-165) ignores return value by (None,tick,None,None) = poolManager.getSlot0(poolId) (src/SIMDTESTHook.sol#162)\n[low/medium] events-maths at src/SIMDTESTHook.sol:115: SIMDTESTHook.beforeInitialize(address,PoolKey,uint160) (src/SIMDTESTHook.sol#115-129) should emit an event for: \n[low/medium] reentrancy-benign at src/SIMDTESTHook.sol:224: Reentrancy in SIMDTESTHook.executeBatch() (src/SIMDTESTHook.sol#224-242):\n[low/medium] reentrancy-benign at src/SIMDTESTHook.sol:149: Reentrancy in SIMDTESTHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/SIMDTESTHook.sol#149-165):\n[low/medium] reentrancy-benign at src/SIMDTESTHook.sol:244: Reentrancy in SIMDTESTHook.unlockCallback(bytes) (src/SIMDTESTHook.sol#244-272):\n[low/medium] reentrancy-events at src/SIMDTESTHook.sol:211: Reentrancy in SIMDTESTHook._accrue(uint256) (src/SIMDTESTHook.sol#211-216):\n[low/medium] reentrancy-events at src/SIMDTESTHook.sol:131: Reentrancy in SIMDTESTHook.beforeSwap(address,PoolKey,SwapParams,bytes) (src/SIMDTESTHook.sol#131-147):\n[low/medium] reentrancy-events at src/SIMDTESTHook.sol:224: Reentrancy in SIMDTESTHook.executeBatch() (src/SIMDTESTHook.sol#224-242):\n[low/medium] timestamp at src/SIMDTESTHook.sol:224: SIMDTESTHook.executeBatch() (src/SIMDTESTHook.sol#224-242) uses timestamp for comparisons\n[low/medium] timestamp at src/SIMDTESTHook.sol:291: SIMDTESTHook.consult() (src/SIMDTESTHook.sol#291-318) uses timestamp for comparisons\n[low/medium] timestamp at src/SIMDTESTHook.sol:320: SIMDTESTHook._recordTick(int24) (src/SIMDTESTHook.sol#320-330) uses timestamp for comparisons","passed":true},{"durationMs":418,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/SIMDTESTHook.sol:237: Reentrancy: State change after external call\n[high] unsafe-casting at src/SIMDTESTHook.sol:317: Unsafe Casting of integers\n[low] large-numeric-literal at src/SIMDTEST.sol:9: Large Numeric Literal (2 places)\n[low] literal-instead-of-constant at src/SIMDTESTHook.sol:159: Literal Instead of Constant (10 places)\n[low] state-change-without-event at src/SIMDTESTHook.sol:115: State Change Without Event (2 places)\n[low] uninitialized-local-variable at src/SIMDTESTHook.sol:299: Uninitialized Local Variable","passed":true}],"detail":"launch.json is not a valid launch manifest: kind: Invalid discriminator value. Expected 'univ4_hook' | 'evm_project' | 'custom_token' | 'evm_contracts'","evaluation":"checks","profile":"foundry","status":"rejected","submissionHash":"b1bbea9c4f25adb14c7eac723e338545a6db803e755d49696d71ef4524f7cff1","verifiedTreeHash":"326492745ff7804bf557192efe4f525bef915f38","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":2390,"exitCode":0,"name":"build","output":"Compiling 86 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.28s\nCompiler run successful!\nwarning[missing-events-arithmetic]: `lastBatchAt` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:115:9\n    │\n115 │         lastBatchAt = block.timestamp;\n    │         ━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastTick` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:116:9\n    │\n116 │         lastTick = tick;\n    │         ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `observationIndex` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:227:13\n    │\n227 │             observationIndex = next;\n    │             ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `observationCount` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:228:58\n    │\n228 │             if (observationCount < OBSERVATION_CAPACITY) observationCount++;\n    │                                                          ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastTick` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:230:9\n    │\n230 │         lastTick = tick;\n    │         ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTHook.sol:74:40\n   │\n74 │     constructor(IPoolManager manager_, address token_) {\n   │                                        ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:117:39\n    │\n117 │         observations[0] = Observation(uint64(block.timestamp), 0);\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/SIMDTESTHook.sol:136:38\n    │\n136 │             magnitude = exactInput ? uint256(-(params.amountSpecified + 1)) + 1 : 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/SIMDTESTHook.sol:136:83\n    │\n136 │             magnitude = exactInput ? uint256(-(params.amountSpecified + 1)) + 1 : 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/SIMDTESTHook.sol:141:19\n    │\n141 │         if (fee > uint256(uint128(type(int128).max))) fee = 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/SIMDTESTHook.sol:141:27\n    │\n141 │         if (fee > uint256(uint128(type(int128).max))) fee = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:141:61\n    │\n141 │         if (fee > uint256(uint128(type(int128).max))) fee = 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/SIMDTESTHook.sol:141:69\n    │\n141 │         if (fee > uint256(uint128(type(int128).max))) fee = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:143:63\n    │\n143 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\n    │                                                               ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/SIMDTESTHook.sol:143:70\n    │\n143 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\n    │                                                                      ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:157:29\n    │\n157 │         uint256 magnitude = uint256(imdDelta < 0 ? -imdDelta : imdDelta);\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/SIMDTESTHook.sol:160:44\n    │\n160 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/SIMDTESTHook.sol:160:51\n    │\n160 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                                   ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:168:9\n    │\n168 │         emit FeesAccrued(fee);\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `executing` is updated\n    ╭▸ src/SIMDTESTHook.sol:196:29\n    │\n196 │         burned = abi.decode(poolManager.unlock(abi.encode(spent, minimumOut, limit)), (uint256));\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:177:13\n    │\n177 │         if (block.timestamp < lastBatchAt + COOLDOWN) revert CooldownActive();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:199:9\n    │\n199 │         emit BatchExecuted(budget, spent, burned, referenceTick);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:182:22\n    │\n182 │         if (budget > uint256(uint128(type(int128).max))) budget = 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/SIMDTESTHook.sol:182:30\n    │\n182 │         if (budget > uint256(uint128(type(int128).max))) budget = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:182:67\n    │\n182 │         if (budget > uint256(uint128(type(int128).max))) budget = 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/SIMDTESTHook.sol:182:75\n    │\n182 │         if (budget > uint256(uint128(type(int128).max))) budget = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:205:86\n    │\n205 │         BalanceDelta delta = poolManager.swap(poolKey(), SwapParams(imdIsCurrency0, -int256(amount), limit), bytes(\"\"));\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/SIMDTESTHook.sol:209:28\n    │\n209 │         if (inputDelta != -int256(amount) || outputDelta < int256(minimumOut)) revert SlippageExceeded();\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/SIMDTESTHook.sol:209:60\n    │\n209 │         if (inputDelta != -int256(amount) || outputDelta < int256(minimumOut)) revert SlippageExceeded();\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/SIMDTESTHook.sol:211:26\n    │\n211 │         uint256 output = uint256(outputDelta);\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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:222:13\n    │\n222 │         if (block.timestamp > latest.timestamp) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:224:41\n    │\n224 │                 latest.tickCumulative + int128(int256(lastTick) * int256(block.timestamp - latest.timestamp));\n    │                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/SIMDTESTHook.sol:224:67\n    │\n224 │                 latest.tickCumulative + int128(int256(lastTick) * int256(block.timestamp - latest.timestamp));\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/SIMDTESTHook.sol:226:46\n    │\n226 │             observations[next] = Observation(uint64(block.timestamp), cumulative);\n    │                                              ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/SIMDTESTHook.sol:249:27\n    │\n249 │             while (high - low > 1) {\n    │                           ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:236:29\n    │\n236 │         if (!initialized || block.timestamp < TWAP_WINDOW) revert OracleNotReady();\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:239:13\n    │\n239 │         if (observations[oldest].timestamp > target) revert OracleNotReady();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:244:13\n    │\n244 │         if (target >= latest.timestamp) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:251:21\n    │\n251 │                 if (observations[(oldest + mid) % OBSERVATION_CAPACITY].timestamp <= target) low = mid;\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:242:64\n    │\n242 │             int256(latest.tickCumulative) + int256(lastTick) * int256(block.timestamp - latest.timestamp);\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/SIMDTESTHook.sol:245:81\n    │\n245 │             cumulativeThen = int256(latest.tickCumulative) + int256(lastTick) * int256(target - latest.timestamp);\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/SIMDTESTHook.sol:257:19\n    │\n257 │                 * int256(target - a.timestamp) / int256(uint256(b.timestamp - a.timestamp));\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/SIMDTESTHook.sol:260:29\n    │\n260 │         int256 mean = sum / int256(TWAP_WINDOW);\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/SIMDTESTHook.sol:261:30\n    │\n261 │         if (sum < 0 && sum % int256(TWAP_WINDOW) != 0) mean--;\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/SIMDTESTHook.sol:262:16\n    │\n262 │         return int24(mean);\n    │                ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:110:38\n    │\n110 │         assertEq(hook.lastBatchAt(), block.timestamp);\n    │                                      ━━━━━━━━━━━━━━━\n    ‡\n115 │         vm.warp(block.timestamp + 3599);\n    │         ─────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:115:17\n    │\n115 │         vm.warp(block.timestamp + 3599);\n    │         ────────━━━━━━━━━━━━━━━──────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:237:21\n    │\n237 │             vm.warp(block.timestamp + 12);\n    │             ────────━━━━━━━━━━━━━━━────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:261:21\n    │\n261 │             vm.warp(block.timestamp + 600);\n    │             ────────━━━━━━━━━━━━━━━─────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":623,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 328.29µs (0.00ns CPU time)\n\nRan 6 tests for test/Token.t.sol:TokenTest\n[PASS] testFuzz_transferAndAllowanceDoNotTaxOrChangeSupply(uint256) (runs: 1000, μ: 136648, ~: 137003)\n[PASS] test_entirePolicySupplyBelongsToDeployer() (gas: 44079)\n[PASS] test_invalidTransferAndAllowanceFailWithoutStateChange() (gas: 64549)\n[PASS] test_noMintOrAdminSurfaceEvenForDeployer() (gas: 167859)\n[PASS] test_runtimeHasNoEscapeOpcodes() (gas: 464168)\n[PASS] test_transfersToPoolManagerHaveNoTax() (gas: 78880)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 384.38ms (385.63ms CPU time)\n\nRan 1 test for test/Invariant.t.sol:FeeInvariantTest\n[PASS] invariant_feeClaimsAndBurnAccountingAreConserved() (runs: 64, calls: 2048, reverts: 0)\n\n╭------------------+----------+-------+---------+----------╮\n| Contract         | Selector | Calls | Reverts | Discards |\n+==========================================================+\n| FeeInvariantTest | batch    | 1050  | 0       | 0        |\n|------------------+----------+-------+---------+----------|\n| FeeInvariantTest | trade    | 998   | 0       | 0        |\n╰------------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 406.49ms (382.58ms CPU time)\n\nRan 16 tests for test/Hook.t.sol:HookIMDSecondTest\n[PASS] testFuzz_allSwapModesAccrueOnlyIMD(bool,bool,uint256) (runs: 1000, μ: 393603, ~: 392789)\n[PASS] testFuzz_swapBatchSequenceConservesClaims(uint256) (runs: 1000, μ: 3929758, ~: 3933373)\n[PASS] test_batchPriceImpactAndPartialFillRollBackEverything() (gas: 658337)\n[PASS] test_cooldownNeverRestrictsTrading() (gas: 903091)\n[PASS] test_everyCallbackRejectsUnauthorizedCaller() (gas: 152184)\n[PASS] test_executeBatchBurnsWithinBudgetAndEnforcesCooldown() (gas: 894031)\n[PASS] test_freshManagerWithOnlyLaunchTokenLiquidityCanCollectFees() (gas: 690040)\n[PASS] test_minimumOutputIncludesBothFees() (gas: 679330)\n[PASS] test_nestedBatchCannotUnlockManagerOrLoseFees() (gas: 884590)\n[PASS] test_noFundsAndThresholdAreRejected() (gas: 790634)\n[PASS] test_oracleRingWrapsWithoutAffectingSwaps() (gas: 208773382)\n[PASS] test_oracleWeightsElapsedTimeAndRoundsNegativeDown() (gas: 574608)\n[PASS] test_permissionsStaticFeeAndDirectDeployment() (gas: 2891790)\n[PASS] test_priceLimitedSpecifiedFeeIsReservedUpfront() (gas: 351799)\n[PASS] test_rejectsForeignPoolAtInitialization() (gas: 136015)\n[PASS] test_spotManipulationCannotRewriteTWAPOrSpendFees() (gas: 648185)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 540.29ms (1.50s CPU time)\n\nRan 16 tests for test/Hook.t.sol:HookIMDFirstTest\n[PASS] testFuzz_allSwapModesAccrueOnlyIMD(bool,bool,uint256) (runs: 1000, μ: 393789, ~: 396650)\n[PASS] testFuzz_swapBatchSequenceConservesClaims(uint256) (runs: 1000, μ: 3932680, ~: 3934824)\n[PASS] test_batchPriceImpactAndPartialFillRollBackEverything() (gas: 664633)\n[PASS] test_cooldownNeverRestrictsTrading() (gas: 909019)\n[PASS] test_everyCallbackRejectsUnauthorizedCaller() (gas: 152184)\n[PASS] test_executeBatchBurnsWithinBudgetAndEnforcesCooldown() (gas: 901285)\n[PASS] test_freshManagerWithOnlyLaunchTokenLiquidityCanCollectFees() (gas: 702126)\n[PASS] test_minimumOutputIncludesBothFees() (gas: 686480)\n[PASS] test_nestedBatchCannotUnlockManagerOrLoseFees() (gas: 890237)\n[PASS] test_noFundsAndThresholdAreRejected() (gas: 797954)\n[PASS] test_oracleRingWrapsWithoutAffectingSwaps() (gas: 208793913)\n[PASS] test_oracleWeightsElapsedTimeAndRoundsNegativeDown() (gas: 581168)\n[PASS] test_permissionsStaticFeeAndDirectDeployment() (gas: 2891790)\n[PASS] test_priceLimitedSpecifiedFeeIsReservedUpfront() (gas: 357801)\n[PASS] test_rejectsForeignPoolAtInitialization() (gas: 136015)\n[PASS] test_spotManipulationCannotRewriteTWAPOrSpendFees() (gas: 654950)\nSuite result: ok. 16 passed; 0 failed; 0 skipped; finished in 540.34ms (1.26s CPU time)\n\nRan 5 test suites in 541.96ms (1.87s CPU time): 39 tests passed, 0 failed, 1 skipped (40 total tests)\n","passed":true},{"durationMs":39,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SIMDTEST.approve(address,uint256)\",\"SIMDTEST.transfer(address,uint256)\",\"SIMDTEST.transferFrom(address,address,uint256)\",\"SIMDTESTHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"SIMDTESTHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SIMDTESTHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SIMDTESTHook.executeBatch()\",\"SIMDTESTHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":93,\"SECURITY.md\":15,\"VALIDATION.md\":31,\"foundry.toml\":25,\"launch.json\":27,\"script/MineHook.s.sol\":24,\"script/check_manifest.py\":34,\"src/HookFlags.sol\":29,\"src/SIMDTEST.sol\":11,\"src/SIMDTESTHook.sol\":286,\"test/Hook.t.sol\":280,\"test/Invariant.t.sol\":37,\"test/MainnetFork.t.sol\":45,\"test/Token.t.sol\":79,\"test/helpers/PoolHarness.sol\":131,\"test/mocks/MockERC20.sol\":14},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1361,"exitCode":0,"name":"slither","output":"[high/medium] weak-prng at src/SIMDTESTHook.sol:235: SIMDTESTHook.consult() (src/SIMDTESTHook.sol#235-263) uses a weak PRNG: \"sum < 0 && sum % int256(TWAP_WINDOW) != 0 (src/SIMDTESTHook.sol#261)\"\n[medium/high] incorrect-equality at src/SIMDTESTHook.sol:174: SIMDTESTHook.executeBatch() (src/SIMDTESTHook.sol#174-200) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/SIMDTESTHook.sol:174: Reentrancy in SIMDTESTHook.executeBatch() (src/SIMDTESTHook.sol#174-200):\n[medium/medium] uninitialized-local at src/SIMDTESTHook.sol:247: SIMDTESTHook.consult().low (src/SIMDTESTHook.sol#247) is a local variable never initialized\n[medium/medium] unused-return at src/SIMDTESTHook.sol:202: SIMDTESTHook.unlockCallback(bytes) (src/SIMDTESTHook.sol#202-217) ignores return value by (None,tick,None,None) = poolManager.getSlot0(poolId) (src/SIMDTESTHook.sol#214)\n[medium/medium] unused-return at src/SIMDTESTHook.sol:146: SIMDTESTHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/SIMDTESTHook.sol#146-161) ignores return value by (None,tick,None,None) = poolManager.getSlot0(poolId) (src/SIMDTESTHook.sol#151)\n[medium/medium] unused-return at src/SIMDTESTHook.sol:174: SIMDTESTHook.executeBatch() (src/SIMDTESTHook.sol#174-200) ignores return value by (spot,None,None,None) = poolManager.getSlot0(poolId) (src/SIMDTESTHook.sol#189)\n[low/medium] events-maths at src/SIMDTESTHook.sol:108: SIMDTESTHook.afterInitialize(address,PoolKey,uint160,int24) (src/SIMDTESTHook.sol#108-120) should emit an event for: \n[low/medium] reentrancy-benign at src/SIMDTESTHook.sol:174: Reentrancy in SIMDTESTHook.executeBatch() (src/SIMDTESTHook.sol#174-200):\n[low/medium] reentrancy-benign at src/SIMDTESTHook.sol:202: Reentrancy in SIMDTESTHook.unlockCallback(bytes) (src/SIMDTESTHook.sol#202-217):\n[low/medium] reentrancy-events at src/SIMDTESTHook.sol:163: Reentrancy in SIMDTESTHook._accrue(uint256) (src/SIMDTESTHook.sol#163-169):\n[low/medium] reentrancy-events at src/SIMDTESTHook.sol:174: Reentrancy in SIMDTESTHook.executeBatch() (src/SIMDTESTHook.sol#174-200):\n[low/medium] timestamp at src/SIMDTESTHook.sol:174: SIMDTESTHook.executeBatch() (src/SIMDTESTHook.sol#174-200) uses timestamp for comparisons\n[low/medium] timestamp at src/SIMDTESTHook.sol:279: SIMDTESTHook._priceLimit(int24) (src/SIMDTESTHook.sol#279-285) uses timestamp for comparisons\n[low/medium] timestamp at src/SIMDTESTHook.sol:266: SIMDTESTHook.quoteAtTick(int24,uint256) (src/SIMDTESTHook.sol#266-277) uses timestamp for comparisons\n[low/medium] timestamp at src/SIMDTESTHook.sol:220: SIMDTESTHook._record(int24) (src/SIMDTESTHook.sol#220-231) uses timestamp for comparisons\n[low/medium] timestamp at src/SIMDTESTHook.sol:235: SIMDTESTHook.consult() (src/SIMDTESTHook.sol#235-263) uses timestamp for comparisons","passed":true},{"durationMs":364,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/SIMDTESTHook.sol:196: Reentrancy: State change after external call\n[high] unsafe-casting at src/SIMDTESTHook.sol:160: Unsafe Casting of integers (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/SIMDTEST.sol:9: Large Numeric Literal (2 places)\n[low] literal-instead-of-constant at src/SIMDTESTHook.sol:140: Literal Instead of Constant (9 places)\n[low] state-change-without-event at src/SIMDTESTHook.sol:108: State Change Without Event (2 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"d5ff33b6ceaca8eded331ccf79063833122d030be75adae1519a9135c2792965","verifiedTreeHash":"44db0d73505a9939834b2b529e8512d088682771","verifierVersion":"0.1.0+ad90ce4c"},{"checks":[{"durationMs":2801,"exitCode":0,"name":"build","output":"Compiling 86 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.65s\nCompiler run successful!\nwarning[missing-events-arithmetic]: `lastBatchAt` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:115:9\n    │\n115 │         lastBatchAt = block.timestamp;\n    │         ━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastTick` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:116:9\n    │\n116 │         lastTick = tick;\n    │         ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `observationIndex` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:231:13\n    │\n231 │             observationIndex = next;\n    │             ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `observationCount` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:232:58\n    │\n232 │             if (observationCount < OBSERVATION_CAPACITY) observationCount++;\n    │                                                          ━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-events-arithmetic]: `lastTick` is changed without an event but is used in arithmetic\n    ╭▸ src/SIMDTESTHook.sol:234:9\n    │\n234 │         lastTick = tick;\n    │         ━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/missing-events-arithmetic\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/SIMDTESTHook.sol:74:40\n   │\n74 │     constructor(IPoolManager manager_, address token_) {\n   │                                        ━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:117:39\n    │\n117 │         observations[0] = Observation(uint64(block.timestamp), 0);\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' 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/SIMDTESTHook.sol:136:38\n    │\n136 │             magnitude = exactInput ? uint256(-(params.amountSpecified + 1)) + 1 : 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/SIMDTESTHook.sol:136:83\n    │\n136 │             magnitude = exactInput ? uint256(-(params.amountSpecified + 1)) + 1 : 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/SIMDTESTHook.sol:141:19\n    │\n141 │         if (fee > uint256(uint128(type(int128).max))) fee = 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/SIMDTESTHook.sol:141:27\n    │\n141 │         if (fee > uint256(uint128(type(int128).max))) fee = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:141:61\n    │\n141 │         if (fee > uint256(uint128(type(int128).max))) fee = 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/SIMDTESTHook.sol:141:69\n    │\n141 │         if (fee > uint256(uint128(type(int128).max))) fee = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:143:63\n    │\n143 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\n    │                                                               ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/SIMDTESTHook.sol:143:70\n    │\n143 │         return (IHooks.beforeSwap.selector, toBeforeSwapDelta(int128(uint128(fee)), 0), 0);\n    │                                                                      ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:157:29\n    │\n157 │         uint256 magnitude = uint256(imdDelta < 0 ? -imdDelta : imdDelta);\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/SIMDTESTHook.sol:160:44\n    │\n160 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/SIMDTESTHook.sol:160:51\n    │\n160 │         return (IHooks.afterSwap.selector, int128(uint128(fee)));\n    │                                                   ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:168:9\n    │\n168 │         emit FeesAccrued(fee);\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `executing` is updated\n    ╭▸ src/SIMDTESTHook.sol:200:29\n    │\n200 │         burned = abi.decode(poolManager.unlock(abi.encode(spent, minimumOut, limit)), (uint256));\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:177:13\n    │\n177 │         if (block.timestamp < lastBatchAt + COOLDOWN) revert CooldownActive();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/SIMDTESTHook.sol:203:9\n    │\n203 │         emit BatchExecuted(budget, spent, burned, referenceTick);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:182:22\n    │\n182 │         if (budget > uint256(uint128(type(int128).max))) budget = 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/SIMDTESTHook.sol:182:30\n    │\n182 │         if (budget > uint256(uint128(type(int128).max))) budget = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:182:67\n    │\n182 │         if (budget > uint256(uint128(type(int128).max))) budget = 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/SIMDTESTHook.sol:182:75\n    │\n182 │         if (budget > uint256(uint128(type(int128).max))) budget = 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[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:209:86\n    │\n209 │         BalanceDelta delta = poolManager.swap(poolKey(), SwapParams(imdIsCurrency0, -int256(amount), limit), bytes(\"\"));\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/SIMDTESTHook.sol:213:28\n    │\n213 │         if (inputDelta != -int256(amount) || outputDelta < int256(minimumOut)) revert SlippageExceeded();\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/SIMDTESTHook.sol:213:60\n    │\n213 │         if (inputDelta != -int256(amount) || outputDelta < int256(minimumOut)) revert SlippageExceeded();\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/SIMDTESTHook.sol:215:26\n    │\n215 │         uint256 output = uint256(outputDelta);\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[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:226:13\n    │\n226 │         if (block.timestamp > latest.timestamp) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:228:41\n    │\n228 │                 latest.tickCumulative + int128(int256(lastTick) * int256(block.timestamp - latest.timestamp));\n    │                                         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' 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/SIMDTESTHook.sol:228:67\n    │\n228 │                 latest.tickCumulative + int128(int256(lastTick) * int256(block.timestamp - latest.timestamp));\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/SIMDTESTHook.sol:230:46\n    │\n230 │             observations[next] = Observation(uint64(block.timestamp), cumulative);\n    │                                              ━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint64' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/SIMDTESTHook.sol:253:27\n    │\n253 │             while (high - low > 1) {\n    │                           ━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:240:29\n    │\n240 │         if (!initialized || block.timestamp < TWAP_WINDOW) revert OracleNotReady();\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:243:13\n    │\n243 │         if (observations[oldest].timestamp > target) revert OracleNotReady();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:248:13\n    │\n248 │         if (target >= latest.timestamp) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/SIMDTESTHook.sol:255:21\n    │\n255 │                 if (observations[(oldest + mid) % OBSERVATION_CAPACITY].timestamp <= target) low = mid;\n    │                     ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/SIMDTESTHook.sol:246:64\n    │\n246 │             int256(latest.tickCumulative) + int256(lastTick) * int256(block.timestamp - latest.timestamp);\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/SIMDTESTHook.sol:249:81\n    │\n249 │             cumulativeThen = int256(latest.tickCumulative) + int256(lastTick) * int256(target - latest.timestamp);\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/SIMDTESTHook.sol:261:19\n    │\n261 │                 * int256(target - a.timestamp) / int256(uint256(b.timestamp - a.timestamp));\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/SIMDTESTHook.sol:264:29\n    │\n264 │         int256 mean = sum / int256(TWAP_WINDOW);\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/SIMDTESTHook.sol:265:30\n    │\n265 │         if (sum < 0 && sum % int256(TWAP_WINDOW) != 0) mean--;\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/SIMDTESTHook.sol:266:16\n    │\n266 │         return int24(mean);\n    │                ━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int24' 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[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:110:38\n    │\n110 │         assertEq(hook.lastBatchAt(), block.timestamp);\n    │                                      ━━━━━━━━━━━━━━━\n    ‡\n115 │         vm.warp(block.timestamp + 3599);\n    │         ─────────────────────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:115:17\n    │\n115 │         vm.warp(block.timestamp + 3599);\n    │         ────────━━━━━━━━━━━━━━━──────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:167:21\n    │\n167 │             vm.warp(block.timestamp + 300);\n    │             ────────━━━━━━━━━━━━━━━─────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:277:21\n    │\n277 │             vm.warp(block.timestamp + 12);\n    │             ────────━━━━━━━━━━━━━━━────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/Hook.t.sol:316:21\n    │\n316 │             vm.warp(block.timestamp + 600);\n    │             ────────━━━━━━━━━━━━━━━─────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":671,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 1 test for test/MainnetFork.t.sol:MainnetForkTest\n[SKIP: skipped] setUp() (gas: 0)\nSuite result: ok. 0 passed; 0 failed; 1 skipped; finished in 397.44µs (0.00ns CPU time)\n\nRan 1 test for test/Invariant.t.sol:FeeInvariantTest\n[PASS] invariant_feeClaimsAndBurnAccountingAreConserved() (runs: 64, calls: 2048, reverts: 0)\n\n╭------------------+----------+-------+---------+----------╮\n| Contract         | Selector | Calls | Reverts | Discards |\n+==========================================================+\n| FeeInvariantTest | batch    | 1022  | 0       | 0        |\n|------------------+----------+-------+---------+----------|\n| FeeInvariantTest | trade    | 1026  | 0       | 0        |\n╰------------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 529.51ms (500.54ms CPU time)\n\nRan 6 tests for test/Token.t.sol:TokenTest\n[PASS] testFuzz_transferAndAllowanceDoNotTaxOrChangeSupply(uint256) (runs: 1000, μ: 136691, ~: 137051)\n[PASS] test_entirePolicySupplyBelongsToDeployer() (gas: 44079)\n[PASS] test_invalidTransferAndAllowanceFailWithoutStateChange() (gas: 64549)\n[PASS] test_noMintOrAdminSurfaceEvenForDeployer() (gas: 167859)\n[PASS] test_runtimeHasNoEscapeOpcodes() (gas: 464168)\n[PASS] test_transfersToPoolManagerHaveNoTax() (gas: 78880)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 568.78ms (571.40ms CPU time)\n\nRan 18 tests for test/Hook.t.sol:HookIMDSecondTest\n[PASS] testFuzz_allSwapModesAccrueOnlyIMD(bool,bool,uint256) (runs: 1000, μ: 393623, ~: 392782)\n[PASS] testFuzz_swapBatchSequenceConservesClaims(uint256) (runs: 1000, μ: 3933423, ~: 3933215)\n[PASS] test_batchJustBeyondToleranceIsRejected() (gas: 644963)\n[PASS] test_batchPriceImpactAndPartialFillRollBackEverything() (gas: 658607)\n[PASS] test_batchWithinToleranceExecutesAgainstReferenceNetOfFees() (gas: 3044616)\n[PASS] test_cooldownNeverRestrictsTrading() (gas: 903003)\n[PASS] test_everyCallbackRejectsUnauthorizedCaller() (gas: 152162)\n[PASS] test_executeBatchBurnsWithinBudgetAndEnforcesCooldown() (gas: 895068)\n[PASS] test_freshManagerWithOnlyLaunchTokenLiquidityCanCollectFees() (gas: 689956)\n[PASS] test_nestedBatchCannotUnlockManagerOrLoseFees() (gas: 885220)\n[PASS] test_noFundsAndThresholdAreRejected() (gas: 790906)\n[PASS] test_oracleRingWrapsWithoutAffectingSwaps() (gas: 208750678)\n[PASS] test_oracleWeightsElapsedTimeAndRoundsNegativeDown() (gas: 574542)\n[PASS] test_permissionsStaticFeeAndDirectDeployment() (gas: 2902527)\n[PASS] test_priceLimitedExactOutputSellPaysFeeOnTheRequest() (gas: 373964)\n[PASS] test_priceLimitedSpecifiedFeeIsReservedUpfront() (gas: 351777)\n[PASS] test_rejectsForeignPoolAtInitialization() (gas: 135993)\n[PASS] test_spotManipulationCannotRewriteTWAPOrSpendFees() (gas: 648543)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 568.78ms (1.43s CPU time)\n\nRan 18 tests for test/Hook.t.sol:HookIMDFirstTest\n[PASS] testFuzz_allSwapModesAccrueOnlyIMD(bool,bool,uint256) (runs: 1000, μ: 393647, ~: 396616)\n[PASS] testFuzz_swapBatchSequenceConservesClaims(uint256) (runs: 1000, μ: 3935514, ~: 3935554)\n[PASS] test_batchJustBeyondToleranceIsRejected() (gas: 651650)\n[PASS] test_batchPriceImpactAndPartialFillRollBackEverything() (gas: 664903)\n[PASS] test_batchWithinToleranceExecutesAgainstReferenceNetOfFees() (gas: 3055158)\n[PASS] test_cooldownNeverRestrictsTrading() (gas: 908931)\n[PASS] test_everyCallbackRejectsUnauthorizedCaller() (gas: 152162)\n[PASS] test_executeBatchBurnsWithinBudgetAndEnforcesCooldown() (gas: 902322)\n[PASS] test_freshManagerWithOnlyLaunchTokenLiquidityCanCollectFees() (gas: 702042)\n[PASS] test_nestedBatchCannotUnlockManagerOrLoseFees() (gas: 890867)\n[PASS] test_noFundsAndThresholdAreRejected() (gas: 798226)\n[PASS] test_oracleRingWrapsWithoutAffectingSwaps() (gas: 208771209)\n[PASS] test_oracleWeightsElapsedTimeAndRoundsNegativeDown() (gas: 581102)\n[PASS] test_permissionsStaticFeeAndDirectDeployment() (gas: 2902527)\n[PASS] test_priceLimitedExactOutputSellPaysFeeOnTheRequest() (gas: 368033)\n[PASS] test_priceLimitedSpecifiedFeeIsReservedUpfront() (gas: 357779)\n[PASS] test_rejectsForeignPoolAtInitialization() (gas: 135993)\n[PASS] test_spotManipulationCannotRewriteTWAPOrSpendFees() (gas: 655308)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 568.98ms (1.47s CPU time)\n\nRan 5 test suites in 570.58ms (2.24s CPU time): 43 tests passed, 0 failed, 1 skipped (44 total tests)\n","passed":true},{"durationMs":43,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SIMDTEST.approve(address,uint256)\",\"SIMDTEST.transfer(address,uint256)\",\"SIMDTEST.transferFrom(address,address,uint256)\",\"SIMDTESTHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"SIMDTESTHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SIMDTESTHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SIMDTESTHook.executeBatch()\",\"SIMDTESTHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"README.md\":95,\"SECURITY.md\":15,\"VALIDATION.md\":32,\"foundry.toml\":25,\"launch.json\":30,\"script/MineHook.s.sol\":24,\"script/check_manifest.py\":34,\"src/HookFlags.sol\":29,\"src/SIMDTEST.sol\":11,\"src/SIMDTESTHook.sol\":290,\"test/Hook.t.sol\":335,\"test/Invariant.t.sol\":37,\"test/MainnetFork.t.sol\":45,\"test/Token.t.sol\":79,\"test/helpers/PoolHarness.sol\":131,\"test/mocks/MockERC20.sol\":14},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1660,"exitCode":0,"name":"slither","output":"[high/medium] weak-prng at src/SIMDTESTHook.sol:239: SIMDTESTHook.consult() (src/SIMDTESTHook.sol#239-267) uses a weak PRNG: \"sum < 0 && sum % int256(TWAP_WINDOW) != 0 (src/SIMDTESTHook.sol#265)\"\n[medium/high] incorrect-equality at src/SIMDTESTHook.sol:174: SIMDTESTHook.executeBatch() (src/SIMDTESTHook.sol#174-204) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/SIMDTESTHook.sol:174: Reentrancy in SIMDTESTHook.executeBatch() (src/SIMDTESTHook.sol#174-204):\n[medium/medium] uninitialized-local at src/SIMDTESTHook.sol:251: SIMDTESTHook.consult().low (src/SIMDTESTHook.sol#251) is a local variable never initialized\n[medium/medium] unused-return at src/SIMDTESTHook.sol:206: SIMDTESTHook.unlockCallback(bytes) (src/SIMDTESTHook.sol#206-221) ignores return value by (None,tick,None,None) = poolManager.getSlot0(poolId) (src/SIMDTESTHook.sol#218)\n[medium/medium] unused-return at src/SIMDTESTHook.sol:146: SIMDTESTHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/SIMDTESTHook.sol#146-161) ignores return value by (None,tick,None,None) = poolManager.getSlot0(poolId) (src/SIMDTESTHook.sol#151)\n[medium/medium] unused-return at src/SIMDTESTHook.sol:174: SIMDTESTHook.executeBatch() (src/SIMDTESTHook.sol#174-204) ignores return value by (spot,None,None,None) = poolManager.getSlot0(poolId) (src/SIMDTESTHook.sol#193)\n[low/medium] events-maths at src/SIMDTESTHook.sol:108: SIMDTESTHook.afterInitialize(address,PoolKey,uint160,int24) (src/SIMDTESTHook.sol#108-120) should emit an event for: \n[low/medium] reentrancy-benign at src/SIMDTESTHook.sol:174: Reentrancy in SIMDTESTHook.executeBatch() (src/SIMDTESTHook.sol#174-204):\n[low/medium] reentrancy-benign at src/SIMDTESTHook.sol:206: Reentrancy in SIMDTESTHook.unlockCallback(bytes) (src/SIMDTESTHook.sol#206-221):\n[low/medium] reentrancy-events at src/SIMDTESTHook.sol:163: Reentrancy in SIMDTESTHook._accrue(uint256) (src/SIMDTESTHook.sol#163-169):\n[low/medium] reentrancy-events at src/SIMDTESTHook.sol:174: Reentrancy in SIMDTESTHook.executeBatch() (src/SIMDTESTHook.sol#174-204):\n[low/medium] timestamp at src/SIMDTESTHook.sol:224: SIMDTESTHook._record(int24) (src/SIMDTESTHook.sol#224-235) uses timestamp for comparisons\n[low/medium] timestamp at src/SIMDTESTHook.sol:283: SIMDTESTHook._priceLimit(int24) (src/SIMDTESTHook.sol#283-289) uses timestamp for comparisons\n[low/medium] timestamp at src/SIMDTESTHook.sol:270: SIMDTESTHook.quoteAtTick(int24,uint256) (src/SIMDTESTHook.sol#270-281) uses timestamp for comparisons\n[low/medium] timestamp at src/SIMDTESTHook.sol:174: SIMDTESTHook.executeBatch() (src/SIMDTESTHook.sol#174-204) uses timestamp for comparisons\n[low/medium] timestamp at src/SIMDTESTHook.sol:239: SIMDTESTHook.consult() (src/SIMDTESTHook.sol#239-267) uses timestamp for comparisons","passed":true},{"durationMs":411,"exitCode":0,"name":"aderyn","output":"[high] reentrancy-state-change at src/SIMDTESTHook.sol:200: Reentrancy: State change after external call\n[high] unsafe-casting at src/SIMDTESTHook.sol:160: Unsafe Casting of integers (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/SIMDTEST.sol:9: Large Numeric Literal (4 places)\n[low] literal-instead-of-constant at src/SIMDTESTHook.sol:140: Literal Instead of Constant (11 places)\n[low] state-change-without-event at src/SIMDTESTHook.sol:108: State Change Without Event (2 places)","passed":true},{"durationMs":2060,"exitCode":0,"name":"proof 221c7d0c6460","output":"Compiling 83 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.44s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-3efa3eab/Proof_221c7d0c6460.t.sol:BatchStarvationProofTest\n[PASS] test_batchWithinThreePercentOfTheTWAPExecutes() (gas: 2741405)\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 5.14ms (2.05ms CPU time)\n\nRan 1 test suite in 5.84ms (5.14ms CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"eb091b446f320e3e63d5d2645d321ca3bc8f49107df9389169c3238010caf250","verifiedTreeHash":"698f291a09b141939fd26dfc39ad5f65fd729a04","verifierVersion":"0.1.0+ad90ce4c"}]}