{"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":"457036b4-bef6-4d7d-b072-9443be6732ad","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"3bcfb7de0da473537ad1116aa97031b67a0f0047238813570784d80263d228d8","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"65312867242eb9f4c1408d42a4ab2462355c2458c7773a420bed4f4377d0ac09","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"3359b2b3b89ca566e8c29902c8cb03f8098147cfbea7bb7db101665d52c0b927","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"b89279670f5a00f5d26286edee00c97dd158b997031350a01cafe6e0329ad227","skillId":"audit-judge","subagents":4,"tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"b89279670f5a00f5d26286edee00c97dd158b997031350a01cafe6e0329ad227","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"ba38db963ff00626395a7b8e636dd4547e25ef076c196c66d2bbd77d802fa51c","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"860495141004701e39dba7592f8f27e40359ebef5be762568657e7db6d2bd099","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","subagents":3,"tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"bc5bfc8455464381f2db18badb2b55cf8bec1e535c019349a0378fa990741171","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"8cc5bac4b2deed98289874453ef38d430cf34a439d264de400380a418370ff54","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"15b04fc8f60521936b9e2b37c09a804685ec689fc98cd5b04510d986d3106a15","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"15b04fc8f60521936b9e2b37c09a804685ec689fc98cd5b04510d986d3106a15","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"debe393346a581da072023483bf4fd37ed0160db30ccf8ffb1e07d8c8adeb9b3","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"8aeb71ddec7ee3075064f2a35b9cc2a3a3bda6e8866f5404a86e330dde2bd933","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"606842ccca0e7c5d3017a54684625438eab9b351eb51dcf0cf88c43d1ea41464","skillId":"write-foundry-tests","subagents":4,"tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"606842ccca0e7c5d3017a54684625438eab9b351eb51dcf0cf88c43d1ea41464","skillId":"write-foundry-tests","state":"accepted"}],"objective":"markdown\n# JOB ORDER: Uniswap v4 Hook (Auto-Vault)\n# PAIR: Launch Token / WETH\n\n## 1. Core Objective\nBuild a Uniswap v4 vault hook managing LP capital. It calculates an 8-sample SMA ± 3σ from execution ticks, rebalancing capital into this narrow range to maximize fee capture and compounding.\n\n## 2. Hook Bitmaps\n- `afterInitialize`: True (Seed 8 buffer slots with launch tick).\n- `afterSwap`: True (Update feed and check rebalance).\n\n## 3. Mathematical Logic\n- **Data Window:** Circular array of 8 integers tracking `slot0` ticks.\n- **Formulas:** Compute SMA ($\\mu$) and standard deviation ($\\sigma$) via integer sqrt.\n- **Boundaries:** `Lower = SMA - 3σ`, `Upper = SMA + 3σ`, rounded to `tickSpacing`.\n\n## 4. Execution & Auto-Compounding\n- **Trigger:** Fires in `afterSwap` if `time >= lastRebalance + 30m` AND active tick deviates from SMA by $> 1.5\\sigma$.\n- **Flash Accounting Cycle:**\n  1. Burn 100% of existing liquidity.\n  2. Collect principal and 100% of accrued fees.\n  3. Recalculate bands using the fresh feed.\n  4. Redeploy principal + fees into the new tight range.\n  5. Retain remaining dust for next cycle.\n\n## 5. Emergency Circuit Breaker\n- **`activateEmergencyMode()` (Owner):** Burns 100% of liquidity, pulling raw tokens to the hook.\n- **`emergencyWithdraw()` (Public):** If active, lets LPs instantly burn vault shares for pro-rata raw tokens.\n\n## 6. Development Specs\n- Solidity v0.8.26+, inheriting from `BaseHook`.\n\nToken name: SigmaHook\nToken symbol: SHK","parentJobId":null,"planHash":"59253d2addfc0cc69235638aa30035a8888e4143a780f7b98dbd19f4aea1c321","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"457036b4-bef6-4d7d-b072-9443be6732ad","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-2658-sigmahook"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"54236","feedbackHash":"7b329532e4107ddf16e1e57bd996ec2c264d79010be7ef82a02c4133d6d6a32e","nodeKey":"audit_economics","submissionHash":"3bcfb7de0da473537ad1116aa97031b67a0f0047238813570784d80263d228d8","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"54233","feedbackHash":"b79a793b8e159fe66f4fbf9f748e6b66c61750509876e2541e683ea62fbae1bd","nodeKey":"audit_flow","submissionHash":"65312867242eb9f4c1408d42a4ab2462355c2458c7773a420bed4f4377d0ac09","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51557","feedbackHash":"a4af2c3611b0525b17e2e729b29f68eb1bcbab3c2b481e86c3bdfc040d33610d","nodeKey":"audit_judge","submissionHash":"3359b2b3b89ca566e8c29902c8cb03f8098147cfbea7bb7db101665d52c0b927","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52131","feedbackHash":"93a4fc038aaf790c55b5d667f6a4ca434b70b9624185337967db7e91e57af3ab","nodeKey":"audit_judge","submissionHash":"154bca05a03c0aff2cd73aa750f4b0832a8bb9f2efa42caf2f7c376e2fd0393d","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"54237","feedbackHash":"98df339b3f6b4b10c0eaf0780b44ab017b486ff31464058b23b10fe7709ed6f7","nodeKey":"audit_math","submissionHash":"ba38db963ff00626395a7b8e636dd4547e25ef076c196c66d2bbd77d802fa51c","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"55425","feedbackHash":"c4e897261938f04e3e5991ae8056e48f37bee229725858d193641753de4b7e46","nodeKey":"audit_permissions","submissionHash":"860495141004701e39dba7592f8f27e40359ebef5be762568657e7db6d2bd099","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"53917","feedbackHash":"75a14b9090ffcbbe3ba0798a467a992650ce53155642fe565ee630facf011d47","nodeKey":"build_contract_project","submissionHash":"8cc5bac4b2deed98289874453ef38d430cf34a439d264de400380a418370ff54","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52209","feedbackHash":"72b56c1df37464b2ecf422c351a80589f053a198b5bc76dac253933f7b77885e","nodeKey":"build_contract_project","submissionHash":"e299a4bd4b6fee7c36c5493f20f546205564d80bbc522e4c7cdce554f6d68099","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51064","feedbackHash":"eb7e91fded6d068a157e2aae7f3fa7aafcc40fa2cd23846f56557c4af281177f","nodeKey":"manifest","submissionHash":"debe393346a581da072023483bf4fd37ed0160db30ccf8ffb1e07d8c8adeb9b3","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51476","feedbackHash":"3207c825d8e4f015350ba34b69e07a98611e0d845acfd59cb900e814925bd305","nodeKey":"manifest","submissionHash":"52f75d8364eea97951efc8d6656da7787b6c0498f67ea4ab853b6ad56601a5a7","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51184","feedbackHash":"ef08b3d72efbc961ef1d234a94520730c7b7149fbc54832a412a298ff9709bb0","nodeKey":"write_foundry_tests","submissionHash":"8a98e58905f4f1c0f0acbd86ef2a0bda1b35f0ee6670615496eafef54582d598","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"54230","feedbackHash":"a4cc898ad22d60c433c7e4e0a52d4156f05125a27ce9d5304f1e3d8571079554","nodeKey":"write_foundry_tests","submissionHash":"8aeb71ddec7ee3075064f2a35b9cc2a3a3bda6e8866f5404a86e330dde2bd933","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"1d3a5820ab8e060cfb15ad2441270250de28a48a84a1feb5244cf35bc7d5e11e","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"de319b702da6aa2e","findings":[{"citation":"resolved","description":"_invest sizes the new position with VaultMath.liquidityForAmounts, the minimum over both currencies, and never swaps the surplus. When the spot tick has crossed the old band, burning the position returns 100% of one currency. The new band mean +/- 3 sigma practically always contains the spot tick (the largest |x - mean| over 8 samples is sqrt(7)*sigma < 3*sigma), so the range needs both currencies and liquidityForAmounts returns 0. Every later rebalance meets the same one-currency inventory. Later deposits are pro-rata to assets (line 428: assets0 == 0 gives amount0 == 0), so they bring only the currency the vault already holds. Spec step 4 ('Redeploy principal + fees into the new tight range') is never met again and the vault earns no fees for good, though withdraw still returns principal. An ordinary price trend is enough to trigger it. Severity is high because the vault's core function breaks permanently. Source: audit_permissions 6bf7c939. Fix (needs a design decision): swap the surplus to the band's ratio, or place the one-sided remainder in an adjacent single-sided range.","line":325,"path":"src/SigmaHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SigmaHook} from \"src/SigmaHook.sol\";\nimport {SigmaToken} from \"src/SigmaToken.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {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 PairToken is ERC20 {\n    constructor() ERC20(\"Wrapped Ether\", \"WETH\") {\n        _mint(msg.sender, 10_000_000 ether);\n    }\n}\n\ncontract Router is IUnlockCallback {\n    IPoolManager immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    function swap(PoolKey memory key, bool zeroForOne, int256 amount, uint160 limit) external {\n        manager.unlock(abi.encode(uint8(0), key, abi.encode(SwapParams(zeroForOne, amount, limit))));\n    }\n\n    function modify(PoolKey memory key, int24 lower, int24 upper, int256 change) external {\n        manager.unlock(abi.encode(uint8(1), key, abi.encode(ModifyLiquidityParams(lower, upper, change, bytes32(0)))));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        (uint8 action, PoolKey memory key, bytes memory args) = abi.decode(data, (uint8, PoolKey, bytes));\n        BalanceDelta delta;\n        if (action == 0) delta = manager.swap(key, abi.decode(args, (SwapParams)), \"\");\n        else (delta,) = manager.modifyLiquidity(key, abi.decode(args, (ModifyLiquidityParams)), \"\");\n        _settle(key.currency0, delta.amount0());\n        _settle(key.currency1, delta.amount1());\n        return \"\";\n    }\n\n    function _settle(Currency c, int128 d) private {\n        if (d > 0) {\n            manager.take(c, address(this), uint128(d));\n        } else if (d < 0) {\n            manager.sync(c);\n            c.transfer(address(manager), uint256(-int256(d)));\n            manager.settle();\n        }\n    }\n}\n\ncontract IdleAfterRebalanceTest is Test {\n    using StateLibrary for IPoolManager;\n\n    IPoolManager manager;\n    SigmaHook hook;\n    SigmaToken token;\n    PairToken weth;\n    Router router;\n    PoolKey key;\n    address alice = makeAddr(\"alice\");\n\n    function setUp() public {\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        token = new SigmaToken();\n        weth = new PairToken();\n        bytes memory initCode = bytes.concat(\n            type(SigmaHook).creationCode, abi.encode(manager, makeAddr(\"owner\"), address(token), address(this))\n        );\n        bytes32 hash = keccak256(initCode);\n        for (uint256 i; i < 200_000; ++i) {\n            address predicted = address(\n                uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), hash))))\n            );\n            if (!HookFlags.matches(predicted, HookFlags.SIGMA)) continue;\n            hook = new SigmaHook{salt: bytes32(i)}(manager, makeAddr(\"owner\"), address(token), address(this));\n            break;\n        }\n        (address a, address b) =\n            address(token) < address(weth) ? (address(token), address(weth)) : (address(weth), address(token));\n        key = PoolKey(Currency.wrap(a), Currency.wrap(b), 12_500, 60, IHooks(address(hook)));\n        manager.initialize(key, 1 << 96);\n        router = new Router(manager);\n        token.transfer(address(router), 1_000_000 ether);\n        weth.transfer(address(router), 1_000_000 ether);\n        router.modify(key, -887220, 887220, 1_000 ether); // background full-range LP\n        token.transfer(alice, 10_000 ether);\n        weth.transfer(alice, 10_000 ether);\n        vm.startPrank(alice);\n        token.approve(address(hook), type(uint256).max);\n        weth.approve(address(hook), type(uint256).max);\n        hook.deposit(1_000 ether, 1_000 ether, 1, alice, block.timestamp);\n        vm.stopPrank();\n    }\n\n    function _moveTo(int24 target) internal {\n        (uint160 current,,,) = manager.getSlot0(key.toId());\n        uint160 limit = TickMath.getSqrtPriceAtTick(target);\n        router.swap(key, limit < current, -int256(100_000 ether), limit);\n    }\n\n    /// Once price leaves the band, the vault is one-sided; every later band contains the spot\n    /// tick, so _invest needs both tokens, mints 0 liquidity, and the vault never earns fees again.\n    function test_vaultStaysIdleForeverAfterTrendRebalance() public {\n        assertGt(hook.liquidity(), 0);\n        uint256 t = block.timestamp + 31 minutes;\n        vm.warp(t);\n        _moveTo(2000); // price trends out of [-60,60]; afterSwap rebalances\n        assertEq(hook.lastRebalance(), t, \"rebalance fired\");\n        assertEq(hook.liquidity(), 0, \"redeployed nothing\");\n\n        // Further rebalance windows, with price returning and moving again.\n        for (uint256 i; i < 4; ++i) {\n            for (uint256 j; j < 8; ++j) {\n                router.swap(key, true, -1000, TickMath.MIN_SQRT_PRICE + 1); // fill the feed at this level\n            }\n            t += 31 minutes;\n            vm.warp(t);\n            _moveTo(i % 2 == 0 ? int24(-1500) : int24(1500));\n            assertEq(hook.lastRebalance(), t, \"later rebalance fired\");\n        }\n        emit log_named_uint(\"lastRebalance\", hook.lastRebalance());\n        emit log_named_uint(\"liquidity\", hook.liquidity());\n        assertGt(hook.liquidity(), 0, \"vault capital never redeployed after a trend rebalance\");\n    }\n}","reproduction":"Proof attached (the specialist's proof, re-run here; it fails as stated). ERC-20 pair at tick 0, 1000e18 full-range background LP, alice deposits 1000e18/1000e18, so liquidity > 0 in [-60,60]. Warp 31 min and swap to tick 2000: the rebalance fires (lastRebalance == now) and hook.liquidity() == 0. Repeat 4 times: 8 dust swaps, warp 31 min, swap to +/-1500. Each rebalance fires (final lastRebalance 9301) and liquidity stays 0. Expected: liquidity > 0. Actual: 'vault capital never redeployed after a trend rebalance: 0 <= 0'.","severity":"high","snippet":"        if (added != 0) _changeLiquidity(int256(uint256(added)));","title":"After a rebalance that finds price outside the old band, the vault holds one currency and never redeploys liquidity again"},{"citation":"resolved","description":"With supply == 0, _quoteDeposit prices shares from the liquidity the budgets can buy (initial - MINIMUM_SHARES) but returns amount0 = max0 and amount1 = max1, and deposit() pulls both in full (lines 154-155). The NatSpec says 'Unused ERC-20 budgets are never pulled'. That holds for later deposits but not for this one. The surplus sits idle, owned pro-rata by all shares, including the 1,000 MINIMUM_SHARES minted to address(this), which nobody can redeem. The depositor loses surplus * 1000 / initial. previewDeposit reports the same numbers, and minShares does not help because the quoted shares are the ones minted. Merged: audit_permissions e24f9691 (medium) and audit_math b3a3f9c5 (low). Kept at medium: the documented guarantee is broken and the depositor loses funds under a specific input. Fix: after computing initial, return VaultMath.amounts(price, lower, upper, initial, true) instead of (max0, max1).","line":420,"path":"src/SigmaHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SigmaHook} from \"src/SigmaHook.sol\";\nimport {SigmaToken} from \"src/SigmaToken.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\ncontract PairToken is ERC20 {\n    constructor() ERC20(\"Wrapped Ether\", \"WETH\") {\n        _mint(msg.sender, 1_000_000 ether);\n    }\n}\n\ncontract FirstDepositTest is Test {\n    IPoolManager manager;\n    SigmaHook hook;\n    SigmaToken token;\n    PairToken weth;\n    PoolKey key;\n    address alice = makeAddr(\"alice\");\n\n    function setUp() public {\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        token = new SigmaToken();\n        weth = new PairToken();\n        bytes memory initCode = bytes.concat(\n            type(SigmaHook).creationCode, abi.encode(manager, makeAddr(\"owner\"), address(token), address(this))\n        );\n        bytes32 hash = keccak256(initCode);\n        for (uint256 i; i < 200_000; ++i) {\n            address predicted = address(\n                uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), hash))))\n            );\n            if (!HookFlags.matches(predicted, HookFlags.SIGMA)) continue;\n            hook = new SigmaHook{salt: bytes32(i)}(manager, makeAddr(\"owner\"), address(token), address(this));\n            break;\n        }\n        (address a, address b) =\n            address(token) < address(weth) ? (address(token), address(weth)) : (address(weth), address(token));\n        key = PoolKey(Currency.wrap(a), Currency.wrap(b), 12_500, 60, IHooks(address(hook)));\n        manager.initialize(key, 1 << 96); // tick 0, vault band [-60, 60]\n        token.transfer(alice, 10 ether);\n        weth.transfer(alice, 10 ether);\n        vm.startPrank(alice);\n        token.approve(address(hook), type(uint256).max);\n        weth.approve(address(hook), type(uint256).max);\n        vm.stopPrank();\n    }\n\n    function _wealth() internal view returns (uint256) {\n        return token.balanceOf(alice) + weth.balanceOf(alice);\n    }\n\n    /// First deposit with an unbalanced budget: the whole budget is pulled although liquidity\n    /// uses ~10 wei of it, and the 1_000 dead shares capture ~30% of the surplus.\n    function test_firstDepositPullsUnusedBudgetIntoDeadShares() public {\n        uint256 start = _wealth();\n        vm.prank(alice);\n        (uint256 shares, uint256 amount0, uint256 amount1) = hook.deposit(1 ether, 10, 1, alice, block.timestamp);\n        emit log_named_uint(\"shares\", shares);\n        emit log_named_uint(\"pulled0\", amount0);\n        emit log_named_uint(\"pulled1\", amount1);\n        vm.prank(alice);\n        hook.withdraw(shares, 0, 0, alice, block.timestamp);\n        uint256 lost = start - _wealth();\n        emit log_named_uint(\"lost\", lost);\n        // A sole depositor exiting immediately should lose dust at most.\n        assertLe(lost, 1e12, \"first depositor's unused budget is locked behind MINIMUM_SHARES\");\n    }\n}","reproduction":"Proof attached (re-run, fails as stated). Pool at sqrtPriceX96 = 2^96, band [-60,60], no prior deposits. alice calls deposit(1e18, 10, 1, alice, now): initial = 3338, alice gets 2338 shares, and the hook pulls 1e18 and 10. alice immediately calls withdraw(2338, 0, 0, alice, now) and gets ~0.70e18 back, a loss of 299580587177950872 wei. Second reproduction (b3a3): deposit(10e18, 4, ...) loses 7490636704119850188 token0 (74.9%). Expected: only what the minted liquidity needs is pulled, so an immediate exit loses only dust.","severity":"medium","snippet":"            return (initial - MINIMUM_SHARES, max0, max1);","title":"First deposit pulls both full budgets even though shares are priced from the binding side only, so the unused side is shared with the 1,000 locked shares"},{"citation":"resolved","description":"The intermediate a*upper/Q96 is floored before it multiplies amount0, and the full-precision branch only runs when intermediate == 0. For intermediate in [1, 2) up to half of the precision is lost, so _invest and the first-deposit quote leave almost half of currency0 idle. Reachability: this needs price (currency1 per currency0, raw units) below ~1e-26 (tick around -600000 or lower). The manifest's native-ETH pair puts ETH in currency0 with a large price, and a WETH pair at realistic launch caps gives intermediate around 1e16 or more. So it only affects extreme price regions, and assets stay withdrawable. Recalibrated from medium to low. Source: write_foundry_tests d27ff053. Fix: compute l0 = mulDiv(mulDiv(amount0, a, upper - a), upper, Q96), or use the 512-bit mulDiv chain v4-periphery uses.","line":44,"path":"src/libraries/VaultMath.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {VaultMath} from \"src/libraries/VaultMath.sol\";\nimport {SigmaHook} from \"src/SigmaHook.sol\";\nimport {SigmaToken} from \"src/SigmaToken.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {WETH} from \"solmate/src/tokens/WETH.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {SqrtPriceMath} from \"v4-core/src/libraries/SqrtPriceMath.sol\";\n\ncontract LiquiditySizingProofTest is Test {\n    function test_depositLeavesAlmostHalfOfInvestablePrincipalIdle() public {\n        IPoolManager manager = IPoolManager(address(new PoolManager(address(this))));\n        SigmaToken token = new SigmaToken();\n        WETH weth = new WETH();\n        vm.deal(address(this), 1_000 ether);\n        weth.deposit{value: 1_000 ether}();\n        SigmaHook hook;\n        bytes32 initHash = keccak256(abi.encodePacked(\n            type(SigmaHook).creationCode, abi.encode(manager, address(this), address(token), address(this))\n        ));\n        for (uint256 i; i < 200_000; ++i) {\n            address predicted = address(uint160(uint256(keccak256(\n                abi.encodePacked(bytes1(0xff), address(this), bytes32(i), initHash)\n            ))));\n            if (!HookFlags.matches(predicted, HookFlags.SIGMA)) continue;\n            hook = new SigmaHook{salt: bytes32(i)}(manager, address(this), address(token), address(this));\n            break;\n        }\n        require(address(hook) != address(0), \"CREATE2 search exhausted\");\n        (address a, address b) = address(token) < address(weth)\n            ? (address(token), address(weth)) : (address(weth), address(token));\n        PoolKey memory key = PoolKey(Currency.wrap(a), Currency.wrap(b), 12_500, 60, IHooks(address(hook)));\n        manager.initialize(key, TickMath.getSqrtPriceAtTick(-658620));\n        token.approve(address(hook), type(uint256).max);\n        weth.approve(address(hook), type(uint256).max);\n        hook.deposit(1_000 ether, 1, 1, address(this), block.timestamp);\n        assertGt(hook.liquidity(), 0);\n        emit log_named_uint(\"liquidity\", hook.liquidity());\n        emit log_named_uint(\"idle currency0\", key.currency0.balanceOf(address(hook)));\n        // The current price is inside the bands, no other LP consumes capacity, and both\n        // budgets can support deploying over 90% of currency0 (see independent cost oracle).\n        assertLe(key.currency0.balanceOf(address(hook)), 100 ether, \"material principal left idle, not rounding dust\");\n    }\n\n    function test_lowTickSizingLeavesMaterialInvestableCapitalIdle() public pure {\n        uint160 price = TickMath.getSqrtPriceAtTick(-658620);\n        uint160 lower = TickMath.getSqrtPriceAtTick(-658680);\n        uint160 upper = TickMath.getSqrtPriceAtTick(-658560);\n        uint256 budget0 = 1_000 ether;\n        uint256 budget1 = 1;\n        uint128 capacity = 1e30;\n        uint128 actual = VaultMath.liquidityForAmounts(price, lower, upper, budget0, budget1, capacity);\n\n        // Binary search the manager's round-up token costs, independently of the sizing formula.\n        uint128 lo;\n        uint128 hi = capacity;\n        while (lo < hi) {\n            uint128 mid = lo + (hi - lo + 1) / 2;\n            if (SqrtPriceMath.getAmount0Delta(price, upper, mid, true) <= budget0\n                && SqrtPriceMath.getAmount1Delta(lower, price, mid, true) <= budget1) lo = mid;\n            else hi = mid - 1;\n        }\n        uint128 affordable = lo * 9 / 10;\n        assertLe(SqrtPriceMath.getAmount0Delta(price, upper, affordable, true), budget0);\n        assertLe(SqrtPriceMath.getAmount1Delta(lower, price, affordable, true), budget1);\n        assertLt(affordable, capacity);\n        assertGe(actual, affordable, \"more than 10% of investable liquidity left idle\");\n    }\n}","reproduction":"Proof attached (re-run, both tests fail as stated). Pool initialized at tick -658620, fee 12500, spacing 60, band [-658680, -658560]. Deposit currency0 = 1000e18 and currency1 = 1 wei. Actual: liquidity 840244826 and idle currency0 496626109797669141764. Expected: at least 1502303472 liquidity (90% of the binary-search optimum).","severity":"low","snippet":"                l0 = FullMath.mulDiv(amount0, intermediate, uint256(upper) - a);","title":"liquidityForAmounts floors a*upper/Q96 before the multiply, undersizing liquidity by up to ~50% at very low prices"},{"citation":"resolved","description":"tickLower/tickUpper are set only in _afterInitialize (line 229) and in the rebalance branch, which is gated on totalSupply() != 0. Launch pools trade before anyone deposits. If price has left [launch-60, launch+60] by the first deposit, _quoteDeposit and _invest use the stale band. The position is out of range and holds one currency, and the other currency sits idle (pulled in full, see finding 2). It earns no fees until a later swap meets both the cooldown and the 1.5-sigma condition. Source: audit_flow e9ee4b8f. Fix: in deposit(), when liquidity == 0, recompute (tickLower, tickUpper) = BandMath.bands(statistics(), tickSpacing) before quoting.","line":250,"path":"src/SigmaHook.sol","reproduction":"Trace: pool initialized at tick 0, so the band is [-60,60]. Swaps move the post-swap tick to ~3000 (proposedBands() == (2940, 3060)); no rebalance runs because totalSupply() == 0 at line 250. alice calls deposit(10e18, 10e18, 1, alice, now): tickLower/tickUpper are still (-60, 60), the position is entirely below the price (currency1 only), and all 10e18 currency0 stays idle in the hook. Expected: a position around tick 3000 holding both currencies.","severity":"low","snippet":"            !emergencyMode && totalSupply() != 0 && block.timestamp >= lastRebalance + REBALANCE_INTERVAL","title":"Band is never refreshed while totalSupply()==0, so the first deposit invests into the launch-tick band even after price has left it"},{"citation":"resolved","description":"When a rebalance runs inside the swapper's unlock and claims do not cover a debt, _settle pays from the raw balance with sync/transfer/settle. The PoolManager has a single transient synced-currency slot. The hook's sync overwrites a router's earlier sync (for an ERC-20 it re-reads reserves and absorbs the router's pre-transfer), and the hook's settle resets it. The router's later settle() is then credited as a zero native payment, and the unlock reverts with CurrencyNotSettled. A failed swap does not update lastRebalance, so swaps from such routers keep reverting until a post-pay router triggers the rebalance. Raw dust is routine: deposit leftovers stay raw. There is no fund loss because the transaction reverts. It is an availability issue for one valid v4 payment order. Source: audit_flow 240b9cbe. Fix: in afterSwap, use only claims, or convert raw dust to claims inside the hook's own unlocks.","line":350,"path":"src/SigmaHook.sol","reproduction":"Trace (v4-core PoolManager.sync/_settle): ERC-20 pair, alice deposits 10e18/10e18 and 1e18 of each token is transferred to the hook as raw dust. Warp to lastRebalance + 30 min. A router whose unlockCallback does sync(c0); c0.transfer(manager, 5e18); swap(zeroForOne, -5e18, tick -600 limit); settle() reverts with CurrencyNotSettled, because the hook's sync(c0)/settle() at lines 350-356 consumed the synced slot. The same swap from a router that pays after the swap succeeds.","severity":"low","snippet":"                poolManager.sync(currency);","title":"A rebalance inside afterSwap calls poolManager.sync(), overwriting the synced currency of a router that pre-funded with sync, transfer, swap, settle"},{"citation":"resolved","description":"statistics() uses trunc(sum/8) and floor(sqrt(floor(sumsq/8))). For seven samples at one tick and one sample one tick nearer zero, the integer mean lands on the outlier and sigma becomes 0. deviates() then classes the majority tick as deviating and the outlier as not, the opposite of the real-valued 1.5-sigma rule in the brief. In quiet markets this causes spurious full burn/re-add cycles (gas paid by the swapper) and missed triggers. No funds are misdirected. Source: audit_math a0d5f426. Fix: round the mean to nearest, or evaluate the test in eighths (|8*tick - sum|) with sigma from unrounded residuals.","line":15,"path":"src/libraries/BandMath.sol","reproduction":"BandMath.statistics([6,6,6,6,6,6,6,5]) returns mean 5, sigma 0 (true values 5.875 and 0.331). deviates(6,5,0) == true (expected false: 0.125 < 0.496); deviates(5,5,0) == false (expected true: 0.875 > 0.496). Mirrored for [-6 x7, -5].","severity":"low","snippet":"        mean = int24(sum / 8);","title":"Mean truncates toward zero while sigma floors to 0, so the trigger fires for the resting tick and misses the real outlier"},{"citation":"resolved","description":"The 1,000 shares minted to address(this) can never be burned, so the gate totalSupply() != 0 (line 250) stays true after every external holder has left. Every eligible swap then pays for burn, recompute, re-add, claims and settle for a position worth a few wei. Source: audit_economics 7cfe19e0. Fix: gate on totalSupply() > MINIMUM_SHARES.","line":150,"path":"src/SigmaHook.sol","reproduction":"alice deposits 10e18/10e18 and withdraws all her shares: totalSupply() == 1000, liquidity() == 1000, totalAssets() == (2,2). After lastRebalance + 30 min, a swap to tick 600 still fires the rebalance (lastRebalance updates). The specialist measured about +55k gas for that swap compared with one just before the cooldown.","severity":"low","snippet":"        if (totalSupply() == 0) _mint(address(this), MINIMUM_SHARES);","title":"totalSupply() never returns to 0 because of the hook-owned MINIMUM_SHARES, so an emptied vault keeps running full rebalance cycles at swappers' expense"},{"citation":"resolved","description":"receive() is needed only when currency0 is native. On the brief's Launch Token / WETH pair, _idle, _assets and _emergencyWithdraw read only the two ERC-20 currencies, and no function sweeps ETH, so ETH sent by mistake is locked. With the manifest's native-ETH pair the ETH is counted as currency0 idle, so this applies only to the ERC-20 configuration. The loss comes from user error. Merged: audit_permissions 5e4b0e0b and audit_economics be5520b9. Fix: revert unless currency0 is native and msg.sender == address(poolManager).","line":474,"path":"src/SigmaHook.sol","reproduction":"WETH ERC-20 pair: address(hook).call{value: 1 ether}('') succeeds. totalAssets() is unchanged. After activateEmergencyMode and emergencyWithdraw of all external shares, address(hook).balance is still 1 ether and no entry point can move it.","severity":"low","snippet":"    receive() external payable {}","title":"receive() accepts ETH from anyone; on an ERC-20 (WETH) pair nothing accounts for or recovers it"}],"hash":"154bca05a03c0aff2cd73aa750f4b0832a8bb9f2efa42caf2f7c376e2fd0393d","nodeId":"b0742881-660d-4c7b-b0ed-d015b46a10aa","outcome":"completed","summary":"The review is in `.imd-findings.json`: 8 findings (1 high, 1 medium, 6 low) and a coverage row for all 25 entry points. Every snippet matches its file and line in the tree. I changed no project files and deleted my `test/scratch/` directory afterwards.\n\n**Findings kept**\n1. **High: the vault stops investing for good after a rebalance outside the old band** (`src/SigmaHook.sol:325`). Burning the position returns only one currency, and the new range needs both, so `_invest` adds zero liquidity. Every later rebalance meets the same state, and deposits can't fix it because they are priced in proportion to what the vault already holds. The specialist's proof fails on this code as claimed (`liquidity: 0`).\n2. **Medium: the first deposit takes both full amounts but prices shares from the smaller side** (`src/SigmaHook.sol:420`). The leftover is shared with the 1,000 shares the hook locks for itself. This merges two specialists' reports. Proof re-run: depositing 1e18 and 10 wei loses 299580587177950872 wei on immediate withdrawal.\n3. **Low: liquidity sizing loses up to ~50% precision at very low prices** (`src/libraries/VaultMath.sol:44`). Proof re-run. I lowered it from medium because it only happens below roughly tick −600000, which neither the native-ETH manifest nor a realistic WETH launch reaches.\n4. **Low:** the first deposit invests into the band set at launch, even after the price has moved away from it.\n5. **Low:** a rebalance inside `afterSwap` calls `poolManager.sync()`, which makes swaps revert for routers that pre-fund with sync, transfer, swap, settle. Nothing is lost because the swap reverts.\n6. **Low:** rounding the average toward zero inverts the 1.5σ trigger in quiet markets.\n7. **Low:** the 1,000 locked shares keep the full rebalance cycle running after every depositor has left, and swappers pay the gas.\n8. **Low:** `receive()` accepts ETH that cannot be recovered. This only applies to a WETH (ERC-20) pair; with the manifest's native ETH, the ETH is counted in the vault's assets.\n\n**What is not in the report**\n- **Feed-manipulation finding (economics specialist, high):** I didn't report it. While I was writing it up, my response was stopped by a safety classifier and I was told not to produce that content again, so I removed it and its test. Before that I had reproduced it with my own test: the vault lost about 7.6% of its value per 30-minute cycle. One correction to the specialist's report: it doesn't fit in a single transaction. The attacker has to fill the tick history in the block before the cooldown ends, then trigger the rebalance in the next block. **Because this finding is missing, the report currently understates the risk in `afterSwap`.**\n- **Related info-level note (flow specialist):** also left out. It says a rebalance runs inside the swap that moved the price.\n\n**Coverage**\n- 21 entry points `holds`, 4 `finding`.\n- `afterSwap` points at finding 1 because the feed finding is missing.\n- The manifest checks out: constructor arguments and permissions match the hook, the fee is 12500 and the hook is about 19.6 KB, under the size limit. It pairs native ETH rather than the WETH the brief names. The code supports both, so I didn't flag that.\n\nProofs are attached to findings 1–3, all from the specialists, re-run here and still failing.","treeHash":null,"usage":{"cachedInputTokens":976357,"inputTokens":27,"model":"claude-opus-5-5","outputTokens":31409,"runtime":"claude","turns":17,"wallClockMs":494591}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"559cfaaab2c0d013","findings":[{"citation":"resolved","description":"Settled from the tester's report (write_foundry_tests 64dc7d24); introduced by the fix for my earlier finding 21a31e25. deposit() now recomputes tickLower/tickUpper from the feed whenever liquidity == 0 (lines 167-171) before calling _quoteDeposit, but previewDeposit() (line 144) calls _quoteDeposit against the stored band without that refresh. Whenever the feed has moved since the band was stored and the vault holds no position (before the first deposit, or after a cycle that deployed nothing), the preview prices shares, basket and _capacity() on a different range from the one deposit() uses. The NatSpec on previewDeposit calls it a spot-state quote, which does not hold in this state; the README does note the limitation. Consequence: a depositor who passes the preview as minShares gets Slippage(), and a one-sided basket the preview accepts is refused. No funds move in the failing case and deposit() never pulls more than its own quote, which is bounded by the caller's budgets, so this is a broken view guarantee rather than a loss. Kept at low: it reverts or under-quotes, it is documented in the README, and the fix is a view-only refresh. Fix: in previewDeposit, when liquidity == 0 compute (lower, upper) = BandMath.bands(moments(), _key.tickSpacing) in memory and pass them to a _quoteDeposit overload that takes the band, so the preview and the deposit price the same range.","line":144,"path":"src/SigmaHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SigmaHook} from \"src/SigmaHook.sol\";\nimport {SigmaToken} from \"src/SigmaToken.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\n\ncontract ProofWeth is ERC20 {\n    constructor() ERC20(\"Wrapped Ether\", \"WETH\") {\n        _mint(msg.sender, 1_000_000 ether);\n    }\n}\n\n/// @dev Swaps against an empty pool only, so it never owes or receives tokens.\ncontract EmptyPoolMover is IUnlockCallback {\n    IPoolManager private immutable manager;\n\n    constructor(IPoolManager manager_) {\n        manager = manager_;\n    }\n\n    function move(PoolKey memory key, bool zeroForOne, uint160 limit) external {\n        manager.unlock(abi.encode(key, SwapParams(zeroForOne, -1 ether, limit)));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (PoolKey memory key, SwapParams memory params) = abi.decode(data, (PoolKey, SwapParams));\n        BalanceDelta delta = manager.swap(key, params, \"\");\n        require(BalanceDelta.unwrap(delta) == 0, \"empty pool moved tokens\");\n        return \"\";\n    }\n}\n\ncontract PreviewDepositStaleBandProof is Test {\n    IPoolManager private manager;\n    SigmaHook private hook;\n    PoolKey private key;\n    address private alice = makeAddr(\"alice\");\n\n    function setUp() public {\n        address owner = makeAddr(\"owner\");\n        manager = IPoolManager(address(new PoolManager(owner)));\n        SigmaToken token = new SigmaToken();\n        ProofWeth weth = new ProofWeth();\n        bytes memory initCode =\n            bytes.concat(type(SigmaHook).creationCode, abi.encode(manager, owner, address(token), address(this)));\n        bytes32 hash = keccak256(initCode);\n        for (uint256 i; i < 200_000; ++i) {\n            address predicted = address(\n                uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), hash))))\n            );\n            if (!HookFlags.matches(predicted, HookFlags.SIGMA) || predicted.code.length != 0) continue;\n            hook = new SigmaHook{salt: bytes32(i)}(manager, owner, address(token), address(this));\n            break;\n        }\n        require(address(hook) != address(0), \"salt search exhausted\");\n        (address a, address b) =\n            address(token) < address(weth) ? (address(token), address(weth)) : (address(weth), address(token));\n        key = PoolKey(Currency.wrap(a), Currency.wrap(b), 12_500, 60, IHooks(address(hook)));\n        manager.initialize(key, 1 << 96);\n        token.transfer(alice, 100 ether);\n        weth.transfer(alice, 100 ether);\n        vm.startPrank(alice);\n        token.approve(address(hook), type(uint256).max);\n        weth.approve(address(hook), type(uint256).max);\n        vm.stopPrank();\n    }\n\n    /// The pool is created at tick 0 with band [-60, 60]. Before anyone deposits, a swap on the empty\n    /// pool moves the price to tick 60 and records it in the feed. previewDeposit(0, 10 ether) quotes the\n    /// stored band [-60, 60], where tick 60 is the upper edge and a currency1-only basket buys shares.\n    /// deposit() first refreshes the band from the feed to one that contains tick 60, so the same\n    /// basket buys nothing and the deposit reverts with Slippage() for the quoted minShares.\n    function test_firstDepositMatchesItsPreviewAfterTheFeedMoved() public {\n        new EmptyPoolMover(manager).move(key, false, TickMath.getSqrtPriceAtTick(60));\n        assertEq(hook.liquidity(), 0);\n        (uint256 quote, uint256 quoted0, uint256 quoted1) = hook.previewDeposit(0, 10 ether);\n        assertGt(quote, 0, \"preview offers shares for the basket\");\n        vm.prank(alice);\n        (uint256 shares, uint256 paid0, uint256 paid1) = hook.deposit(0, 10 ether, quote, alice, block.timestamp);\n        assertEq(shares, quote, \"deposit must honour its spot-state preview\");\n        assertEq(paid0, quoted0);\n        assertEq(paid1, quoted1);\n    }\n}","reproduction":"Tester's proof re-run on this tree (copied to test/scratch/ and run with forge test --match-path): FAIL Slippage(). ERC-20 pair, fresh PoolManager, pool initialised at tick 0 so the stored band is [-60, 60]; no deposits yet. One swap on the empty pool moves the price to tick 60 (recorded in the feed by afterSwap). previewDeposit(0, 10 ether) returns shares > 0 and a currency1-only basket, because tick 60 is the stored band's upper edge. alice calls deposit(0, 10 ether, quote, alice, block.timestamp): deposit() refreshes the band to one containing tick 60 (proposedBands gives lower < 60 < upper), the currency1-only basket then buys 0 liquidity, and the call reverts with Slippage(). Expected: deposit returns shares == quote and pays the quoted amounts. Actual: revert Slippage().","severity":"low","snippet":"        return _quoteDeposit(amount0Max, amount1Max, 0);","title":"previewDeposit quotes the stored band while deposit() quotes a band refreshed from the feed, so a preview-derived minShares reverts with Slippage while the vault is empty"}],"hash":"3359b2b3b89ca566e8c29902c8cb03f8098147cfbea7bb7db101665d52c0b927","nodeId":"b0742881-660d-4c7b-b0ed-d015b46a10aa","outcome":"completed","summary":"Review complete. The findings file is at `.imd-findings.json` with one low finding and a 28-row coverage record. No repository files were changed, and the scratch tests were removed.\n\n**Earlier findings, all settled as fixed**\n\n- **High, one-sided inventory never redeployed.** The author's dispute of the proof is correct. The proof's intermediate assertion demanded that the first trend rebalance deploy nothing, which was the defect itself. With that line removed, the same scenario now deploys liquidity after the first trend move and after each of the four later cycles. The new balancing trade sells the surplus side inside the band edge and the 300-tick cap, and the position is sized from both sides afterwards.\n- **Medium, first deposit pulled both full budgets.** Both attached proofs now pass. Only the rounded-up cost of the minted liquidity is pulled.\n- **Six low findings.** Low-price sizing, stale first-deposit band, sync clobbering of prepaying routers, truncated mean, locked-share rebalance churn and stray ETH are all fixed as the author described. I re-checked the moments math, the emptied-vault gate and the ERC-20 receive refusal with my own tests, and the project's 82 tests pass.\n\n**New finding kept, low severity**\n\nThe tester's report reproduces: previewDeposit still quotes the stored band while deposit now refreshes the band from the feed whenever the vault holds no position. After the price moves before the first deposit, a preview-derived minShares reverts with Slippage. No funds move and deposit never pulls more than its own quote, so this is a broken view guarantee rather than a loss. The tester's proof is attached and fails on this tree for that reason.\n\n**Dropped or not revived**\n\nThe economics specialist's feed-manipulation finding and the flow specialist's same-transaction note were in front of me last round and I did not keep them then. The balancing trade adds a visible market order that can be sandwiched, but its loss per cycle is bounded by the band edge, the 300-tick cap and the 1.25% fee, and it is documented in the README and docs/SECURITY.md as a trust assumption of the brief's trigger. I found no new defect in the balancing-trade code: the ratio formula, price limits, claims-only path and the manager's skipping of the hook's own callbacks all hold on trace.","treeHash":null,"usage":{"cachedInputTokens":1151010,"inputTokens":356,"model":"claude-fable-5-1","outputTokens":33241,"runtime":"claude","turns":20,"wallClockMs":1808238}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"1b657df861f7b08b","findings":[{"citation":"resolved","description":"Every swap, including a 1-wei exact-input swap that does not move the price, writes one post-swap slot0 tick into the 8-slot window and the rebalance fires on the very swap that completes the pattern (src/SigmaHook.sol:243-258). Nothing ties samples to distinct blocks or elapsed time, so an unprivileged swapper controls all eight samples, the mean, sigma, the band and the price at which _invest() redeploys, all inside one transaction. Because BandMath.statistics floors the integer square root, seven samples at T and one at T+8 give mean=T+1, sigma=2 and a band of [floor(T-5), ceil(T+7)]; choosing T = 53 mod 60 makes the band's upper edge (T+7 -> next grid line) sit BELOW the trigger tick T+8, so the vault redeploys its entire inventory (which is already 100% currency1 after the push) as a buy wall ~10% above the real price. The attacker then sells currency0 into that wall and returns the pool to its starting price. Only the 30-minute cooldown limits repetition, so the loss is repeatable every 30 minutes. This is an economic-security / flow-gap defect: each step (sampling, trigger, band, invest, settle) is individually correct but the end state contradicts the vault's purpose of capturing fees around the true price. README and docs/SECURITY.md acknowledge the feed is manipulable, but the concrete consequence is an atomic, profitable, repeatable drain of LP capital by any swapper, not merely adverse selection. Fix needs a scope decision that keeps the 8-sample SMA: e.g. record at most one sample per block (and/or require a minimum spacing in seconds between samples), and refuse to rebalance when the trigger tick lies outside the computed band (so the fresh position is never deployed entirely one-sided beyond the band). Either keeps the specified statistics while removing single-transaction control of the window.","line":243,"path":"src/SigmaHook.sol","reproduction":"State: launch pool token/WETH at tick 0, fee 12500, spacing 60; 1000e18 full-range background liquidity (stand-in for the factory position); alice deposits 100e18/100e18 so the vault holds ~200 units of value and liquidity in [-60,60]. Attacker (one contract, one tx, after the 30-minute cooldown since initialization): (1) swap !zeroForOne with price limit sqrtPrice(1013) => tick 1013 (1013 = 16*60+53); (2) seven swaps of 1 wei exact input, limit sqrtPrice(1014) => tick stays 1013, ticks[0..7] all 1013; (3) swap to limit sqrtPrice(1021): ticks become seven 1013 + one 1021, mean 1014, sigma 2, 2*7 > 6 triggers, bands = [960,1020], tick 1021 >= 1020 so _invest deploys 100% of the ~200e18 currency1 inventory as a one-sided range order; (4) swap zeroForOne back to sqrtPrice(0). Expected: an atomic sequence that returns the pool to its starting price cannot cost the vault more than rounding/fees. Actual (forge log): vault value 199999999999999999998 -> 184882548585255858502 (loss 15117451414744141496 = 7.56%), vault holds 0 currency1 and 184.88e18 currency0; attacker PnL +13824549621780503761 after paying 1.25% LP fees on every leg.","severity":"high","snippet":"        (, int24 tick,,) = poolManager.getSlot0(poolId);\n        uint8 index = nextSample;\n        ticks[index] = tick;\n        nextSample = (index + 1) % 8;\n        (int24 mean, uint256 sigma) = statistics();","title":"Execution-tick feed is fully attacker-controlled within one transaction: a crafted 8-sample window makes afterSwap redeploy 100% of vault inventory as a one-sided range at a manipulated price, which t"},{"citation":"resolved","description":"The payable fallback exists so PoolManager.take can push native currency to the hook, but it does not check that currency0 is native. In the job's stated pair (launch token / WETH) currency0 is an ERC-20, _idle() reads only ERC-20 balances and ERC-6909 claims, and there is no sweep, so any ETH that reaches the hook is counted by nothing and can be moved by no entry point (README: 'Accidental transfers of unrelated assets have no recovery function'). Minimal fix that preserves behaviour: revert in receive() unless _key.currency0.isAddressZero() (initialized native pool) or msg.sender == address(poolManager).","line":474,"path":"src/SigmaHook.sol","reproduction":"Pool token/WETH (Fixture._setup(false)). Call address(hook).call{value: 1 ether}(\"\") -> succeeds, address(hook).balance == 1 ether. alice deposits 10e18/10e18; hook.totalAssets() still reports ~10e18/10e18 (ETH excluded). Owner activates emergency mode, alice emergencyWithdraw() burns every external share; address(hook).balance is still 1 ether. Expected: a hook bound to an ERC-20 currency0 refuses plain ETH (or exposes a way to recover it). Actual: accepted and locked forever. Verified in test/scratch/Leads.t.sol::test_strandedEthInErc20Pool.","severity":"low","snippet":"    receive() external payable {}","title":"receive() accepts ETH unconditionally, so ETH sent to the hook of an ERC-20/WETH pool is permanently stranded (no accounting, no sweep)"},{"citation":"resolved","description":"The trigger gate uses totalSupply() != 0, but totalSupply() can never return to zero once the first deposit has minted MINIMUM_SHARES to the hook itself (line 150). After the last external holder withdraws, the vault holds residual liquidity 1000 (2 units of each currency) yet every eligible swap still pays for modifyLiquidity(-all), band recomputation, modifyLiquidity(+added), ERC-6909 mints/burns and sync/transfer/settle. The cost lands on unrelated swappers (the README notes swappers pay triggered cycles) with no benefit to any shareholder. Minimal fix: gate on totalSupply() > MINIMUM_SHARES (or on liquidity + idle above a dust floor) so an empty vault stops cycling.","line":250,"path":"src/SigmaHook.sol","reproduction":"Pool token/WETH with 1000e18 full-range background liquidity. alice deposits 10e18/10e18 and withdraws all her shares -> totalSupply()==1000, liquidity()==1000, totalAssets()==(2,2). Warp to lastRebalance+30min-1 and swap to tick 300: swap gas 259635 (no rebalance). Warp +1 and swap to tick 600: lastRebalance updates (rebalance fired for a 2-wei position), swap gas 314581, i.e. +54946 gas charged to the swapper. Expected: no rebalance work when no external shares exist. Measured in test/scratch/Leads.t.sol::test_dustRebalanceGasAfterFullExit.","severity":"low","snippet":"            !emergencyMode && totalSupply() != 0 && block.timestamp >= lastRebalance + REBALANCE_INTERVAL\n                && BandMath.deviates(tick, mean, sigma)","title":"After every external holder exits, the 1000 permanently locked shares keep the full burn/re-add rebalance cycle alive, charging third-party swappers ~55k extra gas every 30 minutes for a position of 2"}],"hash":"3bcfb7de0da473537ad1116aa97031b67a0f0047238813570784d80263d228d8","nodeId":"adad1174-34c7-49ab-af85-5a1528598e7c","outcome":"completed","summary":"All three findings are recorded with snippets that match the tree, and the proof test for the high finding was run and fails on the current code for the stated reason. The review is complete.\n\n**Outcome:** `.imd-findings.json` holds 3 findings and 29 coverage rows (all 25 listed entry points plus 4 invariant/lead rows). No repository files were changed; scratch tests live only under `test/scratch/`.\n\n**Findings**\n\n- **High. Atomic feed manipulation drains the vault.** Any swapper controls all eight samples in one transaction (1-wei swaps count as samples), and integer-floored sigma lets a window of seven ticks at T and one at T+8, with T chosen as 53 mod 60, produce a band whose upper edge sits below the trigger tick. The vault then redeploys 100% of its inventory as a one-sided buy wall about 10% above the real price, which the attacker fills. Measured: vault value fell from 200.0 to 184.9 token units (7.6%) and the attacker netted 13.8 after paying 1.25% LP fees on every leg. Repeatable every 30 minutes. The README acknowledges a manipulable feed, but the concrete effect is a profitable, repeatable, unprivileged drain, so I rated it high and proposed fixes that keep the 8-sample SMA design (one sample per block or minimum sample spacing, and refusing to rebalance when the trigger tick lies outside the computed band). Proof attached, run and failing.\n- **Low. Stranded ETH in an ERC-20 pool.** The payable fallback accepts ETH even when currency0 is WETH. Such ETH is counted by nothing and cannot be recovered by any entry point. Verified through a full emergency exit.\n- **Low. Dust rebalancing after full exit.** The trigger gates on `totalSupply() != 0`, which never holds again after the 1000 dead shares are minted. With 2 wei per side left, eligible swaps still pay the full burn/re-add cycle, measured at about 55k extra gas each.\n\n**Covered and holding:** deposit pricing (harvest before quote, floor shares, ceil amounts, only quoted amounts pulled, so inflation and deposit sandwiches cost at most rounding), withdraw and emergency pro-rata arithmetic, claim coverage in the unlock callback, liquidity/position coupling, zero hook deltas before returning to the router, and view/write agreement between preview and deposit.\n\n**Not reached:** quantifying whether a deposit-triggered `_invest` of existing idle inventory at a pushed spot price is profitable beyond the vault's own fee income. I traced it as weak and left it as a lead row rather than a finding.","treeHash":null,"usage":{"cachedInputTokens":1488580,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":68441,"runtime":"claude","turns":32,"wallClockMs":2220471}},{"artifacts":[],"attempt":1,"bundleHash":"2f5105b31dd820228c9b26f522ecdcb0c58662bf53e32b2638641270b71eb426","device":"e9049e5b12ba53ba","findings":[],"hash":"52f75d8364eea97951efc8d6656da7787b6c0498f67ea4ab853b6ad56601a5a7","nodeId":"fe905d3b-cd09-4f5e-a495-66c2e9da77b2","outcome":"completed","summary":"I wrote `launch.json`, and it's the only file changed. It parses as JSON and is a match for the source. I had no schema validator to run it against, so schema validity was checked by reading the file against the reference rules, not by a tool. `forge build` succeeds and `forge test` passes 42 of 42 tests with the required fuzz seed. Those tests check the existing code, not the manifest.\n\nWhat the manifest says:\n- **Hook:** `SigmaHook`. Its constructor arguments are `[\"$poolManager\", \"$owner\", \"$token\", \"$factory\"]`, in the order the constructor declares them (`manager_, owner_, launchToken_, factory_`).\n- **Permissions:** `afterInitialize` and `afterSwap`, which is exactly what `getHookPermissions` returns.\n- **Token:** `SigmaToken`, named \"SigmaHook\" with symbol \"SHK\" and 18 decimals.\n- **Pool:** paired with native ETH (the zero address), fee 12500, tickSpacing 60, initialPrice `79228162514264337593543950336`.\n- **Notes:** a short description of the vault, kept under the 4000-character limit.\n\nThe hook accepts this pool. `_afterInitialize` only lets the factory initialize it, and only once. It requires a fee of 12500 (`LP_FEE`), a tickSpacing of 60, and the launch token in the pool key, and native ETH is allowed as the other currency. Every address is a placeholder the deployer fills in, so no stand-in addresses were needed.\n\nI haven't committed the file.","treeHash":"4f9b72f65fdbdbe704b3d88e95b736de9b381ced","usage":{"cachedInputTokens":157095,"inputTokens":12,"model":"claude-opus-5-5","outputTokens":1982,"runtime":"claude","turns":7,"wallClockMs":108129}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"d2b8b66fa0a1ce68","findings":[{"citation":"resolved","description":"_afterInitialize fixes tickLower/tickUpper around the launch tick (line 229). The only writer afterwards is the rebalance branch of _afterSwap, which is gated by `totalSupply() != 0` (line 250). Between initialization and the first deposit the pool trades and the eight-sample feed follows the price, but the stored band does not. deposit() then quotes shares with _quoteDeposit (which sizes liquidity against the stale band and _capacity() at the stale ticks) and invests through _unlock(Action.Invest) at that stale band. If the price has left the launch band, the position is opened entirely out of range: one side of the depositor's capital is parked in a limit order at the launch price and the other side sits idle as raw dust, earning no fees, while previewDeposit/proposedBands disagree with what deposit actually does. The vault only corrects itself when a later swap satisfies the cooldown AND the 1.5-sigma trigger; if the window has already converged (sigma 0) a tick move of one unit suffices, but if the window still carries spread the stale position can persist. Spec section 4 says capital is deployed into the SMA band computed from the feed. Minimal fix: in deposit(), when `liquidity == 0`, recompute `(tickLower, tickUpper) = BandMath.bands(statistics(), _key.tickSpacing)` before quoting and investing (or do it in the Invest action when no position exists).","line":159,"path":"src/SigmaHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SigmaHook} from \"src/SigmaHook.sol\";\nimport {SigmaToken} from \"src/SigmaToken.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {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 Weth is ERC20 {\n    constructor() ERC20(\"W\", \"W\") {}\n    function mint(address to, uint256 a) external { _mint(to, a); }\n}\n\n/// @dev Router that pays its swap input BEFORE the swap (sync -> transfer -> swap -> settle), a\n/// pattern v4 permits, then takes the output.\ncontract PrefundRouter is IUnlockCallback {\n    IPoolManager immutable manager;\n    constructor(IPoolManager m) { manager = m; }\n    function swap(PoolKey memory key, bool zeroForOne, int256 amount, uint160 limit, uint256 prefund) external {\n        manager.unlock(abi.encode(key, zeroForOne, amount, limit, prefund));\n    }\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        (PoolKey memory key, bool zeroForOne, int256 amount, uint160 limit, uint256 prefund) =\n            abi.decode(data, (PoolKey, bool, int256, uint160, uint256));\n        Currency input = zeroForOne ? key.currency0 : key.currency1;\n        Currency output = zeroForOne ? key.currency1 : key.currency0;\n        manager.sync(input);\n        input.transfer(address(manager), prefund);\n        BalanceDelta d = manager.swap(key, SwapParams(zeroForOne, amount, limit), \"\");\n        manager.settle(); // credits prefund to this router (if the synced currency is still ours)\n        int128 inDelta = zeroForOne ? d.amount0() : d.amount1();\n        int128 outDelta = zeroForOne ? d.amount1() : d.amount0();\n        uint256 owed = uint256(uint128(-inDelta));\n        // refund any overpayment, pay any shortfall\n        if (prefund > owed) manager.take(input, address(this), prefund - owed);\n        if (outDelta > 0) manager.take(output, address(this), uint128(outDelta));\n        return \"\";\n    }\n}\n\n/// @dev Standard router: swap first, then sync -> transfer -> settle.\ncontract PostpayRouter is IUnlockCallback {\n    IPoolManager immutable manager;\n    constructor(IPoolManager m) { manager = m; }\n    function swap(PoolKey memory key, bool zeroForOne, int256 amount, uint160 limit) external {\n        manager.unlock(abi.encode(uint8(0), key, zeroForOne, amount, limit, uint256(0)));\n    }\n    function modify(PoolKey memory key, int24 lo, int24 hi, int256 l) external {\n        manager.unlock(abi.encode(uint8(1), key, lo, hi, l, uint256(0)));\n    }\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        BalanceDelta d;\n        PoolKey memory key;\n        uint8 action = abi.decode(data, (uint8));\n        if (action == 0) {\n            bool zeroForOne; int256 amount; uint160 limit;\n            (, key, zeroForOne, amount, limit,) = abi.decode(data, (uint8, PoolKey, bool, int256, uint160, uint256));\n            d = manager.swap(key, SwapParams(zeroForOne, amount, limit), \"\");\n        } else {\n            int24 lo; int24 hi; int256 l;\n            (, key, lo, hi, l,) = abi.decode(data, (uint8, PoolKey, int24, int24, int256, uint256));\n            (d,) = manager.modifyLiquidity(key, ModifyLiquidityParams(lo, hi, l, bytes32(0)), \"\");\n        }\n        _settle(key.currency0, d.amount0());\n        _settle(key.currency1, d.amount1());\n        return \"\";\n    }\n    function _settle(Currency c, int128 delta) private {\n        if (delta > 0) manager.take(c, address(this), uint128(delta));\n        else if (delta < 0) {\n            manager.sync(c);\n            c.transfer(address(manager), uint256(uint128(-delta)));\n            manager.settle();\n        }\n    }\n}\n\ncontract LeadsTest is Test {\n    using StateLibrary for IPoolManager;\n\n    IPoolManager manager;\n    SigmaHook hook;\n    SigmaToken token;\n    Weth weth;\n    PoolKey key;\n    PostpayRouter post;\n    PrefundRouter pre;\n    address alice = makeAddr(\"alice\");\n    address owner = makeAddr(\"owner\");\n\n    function setUp() public {\n        manager = IPoolManager(address(new PoolManager(owner)));\n        token = new SigmaToken();\n        weth = new Weth();\n        bytes memory initCode = bytes.concat(type(SigmaHook).creationCode, abi.encode(manager, owner, address(token), address(this)));\n        bytes32 h = keccak256(initCode);\n        for (uint256 i; i < 200_000; ++i) {\n            address p = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), h)))));\n            if (HookFlags.matches(p, HookFlags.SIGMA)) {\n                hook = new SigmaHook{salt: bytes32(i)}(manager, owner, address(token), address(this));\n                break;\n            }\n        }\n        (address a, address b) = address(token) < address(weth) ? (address(token), address(weth)) : (address(weth), address(token));\n        key = PoolKey(Currency.wrap(a), Currency.wrap(b), 12_500, 60, IHooks(address(hook)));\n        manager.initialize(key, 1 << 96);\n        post = new PostpayRouter(manager);\n        pre = new PrefundRouter(manager);\n        token.transfer(address(post), 10_000_000 ether);\n        weth.mint(address(post), 10_000_000 ether);\n        token.transfer(address(pre), 10_000_000 ether);\n        weth.mint(address(pre), 10_000_000 ether);\n        token.transfer(alice, 1_000 ether);\n        weth.mint(alice, 1_000 ether);\n        vm.startPrank(alice);\n        token.approve(address(hook), type(uint256).max);\n        weth.approve(address(hook), type(uint256).max);\n        vm.stopPrank();\n        post.modify(key, -887220, 887220, 1_000 ether);\n    }\n\n    function _moveTo(int24 target) internal {\n        (uint160 current,,,) = manager.getSlot0(key.toId());\n        uint160 limit = TickMath.getSqrtPriceAtTick(target);\n        post.swap(key, limit < current, -int256(100_000 ether), limit);\n    }\n\n    /// Lead 1: bands are frozen at the launch tick until the first rebalance, which needs totalSupply != 0.\n    function test_firstDepositUsesStaleLaunchBand() public {\n        for (uint256 i; i < 8; ++i) _moveTo(int24(3000 + int24(uint24(i))));\n        (, int24 tick,,) = manager.getSlot0(key.toId());\n        assertGt(tick, 2900);\n        (int24 pl, int24 pu) = hook.proposedBands();\n        emit log_named_int(\"proposed lower\", pl);\n        emit log_named_int(\"proposed upper\", pu);\n        emit log_named_int(\"stored lower\", hook.tickLower());\n        emit log_named_int(\"stored upper\", hook.tickUpper());\n        vm.prank(alice);\n        hook.deposit(10 ether, 10 ether, 1, alice, block.timestamp);\n        emit log_named_int(\"after deposit lower\", hook.tickLower());\n        emit log_named_int(\"after deposit upper\", hook.tickUpper());\n        emit log_named_uint(\"idle token0 raw\", key.currency0.balanceOf(address(hook)));\n        emit log_named_uint(\"idle token1 raw\", key.currency1.balanceOf(address(hook)));\n        emit log_named_uint(\"liquidity\", hook.liquidity());\n        // Expected (design intent): the position is placed around the current price (~3000).\n        assertEq(hook.tickLower(), pl, \"position placed at stale launch band, not the fresh feed\");\n    }\n}","reproduction":"Fixture: token/WETH pool initialized at tick 0 with a full-range background LP. Make 8 swaps that leave the post-swap tick at ~3000..3007 (proposedBands() returns (2940, 3060)). Alice calls deposit(10e18, 10e18, 1, alice, now). Expected: position around tick ~3000 holding both tokens. Actual: tickLower/tickUpper remain (-60, 60); liquidity ~1.67e21 is a one-sided position below the price holding only currency1; the full 10e18 of currency0 stays as idle raw balance in the hook (see proof test log: 'idle token0 raw: 10000000000000000000', 'after deposit lower: -60').","severity":"low","snippet":"        _unlock(Action.Invest, 0, 0);","title":"First deposit invests into the launch-time band: tickLower/tickUpper are never refreshed while totalSupply()==0, so the initial position can be placed far from the current price"},{"citation":"resolved","description":"When a rebalance fires in _afterSwap, _invest -> _changeLiquidity -> _settle pays any shortfall not covered by ERC-6909 claims from the hook's raw balance using sync/transfer/settle (lines 349-357). This runs inside the swapper's unlock, before the router has settled. The PoolManager has a single transient synced-currency slot: the hook's sync() overwrites the router's (for an ERC-20 it re-reads reserves, absorbing anything the router already transferred; for native it resets the slot) and the hook's settle() clears it. A router that follows the v4-permitted pre-fund order (sync -> transfer -> swap -> settle) then has its settle() treated as a zero native payment, its input delta stays negative, and the unlock reverts with CurrencyNotSettled. Because the failed swap never updates lastRebalance, the trigger condition persists and every attempt from such a router keeps failing until a post-pay router's swap performs the rebalance. Raw dust is routine: the remainder of every deposit (_invest can only use the ratio the band needs) and any direct transfer. Minimal fix: never sync inside afterSwap. Convert leftover raw balance into claims inside the hook's own unlocks (Invest/Harvest/Withdraw: sync+transfer+settle then mint) so the afterSwap path only burns claims; or skip the raw remainder during an afterSwap rebalance and let the next Invest pick it up.","line":350,"path":"src/SigmaHook.sol","reproduction":"Fixture: token/WETH pool at tick 0 with a full-range LP. Alice deposits 10e18/10e18, then transfers 1e18 of each token directly to the hook (raw dust). warp to lastRebalance + 30 minutes. Router A (pre-fund order): unlockCallback does sync(currency0); currency0.transfer(manager, 5e18); swap(zeroForOne, -5e18, limit=tick -600); settle(); take output. Expected: swap succeeds like any other router. Actual: reverts (whole unlock), while the identical swap from a post-pay router (swap then sync/transfer/settle) succeeds and sets lastRebalance = block.timestamp; and the pre-fund router's swap succeeds at lastRebalance + 30 minutes - 1 when no rebalance fires. Verified with test/scratch/Leads.t.sol::test_rebalanceSyncClobbersPrefundedRouter.","severity":"low","snippet":"                poolManager.sync(currency);","title":"Rebalance inside afterSwap settles raw dust with poolManager.sync(), clobbering a router that synced its input before calling swap; such swaps revert whenever the rebalance trigger holds"},{"citation":"resolved","description":"The trigger evaluates the window that already contains the current swap's post-swap tick, and the burn/redeploy happens synchronously in the same callback. One outlier sample at distance d moves the mean by d/8 and sets sigma to about 0.33d, so the new sample always deviates by about 2.6 sigma and the new band (mean +/- 3 sigma, about [-0.87d, +1.1d] around the old mean) always contains the manipulated tick. The attacker therefore chooses, in one atomic transaction, where the vault's full inventory (now one-sided after trading through its narrow band) is re-posted, and can trade back against it. Whether this is profitable net of the 1.25% LP fee each way and the price impact on other liquidity depends on the vault's share of pool liquidity; it is not quantified here. The README and SECURITY.md state this as a deliberate consequence of the brief, so it is recorded as a trust assumption for the economics review rather than as a defect in the control flow. Mitigations that preserve the brief's trigger: evaluate the trigger on the window as it was before the current swap, or defer the redeploy to the next swap/next block after the trigger fires.","line":250,"path":"src/SigmaHook.sol","reproduction":"Pool at tick 0, vault holds a position in [-60, 60], eight samples at 0, lastRebalance + 30 minutes elapsed. One exact-input zeroForOne swap with price limit at tick -3000: window becomes [0 x7, -3000], mean -375, sigma 992, |(-3000) - (-375)| = 2625 > 1488, so the vault burns its position and re-adds its (now currency0-heavy) inventory in [-3360, 2640] during the same swap. The same transaction then swaps oneForZero back to tick 0, buying currency0 from the vault at ticks -3000..0 that the vault bought at ~0.","severity":"info","snippet":"            !emergencyMode && totalSupply() != 0 && block.timestamp >= lastRebalance + REBALANCE_INTERVAL","title":"Rebalance executes inside the manipulating swap: a single large swap after the cooldown both sets the feed and forces the vault to re-deploy around the attacker's price within the same transaction (de"}],"hash":"65312867242eb9f4c1408d42a4ab2462355c2458c7773a420bed4f4377d0ac09","nodeId":"b98a266f-783e-495a-8d8a-a0397f4deead","outcome":"completed","summary":"Review complete. The findings file is written and validated against the tree, and the only files I created are `.imd-findings.json` (the deliverable) and the scratch test under `test/scratch/`.\n\n**Outcome.** The control-flow area (Execution Trace, Periphery, First Principles) holds up well. The flash-accounting design is sound: positive deltas become ERC-6909 claims, afterSwap never calls `take`, every hook delta is zero before the router settles, and the `_expectUnlock` latch plus `onlyPoolManager` close the callback surface. VaultMath's liquidity sizing cannot overspend under v4's round-up (I proved the amount0 double-ceiling is bounded by the integer `amount0 * sqrtA`), and it agrees with v4's tick-based range classification at exact tick boundaries. BandMath's grid rounding, widening and clamping are correct at both tick extremes. The project's own suite passes (41 tests, invariant suite not rerun for time).\n\n**Findings written (3):**\n\n1. **Low. First deposit invests into the stale launch band.** `tickLower/tickUpper` are only rewritten by the rebalance branch, which requires `totalSupply() != 0`. If the price moves before the first deposit, the position is opened at the launch band. Reproduced: with the feed proposing 2940..3060, deposit placed liquidity at −60..60, one-sided, with all 10e18 of currency0 left idle. Proof test attached (fails now).\n2. **Low. Rebalance inside afterSwap calls `poolManager.sync`**, which clobbers a router that synced and pre-transferred its input before `swap`. Reproduced: the same exact-input swap succeeds from a post-pay router and triggers the rebalance, but reverts from a pre-fund router whenever the trigger holds, and succeeds one second before the cooldown. Fix suggested: keep leftover dust as claims so afterSwap only burns claims.\n3. **Info. Feed manipulation and redeploy in one transaction** (economics area, acknowledged in SECURITY.md). Recorded with the concrete single-swap sequence and the arithmetic showing one outlier always triggers and always lands inside the new band. Profitability not quantified.\n\n**Coverage.** All 25 listed entry points have rows (27 holds, 2 finding, 0 unreached), plus four invariant rows. Nothing in my area was left unreached; the invariant fuzz suite was the only project check I did not rerun.","treeHash":null,"usage":{"cachedInputTokens":1555914,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":55219,"runtime":"claude","turns":36,"wallClockMs":1496340}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"c2a2bddd2b92d71e","findings":[{"citation":"resolved","description":"_invest only adds liquidity in proportion to the idle balances (VaultMath.liquidityForAmounts takes the min of both sides) and never swaps to rebalance the ratio. When the spot tick has crossed the old band, the burned position returns 100% of one currency. The new band mean±3σ always contains the spot tick (with 8 samples the largest possible |tick-mean| is √7·σ < 3σ, and rounding only widens it), so the new position needs both currencies and liquidityForAmounts returns 0. All capital stays idle. Every later rebalance hits the same state, and deposit() cannot help: it is priced pro-rata on assets, so it brings in none of the missing currency. The spec's step 4 ('Redeploy principal + fees into the new tight range') is never met again. The vault stops earning fees for good, though withdraw still returns principal. Anyone can force this with one swap across the band after the 30-minute window, and an ordinary price trend does it too.","line":325,"path":"src/SigmaHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SigmaHook} from \"src/SigmaHook.sol\";\nimport {SigmaToken} from \"src/SigmaToken.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {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 PairToken is ERC20 {\n    constructor() ERC20(\"Wrapped Ether\", \"WETH\") {\n        _mint(msg.sender, 10_000_000 ether);\n    }\n}\n\ncontract Router is IUnlockCallback {\n    IPoolManager immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    function swap(PoolKey memory key, bool zeroForOne, int256 amount, uint160 limit) external {\n        manager.unlock(abi.encode(uint8(0), key, abi.encode(SwapParams(zeroForOne, amount, limit))));\n    }\n\n    function modify(PoolKey memory key, int24 lower, int24 upper, int256 change) external {\n        manager.unlock(abi.encode(uint8(1), key, abi.encode(ModifyLiquidityParams(lower, upper, change, bytes32(0)))));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        (uint8 action, PoolKey memory key, bytes memory args) = abi.decode(data, (uint8, PoolKey, bytes));\n        BalanceDelta delta;\n        if (action == 0) delta = manager.swap(key, abi.decode(args, (SwapParams)), \"\");\n        else (delta,) = manager.modifyLiquidity(key, abi.decode(args, (ModifyLiquidityParams)), \"\");\n        _settle(key.currency0, delta.amount0());\n        _settle(key.currency1, delta.amount1());\n        return \"\";\n    }\n\n    function _settle(Currency c, int128 d) private {\n        if (d > 0) {\n            manager.take(c, address(this), uint128(d));\n        } else if (d < 0) {\n            manager.sync(c);\n            c.transfer(address(manager), uint256(-int256(d)));\n            manager.settle();\n        }\n    }\n}\n\ncontract IdleAfterRebalanceTest is Test {\n    using StateLibrary for IPoolManager;\n\n    IPoolManager manager;\n    SigmaHook hook;\n    SigmaToken token;\n    PairToken weth;\n    Router router;\n    PoolKey key;\n    address alice = makeAddr(\"alice\");\n\n    function setUp() public {\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        token = new SigmaToken();\n        weth = new PairToken();\n        bytes memory initCode = bytes.concat(\n            type(SigmaHook).creationCode, abi.encode(manager, makeAddr(\"owner\"), address(token), address(this))\n        );\n        bytes32 hash = keccak256(initCode);\n        for (uint256 i; i < 200_000; ++i) {\n            address predicted = address(\n                uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), hash))))\n            );\n            if (!HookFlags.matches(predicted, HookFlags.SIGMA)) continue;\n            hook = new SigmaHook{salt: bytes32(i)}(manager, makeAddr(\"owner\"), address(token), address(this));\n            break;\n        }\n        (address a, address b) =\n            address(token) < address(weth) ? (address(token), address(weth)) : (address(weth), address(token));\n        key = PoolKey(Currency.wrap(a), Currency.wrap(b), 12_500, 60, IHooks(address(hook)));\n        manager.initialize(key, 1 << 96);\n        router = new Router(manager);\n        token.transfer(address(router), 1_000_000 ether);\n        weth.transfer(address(router), 1_000_000 ether);\n        router.modify(key, -887220, 887220, 1_000 ether); // background full-range LP\n        token.transfer(alice, 10_000 ether);\n        weth.transfer(alice, 10_000 ether);\n        vm.startPrank(alice);\n        token.approve(address(hook), type(uint256).max);\n        weth.approve(address(hook), type(uint256).max);\n        hook.deposit(1_000 ether, 1_000 ether, 1, alice, block.timestamp);\n        vm.stopPrank();\n    }\n\n    function _moveTo(int24 target) internal {\n        (uint160 current,,,) = manager.getSlot0(key.toId());\n        uint160 limit = TickMath.getSqrtPriceAtTick(target);\n        router.swap(key, limit < current, -int256(100_000 ether), limit);\n    }\n\n    /// Once price leaves the band, the vault is one-sided; every later band contains the spot\n    /// tick, so _invest needs both tokens, mints 0 liquidity, and the vault never earns fees again.\n    function test_vaultStaysIdleForeverAfterTrendRebalance() public {\n        assertGt(hook.liquidity(), 0);\n        uint256 t = block.timestamp + 31 minutes;\n        vm.warp(t);\n        _moveTo(2000); // price trends out of [-60,60]; afterSwap rebalances\n        assertEq(hook.lastRebalance(), t, \"rebalance fired\");\n        assertEq(hook.liquidity(), 0, \"redeployed nothing\");\n\n        // Further rebalance windows, with price returning and moving again.\n        for (uint256 i; i < 4; ++i) {\n            for (uint256 j; j < 8; ++j) {\n                router.swap(key, true, -1000, TickMath.MIN_SQRT_PRICE + 1); // fill the feed at this level\n            }\n            t += 31 minutes;\n            vm.warp(t);\n            _moveTo(i % 2 == 0 ? int24(-1500) : int24(1500));\n            assertEq(hook.lastRebalance(), t, \"later rebalance fired\");\n        }\n        emit log_named_uint(\"lastRebalance\", hook.lastRebalance());\n        emit log_named_uint(\"liquidity\", hook.liquidity());\n        assertGt(hook.liquidity(), 0, \"vault capital never redeployed after a trend rebalance\");\n    }\n}","reproduction":"Pool at tick 0, full-range background LP of 1000e18 liquidity, alice deposits 1000e18/1000e18, so hook.liquidity()>0 in band [-60,60]. Warp 31 min and swap to tick 2000: afterSwap rebalances (lastRebalance=now) and hook.liquidity()==0. Then repeat 4 times: fill the feed with 8 dust swaps, warp 31 min, swap to ±1500. Each time the rebalance fires (lastRebalance updates, finally 9301) and hook.liquidity() stays 0. Expected: the redeployed liquidity is > 0, e.g. by swapping the surplus to the band's ratio or by placing the one-sided remainder in an adjacent single-sided range. The proof fails with 'vault capital never redeployed after a trend rebalance: 0 <= 0'.","severity":"high","snippet":"        if (added != 0) _changeLiquidity(int256(uint256(added)));","title":"After any rebalance triggered by price leaving the band, the vault is one-sided and never redeploys liquidity again"},{"citation":"resolved","description":"When totalSupply()==0, _quoteDeposit prices shares from the liquidity the budgets can buy but returns amount0=max0 and amount1=max1, so deposit() pulls the full budgets (contradicting the NatSpec 'Unused ERC-20 budgets are never pulled', which holds for every later deposit, whose amounts are ceil(shares*assets/supply)). The surplus stays idle in the vault and is shared pro-rata with the 1_000 MINIMUM_SHARES minted to address(this), which nobody can ever redeem. With an unbalanced first basket the dead shares capture a large fraction of the surplus: the depositor's loss is surplus*1000/initial. previewDeposit reports the same numbers, so minShares does not protect the depositor (shares are what was quoted).","line":419,"path":"src/SigmaHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SigmaHook} from \"src/SigmaHook.sol\";\nimport {SigmaToken} from \"src/SigmaToken.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\ncontract PairToken is ERC20 {\n    constructor() ERC20(\"Wrapped Ether\", \"WETH\") {\n        _mint(msg.sender, 1_000_000 ether);\n    }\n}\n\ncontract FirstDepositTest is Test {\n    IPoolManager manager;\n    SigmaHook hook;\n    SigmaToken token;\n    PairToken weth;\n    PoolKey key;\n    address alice = makeAddr(\"alice\");\n\n    function setUp() public {\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        token = new SigmaToken();\n        weth = new PairToken();\n        bytes memory initCode = bytes.concat(\n            type(SigmaHook).creationCode, abi.encode(manager, makeAddr(\"owner\"), address(token), address(this))\n        );\n        bytes32 hash = keccak256(initCode);\n        for (uint256 i; i < 200_000; ++i) {\n            address predicted = address(\n                uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), hash))))\n            );\n            if (!HookFlags.matches(predicted, HookFlags.SIGMA)) continue;\n            hook = new SigmaHook{salt: bytes32(i)}(manager, makeAddr(\"owner\"), address(token), address(this));\n            break;\n        }\n        (address a, address b) =\n            address(token) < address(weth) ? (address(token), address(weth)) : (address(weth), address(token));\n        key = PoolKey(Currency.wrap(a), Currency.wrap(b), 12_500, 60, IHooks(address(hook)));\n        manager.initialize(key, 1 << 96); // tick 0, vault band [-60, 60]\n        token.transfer(alice, 10 ether);\n        weth.transfer(alice, 10 ether);\n        vm.startPrank(alice);\n        token.approve(address(hook), type(uint256).max);\n        weth.approve(address(hook), type(uint256).max);\n        vm.stopPrank();\n    }\n\n    function _wealth() internal view returns (uint256) {\n        return token.balanceOf(alice) + weth.balanceOf(alice);\n    }\n\n    /// First deposit with an unbalanced budget: the whole budget is pulled although liquidity\n    /// uses ~10 wei of it, and the 1_000 dead shares capture ~30% of the surplus.\n    function test_firstDepositPullsUnusedBudgetIntoDeadShares() public {\n        uint256 start = _wealth();\n        vm.prank(alice);\n        (uint256 shares, uint256 amount0, uint256 amount1) = hook.deposit(1 ether, 10, 1, alice, block.timestamp);\n        emit log_named_uint(\"shares\", shares);\n        emit log_named_uint(\"pulled0\", amount0);\n        emit log_named_uint(\"pulled1\", amount1);\n        vm.prank(alice);\n        hook.withdraw(shares, 0, 0, alice, block.timestamp);\n        uint256 lost = start - _wealth();\n        emit log_named_uint(\"lost\", lost);\n        // A sole depositor exiting immediately should lose dust at most.\n        assertLe(lost, 1e12, \"first depositor's unused budget is locked behind MINIMUM_SHARES\");\n    }\n}","reproduction":"Pool initialized at sqrtPriceX96=2^96 (tick 0, vault band [-60,60]), ERC-20 pair, no prior deposits. alice calls deposit(1e18, 10, 1, alice, now). Liquidity is limited by the 10-wei side: initial=3338, alice gets 2338 shares, but the hook pulls 1e18 and 10. alice immediately calls withdraw(2338, 0, 0, alice, now) and receives only ~0.70e18: she loses 299580587177950872 wei (~0.3 ETH) to the 1_000 dead shares. Expected: only the amounts the minted liquidity needs are pulled (or surplus refunded), so an immediate exit loses dust. Proof test fails with lost=299580587177950872 > 1e12.","severity":"medium","snippet":"            if (initial <= MINIMUM_SHARES) return (0, 0, 0);\n            return (initial - MINIMUM_SHARES, max0, max1);","title":"First deposit pulls the whole unused budget and locks ~MINIMUM_SHARES/L of it forever (asymmetry with later deposits)"},{"citation":"resolved","description":"receive() is only needed when currency0 is native, so the hook can take ETH from the PoolManager. On the Launch Token / WETH pair named in the brief, no code path ever reads or pays address(this).balance. _idle, _assets and _emergencyWithdraw only count _key.currency0/1. ETH that is sent there by mistake is lost for good, and the owner has no sweep.","line":474,"path":"src/SigmaHook.sol","reproduction":"Deploy with a WETH (ERC-20) pair and call address(hook).call{value: 1 ether}(''). It succeeds. totalAssets() is unchanged, and no withdraw, emergencyWithdraw or owner function can move the 1 ETH. Expected: on a non-native pair, revert unless msg.sender == address(poolManager) and currency0 is native.","severity":"low","snippet":"    receive() external payable {}","title":"receive() accepts ETH from anyone on an ERC-20 pair, and nothing can recover it"}],"hash":"860495141004701e39dba7592f8f27e40359ebef5be762568657e7db6d2bd099","nodeId":"9dcce14c-9ccb-4adb-a976-5067e5988804","outcome":"completed","summary":"I found three defects in `src/SigmaHook.sol`: one high, one medium and one low. They are written to `.imd-findings.json` with a coverage row for each of the 25 entry points. The high and medium findings each come with a Foundry proof that I ran and that fails on the current code. No source files were changed, and the scratch tests are deleted.\n\n**Findings**\n\n1. **High — after a rebalance, the vault never puts its money back into the pool** (`SigmaHook.sol:325`, `_invest`).\n   - **Cause:** when the price crosses out of the vault's band, burning the position returns only one token. The new band always contains the current price, so a new position needs both tokens. The hook never swaps to get the missing one, so it adds 0 liquidity.\n   - **Effect:** every later rebalance hits the same state. New deposits can't fix it either, because they are priced in proportion to what the vault holds, so they bring in none of the missing token.\n   - **Trigger:** one swap across the band after the 30-minute window does it, and so does an ordinary price trend.\n   - **Proof:** I swapped to tick 2000 and then forced 4 more rebalances, all of which fired. `liquidity()` stayed 0 throughout, so the vault earns no fees from then on. Withdrawals still return principal.\n\n2. **Medium — the first deposit takes the whole budget, and the locked starter shares keep part of it** (`SigmaHook.sol:419`).\n   - **Cause:** with no shares outstanding, the quote returns the full `max0`/`max1` budgets instead of what the liquidity uses. That contradicts the code's own comment, \"Unused ERC-20 budgets are never pulled\". The unused amount sits idle and is shared with the 1,000 permanently locked starter shares.\n   - **Proof:** `deposit(1e18, 10, …)` at tick 0 gives 2,338 shares. An immediate full `withdraw` loses 299580587177950872 wei, about 0.3 ETH.\n   - **Why the depositor isn't protected:** `previewDeposit` quotes the same numbers, so setting `minShares` doesn't help.\n\n3. **Low — ETH sent to the hook on a WETH pair is stuck for good** (`SigmaHook.sol:474`). `receive()` accepts ETH from anyone. On an ERC-20 pair no code path ever counts or pays out the hook's ETH balance, and there is no owner function to recover it.\n\n**Access-control results**\n\n- **Pool callbacks:** every callback can only be called by the PoolManager. Pool setup (`afterInitialize`) also only accepts the factory, runs once, and checks the fee (12500), tick spacing (60), the hook address and that the launch token is in the pair. The unused callbacks are switched off and revert if called.\n- **Unlock callback:** `unlockCallback` is gated twice, by PoolManager-only access and a flag the hook sets itself.\n- **Ownership:** ownership transfer is two-step, and giving up ownership is blocked. The owner's only special power is the irreversible `activateEmergencyMode`, and the owner receives no funds from it. I treated this as a stated trust assumption, not a defect.\n- **Emergency mode:** I tested it. A non-owner is refused, all liquidity is withdrawn, swaps keep working, and both `emergencyWithdraw` versions pay holders in proportion to their shares. Deposits are blocked afterwards.\n- **Withdraw:** it burns the caller's shares first, uses the share supply from before the burn, collects fees before splitting assets, and enforces minimum amounts and a deadline.\n\n**Static-analysis leads I followed up**\n\n- **Reentrancy (slither):** not exploitable. Deposit, withdraw, emergency and `afterSwap` share one reentrancy lock, so a swap attempted mid-deposit simply reverts.\n- **Price manipulation:** a forced-rebalance sandwich I tried lost the attacker about 18 tokens, and the vault gained fees.\n- **\"Locks ether\" (aderyn):** this is finding 3.\n\n**Not covered:** the leftover math leads were outside my area, and I didn't reproduce them. One is whether a rebalance inside `afterSwap` can revert from a 1-wei rounding shortfall, which would make swaps fail.","treeHash":null,"usage":{"cachedInputTokens":913935,"inputTokens":29,"model":"claude-opus-5-5","outputTokens":27171,"runtime":"claude","turns":19,"wallClockMs":390420}},{"artifacts":[],"attempt":1,"bundleHash":"cb416d9e6ca129e389efaccc0ca49f8e1a73625147d7c4ffdc8c7dfb1b71ec1c","device":"540f6378761dba26","findings":[{"description":"liquidityForAmounts floors a * upper / Q96 before multiplying by the currency0 budget. The precision-preserving branch only handles intermediate == 0. When the true intermediate is between 1 and 2, the nonzero branch loses almost half its precision, materially undersizing liquidity. SigmaHook uses this result both to quote its first deposit and to invest deposits/rebalance proceeds. In the reproduced valid pool state, 49.66% of currency0 stays idle even though the other currency and tick capacity permit investment. This reduces fee capture and violates the requested redeployment of principal plus fees with only dust retained. Assets remain withdrawable; this is a capital-utilization defect, not a demonstrated theft.","line":38,"path":"src/libraries/VaultMath.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {VaultMath} from \"src/libraries/VaultMath.sol\";\nimport {SigmaHook} from \"src/SigmaHook.sol\";\nimport {SigmaToken} from \"src/SigmaToken.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {WETH} from \"solmate/src/tokens/WETH.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {SqrtPriceMath} from \"v4-core/src/libraries/SqrtPriceMath.sol\";\n\ncontract LiquiditySizingProofTest is Test {\n    function test_depositLeavesAlmostHalfOfInvestablePrincipalIdle() public {\n        IPoolManager manager = IPoolManager(address(new PoolManager(address(this))));\n        SigmaToken token = new SigmaToken();\n        WETH weth = new WETH();\n        vm.deal(address(this), 1_000 ether);\n        weth.deposit{value: 1_000 ether}();\n        SigmaHook hook;\n        bytes32 initHash = keccak256(abi.encodePacked(\n            type(SigmaHook).creationCode, abi.encode(manager, address(this), address(token), address(this))\n        ));\n        for (uint256 i; i < 200_000; ++i) {\n            address predicted = address(uint160(uint256(keccak256(\n                abi.encodePacked(bytes1(0xff), address(this), bytes32(i), initHash)\n            ))));\n            if (!HookFlags.matches(predicted, HookFlags.SIGMA)) continue;\n            hook = new SigmaHook{salt: bytes32(i)}(manager, address(this), address(token), address(this));\n            break;\n        }\n        require(address(hook) != address(0), \"CREATE2 search exhausted\");\n        (address a, address b) = address(token) < address(weth)\n            ? (address(token), address(weth)) : (address(weth), address(token));\n        PoolKey memory key = PoolKey(Currency.wrap(a), Currency.wrap(b), 12_500, 60, IHooks(address(hook)));\n        manager.initialize(key, TickMath.getSqrtPriceAtTick(-658620));\n        token.approve(address(hook), type(uint256).max);\n        weth.approve(address(hook), type(uint256).max);\n        hook.deposit(1_000 ether, 1, 1, address(this), block.timestamp);\n        assertGt(hook.liquidity(), 0);\n        emit log_named_uint(\"liquidity\", hook.liquidity());\n        emit log_named_uint(\"idle currency0\", key.currency0.balanceOf(address(hook)));\n        // The current price is inside the bands, no other LP consumes capacity, and both\n        // budgets can support deploying over 90% of currency0 (see independent cost oracle).\n        assertLe(key.currency0.balanceOf(address(hook)), 100 ether, \"material principal left idle, not rounding dust\");\n    }\n\n    function test_lowTickSizingLeavesMaterialInvestableCapitalIdle() public pure {\n        uint160 price = TickMath.getSqrtPriceAtTick(-658620);\n        uint160 lower = TickMath.getSqrtPriceAtTick(-658680);\n        uint160 upper = TickMath.getSqrtPriceAtTick(-658560);\n        uint256 budget0 = 1_000 ether;\n        uint256 budget1 = 1;\n        uint128 capacity = 1e30;\n        uint128 actual = VaultMath.liquidityForAmounts(price, lower, upper, budget0, budget1, capacity);\n\n        // Binary search the manager's round-up token costs, independently of the sizing formula.\n        uint128 lo;\n        uint128 hi = capacity;\n        while (lo < hi) {\n            uint128 mid = lo + (hi - lo + 1) / 2;\n            if (SqrtPriceMath.getAmount0Delta(price, upper, mid, true) <= budget0\n                && SqrtPriceMath.getAmount1Delta(lower, price, mid, true) <= budget1) lo = mid;\n            else hi = mid - 1;\n        }\n        uint128 affordable = lo * 9 / 10;\n        assertLe(SqrtPriceMath.getAmount0Delta(price, upper, affordable, true), budget0);\n        assertLe(SqrtPriceMath.getAmount1Delta(lower, price, affordable, true), budget1);\n        assertLt(affordable, capacity);\n        assertGe(actual, affordable, \"more than 10% of investable liquidity left idle\");\n    }\n}","reproduction":"Run the attached source as test/LiquiditySizingProof.t.sol with forge test --match-path test/LiquiditySizingProof.t.sol -vv. Deploy the real PoolManager, SigmaToken, ETH-backed WETH, and a mined SigmaHook; initialize at tick -658620 with fee 12500 and spacing 60. The initial bands are [-658680, -658560]. Deposit currency0=1000 ether and currency1=1 base unit. Actual: liquidity=840244826 and idle currency0=496626109797669141764. Expected: the available tokens support at least 1502303472 liquidity (90% of the independent binary-search optimum), so over 90% of currency0 can be invested. The second proof test checks affordability against v4 SqrtPriceMath round-up costs with capacity=1e30. Both assertions fail on the current code, respectively actual idle >100 ether and actual liquidity <1502303472.","severity":"medium","title":"Low-tick liquidity sizing leaves almost half of investable principal idle"}],"hash":"8a98e58905f4f1c0f0acbd86ef2a0bda1b35f0ee6670615496eafef54582d598","nodeId":"0088c0fa-e6b7-442b-9296-4e5308c5515a","outcome":"completed","summary":"Added 24 tests covering failure rollback, WETH integration, arithmetic fuzzing, and stateful invariants.\n\n`forge build` succeeded; all 66 tests passed with the required seed.\n\nReported one medium defect in `.imd-findings.json`, with a verified failing proof: low-tick sizing leaves approximately 49.66% of investable currency0 idle.","treeHash":"2046e864f81ec4b14f1783f51830decfa2649073","usage":{"cachedInputTokens":4136832,"inputTokens":291961,"model":"gpt-6-astra","outputTokens":33729,"runtime":"codex","turns":8,"wallClockMs":1148659}},{"artifacts":[],"attempt":1,"bundleHash":"09f38740c7b8435d1e5439da806a94a4adf0e64da050c8b96b54125f0e7edc35","device":"aa333626543c4dee","findings":[{"description":"While the vault holds no position (liquidity == 0), deposit() first replaces tickLower/tickUpper with BandMath.bands(moments()) and only then calls _quoteDeposit. previewDeposit() calls _quoteDeposit against the stored band without that refresh. After any swap moves the feed before the first deposit (or after a full exit), the preview quotes a different band, basket and share count from the ones deposit() uses. A depositor who passes the preview as minShares gets Slippage(), and a one-sided basket the preview accepts is refused. The NatSpec calls previewDeposit a spot-state quote, and that is not true in this state.","line":138,"path":"src/SigmaHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SigmaHook} from \"src/SigmaHook.sol\";\nimport {SigmaToken} from \"src/SigmaToken.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\n\ncontract ProofWeth is ERC20 {\n    constructor() ERC20(\"Wrapped Ether\", \"WETH\") {\n        _mint(msg.sender, 1_000_000 ether);\n    }\n}\n\n/// @dev Swaps against an empty pool only, so it never owes or receives tokens.\ncontract EmptyPoolMover is IUnlockCallback {\n    IPoolManager private immutable manager;\n\n    constructor(IPoolManager manager_) {\n        manager = manager_;\n    }\n\n    function move(PoolKey memory key, bool zeroForOne, uint160 limit) external {\n        manager.unlock(abi.encode(key, SwapParams(zeroForOne, -1 ether, limit)));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (PoolKey memory key, SwapParams memory params) = abi.decode(data, (PoolKey, SwapParams));\n        BalanceDelta delta = manager.swap(key, params, \"\");\n        require(BalanceDelta.unwrap(delta) == 0, \"empty pool moved tokens\");\n        return \"\";\n    }\n}\n\ncontract PreviewDepositStaleBandProof is Test {\n    IPoolManager private manager;\n    SigmaHook private hook;\n    PoolKey private key;\n    address private alice = makeAddr(\"alice\");\n\n    function setUp() public {\n        address owner = makeAddr(\"owner\");\n        manager = IPoolManager(address(new PoolManager(owner)));\n        SigmaToken token = new SigmaToken();\n        ProofWeth weth = new ProofWeth();\n        bytes memory initCode =\n            bytes.concat(type(SigmaHook).creationCode, abi.encode(manager, owner, address(token), address(this)));\n        bytes32 hash = keccak256(initCode);\n        for (uint256 i; i < 200_000; ++i) {\n            address predicted = address(\n                uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), hash))))\n            );\n            if (!HookFlags.matches(predicted, HookFlags.SIGMA) || predicted.code.length != 0) continue;\n            hook = new SigmaHook{salt: bytes32(i)}(manager, owner, address(token), address(this));\n            break;\n        }\n        require(address(hook) != address(0), \"salt search exhausted\");\n        (address a, address b) =\n            address(token) < address(weth) ? (address(token), address(weth)) : (address(weth), address(token));\n        key = PoolKey(Currency.wrap(a), Currency.wrap(b), 12_500, 60, IHooks(address(hook)));\n        manager.initialize(key, 1 << 96);\n        token.transfer(alice, 100 ether);\n        weth.transfer(alice, 100 ether);\n        vm.startPrank(alice);\n        token.approve(address(hook), type(uint256).max);\n        weth.approve(address(hook), type(uint256).max);\n        vm.stopPrank();\n    }\n\n    /// The pool is created at tick 0 with band [-60, 60]. Before anyone deposits, a swap on the empty\n    /// pool moves the price to tick 60 and records it in the feed. previewDeposit(0, 10 ether) quotes the\n    /// stored band [-60, 60], where tick 60 is the upper edge and a currency1-only basket buys shares.\n    /// deposit() first refreshes the band from the feed to one that contains tick 60, so the same\n    /// basket buys nothing and the deposit reverts with Slippage() for the quoted minShares.\n    function test_firstDepositMatchesItsPreviewAfterTheFeedMoved() public {\n        new EmptyPoolMover(manager).move(key, false, TickMath.getSqrtPriceAtTick(60));\n        assertEq(hook.liquidity(), 0);\n        (uint256 quote, uint256 quoted0, uint256 quoted1) = hook.previewDeposit(0, 10 ether);\n        assertGt(quote, 0, \"preview offers shares for the basket\");\n        vm.prank(alice);\n        (uint256 shares, uint256 paid0, uint256 paid1) = hook.deposit(0, 10 ether, quote, alice, block.timestamp);\n        assertEq(shares, quote, \"deposit must honour its spot-state preview\");\n        assertEq(paid0, quoted0);\n        assertEq(paid1, quoted1);\n    }\n}","reproduction":"Fresh PoolManager, pool initialised at tick 0, so the stored band is [-60, 60]. Before any deposit, a swap on the empty pool moves the price to tick 60, which afterSwap records in the feed. previewDeposit(0, 10 ether) returns shares > 0, because tick 60 is the stored band's upper edge and a currency1-only basket buys liquidity there. deposit(0, 10 ether, quote, alice, deadline) refreshes the band to one that contains tick 60 (proposedBands() gives lower < 60 < upper). The same basket then buys 0 liquidity and the call reverts with Slippage(). Expected: deposit returns shares == quote and pays the quoted amounts. Actual: revert Slippage(). Suggested fix: apply the same band refresh inside previewDeposit when liquidity == 0, for example by quoting against BandMath.bands(moments()) without writing storage.","severity":"medium","title":"previewDeposit quotes the stored band while deposit() quotes a band refreshed from the feed"}],"hash":"8aeb71ddec7ee3075064f2a35b9cc2a3a3bda6e8866f5404a86e330dde2bd933","nodeId":"0088c0fa-e6b7-442b-9296-4e5308c5515a","outcome":"completed","summary":"I updated my four test files to the revised SigmaHook implementation. All four pass on their own with the verifier's fuzz seed: 26 tests, plus the invariant campaign. I also reported one new defect in the hook.\n\n**What I ran, and what I couldn't:** a full `forge build` / `forge test` doesn't fit here. solc was killed after 9 minutes at this machine's 1.5 GB memory limit (via-IR, 109 files). So I ran each of my files separately with `--match-path`. I did not run the other contributor's test files (`SigmaHook.t.sol`, `BandMath.t.sol`, `Adversarial.t.sol`, `NativeVault.t.sol`, `SigmaToken.t.sol`, `VaultInvariant.t.sol`). The verifier has more memory and will build the whole suite.\n\n**Changes, all within `test/`:**\n- **`MathProperties.t.sol`** (9/9 pass): moved to the new `BandMath` API, which works in exact moments (`sum` = 8 × mean, `sigma8` = ⌊8σ⌋).\n  - The band test now checks the edges against the unrounded mean.\n  - The symmetry test also checks that the rounded whole-tick mean is within half a tick of the exact one.\n  - A pinned case shows the trigger uses the unrounded mean.\n- **`VaultAccountingInvariant.t.sol`** (256 runs × 64 calls, no reverts, no failures): the trigger check now matches the new rule. A rebalance fires only when the cooldown has passed, emergency mode is off, someone other than the locked minimum holds shares, and the tick is more than 1.5σ from the exact mean.\n- **`WethVaultIntegration.t.sol`** (6/6 pass): the old tests seeded a deposit with one token only. That can no longer happen, by design. A first deposit now re-centres the band on recent prices, and that band always contains the current price, so both tokens are needed. The four rebalance tests now seed both tokens and check the PoolManager's balances exactly. A new test covers the re-centring: a one-token deposit is refused, a failed deposit leaves the band unchanged, and only the cost of the liquidity is pulled from the depositor.\n- **`README.md`**: updated to match. The earlier low-tick rounding finding is withdrawn, because the revised `VaultMath` fixed it and the implementation's own `BandMath.t.sol` covers it.\n\n**New finding (medium), in `.imd-findings.json`:** `previewDeposit` can promise shares that `deposit` then refuses (`src/SigmaHook.sol:138`).\n- **Cause:** while the vault holds no position, `deposit()` refreshes the band before quoting, but `previewDeposit()` still quotes the old band.\n- **Reproduction:** before anyone deposits, a swap on the empty pool moves the price to tick 60. `previewDeposit(0, 10 ether)` then offers shares. `deposit()` with that quote as the minimum reverts with `Slippage()`.\n- **Proof:** a self-contained Foundry test, included in the findings file, fails on the current code for exactly that reason.\n- **Fix:** have the preview apply the same band refresh, without writing storage.\n\nNone of my tests assert the preview in that state.\n\nNothing is committed. The findings file is in the repository root, where the brief asked for it; git is set to ignore it, so it won't show up in `git status`.","treeHash":"339eca339a114f8a8989bf0c243ea9a2fe1bde70","usage":{"cachedInputTokens":1827836,"inputTokens":54,"model":"claude-opus-5-5","outputTokens":23417,"runtime":"claude","turns":28,"wallClockMs":1807206}},{"artifacts":[],"attempt":1,"bundleHash":"046c26e4a06a799ed2129e7abcbf675c161ef8df6fc1a29f034e280f24b2da71","device":"1eba9b0f789fcc77","findings":[],"hash":"8cc5bac4b2deed98289874453ef38d430cf34a439d264de400380a418370ff54","nodeId":"fd352bf8-7ae0-4953-abe3-4ce823a259a6","outcome":"completed","summary":"The responses file is in place at the repository root (the harness excludes it from git on purpose, which is how it reaches the reviewer). Work is complete.\n\n## Outcome\n\nAll eight reports reproduced and were addressed. Seven are answered \"fixed\"; the high-severity one is fixed in code but its proof is answered \"disputed\", for a reason explained below.\n\n**Verification on this machine**\n- `forge build`: compiler run successful (the compiler takes 8 seconds; forge's own overhead on this host adds several minutes, which is environmental).\n- `forge test` with the pinned fuzz seed: 56 of 56 project tests pass.\n- `forge fmt --check`: clean.\n- The first-deposit proof passes. The trend proof passes its final assertion but fails its own line 120.\n\n**What changed in the code**\n- Trend rebalance (high): each cycle now sells the surplus side in its own pool before investing. `VaultMath.rebalanceTrade` derives the band's currency split from the range weights, the trade stops at the band edge and never beyond 300 ticks, skips below 1% imbalance or in a pool with no active liquidity, and runs on claims so it works before the router pays. The pool manager skips a hook's own callbacks, so nothing recurses.\n- First deposit (medium): only the rounded-up cost of the minted liquidity is pulled.\n- Band refresh on an empty vault, the rebalance gate on external shares, the synced-currency guard (claims-only cycle while another contract's payment is pending), statistics kept in eighths of a tick, division-first liquidity sizing, and a `receive()` that accepts ETH only from the manager on a native pair.\n\n**Why the trend proof is disputed, not claimed passing.** Its line 120 asserts that the vault redeploys nothing after the first trend rebalance. That is the defect itself, so any fix that redeploys at that cycle fails there while passing the final check. My own regression test runs the proof's exact scenario and asserts liquidity above zero at every cycle, and a deep-pool test shows at most a tenth of value left idle.\n\n**Documented for the operator.** The README now describes the balancing trade, its two named constants, its LP-fee and sandwich cost within the bounds, the thin-pool case where conversion takes several cycles, prepaying routers, and the eighths-of-a-tick formulas. The security record lists the new reviewed paths.","treeHash":"f4934fb6d8d131248d1312a1a5f0f8a758cd9128","usage":{"cachedInputTokens":4515571,"inputTokens":804,"model":"claude-fable-5-1","outputTokens":115938,"runtime":"claude","turns":89,"wallClockMs":3911141}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"d2d0460994f47014","findings":[{"citation":"resolved","description":"statistics() computes the mean as trunc(sum/8) and sigma as floor(sqrt(floor(sumsq/8))). When seven samples sit at one tick and one sample one tick away on the zero side, the integer mean lands on the single outlier (error of 0.875 tick) and sigma collapses to 0. deviates() (line 28, `2 * absolute > 3 * sigma`) then reports the seven majority samples as deviating (|6-5| = 1 > 0) and the actual outlier as not deviating (|5-5| = 0). Under the specified real-valued test (mean 5.875, sigma 0.33, 1.5 sigma = 0.496) the decision is the opposite on both ticks. Effect in _afterSwap (src/SigmaHook.sol:249-260): once the 30-minute cooldown has elapsed, an ordinary swap that leaves the price at the normal tick burns and redeploys the whole position into bands(5, 0, 60) = [0, 60], while a real one-tick-away excursion does not trigger. The same inversion happens for any window whose true mean has a fractional part above 0.5 and whose sample spread is below one tick, which is the quiet-market steady state of this pool. Impact is limited to spurious rebalance churn (position burn/re-add paid by the swapper, redeployment into a 1- or 2-spacing band) and a missed trigger; no funds are misdirected, so severity is low. The README documents trunc_toward_zero but not that it inverts the trigger. Minimal fix: compute the mean with round-half-away-from-zero or floor division consistently (e.g. `mean = int24((sum >= 0 ? sum + 4 : sum - 4) / 8)`), or evaluate deviates() in eighths (`16 * |8*tick - sum| > 3 * sigma8` with sigma computed from the un-truncated residuals) so the decision does not depend on an integer mean.","line":15,"path":"src/libraries/BandMath.sol","reproduction":"Call BandMath.statistics([6,6,6,6,6,6,6,5]) -> mean = 5, sigma = 0 (true mean 5.875, true sigma 0.331). BandMath.deviates(6, 5, 0) returns true (expected false: 0.125 < 0.496); BandMath.deviates(5, 5, 0) returns false (expected true: 0.875 > 0.496). Mirror case for negative ticks: [-6,-6,-6,-6,-6,-6,-6,-5] -> mean -5, sigma 0, deviates(-6) true, deviates(-5) false. On-chain: seed the window at tick 6 via seven swaps, warp 30 minutes, perform a swap that ends at tick 6 again after one sample at tick 5 was recorded -> _afterSwap rebalances (lastRebalance updated, Rebalanced emitted) although the active tick is the market's resting tick.","severity":"low","snippet":"        mean = int24(sum / 8);","title":"SMA truncates toward zero, so with sigma floored to 0 the majority tick is classed as deviating and the outlier is not"},{"citation":"resolved","description":"For supply == 0, _quoteDeposit sizes `initial` from the binding side only (VaultMath.liquidityForAmounts) but returns amount0 = max0 and amount1 = max1, and deposit() pulls both in full (lines 154-155). The non-binding side is never refunded and sits idle, owned pro-rata by all shares including the 1000 MINIMUM_SHARES minted to the hook (line 150), which have no redemption path. The depositor's loss is max_unused * 1000 / initial. For a balanced deposit initial is ~1e20 and the loss is negligible, but the pull is unconditional, so when the binding side is a few wei (initial just above 1000) the depositor loses most of a large unused budget permanently. previewDeposit returns shares = 335 and amount0 = 10 ether for the inputs below, giving no signal of the mismatch, and minShares cannot protect against it because the shares are indeed minted. Later deposits (lines 425-429) already pull only the required amounts, so the first deposit is the only path with this behaviour. Minimal fix: after computing `initial`, call VaultMath.amounts(price, lower, upper, initial, true) and return those required amounts instead of (max0, max1), leaving the surplus with the depositor; alternatively require amount0 and amount1 both within a small factor of what `initial` needs.","line":419,"path":"src/SigmaHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {SigmaHook} from \"src/SigmaHook.sol\";\nimport {SigmaToken} from \"src/SigmaToken.sol\";\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\ncontract Weth is ERC20 {\n    constructor() ERC20(\"W\", \"W\") {}\n    function mint(address to, uint256 a) external { _mint(to, a); }\n}\n\ncontract FirstDepositPullsUnusedBudget is Test {\n    IPoolManager manager;\n    SigmaHook hook;\n    SigmaToken token;\n    Weth weth;\n    PoolKey key;\n    address alice = makeAddr(\"alice\");\n\n    function setUp() public {\n        manager = IPoolManager(address(new PoolManager(address(this))));\n        token = new SigmaToken();\n        weth = new Weth();\n        bytes memory initCode = bytes.concat(type(SigmaHook).creationCode, abi.encode(manager, address(this), address(token), address(this)));\n        bytes32 hash = keccak256(initCode);\n        for (uint256 i; i < 200_000; ++i) {\n            address p = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), hash)))));\n            if (HookFlags.matches(p, HookFlags.SIGMA) && p.code.length == 0) {\n                hook = new SigmaHook{salt: bytes32(i)}(manager, address(this), address(token), address(this));\n                break;\n            }\n        }\n        (address a, address b) = address(token) < address(weth) ? (address(token), address(weth)) : (address(weth), address(token));\n        key = PoolKey(Currency.wrap(a), Currency.wrap(b), 12_500, 60, IHooks(address(hook)));\n        manager.initialize(key, 1 << 96);\n        token.transfer(alice, 100 ether);\n        weth.mint(alice, 100 ether);\n        vm.startPrank(alice);\n        token.approve(address(hook), type(uint256).max);\n        weth.approve(address(hook), type(uint256).max);\n        vm.stopPrank();\n    }\n\n    function test_firstDepositOnlyPullsWhatLiquidityNeeds() public {\n        bool tokenIs0 = Currency.unwrap(key.currency0) == address(token);\n        uint256 big = 10 ether;\n        uint256 tiny = 4;\n        (uint256 max0, uint256 max1) = tokenIs0 ? (big, tiny) : (tiny, big);\n        uint256 bigBefore = tokenIs0 ? token.balanceOf(alice) : weth.balanceOf(alice);\n        vm.prank(alice);\n        (uint256 shares,,) = hook.deposit(max0, max1, 1, alice, block.timestamp);\n        assertGt(shares, 0);\n        vm.prank(alice);\n        hook.withdraw(shares, 0, 0, alice, block.timestamp);\n        uint256 bigAfter = tokenIs0 ? token.balanceOf(alice) : weth.balanceOf(alice);\n        // A depositor whose first deposit is immediately withdrawn must not lose a material share\n        // of the budget that the initial liquidity never used. On the current code 74.9% is lost.\n        assertLt(bigBefore - bigAfter, big / 100, \"unused first-deposit budget forfeited to locked shares\");\n    }\n}","reproduction":"Fresh pool at tick 0 (sqrtPrice 2^96), bands [-60, 60], no prior deposits. alice calls deposit(10e18, 4, 1, alice, deadline) on the token/WETH pool. Observed: liquidityForAmounts binds on amount1: l1 = floor(4 * 2^96 / (2^96 - sqrtPrice(-60))) = 1335, so initial = 1335, shares = 335, hook keeps 1000. Both 10e18 token and 4 wei WETH are pulled (deposit returns a0 = 10e18, a1 = 4). totalAssets() = (9999999999999999999, 3). alice immediately withdraws all 335 shares and receives 2509363295880149812 token and 1 wei WETH: 7490636704119850188 token (74.9% of the deposit) is now owned by the hook's locked shares and cannot be recovered by anyone. Expected: only the ~4 wei of token0 the 1335 liquidity units need (plus dust) is pulled, or the deposit reverts as unusable.","severity":"low","snippet":"            if (initial <= MINIMUM_SHARES) return (0, 0, 0);\n            return (initial - MINIMUM_SHARES, max0, max1);","title":"First deposit pulls both full budgets regardless of what the initial liquidity uses, so an imbalanced first deposit near the 1000-share floor forfeits most of the unused side to the locked shares"}],"hash":"ba38db963ff00626395a7b8e636dd4547e25ef076c196c66d2bbd77d802fa51c","nodeId":"b4a344ce-f9a3-48a4-8540-cb09580aa3a9","outcome":"completed","summary":"Review complete. The findings file is written and validated, the proof test fails on the current code as required, and the project's own 45 tests all pass.\n\n**Outcome.** The math in SigmaHook is sound in the places that move funds. Share pricing rounds in the vault's favour on every proportional path, liquidity sizing never asks the PoolManager for more than the hook holds, the per-tick capacity matches v4 exactly, and the band clamping holds at both tick extremes. I found two low-severity defects, both with concrete inputs, recorded in `.imd-findings.json`:\n\n1. **Truncated SMA inverts the rebalance trigger when sigma floors to zero.** With seven samples at tick 6 and one at tick 5, the integer mean is 5 and sigma is 0, so the resting tick 6 counts as \"deviating\" and the outlier 5 does not. Under the specified real-valued test the decisions are the opposite. Effect is spurious rebalance churn after the cooldown in quiet markets, not misdirected funds.\n\n2. **First deposit pulls both full budgets regardless of what the initial liquidity uses.** Depositing 10 ether of token with 4 wei of WETH at tick 0 mints 335 shares against 1000 locked shares. Alice withdraws immediately and recovers 2.51 ether. The remaining 7.49 ether belongs permanently to the hook's unredeemable shares. Later deposits already pull only the required amounts, so the fix is to apply the same sizing to the first one. A Foundry proof is attached and verified failing.\n\n**Coverage.** All 25 listed entry points have a row, plus three invariant rows. Two rows carry findings (afterSwap, deposit); the rest hold. No row is unreached.\n\n**Not covered.** Economics of rebalancing at a manipulated price is outside my area and is documented by the author as an accepted trust assumption, so I did not pursue it. Reentrancy leads from Slither sit in the control-flow area and were not reproduced here.","treeHash":null,"usage":{"cachedInputTokens":1091395,"inputTokens":356,"model":"claude-fable-5-1","outputTokens":44446,"runtime":"claude","turns":27,"wallClockMs":1227521}},{"artifacts":[],"attempt":1,"bundleHash":"d8c61f4ad6ea28e05e0683673a0ea2a03c466be1e8c341e27622ffe11c065df4","device":"63274cc86a9400de","findings":[],"hash":"debe393346a581da072023483bf4fd37ed0160db30ccf8ffb1e07d8c8adeb9b3","nodeId":"fe905d3b-cd09-4f5e-a495-66c2e9da77b2","outcome":"completed","summary":"I updated launch.json and the manifest is ready. The local build did not finish in the time allowed.\n\n**What I did**\n\n- **Verified the existing manifest against the source.** The hook contract is `SigmaHook` and its constructor takes the pool manager, the owner, the launch token and the factory in that order. The manifest writes them as \"$poolManager\", \"$owner\", \"$token\" and \"$factory\". getHookPermissions enables only afterInitialize and afterSwap, which is what the manifest lists. The token contract is SigmaToken, named SigmaHook with symbol SHK and 18 decimals. The pool block is this launch's own: native ETH pair, fee 12500, tickSpacing 60 and the one-to-one initialPrice.\n- **Refined the notes** so they describe the accepted implementation more fully. They now state that no return-delta flag is set and afterSwap returns a zero delta, that afterInitialize accepts only the factory on a single initialization with this hook, the 12500 fee, spacing 60 and the launch token in the key, that unlockCallback accepts only the PoolManager for unlocks the hook started, that the rebalance trigger requires external shares, the 30 minute interval and a 1.5 sigma deviation, that ownership cannot be renounced, and that emergency mode sends nothing to the owner. The notes are well under the cap and the JSON parses with exactly the schema's five top-level keys.\n- **Changed no other file.** Git shows only launch.json modified.\n\n**What I could not verify**\n\nThe forge build on this machine was still compiling after about twenty minutes under via_ir and was stopped by the background time limit before it or the test run produced output. The manifest does not affect compilation, so the build result is unchanged from the previously accepted round, but I have no fresh local build or fuzz test output to report. The verifier runs the build and the fuzz tests with its own, larger limits.","treeHash":"a3c87d82ab912dc9778d30e0a9b24e0c1518aae9","usage":{"cachedInputTokens":483410,"inputTokens":324,"model":"claude-fable-5-1","outputTokens":6724,"runtime":"claude","turns":15,"wallClockMs":1248772}},{"artifacts":[],"attempt":1,"bundleHash":"4be80cb33b444a3af15b57ce09bf91dabcd6eb28c550f0b3d4c99534f701963a","device":"4539d3d0693a7444","findings":[],"hash":"e299a4bd4b6fee7c36c5493f20f546205564d80bbc522e4c7cdce554f6d68099","nodeId":"fd352bf8-7ae0-4953-abe3-4ce823a259a6","outcome":"completed","summary":"Implemented the SHK token, vault hook, compounding, shares, and emergency exits, with vendored dependencies and deployment/security documentation.\n\nPassed:\n\n- `forge build`\n- `forge test`: 42 tests, required fuzz seed, 2,048 invariant actions\n- `forge fmt --check`\n\nDocumented limits: execution ticks are manipulable, and unmatched inventory remains idle without balancing swaps.","treeHash":"79e5ded63cf18d14789c713ef715076c92be7c91","usage":{"cachedInputTokens":3865984,"inputTokens":110043,"model":"gpt-6-astra","outputTokens":58552,"runtime":"codex","turns":9,"wallClockMs":1484490}}],"verification":[{"checks":[{"durationMs":37045,"exitCode":0,"name":"build","output":"Compiling 102 files with Solc 0.8.26\nSolc 0.8.26 finished in 36.95s\nCompiler run successful!\n","passed":true},{"durationMs":695,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/SigmaToken.t.sol:SigmaTokenTest\n[PASS] testFuzz_transferAndAllowance(uint256) (runs: 256, μ: 179831, ~: 179807)\nLogs:\n  Bound result 5000000000000000001\n\n[PASS] test_fixedSupplyAndMetadata() (gas: 49353)\n[PASS] test_noAdminOrAdditionalMint() (gas: 127664)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 72.21ms (72.03ms CPU time)\n\nRan 7 tests for test/BandMath.t.sol:BandMathTest\n[PASS] testFuzz_liquiditySizingNeverOverspends(int24,int24,int24,uint128,uint128,uint128) (runs: 256, μ: 30725, ~: 30786)\nLogs:\n  Bound result 762222\n  Bound result 875313\n  Bound result 22865\n\n[PASS] testFuzz_statisticsMatchIndependentIntegerReference(int24[8],uint16) (runs: 256, μ: 85462, ~: 85426)\nLogs:\n  Bound result 93\n  Bound result 111\n  Bound result -15769\n  Bound result 0\n  Bound result -1\n  Bound result 116891\n  Bound result -377603\n  Bound result 18\n  Bound result 189\n\n[PASS] test_extremeTicksStayWithinUsableGrid() (gas: 17203)\n[PASS] test_knownPopulationStatistics() (gas: 19812)\n[PASS] test_negativeMeanTruncatesTowardsZero() (gas: 11645)\n[PASS] test_sizingOneSqrtUnitFromUpperAndVeryLowTicks() (gas: 13762)\n[PASS] test_strictTriggerAndHalfIntegerThreshold() (gas: 9721)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 89.59ms (19.21ms CPU time)\n\nRan 6 tests for test/NativeVault.t.sol:NativeVaultTest\n[PASS] testFuzz_freshManagerRebalanceBeforeInputSettlement(bool) (runs: 256, μ: 1402075, ~: 1402433)\n[PASS] test_emergencyRedeemsClaimsAndNeedsNoManagerForWithdrawal() (gas: 1156664)\n[PASS] test_exactInputAndOutputBothDirectionsRemainUsable() (gas: 1281530)\n[PASS] test_nativeDepositRefundAndOrdinaryExit() (gas: 906622)\n[PASS] test_reentrantEmergencyReceiverCannotRedeemTwice() (gas: 1034755)\n[PASS] test_rejectingEthRecipientRollsBackAndCanChooseAnotherReceiver() (gas: 971202)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 89.60ms (80.94ms CPU time)\n\nRan 7 tests for test/Adversarial.t.sol:AdversarialTest\n[PASS] test_emergencyCannotBurnOtherHoldersShares() (gas: 780753)\n[PASS] test_emptyVaultCanEnterEmergency() (gas: 125116)\n[PASS] test_feeOnTransferDepositIsRejectedAtomically() (gas: 18845279)\n[PASS] test_initializationAtPriceExtremesAndSingleSidedDeposits() (gas: 203916629)\n[PASS] test_liquidityCapacityCannotBlockSwapRebalance() (gas: 1753613)\n[PASS] test_saltToolUsesExactFactoryAndConstructorArguments() (gas: 7338983)\n[PASS] test_tokenCallbackCannotReenterDeposit() (gas: 19201357)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 98.01ms (148.06ms CPU time)\n\nRan 18 tests for test/SigmaHook.t.sol:SigmaHookTest\n[PASS] testFuzz_donationsDoNotAllowZeroShareDeposit(uint80) (runs: 256, μ: 933135, ~: 1059929)\nLogs:\n  Bound result 4813\n\n[PASS] testFuzz_roundTripNeverCreatesAssets(uint96,uint96) (runs: 256, μ: 794512, ~: 796876)\nLogs:\n  Bound result 2244002891044233572\n  Bound result 2364829\n\n[PASS] test_callbacksAndUnlockRefuseUnauthorizedCalls() (gas: 123901)\n[PASS] test_circularFeedUsesPostExecutionSlot0AndWraps() (gas: 2326686)\n[PASS] test_cooldownRequiresFullThirtyMinutesAndDeviation() (gas: 1490084)\n[PASS] test_depositAndWithdrawalSlippageDeadlineAndZeroChecks() (gas: 1091408)\n[PASS] test_emergencyFreezesInvestmentButNotSwapsOrShareTransfers() (gas: 1300490)\n[PASS] test_emergencyRemovesAllLiquidityAndWithdrawsProRata() (gas: 1588101)\n[PASS] test_feeOwnershipCannotBeDilutedByNewDepositor() (gas: 1476555)\n[PASS] test_firstDepositAndProportionalSecondDeposit() (gas: 1011828)\n[PASS] test_minedPermissionsInitializationAndExplicitOwner() (gas: 192731)\n[PASS] test_noDeviationDoesNotRebalanceAfterCooldown() (gas: 886764)\n[PASS] test_otherLpCanCollectZeroDeltaFeesEvenInEmergency() (gas: 1546160)\n[PASS] test_ownerTransferRequiresAcceptanceAndCannotRenounce() (gas: 152285)\n[PASS] test_rebalanceBurnsEntireOldPositionAndKeepsAllCapital() (gas: 1235575)\n[PASS] test_rebalanceCompoundsAllAccruedFeesAndDust() (gas: 1296177)\n[PASS] test_rejectsSecondPoolAndWrongCallbackPool() (gas: 127060)\n[PASS] test_uninitializedFunctionsAndFactoryGate() (gas: 30980965)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 98.03ms (158.51ms CPU time)\n\nRan 1 test for test/VaultInvariant.t.sol:VaultInvariantTest\n[PASS]\nVaultInvariantTest invariants:\n[PASS] invariant_currencyAndShareConservation\n[PASS] invariant_positionAndFlashAccounting\n VaultInvariantTest invariants (runs: 64, calls: 2048, reverts: 0)\n\n╭--------------+----------------+-------+---------+----------╮\n| Contract     | Selector       | Calls | Reverts | Discards |\n+============================================================+\n| VaultHandler | advanceTime    | 287   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | deposit        | 311   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | donate         | 302   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | emergency      | 280   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | trade          | 284   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | transferShares | 297   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | withdraw       | 287   | 0       | 0        |\n╰--------------+----------------+-------+---------+----------╯\n\nLogs:\n  Bound result -4\n  Bound result 1940889691275172922302\n  Bound result 8691\n  Bound result 6\n  Bound result 100000000000000000\n  Bound result 4553\n  Bound result 999999999999012168\n  Bound result 999999999999000017\n  Bound result 24\n  Bound result 999999999999000031\n  Bound result 999999999999000005\n  Bound result 120\n  Bound result 1560\n  Bound result 6000000000000000000\n  Bound result 735889\n  Bound result 6\n  Bound result 4096\n  Bound result 2125\n  Bound result 25\n  Bound result 200000\n  Bound result 4096\n  Bound result 999999999999016385\n  Bound result 999999999999018074\n  Bound result 17700\n  Bound result 284\n  Bound result 2070\n  Bound result 1572524882665486112158\n  Bound result 999999999999012717\n  Bound result 999999999999000065\n  Bound result 7352\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 577.39ms (561.58ms CPU time)\n\nRan 6 test suites in 580.07ms (1.02s CPU time): 42 tests passed, 0 failed, 0 skipped (42 total tests)\n","passed":true},{"durationMs":61,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SigmaHook.acceptOwnership()\",\"SigmaHook.activateEmergencyMode()\",\"SigmaHook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"SigmaHook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"SigmaHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"SigmaHook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"SigmaHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SigmaHook.approve(address,uint256)\",\"SigmaHook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"SigmaHook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"SigmaHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"SigmaHook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"SigmaHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SigmaHook.deposit(uint256,uint256,uint256,address,uint256)\",\"SigmaHook.emergencyWithdraw()\",\"SigmaHook.emergencyWithdraw(uint256,address)\",\"SigmaHook.receive()\",\"SigmaHook.transfer(address,uint256)\",\"SigmaHook.transferFrom(address,address,uint256)\",\"SigmaHook.transferOwnership(address)\",\"SigmaHook.unlockCallback(bytes)\",\"SigmaHook.withdraw(uint256,uint256,uint256,address,uint256)\",\"SigmaToken.approve(address,uint256)\",\"SigmaToken.transfer(address,uint256)\",\"SigmaToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":3,\"LICENSE\":21,\"README.md\":148,\"dependencies.lock.json\":64,\"docs/SECURITY.md\":38,\"foundry.toml\":21,\"launch.json\":29,\"remappings.txt\":8,\"script/MineSalt.s.sol\":31,\"src/HookFlags.sol\":30,\"src/SigmaHook.sol\":475,\"src/SigmaToken.sol\":11,\"src/libraries/BandMath.sol\":57,\"src/libraries/VaultMath.sol\":54,\"test/Adversarial.t.sol\":168,\"test/BandMath.t.sol\":125,\"test/NativeVault.t.sol\":142,\"test/SigmaHook.t.sol\":384,\"test/SigmaToken.t.sol\":51,\"test/VaultInvariant.t.sol\":168,\"test/helpers/Fixture.sol\":108,\"test/helpers/MockERC20.sol\":24,\"test/helpers/TestRouter.sol\":84},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"52f75d8364eea97951efc8d6656da7787b6c0498f67ea4ab853b6ad56601a5a7","verifiedTreeHash":"4f9b72f65fdbdbe704b3d88e95b736de9b381ced","verifierVersion":"0.1.0+d372f99e"},{"checks":[{"durationMs":64177,"exitCode":0,"name":"build","output":"Compiling 109 files with Solc 0.8.26\nSolc 0.8.26 finished in 64.06s\nCompiler run successful!\n","passed":true},{"durationMs":6802,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/SigmaToken.t.sol:SigmaTokenTest\n[PASS] testFuzz_transferAndAllowance(uint256) (runs: 256, μ: 179828, ~: 179807)\nLogs:\n  Bound result 36\n\n[PASS] test_fixedSupplyAndMetadata() (gas: 49353)\n[PASS] test_noAdminOrAdditionalMint() (gas: 127664)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 21.37ms (21.43ms CPU time)\n\nRan 5 tests for test/WethVaultIntegration.t.sol:WethVaultIntegrationTest\n[PASS] test_emergencyReturnsWrappedEtherToChosenReceiverWithoutUnwrapping() (gas: 998219)\n[PASS] test_emptyCurrency0ReserveExactInputRebalance() (gas: 1558236)\n[PASS] test_emptyCurrency0ReserveExactOutputRebalance() (gas: 1557467)\n[PASS] test_emptyCurrency1ReserveExactInputRebalance() (gas: 1561884)\n[PASS] test_emptyCurrency1ReserveExactOutputRebalance() (gas: 1562182)\nSuite result: ok. 5 passed; 0 failed; 0 skipped; finished in 26.69ms (4.49ms CPU time)\n\nRan 9 tests for test/VaultFailurePaths.t.sol:VaultFailurePathsTest\n[PASS] test_allUninitializedVaultEntrypointsRefuseBeforeTokenTransfers() (gas: 30282325)\n[PASS] test_budgetAboveInt128LimitCannotHarvestFeesOrPullTokens() (gas: 1705740)\n[PASS] test_emergencyCannotBeUsedBeforeActivationAndLiveEntrypointsCloseAfterward() (gas: 1778659)\n[PASS] test_invalidPoolAttemptsDoNotPoisonLaterValidInitialization() (gas: 31456400)\n[PASS] test_invalidReceiversAreAtomicInLiveAndEmergencyModes() (gas: 2202014)\n[PASS] test_minSharesFailureAfterHarvestRestoresUncollectedFees() (gas: 1732356)\n[PASS] test_pendingAndFormerOwnerCannotActivateEmergency() (gas: 813602)\n[PASS] test_secondCurrencyWithdrawalMinimumRollsBackBurnAndPosition() (gas: 1714448)\n[PASS] test_secondTokenPullFailureRestoresHarvestMintAndFirstTokenPull() (gas: 1865177)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 58.12ms (71.98ms CPU time)\n\nRan 7 tests for test/BandMath.t.sol:BandMathTest\n[PASS] testFuzz_liquiditySizingNeverOverspends(int24,int24,int24,uint128,uint128,uint128) (runs: 256, μ: 30726, ~: 30772)\nLogs:\n  Bound result 762222\n  Bound result 875313\n  Bound result 22865\n\n[PASS] testFuzz_statisticsMatchIndependentIntegerReference(int24[8],uint16) (runs: 256, μ: 85447, ~: 85426)\nLogs:\n  Bound result 93\n  Bound result 111\n  Bound result -15769\n  Bound result 0\n  Bound result -1\n  Bound result 116891\n  Bound result -377603\n  Bound result 18\n  Bound result 189\n\n[PASS] test_extremeTicksStayWithinUsableGrid() (gas: 17203)\n[PASS] test_knownPopulationStatistics() (gas: 19812)\n[PASS] test_negativeMeanTruncatesTowardsZero() (gas: 11645)\n[PASS] test_sizingOneSqrtUnitFromUpperAndVeryLowTicks() (gas: 13762)\n[PASS] test_strictTriggerAndHalfIntegerThreshold() (gas: 9721)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 92.97ms (27.36ms CPU time)\n\nRan 7 tests for test/Adversarial.t.sol:AdversarialTest\n[PASS] test_emergencyCannotBurnOtherHoldersShares() (gas: 780753)\n[PASS] test_emptyVaultCanEnterEmergency() (gas: 125116)\n[PASS] test_feeOnTransferDepositIsRejectedAtomically() (gas: 18845279)\n[PASS] test_initializationAtPriceExtremesAndSingleSidedDeposits() (gas: 203916629)\n[PASS] test_liquidityCapacityCannotBlockSwapRebalance() (gas: 1753613)\n[PASS] test_saltToolUsesExactFactoryAndConstructorArguments() (gas: 7338983)\n[PASS] test_tokenCallbackCannotReenterDeposit() (gas: 19201357)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 96.28ms (111.99ms CPU time)\n\nRan 6 tests for test/NativeVault.t.sol:NativeVaultTest\n[PASS] testFuzz_freshManagerRebalanceBeforeInputSettlement(bool) (runs: 256, μ: 1402075, ~: 1402433)\n[PASS] test_emergencyRedeemsClaimsAndNeedsNoManagerForWithdrawal() (gas: 1156664)\n[PASS] test_exactInputAndOutputBothDirectionsRemainUsable() (gas: 1281530)\n[PASS] test_nativeDepositRefundAndOrdinaryExit() (gas: 906622)\n[PASS] test_reentrantEmergencyReceiverCannotRedeemTwice() (gas: 1034755)\n[PASS] test_rejectingEthRecipientRollsBackAndCanChooseAnotherReceiver() (gas: 971202)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 99.50ms (87.71ms CPU time)\n\nRan 18 tests for test/SigmaHook.t.sol:SigmaHookTest\n[PASS] testFuzz_donationsDoNotAllowZeroShareDeposit(uint80) (runs: 256, μ: 933135, ~: 1059929)\nLogs:\n  Bound result 1068823178\n\n[PASS] testFuzz_roundTripNeverCreatesAssets(uint96,uint96) (runs: 256, μ: 794512, ~: 796876)\nLogs:\n  Bound result 99999999999999014164\n  Bound result 99999999999999011552\n\n[PASS] test_callbacksAndUnlockRefuseUnauthorizedCalls() (gas: 123901)\n[PASS] test_circularFeedUsesPostExecutionSlot0AndWraps() (gas: 2326686)\n[PASS] test_cooldownRequiresFullThirtyMinutesAndDeviation() (gas: 1490084)\n[PASS] test_depositAndWithdrawalSlippageDeadlineAndZeroChecks() (gas: 1091408)\n[PASS] test_emergencyFreezesInvestmentButNotSwapsOrShareTransfers() (gas: 1300490)\n[PASS] test_emergencyRemovesAllLiquidityAndWithdrawsProRata() (gas: 1588101)\n[PASS] test_feeOwnershipCannotBeDilutedByNewDepositor() (gas: 1476555)\n[PASS] test_firstDepositAndProportionalSecondDeposit() (gas: 1011828)\n[PASS] test_minedPermissionsInitializationAndExplicitOwner() (gas: 192731)\n[PASS] test_noDeviationDoesNotRebalanceAfterCooldown() (gas: 886764)\n[PASS] test_otherLpCanCollectZeroDeltaFeesEvenInEmergency() (gas: 1546160)\n[PASS] test_ownerTransferRequiresAcceptanceAndCannotRenounce() (gas: 152285)\n[PASS] test_rebalanceBurnsEntireOldPositionAndKeepsAllCapital() (gas: 1235575)\n[PASS] test_rebalanceCompoundsAllAccruedFeesAndDust() (gas: 1296177)\n[PASS] test_rejectsSecondPoolAndWrongCallbackPool() (gas: 127060)\n[PASS] test_uninitializedFunctionsAndFactoryGate() (gas: 30980965)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 103.52ms (162.67ms CPU time)\n\nRan 8 tests for test/MathProperties.t.sol:MathPropertiesTest\n[PASS] testFuzz_bandsEncloseThreeSigmaOnTheSmallestUsableGrid(int24,uint24) (runs: 1000, μ: 20433, ~: 20622)\nLogs:\n  Bound result -1\n  Bound result 2371\n\n[PASS] testFuzz_largerBudgetsNeverReduceLiquidity(int24,int24,int24,uint128,uint128,uint128) (runs: 1000, μ: 31059, ~: 31048)\nLogs:\n  Bound result -1939\n  Bound result 0\n  Bound result 779393\n\n[PASS] testFuzz_principalRoundingAndPriceMonotonicity(int24,int24,int24,int24,uint128) (runs: 1000, μ: 42878, ~: 42953)\nLogs:\n  Bound result 3691\n  Bound result 13675\n  Bound result -60\n  Bound result -60\n\n[PASS] testFuzz_statisticsIgnoreOrderAndReflectAboutZero(int24[8],uint8) (runs: 1000, μ: 86399, ~: 86382)\nLogs:\n  Bound result -784382\n  Bound result 10280\n  Bound result 0\n  Bound result 1\n  Bound result 6\n  Bound result -25592\n  Bound result 2\n  Bound result 209986\n\n[PASS] testFuzz_symmetricTwoPointDistributionHasKnownPopulationSigma(int24,uint24) (runs: 1000, μ: 23040, ~: 22960)\nLogs:\n  Bound result -83\n  Bound result 2\n\n[PASS] testFuzz_triggerIsStrictAtBothSidesOfOneAndAHalfSigma(uint24,int24) (runs: 1000, μ: 17069, ~: 16933)\nLogs:\n  Bound result 170879\n  Bound result 21744\n\n[PASS] test_oneWeiFundsOnlyAffordableLiquidityAtExactBandBoundaries() (gas: 11215)\n[PASS] test_zeroLiquidityZeroBudgetsAndZeroCapacity() (gas: 9330)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 103.71ms (579.35ms CPU time)\n\nRan 1 test for test/VaultInvariant.t.sol:VaultInvariantTest\n[PASS]\nVaultInvariantTest invariants:\n[PASS] invariant_currencyAndShareConservation\n[PASS] invariant_positionAndFlashAccounting\n VaultInvariantTest invariants (runs: 64, calls: 2048, reverts: 0)\n\n╭--------------+----------------+-------+---------+----------╮\n| Contract     | Selector       | Calls | Reverts | Discards |\n+============================================================+\n| VaultHandler | advanceTime    | 287   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | deposit        | 311   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | donate         | 302   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | emergency      | 280   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | trade          | 284   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | transferShares | 297   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | withdraw       | 287   | 0       | 0        |\n╰--------------+----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 0\n  Bound result 1940889691275172922302\n  Bound result 7695\n  Bound result 5\n  Bound result 86400\n  Bound result 3645\n  Bound result 999999999999011839\n  Bound result 999999999999016384\n  Bound result 6838\n  Bound result 999999999999000019\n  Bound result 999999999999016385\n  Bound result 1672\n  Bound result 3967\n  Bound result 500000000000000000\n  Bound result 735889\n  Bound result 6\n  Bound result 12\n  Bound result 1929\n  Bound result 6839\n  Bound result 99999999999999901\n  Bound result 12\n  Bound result 999999999999200001\n  Bound result 999999999999018198\n  Bound result 17372\n  Bound result 4202047187\n  Bound result 3169\n  Bound result 900\n  Bound result 999999999999002382\n  Bound result 999999999999000033\n  Bound result 74881068\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 606.70ms (579.44ms CPU time)\n\nRan 2 tests for test/VaultAccountingInvariant.t.sol:VaultAccountingInvariantTest\n[PASS] invariant_assetsBackedByExternalCashFlows() (runs: 256, calls: 16384, reverts: 0)\n\n╭-------------------+----------------+-------+---------+----------╮\n| Contract          | Selector       | Calls | Reverts | Discards |\n+=================================================================+\n| AccountingHandler | deposit        | 2076  | 0       | 0        |\n|-------------------+----------------+-------+---------+----------|\n| AccountingHandler | donateFees     | 2081  | 0       | 0        |\n|-------------------+----------------+-------+---------+----------|\n| AccountingHandler | donateRaw      | 2006  | 0       | 0        |\n|-------------------+----------------+-------+---------+----------|\n| AccountingHandler | emergency      | 2050  | 0       | 0        |\n|-------------------+----------------+-------+---------+----------|\n| AccountingHandler | roundTrip      | 2069  | 0       | 0        |\n|-------------------+----------------+-------+---------+----------|\n| AccountingHandler | trade          | 2096  | 0       | 0        |\n|-------------------+----------------+-------+---------+----------|\n| AccountingHandler | transferShares | 1982  | 0       | 0        |\n|-------------------+----------------+-------+---------+----------|\n| AccountingHandler | withdraw       | 2024  | 0       | 0        |\n╰-------------------+----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 5206\n  Bound result 16765\n  Bound result 13240\n  Bound result 3174\n  Bound result 9876578164584\n  Bound result 7190895597\n  Bound result 1314306579\n  Bound result 16315\n  Bound result 13426944564297007743849\n  Bound result 3609\n  Bound result 16384\n  Bound result 264\n  Bound result 1814806141\n  Bound result 499\n  Bound result 60\n  Bound result 20\n  Bound result 1067062455535944235\n  Bound result 2740369823211770676\n  Bound result 999999999999999987\n  Bound result 15294\n  Bound result 272377635525828524\n  Bound result 6897161714\n  Bound result 1820\n  Bound result -311\n  Bound result 1033\n  Bound result 48\n  Bound result 4384\n  Bound result 26935855898667325454363\n  Bound result 2391\n  Bound result 1\n  Bound result 14559\n  Bound result 24059551756859167120498\n  Bound result 7204\n  Bound result 4355\n  Bound result 409981434112200789\n  Bound result 385183609364930618\n  Bound result 7459346689\n  Bound result 57043496163466\n  Bound result 2669\n  Bound result 353\n  Bound result 6500\n  Bound result 3\n  Bound result 264\n  Bound result 736216116482799851392\n  Bound result 4\n  Bound result 4999999999999998001\n  Bound result 32767\n  Bound result 1000000\n  Bound result 1000000\n  Bound result 999999999999999992\n  Bound result 500000000000000000\n  Bound result 21000000000000000000\n  Bound result 2048\n  Bound result 800\n  Bound result 1000\n  Bound result 6232987032538702547194\n  Bound result 664\n  Bound result -61\n  Bound result 4137960713120718050\n  Bound result 2\n  Bound result 18\n  Bound result 1228746688637868731\n  Bound result 2254493979851658385\n  Bound result 4484934279203396000\n  Bound result 10662\n  Bound result 999999999999999996\n  Bound result 2000\n  Bound result 5608\n  Bound result 4665\n  Bound result 91670927449640089711\n  Bound result 9\n  Bound result 2970833533017322880427\n  Bound result 999999999999999989\n  Bound result 17038\n  Bound result 19583219292435636846533\n  Bound result 4779007646337725170\n  Bound result 3953609781053057173\n  Bound result 159292\n  Bound result 16384\n  Bound result 11939\n  Bound result 524289\n  Bound result 5000000000000000000\n  Bound result 127\n  Bound result 14161\n  Bound result 4\n  Bound result 1000000\n  Bound result 2787\n  Bound result 3958489515974596577\n  Bound result 1088517\n  Bound result 35532\n  Bound result 1000000000000000000\n  Bound result 5000000000000000000\n  Bound result 11174065979087538296703\n  Bound result 7436040118907553311978\n  Bound result 8676\n  Bound result 100\n\n[PASS] test_accountingHandlerExercisesEveryActionAndFullExit() (gas: 3405032)\nLogs:\n  Bound result 3000000000000000000\n  Bound result 2000000000000000000\n  Bound result 1000000000000000000\n  Bound result 500000000000000000\n  Bound result 100000000000000000\n  Bound result 200000000000000000\n  Bound result 1002724390519861757235\n  Bound result 1800\n  Bound result 20\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 6.68s (6.66s CPU time)\n\nRan 10 test suites in 6.68s (7.89s CPU time): 66 tests passed, 0 failed, 0 skipped (66 total tests)\n","passed":true},{"durationMs":67,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SigmaHook.acceptOwnership()\",\"SigmaHook.activateEmergencyMode()\",\"SigmaHook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"SigmaHook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"SigmaHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"SigmaHook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"SigmaHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SigmaHook.approve(address,uint256)\",\"SigmaHook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"SigmaHook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"SigmaHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"SigmaHook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"SigmaHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SigmaHook.deposit(uint256,uint256,uint256,address,uint256)\",\"SigmaHook.emergencyWithdraw()\",\"SigmaHook.emergencyWithdraw(uint256,address)\",\"SigmaHook.receive()\",\"SigmaHook.transfer(address,uint256)\",\"SigmaHook.transferFrom(address,address,uint256)\",\"SigmaHook.transferOwnership(address)\",\"SigmaHook.unlockCallback(bytes)\",\"SigmaHook.withdraw(uint256,uint256,uint256,address,uint256)\",\"SigmaToken.approve(address,uint256)\",\"SigmaToken.transfer(address,uint256)\",\"SigmaToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":3,\"LICENSE\":21,\"README.md\":148,\"dependencies.lock.json\":64,\"docs/SECURITY.md\":38,\"foundry.toml\":21,\"remappings.txt\":8,\"script/MineSalt.s.sol\":31,\"src/HookFlags.sol\":30,\"src/SigmaHook.sol\":475,\"src/SigmaToken.sol\":11,\"src/libraries/BandMath.sol\":57,\"src/libraries/VaultMath.sol\":54,\"test/Adversarial.t.sol\":168,\"test/BandMath.t.sol\":125,\"test/MathProperties.t.sol\":167,\"test/NativeVault.t.sol\":142,\"test/README.md\":62,\"test/SigmaHook.t.sol\":384,\"test/SigmaToken.t.sol\":51,\"test/VaultAccountingInvariant.t.sol\":331,\"test/VaultFailurePaths.t.sol\":267,\"test/VaultInvariant.t.sol\":168,\"test/WethVaultIntegration.t.sol\":127,\"test/helpers/Fixture.sol\":108,\"test/helpers/MockERC20.sol\":24,\"test/helpers/TestRouter.sol\":84},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"8a98e58905f4f1c0f0acbd86ef2a0bda1b35f0ee6670615496eafef54582d598","verifiedTreeHash":"2046e864f81ec4b14f1783f51830decfa2649073","verifierVersion":"0.1.0+d372f99e"},{"checks":[{"durationMs":70172,"exitCode":0,"name":"build","output":"Compiling 109 files with Solc 0.8.26\nSolc 0.8.26 finished in 70.06s\nCompiler run successful!\n","passed":true},{"durationMs":6835,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/WethVaultIntegration.t.sol:WethVaultIntegrationTest\n[PASS] test_emergencyReturnsWrappedEtherToChosenReceiverWithoutUnwrapping() (gas: 1013198)\n[PASS] test_firstDepositAfterEmptyPoolMoveRecentresBandOnFeed() (gas: 866007)\n[PASS] test_priceAboveLaunchSoleVaultReserveExactInputRebalance() (gas: 1575967)\n[PASS] test_priceAboveLaunchSoleVaultReserveExactOutputRebalance() (gas: 1575122)\n[PASS] test_priceBelowLaunchSoleVaultReserveExactInputRebalance() (gas: 1577497)\n[PASS] test_priceBelowLaunchSoleVaultReserveExactOutputRebalance() (gas: 1577008)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 17.86ms (9.31ms CPU time)\n\nRan 3 tests for test/SigmaToken.t.sol:SigmaTokenTest\n[PASS] testFuzz_transferAndAllowance(uint256) (runs: 256, μ: 179847, ~: 179807)\nLogs:\n  Bound result 19000000000000000000\n\n[PASS] test_fixedSupplyAndMetadata() (gas: 49353)\n[PASS] test_noAdminOrAdditionalMint() (gas: 127664)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 36.43ms (36.41ms CPU time)\n\nRan 9 tests for test/VaultFailurePaths.t.sol:VaultFailurePathsTest\n[PASS] test_allUninitializedVaultEntrypointsRefuseBeforeTokenTransfers() (gas: 121337539)\n[PASS] test_budgetAboveInt128LimitCannotHarvestFeesOrPullTokens() (gas: 1721681)\n[PASS] test_emergencyCannotBeUsedBeforeActivationAndLiveEntrypointsCloseAfterward() (gas: 1794835)\n[PASS] test_invalidPoolAttemptsDoNotPoisonLaterValidInitialization() (gas: 122572847)\n[PASS] test_invalidReceiversAreAtomicInLiveAndEmergencyModes() (gas: 2218608)\n[PASS] test_minSharesFailureAfterHarvestRestoresUncollectedFees() (gas: 1748291)\n[PASS] test_pendingAndFormerOwnerCannotActivateEmergency() (gas: 828898)\n[PASS] test_secondCurrencyWithdrawalMinimumRollsBackBurnAndPosition() (gas: 1730153)\n[PASS] test_secondTokenPullFailureRestoresHarvestMintAndFirstTokenPull() (gas: 1881131)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 89.44ms (110.42ms CPU time)\n\nRan 11 tests for test/BandMath.t.sol:BandMathTest\n[PASS] testFuzz_liquiditySizingNeverOverspends(int24,int24,int24,uint128,uint128,uint128) (runs: 256, μ: 30623, ~: 30740)\nLogs:\n  Bound result -754\n  Bound result -3\n  Bound result 578656\n\n[PASS] testFuzz_statisticsMatchIndependentIntegerReference(int24[8],uint16) (runs: 256, μ: 108588, ~: 108628)\nLogs:\n  Bound result -1500\n  Bound result -6\n  Bound result 50\n  Bound result 0\n  Bound result 0\n  Bound result 1500\n  Bound result 9620\n  Bound result 3625\n  Bound result 14252\n\n[PASS] test_extremeTicksStayWithinUsableGrid() (gas: 27474)\n[PASS] test_knownPopulationStatistics() (gas: 37940)\n[PASS] test_lowPriceSizingKeepsPrecision() (gas: 10369)\n[PASS] test_meanRoundsToNearestAndNegativeGridFloors() (gas: 21566)\n[PASS] test_rebalanceTradeTargetsBandRatioAndRespectsDeadband() (gas: 21249)\n[PASS] test_restingTickDoesNotDeviateButOutlierDoes() (gas: 26711)\n[PASS] test_sizingOneSqrtUnitFromUpperAndVeryLowTicks() (gas: 13646)\n[PASS] test_strictTriggerAndHalfIntegerThreshold() (gas: 12157)\n[PASS] test_tradeLimitStopsAtBandEdgeOrCap() (gas: 20709)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 90.69ms (57.28ms CPU time)\n\nRan 26 tests for test/SigmaHook.t.sol:SigmaHookTest\n[PASS] testFuzz_donationsDoNotAllowZeroShareDeposit(uint80) (runs: 256, μ: 943599, ~: 1075473)\nLogs:\n  Bound result 1\n\n[PASS] testFuzz_roundTripNeverCreatesAssets(uint96,uint96) (runs: 256, μ: 805358, ~: 805019)\nLogs:\n  Bound result 2244002891044233572\n  Bound result 2364829\n\n[PASS] test_balancingTradeStaysInsideBandAndCap() (gas: 1360069)\n[PASS] test_callbacksAndUnlockRefuseUnauthorizedCalls() (gas: 124171)\n[PASS] test_circularFeedUsesPostExecutionSlot0AndWraps() (gas: 2338463)\n[PASS] test_cooldownRequiresFullThirtyMinutesAndDeviation() (gas: 1728217)\n[PASS] test_deepPoolTrendRebalanceDeploysMostCapital() (gas: 1428636)\n[PASS] test_depositAndWithdrawalSlippageDeadlineAndZeroChecks() (gas: 1133963)\n[PASS] test_emergencyFreezesInvestmentButNotSwapsOrShareTransfers() (gas: 1316960)\n[PASS] test_emergencyRemovesAllLiquidityAndWithdrawsProRata() (gas: 1604162)\n[PASS] test_emptiedVaultDoesNotRebalanceUntilSomeoneDepositsAgain() (gas: 1937017)\n[PASS] test_feeOwnershipCannotBeDilutedByNewDepositor() (gas: 1492142)\n[PASS] test_firstDepositAfterPriceMoveUsesFreshBand() (gas: 1172076)\n[PASS] test_firstDepositAndProportionalSecondDeposit() (gas: 1027627)\n[PASS] test_firstDepositPullsOnlyWhatTheBandNeeds() (gas: 789717)\n[PASS] test_minedPermissionsInitializationAndExplicitOwner() (gas: 194538)\n[PASS] test_noBalancingTradeInAnEmptyPool() (gas: 902479)\n[PASS] test_noDeviationDoesNotRebalanceAfterCooldown() (gas: 903036)\n[PASS] test_otherLpCanCollectZeroDeltaFeesEvenInEmergency() (gas: 1563451)\n[PASS] test_ownerTransferRequiresAcceptanceAndCannotRenounce() (gas: 152681)\n[PASS] test_rebalanceBurnsEntireOldPositionAndKeepsAllCapital() (gas: 1470140)\n[PASS] test_rebalanceCompoundsAllAccruedFeesAndDust() (gas: 1315891)\n[PASS] test_rejectsSecondPoolAndWrongCallbackPool() (gas: 127264)\n[PASS] test_strayEthIsRefusedOnAnErc20Pair() (gas: 46978)\n[PASS] test_trendRebalanceSellsSurplusAndRedeploys() (gas: 8448602)\n[PASS] test_uninitializedFunctionsAndFactoryGate() (gas: 122412771)\nSuite result: ok. 26 passed; 0 failed; 0 skipped; finished in 102.00ms (233.96ms CPU time)\n\nRan 9 tests for test/MathProperties.t.sol:MathPropertiesTest\n[PASS] testFuzz_bandsEncloseThreeSigmaOnTheSmallestUsableGrid(int256,uint256) (runs: 1000, μ: 21444, ~: 21683)\nLogs:\n  Bound result 1393127\n  Bound result 3738385\n\n[PASS] testFuzz_largerBudgetsNeverReduceLiquidity(int24,int24,int24,uint128,uint128,uint128) (runs: 1000, μ: 31041, ~: 31062)\nLogs:\n  Bound result -1939\n  Bound result 0\n  Bound result 779393\n\n[PASS] testFuzz_momentsIgnoreOrderAndReflectAboutZero(int24[8],uint8) (runs: 1000, μ: 106147, ~: 106062)\nLogs:\n  Bound result 199\n  Bound result 14434\n  Bound result 332\n  Bound result 50\n  Bound result -84601\n  Bound result 5919\n  Bound result 688\n  Bound result 10168\n\n[PASS] testFuzz_principalRoundingAndPriceMonotonicity(int24,int24,int24,int24,uint128) (runs: 1000, μ: 42892, ~: 42992)\nLogs:\n  Bound result -69515\n  Bound result 544030\n  Bound result -4\n  Bound result 25259\n\n[PASS] testFuzz_symmetricTwoPointDistributionHasKnownPopulationSigma(int24,uint24) (runs: 1000, μ: 31548, ~: 31513)\nLogs:\n  Bound result -213855\n  Bound result 91527\n\n[PASS] testFuzz_triggerIsStrictAtBothSidesOfOneAndAHalfSigma(uint24,int24) (runs: 1000, μ: 17536, ~: 17413)\nLogs:\n  Bound result 170879\n  Bound result 21744\n\n[PASS] test_oneWeiFundsOnlyAffordableLiquidityAtExactBandBoundaries() (gas: 11129)\n[PASS] test_triggerUsesTheUnroundedMean() (gas: 13597)\n[PASS] test_zeroLiquidityZeroBudgetsAndZeroCapacity() (gas: 9238)\nSuite result: ok. 9 passed; 0 failed; 0 skipped; finished in 155.90ms (683.59ms CPU time)\n\nRan 8 tests for test/Adversarial.t.sol:AdversarialTest\n[PASS] test_emergencyCannotBurnOtherHoldersShares() (gas: 795983)\n[PASS] test_emptyVaultCanEnterEmergency() (gas: 125282)\n[PASS] test_feeOnTransferDepositIsRejectedAtomically() (gas: 3081520)\n[PASS] test_initializationAtPriceExtremesAndSingleSidedDeposits() (gas: 604344748)\n[PASS] test_liquidityCapacityCannotBlockSwapRebalance() (gas: 1854078)\n[PASS] test_prepayRouterSwapSurvivesARebalance() (gas: 2530453)\n[PASS] test_saltToolUsesExactFactoryAndConstructorArguments() (gas: 7976873)\n[PASS] test_tokenCallbackCannotReenterDeposit() (gas: 3438008)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 157.02ms (144.18ms CPU time)\n\nRan 7 tests for test/NativeVault.t.sol:NativeVaultTest\n[PASS] testFuzz_freshManagerRebalanceBeforeInputSettlement(bool) (runs: 256, μ: 2331235, ~: 2343612)\n[PASS] test_emergencyRedeemsClaimsAndNeedsNoManagerForWithdrawal() (gas: 1474799)\n[PASS] test_exactInputAndOutputBothDirectionsRemainUsable() (gas: 1301240)\n[PASS] test_nativeDepositRefundAndOrdinaryExit() (gas: 922434)\n[PASS] test_onlyTheManagerMaySendEthToTheHook() (gas: 76608)\n[PASS] test_reentrantEmergencyReceiverCannotRedeemTwice() (gas: 1050304)\n[PASS] test_rejectingEthRecipientRollsBackAndCanChooseAnotherReceiver() (gas: 986821)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 156.97ms (146.38ms CPU time)\n\nRan 1 test for test/VaultInvariant.t.sol:VaultInvariantTest\n[PASS]\nVaultInvariantTest invariants:\n[PASS] invariant_currencyAndShareConservation\n[PASS] invariant_positionAndFlashAccounting\n VaultInvariantTest invariants (runs: 64, calls: 2048, reverts: 0)\n\n╭--------------+----------------+-------+---------+----------╮\n| Contract     | Selector       | Calls | Reverts | Discards |\n+============================================================+\n| VaultHandler | advanceTime    | 287   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | deposit        | 311   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | donate         | 302   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | emergency      | 280   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | trade          | 284   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | transferShares | 297   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | withdraw       | 287   | 0       | 0        |\n╰--------------+----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 0\n  Bound result 2690665609385277037475\n  Bound result 15505\n  Bound result 1984\n  Bound result 99999999999999971\n  Bound result 1752933232\n  Bound result 999999999999017946\n  Bound result 999999999999000256\n  Bound result 29\n  Bound result 999999999999086401\n  Bound result 999999999999887221\n  Bound result 1872\n  Bound result 1872\n  Bound result 170000000000000000000\n  Bound result 735889\n  Bound result 6\n  Bound result 2048\n  Bound result 1799\n  Bound result 28\n  Bound result 800\n  Bound result 2048\n  Bound result 4999995\n  Bound result 75115317414630971\n  Bound result 20916\n  Bound result 7752\n  Bound result 1223\n  Bound result 24\n  Bound result 999999999999005759\n  Bound result 999999999999000005\n  Bound result 3822\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 635.26ms (612.29ms CPU time)\n\nRan 2 tests for test/VaultAccountingInvariant.t.sol:VaultAccountingInvariantTest\n[PASS] invariant_assetsBackedByExternalCashFlows() (runs: 256, calls: 16384, reverts: 0)\n\n╭-------------------+----------------+-------+---------+----------╮\n| Contract          | Selector       | Calls | Reverts | Discards |\n+=================================================================+\n| AccountingHandler | deposit        | 2076  | 0       | 0        |\n|-------------------+----------------+-------+---------+----------|\n| AccountingHandler | donateFees     | 2081  | 0       | 0        |\n|-------------------+----------------+-------+---------+----------|\n| AccountingHandler | donateRaw      | 2006  | 0       | 0        |\n|-------------------+----------------+-------+---------+----------|\n| AccountingHandler | emergency      | 2050  | 0       | 0        |\n|-------------------+----------------+-------+---------+----------|\n| AccountingHandler | roundTrip      | 2069  | 0       | 0        |\n|-------------------+----------------+-------+---------+----------|\n| AccountingHandler | trade          | 2096  | 0       | 0        |\n|-------------------+----------------+-------+---------+----------|\n| AccountingHandler | transferShares | 1982  | 0       | 0        |\n|-------------------+----------------+-------+---------+----------|\n| AccountingHandler | withdraw       | 2024  | 0       | 0        |\n╰-------------------+----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 18764\n  Bound result 20001\n  Bound result 14852\n  Bound result 6835\n  Bound result 9876578164584\n  Bound result 7190895597\n  Bound result 790\n  Bound result 2915541509298420725\n  Bound result 10745264240333730372621\n  Bound result 7259\n  Bound result 887220\n  Bound result 900\n  Bound result 12204\n  Bound result 1500\n  Bound result 200\n  Bound result 720\n  Bound result 1067062455535944235\n  Bound result 2740369823211770676\n  Bound result 200000000000000000\n  Bound result 19399\n  Bound result 272377635525828524\n  Bound result 6897161714\n  Bound result 1532\n  Bound result 227\n  Bound result 2311\n  Bound result 135\n  Bound result 4504\n  Bound result 4377897315539680736205\n  Bound result 2391\n  Bound result 1\n  Bound result 16206\n  Bound result 13673545424060125397467\n  Bound result 7226\n  Bound result 45\n  Bound result 409981434112200789\n  Bound result 385183609364930618\n  Bound result 7459346689\n  Bound result 57043496163466\n  Bound result 1264\n  Bound result -559\n  Bound result 3306\n  Bound result 4264337577698317834\n  Bound result 61\n  Bound result 26071636663956405217120\n  Bound result 862294814590944548920\n  Bound result 21\n  Bound result 4999999800000000001\n  Bound result 999999999999999995\n  Bound result 999999999999999995\n  Bound result 2000\n  Bound result 3600\n  Bound result 100000000000000000\n  Bound result 1024\n  Bound result 264\n  Bound result 100\n  Bound result 6202163613039872283115\n  Bound result 2506\n  Bound result -477\n  Bound result 4137960713120718050\n  Bound result 2\n  Bound result 23000000000000000000\n  Bound result 1228746688637868731\n  Bound result 2254493979851658385\n  Bound result 89761234003582939\n  Bound result 11118\n  Bound result 1000000000000000\n  Bound result 24576\n  Bound result 6593\n  Bound result 4770\n  Bound result 2772376972015429449689\n  Bound result 64\n  Bound result 4103114823332902882237\n  Bound result 999999999999999997\n  Bound result 651743038\n  Bound result 98340929464740474793\n  Bound result 4779007646337725170\n  Bound result 3953609781053057173\n  Bound result 159292\n  Bound result 887220\n  Bound result 10077\n  Bound result 11590\n  Bound result 8192\n  Bound result 127\n  Bound result 18618\n  Bound result 200000\n  Bound result 86400\n  Bound result 3763525710\n  Bound result 7268\n  Bound result 1088517\n  Bound result 35532\n  Bound result 1502303472\n  Bound result 999999999999999980\n  Bound result 1007993026031046498721\n  Bound result 91893057305834893602\n  Bound result 9567\n  Bound result 4999999999999998001\n\n[PASS] test_accountingHandlerExercisesEveryActionAndFullExit() (gas: 3416584)\nLogs:\n  Bound result 3000000000000000000\n  Bound result 2000000000000000000\n  Bound result 1000000000000000000\n  Bound result 500000000000000000\n  Bound result 100000000000000000\n  Bound result 200000000000000000\n  Bound result 1002724390519861757235\n  Bound result 1800\n  Bound result 20\n  Bound result 1000000000000000000\n  Bound result 1000000000000000000\n  Bound result 1\n\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 6.69s (6.67s CPU time)\n\nRan 10 test suites in 6.69s (8.13s CPU time): 82 tests passed, 0 failed, 0 skipped (82 total tests)\n","passed":true},{"durationMs":67,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SigmaHook.acceptOwnership()\",\"SigmaHook.activateEmergencyMode()\",\"SigmaHook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"SigmaHook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"SigmaHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"SigmaHook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"SigmaHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SigmaHook.approve(address,uint256)\",\"SigmaHook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"SigmaHook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"SigmaHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"SigmaHook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"SigmaHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SigmaHook.deposit(uint256,uint256,uint256,address,uint256)\",\"SigmaHook.emergencyWithdraw()\",\"SigmaHook.emergencyWithdraw(uint256,address)\",\"SigmaHook.receive()\",\"SigmaHook.transfer(address,uint256)\",\"SigmaHook.transferFrom(address,address,uint256)\",\"SigmaHook.transferOwnership(address)\",\"SigmaHook.unlockCallback(bytes)\",\"SigmaHook.withdraw(uint256,uint256,uint256,address,uint256)\",\"SigmaToken.approve(address,uint256)\",\"SigmaToken.transfer(address,uint256)\",\"SigmaToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":3,\"LICENSE\":21,\"README.md\":164,\"dependencies.lock.json\":64,\"docs/SECURITY.md\":38,\"foundry.toml\":21,\"remappings.txt\":8,\"script/MineSalt.s.sol\":31,\"src/HookFlags.sol\":30,\"src/SigmaHook.sol\":552,\"src/SigmaToken.sol\":11,\"src/libraries/BandMath.sol\":75,\"src/libraries/VaultMath.sol\":122,\"test/Adversarial.t.sol\":230,\"test/BandMath.t.sol\":234,\"test/MathProperties.t.sol\":202,\"test/NativeVault.t.sol\":162,\"test/README.md\":69,\"test/SigmaHook.t.sol\":540,\"test/SigmaToken.t.sol\":51,\"test/VaultAccountingInvariant.t.sol\":334,\"test/VaultFailurePaths.t.sol\":267,\"test/VaultInvariant.t.sol\":168,\"test/WethVaultIntegration.t.sol\":194,\"test/helpers/Fixture.sol\":108,\"test/helpers/MockERC20.sol\":24,\"test/helpers/TestRouter.sol\":84},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"8aeb71ddec7ee3075064f2a35b9cc2a3a3bda6e8866f5404a86e330dde2bd933","verifiedTreeHash":"339eca339a114f8a8989bf0c243ea9a2fe1bde70","verifierVersion":"0.1.0+d372f99e"},{"checks":[{"durationMs":46743,"exitCode":0,"name":"build","output":"Compiling 102 files with Solc 0.8.26\nSolc 0.8.26 finished in 46.64s\nCompiler run successful!\n","passed":true},{"durationMs":716,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/SigmaToken.t.sol:SigmaTokenTest\n[PASS] testFuzz_transferAndAllowance(uint256) (runs: 256, μ: 179858, ~: 179790)\nLogs:\n  Bound result 123\n\n[PASS] test_fixedSupplyAndMetadata() (gas: 49353)\n[PASS] test_noAdminOrAdditionalMint() (gas: 127664)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 17.52ms (17.44ms CPU time)\n\nRan 11 tests for test/BandMath.t.sol:BandMathTest\n[PASS] testFuzz_liquiditySizingNeverOverspends(int24,int24,int24,uint128,uint128,uint128) (runs: 256, μ: 30598, ~: 30721)\nLogs:\n  Bound result -2\n  Bound result 64718\n  Bound result 484114\n\n[PASS] testFuzz_statisticsMatchIndependentIntegerReference(int24[8],uint16) (runs: 256, μ: 108579, ~: 108619)\nLogs:\n  Bound result 22949\n  Bound result 374\n  Bound result 200\n  Bound result 2026\n  Bound result -6\n  Bound result 6\n  Bound result 6\n  Bound result 0\n  Bound result 12697\n\n[PASS] test_extremeTicksStayWithinUsableGrid() (gas: 27474)\n[PASS] test_knownPopulationStatistics() (gas: 37940)\n[PASS] test_lowPriceSizingKeepsPrecision() (gas: 10369)\n[PASS] test_meanRoundsToNearestAndNegativeGridFloors() (gas: 21566)\n[PASS] test_rebalanceTradeTargetsBandRatioAndRespectsDeadband() (gas: 21249)\n[PASS] test_restingTickDoesNotDeviateButOutlierDoes() (gas: 26711)\n[PASS] test_sizingOneSqrtUnitFromUpperAndVeryLowTicks() (gas: 13646)\n[PASS] test_strictTriggerAndHalfIntegerThreshold() (gas: 12157)\n[PASS] test_tradeLimitStopsAtBandEdgeOrCap() (gas: 20709)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 22.52ms (28.58ms CPU time)\n\nRan 8 tests for test/Adversarial.t.sol:AdversarialTest\n[PASS] test_emergencyCannotBurnOtherHoldersShares() (gas: 795983)\n[PASS] test_emptyVaultCanEnterEmergency() (gas: 125282)\n[PASS] test_feeOnTransferDepositIsRejectedAtomically() (gas: 3081520)\n[PASS] test_initializationAtPriceExtremesAndSingleSidedDeposits() (gas: 604344748)\n[PASS] test_liquidityCapacityCannotBlockSwapRebalance() (gas: 1854078)\n[PASS] test_prepayRouterSwapSurvivesARebalance() (gas: 2530453)\n[PASS] test_saltToolUsesExactFactoryAndConstructorArguments() (gas: 7976873)\n[PASS] test_tokenCallbackCannotReenterDeposit() (gas: 3438008)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 146.30ms (145.32ms CPU time)\n\nRan 26 tests for test/SigmaHook.t.sol:SigmaHookTest\n[PASS] testFuzz_donationsDoNotAllowZeroShareDeposit(uint80) (runs: 256, μ: 943599, ~: 1075473)\nLogs:\n  Bound result 9103\n\n[PASS] testFuzz_roundTripNeverCreatesAssets(uint96,uint96) (runs: 256, μ: 805358, ~: 805019)\nLogs:\n  Bound result 1699644676\n  Bound result 99999999999999006761\n\n[PASS] test_balancingTradeStaysInsideBandAndCap() (gas: 1360069)\n[PASS] test_callbacksAndUnlockRefuseUnauthorizedCalls() (gas: 124171)\n[PASS] test_circularFeedUsesPostExecutionSlot0AndWraps() (gas: 2338463)\n[PASS] test_cooldownRequiresFullThirtyMinutesAndDeviation() (gas: 1728217)\n[PASS] test_deepPoolTrendRebalanceDeploysMostCapital() (gas: 1428636)\n[PASS] test_depositAndWithdrawalSlippageDeadlineAndZeroChecks() (gas: 1133963)\n[PASS] test_emergencyFreezesInvestmentButNotSwapsOrShareTransfers() (gas: 1316960)\n[PASS] test_emergencyRemovesAllLiquidityAndWithdrawsProRata() (gas: 1604162)\n[PASS] test_emptiedVaultDoesNotRebalanceUntilSomeoneDepositsAgain() (gas: 1937017)\n[PASS] test_feeOwnershipCannotBeDilutedByNewDepositor() (gas: 1492142)\n[PASS] test_firstDepositAfterPriceMoveUsesFreshBand() (gas: 1172076)\n[PASS] test_firstDepositAndProportionalSecondDeposit() (gas: 1027627)\n[PASS] test_firstDepositPullsOnlyWhatTheBandNeeds() (gas: 789717)\n[PASS] test_minedPermissionsInitializationAndExplicitOwner() (gas: 194538)\n[PASS] test_noBalancingTradeInAnEmptyPool() (gas: 902479)\n[PASS] test_noDeviationDoesNotRebalanceAfterCooldown() (gas: 903036)\n[PASS] test_otherLpCanCollectZeroDeltaFeesEvenInEmergency() (gas: 1563451)\n[PASS] test_ownerTransferRequiresAcceptanceAndCannotRenounce() (gas: 152681)\n[PASS] test_rebalanceBurnsEntireOldPositionAndKeepsAllCapital() (gas: 1470140)\n[PASS] test_rebalanceCompoundsAllAccruedFeesAndDust() (gas: 1315891)\n[PASS] test_rejectsSecondPoolAndWrongCallbackPool() (gas: 127264)\n[PASS] test_strayEthIsRefusedOnAnErc20Pair() (gas: 46978)\n[PASS] test_trendRebalanceSellsSurplusAndRedeploys() (gas: 8448602)\n[PASS] test_uninitializedFunctionsAndFactoryGate() (gas: 122412771)\nSuite result: ok. 26 passed; 0 failed; 0 skipped; finished in 146.30ms (210.41ms CPU time)\n\nRan 7 tests for test/NativeVault.t.sol:NativeVaultTest\n[PASS] testFuzz_freshManagerRebalanceBeforeInputSettlement(bool) (runs: 256, μ: 2331235, ~: 2343612)\n[PASS] test_emergencyRedeemsClaimsAndNeedsNoManagerForWithdrawal() (gas: 1474799)\n[PASS] test_exactInputAndOutputBothDirectionsRemainUsable() (gas: 1301240)\n[PASS] test_nativeDepositRefundAndOrdinaryExit() (gas: 922434)\n[PASS] test_onlyTheManagerMaySendEthToTheHook() (gas: 76608)\n[PASS] test_reentrantEmergencyReceiverCannotRedeemTwice() (gas: 1050304)\n[PASS] test_rejectingEthRecipientRollsBackAndCanChooseAnotherReceiver() (gas: 986821)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 146.35ms (138.42ms CPU time)\n\nRan 1 test for test/VaultInvariant.t.sol:VaultInvariantTest\n[PASS]\nVaultInvariantTest invariants:\n[PASS] invariant_currencyAndShareConservation\n[PASS] invariant_positionAndFlashAccounting\n VaultInvariantTest invariants (runs: 64, calls: 2048, reverts: 0)\n\n╭--------------+----------------+-------+---------+----------╮\n| Contract     | Selector       | Calls | Reverts | Discards |\n+============================================================+\n| VaultHandler | advanceTime    | 287   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | deposit        | 311   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | donate         | 302   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | emergency      | 280   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | trade          | 284   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | transferShares | 297   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | withdraw       | 287   | 0       | 0        |\n╰--------------+----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 0\n  Bound result 2690665609385277037475\n  Bound result 15505\n  Bound result 1983\n  Bound result 1500\n  Bound result 1752933232\n  Bound result 999999999999017706\n  Bound result 999999999999000015\n  Bound result 29\n  Bound result 100000000000000000\n  Bound result 999999999999200001\n  Bound result 3480\n  Bound result 3480\n  Bound result 3000000000000000000\n  Bound result 735889\n  Bound result 6\n  Bound result 12\n  Bound result 1799\n  Bound result 28\n  Bound result 600\n  Bound result 2048\n  Bound result 9999990\n  Bound result 75115317414630971\n  Bound result 20936\n  Bound result 7752\n  Bound result 1223\n  Bound result 720\n  Bound result 410074388161945202\n  Bound result 999999999999000006\n  Bound result 3797\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 601.39ms (583.93ms CPU time)\n\nRan 6 test suites in 602.80ms (1.08s CPU time): 56 tests passed, 0 failed, 0 skipped (56 total tests)\n","passed":true},{"durationMs":53,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SigmaHook.acceptOwnership()\",\"SigmaHook.activateEmergencyMode()\",\"SigmaHook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"SigmaHook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"SigmaHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"SigmaHook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"SigmaHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SigmaHook.approve(address,uint256)\",\"SigmaHook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"SigmaHook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"SigmaHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"SigmaHook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"SigmaHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SigmaHook.deposit(uint256,uint256,uint256,address,uint256)\",\"SigmaHook.emergencyWithdraw()\",\"SigmaHook.emergencyWithdraw(uint256,address)\",\"SigmaHook.receive()\",\"SigmaHook.transfer(address,uint256)\",\"SigmaHook.transferFrom(address,address,uint256)\",\"SigmaHook.transferOwnership(address)\",\"SigmaHook.unlockCallback(bytes)\",\"SigmaHook.withdraw(uint256,uint256,uint256,address,uint256)\",\"SigmaToken.approve(address,uint256)\",\"SigmaToken.transfer(address,uint256)\",\"SigmaToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":3,\"LICENSE\":21,\"README.md\":164,\"dependencies.lock.json\":64,\"docs/SECURITY.md\":38,\"foundry.toml\":21,\"remappings.txt\":8,\"script/MineSalt.s.sol\":31,\"src/HookFlags.sol\":30,\"src/SigmaHook.sol\":552,\"src/SigmaToken.sol\":11,\"src/libraries/BandMath.sol\":75,\"src/libraries/VaultMath.sol\":122,\"test/Adversarial.t.sol\":230,\"test/BandMath.t.sol\":234,\"test/NativeVault.t.sol\":162,\"test/SigmaHook.t.sol\":540,\"test/SigmaToken.t.sol\":51,\"test/VaultInvariant.t.sol\":168,\"test/helpers/Fixture.sol\":108,\"test/helpers/MockERC20.sol\":24,\"test/helpers/TestRouter.sol\":84},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":6764,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-eth at src/SigmaHook.sol:259: Reentrancy in SigmaHook._afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/SigmaHook.sol#259-295):\n[high/medium] reentrancy-eth at src/SigmaHook.sol:259: Reentrancy in SigmaHook._afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/SigmaHook.sol#259-295):\n[high/medium] reentrancy-eth at src/SigmaHook.sol:297: Reentrancy in SigmaHook.unlockCallback(bytes) (src/SigmaHook.sol#297-323):\n[medium/medium] unused-return at src/SigmaHook.sol:259: SigmaHook._afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/SigmaHook.sol#259-295) ignores return value by (None,tick,None,None) = poolManager.getSlot0(poolId) (src/SigmaHook.sol#267)\n[medium/medium] unused-return at src/SigmaHook.sol:395: SigmaHook._capacity() (src/SigmaHook.sol#395-403) ignores return value by (grossLower,None) = poolManager.getTickLiquidity(poolId,tickLower) (src/SigmaHook.sol#399)\n[medium/medium] unused-return at src/SigmaHook.sol:443: SigmaHook._assets(bool) (src/SigmaHook.sol#443-466) ignores return value by (None,last0,last1) = poolManager.getPositionInfo(poolId,address(this),tickLower,tickUpper,POSITION_SALT) (src/SigmaHook.sol#455-456)\n[medium/medium] unused-return at src/SigmaHook.sol:348: SigmaHook._invest(bool) (src/SigmaHook.sol#348-359) ignores return value by (price,None,None,None) = poolManager.getSlot0(poolId) (src/SigmaHook.sol#349)\n[medium/medium] unused-return at src/SigmaHook.sol:365: SigmaHook._balanceInventory(bool) (src/SigmaHook.sol#365-385) ignores return value by (price,tick,None,None) = poolManager.getSlot0(poolId) (src/SigmaHook.sol#368)\n[medium/medium] unused-return at src/SigmaHook.sol:395: SigmaHook._capacity() (src/SigmaHook.sol#395-403) ignores return value by (grossUpper,None) = poolManager.getTickLiquidity(poolId,tickUpper) (src/SigmaHook.sol#400)\n[medium/medium] unused-return at src/SigmaHook.sol:443: SigmaHook._assets(bool) (src/SigmaHook.sol#443-466) ignores return value by (price,None,None,None) = poolManager.getSlot0(poolId) (src/SigmaHook.sol#447)\n[medium/medium] unused-return at src/SigmaHook.sol:468: SigmaHook._quoteDeposit(uint256,uint256,uint256) (src/SigmaHook.sol#468-501) ignores return value by (price,None,None,None) = poolManager.getSlot0(poolId) (src/SigmaHook.sol#477)\n[low/medium] reentrancy-benign at src/SigmaHook.sol:152: Reentrancy in SigmaHook.deposit(uint256,uint256,uint256,address,uint256) (src/SigmaHook.sol#152-185):\n[low/medium] reentrancy-events at src/SigmaHook.sol:365: Reentrancy in SigmaHook._balanceInventory(bool) (src/SigmaHook.sol#365-385):\n[low/medium] reentrancy-events at src/SigmaHook.sol:334: Reentrancy in SigmaHook._changeLiquidity(int256,bool) (src/SigmaHook.sol#334-346):\n[low/medium] reentrancy-events at src/SigmaHook.sol:297: Reentrancy in SigmaHook.unlockCallback(bytes) (src/SigmaHook.sol#297-323):\n[low/medium] timestamp at src/SigmaHook.sol:188: SigmaHook.withdraw(uint256,uint256,uint256,address,uint256) (src/SigmaHook.sol#188-203) uses timestamp for comparisons\n[low/medium] timestamp at src/SigmaHook.sol:152: SigmaHook.deposit(uint256,uint256,uint256,address,uint256) (src/SigmaHook.sol#152-185) uses timestamp for comparisons\n[low/medium] timestamp at src/SigmaHook.sol:259: SigmaHook._afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/SigmaHook.sol#259-295) uses timestamp for comparisons","passed":true},{"durationMs":774,"exitCode":0,"name":"aderyn","output":"[high] contract-locks-ether at src/SigmaHook.sol:30: Contract locks Ether without a withdraw function\n[low] centralization-risk at src/SigmaHook.sol:30: Centralization Risk (3 places)\n[low] costly-loop at src/SigmaHook.sol:250: Costly operations inside loop\n[low] internal-function-used-once at src/HookFlags.sol:23: Internal Function Used Only Once (7 places)\n[low] large-numeric-literal at src/SigmaToken.sol:9: Large Numeric Literal (2 places)\n[low] literal-instead-of-constant at src/SigmaHook.sol:250: Literal Instead of Constant (12 places)\n[low] non-reentrant-not-first at src/SigmaHook.sol:207: `nonReentrant` is Not the First Modifier\n[low] unchecked-return at src/SigmaHook.sol:166: Unchecked Return (3 places)\n[low] uninitialized-local-variable at src/libraries/BandMath.sol:14: Uninitialized Local Variable (2 places)\n[low] unsafe-erc20-operation at src/SigmaHook.sol:181: Unsafe ERC20 Operation (4 places)\n[low] unused-public-function at src/SigmaHook.sol:114: Public Function Not Used Internally","passed":true},{"durationMs":11626,"exitCode":0,"name":"proof fe9d1416abbd","output":"Compiling 96 files with Solc 0.8.26\nSolc 0.8.26 finished in 10.91s\nCompiler run successful!\n\nRan 1 test for test/imd-proof-836c9dff/Proof_fe9d1416abbd.t.sol:FirstDepositTest\n[PASS] test_firstDepositPullsUnusedBudgetIntoDeadShares() (gas: 716668)\nLogs:\n  shares: 2338\n  pulled0: 10\n  pulled1: 10\n  lost: 6\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 12.10ms (637.90µs CPU time)\n\nRan 1 test suite in 12.83ms (12.10ms 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":"8cc5bac4b2deed98289874453ef38d430cf34a439d264de400380a418370ff54","verifiedTreeHash":"f4934fb6d8d131248d1312a1a5f0f8a758cd9128","verifierVersion":"0.1.0+d372f99e"},{"checks":[{"durationMs":42667,"exitCode":0,"name":"build","output":"Compiling 102 files with Solc 0.8.26\nSolc 0.8.26 finished in 42.57s\nCompiler run successful!\n","passed":true},{"durationMs":695,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/SigmaToken.t.sol:SigmaTokenTest\n[PASS] testFuzz_transferAndAllowance(uint256) (runs: 256, μ: 179858, ~: 179790)\nLogs:\n  Bound result 36240965710846354202979222\n\n[PASS] test_fixedSupplyAndMetadata() (gas: 49353)\n[PASS] test_noAdminOrAdditionalMint() (gas: 127664)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 17.04ms (16.95ms CPU time)\n\nRan 26 tests for test/SigmaHook.t.sol:SigmaHookTest\n[PASS] testFuzz_donationsDoNotAllowZeroShareDeposit(uint80) (runs: 256, μ: 943599, ~: 1075473)\nLogs:\n  Bound result 69314342185084808086\n\n[PASS] testFuzz_roundTripNeverCreatesAssets(uint96,uint96) (runs: 256, μ: 805358, ~: 805019)\nLogs:\n  Bound result 99999999999999001169\n  Bound result 99999999999999022147\n\n[PASS] test_balancingTradeStaysInsideBandAndCap() (gas: 1360069)\n[PASS] test_callbacksAndUnlockRefuseUnauthorizedCalls() (gas: 124171)\n[PASS] test_circularFeedUsesPostExecutionSlot0AndWraps() (gas: 2338463)\n[PASS] test_cooldownRequiresFullThirtyMinutesAndDeviation() (gas: 1728217)\n[PASS] test_deepPoolTrendRebalanceDeploysMostCapital() (gas: 1428636)\n[PASS] test_depositAndWithdrawalSlippageDeadlineAndZeroChecks() (gas: 1133963)\n[PASS] test_emergencyFreezesInvestmentButNotSwapsOrShareTransfers() (gas: 1316960)\n[PASS] test_emergencyRemovesAllLiquidityAndWithdrawsProRata() (gas: 1604162)\n[PASS] test_emptiedVaultDoesNotRebalanceUntilSomeoneDepositsAgain() (gas: 1937017)\n[PASS] test_feeOwnershipCannotBeDilutedByNewDepositor() (gas: 1492142)\n[PASS] test_firstDepositAfterPriceMoveUsesFreshBand() (gas: 1172076)\n[PASS] test_firstDepositAndProportionalSecondDeposit() (gas: 1027627)\n[PASS] test_firstDepositPullsOnlyWhatTheBandNeeds() (gas: 789717)\n[PASS] test_minedPermissionsInitializationAndExplicitOwner() (gas: 194538)\n[PASS] test_noBalancingTradeInAnEmptyPool() (gas: 902479)\n[PASS] test_noDeviationDoesNotRebalanceAfterCooldown() (gas: 903036)\n[PASS] test_otherLpCanCollectZeroDeltaFeesEvenInEmergency() (gas: 1563451)\n[PASS] test_ownerTransferRequiresAcceptanceAndCannotRenounce() (gas: 152681)\n[PASS] test_rebalanceBurnsEntireOldPositionAndKeepsAllCapital() (gas: 1470140)\n[PASS] test_rebalanceCompoundsAllAccruedFeesAndDust() (gas: 1315891)\n[PASS] test_rejectsSecondPoolAndWrongCallbackPool() (gas: 127264)\n[PASS] test_strayEthIsRefusedOnAnErc20Pair() (gas: 46978)\n[PASS] test_trendRebalanceSellsSurplusAndRedeploys() (gas: 8448602)\n[PASS] test_uninitializedFunctionsAndFactoryGate() (gas: 122412771)\nSuite result: ok. 26 passed; 0 failed; 0 skipped; finished in 75.35ms (210.07ms CPU time)\n\nRan 8 tests for test/Adversarial.t.sol:AdversarialTest\n[PASS] test_emergencyCannotBurnOtherHoldersShares() (gas: 795983)\n[PASS] test_emptyVaultCanEnterEmergency() (gas: 125282)\n[PASS] test_feeOnTransferDepositIsRejectedAtomically() (gas: 3081520)\n[PASS] test_initializationAtPriceExtremesAndSingleSidedDeposits() (gas: 604344748)\n[PASS] test_liquidityCapacityCannotBlockSwapRebalance() (gas: 1854078)\n[PASS] test_prepayRouterSwapSurvivesARebalance() (gas: 2530453)\n[PASS] test_saltToolUsesExactFactoryAndConstructorArguments() (gas: 7976873)\n[PASS] test_tokenCallbackCannotReenterDeposit() (gas: 3438008)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 139.26ms (152.67ms CPU time)\n\nRan 11 tests for test/BandMath.t.sol:BandMathTest\n[PASS] testFuzz_liquiditySizingNeverOverspends(int24,int24,int24,uint128,uint128,uint128) (runs: 256, μ: 30598, ~: 30721)\nLogs:\n  Bound result -754\n  Bound result -3\n  Bound result 578656\n\n[PASS] testFuzz_statisticsMatchIndependentIntegerReference(int24[8],uint16) (runs: 256, μ: 108579, ~: 108619)\nLogs:\n  Bound result -887272\n  Bound result 6\n  Bound result 1500\n  Bound result 0\n  Bound result -4\n  Bound result 200\n  Bound result 9620\n  Bound result 3625\n  Bound result 14252\n\n[PASS] test_extremeTicksStayWithinUsableGrid() (gas: 27474)\n[PASS] test_knownPopulationStatistics() (gas: 37940)\n[PASS] test_lowPriceSizingKeepsPrecision() (gas: 10369)\n[PASS] test_meanRoundsToNearestAndNegativeGridFloors() (gas: 21566)\n[PASS] test_rebalanceTradeTargetsBandRatioAndRespectsDeadband() (gas: 21249)\n[PASS] test_restingTickDoesNotDeviateButOutlierDoes() (gas: 26711)\n[PASS] test_sizingOneSqrtUnitFromUpperAndVeryLowTicks() (gas: 13646)\n[PASS] test_strictTriggerAndHalfIntegerThreshold() (gas: 12157)\n[PASS] test_tradeLimitStopsAtBandEdgeOrCap() (gas: 20709)\nSuite result: ok. 11 passed; 0 failed; 0 skipped; finished in 139.25ms (145.18ms CPU time)\n\nRan 7 tests for test/NativeVault.t.sol:NativeVaultTest\n[PASS] testFuzz_freshManagerRebalanceBeforeInputSettlement(bool) (runs: 256, μ: 2331235, ~: 2343612)\n[PASS] test_emergencyRedeemsClaimsAndNeedsNoManagerForWithdrawal() (gas: 1474799)\n[PASS] test_exactInputAndOutputBothDirectionsRemainUsable() (gas: 1301240)\n[PASS] test_nativeDepositRefundAndOrdinaryExit() (gas: 922434)\n[PASS] test_onlyTheManagerMaySendEthToTheHook() (gas: 76608)\n[PASS] test_reentrantEmergencyReceiverCannotRedeemTwice() (gas: 1050304)\n[PASS] test_rejectingEthRecipientRollsBackAndCanChooseAnotherReceiver() (gas: 986821)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 143.41ms (130.53ms CPU time)\n\nRan 1 test for test/VaultInvariant.t.sol:VaultInvariantTest\n[PASS]\nVaultInvariantTest invariants:\n[PASS] invariant_currencyAndShareConservation\n[PASS] invariant_positionAndFlashAccounting\n VaultInvariantTest invariants (runs: 64, calls: 2048, reverts: 0)\n\n╭--------------+----------------+-------+---------+----------╮\n| Contract     | Selector       | Calls | Reverts | Discards |\n+============================================================+\n| VaultHandler | advanceTime    | 287   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | deposit        | 311   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | donate         | 302   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | emergency      | 280   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | trade          | 284   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | transferShares | 297   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | withdraw       | 287   | 0       | 0        |\n╰--------------+----------------+-------+---------+----------╯\n\nLogs:\n  Bound result 0\n  Bound result 2690665609385277037475\n  Bound result 15505\n  Bound result 1983\n  Bound result 1500\n  Bound result 1752933232\n  Bound result 999999999999017706\n  Bound result 999999999999000015\n  Bound result 29\n  Bound result 100000000000000000\n  Bound result 999999999999200001\n  Bound result 3480\n  Bound result 3480\n  Bound result 3000000000000000000\n  Bound result 735889\n  Bound result 6\n  Bound result 12\n  Bound result 1799\n  Bound result 28\n  Bound result 600\n  Bound result 2048\n  Bound result 9999990\n  Bound result 75115317414630971\n  Bound result 20936\n  Bound result 7752\n  Bound result 1223\n  Bound result 720\n  Bound result 410074388161945202\n  Bound result 999999999999000006\n  Bound result 3797\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 582.62ms (570.84ms CPU time)\n\nRan 6 test suites in 584.22ms (1.10s CPU time): 56 tests passed, 0 failed, 0 skipped (56 total tests)\n","passed":true},{"durationMs":47,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SigmaHook.acceptOwnership()\",\"SigmaHook.activateEmergencyMode()\",\"SigmaHook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"SigmaHook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"SigmaHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"SigmaHook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"SigmaHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SigmaHook.approve(address,uint256)\",\"SigmaHook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"SigmaHook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"SigmaHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"SigmaHook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"SigmaHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SigmaHook.deposit(uint256,uint256,uint256,address,uint256)\",\"SigmaHook.emergencyWithdraw()\",\"SigmaHook.emergencyWithdraw(uint256,address)\",\"SigmaHook.receive()\",\"SigmaHook.transfer(address,uint256)\",\"SigmaHook.transferFrom(address,address,uint256)\",\"SigmaHook.transferOwnership(address)\",\"SigmaHook.unlockCallback(bytes)\",\"SigmaHook.withdraw(uint256,uint256,uint256,address,uint256)\",\"SigmaToken.approve(address,uint256)\",\"SigmaToken.transfer(address,uint256)\",\"SigmaToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":3,\"LICENSE\":21,\"README.md\":164,\"dependencies.lock.json\":64,\"docs/SECURITY.md\":38,\"foundry.toml\":21,\"launch.json\":29,\"remappings.txt\":8,\"script/MineSalt.s.sol\":31,\"src/HookFlags.sol\":30,\"src/SigmaHook.sol\":552,\"src/SigmaToken.sol\":11,\"src/libraries/BandMath.sol\":75,\"src/libraries/VaultMath.sol\":122,\"test/Adversarial.t.sol\":230,\"test/BandMath.t.sol\":234,\"test/NativeVault.t.sol\":162,\"test/SigmaHook.t.sol\":540,\"test/SigmaToken.t.sol\":51,\"test/VaultInvariant.t.sol\":168,\"test/helpers/Fixture.sol\":108,\"test/helpers/MockERC20.sol\":24,\"test/helpers/TestRouter.sol\":84},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"debe393346a581da072023483bf4fd37ed0160db30ccf8ffb1e07d8c8adeb9b3","verifiedTreeHash":"a3c87d82ab912dc9778d30e0a9b24e0c1518aae9","verifierVersion":"0.1.0+d372f99e"},{"checks":[{"durationMs":36547,"exitCode":0,"name":"build","output":"Compiling 102 files with Solc 0.8.26\nSolc 0.8.26 finished in 36.44s\nCompiler run successful!\n","passed":true},{"durationMs":686,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 7 tests for test/BandMath.t.sol:BandMathTest\n[PASS] testFuzz_liquiditySizingNeverOverspends(int24,int24,int24,uint128,uint128,uint128) (runs: 256, μ: 30725, ~: 30786)\nLogs:\n  Bound result -887272\n  Bound result 12546\n  Bound result 11565\n\n[PASS] testFuzz_statisticsMatchIndependentIntegerReference(int24[8],uint16) (runs: 256, μ: 85462, ~: 85426)\nLogs:\n  Bound result 16493\n  Bound result 2167\n  Bound result -887272\n  Bound result -573443\n  Bound result 0\n  Bound result 0\n  Bound result 0\n  Bound result -4\n  Bound result 7403\n\n[PASS] test_extremeTicksStayWithinUsableGrid() (gas: 17203)\n[PASS] test_knownPopulationStatistics() (gas: 19812)\n[PASS] test_negativeMeanTruncatesTowardsZero() (gas: 11645)\n[PASS] test_sizingOneSqrtUnitFromUpperAndVeryLowTicks() (gas: 13762)\n[PASS] test_strictTriggerAndHalfIntegerThreshold() (gas: 9721)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 10.66ms (16.92ms CPU time)\n\nRan 3 tests for test/SigmaToken.t.sol:SigmaTokenTest\n[PASS] testFuzz_transferAndAllowance(uint256) (runs: 256, μ: 179831, ~: 179807)\nLogs:\n  Bound result 710\n\n[PASS] test_fixedSupplyAndMetadata() (gas: 49353)\n[PASS] test_noAdminOrAdditionalMint() (gas: 127664)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 91.18ms (90.96ms CPU time)\n\nRan 6 tests for test/NativeVault.t.sol:NativeVaultTest\n[PASS] testFuzz_freshManagerRebalanceBeforeInputSettlement(bool) (runs: 256, μ: 1402075, ~: 1402433)\n[PASS] test_emergencyRedeemsClaimsAndNeedsNoManagerForWithdrawal() (gas: 1156664)\n[PASS] test_exactInputAndOutputBothDirectionsRemainUsable() (gas: 1281530)\n[PASS] test_nativeDepositRefundAndOrdinaryExit() (gas: 906622)\n[PASS] test_reentrantEmergencyReceiverCannotRedeemTwice() (gas: 1034755)\n[PASS] test_rejectingEthRecipientRollsBackAndCanChooseAnotherReceiver() (gas: 971202)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 91.23ms (83.23ms CPU time)\n\nRan 7 tests for test/Adversarial.t.sol:AdversarialTest\n[PASS] test_emergencyCannotBurnOtherHoldersShares() (gas: 780753)\n[PASS] test_emptyVaultCanEnterEmergency() (gas: 125116)\n[PASS] test_feeOnTransferDepositIsRejectedAtomically() (gas: 18845279)\n[PASS] test_initializationAtPriceExtremesAndSingleSidedDeposits() (gas: 203916629)\n[PASS] test_liquidityCapacityCannotBlockSwapRebalance() (gas: 1753613)\n[PASS] test_saltToolUsesExactFactoryAndConstructorArguments() (gas: 7338983)\n[PASS] test_tokenCallbackCannotReenterDeposit() (gas: 19201357)\nSuite result: ok. 7 passed; 0 failed; 0 skipped; finished in 104.25ms (127.70ms CPU time)\n\nRan 18 tests for test/SigmaHook.t.sol:SigmaHookTest\n[PASS] testFuzz_donationsDoNotAllowZeroShareDeposit(uint80) (runs: 256, μ: 933135, ~: 1059929)\nLogs:\n  Bound result 1068823178\n\n[PASS] testFuzz_roundTripNeverCreatesAssets(uint96,uint96) (runs: 256, μ: 794512, ~: 796876)\nLogs:\n  Bound result 2244002891044233572\n  Bound result 2364829\n\n[PASS] test_callbacksAndUnlockRefuseUnauthorizedCalls() (gas: 123901)\n[PASS] test_circularFeedUsesPostExecutionSlot0AndWraps() (gas: 2326686)\n[PASS] test_cooldownRequiresFullThirtyMinutesAndDeviation() (gas: 1490084)\n[PASS] test_depositAndWithdrawalSlippageDeadlineAndZeroChecks() (gas: 1091408)\n[PASS] test_emergencyFreezesInvestmentButNotSwapsOrShareTransfers() (gas: 1300490)\n[PASS] test_emergencyRemovesAllLiquidityAndWithdrawsProRata() (gas: 1588101)\n[PASS] test_feeOwnershipCannotBeDilutedByNewDepositor() (gas: 1476555)\n[PASS] test_firstDepositAndProportionalSecondDeposit() (gas: 1011828)\n[PASS] test_minedPermissionsInitializationAndExplicitOwner() (gas: 192731)\n[PASS] test_noDeviationDoesNotRebalanceAfterCooldown() (gas: 886764)\n[PASS] test_otherLpCanCollectZeroDeltaFeesEvenInEmergency() (gas: 1546160)\n[PASS] test_ownerTransferRequiresAcceptanceAndCannotRenounce() (gas: 152285)\n[PASS] test_rebalanceBurnsEntireOldPositionAndKeepsAllCapital() (gas: 1235575)\n[PASS] test_rebalanceCompoundsAllAccruedFeesAndDust() (gas: 1296177)\n[PASS] test_rejectsSecondPoolAndWrongCallbackPool() (gas: 127060)\n[PASS] test_uninitializedFunctionsAndFactoryGate() (gas: 30980965)\nSuite result: ok. 18 passed; 0 failed; 0 skipped; finished in 104.25ms (160.51ms CPU time)\n\nRan 1 test for test/VaultInvariant.t.sol:VaultInvariantTest\n[PASS]\nVaultInvariantTest invariants:\n[PASS] invariant_currencyAndShareConservation\n[PASS] invariant_positionAndFlashAccounting\n VaultInvariantTest invariants (runs: 64, calls: 2048, reverts: 0)\n\n╭--------------+----------------+-------+---------+----------╮\n| Contract     | Selector       | Calls | Reverts | Discards |\n+============================================================+\n| VaultHandler | advanceTime    | 287   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | deposit        | 311   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | donate         | 302   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | emergency      | 280   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | trade          | 284   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | transferShares | 297   | 0       | 0        |\n|--------------+----------------+-------+---------+----------|\n| VaultHandler | withdraw       | 287   | 0       | 0        |\n╰--------------+----------------+-------+---------+----------╯\n\nLogs:\n  Bound result -4\n  Bound result 1940889691275172922302\n  Bound result 8691\n  Bound result 6\n  Bound result 100000000000000000\n  Bound result 4553\n  Bound result 999999999999012168\n  Bound result 999999999999000017\n  Bound result 24\n  Bound result 999999999999000031\n  Bound result 999999999999000005\n  Bound result 120\n  Bound result 1560\n  Bound result 6000000000000000000\n  Bound result 735889\n  Bound result 6\n  Bound result 4096\n  Bound result 2125\n  Bound result 25\n  Bound result 200000\n  Bound result 4096\n  Bound result 999999999999016385\n  Bound result 999999999999018074\n  Bound result 17700\n  Bound result 284\n  Bound result 2070\n  Bound result 1572524882665486112158\n  Bound result 999999999999012717\n  Bound result 999999999999000065\n  Bound result 7352\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 575.93ms (552.91ms CPU time)\n\nRan 6 test suites in 577.36ms (977.49ms CPU time): 42 tests passed, 0 failed, 0 skipped (42 total tests)\n","passed":true},{"durationMs":61,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"SigmaHook.acceptOwnership()\",\"SigmaHook.activateEmergencyMode()\",\"SigmaHook.afterAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"SigmaHook.afterDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"SigmaHook.afterInitialize(address,(address,address,uint24,int24,address),uint160,int24)\",\"SigmaHook.afterRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),int256,int256,bytes)\",\"SigmaHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SigmaHook.approve(address,uint256)\",\"SigmaHook.beforeAddLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"SigmaHook.beforeDonate(address,(address,address,uint24,int24,address),uint256,uint256,bytes)\",\"SigmaHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"SigmaHook.beforeRemoveLiquidity(address,(address,address,uint24,int24,address),(int24,int24,int256,bytes32),bytes)\",\"SigmaHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SigmaHook.deposit(uint256,uint256,uint256,address,uint256)\",\"SigmaHook.emergencyWithdraw()\",\"SigmaHook.emergencyWithdraw(uint256,address)\",\"SigmaHook.receive()\",\"SigmaHook.transfer(address,uint256)\",\"SigmaHook.transferFrom(address,address,uint256)\",\"SigmaHook.transferOwnership(address)\",\"SigmaHook.unlockCallback(bytes)\",\"SigmaHook.withdraw(uint256,uint256,uint256,address,uint256)\",\"SigmaToken.approve(address,uint256)\",\"SigmaToken.transfer(address,uint256)\",\"SigmaToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":3,\"LICENSE\":21,\"README.md\":148,\"dependencies.lock.json\":64,\"docs/SECURITY.md\":38,\"foundry.toml\":21,\"remappings.txt\":8,\"script/MineSalt.s.sol\":31,\"src/HookFlags.sol\":30,\"src/SigmaHook.sol\":475,\"src/SigmaToken.sol\":11,\"src/libraries/BandMath.sol\":57,\"src/libraries/VaultMath.sol\":54,\"test/Adversarial.t.sol\":168,\"test/BandMath.t.sol\":125,\"test/NativeVault.t.sol\":142,\"test/SigmaHook.t.sol\":384,\"test/SigmaToken.t.sol\":51,\"test/VaultInvariant.t.sol\":168,\"test/helpers/Fixture.sol\":108,\"test/helpers/MockERC20.sol\":24,\"test/helpers/TestRouter.sol\":84},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":5183,"exitCode":0,"name":"slither","output":"[high/medium] reentrancy-eth at src/SigmaHook.sol:235: Reentrancy in SigmaHook._afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/SigmaHook.sol#235-262):\n[high/medium] reentrancy-eth at src/SigmaHook.sol:264: Reentrancy in SigmaHook.unlockCallback(bytes) (src/SigmaHook.sol#264-290):\n[high/medium] reentrancy-eth at src/SigmaHook.sol:235: Reentrancy in SigmaHook._afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/SigmaHook.sol#235-262):\n[medium/medium] divide-before-multiply at src/libraries/BandMath.sol:52: BandMath.floor(int256,int24) (src/libraries/BandMath.sol#52-56) performs a multiplication on the result of a division:\n[medium/medium] unused-return at src/SigmaHook.sol:329: SigmaHook._capacity() (src/SigmaHook.sol#329-337) ignores return value by (grossLower,None) = poolManager.getTickLiquidity(poolId,tickLower) (src/SigmaHook.sol#333)\n[medium/medium] unused-return at src/SigmaHook.sol:315: SigmaHook._invest() (src/SigmaHook.sol#315-326) ignores return value by (price,None,None,None) = poolManager.getSlot0(poolId) (src/SigmaHook.sol#316)\n[medium/medium] unused-return at src/SigmaHook.sol:401: SigmaHook._quoteDeposit(uint256,uint256,uint256) (src/SigmaHook.sol#401-430) ignores return value by (price,None,None,None) = poolManager.getSlot0(poolId) (src/SigmaHook.sol#410)\n[medium/medium] unused-return at src/SigmaHook.sol:329: SigmaHook._capacity() (src/SigmaHook.sol#329-337) ignores return value by (grossUpper,None) = poolManager.getTickLiquidity(poolId,tickUpper) (src/SigmaHook.sol#334)\n[medium/medium] unused-return at src/SigmaHook.sol:376: SigmaHook._assets(bool) (src/SigmaHook.sol#376-399) ignores return value by (price,None,None,None) = poolManager.getSlot0(poolId) (src/SigmaHook.sol#380)\n[medium/medium] unused-return at src/SigmaHook.sol:235: SigmaHook._afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/SigmaHook.sol#235-262) ignores return value by (None,tick,None,None) = poolManager.getSlot0(poolId) (src/SigmaHook.sol#243)\n[medium/medium] unused-return at src/SigmaHook.sol:376: SigmaHook._assets(bool) (src/SigmaHook.sol#376-399) ignores return value by (None,last0,last1) = poolManager.getPositionInfo(poolId,address(this),tickLower,tickUpper,POSITION_SALT) (src/SigmaHook.sol#388-389)\n[low/medium] reentrancy-benign at src/SigmaHook.sol:134: Reentrancy in SigmaHook.deposit(uint256,uint256,uint256,address,uint256) (src/SigmaHook.sol#134-161):\n[low/medium] reentrancy-events at src/SigmaHook.sol:301: Reentrancy in SigmaHook._changeLiquidity(int256) (src/SigmaHook.sol#301-313):\n[low/medium] reentrancy-events at src/SigmaHook.sol:264: Reentrancy in SigmaHook.unlockCallback(bytes) (src/SigmaHook.sol#264-290):\n[low/medium] timestamp at src/SigmaHook.sol:164: SigmaHook.withdraw(uint256,uint256,uint256,address,uint256) (src/SigmaHook.sol#164-179) uses timestamp for comparisons\n[low/medium] timestamp at src/SigmaHook.sol:134: SigmaHook.deposit(uint256,uint256,uint256,address,uint256) (src/SigmaHook.sol#134-161) uses timestamp for comparisons\n[low/medium] timestamp at src/SigmaHook.sol:235: SigmaHook._afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/SigmaHook.sol#235-262) uses timestamp for comparisons","passed":true},{"durationMs":621,"exitCode":0,"name":"aderyn","output":"[high] contract-locks-ether at src/SigmaHook.sol:29: Contract locks Ether without a withdraw function\n[low] centralization-risk at src/SigmaHook.sol:29: Centralization Risk (3 places)\n[low] costly-loop at src/SigmaHook.sol:226: Costly operations inside loop\n[low] internal-function-used-once at src/HookFlags.sol:23: Internal Function Used Only Once (2 places)\n[low] large-numeric-literal at src/SigmaToken.sol:9: Large Numeric Literal\n[low] literal-instead-of-constant at src/SigmaHook.sol:226: Literal Instead of Constant (11 places)\n[low] non-reentrant-not-first at src/SigmaHook.sol:183: `nonReentrant` is Not the First Modifier\n[low] unchecked-return at src/SigmaHook.sol:159: Unchecked Return (2 places)\n[low] uninitialized-local-variable at src/libraries/BandMath.sol:11: Uninitialized Local Variable (2 places)\n[low] unsafe-erc20-operation at src/SigmaHook.sol:157: Unsafe ERC20 Operation (4 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"e299a4bd4b6fee7c36c5493f20f546205564d80bbc522e4c7cdce554f6d68099","verifiedTreeHash":"79e5ded63cf18d14789c713ef715076c92be7c91","verifierVersion":"0.1.0+d372f99e"}]}