{"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":"0361d0fa-16da-4df6-a2c6-722dfab5e920","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"d34cf6a0a3c1b12184ca7b6b0a2f5862f0cf77806cd58ca16934dbedc6b60336","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"47a7e785c5272a73091c3f5728602de1f124e40fba606f836d73ea7fbefc00bd","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"11b3a124c1e560bed782012bf2fc981d812a744fbf7517f8a96e246f0752d354","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"3b24ef8b5efe74a5cf66606e06da66002f2abb61ffb798bcbaf62e601a6085e2","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"01dafcdc56047dd3e06b5dab089f203b0221d9804b2bc906c819f322f7ef89ed","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"7fbc0051f9a6248e2027b8e5048d56ba559b7ee30f18cf99d9d7aa0d464b5e52","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"6b8d2c2ebd3301cff2be8edb1e1cc6ba65c858c02fe7247be81722b969842265","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"6b8d2c2ebd3301cff2be8edb1e1cc6ba65c858c02fe7247be81722b969842265","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"6a594d81e84c0c4704bf310a530955c50461f914ed7bf4df42d90b92bb2148fb","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"6f14e97a7323324387ee57ca82fc1717f199ee2244abd01eef0943cb0dc5ed7c","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"73431852439ad3a343f3d2b7db1c43cd363b9f48f9bb51497374a0cf6d50b223","skillId":"write-foundry-tests","state":"accepted"}],"objective":"Launch the rnnn.bid community token (token name: rnnn.bid, symbol: RNNN) on Robinhood Chain, paired with IMD on Uniswap v4, with a hook that has exactly one rule: on every swap, take 1% of the swap amount in IMD (the pool's IMD side) and transfer it, as IMD, to the fixed address 0x45c89d0cf0b8a05218506236fcfcb18da1d3ebe4. Do NOT swap or sell the fee for ETH or any other token; the recipient must receive IMD. The fee recipient is a constructor argument and can never change. The hook has no owner, no pause, no mint, no other fees, and does not block or alter swaps in any other way. Include Foundry tests: a buy and a sell each send exactly 1% of the IMD side to 0x45c89d0cf0b8a05218506236fcfcb18da1d3ebe4; the hook has no privileged functions.","parentJobId":null,"planHash":"8b2f43cf614cd01218d0e4e65f8d6ebf11b2d7adaaaf97b45ec88e9347a35b8f","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"0361d0fa-16da-4df6-a2c6-722dfab5e920","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1330-launch-rnnn-bid-community-token"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"52232","feedbackHash":"f8b82eef5d3ad8dc39562e5193a395d6bd079e58a62591ceb94e95e1425162bd","nodeKey":"audit_economics","submissionHash":"d34cf6a0a3c1b12184ca7b6b0a2f5862f0cf77806cd58ca16934dbedc6b60336","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51229","feedbackHash":"dca3dff69a0275a61fa3dd95174dc54f791ef0956d5cb846d4ce309097180a4f","nodeKey":"audit_flow","submissionHash":"47a7e785c5272a73091c3f5728602de1f124e40fba606f836d73ea7fbefc00bd","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52603","feedbackHash":"856f30b8b2dbeb3ad08f6bc6cb437456055daa5f7c005d692c7e96ba17fefc22","nodeKey":"audit_judge","submissionHash":"519dcc6655e053dae4a66a991b07766a78b6a5e7ac89450e6833daa575d5496e","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51352","feedbackHash":"0790d2abe4ea629244bac01a39ea1770b214a916f2085b340460984ade61faf0","nodeKey":"audit_judge","submissionHash":"11b3a124c1e560bed782012bf2fc981d812a744fbf7517f8a96e246f0752d354","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50961","feedbackHash":"23dec26720551b1acc90165754d5fe9472ac729cf5c010a45d7135f0ef4944a2","nodeKey":"audit_math","submissionHash":"3b24ef8b5efe74a5cf66606e06da66002f2abb61ffb798bcbaf62e601a6085e2","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50985","feedbackHash":"2bf95bb3900918fe5781722642a329cf7ff12d6240e51672c7c7f69300b5b665","nodeKey":"audit_permissions","submissionHash":"01dafcdc56047dd3e06b5dab089f203b0221d9804b2bc906c819f322f7ef89ed","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52603","feedbackHash":"d0fd9ccd35490da7e1c0d7eac16709e063bb1a816f46984205bcdd87b6bb30ca","nodeKey":"build_contract_project","submissionHash":"7fbc0051f9a6248e2027b8e5048d56ba559b7ee30f18cf99d9d7aa0d464b5e52","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52268","feedbackHash":"ef348419f4cee23d2cab1c1a9ffa23d7621bb9c6dd25f2fcf5e9332cb27c160c","nodeKey":"build_contract_project","submissionHash":"934f1f566a04d12cd0b0cf6099587c2dc801882219d5cdf07490fa9ad7c8950c","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52626","feedbackHash":"27ae56834c1574b232b4a9fe5e78e4d458438213709ce85bcff2137455c6b316","nodeKey":"manifest","submissionHash":"6a594d81e84c0c4704bf310a530955c50461f914ed7bf4df42d90b92bb2148fb","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51247","feedbackHash":"6bb3e6a699a6983fbbed68edf2284f4354d0c95a3ee8f8f65d7aba33b1ad5f5e","nodeKey":"manifest","submissionHash":"87b9d93ec5eeea73f5ac8876ecdef3fab92e6aa42cdf245a9dcf791957d5b172","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51231","feedbackHash":"aab1b31fb5f36ab12b8ff3da90a6fbc9619427d51c2cda26b43249532718a1ab","nodeKey":"write_foundry_tests","submissionHash":"c3350fe800de589e53172c34548532c18ebc3cd5b127df2f547f8e2460176e5a","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51071","feedbackHash":"47c079d310e6248e33a8c1c2dc2c416a88220a82a876f622bd68f6f7983f0e63","nodeKey":"write_foundry_tests","submissionHash":"6f14e97a7323324387ee57ca82fc1717f199ee2244abd01eef0943cb0dc5ed7c","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"9af877ef7c886441d8939d92494e785f95d82460c24fc170b7d522edf22f5433","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"410247426ec5a6df","findings":[{"citation":"resolved","description":"afterSwap has two branches that are not economically symmetric. When IMD is the unspecified currency (lines 194-196) the fee is 1% of `delta`, the IMD the pool actually moved. When IMD is the specified currency (line 192, and the matching credit in beforeSwap lines 173-175) the fee is 1% of `params.amountSpecified`, the amount the trader *asked* for, fixed before the pool runs. The two agree only on a full fill. Whenever the trader's `sqrtPriceLimitX96` stops the swap early (the ordinary slippage control in v4; also reachable by a front-runner moving the price toward a victim's limit), the specified-side branch still charges the full 1% of the request. For an exact-input buy the trader pays 1% of the requested IMD on a fill that may be a fraction of it, so the effective rate is far above 1% (in the reproduction: 1 IMD fee on 3.04 IMD that reached the pool, 33% of the IMD side). For an exact-output sell the hook asks the pool for 1.01 N IMD and keeps N/100 of whatever comes out; when the limit leaves less than N/100, the swapper's IMD delta turns negative: they give up RNNN and *pay* IMD to the recipient, so a sell becomes a net outflow of both tokens. The brief's single rule is 1% of the swap amount; the README (lines 54-56) acknowledges the specified-side behaviour but the exact-output-sell sign flip is not described and no code path bounds the fee to the IMD that actually moved. Fix options: (a) in afterSwap, for the specified-IMD branch compute the fee from the realised IMD delta and refund the overcharge to `sender` (take the excess to the hook, then sync/transfer/settleFor(sender)), or (b) drop beforeSwapReturnDelta and compute the fee from the realised delta for all four shapes, taking it in afterSwap and converting the specified-side cases into a settleFor-based charge; (c) at minimum revert in beforeSwap when the limit is not the extreme so the trader is never charged for an unfilled request (this blocks limit orders and must be a conscious design decision).","line":192,"path":"src/RnnnFeeHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {StateLibrary} from \"v4-core/src/libraries/StateLibrary.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {PoolIdLibrary} from \"v4-core/src/types/PoolId.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {PoolSwapTest} from \"v4-core/src/test/PoolSwapTest.sol\";\nimport {PoolModifyLiquidityTest} from \"v4-core/src/test/PoolModifyLiquidityTest.sol\";\n\nimport {RnnnFeeHook} from \"src/RnnnFeeHook.sol\";\nimport {RnnnToken} from \"src/RnnnToken.sol\";\n\ncontract SimpleERC20 {\n    string public name = \"IMD\";\n    string public symbol = \"IMD\";\n    uint8 public constant decimals = 18;\n    uint256 public totalSupply;\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n\n    constructor(uint256 supply) {\n        totalSupply = supply;\n        balanceOf[msg.sender] = supply;\n    }\n\n    function approve(address s, uint256 a) external returns (bool) {\n        allowance[msg.sender][s] = a;\n        return true;\n    }\n\n    function transfer(address to, uint256 a) external returns (bool) {\n        balanceOf[msg.sender] -= a;\n        balanceOf[to] += a;\n        return true;\n    }\n\n    function transferFrom(address f, address to, uint256 a) external returns (bool) {\n        if (allowance[f][msg.sender] != type(uint256).max) allowance[f][msg.sender] -= a;\n        balanceOf[f] -= a;\n        balanceOf[to] += a;\n        return true;\n    }\n}\n\n/// @notice When IMD is the specified currency, the fee is 1% of the *requested* amount, fixed in\n/// beforeSwap, while the unspecified-side branch charges 1% of the *filled* amount. A swap that the\n/// trader's price limit stops early pays a fee far above 1% of the IMD that actually moved.\ncontract PartialFillFeeTest is Test {\n    using PoolIdLibrary for PoolKey;\n    using StateLibrary for IPoolManager;\n\n    uint160 constant SQRT_PRICE_1_1 = 79228162514264337593543950336;\n    uint160 constant FLAGS = (1 << 13) | (1 << 7) | (1 << 6) | (1 << 3) | (1 << 2);\n    address constant RECIPIENT = 0x45C89d0CF0B8a05218506236FcfcB18DA1D3ebE4;\n\n    PoolManager manager;\n    PoolSwapTest swapRouter;\n    PoolModifyLiquidityTest lpRouter;\n    SimpleERC20 imd;\n    RnnnToken rnnn;\n    RnnnFeeHook hook;\n    PoolKey key;\n    bool rnnnIsZero;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        swapRouter = new PoolSwapTest(manager);\n        lpRouter = new PoolModifyLiquidityTest(manager);\n        imd = new SimpleERC20(1e30);\n        rnnn = new RnnnToken();\n\n        bytes memory code = abi.encodePacked(type(RnnnFeeHook).creationCode, abi.encode(address(manager), address(imd), RECIPIENT));\n        bytes32 h = keccak256(code);\n        address at;\n        for (uint256 i = 0; i < 200_000; i++) {\n            address p = address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), h)))));\n            if (uint160(p) & ((1 << 14) - 1) == FLAGS) {\n                bytes32 salt = bytes32(i);\n                assembly (\"memory-safe\") {\n                    at := create2(0, add(code, 0x20), mload(code), salt)\n                }\n                break;\n            }\n        }\n        require(at != address(0), \"deploy\");\n        hook = RnnnFeeHook(at);\n\n        rnnnIsZero = address(rnnn) < address(imd);\n        (Currency c0, Currency c1) = rnnnIsZero\n            ? (Currency.wrap(address(rnnn)), Currency.wrap(address(imd)))\n            : (Currency.wrap(address(imd)), Currency.wrap(address(rnnn)));\n        key = PoolKey({currency0: c0, currency1: c1, fee: 12_500, tickSpacing: 60, hooks: IHooks(address(hook))});\n        manager.initialize(key, SQRT_PRICE_1_1);\n\n        imd.approve(address(swapRouter), type(uint256).max);\n        imd.approve(address(lpRouter), type(uint256).max);\n        rnnn.approve(address(swapRouter), type(uint256).max);\n        rnnn.approve(address(lpRouter), type(uint256).max);\n\n        // Full-range liquidity, both sides funded (about 1,000 IMD and 1,000 RNNN in the pool), so\n        // the direct-pay path is used and a 100 IMD order moves the price by whole ticks.\n        lpRouter.modifyLiquidity(\n            key, ModifyLiquidityParams(TickMath.minUsableTick(60), TickMath.maxUsableTick(60), 1e21, bytes32(0)), \"\"\n        );\n    }\n\n    function _imd(BalanceDelta d) internal view returns (int128) {\n        return rnnnIsZero ? d.amount1() : d.amount0();\n    }\n\n    /// Exact-input buy of 100 IMD, price limit a few ticks away: only a small part fills, but the\n    /// recipient still gets 1 IMD, far more than 1% of the IMD the trader actually swapped.\n    function test_exactInputBuy_partialFill_feeExceedsOnePercentOfFilledAmount() public {\n        bool zeroForOne = !rnnnIsZero; // IMD in, RNNN out\n        (, int24 tick,,) = IPoolManager(address(manager)).getSlot0(key.toId());\n        // Limit 60 ticks (one spacing) past the current price in the swap's direction.\n        uint160 limit = TickMath.getSqrtPriceAtTick(zeroForOne ? tick - 60 : tick + 60);\n\n        uint256 recipientBefore = imd.balanceOf(RECIPIENT);\n        uint256 swapperBefore = imd.balanceOf(address(this));\n        uint256 managerBefore = imd.balanceOf(address(manager));\n\n        swapRouter.swap(key, SwapParams(zeroForOne, -100 ether, limit), PoolSwapTest.TestSettings(false, false), \"\");\n\n        uint256 fee = imd.balanceOf(RECIPIENT) - recipientBefore;\n        uint256 paid = swapperBefore - imd.balanceOf(address(this));\n        uint256 poolGot = imd.balanceOf(address(manager)) - managerBefore;\n\n        emit log_named_uint(\"fee to recipient\", fee);\n        emit log_named_uint(\"IMD the pool received\", poolGot);\n        emit log_named_uint(\"IMD the trader paid\", paid);\n\n        // The fee the one rule allows: 1% of the IMD side of the swap that actually happened.\n        // (poolGot is 99% of the IMD side on a full fill; on a partial fill it is the whole IMD side.)\n        assertLe(fee, (poolGot + fee) / 100 + 1, \"fee exceeds 1% of the IMD that moved in the swap\");\n    }\n\n    /// Exact-output sell asking for 100 IMD, price limit a few ticks away: the pool pays out less\n    /// IMD than the fixed 1 IMD fee, so the trader gives up RNNN AND ends the swap paying IMD.\n    function test_exactOutputSell_partialFill_traderPaysImdWhileSelling() public {\n        bool zeroForOne = rnnnIsZero; // RNNN in, IMD out\n        (, int24 tick,,) = IPoolManager(address(manager)).getSlot0(key.toId());\n        // A very tight limit so only a sliver of the requested 100 IMD can be produced.\n        uint160 limit = TickMath.getSqrtPriceAtTick(zeroForOne ? tick - 1 : tick + 1);\n\n        uint256 recipientBefore = imd.balanceOf(RECIPIENT);\n        uint256 swapperImdBefore = imd.balanceOf(address(this));\n        uint256 swapperRnnnBefore = rnnn.balanceOf(address(this));\n\n        BalanceDelta delta =\n            swapRouter.swap(key, SwapParams(zeroForOne, 100 ether, limit), PoolSwapTest.TestSettings(false, false), \"\");\n\n        uint256 fee = imd.balanceOf(RECIPIENT) - recipientBefore;\n        emit log_named_uint(\"fee to recipient\", fee);\n        emit log_named_int(\"trader IMD delta\", _imd(delta));\n        emit log_named_uint(\"RNNN the trader gave\", swapperRnnnBefore - rnnn.balanceOf(address(this)));\n\n        assertGt(swapperRnnnBefore - rnnn.balanceOf(address(this)), 0, \"trader sold RNNN\");\n        // A seller must never end up paying IMD.\n        assertGe(imd.balanceOf(address(this)), swapperImdBefore, \"a sell of RNNN made the trader pay IMD\");\n    }\n}","reproduction":"Pool IMD/RNNN at price 1:1, fee 12500, spacing 60, full-range liquidity L = 1e21 (about 1,000 IMD and 1,000 RNNN), manager funded so the direct-pay path is used. (1) Exact-input buy: swap(zeroForOne = IMD->RNNN, amountSpecified = -100e18, sqrtPriceLimitX96 = price at currentTick +/- 60). Expected: fee <= 1% of the IMD side. Actual: recipient receives 1.000 IMD, the pool receives 3.042 IMD, the trader pays 4.042 IMD; the fee is 33% of the IMD that moved and 24.7% of the trader's outlay. (2) Exact-output sell: swap(zeroForOne = RNNN->IMD, amountSpecified = +100e18, sqrtPriceLimitX96 = price at currentTick +/- 1). Expected: a seller's IMD balance never decreases. Actual: the trader gives 0.0506 RNNN, the pool pays out 0.05 IMD, the recipient receives 1.000 IMD, and the trader's IMD delta is -0.950 IMD: the seller ends the swap paying IMD. Both are in test/scratch/PartialFillFee.t.sol (`forge test --match-path test/scratch/PartialFillFee.t.sol`), 2 failing on the current code.","severity":"medium","snippet":"        if (_specifiedCurrency(key, params) == imd) {\n            fee = feeFor(_abs(params.amountSpecified));\n        } else {\n            int128 imdDelta = key.currency0 == imd ? delta.amount0() : delta.amount1();\n            fee = feeFor(_abs(int256(imdDelta)));\n            hookDeltaUnspecified = fee.toInt128();\n        }","title":"Fee on IMD-specified swaps is 1% of the requested amount, not of the IMD that moved: a price-limited exact-output sell leaves the seller paying IMD"},{"citation":"resolved","description":"The README (lines 49-52 and 137-139) states that IMD has no blocklist (\"if IMD ever had one\") and treats a standard ERC-20 as an assumption. The IMD deployed on Robinhood Chain at 0x5f7bb59365ce557c26dbcaa4ee9d39a4b95b7127 (chain id 4663, read over this machine's RPC on 2026-10-11) is a LayerZero OFT whose runtime exposes `blocked(address)` (0xe5962195), `transfersEnabled()` (0xbef97c87, currently true), `enableTransfers()`, `setV4Config(address,address,bool)` (0x1d02710c, with `poolManager()` currently zero), `owner()` = 0x047f606fd5b2baa5f5c6c4ab8958e45cb6b054b7 and `transferOwnership`. The recipient 0x45c89d0cf0b8a05218506236fcfcb18da1d3ebe4 is a contract (a permissionless forwarder whose `feed()` reads its IMD balance, approves adapter 0x93584a3c6afbdbbf35f7c736b903f21f67d7c878 and pushes the IMD there; `blocked(recipient)` is currently false). Because `_collect` pays with `poolManager.take(imd, feeRecipient, fee)` inside the swap whenever the manager holds enough IMD, an IMD-side refusal of that single transfer (the recipient or the PoolManager added to `blocked`, or an IMD v4 config that constrains transfers out of the PoolManager) reverts every fee-bearing swap in the pool, both buys and sells, although the trade itself was fine; the claim path (`mint`) that the hook already has for the empty-manager case does not have this dependency. This is a trust assumption on a third party's admin, not a bypass, and is reported at low severity with the factual correction. Suggested fix: wrap the direct `take` in a try/catch (or probe `blocked(feeRecipient)`) and fall back to minting the claim, so a refused transfer defers the fee instead of blocking trading; correct the README.","line":282,"path":"src/RnnnFeeHook.sol","reproduction":"State: IMD.blocked(0x45c89d0cf0b8a05218506236fcfcb18da1d3ebe4) == true (one owner call on the live IMD; the selector exists today) and the PoolManager holds >= fee IMD. Call: any swap in the RNNN/IMD pool with |IMD side| >= 100 wei, e.g. exact-input buy of 100 IMD. Expected per the brief: the swap executes and 1 IMD is accounted to the recipient (deferred if it cannot be delivered). Actual: afterSwap -> _collect -> poolManager.take(imd, feeRecipient, 1e18) -> IMD.transfer reverts -> the whole swap reverts; the pool is untradeable until the block is lifted. With `transfersEnabled() == false` the same happens for every path (and for the router's own settlement). Verified in test/scratch/BlockedRecipient.t.sol with a blocklisting ERC-20 standing in for IMD: on a funded pool both a buy and a sell revert once the recipient is blocked (direct-pay branch), while on an RNNN-only pool the same buy succeeds and 1 IMD sits in pendingFees() (claim branch).","severity":"low","snippet":"        uint256 available = imd.balanceOf(address(poolManager));\n        if (available >= fee) {\n            poolManager.take(imd, feeRecipient, fee);\n            emit FeeTaken(key.toId(), fee, true);","title":"README assumption is wrong: live IMD has an owner-controlled blocklist and transfer switch, and the direct take inside afterSwap ties every swap's success to them"}],"hash":"01dafcdc56047dd3e06b5dab089f203b0221d9804b2bc906c819f322f7ef89ed","nodeId":"eccf3706-2939-4d7b-a49d-aa24bcc0f58d","outcome":"completed","summary":"The review is complete. Findings and coverage are in `.imd-findings.json`; no repository file was changed (the only new paths are the findings file and the two scratch tests, which are not kept).\n\n**Findings**\n\n- **Medium, fee asymmetry on IMD-specified swaps.** The two branches of `afterSwap` are not economically symmetric. When IMD is the unspecified currency the fee is 1% of the IMD the pool actually moved. When IMD is the specified currency the fee is fixed in `beforeSwap` at 1% of the amount requested. The branches agree only on a full fill. With a price limit that stops the swap early, an exact-input buy of 100 IMD filled 3.04 IMD into the pool and still paid 1 IMD, a 33% rate on the IMD side. An exact-output sell asking for 100 IMD with a tight limit received 0.05 IMD, paid the 1 IMD fee, and ended the swap with the seller paying IMD while also giving up RNNN. A front-runner can push a victim's fill toward their limit to trigger it. The proof test fails on the current code with both scenarios and passes once the fee is bounded to the IMD that moved.\n\n- **Low, wrong README assumption about IMD.** The README says IMD has no blocklist. The live IMD on Robinhood Chain is a LayerZero OFT whose runtime exposes `blocked(address)`, `transfersEnabled()`, `setV4Config` and an owner. Because the hook pays with a direct `take` inside the swap whenever the manager holds IMD, blocking the recipient on the IMD side reverts every buy and sell in the pool, while the hook's existing claim path does not. Verified with a blocklisting mock. This is a third-party admin trust assumption, reported for the factual correction and the simple fallback fix.\n\n**What held in my area**\n\nEvery callback and `unlockCallback` is PoolManager-only, and the manager only calls back the hook from its own `redeemPendingFees`. There is no owner, setter, pause, mint or escape opcode. Claims are minted only from the hook's own swap credit, can be burned only by the hook, and every `take` targets the immutable recipient. Third parties can open other IMD pools on the hook but only pay the same recipient. The recipient address is a permissionless forwarder contract that pushes received IMD into an adapter owned by the launch owner, so fees are not stuck there.\n\n**Coverage**\n\nAll six listed entry points have rows, plus seven extra rows for `beforeSwap`, `beforeInitialize`, the constructor, the unused callbacks and three invariants. `afterSwap` and `beforeSwap` are marked `finding` against the first item, the rest `holds`. Nothing is `unreached`.","treeHash":null,"usage":{"cachedInputTokens":1477016,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":53824,"runtime":"claude","turns":36,"wallClockMs":745387}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"8f08088e7a7f557f","findings":[],"hash":"11b3a124c1e560bed782012bf2fc981d812a744fbf7517f8a96e246f0752d354","nodeId":"8653da2f-8d26-47ba-8a5d-dd02f3894678","outcome":"completed","summary":"The review is complete: every finding from the previous round is settled as fixed, and I found no new defect. The findings file holds an empty list and a coverage record answering all eight entry points.\n\n**What I verified on the revised tree**\n\n- **Medium, fee on request not fill:** fixed. Both attached proofs pass (4 of 4). The new self-call simulation reverts fully, and v4-core skips the hook's callbacks for its own swap, which I confirmed in the vendored Hooks library. My fuzz probes over random sizes and limit distances agree: full fills pay 1% of the request, short fills pay 1% of the IMD the pool moved, zero fills pay nothing, and the trader always pays fill plus fee.\n- **Low, int128 cast on huge exact-input:** fixed. The cast is now on the bounded fill, and the author's hookless-comparison test exists and passes.\n- **Low, blocked recipient halts swaps:** fixed. The direct take is wrapped in try/catch and falls back to a claim. The Blocklist suite passes and the README now states the verified on-chain facts.\n- **Info, NatSpec/README direct-pay condition:** fixed in text, with a regression test.\n- **Info, dynamic-fee pool:** fixed. Initialization now reverts on the dynamic flag.\n- **Info, fee base by swap shape:** dropped. The author confirmed it as intended and documents it, which matches what I reported.\n\n**Additional checks**\n\n- The whole suite passes (81 tests). The protected hook and token floor suites pass with the manifest's constructor arguments, flags, fee 12500 and spacing 60 from a factory probe.\n- Two IMD-specified swaps in one unlock are each charged correctly, and a pending-claim flush during a sell leaves the seller's full output intact.\n- The manifest's arguments match the constructor order, permissions match the implementation, and notes are under the length limit. Bytecode is well under the size limits.\n- Slither's lines are benign: the uninitialized local is an intentional zero default, the ignored unlock return is unused by design, and the reentrancy-events hits are event ordering only.\n\nScratch tests were removed, so the only file written is the findings record.","treeHash":null,"usage":{"cachedInputTokens":1544313,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":34620,"runtime":"claude","turns":24,"wallClockMs":481625}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"d68ba89c0b30801c","findings":[{"citation":"resolved","description":"When IMD is the specified currency (exact-input buy, exact-output sell) the hook fixes its fee in beforeSwap as feeFor(|amountSpecified|) and recomputes the same value in afterSwap (line 192), never looking at the BalanceDelta the pool actually produced. A swap that the trader's sqrtPriceLimitX96 stops early, or that runs out of liquidity in its direction, executes P < |amountSpecified| of IMD, yet the hook still credits itself amountSpecified/100. Hooks.afterSwap then charges the caller that full hook delta (swapDelta - hookDelta), so the recipient receives more than 1% of the IMD side and the trader bears the difference. The other two shapes (afterSwap unspecified path, line 194-196) use the executed delta and are correct, so the two halves of the one rule disagree with each other. Boundary x invariant seam: the invariant 'fee == 1% of the swap's IMD amount' holds only on the full-fill path; the early-exit path (price limit reached, zero liquidity) bypasses it. Worst case is the launch pool itself: it is seeded with RNNN only, so there is no IMD below the current price and every sell fills zero until someone buys. An exact-output sell of N IMD on that pool moves no tokens through the pool, yet the hook returns a specified delta of N/100, the trader's IMD delta becomes -N/100, and the router settles that from the seller: the seller pays 1% of N in IMD and receives nothing, and the recipient is paid for a swap that did not happen (as a pending claim, since the manager holds no IMD). With a router that takes the output with an open delta (V4Router style) the negative IMD delta makes the take revert instead, so the swap is blocked rather than paid, which the brief forbids as well. The README records the partial-fill behaviour as known, but the brief's rule is 1% of the swap amount on every swap, so this is a departure from the requested behaviour, not a documented design choice the brief allows. Fix direction: in afterSwap, compute the fee from the executed IMD amount for every shape. For the specified-IMD shapes the executed amount is the IMD component of the BalanceDelta the pool returned (|delta.amountX| for the IMD side); when it is below what beforeSwap credited, return the difference to the swapper. Since afterSwapReturnDelta can only adjust the unspecified side, the simplest correct shape is to take no delta in beforeSwap and instead mint the excess credit back, or to compute the fee entirely in afterSwap and give the hook delta back via poolManager.mint to the sender when the specified amount was not fully filled; if the author prefers, revert on partial fill and document it, but that still alters swaps. The attached test passes under any fix that keeps the fee within 1% of the IMD that actually moved and charges nothing on a zero fill.","line":173,"path":"src/RnnnFeeHook.sol","reproduction":"State: pool RNNN/IMD at sqrtPrice 1:1, LP fee 12500, tick spacing 60, full-range liquidity 1e24 (test setUp). Case A, exact-output sell: swapRouter.swap(key, SwapParams(zeroForOne = rnnnIsZero, amountSpecified = +100_000e18 IMD, sqrtPriceLimitX96 = getSqrtPriceAtTick(600 ticks past current)), ...). Pool pays out 29,553.0108791371696808e18 IMD (partial fill). Expected: recipient receives about 295.53e18 (1%). Actual: recipient receives 1,000e18 (3.38% of the IMD that moved), trader receives 28,553e18 instead of about 29,257e18. Case B, exact-input buy: SwapParams(zeroForOne = !rnnnIsZero, amountSpecified = -100_000e18 IMD, limit 600 ticks past current). Trader pays 31,838.384692414306401635e18 IMD in total. Expected fee about 318.38e18. Actual fee 1,000e18 (3.14%). Case C, zero fill on the launch pool shape: liquidity only on the RNNN side of the current price (as the launch seeds it), manager IMD balance 0. swapRouter.swap(key, SwapParams(zeroForOne = rnnnIsZero, amountSpecified = +1_000e18 IMD, extreme limit)). Expected: nothing moves, no fee. Actual: no RNNN leaves the seller, the seller's IMD balance drops by exactly 10e18 (1e30 -> 999,999,999,990e18 in the test), and hook.pendingFees() == 10e18 owed to the recipient. Run: forge test --match-path test/scratch/PartialFillFee.t.sol (3 tests fail on the current code).","severity":"medium","snippet":"        if (_specifiedCurrency(key, params) == imd) {\n            uint256 fee = feeFor(_abs(params.amountSpecified));\n            return (IHooks.beforeSwap.selector, toBeforeSwapDelta(fee.toInt128(), 0), 0);","title":"Fee on IMD-specified swaps is 1% of the requested amount, not of the IMD that actually moved: partial fills over-charge and a zero fill charges a seller IMD for nothing"},{"citation":"resolved","description":"Same root cause as finding 1 (fee computed from the request, not the fill), distinct mechanism. Routers and integrators sometimes express 'swap as much as needed up to this price' as a huge exact-input amount with a sqrtPriceLimitX96; v4-core accepts this and fills to the limit (the pool only requires the filled part to fit int128). The hook computes fee = |amountSpecified| / 100 and casts it with SafeCast.toInt128(), which reverts for |amountSpecified| >= 100 * 2^127 (about 1.7e40). A hookless pool fills ~30,838e18 IMD for the same call; the hooked pool reverts, so the hook blocks a swap it should have charged 1% on. Just below the cast bound the swap goes through but the fee is 1% of the sentinel (1e38 IMD for a 1e40 request), which no trader can settle, so the call reverts in settlement instead. Low: the input is a sentinel-style amount larger than any IMD supply, so it only matters to integrators using that pattern. Fixing finding 1 (fee from executed amount) removes this path as well.","line":175,"path":"src/RnnnFeeHook.sol","reproduction":"State: two pools RNNN/IMD at 1:1, both with full-range liquidity 1e24, one with the hook and one with hooks = address(0). Call swapRouter.swap(plainKey, SwapParams(buy direction, amountSpecified = -1e45, sqrtPriceLimitX96 = getSqrtPriceAtTick(600 ticks past current))): succeeds, trader pays 30,838.469241430640163516e18 IMD. Call swapRouter.swap(key /* hooked */, same params): reverts (SafeCastOverflow in beforeSwap at fee.toInt128(), fee = 1e43 > int128 max). With amountSpecified = -1e40 on the hooked pool: beforeSwap succeeds with fee = 1e38 IMD; the trader's IMD delta is -(fill + 1e38) and settlement fails with the token's balance check. Scratch test used: test/scratch/LargeAmount.t.sol (test_hugeExactInputWithLimit_plainPoolFills_hookedPoolReverts passes, i.e. the plain pool fills and the hooked pool reverts).","severity":"low","snippet":"            return (IHooks.beforeSwap.selector, toBeforeSwapDelta(fee.toInt128(), 0), 0);","title":"int128 cast of the fee reverts swaps with a very large exact-input amount that the pool itself would partially fill"},{"citation":"resolved","description":"feeFor truncates (amount * 100 / 10_000). The Math Precision guide asks that fees round up; here they round down in the trader's favour. The loss is at most 1 wei of IMD (1e-18 IMD) per swap and does not compound, so this is dust and recorded for completeness only. The README documents the direction. No change is required unless the author wants fees to round up (use (amount * FEE_BPS + BPS_DENOMINATOR - 1) / BPS_DENOMINATOR).","line":124,"path":"src/RnnnFeeHook.sol","reproduction":"hook.feeFor(99) returns 0 (expected 1 if rounding up); hook.feeFor(199) returns 1 (0.5% instead of 1%). An exact-input buy of 99 wei IMD on a funded pool succeeds and transfers 0 to the recipient (existing test test_tinySwapWithZeroFeeStillSucceeds shows the path).","severity":"info","snippet":"        return imdAmount * FEE_BPS / BPS_DENOMINATOR;","title":"Fee rounds down: swaps under 100 wei of IMD pay no fee and swaps under 200 wei pay 0.5%"}],"hash":"3b24ef8b5efe74a5cf66606e06da66002f2abb61ffb798bcbaf62e601a6085e2","nodeId":"ff1e6acf-636a-4fb0-916c-5402dccf62a5","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds three findings plus ten coverage rows. No source files were changed; the only new files are the findings JSON and scratch tests under `test/scratch/`, which are not kept.\n\n**What I found**\n\n- **Medium, with a proof test.** When IMD is the specified currency, the hook fixes its fee in beforeSwap from the requested amount and never checks what the pool executed. A swap stopped early by the trader's price limit, or by missing liquidity, is charged 1% of the request rather than of the IMD that moved. In the proof, a 100,000 IMD request filled for about 30,000 IMD still paid 1,000 IMD to the recipient, over 3%. The launch pool is seeded with RNNN only, so every sell fills zero until someone buys. An exact-output sell there moves nothing and still takes 1% of the requested IMD from the seller, recorded as a pending claim for the recipient. The README documents partial fills as known, but the brief asks for 1% of the swap amount on every swap, so I reported it as a defect. The proof at `test/scratch/PartialFillFee.t.sol` fails three ways on the current code and accepts either reading of \"the IMD side\" after a fix.\n- **Low.** The same root cause makes the int128 cast of the fee revert for sentinel-sized exact-input amounts above about 1.7e40. A hookless pool fills such a \"swap up to this price\" request to the limit. The hooked pool reverts. Fixing the first finding removes this path.\n- **Info.** The fee rounds down, so swaps under 100 wei of IMD pay nothing. Dust, no compounding, documented in the README.\n\n**What holds**\n\nThe unspecified-IMD paths, the take-or-mint accounting in `_collect`, the pending-claim flush, redemption, the unlock callback, and the token's fixed-supply arithmetic all trace cleanly. Slither's uninitialized-local and unused-return leads are intentional and harmless, so I did not report them.","treeHash":null,"usage":{"cachedInputTokens":1584533,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":37824,"runtime":"claude","turns":28,"wallClockMs":529208}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"4199bf0c80a9d6cf","findings":[{"citation":"resolved","description":"For the two swap shapes where IMD is the specified currency (exact-input buy, exact-output sell) `beforeSwap` fixes the fee as `feeFor(_abs(params.amountSpecified))` and `afterSwap` (line 192) recomputes the same number from `params.amountSpecified`, never from the `delta` the pool actually produced. The specified-side credit is applied by `Hooks.beforeSwap` before the pool runs, so if the pool stops early (the trader's `sqrtPriceLimitX96` is reached, or there is no liquidity in the swap's direction) the fee stays at 1% of the requested amount while the IMD that moved is smaller. Consequences: (a) partial fill: the recipient receives more than 1% of the IMD side (3.2% in the reproduction), which breaks the brief's single rule and the README's 'exactly 1%'; (b) zero fill: an exact-output sell on the launch pool as the factory seeds it (RNNN-only liquidity, no IMD in the manager) moves no RNNN and no IMD through the pool, but the trader's IMD delta becomes `0 - fee`, so the trader pays 1% of the IMD they asked to receive and gets nothing, and the amount is recorded as a pending claim for the recipient; (c) a sell asking for more IMD than the pool holds flips the trader's IMD delta negative: with 99 IMD in the manager, an exact-output sell for 20,000 IMD makes the trader pay 102.24 IMD and 98.4 RNNN, receive nothing, and credits the hook a 200 IMD claim. The unspecified-side shapes (exact-output buy, exact-input sell) are correct because they read the real delta. Routers that check the output credit (v4-periphery V4Router) revert on cases (b) and (c) and only expose (a); routers without that check (PoolSwapTest, a bare unlock caller) execute all three. The README discloses (a) as a design limitation; the brief it is measured against says the fee is 1% of the swap amount, and a sell that takes IMD from the seller is not covered by that disclosure. The specified side cannot be adjusted after the pool runs, so the minimal fix that keeps 'the recipient always receives IMD' is to reconcile in `afterSwap` for the IMD-specified branch: derive the IMD the pool actually moved from `delta` (exact-input buy: `|imdDelta|` must equal `|amountSpecified| - fee`; exact-output sell: `|imdDelta|` must equal `amountSpecified + fee`) and revert when it is smaller, i.e. refuse partial fills of IMD-specified swaps (swaps sent with the extreme price limit on a pool with liquidity are unaffected). If the author prefers to keep partial fills, the README table must state that IMD-specified swaps are charged on the requested amount and that an exact-output sell can take IMD from the seller; the proof test then documents the accepted deviation.","line":173,"path":"src/RnnnFeeHook.sol","reproduction":"State: PoolManager, RnnnToken, standard 18-decimal IMD, hook deployed at flags 0x20CC with (manager, imd, 0x45C89d0CF0B8a05218506236FcfcB18DA1D3ebE4), pool (RNNN, IMD, fee 12500, spacing 60) initialised at sqrtPrice 2^96. Case (a): add full-range liquidity 1e24; swap exact-input buy amountSpecified = -10_000e18 IMD with sqrtPriceLimitX96 = getSqrtPriceAtTick(+/-60) through PoolSwapTest. Expected: fee = 1% of the IMD the trader paid. Actual: trader pays 3,142.38 IMD in total (3,042.38 to the pool), recipient receives 100 IMD = 3.18% of the IMD side. Case (b): seed RNNN-only liquidity 1e24 (ticks [60, max] when RNNN is currency0, else [min, -60]), manager IMD balance 0; swap exact-output sell amountSpecified = +100e18 (RNNN in, IMD out) with the extreme price limit. Expected: nothing moves and no fee is charged, or the swap is refused. Actual: swap succeeds, trader's RNNN balance unchanged, trader's IMD balance falls by exactly 1e18, hook.pendingFees() = 1e18. Case (c): after one exact-input buy of 100e18 IMD and redeemPendingFees() (manager holds 99e18 IMD), swap exact-output sell amountSpecified = +20_000e18. Actual: trader IMD change -102.2375e18, RNNN change -98.398e18, hook credited a 200e18 claim; the trader received nothing. Proof file: test/scratch/PartialFillFee.t.sol; both tests fail on this tree with 'fee must be 1% of the IMD the trader actually paid' (100e18 != 31.42e18) and 'a sell that moved nothing took IMD from the trader'.","severity":"medium","snippet":"        if (_specifiedCurrency(key, params) == imd) {\n            uint256 fee = feeFor(_abs(params.amountSpecified));\n            return (IHooks.beforeSwap.selector, toBeforeSwapDelta(fee.toInt128(), 0), 0);","title":"IMD-specified swaps pay 1% of the requested amount, not of the IMD moved: partial fills overcharge, and a sell that moves nothing still takes IMD from the trader"},{"citation":"resolved","description":"The design note, and README 'How the fee reaches the recipient' item 1 ('This is the case on every sell and on every buy after the pool has traded once'), say direct payment always happens once the pool has traded. `_collect` (lines 280-281) pays directly only when `imd.balanceOf(poolManager) >= fee`, and the trader's input is not in the manager yet when `afterSwap` runs, so any buy whose 1% exceeds the IMD the manager currently holds is deferred as a claim regardless of history. This is not a loss (the claim is backed and anyone can redeem it) but the operational text tells the recipient and keepers to expect direct payment after the first trade, which is wrong for the early life of the launch pool while the manager holds little IMD. The statement about sells holds. Fix: state the real condition ('when the manager already holds at least the fee in IMD') in the NatSpec and the README.","line":29,"path":"src/RnnnFeeHook.sol","reproduction":"State: launch-shaped pool seeded with RNNN-only liquidity 1e24, manager IMD balance 0. Sequence: exact-input buy of 100e18 IMD (fee 1e18 deferred), redeemPendingFees() (manager now holds 99e18 IMD, recipient has 1e18), then exact-input buy of 1_000_000e18 IMD. Expected per the text: fee of 10_000e18 paid directly to the recipient inside the swap. Actual: imd.balanceOf(manager) = 99e18 < 10_000e18, so FeeTaken(poolId, 10_000e18, false) is emitted, the recipient's balance is unchanged and hook.pendingFees() = 10_000e18 until the next qualifying swap or a redeemPendingFees() call.","severity":"info","snippet":"/// - In `afterSwap` the credit is paid out at once with `poolManager.take(imd, feeRecipient, fee)`\n///   whenever the PoolManager already holds enough IMD (it always does once the pool has traded, and\n///   on every sell). On the first buys into a pool seeded with RNNN only, the manager has no IMD yet","title":"NatSpec and README overstate when the fee is paid directly: a buy whose fee exceeds the manager's IMD balance still defers, however many trades came before"}],"hash":"47a7e785c5272a73091c3f5728602de1f124e40fba606f836d73ea7fbefc00bd","nodeId":"f11bf13e-bd07-49b0-8f83-100af8f305e4","outcome":"completed","summary":"The review is complete. The findings file is written and validated, no tracked files changed, and the only files I created are the required output and one proof test under the scratch directory the task allows.\n\n**Outcome.** The hook's control flow is sound in every path I traced except one: swaps where IMD is the specified currency are charged on the amount requested, not on the amount that moved. I reported that as medium with a failing Foundry proof, plus one info-level documentation inaccuracy. Both are recorded in `.imd-findings.json` with reproductions and a coverage row for every listed entry point.\n\n**Finding 1, medium.** In `beforeSwap`, the fee for an exact-input buy or exact-output sell is fixed at 1% of `params.amountSpecified` before the pool runs, and `afterSwap` never reconciles it with the real delta. Three concrete consequences, all reproduced:\n- A partial fill (price limit one tick spacing away, 10,000 IMD exact-input buy) pays 100 IMD on 3,142 IMD moved, about 3.2% instead of 1%.\n- On the launch pool as seeded (RNNN-only liquidity, no IMD in the manager), an exact-output sell for 100 IMD moves nothing yet takes 1 IMD from the seller and records it as a pending claim.\n- A sell asking for more IMD than the pool holds makes the trader pay 102 IMD plus 98 RNNN and receive nothing.\nThe proof test accepts either a 1%-of-moved fee or a revert, so a fix that refuses partial fills of IMD-specified swaps makes it pass. The README discloses only the first case.\n\n**Finding 2, info.** The NatSpec and README say direct payment always happens once the pool has traded. A buy whose fee exceeds the manager's current IMD balance still defers, regardless of history.\n\n**What held.** Delta accounting nets to zero on every path (take or mint before the manager's credit, burn plus take in flush and redemption). The redemption path is permissionless, re-entry safe through the manager's lock, and the callback can only be reached by the hook's own unlock. Pending claims cannot be moved anywhere but the recipient. The token is a plain fixed-supply ERC-20. Periphery (HookFlags, HookDeployer, script, mock) matches v4-core. I also confirmed, but did not report, that dynamic-fee IMD pools are admitted at 0 LP fee, since any such second pool is possible without the hook and the brief forbids blocking.\n\n**Not reached.** No launch.json exists in the tree yet, so the manifest row is marked unreached; the README's constructor table and permissions match the implementation.","treeHash":null,"usage":{"cachedInputTokens":2255268,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":56200,"runtime":"claude","turns":35,"wallClockMs":746537}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"7e64d4917fa814e6","findings":[{"citation":"resolved","description":"Merged from audit_permissions, audit_math, audit_economics, audit_flow and write_foundry_tests (same root cause; the int128 overflow mechanism is reported separately as finding 2). When IMD is the specified currency (exact-input buy, exact-output sell) beforeSwap (line 174) fixes the fee as feeFor(|amountSpecified|) and returns it as the specified-side delta before the pool runs, and afterSwap (line 192) books the same number with _collect, never reading the BalanceDelta the pool produced. Hooks.afterSwap then subtracts that fixed hook delta from the swapper's delta whatever the pool filled, and Pool.swap stops early without reverting when the trader's sqrtPriceLimitX96 is reached or liquidity in that direction runs out. The other two shapes (lines 194-196) use the real delta and are correct, so the single rule is applied two different ways. Consequences, all reproduced: (a) a price-limited exact-input buy pays 1% of the whole request on a small fill (10,000 IMD fee on about 50.6 IMD traded in the proof; 1 IMD on 3.04 IMD with a one-spacing limit); (b) on the launch pool as the factory seeds it (RNNN-only liquidity, manager holds no IMD) an exact-output sell before the first buy moves no RNNN and no IMD, yet the seller's IMD delta becomes -fee: the seller pays 1 IMD for a 100 IMD request and receives nothing, and the hook mints that as a pending claim for the recipient; (c) an exact-output sell asking for more IMD than the pool can serve, the ordinary early-life shape of this one-sided pool, is charged on the request (10 IMD on a 97.76 IMD payout in audit_economics' run), and when the payout is below the fee the seller ends the swap paying IMD as well as RNNN. Routers that check the output credit (V4Router/Universal Router TAKE) revert on (b) and (c) instead, which blocks the swap; PoolSwapTest-style routers execute them. The brief's one rule is 1% of the swap's IMD amount and no other alteration of swaps; the README discloses only the price-limit case. Severity medium: the loss is bounded to 1% of the requested amount, happens only on short or empty fills, and the trader chose the swap shape, but on this launch pool it is the normal case for early sells, and the recipient is paid for IMD that never moved. Fix direction (author's design choice): reconcile in afterSwap for the IMD-specified branch using the IMD component of `delta` (exact-input buy: |imdDelta| == |amountSpecified| - fee on a full fill; exact-output sell: |imdDelta| == amountSpecified + fee) and either revert on a short fill or return the excess credit to the swapper (poolManager.take(imd, sender, excess) with sender the router, or mint it to the sender) and charge 1% of the realised amount; alternatively drop beforeSwapReturnDelta and charge all four shapes in afterSwap from the realised delta. Any fix must keep the recipient receiving IMD only.","line":192,"path":"src/RnnnFeeHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {PoolSwapTest} from \"v4-core/src/test/PoolSwapTest.sol\";\nimport {PoolModifyLiquidityTest} from \"v4-core/src/test/PoolModifyLiquidityTest.sol\";\n\nimport {RnnnFeeHook} from \"src/RnnnFeeHook.sol\";\nimport {RnnnToken} from \"src/RnnnToken.sol\";\n\n/// @dev Minimal ERC-20 standing in for IMD. Self-contained so the proof needs no other test file.\ncontract ProofERC20 {\n    string public constant name = \"IMD\";\n    string public constant symbol = \"IMD\";\n    uint8 public constant decimals = 18;\n    uint256 public totalSupply;\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n\n    constructor(uint256 supply) {\n        totalSupply = supply;\n        balanceOf[msg.sender] = supply;\n    }\n\n    function approve(address spender, uint256 amount) external returns (bool) {\n        allowance[msg.sender][spender] = amount;\n        return true;\n    }\n\n    function transfer(address to, uint256 amount) external returns (bool) {\n        balanceOf[msg.sender] -= amount;\n        balanceOf[to] += amount;\n        return true;\n    }\n\n    function transferFrom(address from, address to, uint256 amount) external returns (bool) {\n        if (allowance[from][msg.sender] != type(uint256).max) allowance[from][msg.sender] -= amount;\n        balanceOf[from] -= amount;\n        balanceOf[to] += amount;\n        return true;\n    }\n}\n\n/// @notice Proof: when IMD is the swap's specified currency (exact-input buy, exact-output sell) the\n/// hook charges 1% of the *requested* IMD amount even when the pool fills only part of it, or none\n/// of it. The brief's rule is 1% of the swap amount; a swap that traded 0 IMD must pay 0 fee, and a\n/// seller must never end up paying IMD.\ncontract PartialFillFeeProof is Test {\n    uint160 constant SQRT_PRICE_1_1 = 79228162514264337593543950336;\n    uint24 constant POOL_FEE = 12_500;\n    int24 constant TICK_SPACING = 60;\n    uint160 constant FLAGS = uint160(0x20CC);\n    address constant RECIPIENT = 0x45C89d0CF0B8a05218506236FcfcB18DA1D3ebE4;\n\n    PoolManager manager;\n    PoolSwapTest swapRouter;\n    PoolModifyLiquidityTest lpRouter;\n    ProofERC20 imd;\n    RnnnToken rnnn;\n    RnnnFeeHook hook;\n    PoolKey key;\n    bool rnnnIsZero;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        swapRouter = new PoolSwapTest(manager);\n        lpRouter = new PoolModifyLiquidityTest(manager);\n        imd = new ProofERC20(1e30);\n        rnnn = new RnnnToken();\n\n        bytes memory code =\n            abi.encodePacked(type(RnnnFeeHook).creationCode, abi.encode(address(manager), address(imd), RECIPIENT));\n        bytes32 initHash = keccak256(code);\n        address at;\n        for (uint256 i = 0; i < 200_000; i++) {\n            address predicted = address(\n                uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), initHash))))\n            );\n            if (uint160(predicted) & uint160((1 << 14) - 1) != FLAGS) continue;\n            bytes32 salt = bytes32(i);\n            assembly (\"memory-safe\") {\n                at := create2(0, add(code, 0x20), mload(code), salt)\n            }\n            break;\n        }\n        require(at != address(0), \"hook deployment failed\");\n        hook = RnnnFeeHook(at);\n\n        rnnnIsZero = address(rnnn) < address(imd);\n        (Currency c0, Currency c1) = rnnnIsZero\n            ? (Currency.wrap(address(rnnn)), Currency.wrap(address(imd)))\n            : (Currency.wrap(address(imd)), Currency.wrap(address(rnnn)));\n        key = PoolKey({currency0: c0, currency1: c1, fee: POOL_FEE, tickSpacing: TICK_SPACING, hooks: IHooks(at)});\n        manager.initialize(key, SQRT_PRICE_1_1);\n\n        imd.approve(address(swapRouter), type(uint256).max);\n        imd.approve(address(lpRouter), type(uint256).max);\n        rnnn.approve(address(swapRouter), type(uint256).max);\n        rnnn.approve(address(lpRouter), type(uint256).max);\n    }\n\n    function _limit(bool zeroForOne) internal pure returns (uint160) {\n        return zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1;\n    }\n\n    /// @dev Liquidity only on the RNNN side of the price (the launch's seed shape). Selling RNNN\n    /// moves the price into a region with no liquidity: nothing fills, no IMD is paid out, yet the\n    /// hook charges 1% of the requested 100 IMD and the *seller* pays 1 IMD for nothing.\n    function test_exactOutputSellThatFillsNothing_mustChargeNoFee() public {\n        (int24 lower, int24 upper) = rnnnIsZero\n            ? (TICK_SPACING, TickMath.maxUsableTick(TICK_SPACING))\n            : (TickMath.minUsableTick(TICK_SPACING), -TICK_SPACING);\n        lpRouter.modifyLiquidity(key, ModifyLiquidityParams(lower, upper, 1e24, bytes32(0)), \"\");\n\n        uint256 traderImdBefore = imd.balanceOf(address(this));\n        uint256 traderRnnnBefore = rnnn.balanceOf(address(this));\n        uint256 want = 100 ether;\n\n        swapRouter.swap(\n            key, SwapParams(rnnnIsZero, int256(want), _limit(rnnnIsZero)), PoolSwapTest.TestSettings(false, false), \"\"\n        );\n\n        uint256 rnnnPaid = traderRnnnBefore - rnnn.balanceOf(address(this));\n        assertEq(rnnnPaid, 0, \"setup: the pool had nothing to fill, no RNNN was taken\");\n        uint256 feeTaken = imd.balanceOf(RECIPIENT) + hook.pendingFees();\n        // Expected: 0 (1% of the 0 IMD traded). Actual: 1 IMD.\n        assertEq(feeTaken, 0, \"fee must be 1% of the IMD actually traded, which is 0\");\n        // Expected: the seller's IMD is untouched. Actual: the seller paid 1 IMD.\n        assertGe(imd.balanceOf(address(this)), traderImdBefore, \"a sell made the trader pay IMD\");\n    }\n\n    /// @dev A funded pool and a price limit one tick away: the exact-input buy fills only a sliver,\n    /// yet the fee is 1% of the whole requested input (10,000 IMD of fee on about 50 IMD traded).\n    function test_exactInputBuyWithPriceLimit_feeMustBeOnePercentOfTheFill() public {\n        lpRouter.modifyLiquidity(\n            key,\n            ModifyLiquidityParams(\n                TickMath.minUsableTick(TICK_SPACING), TickMath.maxUsableTick(TICK_SPACING), 1e24, bytes32(0)\n            ),\n            \"\"\n        );\n        uint256 amountIn = 1_000_000 ether;\n        bool zeroForOne = !rnnnIsZero; // IMD in, RNNN out\n        uint160 limit = zeroForOne ? TickMath.getSqrtPriceAtTick(-1) : TickMath.getSqrtPriceAtTick(1);\n\n        uint256 traderImdBefore = imd.balanceOf(address(this));\n        uint256 managerImdBefore = imd.balanceOf(address(manager));\n        swapRouter.swap(\n            key, SwapParams(zeroForOne, -int256(amountIn), limit), PoolSwapTest.TestSettings(false, false), \"\"\n        );\n\n        uint256 feeTaken = imd.balanceOf(RECIPIENT) + hook.pendingFees();\n        uint256 imdIntoPool = imd.balanceOf(address(manager)) - managerImdBefore - hook.pendingFees();\n        uint256 traderPaid = traderImdBefore - imd.balanceOf(address(this));\n        assertEq(traderPaid, imdIntoPool + feeTaken);\n        assertLt(imdIntoPool, amountIn / 2, \"setup: the fill is partial\");\n        // Expected: 1% of the IMD the pool took. Actual: 1% of the 1,000,000 IMD requested.\n        assertEq(feeTaken, imdIntoPool / 100, \"fee must be 1% of the IMD the pool actually took\");\n    }\n}","reproduction":"Setup: PoolManager, RnnnToken, plain 18-decimal ERC-20 as IMD, RnnnFeeHook(manager, imd, 0x45C89d0CF0B8a05218506236FcfcB18DA1D3ebE4) at a 0x20CC-flag address, pool (RNNN, IMD, fee 12500, spacing 60) at sqrtPrice 2^96, PoolSwapTest router. Case A (zero fill, launch seed shape): add 1e24 liquidity only on the RNNN side of the price (ticks [60, max] when RNNN is currency0, else [min, -60]); swap SwapParams(zeroForOne = RNNN is currency0, amountSpecified = +100e18, extreme limit). Expected: 0 RNNN taken, 0 IMD moved, fee 0, seller's IMD unchanged. Actual: 0 RNNN taken, seller's IMD balance falls by exactly 1e18, hook.pendingFees() == 1e18. Case B (partial fill): 1e24 full-range liquidity; swap exact-input buy amountSpecified = -1_000_000e18 IMD with sqrtPriceLimitX96 = getSqrtPriceAtTick(one tick past current). Expected: fee == 1% of the IMD the pool took (about 0.506e18). Actual: recipient receives 10_000e18; pool took 50.63e18. Case C (one-spacing limit, 1e21 liquidity): exact-input buy of 100e18 with limit 60 ticks away: recipient 1e18, pool received 3.042e18, trader paid 4.042e18. Exact-output sell of 100e18 with a 1-tick limit: trader gives 0.0506 RNNN, recipient 1e18, trader IMD delta -0.95e18. Run: forge test --match-path test/scratch/PartialFillFee.t.sol (proof below, 2 failing: 'fee must be 1% of the IMD actually traded, which is 0: 1000000000000000000 != 0' and 'fee must be 1% of the IMD the pool actually took: 10000000000000000000000 != 506316456329074369'). Both specialist proofs (.imd/reads/proofs) were run on this tree and fail for the stated reasons.","severity":"medium","snippet":"            fee = feeFor(_abs(params.amountSpecified));","title":"IMD-specified swaps are charged 1% of the requested amount, not of the IMD that moved: partial fills overcharge and a zero-fill sell takes IMD from the seller"},{"citation":"resolved","description":"From audit_math; same root cause as finding 1 (fee from the request, not the fill) with a different outcome: the swap is blocked instead of overcharged. v4-core accepts an exact-input amountSpecified far above int128 and fills only to the price limit (only the filled part must fit int128). beforeSwap computes fee = |amountSpecified| / 100 and casts it with SafeCast.toInt128(), which reverts for |amountSpecified| >= 100 * 2^127 (about 1.7e40); just below that bound the fee is 1% of a sentinel amount nobody can settle, so the swap reverts in settlement instead. Integrators that express 'buy up to this price' as a huge exact-input with a limit cannot trade the hooked pool. Low: the input is a sentinel-style amount larger than any IMD supply. Fixing finding 1 (fee from the realised IMD delta) removes this path.","line":175,"path":"src/RnnnFeeHook.sol","reproduction":"Two RNNN/IMD pools at 1:1 with 1e24 full-range liquidity, one with the hook and one with hooks = address(0). swapRouter.swap(plainKey, SwapParams(IMD-in direction, amountSpecified = -1e45, sqrtPriceLimitX96 = getSqrtPriceAtTick(600 ticks past current))) succeeds and the trader pays 30,838.469241430640163516e18 IMD. The same call on the hooked pool reverts (SafeCastOverflow in beforeSwap at fee.toInt128(), fee = 1e43 > type(int128).max). Verified in test/scratch/Leads.t.sol test_hugeExactInput_plainFills_hookedReverts (plain fills, hooked reverts).","severity":"low","snippet":"            return (IHooks.beforeSwap.selector, toBeforeSwapDelta(fee.toInt128(), 0), 0);","title":"int128 cast of the request-based fee reverts a huge exact-input IMD swap that a hookless pool fills to its price limit"},{"citation":"resolved","description":"From audit_permissions. _collect pays the fee with poolManager.take(imd, feeRecipient, fee) inside the swap whenever the manager holds at least the fee in IMD. If the IMD token refuses that single transfer (recipient or PoolManager on a token-side blocklist, transfers paused), every swap whose fee is non-zero reverts, buys and sells alike, although the trade itself was fine; the claim path the hook already has for the empty-manager case does not depend on it. This is a trust assumption on the IMD token's admin, not a bypass, and the harm needs that admin's action, so it is low. audit_permissions reports that the live IMD on Robinhood Chain (0x5f7bb59365ce557c26dbcaa4ee9d39a4b95b7127) is an owner-controlled LayerZero OFT exposing blocked(address) and transfersEnabled(), and that the recipient is a contract; I could not read the chain from this machine, so that factual claim is unverified here, but the README (lines 49-52, 137-139) states the assumption as 'if IMD ever had one' and should be corrected if it is wrong. Suggested fix: try the direct take and fall back to minting the claim when it reverts (or probe blocked(feeRecipient)), so a refused transfer defers the fee rather than halting trading; correct the README.","line":282,"path":"src/RnnnFeeHook.sol","reproduction":"With an ERC-20 standing in for IMD whose transfer reverts when `to` is blocked: funded pool (1e24 full-range liquidity), imd.setBlocked(0x45C89d0CF0B8a05218506236FcfcB18DA1D3ebE4, true). swapRouter.swap exact-input buy of 100e18 IMD: reverts. swapRouter.swap exact-input sell of 100e18 RNNN: reverts. A 50-wei buy (fee 0) still succeeds. Expected per the brief: the trade executes and the fee is accounted (deferred if it cannot be delivered). Verified in test/scratch/Leads.t.sol test_blockedRecipient_haltsSwapsOnFundedPool.","severity":"low","snippet":"            poolManager.take(imd, feeRecipient, fee);","title":"Direct take inside afterSwap ties every fee-bearing swap to IMD accepting a transfer to the recipient; README's 'no blocklist' assumption is unverified"},{"citation":"resolved","description":"From audit_flow. The design note (lines 29-31) and README item 1 under 'How the fee reaches the recipient' say direct payment always happens once the pool has traded. _collect pays directly only when imd.balanceOf(poolManager) >= fee, and the buyer's input is not in the manager when afterSwap runs, so any buy whose 1% exceeds the IMD the manager holds at that moment is deferred as a claim. Not a loss (the claim is backed and anyone redeems it), but the operational text tells the recipient and keepers to expect the wrong thing during the early life of the one-sided launch pool. Fix: state the real condition in the NatSpec and README.","line":30,"path":"src/RnnnFeeHook.sol","reproduction":"Launch-shaped pool (RNNN-only liquidity 1e24, manager IMD 0). Exact-input buy of 100e18 IMD (fee deferred), redeemPendingFees() (manager now holds 99e18, pending 0), then exact-input buy of 1_000_000e18 IMD. Expected per the text: 10_000e18 paid to the recipient in the swap. Actual: recipient balance unchanged, hook.pendingFees() == 10_000e18. Verified in test/scratch/Leads.t.sol test_largeBuyAfterFirstTrade_stillDeferred.","severity":"info","snippet":"///   whenever the PoolManager already holds enough IMD (it always does once the pool has traded, and","title":"NatSpec and README overstate direct payment: a buy whose fee exceeds the manager's current IMD balance is deferred regardless of trading history"},{"citation":"resolved","description":"From write_foundry_tests. beforeInitialize accepts any pool containing IMD, including fee == 0x800000 (the dynamic-fee flag). The hook never calls updateDynamicLPFee and beforeSwap returns an lpFeeOverride of 0 without the override flag, so such a pool's LP fee is 0 and nothing can change it. The launch pool (fee 12500) is unaffected and the 1% hook fee is still charged on the dynamic pool. Anyone may open such a pool; if that is unwanted, refuse the dynamic flag in beforeInitialize.","line":159,"path":"src/RnnnFeeHook.sol","reproduction":"Initialize PoolKey{RNNN/IMD sorted, fee = LPFeeLibrary.DYNAMIC_FEE_FLAG, tickSpacing 60, hooks = RnnnFeeHook} at 2^96: succeeds; getSlot0 reports lpFee 0; add 1e24 liquidity and buy 100e18 IMD exact-input: recipient receives 1e18, lpFee still 0. Verified in test/scratch/Leads.t.sol test_dynamicFeePool_lpFeeZero.","severity":"info","snippet":"        if (!(key.currency0 == imd || key.currency1 == imd)) revert PoolMustIncludeImd();","title":"beforeInitialize admits a dynamic-fee IMD pool whose LP fee is then 0 forever"},{"citation":"resolved","description":"From audit_economics. For IMD-specified swaps the fee is 1% of amountSpecified (the trader's figure); for IMD-unspecified swaps it is 1% of the pool's IMD delta. Measured against the brief's wording '1% of the swap amount in IMD (the pool's IMD side)', an exact-input buy of 100 IMD leaves 99 IMD for the pool and pays 1 IMD, 1.0101% of the pool side; an exact-output buy priced at 99 IMD pays 0.99 IMD, exactly 1% of the pool side. The README table documents both definitions, so this is a note for the author to confirm the intended base, not a defect. The rounding-down of feeFor (99 wei -> 0, 199 wei -> 1) reported by audit_math is dust, documented in the README, and not reported separately.","line":174,"path":"src/RnnnFeeHook.sol","reproduction":"Funded pool (1e24 full-range liquidity, price 1:1). Exact-input buy amountSpecified = -100e18 IMD: manager's IMD balance rises by 99e18, recipient receives 1e18 (1.0101% of 99e18). The existing tests test_buyExactInput_sendsExactlyOnePercentOfImdToRecipient and test_buyExactOutput_sendsExactlyOnePercentOfImdToRecipient assert the two bases. Verified in test/scratch/Leads.t.sol test_feeBase_exactInputBuy.","severity":"info","snippet":"            uint256 fee = feeFor(_abs(params.amountSpecified));","title":"Fee base differs by swap shape: an exact-input buy pays 1% of the trader's IMD (1.0101% of the pool's IMD side), the unspecified shapes pay 1% of the pool's IMD"}],"hash":"519dcc6655e053dae4a66a991b07766a78b6a5e7ac89450e6833daa575d5496e","nodeId":"8653da2f-8d26-47ba-8a5d-dd02f3894678","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds six findings and a thirteen-row coverage record. No tracked file was changed; only the findings file and my scratch tests under `test/scratch/` exist, and both are gitignored.\n\n**What I found**\n\n- **Medium, one merged finding.** All five areas reported the same root cause and I merged them: when IMD is the specified currency, the hook charges 1% of the requested amount, fixed in beforeSwap, instead of 1% of the IMD the pool actually moved. Both specialist proofs fail on this tree for the stated reasons. A zero-fill exact-output sell on the launch pool's own seed shape takes 1 IMD from the seller for a 100 IMD request that moved nothing. A price-limited exact-input buy of 1,000,000 IMD fills about 50 IMD and pays 10,000 IMD of fee. I attached the write_foundry_tests proof, which fails with those exact numbers.\n- **Low.** The same request-based fee overflows the int128 cast for a sentinel-sized exact-input amount that a hookless pool fills to its price limit, so the hooked pool reverts instead of trading. Reproduced against a plain pool side by side.\n- **Low.** The direct take inside afterSwap ties every fee-bearing swap to IMD accepting a transfer to the recipient. Reproduced with a blocklisting mock: a blocked recipient reverts both buys and sells on a funded pool. I could not read the live chain, so the specialist's claim that IMD is an owner-controlled OFT with a blocklist is recorded as unverified.\n- **Info, three items.** The NatSpec and README overstate when direct payment happens. A dynamic-fee IMD pool is admitted with a permanent zero LP fee. The fee base differs between the specified and unspecified swap shapes. Each reproduced in a scratch test.\n\n**What I dropped**\n\nThe rounding-down note from audit_math is dust, documented in the README, and folded into the fee-base info item rather than reported on its own.\n\n**Coverage**\n\nAll six listed entry points are answered. The swap callbacks carry the finding; redeemPendingFees, unlockCallback and the three token functions hold. Extra rows cover beforeInitialize, the constructor and deployment model, the manifest, and three invariants from the brief, all of which hold. Contract sizes are well inside the limits and the manifest is schema-valid with real addresses in lowercase.","treeHash":null,"usage":{"cachedInputTokens":1200821,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":29245,"runtime":"claude","turns":28,"wallClockMs":394062}},{"artifacts":[],"attempt":1,"bundleHash":"bdbe5bcb3429bda84e5c96ab416cb02e44b66a8fa07cdbe596da6eef1afe0d6e","device":"790f4e545968ee92","findings":[],"hash":"6a594d81e84c0c4704bf310a530955c50461f914ed7bf4df42d90b92bb2148fb","nodeId":"20f2b2ff-d406-4423-bc63-c7d5d22ad93a","outcome":"completed","summary":"Manifest validates as JSON, the first key is `kind`, notes are 3600 characters, and only launch.json changed.\n\n**Outcome:** launch.json now describes the accepted RnnnFeeHook and RnnnToken as they exist in the tree. The build passes and all 57 Foundry tests pass.\n\n**What changed:** only the notes field. The previous manifest was written against an earlier revision of the hook and described behaviour the accepted code no longer has. It said partial fills charge 1% of the requested amount, said nothing about dynamic-fee refusal, and did not cover deferred payment when the IMD token refuses the transfer. The structural fields were already correct and are unchanged:\n\n- Hook contract RnnnFeeHook with constructor arguments `$poolManager`, the IMD address, and the fixed recipient, in declaration order. The hook takes no `$token`, `$owner` or `$factory` because it has no owner, finds IMD in the PoolKey, and does not restrict who initializes.\n- Permissions beforeInitialize, beforeSwap, afterSwap, beforeSwapReturnDelta and afterSwapReturnDelta, matching getHookPermissions and address bits 0x20cc.\n- Token RnnnToken, rnnn.bid, RNNN, 18 decimals. Pool paired with IMD at fee 12500, tick spacing 60, the reference initial price.\n\n**What the notes now say first:** the fee is paid inside the swap only when the PoolManager already holds enough IMD and the token accepts the transfer, so the first buys on the RNNN-only launch pool defer their fee as an ERC-6909 claim. Claims are flushed by the next swap or by the permissionless redeem function. The notes also disclose the swap-simulation gas cost on IMD-specified swaps, that other IMD pools can attach to the hook, and that the fee deliberately stays in IMD against the IMD default of settling in ETH.","treeHash":"d6224c877c309f8e7ac023081a1057b983503d4d","usage":{"cachedInputTokens":415013,"inputTokens":194,"model":"claude-fable-5-1","outputTokens":5970,"runtime":"claude","turns":14,"wallClockMs":84907}},{"artifacts":[],"attempt":1,"bundleHash":"d91cac0b39345b1c197fe5a0b773f04a81986a24c1c0484f6f68c3815ebd7266","device":"39da99ded7f125c8","findings":[],"hash":"6f14e97a7323324387ee57ca82fc1717f199ee2244abd01eef0943cb0dc5ed7c","nodeId":"808bc47c-e283-4899-9a69-f3c379dc86da","outcome":"completed","summary":"The build succeeds; the lines printed are forge lint warnings on pre-existing code, not compiler errors. The revision is complete.\n\n**What was wrong.** The rejected attempt failed at runtime because one of my accepted adversarial tests, the direct `beforeSwap` call from the manager's address, assumed the old view implementation. The hook was revised to simulate the swap inside `beforeSwap`, which needs the manager unlocked, so that call now reverts with `ManagerLocked`.\n\n**What I changed**, all in `test/RnnnFeeHookAdversarial.t.sol`:\n\n- **Direct `beforeSwap` from the manager** now asserts the real behaviour: with IMD specified it reverts with the manager's `ManagerLocked`, with RNNN specified it returns the zero delta and no LP fee override. Both leave no claim, no payout and no hook balance.\n- **Replaced the fee-overflow test**, which only reverted for the wrong reason, with two real-pool failure paths. An exact input above `int128` reverts whole on both a hooked and a plain pool and leaves nothing behind. An exact input of exactly `int128.max` fills in full, pays exactly 1% as a deferred claim backed by the settled input, and redeems to the recipient.\n\n**Verification.** `forge build` succeeds and `forge test` passes all 77 tests across 7 suites, including the two invariant suites with 96 runs each. No `.imd-findings.json` was written: reading the revised simulation path adversarially, I found no concrete failing input. The scratch probe was removed and only the one test file differs from the accepted tree.","treeHash":"8a11c4f9735f1f65563c7ed3e6f23385e8210163","usage":{"cachedInputTokens":1243122,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":21816,"runtime":"claude","turns":23,"wallClockMs":394358}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"92352b60f8e93ddd","findings":[],"hash":"728d01136ea79d89e4fc789e80eee2133e12f8e26896cf39b94f9a01e10d329c","nodeId":"808bc47c-e283-4899-9a69-f3c379dc86da","outcome":"failed","summary":"This content was flagged for possible cybersecurity risk. If this seems wrong, try rephrasing your request. If you’re doing authorized security work that requires more cyber permissive safeguards, apply for Daybreak access via https://platform.openai.com/settings/organization/status-and-access before retrying.","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":"gpt-6-astra","outputTokens":0,"runtime":"codex","turns":5,"wallClockMs":1027647}},{"artifacts":[],"attempt":1,"bundleHash":"9cd344d615fddacafc39a3ca2d348f0b3f0f91e4f4937ccf401328a5865f23a8","device":"7e64d4917fa814e6","findings":[],"hash":"7fbc0051f9a6248e2027b8e5048d56ba559b7ee30f18cf99d9d7aa0d464b5e52","nodeId":"350c4ef5-b650-4873-9b3d-9654dba05c40","outcome":"completed","summary":"All checks pass and every finding is answered. Nothing else is needed from the user.\n\n**Outcome**\n\n- The reviewer's proof reproduced on the starting tree (both tests failed with the reported messages) and now passes.\n- `forge build`, `forge test` (59 tests, 0 failures) and `forge fmt --check` pass with the pinned `solc = \"0.8.26\"`.\n- The protected hook floor suite was rehearsed against the rebuilt creation code with the launch's pool terms and passed 4 of 4.\n\n**What changed in `src/RnnnFeeHook.sol`**\n\n- **Short fills (medium finding, fixed).** For swaps where IMD is the specified currency, `beforeSwap` now simulates the swap before committing its delta. It calls a new self-only `simulateSwap`, which runs the pool swap and always reverts with the resulting delta, so nothing persists. v4-core skips a hook's own callbacks for its own swaps, so the simulation is a plain pool swap. A full fill still pays 1% of the request; a short fill pays 1% of what the pool moved, and a zero fill pays nothing. The fee passes from `beforeSwap` to `afterSwap` through transient storage, so the hook's delta always nets to zero and no refund path is needed. A refund-in-afterSwap design could not pass the zero-fill case because an empty manager holds no IMD to refund with.\n- **Huge exact input (low, fixed).** Amounts are computed in int256 and the only int128 cast is on 1% of the realised fill, which the pool already bounds. A test compares a 1e45 limited buy on hooked and hookless pools: identical fills.\n- **Blocked recipient (low, fixed).** I verified the claim on chain through Robinhood Chain's public RPC: the live IMD token answers `owner()`, `transfersEnabled()` and `blocked(address)`, and the recipient has code. The direct take now runs inside try/catch and falls back to minting a claim, so a blocked recipient defers fees instead of halting trading. A new test file covers it with a blocklisted stand-in token.\n- **Dynamic-fee pools (info, fixed).** `beforeInitialize` refuses the dynamic-fee flag; static fees including 12500 are accepted.\n- **Direct-payment wording (info, fixed).** NatSpec and README now state the real condition, and a test asserts the deferral of a large buy.\n- **Fee base (info, not reproducible as a defect).** Confirmed as intended and documented explicitly.\n\n**Other files**\n\n- Tests: 15 new tests across `test/RnnnFeeHook.t.sol` and the new `test/RnnnFeeHookBlocklist.t.sol`, plus a `BlocklistERC20` mock. One existing test was updated because a forged `afterSwap` without a preceding `beforeSwap` is now a no-op rather than a revert.\n- README: new \"How the fee is computed\" section, corrected payment conditions and IMD blocklist facts, dynamic-fee refusal, gas note, and the updated test inventory.\n- `.imd-responses.json` holds the six verdicts with details. It sits in the repository root and is excluded from git by the harness's own exclude file, as expected.\n\nNothing was committed, per the standing instruction to commit only when asked.","treeHash":"55502d61d3aa698ba8502df626e13c8830a2a510","usage":{"cachedInputTokens":1652198,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":77938,"runtime":"claude","turns":39,"wallClockMs":975560}},{"artifacts":[],"attempt":1,"bundleHash":"599269b020442096bbb41a652b8d72817f5c28fea2884286ecc5015aff955a37","device":"93652ca7a1cbb0e8","findings":[],"hash":"87b9d93ec5eeea73f5ac8876ecdef3fab92e6aa42cdf245a9dcf791957d5b172","nodeId":"20f2b2ff-d406-4423-bc63-c7d5d22ad93a","outcome":"completed","summary":"Created [launch.json](/root/.identitymd/work/0361d0fa-16da-4df6-a2c6-722dfab5e920/20f2b2ff-d406-4423-bc63-c7d5d22ad93a/launch.json), matching the contracts, constructor arguments, permissions, and required pool settings. No other deliverable files changed.\n\n`forge build` succeeded; all 42 tests passed.\n\nNotes document existing limitations: deferred IMD payments and fees based on requested amounts for IMD-specified partial fills.","treeHash":"d1d2306f6f08798f76b3154b530d80391ffcecf0","usage":{"cachedInputTokens":254080,"inputTokens":54416,"model":"gpt-6-astra","outputTokens":3612,"runtime":"codex","turns":3,"wallClockMs":100987}},{"artifacts":[],"attempt":1,"bundleHash":"94ec9d55a2f058b6d934a21f6c25736185227b755f405c323238aee92a93b03b","device":"ff35db055462d2ad","findings":[],"hash":"934f1f566a04d12cd0b0cf6099587c2dc801882219d5cdf07490fa9ad7c8950c","nodeId":"350c4ef5-b650-4873-9b3d-9654dba05c40","outcome":"completed","summary":"The deliverable is complete and committed on `main` as commit e2df7af.\n\n**What was built**\n\n- `src/RnnnToken.sol`: the rnnn.bid token, symbol RNNN, 18 decimals, one billion tokens minted once to the deployer. No owner, mint, pause, blocklist or fee.\n- `src/RnnnFeeHook.sol`: the Uniswap v4 hook with one rule. On every swap it takes 1% of the swap's IMD amount and transfers it as IMD to the fixed recipient given in the constructor as an immutable. No owner, no setter, no pause, no other fee, no LP fee override. Constructor arguments are the PoolManager, the IMD address and the fee recipient.\n- `src/HookFlags.sol` and `test/mocks/MockERC20.sol`: the helpers the protected floor suite imports.\n- `script/DeployRnnnFeeHook.s.sol`: a manual CREATE2 deployment path with salt mining, separate from the launch factory.\n- `README.md`: fee semantics per swap shape, deferred-claim behaviour, the Wizard configuration record, launch parameters, assumptions, and operational responsibilities.\n\n**Design decisions worth knowing**\n\n- IMD is taken on the specified side through `beforeSwapReturnDelta` for exact-input buys and exact-output sells, and on the unspecified side through `afterSwapReturnDelta` otherwise. All four swap shapes pay exactly 1%.\n- When the PoolManager already holds IMD, the recipient is paid inside the swap. On the first buys into a pool seeded with RNNN only, the manager has no IMD yet, so the hook mints itself an ERC-6909 claim instead of reverting the trade. The next swap that finds IMD flushes it, and anyone can call `redeemPendingFees` at any time.\n- `beforeInitialize` admits any pool containing IMD from any caller, so the launch factory can open the pool at fee 12500 and spacing 60.\n- The brief's instruction to pay the fee in IMD rather than ETH is followed literally, and the README states this departure from the IMD default.\n\n**Verification**\n\n| Check | Result |\n| --- | --- |\n| `forge build` (solc 0.8.26, bytecode_hash none) | passes |\n| `forge test`, empty environment | 42 passed, 0 failed |\n| `forge fmt --check` | passes |\n| Protected floor suite rehearsal (hook + token) | 11 passed, 0 failed |\n\nDependencies are vendored as plain files under `lib/` with no submodules. forge-std is taken from master rather than the v1.9.7 tag because the floor suite calls `fail(string)`, which the tag lacks.\n\n**Open items for the launch**\n\nSlither and Mythril were not run because they are not available here. The hook enables `beforeSwapReturnDelta`, which the v4 security guide rates critical, so an independent adversarial review before release is recommended. Explorer verification and the fork rehearsal of the live pool belong to the network's deployer.","treeHash":"463771e97df5ea78369e009403e9a0cad6a21409","usage":{"cachedInputTokens":2765443,"inputTokens":738,"model":"claude-fable-5-1","outputTokens":78215,"runtime":"claude","turns":46,"wallClockMs":1092486}},{"artifacts":[],"attempt":2,"bundleHash":"ffc871da315ad5d80e16658fd6aa40e8f2eaeb87a30cc4f01f95803c624ed7b7","device":"1507f63d3f1b973a","findings":[{"description":"When IMD is the swap's specified currency (exact-input buy, exact-output sell) the hook fixes the fee in beforeSwap as feeFor(|params.amountSpecified|) and afterSwap re-derives the same number from params.amountSpecified (line 192) instead of from the BalanceDelta the pool actually produced. The PoolManager then adds that fixed hook delta to the trader's delta whatever the pool filled. Two consequences. (1) A price-limited exact-input buy that fills only part of its input pays 1% of the whole request: with 1e24 full-range liquidity, an exact-input buy of 1,000,000 IMD limited to one tick fills about 50.6 IMD and pays 10,000 IMD of fee, roughly 200x the rule. (2) An exact-output sell into a region with no liquidity (the launch's own seed shape: RNNN-only liquidity above the price, so any sell at launch before the first buy) fills nothing, pays out no IMD, takes no RNNN, and still charges 1% of the requested output; because the trader's IMD delta becomes -fee, the seller pays 1 IMD to the recipient for a swap that moved nothing. The brief's rule is 1% of the swap amount and that the hook does not alter swaps in any other way; both cases break it. The README documents the partial-fill behaviour as a design choice, but the zero-fill sell is a trader paying IMD on a sell, which no reading of the rule allows. The swaps in question are trader-chosen (a price limit, or selling before the pool has IMD-side liquidity), so only the trader loses and no third party can extract value. Suggested fix: compute the fee in afterSwap from the IMD side of the actual BalanceDelta in every case; when IMD was specified, the beforeSwap pre-charge should be reconciled against the filled amount (refund the difference to the swapper via the hook's delta, or take the specified-side fee only up to 1% of what the pool actually filled).","line":174,"path":"src/RnnnFeeHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {PoolSwapTest} from \"v4-core/src/test/PoolSwapTest.sol\";\nimport {PoolModifyLiquidityTest} from \"v4-core/src/test/PoolModifyLiquidityTest.sol\";\n\nimport {RnnnFeeHook} from \"src/RnnnFeeHook.sol\";\nimport {RnnnToken} from \"src/RnnnToken.sol\";\n\n/// @dev Minimal ERC-20 standing in for IMD. Self-contained so the proof needs no other test file.\ncontract ProofERC20 {\n    string public constant name = \"IMD\";\n    string public constant symbol = \"IMD\";\n    uint8 public constant decimals = 18;\n    uint256 public totalSupply;\n    mapping(address => uint256) public balanceOf;\n    mapping(address => mapping(address => uint256)) public allowance;\n\n    constructor(uint256 supply) {\n        totalSupply = supply;\n        balanceOf[msg.sender] = supply;\n    }\n\n    function approve(address spender, uint256 amount) external returns (bool) {\n        allowance[msg.sender][spender] = amount;\n        return true;\n    }\n\n    function transfer(address to, uint256 amount) external returns (bool) {\n        balanceOf[msg.sender] -= amount;\n        balanceOf[to] += amount;\n        return true;\n    }\n\n    function transferFrom(address from, address to, uint256 amount) external returns (bool) {\n        if (allowance[from][msg.sender] != type(uint256).max) allowance[from][msg.sender] -= amount;\n        balanceOf[from] -= amount;\n        balanceOf[to] += amount;\n        return true;\n    }\n}\n\n/// @notice Proof: when IMD is the swap's specified currency (exact-input buy, exact-output sell) the\n/// hook charges 1% of the *requested* IMD amount even when the pool fills only part of it, or none\n/// of it. The brief's rule is 1% of the swap amount; a swap that traded 0 IMD must pay 0 fee, and a\n/// seller must never end up paying IMD.\ncontract PartialFillFeeProof is Test {\n    uint160 constant SQRT_PRICE_1_1 = 79228162514264337593543950336;\n    uint24 constant POOL_FEE = 12_500;\n    int24 constant TICK_SPACING = 60;\n    uint160 constant FLAGS = uint160(0x20CC);\n    address constant RECIPIENT = 0x45C89d0CF0B8a05218506236FcfcB18DA1D3ebE4;\n\n    PoolManager manager;\n    PoolSwapTest swapRouter;\n    PoolModifyLiquidityTest lpRouter;\n    ProofERC20 imd;\n    RnnnToken rnnn;\n    RnnnFeeHook hook;\n    PoolKey key;\n    bool rnnnIsZero;\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        swapRouter = new PoolSwapTest(manager);\n        lpRouter = new PoolModifyLiquidityTest(manager);\n        imd = new ProofERC20(1e30);\n        rnnn = new RnnnToken();\n\n        bytes memory code =\n            abi.encodePacked(type(RnnnFeeHook).creationCode, abi.encode(address(manager), address(imd), RECIPIENT));\n        bytes32 initHash = keccak256(code);\n        address at;\n        for (uint256 i = 0; i < 200_000; i++) {\n            address predicted = address(\n                uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), initHash))))\n            );\n            if (uint160(predicted) & uint160((1 << 14) - 1) != FLAGS) continue;\n            bytes32 salt = bytes32(i);\n            assembly (\"memory-safe\") {\n                at := create2(0, add(code, 0x20), mload(code), salt)\n            }\n            break;\n        }\n        require(at != address(0), \"hook deployment failed\");\n        hook = RnnnFeeHook(at);\n\n        rnnnIsZero = address(rnnn) < address(imd);\n        (Currency c0, Currency c1) = rnnnIsZero\n            ? (Currency.wrap(address(rnnn)), Currency.wrap(address(imd)))\n            : (Currency.wrap(address(imd)), Currency.wrap(address(rnnn)));\n        key = PoolKey({currency0: c0, currency1: c1, fee: POOL_FEE, tickSpacing: TICK_SPACING, hooks: IHooks(at)});\n        manager.initialize(key, SQRT_PRICE_1_1);\n\n        imd.approve(address(swapRouter), type(uint256).max);\n        imd.approve(address(lpRouter), type(uint256).max);\n        rnnn.approve(address(swapRouter), type(uint256).max);\n        rnnn.approve(address(lpRouter), type(uint256).max);\n    }\n\n    function _limit(bool zeroForOne) internal pure returns (uint160) {\n        return zeroForOne ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1;\n    }\n\n    /// @dev Liquidity only on the RNNN side of the price (the launch's seed shape). Selling RNNN\n    /// moves the price into a region with no liquidity: nothing fills, no IMD is paid out, yet the\n    /// hook charges 1% of the requested 100 IMD and the *seller* pays 1 IMD for nothing.\n    function test_exactOutputSellThatFillsNothing_mustChargeNoFee() public {\n        (int24 lower, int24 upper) = rnnnIsZero\n            ? (TICK_SPACING, TickMath.maxUsableTick(TICK_SPACING))\n            : (TickMath.minUsableTick(TICK_SPACING), -TICK_SPACING);\n        lpRouter.modifyLiquidity(key, ModifyLiquidityParams(lower, upper, 1e24, bytes32(0)), \"\");\n\n        uint256 traderImdBefore = imd.balanceOf(address(this));\n        uint256 traderRnnnBefore = rnnn.balanceOf(address(this));\n        uint256 want = 100 ether;\n\n        swapRouter.swap(\n            key, SwapParams(rnnnIsZero, int256(want), _limit(rnnnIsZero)), PoolSwapTest.TestSettings(false, false), \"\"\n        );\n\n        uint256 rnnnPaid = traderRnnnBefore - rnnn.balanceOf(address(this));\n        assertEq(rnnnPaid, 0, \"setup: the pool had nothing to fill, no RNNN was taken\");\n        uint256 feeTaken = imd.balanceOf(RECIPIENT) + hook.pendingFees();\n        // Expected: 0 (1% of the 0 IMD traded). Actual: 1 IMD.\n        assertEq(feeTaken, 0, \"fee must be 1% of the IMD actually traded, which is 0\");\n        // Expected: the seller's IMD is untouched. Actual: the seller paid 1 IMD.\n        assertGe(imd.balanceOf(address(this)), traderImdBefore, \"a sell made the trader pay IMD\");\n    }\n\n    /// @dev A funded pool and a price limit one tick away: the exact-input buy fills only a sliver,\n    /// yet the fee is 1% of the whole requested input (10,000 IMD of fee on about 50 IMD traded).\n    function test_exactInputBuyWithPriceLimit_feeMustBeOnePercentOfTheFill() public {\n        lpRouter.modifyLiquidity(\n            key,\n            ModifyLiquidityParams(\n                TickMath.minUsableTick(TICK_SPACING), TickMath.maxUsableTick(TICK_SPACING), 1e24, bytes32(0)\n            ),\n            \"\"\n        );\n        uint256 amountIn = 1_000_000 ether;\n        bool zeroForOne = !rnnnIsZero; // IMD in, RNNN out\n        uint160 limit = zeroForOne ? TickMath.getSqrtPriceAtTick(-1) : TickMath.getSqrtPriceAtTick(1);\n\n        uint256 traderImdBefore = imd.balanceOf(address(this));\n        uint256 managerImdBefore = imd.balanceOf(address(manager));\n        swapRouter.swap(\n            key, SwapParams(zeroForOne, -int256(amountIn), limit), PoolSwapTest.TestSettings(false, false), \"\"\n        );\n\n        uint256 feeTaken = imd.balanceOf(RECIPIENT) + hook.pendingFees();\n        uint256 imdIntoPool = imd.balanceOf(address(manager)) - managerImdBefore - hook.pendingFees();\n        uint256 traderPaid = traderImdBefore - imd.balanceOf(address(this));\n        assertEq(traderPaid, imdIntoPool + feeTaken);\n        assertLt(imdIntoPool, amountIn / 2, \"setup: the fill is partial\");\n        // Expected: 1% of the IMD the pool took. Actual: 1% of the 1,000,000 IMD requested.\n        assertEq(feeTaken, imdIntoPool / 100, \"fee must be 1% of the IMD the pool actually took\");\n    }\n}","reproduction":"Case A (zero fill, seller pays): deploy PoolManager, RnnnToken, an IMD ERC-20 and RnnnFeeHook(manager, imd, 0x45C89d0CF0B8a05218506236FcfcB18DA1D3ebE4) at a 0x20CC-flag address; initialize the RNNN/IMD pool at price 1:1, fee 12500, spacing 60; add 1e24 liquidity only on the RNNN side of the price (ticks [60, max] if RNNN is currency0, else [min, -60]); swap exact-output 100e18 IMD selling RNNN with the extreme price limit. Expected: 0 RNNN taken, 0 IMD paid out, fee 0, seller's IMD unchanged. Actual: 0 RNNN taken, fee 1e18 IMD credited to the hook (as a claim), seller's IMD balance falls by 1e18. Case B (partial fill): same setup with 1e24 full-range liquidity; swap exact-input 1_000_000e18 IMD for RNNN with sqrtPriceLimit one tick from the current price. Expected fee: 1% of the ~50.6e18 IMD the pool took (about 0.506e18). Actual fee: 10_000e18 IMD transferred to the recipient. Both are in the proof: forge test --match-path test/scratch/PartialFillFee.t.sol","severity":"medium","title":"Fee is 1% of the requested IMD amount, not of the IMD traded: partial and zero fills are overcharged, and a zero-fill sell makes the seller pay IMD"},{"description":"beforeInitialize admits any pool that contains IMD, including one whose fee field is the dynamic-fee flag (0x800000). The hook never calls updateDynamicLPFee and beforeSwap returns an lpFeeOverride of 0 without the override flag, so such a pool's LP fee is 0 and nothing can ever change it. This does not affect the launch pool (fee 12500) nor the 1% hook fee, which is charged on that pool as on any other. Noted because anyone may open such a pool on the hook; if that is unwanted, beforeInitialize could refuse the dynamic flag.","line":158,"path":"src/RnnnFeeHook.sol","reproduction":"Initialize PoolKey{currency0/1 = RNNN and IMD sorted, fee = 0x800000, tickSpacing = 60, hooks = RnnnFeeHook} at price 1:1: it succeeds; getSlot0 reports lpFee 0; add liquidity and swap: the LP fee stays 0 while the hook's 1% is still taken.","severity":"info","title":"A dynamic-fee pool opened on this hook has a zero LP fee forever"}],"hash":"c3350fe800de589e53172c34548532c18ebc3cd5b127df2f547f8e2460176e5a","nodeId":"808bc47c-e283-4899-9a69-f3c379dc86da","outcome":"completed","summary":"Done. The suite passes and one real defect is reported with a runnable proof.\n\n**Outcome**\n\n`forge build` and `forge test` pass: 61 tests across 6 suites, including two handler-based invariant suites at 96 runs by depth 48 with zero reverts, stable under five fixed fuzz seeds. Only files under `test/` were added; `git status --porcelain` shows nothing else.\n\n**What I added**\n\n- `test/handlers/RnnnFeeHookHandler.sol`: an invariant handler with three actors that drives all four swap shapes, liquidity adds and removes, donations, ERC-6909 claim gifts to the hook, and permissionless redemption, with ghost accounting and no asserts in the handler itself.\n- `test/RnnnFeeHook.invariant.t.sol`: ten invariants run over two pool shapes, a funded pool and the launch's RNNN-only seed where the first fees are deferred as claims. They check that every swap's fee is exactly 1% of the IMD side, that recipient balance plus pending claims equals everything owed, that claims are always backed by IMD in the manager, that the hook never holds anything but IMD claims, that the recipient never receives RNNN, that both tokens are conserved, and that no hook or pool parameter ever changes. Two fixed-sequence tests prove the handler really hits the deferral, flush and redeem paths.\n- `test/RnnnFeeHookAdversarial.t.sol`: 14 failure-path and edge tests, among them IMD on the other side of the pool key, hook data ignored, donations and liquidity changes paying nothing, gifted claims flowing only to the recipient, the branch where a flush cannot be afforded, callbacks driven from the manager outside a swap, a stranger's unlock not reaching the hook, fee rounding at the edges, and the int128 overflow revert.\n\n**Defect reported, not tested around**\n\nThe hook computes the fee from the requested amount when IMD is the specified currency, not from what the pool traded. Two concrete failures are in `.imd-findings.json` as a medium finding with a self-contained proof file that fails on this code:\n\n- An exact-output sell into the launch's seed shape fills nothing, takes no RNNN, and still charges 1 IMD, so the seller pays IMD on a sell.\n- A price-limited exact-input buy of 1,000,000 IMD filled about 50 IMD and paid 10,000 IMD of fee.\n\nAn info-level note covers dynamic-fee pools opened on the hook keeping a zero LP fee forever. The tests I delivered deliberately avoid asserting either behaviour.","treeHash":"67090bba7796f0c2dfd4e975d11738840c272837","usage":{"cachedInputTokens":6826979,"inputTokens":1346,"model":"claude-fable-5-1","outputTokens":110740,"runtime":"claude","turns":79,"wallClockMs":1821431}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"82692ab5f7f7e54e","findings":[{"citation":"resolved","description":"When IMD is the specified currency (exact-output sell: \"give me N IMD\"; exact-input buy: \"take N IMD\"), beforeSwap fixes the fee as feeFor(|amountSpecified|) and returns it as the specified-side delta, and afterSwap (line 192) books that same amount with _collect. The PoolManager then credits the hook 1% of N whatever the pool actually fills: Hooks.afterSwap subtracts the hook delta from the swapper's delta, and Pool.swap stops early, without reverting, when the price limit is reached or the liquidity in that direction runs out. So the fee is 1% of the request, not \"1% of the swap amount in IMD (the pool's IMD side)\", the hook's one rule. This is not an edge case for this launch: the launch pool is seeded with RNNN only, so for a long time the pool holds only the IMD that buyers have paid in. Any seller who asks for more IMD than the pool holds (an exact-output sell, the shape a wallet produces for \"sell for X IMD\") gets a short fill and pays 1% of X instead of 1% of the fill. In the extreme, a seller whose request the pool cannot serve at all (nothing on the IMD side yet) keeps all their RNNN, receives nothing, and still pays 1% of the request in IMD, which the hook mints as a pending claim for the recipient. The same happens on an exact-input buy whose sqrtPriceLimitX96 stops the swap early: the buyer pays 1% of the IMD they offered, not of the IMD the pool took. With the Universal Router, a fill below 1% of the request makes the seller's IMD delta negative and the router's TAKE reverts (the trade is blocked); fills between 1% and 100% go through and overcharge the seller within their slippage tolerance. The README acknowledges the partial-fill behaviour but calls it a price-limit corner; on a one-sided launch pool it is the ordinary liquidity-exhaustion case. Who loses: the trader, up to 1% of the requested amount minus 1% of the fill, per swap; the fixed recipient receives the difference. Fix (minimal, keeps the design): in afterSwap, when IMD was the specified currency, compare the pool's actual IMD delta with the amount it was asked to trade (|amountSpecified| + fee for exact output, |amountSpecified| - fee for exact input) and, when the fill is short, either refuse the swap (revert, so the trader resubmits as an exact-input sell / exact-output buy, which the afterSwap path charges exactly) or charge 1% of the actual IMD delta and return the excess credit to the swap's sender via poolManager.take(imd, sender, excess) with that behaviour documented. v4 gives no way to refund on the specified side through the return delta, so the choice is a design decision for the author; the proof accepts either.","line":174,"path":"src/RnnnFeeHook.sol","reproduction":"Setup: PoolManager, RnnnFeeHook(manager, IMD, 0x45C8...ebE4) at an address with flags 0x20CC, RNNN/IMD pool at fee 12500, spacing 60, sqrtPrice 2^96, liquidity 1e24 only on the RNNN side of the price (ticks [60, max] when RNNN is currency0), as the launch seeds it. Case A (short fill): 1) buy exact-input 100 IMD (pool now holds 97.7625 IMD of swappable reserve after the 1 IMD hook fee and the 1.25% LP fee); 2) redeemPendingFees(); 3) sell exact-output: SwapParams(zeroForOne = RNNN is currency0, amountSpecified = +1000e18, limit extreme). Observed: the manager pays out 97.7625 IMD in total; the recipient receives 10 IMD (feeFor(1000e18)); the seller receives 87.7625 IMD. Expected under the rule: fee = 1% of the 97.7625 IMD the pool moved = 0.977625 IMD; the seller receives ~96.78 IMD. Overcharge: 9.02 IMD on a 97.76 IMD trade (10.2% instead of 1%). Case B (no fill): on the fresh pool (manager holds 0 IMD), sell exact-output SwapParams(zeroForOne = RNNN is currency0, amountSpecified = +100e18, limit extreme). Observed: the swap succeeds, the seller's RNNN delta is 0, the seller's IMD delta is -1e18 (they pay 1 IMD), hook.pendingFees() == 1e18 and is paid to the recipient by the next redeemPendingFees(). Expected: no IMD moved, so fee 0 and the seller pays nothing. The proof test (test/scratch/PartialFillFee.t.sol) fails on the current code with \"fee is not 1% of the IMD the swap moved: 10000000000000000000 !~= 977624999999999999\" and \"a fee was booked on an unfilled swap: 1000000000000000000 != 0\".","severity":"medium","snippet":"            uint256 fee = feeFor(_abs(params.amountSpecified));\n            return (IHooks.beforeSwap.selector, toBeforeSwapDelta(fee.toInt128(), 0), 0);","title":"Fee on exact-output sells and exact-input buys is 1% of the amount requested, not of the IMD actually swapped: short fills are overcharged and an unfilled sell still pays"},{"citation":"resolved","description":"For IMD-specified swaps the fee is 1% of amountSpecified (the trader's IMD figure); for IMD-unspecified swaps it is 1% of the pool's IMD delta. Measured against the brief's wording, \"1% of the swap amount in IMD (the pool's IMD side)\", the two bases disagree by about 1% of the fee: an exact-input buy of 100 IMD leaves 99 IMD for the pool and pays 1 IMD (1.0101% of the pool side), while an exact-output buy that the pool prices at 99 IMD pays 0.99 IMD (1% of the pool side, 0.98% of the 99.99 IMD the trader hands over). A seller choosing exact-output pays 0.99% of the pool's payout instead of 1%. The README documents all four shapes, so this is a note on the definition rather than a defect; the author should confirm which base the brief means and, if it is the pool side, use feeFor(|amountSpecified|) * 100 / 101 for exact-input buys (so the fee is 1% of what reaches the pool) or leave as is and state it.","line":192,"path":"src/RnnnFeeHook.sol","reproduction":"Funded pool (1e24 full-range liquidity, price 1:1). Exact-input buy with amountSpecified = -100e18 on the IMD side: the pool receives 99e18 IMD, the recipient 1e18 (1.0101% of 99e18). Exact-output buy of 50e18 RNNN: the pool charges G IMD, the recipient gets G/100 (exactly 1% of the pool side). Both numbers come from the existing tests test_buyExactInput_sendsExactlyOnePercentOfImdToRecipient and test_buyExactOutput_sendsExactlyOnePercentOfImdToRecipient, which assert the two different bases.","severity":"info","snippet":"        if (_specifiedCurrency(key, params) == imd) {\n            fee = feeFor(_abs(params.amountSpecified));","title":"The fee base differs between the two legs of the same direction: exact-input buys and exact-output sells pay 1% of the trader's amount, the other two shapes pay 1% of the pool's amount"}],"hash":"d34cf6a0a3c1b12184ca7b6b0a2f5862f0cf77806cd58ca16934dbedc6b60336","nodeId":"b8c6aa5a-4c3a-46c4-baf8-13c3aaea8bdb","outcome":"completed","summary":"## Scope\n\nFiles read in full: `src/RnnnFeeHook.sol` (316 lines), `src/RnnnToken.sol`, `src/HookFlags.sol`, `script/DeployRnnnFeeHook.s.sol`, `test/RnnnFeeHook.t.sol`, `test/utils/HookDeployer.sol`, the protected hook suite, and the v4-core `Hooks.beforeSwap` / `Hooks.afterSwap` / `PoolManager.swap,take,mint,burn` paths the hook depends on. Area: Economic Security, Invariant and Flow Gap guides. The 42 project tests pass. Findings are in `.imd-findings.json` with a 14-row coverage record. Nothing in `src/` was changed.\n\n**Severity counts:** 0 Critical · 0 High · 1 Medium · 0 Low · 1 Info\n\n### [M-1] Fee is 1% of the amount requested, not of the IMD swapped: short fills overcharge, an unfilled sell still pays\n**Location:** `src/RnnnFeeHook.sol:174-175` (`beforeSwap`), booked again at line 192 (`afterSwap`)\n**Root cause:** When IMD is the specified currency, the fee is fixed from `amountSpecified` before the pool runs. The pool can stop short without reverting (price limit or liquidity exhausted), and the manager credits the hook the full 1% of the request anyway.\n**Why it matters here:** The launch pool is seeded with RNNN only, so the pool holds only the IMD buyers have paid in. A seller asking for more IMD than that gets a short fill and is overcharged.\n**Reproduction:** launch-shape pool, one buy of 100 IMD, then an exact-output sell for 1000 IMD.\n\n| quantity | value |\n|---|---|\n| IMD the pool paid out | 97.7625 |\n| fee taken | 10 (10.2%) |\n| fee under the rule | 0.9776 |\n\nOn the fresh pool, an exact-output sell for 100 IMD moves no RNNN, delivers nothing, and still charges the seller 1 IMD, which the hook books as a pending claim.\n**Proof:** `test/scratch/PartialFillFee.t.sol`, two tests. Both fail on the current code and pass on a scratch copy of the hook that refuses short fills. The proof also accepts a hook that charges 1% of the actual fill instead.\n**Fix:** in `afterSwap`, when IMD was specified, compare the pool's IMD delta with the amount it was asked to trade and either revert on a short fill or charge 1% of the actual delta and return the excess credit. Refunding on the specified side is not possible through the return delta, so the author must choose.\n\n### [I-1] Fee base differs between the two legs of each direction\n`src/RnnnFeeHook.sol:191-192`. IMD-specified swaps pay 1% of the trader's figure, IMD-unspecified swaps pay 1% of the pool's figure. The two bases differ by about 1% of the fee. The README documents all four shapes, so this is a definition note, not a defect.\n\n## Observations (non-blocking)\n- Direct fee payment draws on the manager's global IMD balance, which includes other pools' reserves. This is solvent because the swapper settles in the same unlock.\n- A router that interleaves sync, transfer, swap, settle would see its settlement shortened by the hook's take. No shipped router does this.\n- The hook accepts dynamic-fee pools but never sets their LP fee. This cannot affect the launch pool.\n\n## Coverage\nAll six listed entry points have a row. `afterSwap` is `finding`, the rest `holds`. Additional rows cover `beforeSwap` (`finding`), `beforeInitialize`, the constructor, and four invariants: hook delta nets to zero on every path, IMD conservation with backed claims, no RNNN ever taken, and the 1% rule (`finding`). Nothing was left unreached.","treeHash":null,"usage":{"cachedInputTokens":1310303,"inputTokens":322,"model":"claude-fable-5-1","outputTokens":49073,"runtime":"claude","turns":32,"wallClockMs":715265}}],"verification":[{"checks":[{"durationMs":2405,"exitCode":0,"name":"build","output":"Compiling 82 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.28s\nCompiler run successful!\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/RnnnFeeHook.sol:161:9\n    │\n161 │         poolManager.unlock(abi.encode(msg.sender));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/RnnnFeeHook.sol:239:44\n    │\n239 │         return (IHooks.afterSwap.selector, hookDeltaUnspecified);\n    │                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RnnnFeeHook.sol:378:13\n    │\n378 │             emit FeeTaken(key.toId(), fee, true);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RnnnFeeHook.sol:382:17\n    │\n382 │                 emit PendingFeesRedeemed(address(this), pending);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RnnnFeeHook.sol:386:13\n    │\n386 │             emit FeeTaken(key.toId(), fee, false);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/RnnnFeeHook.sol:338:40\n    │\n338 │             if (reason.length == 36 && bytes4(reason) == Simulated.selector) {\n    │                                        ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'bytes4' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RnnnFeeHook.sol:404:9\n    │\n404 │         emit PendingFeesRedeemed(caller, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/RnnnFeeHook.sol:426:28\n    │\n426 │         return value < 0 ? uint256(-value) : uint256(value);\n    │                            ━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/RnnnFeeHook.sol:426:46\n    │\n426 │         return value < 0 ? uint256(-value) : uint256(value);\n    │                                              ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\n","passed":true},{"durationMs":179,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 10 tests for test/RnnnToken.t.sol:RnnnTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 70520, ~: 70724)\n[PASS] test_approveAndTransferFrom() (gas: 246255)\n[PASS] test_infiniteAllowanceIsNotDecremented() (gas: 117105)\n[PASS] test_metadata() (gas: 23834)\n[PASS] test_mintsWholeSupplyToDeployer() (gas: 18392)\n[PASS] test_noAdminFunctionChangesSupply() (gas: 791195)\n[PASS] test_runtimeHasNoDelegatecallOrSelfdestruct() (gas: 510844)\n[PASS] test_transferMovesExactAmount() (gas: 75029)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 37019)\n[PASS] test_transferRevertsToZeroAddress() (gas: 29923)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 4.28ms (6.10ms CPU time)\n\nRan 2 tests for test/DeployRnnnFeeHook.t.sol:DeployRnnnFeeHookTest\n[PASS] test_deployLandsOnAnAddressWithTheDeclaredFlags() (gas: 13865674)\n[PASS] test_deployWithAnUnminedSaltReverts() (gas: 12339641)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 8.90ms (15.35ms CPU time)\n\nRan 2 tests for test/RnnnFeeHookBlocklist.t.sol:RnnnFeeHookBlocklistTest\n[PASS] test_blockedRecipient_laterSwapsDoNotFlushPendingClaims() (gas: 1041708)\n[PASS] test_blockedRecipient_swapsStillExecuteAndFeesAreDeferredUntilUnblocked() (gas: 790070)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 13.59ms (2.01ms CPU time)\n\nRan 43 tests for test/RnnnFeeHook.t.sol:RnnnFeeHookTest\n[PASS] testFuzz_buyExactInputWithLimit_feeIsOnePercentOfTheFill(uint256,uint24) (runs: 256, μ: 655896, ~: 655032)\n[PASS] testFuzz_buyExactInput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 655335, ~: 655246)\n[PASS] testFuzz_buyExactOutput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 593876, ~: 593682)\n[PASS] testFuzz_sellExactInput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 600735, ~: 601343)\n[PASS] testFuzz_sellExactOutputWithLimit_feeIsOnePercentOfThePayout(uint256,uint24) (runs: 256, μ: 643322, ~: 642652)\n[PASS] testFuzz_sellExactOutput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 632334, ~: 632198)\n[PASS] test_afterSwapOutsideAnUnlockReverts() (gas: 427943)\n[PASS] test_buyExactInputWithPriceLimitOnRnnnOnlyPool_feeIsOnePercentOfTheFillAndDeferred() (gas: 770446)\n[PASS] test_buyExactInputWithPriceLimit_feeIsOnePercentOfTheFill() (gas: 712997)\n[PASS] test_buyExactInput_sendsExactlyOnePercentOfImdToRecipient() (gas: 721390)\n[PASS] test_buyExactOutput_sendsExactlyOnePercentOfImdToRecipient() (gas: 648150)\n[PASS] test_callbacksRefuseCallersOtherThanThePoolManager() (gas: 222512)\n[PASS] test_constructorRejectsAnAddressWithoutTheRightFlags() (gas: 37566794)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 132401239)\n[PASS] test_exceededPriceLimitRevertsLikeAPoolWithoutAHook() (gas: 395895)\n[PASS] test_feeIsNeverTakenInRnnnAndLpFeeIsUntouched() (gas: 1183193)\n[PASS] test_firstBuyOnRnnnOnlyPool_defersFeeAsClaimThenAnyoneRedeemsIt() (gas: 868641)\n[PASS] test_firstExactOutputBuyOnRnnnOnlyPool_defersFeeAsClaim() (gas: 692306)\n[PASS] test_hugeExactInputBuyWithLimit_fillsLikeAPoolWithoutAHook() (gas: 1118024)\n[PASS] test_immutablesAreSetByTheConstructor() (gas: 42608)\n[PASS] test_initializeAcceptsAnyCallerSuchAsTheLaunchFactory() (gas: 628041)\n[PASS] test_initializeAcceptsImdOnEitherSideAndAnyListedFee() (gas: 629399)\n[PASS] test_initializeAcceptsTheLaunchPool() (gas: 14829)\n[PASS] test_initializeRefusesADynamicFeePool() (gas: 658101)\n[PASS] test_initializeRefusesANativeEthPoolWithoutImd() (gas: 42036)\n[PASS] test_initializeRefusesAPoolWithoutImd() (gas: 1151529)\n[PASS] test_largeBuyAfterTheFirstTrade_isDeferredWhenTheManagerHoldsLessThanTheFee() (gas: 1016578)\n[PASS] test_noPrivilegedFunctions() (gas: 783631)\n[PASS] test_pendingFeesAreFlushedByTheNextSwapThatFindsImd() (gas: 923561)\n[PASS] test_permissionsMatchTheAddressFlags() (gas: 11294)\n[PASS] test_redeemPendingFeesRevertsWhenNothingIsPending() (gas: 642529)\n[PASS] test_runtimeCodeHasNoEscapeHatch() (gas: 3429429)\n[PASS] test_secondImdPoolWithTheHookIsChargedTheSameWay() (gas: 1276887)\n[PASS] test_sellAfterTheFirstBuyPaysDirectly() (gas: 885802)\n[PASS] test_sellExactInputThatFillsNothing_takesNoFee() (gas: 772527)\n[PASS] test_sellExactInput_sendsExactlyOnePercentOfImdToRecipient() (gas: 650195)\n[PASS] test_sellExactOutputThatFillsNothing_takesNoFee() (gas: 1133086)\n[PASS] test_sellExactOutputWithOneSpacingLimit_feeIsOnePercentOfThePayout() (gas: 639754)\n[PASS] test_sellExactOutputWithPriceLimit_feeIsOnePercentOfThePayout() (gas: 707331)\n[PASS] test_sellExactOutput_sendsExactlyOnePercentOfImdToRecipient() (gas: 694033)\n[PASS] test_simulateSwapRefusesEveryoneButTheHook() (gas: 94542)\n[PASS] test_tinySwapWithZeroFeeStillSucceeds() (gas: 560854)\n[PASS] test_unusedCallbacksRevertEvenFromTheManager() (gas: 126150)\nSuite result: ok. 43 passed; 0 failed; 0 skipped; finished in 87.96ms (586.50ms CPU time)\n\nRan 4 test suites in 88.85ms (114.73ms CPU time): 57 tests passed, 0 failed, 0 skipped (57 total tests)\n","passed":true},{"durationMs":53,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"RnnnFeeHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"RnnnFeeHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"RnnnFeeHook.redeemPendingFees()\",\"RnnnFeeHook.simulateSwap((address,address,uint24,int24,address),(bool,int256,uint160))\",\"RnnnFeeHook.unlockCallback(bytes)\",\"RnnnToken.approve(address,uint256)\",\"RnnnToken.transfer(address,uint256)\",\"RnnnToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":255,\"foundry.toml\":15,\"launch.json\":31,\"remappings.txt\":3,\"script/DeployRnnnFeeHook.s.sol\":70,\"src/HookFlags.sol\":37,\"src/RnnnFeeHook.sol\":428,\"src/RnnnToken.sol\":67,\"test/DeployRnnnFeeHook.t.sol\":46,\"test/RnnnFeeHook.t.sol\":915,\"test/RnnnFeeHookBlocklist.t.sol\":125,\"test/RnnnToken.t.sol\":123,\"test/mocks/BlocklistERC20.sol\":21,\"test/mocks/MockERC20.sol\":60,\"test/utils/HookDeployer.sol\":45},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"6a594d81e84c0c4704bf310a530955c50461f914ed7bf4df42d90b92bb2148fb","verifiedTreeHash":"d6224c877c309f8e7ac023081a1057b983503d4d","verifierVersion":"0.1.0+fb8278c5"},{"checks":[{"durationMs":3705,"exitCode":0,"name":"build","output":"Compiling 87 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.56s\nCompiler run successful!\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/RnnnFeeHook.sol:161:9\n    │\n161 │         poolManager.unlock(abi.encode(msg.sender));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/RnnnFeeHook.sol:239:44\n    │\n239 │         return (IHooks.afterSwap.selector, hookDeltaUnspecified);\n    │                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RnnnFeeHook.sol:378:13\n    │\n378 │             emit FeeTaken(key.toId(), fee, true);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RnnnFeeHook.sol:382:17\n    │\n382 │                 emit PendingFeesRedeemed(address(this), pending);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RnnnFeeHook.sol:386:13\n    │\n386 │             emit FeeTaken(key.toId(), fee, false);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/RnnnFeeHook.sol:338:40\n    │\n338 │             if (reason.length == 36 && bytes4(reason) == Simulated.selector) {\n    │                                        ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'bytes4' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RnnnFeeHook.sol:404:9\n    │\n404 │         emit PendingFeesRedeemed(caller, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/RnnnFeeHook.sol:426:28\n    │\n426 │         return value < 0 ? uint256(-value) : uint256(value);\n    │                            ━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/RnnnFeeHook.sol:426:46\n    │\n426 │         return value < 0 ? uint256(-value) : uint256(value);\n    │                                              ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\n","passed":true},{"durationMs":2070,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 10 tests for test/RnnnToken.t.sol:RnnnTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 70742, ~: 71235)\n[PASS] test_approveAndTransferFrom() (gas: 246255)\n[PASS] test_infiniteAllowanceIsNotDecremented() (gas: 117105)\n[PASS] test_metadata() (gas: 23834)\n[PASS] test_mintsWholeSupplyToDeployer() (gas: 18392)\n[PASS] test_noAdminFunctionChangesSupply() (gas: 791195)\n[PASS] test_runtimeHasNoDelegatecallOrSelfdestruct() (gas: 510844)\n[PASS] test_transferMovesExactAmount() (gas: 75029)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 37019)\n[PASS] test_transferRevertsToZeroAddress() (gas: 29923)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 2.98ms (5.96ms CPU time)\n\nRan 2 tests for test/DeployRnnnFeeHook.t.sol:DeployRnnnFeeHookTest\n[PASS] test_deployLandsOnAnAddressWithTheDeclaredFlags() (gas: 13865674)\n[PASS] test_deployWithAnUnminedSaltReverts() (gas: 12339641)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 9.19ms (17.29ms CPU time)\n\nRan 2 tests for test/RnnnFeeHookBlocklist.t.sol:RnnnFeeHookBlocklistTest\n[PASS] test_blockedRecipient_laterSwapsDoNotFlushPendingClaims() (gas: 1041708)\n[PASS] test_blockedRecipient_swapsStillExecuteAndFeesAreDeferredUntilUnblocked() (gas: 790070)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 14.94ms (2.04ms CPU time)\n\nRan 15 tests for test/RnnnFeeHookAdversarial.t.sol:RnnnFeeHookAdversarialTest\n[PASS] testFuzz_feeForIsExactlyOnePercentRoundedDown(uint256) (runs: 1000, μ: 17617, ~: 17581)\n[PASS] test_beforeSwapFromTheManagerOutsideASwapMovesNothing() (gas: 411972)\n[PASS] test_claimsGiftedToTheHookGoToTheRecipientAndNowhereElse() (gas: 808772)\n[PASS] test_donationsAndLiquidityChangesPayNoFee() (gas: 777397)\n[PASS] test_exactInputBuyAboveInt128RevertsWholeAndLeavesNoFeeBehind() (gas: 1519871)\n[PASS] test_exactInputBuyOfInt128MaxFillsAndPaysExactlyOnePercent() (gas: 1298036)\n[PASS] test_feeForRoundsDownAndNeverOverflowsForPoolSizedAmounts() (gas: 28219)\n[PASS] test_feeIsOnePercentWhateverTheLpFeeAndTickSpacing() (gas: 636667)\n[PASS] test_feeIsOnePercentWhenImdIsOnTheOtherSideOfTheKey() (gas: 2482081)\n[PASS] test_flushIsSkippedWhenTheManagerCannotCoverBothFeeAndPending() (gas: 1453882)\n[PASS] test_hookDataIsIgnored() (gas: 581643)\n[PASS] test_strangerUnlockDoesNotReachTheHook() (gas: 908689)\n[PASS] test_swapperTakingClaimsInsteadOfTokensStillPaysTheFee() (gas: 612929)\n[PASS] test_swapsBelowOneHundredWeiOfImdPayNothingOnEveryShape() (gas: 1023892)\n[PASS] test_unlockCallbackFromTheManagerOutsideRedeemCannotPay() (gas: 674781)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 99.98ms (126.79ms CPU time)\n\nRan 43 tests for test/RnnnFeeHook.t.sol:RnnnFeeHookTest\n[PASS] testFuzz_buyExactInputWithLimit_feeIsOnePercentOfTheFill(uint256,uint24) (runs: 256, μ: 655706, ~: 655032)\n[PASS] testFuzz_buyExactInput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 655285, ~: 655065)\n[PASS] testFuzz_buyExactOutput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 593888, ~: 593686)\n[PASS] testFuzz_sellExactInput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 600017, ~: 601338)\n[PASS] testFuzz_sellExactOutputWithLimit_feeIsOnePercentOfThePayout(uint256,uint24) (runs: 256, μ: 643045, ~: 642638)\n[PASS] testFuzz_sellExactOutput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 632297, ~: 632014)\n[PASS] test_afterSwapOutsideAnUnlockReverts() (gas: 427943)\n[PASS] test_buyExactInputWithPriceLimitOnRnnnOnlyPool_feeIsOnePercentOfTheFillAndDeferred() (gas: 770446)\n[PASS] test_buyExactInputWithPriceLimit_feeIsOnePercentOfTheFill() (gas: 712997)\n[PASS] test_buyExactInput_sendsExactlyOnePercentOfImdToRecipient() (gas: 721390)\n[PASS] test_buyExactOutput_sendsExactlyOnePercentOfImdToRecipient() (gas: 648150)\n[PASS] test_callbacksRefuseCallersOtherThanThePoolManager() (gas: 222512)\n[PASS] test_constructorRejectsAnAddressWithoutTheRightFlags() (gas: 37566794)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 132401239)\n[PASS] test_exceededPriceLimitRevertsLikeAPoolWithoutAHook() (gas: 395895)\n[PASS] test_feeIsNeverTakenInRnnnAndLpFeeIsUntouched() (gas: 1183193)\n[PASS] test_firstBuyOnRnnnOnlyPool_defersFeeAsClaimThenAnyoneRedeemsIt() (gas: 868641)\n[PASS] test_firstExactOutputBuyOnRnnnOnlyPool_defersFeeAsClaim() (gas: 692306)\n[PASS] test_hugeExactInputBuyWithLimit_fillsLikeAPoolWithoutAHook() (gas: 1118024)\n[PASS] test_immutablesAreSetByTheConstructor() (gas: 42608)\n[PASS] test_initializeAcceptsAnyCallerSuchAsTheLaunchFactory() (gas: 628041)\n[PASS] test_initializeAcceptsImdOnEitherSideAndAnyListedFee() (gas: 629399)\n[PASS] test_initializeAcceptsTheLaunchPool() (gas: 14829)\n[PASS] test_initializeRefusesADynamicFeePool() (gas: 658101)\n[PASS] test_initializeRefusesANativeEthPoolWithoutImd() (gas: 42036)\n[PASS] test_initializeRefusesAPoolWithoutImd() (gas: 1151529)\n[PASS] test_largeBuyAfterTheFirstTrade_isDeferredWhenTheManagerHoldsLessThanTheFee() (gas: 1016578)\n[PASS] test_noPrivilegedFunctions() (gas: 783631)\n[PASS] test_pendingFeesAreFlushedByTheNextSwapThatFindsImd() (gas: 923561)\n[PASS] test_permissionsMatchTheAddressFlags() (gas: 11294)\n[PASS] test_redeemPendingFeesRevertsWhenNothingIsPending() (gas: 642529)\n[PASS] test_runtimeCodeHasNoEscapeHatch() (gas: 3429429)\n[PASS] test_secondImdPoolWithTheHookIsChargedTheSameWay() (gas: 1276887)\n[PASS] test_sellAfterTheFirstBuyPaysDirectly() (gas: 885802)\n[PASS] test_sellExactInputThatFillsNothing_takesNoFee() (gas: 772527)\n[PASS] test_sellExactInput_sendsExactlyOnePercentOfImdToRecipient() (gas: 650195)\n[PASS] test_sellExactOutputThatFillsNothing_takesNoFee() (gas: 1133086)\n[PASS] test_sellExactOutputWithOneSpacingLimit_feeIsOnePercentOfThePayout() (gas: 639754)\n[PASS] test_sellExactOutputWithPriceLimit_feeIsOnePercentOfThePayout() (gas: 707331)\n[PASS] test_sellExactOutput_sendsExactlyOnePercentOfImdToRecipient() (gas: 694033)\n[PASS] test_simulateSwapRefusesEveryoneButTheHook() (gas: 94542)\n[PASS] test_tinySwapWithZeroFeeStillSucceeds() (gas: 560854)\n[PASS] test_unusedCallbacksRevertEvenFromTheManager() (gas: 126150)\nSuite result: ok. 43 passed; 0 failed; 0 skipped; finished in 102.47ms (637.50ms CPU time)\n\nRan 2 tests for test/RnnnFeeHook.invariant.t.sol:RnnnFeeHookFundedPoolInvariantTest\n[PASS]\nRnnnFeeHookFundedPoolInvariantTest invariants:\n[PASS] invariant_everySwapFeeIsExactlyOnePercentOfTheImdSide\n[PASS] invariant_hookAndPoolParametersNeverChange\n[PASS] invariant_hookHoldsNothingButImdClaims\n[PASS] invariant_imdIsConserved\n[PASS] invariant_paidPlusDeferredMinusFlushedIsPending\n[PASS] invariant_pendingClaimsAreBackedByManagerImd\n[PASS] invariant_recipientNeverReceivesRnnn\n[PASS] invariant_recipientPlusPendingEqualsEverythingOwed\n[PASS] invariant_redeemPaysExactlyWhatItReports\n[PASS] invariant_rnnnIsConserved\n RnnnFeeHookFundedPoolInvariantTest invariants (runs: 96, calls: 4608, reverts: 0)\n\n╭--------------------+------------------+-------+---------+----------╮\n| Contract           | Selector         | Calls | Reverts | Discards |\n+====================================================================+\n| RnnnFeeHookHandler | addLiquidity     | 514   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | buyExactIn       | 513   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | buyExactOut      | 540   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | donate           | 516   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | giftClaimsToHook | 509   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | redeem           | 482   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | removeLiquidity  | 555   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | sellExactIn      | 477   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | sellExactOut     | 502   | 0       | 0        |\n╰--------------------+------------------+-------+---------+----------╯\n\n[PASS] test_handlerExercisesEveryActionOnAFundedPool() (gas: 2229399)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.95s (1.91s CPU time)\n\nRan 3 tests for test/RnnnFeeHook.invariant.t.sol:RnnnFeeHookFreshPoolInvariantTest\n[PASS]\nRnnnFeeHookFreshPoolInvariantTest invariants:\n[PASS] invariant_everySwapFeeIsExactlyOnePercentOfTheImdSide\n[PASS] invariant_hookAndPoolParametersNeverChange\n[PASS] invariant_hookHoldsNothingButImdClaims\n[PASS] invariant_imdIsConserved\n[PASS] invariant_paidPlusDeferredMinusFlushedIsPending\n[PASS] invariant_pendingClaimsAreBackedByManagerImd\n[PASS] invariant_recipientNeverReceivesRnnn\n[PASS] invariant_recipientPlusPendingEqualsEverythingOwed\n[PASS] invariant_redeemPaysExactlyWhatItReports\n[PASS] invariant_rnnnIsConserved\n RnnnFeeHookFreshPoolInvariantTest invariants (runs: 96, calls: 4608, reverts: 0)\n\n╭--------------------+------------------+-------+---------+----------╮\n| Contract           | Selector         | Calls | Reverts | Discards |\n+====================================================================+\n| RnnnFeeHookHandler | addLiquidity     | 514   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | buyExactIn       | 513   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | buyExactOut      | 540   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | donate           | 516   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | giftClaimsToHook | 509   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | redeem           | 482   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | removeLiquidity  | 555   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | sellExactIn      | 477   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | sellExactOut     | 502   | 0       | 0        |\n╰--------------------+------------------+-------+---------+----------╯\n\n[PASS] test_freshPoolStartsWithoutImd() (gas: 20851)\n[PASS] test_handlerExercisesDeferralFlushAndRedeemOnAFreshPool() (gas: 2049860)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.97s (1.94s CPU time)\n\nRan 7 test suites in 1.97s (4.15s CPU time): 77 tests passed, 0 failed, 0 skipped (77 total tests)\n","passed":true},{"durationMs":47,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"RnnnFeeHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"RnnnFeeHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"RnnnFeeHook.redeemPendingFees()\",\"RnnnFeeHook.simulateSwap((address,address,uint24,int24,address),(bool,int256,uint160))\",\"RnnnFeeHook.unlockCallback(bytes)\",\"RnnnToken.approve(address,uint256)\",\"RnnnToken.transfer(address,uint256)\",\"RnnnToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":255,\"foundry.toml\":15,\"remappings.txt\":3,\"script/DeployRnnnFeeHook.s.sol\":70,\"src/HookFlags.sol\":37,\"src/RnnnFeeHook.sol\":428,\"src/RnnnToken.sol\":67,\"test/DeployRnnnFeeHook.t.sol\":46,\"test/RnnnFeeHook.invariant.t.sol\":297,\"test/RnnnFeeHook.t.sol\":915,\"test/RnnnFeeHookAdversarial.t.sol\":475,\"test/RnnnFeeHookBlocklist.t.sol\":125,\"test/RnnnToken.t.sol\":123,\"test/handlers/RnnnFeeHookHandler.sol\":433,\"test/mocks/BlocklistERC20.sol\":21,\"test/mocks/MockERC20.sol\":60,\"test/utils/HookDeployer.sol\":45},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"6f14e97a7323324387ee57ca82fc1717f199ee2244abd01eef0943cb0dc5ed7c","verifiedTreeHash":"8a11c4f9735f1f65563c7ed3e6f23385e8210163","verifierVersion":"0.1.0+fb8278c5"},{"checks":[{"durationMs":2550,"exitCode":0,"name":"build","output":"Compiling 82 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.42s\nCompiler run successful!\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/RnnnFeeHook.sol:161:9\n    │\n161 │         poolManager.unlock(abi.encode(msg.sender));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/RnnnFeeHook.sol:239:44\n    │\n239 │         return (IHooks.afterSwap.selector, hookDeltaUnspecified);\n    │                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RnnnFeeHook.sol:378:13\n    │\n378 │             emit FeeTaken(key.toId(), fee, true);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RnnnFeeHook.sol:382:17\n    │\n382 │                 emit PendingFeesRedeemed(address(this), pending);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RnnnFeeHook.sol:386:13\n    │\n386 │             emit FeeTaken(key.toId(), fee, false);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/RnnnFeeHook.sol:338:40\n    │\n338 │             if (reason.length == 36 && bytes4(reason) == Simulated.selector) {\n    │                                        ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'bytes4' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RnnnFeeHook.sol:404:9\n    │\n404 │         emit PendingFeesRedeemed(caller, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/RnnnFeeHook.sol:426:28\n    │\n426 │         return value < 0 ? uint256(-value) : uint256(value);\n    │                            ━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/RnnnFeeHook.sol:426:46\n    │\n426 │         return value < 0 ? uint256(-value) : uint256(value);\n    │                                              ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\n","passed":true},{"durationMs":186,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 10 tests for test/RnnnToken.t.sol:RnnnTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 70287, ~: 70712)\n[PASS] test_approveAndTransferFrom() (gas: 246255)\n[PASS] test_infiniteAllowanceIsNotDecremented() (gas: 117105)\n[PASS] test_metadata() (gas: 23834)\n[PASS] test_mintsWholeSupplyToDeployer() (gas: 18392)\n[PASS] test_noAdminFunctionChangesSupply() (gas: 791195)\n[PASS] test_runtimeHasNoDelegatecallOrSelfdestruct() (gas: 510844)\n[PASS] test_transferMovesExactAmount() (gas: 75029)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 37019)\n[PASS] test_transferRevertsToZeroAddress() (gas: 29923)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 3.42ms (5.10ms CPU time)\n\nRan 2 tests for test/DeployRnnnFeeHook.t.sol:DeployRnnnFeeHookTest\n[PASS] test_deployLandsOnAnAddressWithTheDeclaredFlags() (gas: 13865674)\n[PASS] test_deployWithAnUnminedSaltReverts() (gas: 12339641)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 8.58ms (16.11ms CPU time)\n\nRan 2 tests for test/RnnnFeeHookBlocklist.t.sol:RnnnFeeHookBlocklistTest\n[PASS] test_blockedRecipient_laterSwapsDoNotFlushPendingClaims() (gas: 1041708)\n[PASS] test_blockedRecipient_swapsStillExecuteAndFeesAreDeferredUntilUnblocked() (gas: 790070)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 15.40ms (3.03ms CPU time)\n\nRan 43 tests for test/RnnnFeeHook.t.sol:RnnnFeeHookTest\n[PASS] testFuzz_buyExactInputWithLimit_feeIsOnePercentOfTheFill(uint256,uint24) (runs: 256, μ: 655903, ~: 655032)\n[PASS] testFuzz_buyExactInput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 655283, ~: 654966)\n[PASS] testFuzz_buyExactOutput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 593842, ~: 593679)\n[PASS] testFuzz_sellExactInput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 600122, ~: 601318)\n[PASS] testFuzz_sellExactOutputWithLimit_feeIsOnePercentOfThePayout(uint256,uint24) (runs: 256, μ: 643207, ~: 642640)\n[PASS] testFuzz_sellExactOutput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 632333, ~: 632177)\n[PASS] test_afterSwapOutsideAnUnlockReverts() (gas: 427943)\n[PASS] test_buyExactInputWithPriceLimitOnRnnnOnlyPool_feeIsOnePercentOfTheFillAndDeferred() (gas: 770446)\n[PASS] test_buyExactInputWithPriceLimit_feeIsOnePercentOfTheFill() (gas: 712997)\n[PASS] test_buyExactInput_sendsExactlyOnePercentOfImdToRecipient() (gas: 721390)\n[PASS] test_buyExactOutput_sendsExactlyOnePercentOfImdToRecipient() (gas: 648150)\n[PASS] test_callbacksRefuseCallersOtherThanThePoolManager() (gas: 222512)\n[PASS] test_constructorRejectsAnAddressWithoutTheRightFlags() (gas: 37566794)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 132401239)\n[PASS] test_exceededPriceLimitRevertsLikeAPoolWithoutAHook() (gas: 395895)\n[PASS] test_feeIsNeverTakenInRnnnAndLpFeeIsUntouched() (gas: 1183193)\n[PASS] test_firstBuyOnRnnnOnlyPool_defersFeeAsClaimThenAnyoneRedeemsIt() (gas: 868641)\n[PASS] test_firstExactOutputBuyOnRnnnOnlyPool_defersFeeAsClaim() (gas: 692306)\n[PASS] test_hugeExactInputBuyWithLimit_fillsLikeAPoolWithoutAHook() (gas: 1118024)\n[PASS] test_immutablesAreSetByTheConstructor() (gas: 42608)\n[PASS] test_initializeAcceptsAnyCallerSuchAsTheLaunchFactory() (gas: 628041)\n[PASS] test_initializeAcceptsImdOnEitherSideAndAnyListedFee() (gas: 629399)\n[PASS] test_initializeAcceptsTheLaunchPool() (gas: 14829)\n[PASS] test_initializeRefusesADynamicFeePool() (gas: 658101)\n[PASS] test_initializeRefusesANativeEthPoolWithoutImd() (gas: 42036)\n[PASS] test_initializeRefusesAPoolWithoutImd() (gas: 1151529)\n[PASS] test_largeBuyAfterTheFirstTrade_isDeferredWhenTheManagerHoldsLessThanTheFee() (gas: 1016578)\n[PASS] test_noPrivilegedFunctions() (gas: 783631)\n[PASS] test_pendingFeesAreFlushedByTheNextSwapThatFindsImd() (gas: 923561)\n[PASS] test_permissionsMatchTheAddressFlags() (gas: 11294)\n[PASS] test_redeemPendingFeesRevertsWhenNothingIsPending() (gas: 642529)\n[PASS] test_runtimeCodeHasNoEscapeHatch() (gas: 3429429)\n[PASS] test_secondImdPoolWithTheHookIsChargedTheSameWay() (gas: 1276887)\n[PASS] test_sellAfterTheFirstBuyPaysDirectly() (gas: 885802)\n[PASS] test_sellExactInputThatFillsNothing_takesNoFee() (gas: 772527)\n[PASS] test_sellExactInput_sendsExactlyOnePercentOfImdToRecipient() (gas: 650195)\n[PASS] test_sellExactOutputThatFillsNothing_takesNoFee() (gas: 1133086)\n[PASS] test_sellExactOutputWithOneSpacingLimit_feeIsOnePercentOfThePayout() (gas: 639754)\n[PASS] test_sellExactOutputWithPriceLimit_feeIsOnePercentOfThePayout() (gas: 707331)\n[PASS] test_sellExactOutput_sendsExactlyOnePercentOfImdToRecipient() (gas: 694033)\n[PASS] test_simulateSwapRefusesEveryoneButTheHook() (gas: 94542)\n[PASS] test_tinySwapWithZeroFeeStillSucceeds() (gas: 560854)\n[PASS] test_unusedCallbacksRevertEvenFromTheManager() (gas: 126150)\nSuite result: ok. 43 passed; 0 failed; 0 skipped; finished in 93.72ms (630.40ms CPU time)\n\nRan 4 test suites in 94.95ms (121.12ms CPU time): 57 tests passed, 0 failed, 0 skipped (57 total tests)\n","passed":true},{"durationMs":39,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"RnnnFeeHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"RnnnFeeHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"RnnnFeeHook.redeemPendingFees()\",\"RnnnFeeHook.simulateSwap((address,address,uint24,int24,address),(bool,int256,uint160))\",\"RnnnFeeHook.unlockCallback(bytes)\",\"RnnnToken.approve(address,uint256)\",\"RnnnToken.transfer(address,uint256)\",\"RnnnToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":255,\"foundry.toml\":15,\"remappings.txt\":3,\"script/DeployRnnnFeeHook.s.sol\":70,\"src/HookFlags.sol\":37,\"src/RnnnFeeHook.sol\":428,\"src/RnnnToken.sol\":67,\"test/DeployRnnnFeeHook.t.sol\":46,\"test/RnnnFeeHook.t.sol\":915,\"test/RnnnFeeHookBlocklist.t.sol\":125,\"test/RnnnToken.t.sol\":123,\"test/mocks/BlocklistERC20.sol\":21,\"test/mocks/MockERC20.sol\":60,\"test/utils/HookDeployer.sol\":45},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":933,"exitCode":0,"name":"slither","output":"[medium/medium] uninitialized-local at src/RnnnFeeHook.sol:231: RnnnFeeHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes).hookDeltaUnspecified (src/RnnnFeeHook.sol#231) is a local variable never initialized\n[medium/medium] unused-return at src/RnnnFeeHook.sol:158: RnnnFeeHook.redeemPendingFees() (src/RnnnFeeHook.sol#158-162) ignores return value by poolManager.unlock(abi.encode(msg.sender)) (src/RnnnFeeHook.sol#161)\n[low/medium] reentrancy-events at src/RnnnFeeHook.sol:375: Reentrancy in RnnnFeeHook._collect(PoolKey,uint256) (src/RnnnFeeHook.sol#375-388):\n[low/medium] reentrancy-events at src/RnnnFeeHook.sol:401: Reentrancy in RnnnFeeHook._payPending(uint256,address) (src/RnnnFeeHook.sol#401-405):\n[low/medium] reentrancy-events at src/RnnnFeeHook.sol:375: Reentrancy in RnnnFeeHook._collect(PoolKey,uint256) (src/RnnnFeeHook.sol#375-388):\n[low/medium] reentrancy-events at src/RnnnFeeHook.sol:375: Reentrancy in RnnnFeeHook._collect(PoolKey,uint256) (src/RnnnFeeHook.sol#375-388):","passed":true},{"durationMs":326,"exitCode":0,"name":"aderyn","output":"[low] internal-function-used-once at src/HookFlags.sol:29: Internal Function Used Only Once\n[low] large-numeric-literal at src/RnnnFeeHook.sol:60: Large Numeric Literal (2 places)\n[low] missing-inheritance at src/RnnnToken.sol:9: Missing Inheritance\n[low] unchecked-return at src/RnnnFeeHook.sol:161: Unchecked Return","passed":true},{"durationMs":2053,"exitCode":0,"name":"proof 1ab0a35bbe3f","output":"Compiling 77 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.46s\nCompiler run successful!\n\nRan 2 tests for test/imd-proof-ae085a08/Proof_1ab0a35bbe3f.t.sol:PartialFillFeeProof\n[PASS] test_exactInputBuyWithPriceLimit_feeMustBeOnePercentOfTheFill() (gas: 625839)\n[PASS] test_exactOutputSellThatFillsNothing_mustChargeNoFee() (gas: 1105951)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 14.83ms (3.22ms CPU time)\n\nRan 1 test suite in 15.91ms (14.83ms CPU time): 2 tests passed, 0 failed, 0 skipped (2 total tests)\n","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"7fbc0051f9a6248e2027b8e5048d56ba559b7ee30f18cf99d9d7aa0d464b5e52","verifiedTreeHash":"55502d61d3aa698ba8502df626e13c8830a2a510","verifierVersion":"0.1.0+fb8278c5"},{"checks":[{"durationMs":2011,"exitCode":0,"name":"build","output":"Compiling 80 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.88s\nCompiler run successful!\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/RnnnFeeHook.sol:137:9\n    │\n137 │         poolManager.unlock(abi.encode(msg.sender));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/RnnnFeeHook.sol:199:44\n    │\n199 │         return (IHooks.afterSwap.selector, hookDeltaUnspecified);\n    │                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RnnnFeeHook.sol:283:13\n    │\n283 │             emit FeeTaken(key.toId(), fee, true);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RnnnFeeHook.sol:296:9\n    │\n296 │         emit PendingFeesRedeemed(caller, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RnnnFeeHook.sol:288:13\n    │\n288 │             emit FeeTaken(key.toId(), fee, false);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/RnnnFeeHook.sol:313:28\n    │\n313 │         return value < 0 ? uint256(-value) : uint256(value);\n    │                            ━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/RnnnFeeHook.sol:313:46\n    │\n313 │         return value < 0 ? uint256(-value) : uint256(value);\n    │                                              ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\n","passed":true},{"durationMs":171,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/DeployRnnnFeeHook.t.sol:DeployRnnnFeeHookTest\n[PASS] test_deployLandsOnAnAddressWithTheDeclaredFlags() (gas: 64591662)\n[PASS] test_deployWithAnUnminedSaltReverts() (gas: 63397263)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 41.92ms (79.65ms CPU time)\n\nRan 30 tests for test/RnnnFeeHook.t.sol:RnnnFeeHookTest\n[PASS] testFuzz_buyExactInput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 598161, ~: 598220)\n[PASS] testFuzz_buyExactOutput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 593537, ~: 593384)\n[PASS] testFuzz_sellExactInput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 599577, ~: 601053)\n[PASS] testFuzz_sellExactOutput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 581062, ~: 580911)\n[PASS] test_afterSwapOutsideAnUnlockReverts() (gas: 357905)\n[PASS] test_buyExactInput_sendsExactlyOnePercentOfImdToRecipient() (gas: 664157)\n[PASS] test_buyExactOutput_sendsExactlyOnePercentOfImdToRecipient() (gas: 647814)\n[PASS] test_callbacksRefuseCallersOtherThanThePoolManager() (gas: 211507)\n[PASS] test_constructorRejectsAnAddressWithoutTheRightFlags() (gas: 114411144)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 201468118)\n[PASS] test_feeIsNeverTakenInRnnnAndLpFeeIsUntouched() (gas: 1091097)\n[PASS] test_firstBuyOnRnnnOnlyPool_defersFeeAsClaimThenAnyoneRedeemsIt() (gas: 778164)\n[PASS] test_firstExactOutputBuyOnRnnnOnlyPool_defersFeeAsClaim() (gas: 692054)\n[PASS] test_immutablesAreSetByTheConstructor() (gas: 42542)\n[PASS] test_initializeAcceptsAnyCallerSuchAsTheLaunchFactory() (gas: 627810)\n[PASS] test_initializeAcceptsImdOnEitherSideAndAnyListedFee() (gas: 629213)\n[PASS] test_initializeAcceptsTheLaunchPool() (gas: 14784)\n[PASS] test_initializeRefusesANativeEthPoolWithoutImd() (gas: 42080)\n[PASS] test_initializeRefusesAPoolWithoutImd() (gas: 1151484)\n[PASS] test_noPrivilegedFunctions() (gas: 783385)\n[PASS] test_pendingFeesAreFlushedByTheNextSwapThatFindsImd() (gas: 799525)\n[PASS] test_permissionsMatchTheAddressFlags() (gas: 11271)\n[PASS] test_redeemPendingFeesRevertsWhenNothingIsPending() (gas: 585245)\n[PASS] test_runtimeCodeHasNoEscapeHatch() (gas: 2507013)\n[PASS] test_secondImdPoolWithTheHookIsChargedTheSameWay() (gas: 1225937)\n[PASS] test_sellAfterTheFirstBuyPaysDirectly() (gas: 794901)\n[PASS] test_sellExactInput_sendsExactlyOnePercentOfImdToRecipient() (gas: 649882)\n[PASS] test_sellExactOutput_sendsExactlyOnePercentOfImdToRecipient() (gas: 642767)\n[PASS] test_tinySwapWithZeroFeeStillSucceeds() (gas: 503862)\n[PASS] test_unusedCallbacksRevertEvenFromTheManager() (gas: 126193)\nSuite result: ok. 30 passed; 0 failed; 0 skipped; finished in 78.04ms (384.08ms CPU time)\n\nRan 10 tests for test/RnnnToken.t.sol:RnnnTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 70660, ~: 70700)\n[PASS] test_approveAndTransferFrom() (gas: 246255)\n[PASS] test_infiniteAllowanceIsNotDecremented() (gas: 117105)\n[PASS] test_metadata() (gas: 23834)\n[PASS] test_mintsWholeSupplyToDeployer() (gas: 18392)\n[PASS] test_noAdminFunctionChangesSupply() (gas: 791195)\n[PASS] test_runtimeHasNoDelegatecallOrSelfdestruct() (gas: 510844)\n[PASS] test_transferMovesExactAmount() (gas: 75029)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 37019)\n[PASS] test_transferRevertsToZeroAddress() (gas: 29923)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 78.00ms (6.14ms CPU time)\n\nRan 3 test suites in 78.58ms (197.96ms CPU time): 42 tests passed, 0 failed, 0 skipped (42 total tests)\n","passed":true},{"durationMs":49,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"RnnnFeeHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"RnnnFeeHook.redeemPendingFees()\",\"RnnnFeeHook.unlockCallback(bytes)\",\"RnnnToken.approve(address,uint256)\",\"RnnnToken.transfer(address,uint256)\",\"RnnnToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":196,\"foundry.toml\":15,\"launch.json\":31,\"remappings.txt\":3,\"script/DeployRnnnFeeHook.s.sol\":70,\"src/HookFlags.sol\":37,\"src/RnnnFeeHook.sol\":315,\"src/RnnnToken.sol\":67,\"test/DeployRnnnFeeHook.t.sol\":46,\"test/RnnnFeeHook.t.sol\":659,\"test/RnnnToken.t.sol\":123,\"test/mocks/MockERC20.sol\":60,\"test/utils/HookDeployer.sol\":45},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"87b9d93ec5eeea73f5ac8876ecdef3fab92e6aa42cdf245a9dcf791957d5b172","verifiedTreeHash":"d1d2306f6f08798f76b3154b530d80391ffcecf0","verifierVersion":"0.1.0+fb8278c5"},{"checks":[{"durationMs":2082,"exitCode":0,"name":"build","output":"Compiling 80 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.96s\nCompiler run successful!\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/RnnnFeeHook.sol:137:9\n    │\n137 │         poolManager.unlock(abi.encode(msg.sender));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/RnnnFeeHook.sol:199:44\n    │\n199 │         return (IHooks.afterSwap.selector, hookDeltaUnspecified);\n    │                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RnnnFeeHook.sol:283:13\n    │\n283 │             emit FeeTaken(key.toId(), fee, true);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RnnnFeeHook.sol:296:9\n    │\n296 │         emit PendingFeesRedeemed(caller, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RnnnFeeHook.sol:288:13\n    │\n288 │             emit FeeTaken(key.toId(), fee, false);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/RnnnFeeHook.sol:313:28\n    │\n313 │         return value < 0 ? uint256(-value) : uint256(value);\n    │                            ━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/RnnnFeeHook.sol:313:46\n    │\n313 │         return value < 0 ? uint256(-value) : uint256(value);\n    │                                              ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\n","passed":true},{"durationMs":169,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 2 tests for test/DeployRnnnFeeHook.t.sol:DeployRnnnFeeHookTest\n[PASS] test_deployLandsOnAnAddressWithTheDeclaredFlags() (gas: 64591662)\n[PASS] test_deployWithAnUnminedSaltReverts() (gas: 63397263)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 43.16ms (84.18ms CPU time)\n\nRan 10 tests for test/RnnnToken.t.sol:RnnnTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 70444, ~: 70712)\n[PASS] test_approveAndTransferFrom() (gas: 246255)\n[PASS] test_infiniteAllowanceIsNotDecremented() (gas: 117105)\n[PASS] test_metadata() (gas: 23834)\n[PASS] test_mintsWholeSupplyToDeployer() (gas: 18392)\n[PASS] test_noAdminFunctionChangesSupply() (gas: 791195)\n[PASS] test_runtimeHasNoDelegatecallOrSelfdestruct() (gas: 510844)\n[PASS] test_transferMovesExactAmount() (gas: 75029)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 37019)\n[PASS] test_transferRevertsToZeroAddress() (gas: 29923)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 60.76ms (63.91ms CPU time)\n\nRan 30 tests for test/RnnnFeeHook.t.sol:RnnnFeeHookTest\n[PASS] testFuzz_buyExactInput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 598143, ~: 598073)\n[PASS] testFuzz_buyExactOutput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 593551, ~: 593514)\n[PASS] testFuzz_sellExactInput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 599319, ~: 601412)\n[PASS] testFuzz_sellExactOutput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 581084, ~: 581332)\n[PASS] test_afterSwapOutsideAnUnlockReverts() (gas: 357905)\n[PASS] test_buyExactInput_sendsExactlyOnePercentOfImdToRecipient() (gas: 664157)\n[PASS] test_buyExactOutput_sendsExactlyOnePercentOfImdToRecipient() (gas: 647814)\n[PASS] test_callbacksRefuseCallersOtherThanThePoolManager() (gas: 211507)\n[PASS] test_constructorRejectsAnAddressWithoutTheRightFlags() (gas: 114411144)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 201468118)\n[PASS] test_feeIsNeverTakenInRnnnAndLpFeeIsUntouched() (gas: 1091097)\n[PASS] test_firstBuyOnRnnnOnlyPool_defersFeeAsClaimThenAnyoneRedeemsIt() (gas: 778164)\n[PASS] test_firstExactOutputBuyOnRnnnOnlyPool_defersFeeAsClaim() (gas: 692054)\n[PASS] test_immutablesAreSetByTheConstructor() (gas: 42542)\n[PASS] test_initializeAcceptsAnyCallerSuchAsTheLaunchFactory() (gas: 627810)\n[PASS] test_initializeAcceptsImdOnEitherSideAndAnyListedFee() (gas: 629213)\n[PASS] test_initializeAcceptsTheLaunchPool() (gas: 14784)\n[PASS] test_initializeRefusesANativeEthPoolWithoutImd() (gas: 42080)\n[PASS] test_initializeRefusesAPoolWithoutImd() (gas: 1151484)\n[PASS] test_noPrivilegedFunctions() (gas: 783385)\n[PASS] test_pendingFeesAreFlushedByTheNextSwapThatFindsImd() (gas: 799525)\n[PASS] test_permissionsMatchTheAddressFlags() (gas: 11271)\n[PASS] test_redeemPendingFeesRevertsWhenNothingIsPending() (gas: 585245)\n[PASS] test_runtimeCodeHasNoEscapeHatch() (gas: 2507013)\n[PASS] test_secondImdPoolWithTheHookIsChargedTheSameWay() (gas: 1225937)\n[PASS] test_sellAfterTheFirstBuyPaysDirectly() (gas: 794901)\n[PASS] test_sellExactInput_sendsExactlyOnePercentOfImdToRecipient() (gas: 649882)\n[PASS] test_sellExactOutput_sendsExactlyOnePercentOfImdToRecipient() (gas: 642767)\n[PASS] test_tinySwapWithZeroFeeStillSucceeds() (gas: 503862)\n[PASS] test_unusedCallbacksRevertEvenFromTheManager() (gas: 126193)\nSuite result: ok. 30 passed; 0 failed; 0 skipped; finished in 76.86ms (398.67ms CPU time)\n\nRan 3 test suites in 77.75ms (180.78ms CPU time): 42 tests passed, 0 failed, 0 skipped (42 total tests)\n","passed":true},{"durationMs":40,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"RnnnFeeHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"RnnnFeeHook.redeemPendingFees()\",\"RnnnFeeHook.unlockCallback(bytes)\",\"RnnnToken.approve(address,uint256)\",\"RnnnToken.transfer(address,uint256)\",\"RnnnToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":196,\"foundry.toml\":15,\"remappings.txt\":3,\"script/DeployRnnnFeeHook.s.sol\":70,\"src/HookFlags.sol\":37,\"src/RnnnFeeHook.sol\":315,\"src/RnnnToken.sol\":67,\"test/DeployRnnnFeeHook.t.sol\":46,\"test/RnnnFeeHook.t.sol\":659,\"test/RnnnToken.t.sol\":123,\"test/mocks/MockERC20.sol\":60,\"test/utils/HookDeployer.sol\":45},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":805,"exitCode":0,"name":"slither","output":"[medium/medium] uninitialized-local at src/RnnnFeeHook.sol:190: RnnnFeeHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes).hookDeltaUnspecified (src/RnnnFeeHook.sol#190) is a local variable never initialized\n[medium/medium] unused-return at src/RnnnFeeHook.sol:134: RnnnFeeHook.redeemPendingFees() (src/RnnnFeeHook.sol#134-138) ignores return value by poolManager.unlock(abi.encode(msg.sender)) (src/RnnnFeeHook.sol#137)\n[low/medium] reentrancy-events at src/RnnnFeeHook.sol:279: Reentrancy in RnnnFeeHook._collect(PoolKey,uint256) (src/RnnnFeeHook.sol#279-290):\n[low/medium] reentrancy-events at src/RnnnFeeHook.sol:279: Reentrancy in RnnnFeeHook._collect(PoolKey,uint256) (src/RnnnFeeHook.sol#279-290):\n[low/medium] reentrancy-events at src/RnnnFeeHook.sol:293: Reentrancy in RnnnFeeHook._payPending(uint256,address) (src/RnnnFeeHook.sol#293-297):\n[low/medium] reentrancy-events at src/RnnnFeeHook.sol:279: Reentrancy in RnnnFeeHook._collect(PoolKey,uint256) (src/RnnnFeeHook.sol#279-290):","passed":true},{"durationMs":220,"exitCode":0,"name":"aderyn","output":"[low] internal-function-used-once at src/HookFlags.sol:29: Internal Function Used Only Once\n[low] large-numeric-literal at src/RnnnFeeHook.sol:45: Large Numeric Literal (2 places)\n[low] missing-inheritance at src/RnnnToken.sol:9: Missing Inheritance\n[low] unchecked-return at src/RnnnFeeHook.sol:137: Unchecked Return","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"934f1f566a04d12cd0b0cf6099587c2dc801882219d5cdf07490fa9ad7c8950c","verifiedTreeHash":"463771e97df5ea78369e009403e9a0cad6a21409","verifierVersion":"0.1.0+fb8278c5"},{"checks":[{"durationMs":3301,"exitCode":0,"name":"build","output":"Compiling 85 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.18s\nCompiler run successful!\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/RnnnFeeHook.sol:137:9\n    │\n137 │         poolManager.unlock(abi.encode(msg.sender));\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/RnnnFeeHook.sol:199:44\n    │\n199 │         return (IHooks.afterSwap.selector, hookDeltaUnspecified);\n    │                                            ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RnnnFeeHook.sol:283:13\n    │\n283 │             emit FeeTaken(key.toId(), fee, true);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RnnnFeeHook.sol:296:9\n    │\n296 │         emit PendingFeesRedeemed(caller, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RnnnFeeHook.sol:288:13\n    │\n288 │             emit FeeTaken(key.toId(), fee, false);\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/RnnnFeeHook.sol:313:28\n    │\n313 │         return value < 0 ? uint256(-value) : uint256(value);\n    │                            ━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/RnnnFeeHook.sol:313:46\n    │\n313 │         return value < 0 ? uint256(-value) : uint256(value);\n    │                                              ━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\n","passed":true},{"durationMs":1839,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 14 tests for test/RnnnFeeHookAdversarial.t.sol:RnnnFeeHookAdversarialTest\n[PASS] testFuzz_feeForIsExactlyOnePercentRoundedDown(uint256) (runs: 1000, μ: 17394, ~: 17361)\n[PASS] test_beforeSwapFromTheManagerMovesNothing() (gas: 40198)\n[PASS] test_beforeSwapRevertsOnAnAmountWhoseFeeExceedsInt128() (gas: 21721)\n[PASS] test_claimsGiftedToTheHookGoToTheRecipientAndNowhereElse() (gas: 758036)\n[PASS] test_donationsAndLiquidityChangesPayNoFee() (gas: 777442)\n[PASS] test_feeForRoundsDownAndNeverOverflowsForPoolSizedAmounts() (gas: 27768)\n[PASS] test_feeIsOnePercentWhateverTheLpFeeAndTickSpacing() (gas: 585759)\n[PASS] test_feeIsOnePercentWhenImdIsOnTheOtherSideOfTheKey() (gas: 2372773)\n[PASS] test_flushIsSkippedWhenTheManagerCannotCoverBothFeeAndPending() (gas: 1283077)\n[PASS] test_hookDataIsIgnored() (gas: 531002)\n[PASS] test_strangerUnlockDoesNotReachTheHook() (gas: 824706)\n[PASS] test_swapperTakingClaimsInsteadOfTokensStillPaysTheFee() (gas: 556430)\n[PASS] test_swapsBelowOneHundredWeiOfImdPayNothingOnEveryShape() (gas: 917390)\n[PASS] test_unlockCallbackFromTheManagerOutsideRedeemCannotPay() (gas: 590864)\nSuite result: ok. 14 passed; 0 failed; 0 skipped; finished in 9.92ms (18.30ms CPU time)\n\nRan 2 tests for test/DeployRnnnFeeHook.t.sol:DeployRnnnFeeHookTest\n[PASS] test_deployLandsOnAnAddressWithTheDeclaredFlags() (gas: 64591662)\n[PASS] test_deployWithAnUnminedSaltReverts() (gas: 63397263)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 37.37ms (72.33ms CPU time)\n\nRan 30 tests for test/RnnnFeeHook.t.sol:RnnnFeeHookTest\n[PASS] testFuzz_buyExactInput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 598101, ~: 597793)\n[PASS] testFuzz_buyExactOutput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 593477, ~: 593306)\n[PASS] testFuzz_sellExactInput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 599667, ~: 601005)\n[PASS] testFuzz_sellExactOutput_feeIsExactlyOnePercent(uint256) (runs: 256, μ: 581024, ~: 580766)\n[PASS] test_afterSwapOutsideAnUnlockReverts() (gas: 357905)\n[PASS] test_buyExactInput_sendsExactlyOnePercentOfImdToRecipient() (gas: 664157)\n[PASS] test_buyExactOutput_sendsExactlyOnePercentOfImdToRecipient() (gas: 647814)\n[PASS] test_callbacksRefuseCallersOtherThanThePoolManager() (gas: 211507)\n[PASS] test_constructorRejectsAnAddressWithoutTheRightFlags() (gas: 114411144)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 201468118)\n[PASS] test_feeIsNeverTakenInRnnnAndLpFeeIsUntouched() (gas: 1091097)\n[PASS] test_firstBuyOnRnnnOnlyPool_defersFeeAsClaimThenAnyoneRedeemsIt() (gas: 778164)\n[PASS] test_firstExactOutputBuyOnRnnnOnlyPool_defersFeeAsClaim() (gas: 692054)\n[PASS] test_immutablesAreSetByTheConstructor() (gas: 42542)\n[PASS] test_initializeAcceptsAnyCallerSuchAsTheLaunchFactory() (gas: 627810)\n[PASS] test_initializeAcceptsImdOnEitherSideAndAnyListedFee() (gas: 629213)\n[PASS] test_initializeAcceptsTheLaunchPool() (gas: 14784)\n[PASS] test_initializeRefusesANativeEthPoolWithoutImd() (gas: 42080)\n[PASS] test_initializeRefusesAPoolWithoutImd() (gas: 1151484)\n[PASS] test_noPrivilegedFunctions() (gas: 783385)\n[PASS] test_pendingFeesAreFlushedByTheNextSwapThatFindsImd() (gas: 799525)\n[PASS] test_permissionsMatchTheAddressFlags() (gas: 11271)\n[PASS] test_redeemPendingFeesRevertsWhenNothingIsPending() (gas: 585245)\n[PASS] test_runtimeCodeHasNoEscapeHatch() (gas: 2507013)\n[PASS] test_secondImdPoolWithTheHookIsChargedTheSameWay() (gas: 1225937)\n[PASS] test_sellAfterTheFirstBuyPaysDirectly() (gas: 794901)\n[PASS] test_sellExactInput_sendsExactlyOnePercentOfImdToRecipient() (gas: 649882)\n[PASS] test_sellExactOutput_sendsExactlyOnePercentOfImdToRecipient() (gas: 642767)\n[PASS] test_tinySwapWithZeroFeeStillSucceeds() (gas: 503862)\n[PASS] test_unusedCallbacksRevertEvenFromTheManager() (gas: 126193)\nSuite result: ok. 30 passed; 0 failed; 0 skipped; finished in 79.66ms (369.01ms CPU time)\n\nRan 10 tests for test/RnnnToken.t.sol:RnnnTokenTest\n[PASS] testFuzz_transferConservesSupply(address,uint256) (runs: 256, μ: 70189, ~: 70700)\n[PASS] test_approveAndTransferFrom() (gas: 246255)\n[PASS] test_infiniteAllowanceIsNotDecremented() (gas: 117105)\n[PASS] test_metadata() (gas: 23834)\n[PASS] test_mintsWholeSupplyToDeployer() (gas: 18392)\n[PASS] test_noAdminFunctionChangesSupply() (gas: 791195)\n[PASS] test_runtimeHasNoDelegatecallOrSelfdestruct() (gas: 510844)\n[PASS] test_transferMovesExactAmount() (gas: 75029)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 37019)\n[PASS] test_transferRevertsToZeroAddress() (gas: 29923)\nSuite result: ok. 10 passed; 0 failed; 0 skipped; finished in 79.63ms (6.36ms CPU time)\n\nRan 2 tests for test/RnnnFeeHook.invariant.t.sol:RnnnFeeHookFundedPoolInvariantTest\n[PASS]\nRnnnFeeHookFundedPoolInvariantTest invariants:\n[PASS] invariant_everySwapFeeIsExactlyOnePercentOfTheImdSide\n[PASS] invariant_hookAndPoolParametersNeverChange\n[PASS] invariant_hookHoldsNothingButImdClaims\n[PASS] invariant_imdIsConserved\n[PASS] invariant_paidPlusDeferredMinusFlushedIsPending\n[PASS] invariant_pendingClaimsAreBackedByManagerImd\n[PASS] invariant_recipientNeverReceivesRnnn\n[PASS] invariant_recipientPlusPendingEqualsEverythingOwed\n[PASS] invariant_redeemPaysExactlyWhatItReports\n[PASS] invariant_rnnnIsConserved\n RnnnFeeHookFundedPoolInvariantTest invariants (runs: 96, calls: 4608, reverts: 0)\n\n╭--------------------+------------------+-------+---------+----------╮\n| Contract           | Selector         | Calls | Reverts | Discards |\n+====================================================================+\n| RnnnFeeHookHandler | addLiquidity     | 511   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | buyExactIn       | 505   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | buyExactOut      | 530   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | donate           | 504   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | giftClaimsToHook | 495   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | redeem           | 529   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | removeLiquidity  | 530   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | sellExactIn      | 501   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | sellExactOut     | 503   | 0       | 0        |\n╰--------------------+------------------+-------+---------+----------╯\n\n[PASS] test_handlerExercisesEveryActionOnAFundedPool() (gas: 2137384)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 1.66s (1.64s CPU time)\n\nRan 3 tests for test/RnnnFeeHook.invariant.t.sol:RnnnFeeHookFreshPoolInvariantTest\n[PASS]\nRnnnFeeHookFreshPoolInvariantTest invariants:\n[PASS] invariant_everySwapFeeIsExactlyOnePercentOfTheImdSide\n[PASS] invariant_hookAndPoolParametersNeverChange\n[PASS] invariant_hookHoldsNothingButImdClaims\n[PASS] invariant_imdIsConserved\n[PASS] invariant_paidPlusDeferredMinusFlushedIsPending\n[PASS] invariant_pendingClaimsAreBackedByManagerImd\n[PASS] invariant_recipientNeverReceivesRnnn\n[PASS] invariant_recipientPlusPendingEqualsEverythingOwed\n[PASS] invariant_redeemPaysExactlyWhatItReports\n[PASS] invariant_rnnnIsConserved\n RnnnFeeHookFreshPoolInvariantTest invariants (runs: 96, calls: 4608, reverts: 0)\n\n╭--------------------+------------------+-------+---------+----------╮\n| Contract           | Selector         | Calls | Reverts | Discards |\n+====================================================================+\n| RnnnFeeHookHandler | addLiquidity     | 511   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | buyExactIn       | 505   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | buyExactOut      | 530   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | donate           | 504   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | giftClaimsToHook | 495   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | redeem           | 529   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | removeLiquidity  | 530   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | sellExactIn      | 501   | 0       | 0        |\n|--------------------+------------------+-------+---------+----------|\n| RnnnFeeHookHandler | sellExactOut     | 503   | 0       | 0        |\n╰--------------------+------------------+-------+---------+----------╯\n\n[PASS] test_freshPoolStartsWithoutImd() (gas: 20851)\n[PASS] test_handlerExercisesDeferralFlushAndRedeemOnAFreshPool() (gas: 1891017)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 1.74s (1.71s CPU time)\n\nRan 6 test suites in 1.74s (3.61s CPU time): 61 tests passed, 0 failed, 0 skipped (61 total tests)\n","passed":true},{"durationMs":57,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"RnnnFeeHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"RnnnFeeHook.redeemPendingFees()\",\"RnnnFeeHook.unlockCallback(bytes)\",\"RnnnToken.approve(address,uint256)\",\"RnnnToken.transfer(address,uint256)\",\"RnnnToken.transferFrom(address,address,uint256)\"],\"files\":{\".gitignore\":4,\"README.md\":196,\"foundry.toml\":15,\"remappings.txt\":3,\"script/DeployRnnnFeeHook.s.sol\":70,\"src/HookFlags.sol\":37,\"src/RnnnFeeHook.sol\":315,\"src/RnnnToken.sol\":67,\"test/DeployRnnnFeeHook.t.sol\":46,\"test/RnnnFeeHook.invariant.t.sol\":297,\"test/RnnnFeeHook.t.sol\":659,\"test/RnnnFeeHookAdversarial.t.sol\":428,\"test/RnnnToken.t.sol\":123,\"test/handlers/RnnnFeeHookHandler.sol\":433,\"test/mocks/MockERC20.sol\":60,\"test/utils/HookDeployer.sol\":45},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"c3350fe800de589e53172c34548532c18ebc3cd5b127df2f547f8e2460176e5a","verifiedTreeHash":"67090bba7796f0c2dfd4e975d11738840c272837","verifierVersion":"0.1.0+fb8278c5"}]}