{"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":"d6360666-d403-4919-9f2f-d967a365e95a","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"59c7ab67c8b5f81db7e2ac1aa1224510132db6f05d75792a0df05e52a1a4b555","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":"c73db270bd31e603c6ff031faded1d80931374145d314671cd71094f0e99040d","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":"7ed742da6edc3c6a2af5adde02ef514fe1ac18c26f15be555712ad18f31118d2","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","tools":[]},"key":"audit_imported_code","kind":"code","role":"review","skillHash":"9897597976b0e72440db7ca402d225aea20f3d37205ba53e7df2400ff80f29cc","skillId":"audit-imported-code","state":"accepted"},{"acceptedSubmissionHash":"8c3f4199cfc1ffe28d4e4934b9fee36a4a9a821e59bc069dc6374a04a874d9dc","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":"waiting"},{"acceptedSubmissionHash":"bffda3786b54401c0af4d005ccd8f3eab9d707c766b33cfabfec4acf6d18047d","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":"d48e63ba7ab416ca76b690745270f4a95cb20d3011402ec9c071003f669240f2","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":"dfbad1ae62c24c7634937e2f536ce1ad05c8a05c50428eff5c3feb92746fce77","dependsOn":["audit_imported_code"],"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":null,"dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"failed"},{"acceptedSubmissionHash":"38756658f74398fc88c1d8fbdfe84ee609dde5bffb8e8a38aaddef978a5770f4","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":"working"}],"objective":"Launch IMD Money Back ($MONEYBACK) on Robinhood Chain, paired with IMD, from this repository exactly as it is at the pinned commit. The four contracts were written, tested and reviewed by the swarm (jobs 57052175 and cb7160d0); this repository adds only foundry.toml and launch.json. Do not change fee parameters: hook fee 4.25% of the IMD leg on every swap in both directions, sell-only surcharge 2000 bps decaying to 0 over 1800 s, sweep() permissionless to the immutable payout which is the paying wallet ($owner). Keep the fees in IMD; never sell them for ETH. Deploy MoneyBackToken, MoneyBackHook (flags 0x20CC), RoundPayout(owner = paying wallet) and MoneyBackRouter(poolManager, hook). Nobody can change anything after deployment except RoundPayout via its owner. Supply: 10% to the swarm seats (factory rule); of the requester 90%, poolBps 9000 (the maximum) seeds the pool single-sided and the remaining 10% of it is sent to the dead address, so 81% of supply is in the pool, 9% is burned and the team holds nothing. LOGO: the token mark is one open umbrella, canopy and hooked handle, in warm yellow (#FFC83D), centred and large, on a flat navy background (#17356B) that fills the whole square. Nothing else: no text, no rain, no frame, no gradient. The project is Money Back Mutual, a fun insurance-themed memecoin; website https://imdmoneyback.xyz; X https://x.com/IMDmoneyback.","parentJobId":null,"planHash":"d042a827de9cf746c2b48d0b6575cbc7b044e5e34c3026bf948bc284d2a3e51d","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"d6360666-d403-4919-9f2f-d967a365e95a","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-1313-src-moneybacktoken-sol-src-moneybackhook"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"52420","feedbackHash":"ea6c11c967d696206a15063ca0bc9788a2c03f3631fe579c3e89ae0e14570118","nodeKey":"audit_economics","submissionHash":"59c7ab67c8b5f81db7e2ac1aa1224510132db6f05d75792a0df05e52a1a4b555","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51451","feedbackHash":"574b7c3ef412fcee9aa1ba81db718bdc828ac16b8ce620ad1ee2fc6a06c5a72f","nodeKey":"audit_flow","submissionHash":"c73db270bd31e603c6ff031faded1d80931374145d314671cd71094f0e99040d","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51021","feedbackHash":"1cf53f079578b36766358d9e28ffcab6591c690891e0bfcaa7f4aa2978824cb3","nodeKey":"audit_imported_code","submissionHash":"7ed742da6edc3c6a2af5adde02ef514fe1ac18c26f15be555712ad18f31118d2","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52222","feedbackHash":"0116490c278ad92f5925b606e2b33af8a647fd467e906ab7643b2c77ddc70a45","nodeKey":"audit_judge","submissionHash":"8c3f4199cfc1ffe28d4e4934b9fee36a4a9a821e59bc069dc6374a04a874d9dc","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51481","feedbackHash":"e15d3a4382df403896ef575b809596d45b91c22f7d41e7b9e8592161bbb464ca","nodeKey":"audit_math","submissionHash":"bffda3786b54401c0af4d005ccd8f3eab9d707c766b33cfabfec4acf6d18047d","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51514","feedbackHash":"85514f42bce064512d9b536e23eb5c9afbb69f18a88703d0b55b5a36e7503af6","nodeKey":"audit_permissions","submissionHash":"d48e63ba7ab416ca76b690745270f4a95cb20d3011402ec9c071003f669240f2","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50999","feedbackHash":"a32c6cb7f9032d2aa35d336ea0493b8e77810555bb4bae6abe5171677a46cfbe","nodeKey":"build_contract_project","submissionHash":"dfbad1ae62c24c7634937e2f536ce1ad05c8a05c50428eff5c3feb92746fce77","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52282","feedbackHash":"f663dce893ba81b922f7dedf682ace31b1de89583ef09461f90dce34b392c58f","nodeKey":"build_contract_project","submissionHash":"83be859e2d338dd3da45ff5fa2ba0ea3ac6aa087612c0325d1d2736fa8203c03","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51704","feedbackHash":"2ca47dfab064db2e86dc790d96e4fcb9d1ecdc7ad27317b561b69a8d4e20c280","nodeKey":"manifest","submissionHash":"2e55e0c8acda2d9204c90b2db9f2d4b113923d7f4eb2ec26a30b57acdc3fb5f6","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51374","feedbackHash":"4cdcb53b7f97c4ac370e42c5204474152f25049a900946ceefcf1302403c3790","nodeKey":"write_foundry_tests","submissionHash":"38756658f74398fc88c1d8fbdfe84ee609dde5bffb8e8a38aaddef978a5770f4","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"917ceb9bb93d9029204af04c3543bacb5fb68bac12b4d25141f1aa17179d96ae","state":"blocked","submissions":[{"artifacts":[],"attempt":1,"bundleHash":"4f77d9b1f95679fc8d84023dac1755cdc4ff3a9970d7c505174839ce8ab83d75","device":"83ee8122d8d8744c","findings":[],"hash":"2e55e0c8acda2d9204c90b2db9f2d4b113923d7f4eb2ec26a30b57acdc3fb5f6","nodeId":"c699d681-d6f8-494c-b8e6-5446fde23aa2","outcome":"completed","summary":"Updated only `launch.json`, preserving the required pool values, constructor arguments, and `0x20CC` permissions. Notes describe all four contracts and the separate auxiliary deployments; 2,957/4,000 characters.\n\nValidation passed: launch schema/build check, constructor ABI and permission checks, `forge build`, and all 216 tests.","treeHash":"377dde50dc81819a84d2bd5eb4950b791aad7d39","usage":{"cachedInputTokens":1082240,"inputTokens":79407,"model":"gpt-6-astra","outputTokens":7454,"runtime":"codex","turns":4,"wallClockMs":245832}},{"artifacts":[],"attempt":1,"bundleHash":"b50ab16895ee60038eb738e8579e17a4513f505bc8efefa339c887e4ac1e666b","device":"13603a08ada71730","findings":[{"description":"payRound increments round.failedCount once per failed leg (line 261) but accumulates the amount owed per recipient in failed[roundId][to] (line 260). retryFailed and writeOffFailed decrement failedCount once per recipient entry they clear (lines 313 and 328). When the same recipient appears in two or more failed legs of one batch, clearing its single entry removes all the owed amount but only one unit of failedCount. The leftover count can never be cleared: retryFailed and writeOffFailed both revert NothingFailed for that recipient. The NatSpec on line 29 says rounds(roundId).failedCount is 'the number of legs still failed', which is then wrong forever for that round. No funds are at risk: totalPaid, failed[] and balances stay correct; only the public counter the off-chain engine may read to decide whether a round is fully settled is wrong. Fix options: count distinct recipients in payRound (increment only when failed[roundId][to] was zero), or document that failedCount counts legs and may stay nonzero.","line":260,"path":"src/RoundPayout.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {RoundPayout} from \"src/RoundPayout.sol\";\n\ninterface VmDrift {\n    function prank(address) external;\n    function expectRevert(bytes calldata) external;\n}\n\ncontract BlockingToken {\n    mapping(address => uint256) public balanceOf;\n    mapping(address => bool) public blocked;\n\n    function mint(address to, uint256 v) external {\n        balanceOf[to] += v;\n    }\n\n    function setBlocked(address a, bool b) external {\n        blocked[a] = b;\n    }\n\n    function transfer(address to, uint256 v) external returns (bool) {\n        require(!blocked[to], \"blocked\");\n        balanceOf[msg.sender] -= v;\n        balanceOf[to] += v;\n        return true;\n    }\n}\n\n/// @dev A recipient that fails twice in one batch leaves `failedCount` stuck at 1 after the single\n///      retry that clears everything owed; the round can never reach failedCount == 0.\ncontract FailedCountDriftTest {\n    VmDrift constant vm = VmDrift(address(uint160(uint256(keccak256(\"hevm cheat code\")))));\n    address constant OWNER = address(0x0E0E);\n    address constant A = address(0xA1);\n    address constant B = address(0xB2);\n\n    function test_failedCountNeverReturnsToZeroAfterDuplicateFailedLegs() public {\n        RoundPayout payout = new RoundPayout(OWNER);\n        BlockingToken token = new BlockingToken();\n        token.mint(address(payout), 10);\n        token.setBlocked(A, true);\n        address[] memory to = new address[](3);\n        uint256[] memory amts = new uint256[](3);\n        to[0] = A;\n        to[1] = A;\n        to[2] = B;\n        amts[0] = 3;\n        amts[1] = 4;\n        amts[2] = 1;\n        vm.prank(OWNER);\n        payout.payRound(1, address(token), to, amts, 0, 0, 0);\n        (,,, uint256 failedCount,,) = payout.rounds(1);\n        require(failedCount == 2 && payout.failed(1, A) == 7, \"two legs failed, 7 owed\");\n\n        token.setBlocked(A, false);\n        address[] memory retry = new address[](1);\n        retry[0] = A;\n        vm.prank(OWNER);\n        uint256 paid = payout.retryFailed(1, retry);\n        require(paid == 7 && payout.failed(1, A) == 0, \"everything owed was paid\");\n        (,,, failedCount,,) = payout.rounds(1);\n        // Nothing is owed to anyone for round 1, yet the record says one leg is still failed, and no\n        // call can clear it: retry and write-off both revert NothingFailed.\n        vm.prank(OWNER);\n        vm.expectRevert(abi.encodeWithSelector(RoundPayout.NothingFailed.selector, 1, A));\n        payout.retryFailed(1, retry);\n        vm.prank(OWNER);\n        vm.expectRevert(abi.encodeWithSelector(RoundPayout.NothingFailed.selector, 1, A));\n        payout.writeOffFailed(1, A);\n        require(failedCount == 0, \"failedCount should be 0 once nothing is owed (actual: 1)\");\n    }\n}","reproduction":"Owner funds 10 units; recipient A is blocked by the token. payRound(1, token, [A, A, B], [3, 4, 1]) -> rounds(1).failedCount == 2, failed(1, A) == 7. Unblock A, retryFailed(1, [A]) pays 7 and clears failed(1, A). Expected: rounds(1).failedCount == 0 (nothing is owed to anyone). Actual: rounds(1).failedCount == 1, and both retryFailed(1, [A]) and writeOffFailed(1, A) revert NothingFailed(1, A), so the counter is stuck. The existing test test_duplicateFailedRecipientAccumulates in test/RoundPayout.t.sol asserts failedCount == 2 for this shape but stops before the retry.","severity":"low","title":"RoundPayout.failedCount counts legs but failed[] merges recipients, so a round with a duplicate failed recipient can never reach failedCount == 0"},{"description":"The router intends to hold nothing between calls and has no owner, which is correct for the brief. Two consequences are worth stating in the README: MONEYBACK or IMD transferred straight to the router (a user mistake) can never be recovered, since sellForEth refunds only tokens minus the consumed amount of the current call and nothing else moves ERC-20 out; and ETH pushed in by SELFDESTRUCT or a pre-funded address is paid out by _sendAllEth to whoever calls buyWithEth or sellForEth next. Neither is exploitable against users' own funds and no change is required for launch; it is an operational note.","line":419,"path":"src/MoneyBackRouter.sol","reproduction":"Transfer 1 MONEYBACK to the router address, then call any router function: the balance stays at 1 forever (no function moves it). Force 1 wei ETH into the router via selfdestruct, then have Alice call buyWithEth: Alice receives the extra wei with her dust refund.","severity":"info","title":"MoneyBackRouter has no rescue path: ERC-20 sent directly to it is stuck, ETH forced into it goes to the next caller"}],"hash":"38756658f74398fc88c1d8fbdfe84ee609dde5bffb8e8a38aaddef978a5770f4","nodeId":"45707cdb-c682-40a4-abfc-c876ddda3895","outcome":"completed","summary":"The findings file is intentionally excluded from git by the harness's own exclude rules, so it sits in the working tree where the job expects it. The delivered suite is green.\n\n## Recap\n\n**Delivered** one new test file, `test/MoneyBackRealV4Invariants.t.sol`, building on the existing 216-test suite rather than rewriting it. The gap it closes: every existing invariant ran against a mock PoolManager. The new file drives the real vendored v4-core PoolManager with handler contracts over random call sequences.\n\n| Suite | What it drives | Checks |\n|---|---|---|\n| Hook invariants, IMD as currency0 and as currency1 | All four swap cases with wide and tight price limits, LP add/remove, donations, outsider ERC-6909 claim gifts in IMD and MONEYBACK, sweeps from random callers, time warps, re-initialize attempts, stranger callback calls | pending == accrued + gifted − swept; hook holds only IMD claims; payout gets exactly what was swept and nothing else; claims backed by manager balance; IMD and MONEYBACK conserved; binding, payout, flags and surcharge formula final |\n| Router invariants | ETH buys and sells through both real pools from three actors, quotes, sweeps | router holds nothing after every call; quote equals execution; fee per trade matches the formula from the emitted event; fees never become ETH; ETH, IMD, MONEYBACK conserved |\n| Edge cases | Protocol fee at the v4 maximum, outsider claims swept only when IMD, two swaps in one unlock with a partial fill rolling both back, 1 wei legs, the 1799 s / 1800 s window edge on real pool math, no rebind or setter | unit assertions |\n\nHandlers never revert on their own. Each classifies outcomes and records a reasoned `violations` counter, so a broken post-condition cannot hide behind forge's silent handler reverts. A scratch run with fixed seeds confirmed every branch is reached, including PartialFill reverts, partial unspecified fills and empty-route reverts.\n\n**Results**\n\n```\nforge test: 225 passed, 0 failed, 0 skipped (11 suites)\nforge fmt --check: clean on the new file\n```\n\n**Reported, not asserted**, in `.imd-findings.json`:\n- **Low.** In RoundPayout, `failedCount` counts legs while `failed[]` merges recipients. A round with the same recipient failing twice can never reach `failedCount == 0` after the retry clears all it is owed. No funds at risk. The failing reproduction test is included as proof.\n- **Info.** The router has no rescue path for tokens sent to it directly, and force-sent ETH goes to the next caller. Operational note only.\n\n**Not changed:** sources, config, scripts, existing tests. No commit was made; the harness's own exclude rules hide the findings file and scratch directory from git.","treeHash":"9dd8168c9ca940a6ee83726824561f4c9748a1e4","usage":{"cachedInputTokens":3095442,"inputTokens":610,"model":"claude-fable-5-1","outputTokens":65221,"runtime":"claude","turns":43,"wallClockMs":1133568}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"a7581259169534a1","findings":[],"hash":"58395fe895cfa5ec677bc36b9607da55192c6be152575e8e9d7b6c9fee72897d","nodeId":"c699d681-d6f8-494c-b8e6-5446fde23aa2","outcome":"failed","summary":"clone failed: Cloning into '/Users/zhangtao/.identitymd-seat2/work/d6360666-d403-4919-9f2f-d967a365e95a/c699d681-d6f8-494c-b8e6-5446fde23aa2'...\nfatal: unable to access 'https://github.com/BillionaireBonkClub/moneyback-contracts.git/': LibreSSL SSL_connect: SSL_ERROR_SYSCALL in connection to github.com:443 \n","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":null,"outputTokens":0,"runtime":"codex","turns":0,"wallClockMs":5124}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"1559912e747bbcd4","findings":[{"citation":"resolved","description":"The brief fixes one rate: 4.25% of the IMD leg on every swap plus a 2000 bps sell surcharge. The hook applies that rate to a different base depending on which currency the swapper specifies. For IMD-specified swaps (beforeSwap, line 397) the base is the swapper-side amount: on an exact-input buy that is the gross IMD the buyer pays (fee included), on an exact-output sell it is the net IMD the seller receives (fee excluded). For IMD-unspecified swaps (afterSwap, line 425) the base is the pool-side delta: on an exact-output buy the net IMD the pool receives, on an exact-input sell the gross IMD the pool pays. So for the same pool movement P the exact-input route is charged r*P and the exact-output route r*P/(1+r). At r = 0.2425 (surcharge window) a seller who asks for 100 IMD exact-output pays 24.25 IMD while the pool pays out 124.25 IMD: 19.52% of the pool's IMD leg. A seller who sells the identical MONEYBACK amount exact-input from the identical state gets the same 124.25 IMD out of the pool and is charged 30.13 IMD: 24.25%. The exact-output seller keeps 100 IMD versus 94.12 IMD, 6.2% more, and the payout wallet receives 19.5% less fee. After the surcharge decays (r = 0.0425) the gap is 4.25% vs 4.077% of the pool leg. The same split applies to buys: exact-input buyers pay 4.25% of gross, exact-output buyers 4.077% of gross. Every trader who can pick exact-output (any v4 router supports it) rationally does, so the realised surcharge is ~1570 bps of pool output rather than the 2000 bps the brief states, and the realised base fee is ~408 bps rather than 425. The README documents the per-case base, but the brief gives a single rate, and the two routes to the same economic trade produce different fee revenue for the fixed payout and different proceeds for identical sellers. Fix (keeps fee parameters unchanged): compute the fee on the same side in all four cases, e.g. for the IMD-specified exact-output sell gross the fee so that fee = floor(net*r/(1-r)) (equivalently charge r on the pool's output), and for exact-input buys net it so the pool-side base is used; or state the single base explicitly and apply it to both routes. The proof test uses the vendored v4-core PoolManager under test/vendor (this repository carries no forge-std and the reviewer may not add remappings), so it imports no forge-std; run it with `forge test --match-path test/scratch/FeeBaseAsymmetry.t.sol`.","line":397,"path":"src/MoneyBackHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {MoneyBackHook, IPoolManager as HookManager} from \"src/MoneyBackHook.sol\";\nimport {MoneyBackToken} from \"src/MoneyBackToken.sol\";\nimport {PoolManager} from \"test/vendor/v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"test/vendor/v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"test/vendor/v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"test/vendor/v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolKey} from \"test/vendor/v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"test/vendor/v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"test/vendor/v4-core/src/types/BalanceDelta.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"test/vendor/v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"test/vendor/v4-core/src/libraries/TickMath.sol\";\n\ninterface Vm {\n    function etch(address, bytes calldata) external;\n    function warp(uint256) external;\n    function deal(address, uint256) external;\n    function snapshotState() external returns (uint256);\n    function revertToState(uint256) external returns (bool);\n}\n\ninterface IERC20Like {\n    function transfer(address to, uint256 value) external returns (bool);\n}\n\ncontract ScratchIMD {\n    mapping(address => uint256) public balanceOf;\n\n    function mint(address to, uint256 value) external {\n        balanceOf[to] += value;\n    }\n\n    function transfer(address to, uint256 value) external returns (bool) {\n        balanceOf[msg.sender] -= value;\n        balanceOf[to] += value;\n        return true;\n    }\n}\n\n/// @dev Same pool state, same MONEYBACK sold, same IMD leaving the pool: the exact-output route\n///      pays a smaller hook fee than the exact-input route.\ncontract FeeBaseAsymmetryTest is IUnlockCallback {\n    Vm constant vm = Vm(address(uint160(uint256(keccak256(\"hevm cheat code\")))));\n    address constant IMD = 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127;\n    address constant PAYOUT = address(0xBACC);\n    uint160 constant Q96 = 1 << 96;\n    uint256 constant T0 = 1_750_000_000;\n\n    PoolManager manager;\n    MoneyBackToken token;\n    MoneyBackHook hook;\n    PoolKey key;\n    bool tokenIs0;\n\n    function setUp() public {\n        vm.warp(T0);\n        vm.etch(IMD, address(new ScratchIMD()).code);\n        ScratchIMD(IMD).mint(address(this), 1e30);\n        manager = new PoolManager(address(this));\n        token = new MoneyBackToken();\n        tokenIs0 = address(token) < IMD;\n        bytes32 hookHash = keccak256(\n            abi.encodePacked(type(MoneyBackHook).creationCode, abi.encode(address(manager), address(token), PAYOUT))\n        );\n        for (uint256 salt; salt < 1_000_000; ++salt) {\n            address predicted =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(salt), hookHash)))));\n            if (uint160(predicted) & 0x3FFF == 0x20CC) {\n                hook = new MoneyBackHook{salt: bytes32(salt)}(HookManager(address(manager)), address(token), PAYOUT);\n                break;\n            }\n        }\n        require(address(hook) != address(0), \"hook salt\");\n        key = PoolKey({\n            currency0: Currency.wrap(tokenIs0 ? address(token) : IMD),\n            currency1: Currency.wrap(tokenIs0 ? IMD : address(token)),\n            fee: 12_500,\n            tickSpacing: 60,\n            hooks: IHooks(address(hook))\n        });\n        manager.initialize(key, Q96);\n        manager.unlock(abi.encode(uint8(1), abi.encode(ModifyLiquidityParams(-600, 600, 1e24, 0))));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"manager only\");\n        (uint8 action, bytes memory params) = abi.decode(data, (uint8, bytes));\n        BalanceDelta delta;\n        if (action == 0) {\n            delta = manager.swap(key, abi.decode(params, (SwapParams)), \"\");\n        } else {\n            (delta,) = manager.modifyLiquidity(key, abi.decode(params, (ModifyLiquidityParams)), \"\");\n        }\n        _settle(key.currency0, delta.amount0());\n        _settle(key.currency1, delta.amount1());\n        return abi.encode(delta);\n    }\n\n    function _settle(Currency currency, int128 delta) private {\n        if (delta > 0) {\n            manager.take(currency, address(this), uint128(delta));\n        } else if (delta < 0) {\n            manager.sync(currency);\n            require(IERC20Like(Currency.unwrap(currency)).transfer(address(manager), uint256(-int256(delta))), \"settle\");\n            manager.settle();\n        }\n    }\n\n    function _sell(bool exactInput, uint256 amount) private returns (int256 imdDelta, int256 tokenDelta, uint256 fee) {\n        uint256 before = hook.pending();\n        SwapParams memory p = SwapParams({\n            zeroForOne: tokenIs0,\n            amountSpecified: exactInput ? -int256(amount) : int256(amount),\n            sqrtPriceLimitX96: tokenIs0 ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1\n        });\n        BalanceDelta d = abi.decode(manager.unlock(abi.encode(uint8(0), abi.encode(p))), (BalanceDelta));\n        imdDelta = tokenIs0 ? d.amount1() : d.amount0();\n        tokenDelta = tokenIs0 ? d.amount0() : d.amount1();\n        fee = hook.pending() - before;\n    }\n\n    /// @notice Inside the surcharge window (2425 bps). Seller A asks for exactly 100 IMD (exact\n    ///         output). Seller B, from the identical pool state, sells exactly the MONEYBACK A sold\n    ///         (exact input). The pool pays out the same IMD either way, but A is charged\n    ///         ~19.5% of it and B 24.25% of it.\n    function test_sameSaleSameOutputDifferentFee() public {\n        uint256 snap = vm.snapshotState();\n\n        // Route A: exact-output sell of 100 IMD.\n        (int256 imdA, int256 tokA, uint256 feeA) = _sell(false, 100e18);\n        require(imdA == 100e18, \"A receives exactly 100 IMD\");\n        uint256 poolOutA = uint256(imdA) + feeA; // IMD that left the pool\n        uint256 soldA = uint256(-tokA);\n\n        vm.revertToState(snap);\n\n        // Route B: exact-input sell of the same MONEYBACK amount from the same state.\n        (int256 imdB,, uint256 feeB) = _sell(true, soldA);\n        uint256 poolOutB = uint256(imdB) + feeB;\n\n        // Same pool movement (pool math rounding only).\n        require(poolOutB + 2 >= poolOutA && poolOutA + 2 >= poolOutB, \"pool paid out the same IMD\");\n\n        // Both routes must charge the same fee for the same IMD leg leaving the pool.\n        // Fails today: feeA = 24.25e18 (19.52% of 124.25e18), feeB = 30.13e18 (24.25% of 124.25e18).\n        require(feeA + 2 >= feeB && feeB + 2 >= feeA, \"exact-output route pays less hook fee than exact-input route\");\n    }\n\n    /// @notice The same gap, stated as a rate on the pool's IMD output.\n    function test_exactOutputSellSurchargeRateOnPoolOutput() public {\n        (int256 imdA,, uint256 feeA) = _sell(false, 100e18);\n        uint256 poolOut = uint256(imdA) + feeA;\n        uint256 bpsOnPoolOutput = feeA * 10_000 / poolOut;\n        // 2425 expected (425 base + 2000 surcharge). Actual: 1951.\n        require(bpsOnPoolOutput == 2425, \"exact-output sell is charged 1951 bps of the IMD the pool paid, not 2425\");\n    }\n}","reproduction":"Real v4 PoolManager (test/vendor), MONEYBACK/IMD pool at price 1:1 (sqrtPriceX96 = 2^96), fee 12500, spacing 60, liquidity 1e24 over ticks -600..600, block.timestamp == initializedAt (surcharge 2000 bps). Route A: swap exact-output sell, amountSpecified = +100e18 IMD, widest price limit. Observed: swapper IMD delta = +100e18, hook.pending() grows by 24.25e18, pool IMD outflow = 124.25e18, MONEYBACK consumed = X = 125.838420233840636979e18. Revert state to before route A. Route B: swap exact-input sell with amountSpecified = -125.838420233840636979e18 MONEYBACK. Observed: pool IMD outflow = 124.25e18 exactly (observed), hook.pending() grows by 30.130625e18 (= floor(124.25e18*425/1e4) + floor(124.25e18*2000/1e4)), swapper receives 94.119375e18. Expected: identical pool movement charges an identical fee (either 24.25e18 or 30.13e18 on both routes). Actual: fee differs by 5.88e18 IMD (19.5% of the fee) on a 124 IMD sale; the exact-output seller receives 6.2% more IMD than the exact-input seller for the same sale.","severity":"medium","snippet":"        uint256 imdLeg = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);","title":"Hook fee base is route-dependent: exact-output swaps pay r/(1+r) of the IMD leg, exact-input swaps pay r; a seller choosing exact-output cuts the 2425 bps sell charge to 1951 bps of the IMD the pool p"},{"citation":"resolved","description":"quoteBuy is documented as the number buyWithEth would deliver, returning 0 when the buy would revert. Leg 2 of a buy is an exact-input IMD->MONEYBACK swap with the widest price limit (hook case 1). When the launch pool's liquidity cannot absorb the whole IMD amount produced by leg 1, v4 fills the swap partially and MoneyBackHook.afterSwap reverts PartialFill (src/MoneyBackHook.sol:421). The router's _quote (lines 409-412) bubbles every revert other than QuoteResult, so quoteBuy reverts with CustomRevert.WrappedError(hook, afterSwap.selector, PartialFill(), HookCallFailed()) rather than returning 0 or the fillable amount. buyWithEth with the same ETH reverts too, so an integrator sees a revert on both the quote and the buy and gets no signal of how much ETH the route can take. This is the hook's deliberate all-or-nothing rule for IMD-specified swaps surfacing through the router unhandled. Impact is liveness/integration only: no funds move. Fix: in _quote catch the wrapped PartialFill and return 0 as the documentation promises, or document that quoteBuy reverts in this case; optionally let the router fall back to an exact-output buy (hook case 2, partial fills allowed) when the launch leg cannot fully fill.","line":246,"path":"src/MoneyBackRouter.sol","reproduction":"Real v4 PoolManager (test/vendor). Public ETH/IMD pool (fee 10000, spacing 100, no hook) at 1:1 with liquidity 1e24 over ticks -600..600. Launch MONEYBACK/IMD pool (fee 12500, spacing 60, MoneyBackHook) at 1:1 with liquidity 1e18 over ticks -60..60. Call router.quoteBuy(100 ether). Expected per NatSpec: a uint256 (0 if buyWithEth would revert). Actual: the call reverts; the 260-byte revert data is WrappedError(hook, 0xb47b2fb1 afterSwap, PartialFill() 0x..., HookCallFailed()). router.buyWithEth{value: 100 ether}(0, block.timestamp) reverts the same way. Scratch test test/scratch/QuotePartial.t.sol reproduces it (forge test --match-path test/scratch/QuotePartial.t.sol fails with 'quoteBuy reverted; reason length 260').","severity":"low","snippet":"    ///         Returns 0 when the route yields nothing (buyWithEth would revert).","title":"quoteBuy reverts with the hook's wrapped PartialFill instead of returning a quote when the launch leg cannot absorb all the IMD the public pool produced; buyWithEth has no size at which it degrades gr"},{"citation":"resolved","description":"The brief fixes the hook flags at 0x20CC, which carry no liquidity callbacks, and the README (Scope of the surcharge) and docs/AUDIT_RESPONSES.md item 4 already record this. It is listed here so the economics judge sees the magnitude with the fee rules as given: during the first 1800 s a direct sell costs 24.25% plus the 1.25% LP fee, while a holder who adds a MONEYBACK-only position just above the current price, lets buyers (who still pay 4.25%) move the price through it, and removes the position receives IMD having paid no hook fee or surcharge and having earned the 1.25% LP fee instead. Likewise any MONEYBACK/IMD or MONEYBACK/ETH pool created without this hook (anyone can initialize one; the token has no transfer restrictions) is fee-free, so arbitrageurs who buy on the hooked pool and sell elsewhere pay the hook fee once, and liquidity has an incentive to migrate. No code change is proposed within the fixed flags; it is a trust/design assumption the requester accepts, not a defect. Reported at info level because it is reproducible and material to the stated 2000 bps sell-surcharge guarantee.","line":297,"path":"src/MoneyBackHook.sol","reproduction":"Existing test test/MoneyBackRealV4.t.sol:test_liquidityConversionIsOutsideSwapSurcharge (both currency orders, real v4-core) at block.timestamp == initializedAt: add 100_000e18 liquidity units MONEYBACK-only over ticks 60..120 (mirrored for the other order); hook.pending() stays 0. A buyer swaps 10_000e18 IMD exact-input; hook.pending() becomes 425e18 (buyer paid 4.25%). The LP removes the position: the delta is IMD-only (MONEYBACK fully sold to the buyer) and hook.pending() is still 425e18 while hook.currentSurchargeBps() == 2000. Expected under the brief's sell surcharge: the LP's conversion of MONEYBACK to IMD inside the window carries a 2000 bps surcharge. Actual: 0 bps, and the LP collected the 1.25% LP fee on the buyer's input.","severity":"info","snippet":"            beforeAddLiquidity: false,","title":"Sell surcharge and 4.25% hook fee apply only to swaps on the bound pool: a holder converts MONEYBACK to IMD inside the surcharge window by adding single-sided liquidity and withdrawing after buyers cr"}],"hash":"59c7ab67c8b5f81db7e2ac1aa1224510132db6f05d75792a0df05e52a1a4b555","nodeId":"48e64670-b044-4972-893e-1e1917cc24f9","outcome":"completed","summary":"Review complete. The findings file is at `.imd-findings.json` with 3 findings and 27 coverage rows (all 24 listed entry points plus three invariants). No tracked files were changed; only the git-ignored findings file and two scratch tests under `test/scratch/` were created.\n\n**What I covered**\n\nMy area was Economic Security, Invariant and Flow Gap. I traced all four swap cases through the vendored v4-core `PoolManager` and `Hooks` library to confirm what `afterSwap` sees, how the hook's returned deltas settle, and that the claims it mints net to zero. I checked the surcharge formula, the sweep path, the router's two-leg flows and quotes, RoundPayout's conservation couplings, the token, and the manifest against the implementation. The full existing suite passes (216 tests).\n\n**Findings**\n\n1. **Medium, fee base is route-dependent.** The hook charges the rate on the swapper-side amount for IMD-specified swaps and on the pool-side delta for IMD-unspecified ones. From an identical pool state, an exact-output sell asking for 100 IMD and an exact-input sell of the same MONEYBACK both pull 124.25 IMD out of the pool, yet the first is charged 24.25 IMD and the second 30.13 IMD. During the surcharge window the realised sell charge is 1951 bps of pool output on the exact-output route instead of 2425 bps, so rational sellers pick it and the payout wallet collects about a fifth less. The README documents the per-case base, but the brief states one rate. Proof test on real v4 math fails on current code.\n\n2. **Low, quoteBuy reverts instead of quoting.** When the launch pool cannot absorb all the IMD leg 1 produced, the hook's `PartialFill` bubbles out of `quoteBuy` as a wrapped revert, contradicting its NatSpec promise to return 0. Reproduced on real v4 with a shallow launch pool.\n\n3. **Info, surcharge scope.** Converting MONEYBACK to IMD via a single-sided LP position or any hookless pool pays no hook fee or surcharge. Already documented and retained because flags are fixed at 0x20CC. Included for the judge with the magnitude from the project's own test.\n\n**Not reached**\n\nThe live IMD token's behaviour (pausable, blacklist, upgradeable) could not be checked because no RPC was reachable. Sweep and swap liveness depend on it as an external assumption, which the coverage row for `sweep()` records.","treeHash":null,"usage":{"cachedInputTokens":2604865,"inputTokens":642,"model":"claude-fable-5-1","outputTokens":47963,"runtime":"claude","turns":32,"wallClockMs":868031}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"b2fad28b1d801fa8","findings":[],"hash":"5ec8963d9aee551301992b8fa966f8f324f22058c46207f4088c8c5ef6732e79","nodeId":"c699d681-d6f8-494c-b8e6-5446fde23aa2","outcome":"failed","summary":"could not restore accepted work: accepted work conflicts with a dependency that changed the same files after it was accepted: ","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":null,"outputTokens":0,"runtime":"codex","turns":0,"wallClockMs":1759}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"72ae9b5bbd1a54b6","findings":[{"citation":"resolved","description":"Cases 1 and 4 (IMD is the specified currency) compute and mint the fee in beforeSwap from params.amountSpecified, before the pool has moved anything, and return it as a positive deltaSpecified. v4-core (Hooks.sol beforeSwap: amountToSwap += hookDeltaSpecified; Hooks.sol afterSwap: swapDelta = swapDelta - hookDelta) applies that delta to the swapper in full whatever the pool actually filled. A swap fills partially whenever sqrtPriceLimitX96 is reached or the pool runs out of liquidity on that side. The pool is seeded single-sided with MONEYBACK only, so in its first minutes it holds very little IMD and every exact-output sell larger than that reserve is partial. Exact-output SELL (case 4): the pool is asked for amountSpecified + fee, delivers X, and the seller's IMD delta is X - fee. During the surcharge window fee = 24.25% of the amount requested, so any fill below 24.25% of the request makes the seller pay IMD while also giving up MONEYBACK, with a standard router's amountInMaximum check passing (the MONEYBACK input is small). Exact-input BUY (case 1): the hook keeps 4.25% of the IMD offered even when the pool consumed less, so the effective fee rises above 4.25%. afterSwap returns 0 for these cases and cannot correct the specified-currency delta, so the fix is to detect the under-fill in afterSwap (IMD side of delta smaller than imdLeg + fee for case 4, smaller than imdLeg - fee for case 1; carry the fee from beforeSwap in transient storage) and revert, or to refuse exact-output sells / price-limited exact-input buys up front. Measured against a real v4-core PoolManager (commit 46c6834) with this hook bound to a MONEYBACK/IMD pool at price 1:1, liquidity 1e24 over ticks [-600, 600], t = initializedAt.","line":392,"path":"src/MoneyBackHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {\n    MoneyBackHook,\n    IHooks,\n    IPoolManager,\n    PoolKey,\n    SwapParams,\n    Currency,\n    BalanceDelta,\n    BeforeSwapDelta\n} from \"src/MoneyBackHook.sol\";\nimport {MoneyBackToken} from \"src/MoneyBackToken.sol\";\n\n/// @notice Proof: the hook charges the IMD fee on the amount the swapper SPECIFIED, in beforeSwap,\n///         before the pool has moved anything. When the pool fills only part of the order (price\n///         limit reached, or not enough IMD in the pool, which is the state right after the\n///         single-sided seeding), v4 still applies the full beforeSwap delta to the swapper\n///         (v4-core Hooks.sol: `amountToSwap += hookDeltaSpecified` in beforeSwap, then\n///         `swapDelta = swapDelta - hookDelta` in afterSwap). An exact-output SELL whose fill is\n///         smaller than the fee makes the seller PAY IMD on top of the MONEYBACK they sold; an\n///         exact-input BUY that fills partially pays 4.25% of the offered amount, not of the amount\n///         actually swapped.\n///\n///         This contract plays the PoolManager (the hook is built with it as `manager`) and\n///         replays exactly the two v4 callbacks with the deltas v4 would pass. Each test passes if\n///         the hook either refuses the under-filled swap (reverts in beforeSwap or afterSwap) or\n///         charges no more than the IMD the pool actually moved.\ncontract PartialFillFeeTest is Test, IPoolManager {\n    address constant IMD = 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127;\n    uint160 constant FLAGS = 0x20CC;\n    uint160 constant SQRT_1_1 = 79228162514264337593543950336;\n\n    MoneyBackHook hook;\n    MoneyBackToken token;\n    PoolKey key;\n    bool imdIs0;\n    mapping(address => mapping(uint256 => uint256)) claims;\n\n    // ---- minimal PoolManager surface the hook touches in these paths ----\n    function unlock(bytes calldata) external pure returns (bytes memory) {\n        return \"\";\n    }\n\n    function mint(address to, uint256 id, uint256 amount) external {\n        claims[to][id] += amount;\n    }\n\n    function burn(address from, uint256 id, uint256 amount) external {\n        claims[from][id] -= amount;\n    }\n\n    function take(Currency, address, uint256) external {}\n\n    function balanceOf(address owner, uint256 id) external view returns (uint256) {\n        return claims[owner][id];\n    }\n\n    function setUp() public {\n        token = new MoneyBackToken();\n        bytes memory creation =\n            abi.encodePacked(type(MoneyBackHook).creationCode, abi.encode(address(this), address(token), address(0xFA)));\n        bytes32 h = keccak256(creation);\n        address at;\n        for (uint256 i = 0; i < 1_000_000; i++) {\n            address p =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), h)))));\n            if ((uint160(p) & ((1 << 14) - 1)) != FLAGS) continue;\n            bytes32 salt = bytes32(i);\n            assembly (\"memory-safe\") {\n                at := create2(0, add(creation, 0x20), mload(creation), salt)\n            }\n            break;\n        }\n        require(at != address(0), \"hook not deployed\");\n        hook = MoneyBackHook(at);\n\n        imdIs0 = IMD < address(token);\n        (address c0, address c1) = imdIs0 ? (IMD, address(token)) : (address(token), IMD);\n        key = PoolKey({\n            currency0: Currency.wrap(c0),\n            currency1: Currency.wrap(c1),\n            fee: 12_500,\n            tickSpacing: 60,\n            hooks: IHooks(at)\n        });\n        bytes4 sel = hook.beforeInitialize(address(this), key, SQRT_1_1);\n        assertEq(sel, IHooks.beforeInitialize.selector);\n    }\n\n    function _specified(BeforeSwapDelta d) internal pure returns (int256 a) {\n        assembly (\"memory-safe\") {\n            a := sar(128, d)\n        }\n    }\n\n    function _delta(int128 a0, int128 a1) internal pure returns (BalanceDelta d) {\n        assembly (\"memory-safe\") {\n            d := or(shl(128, a0), and(sub(shl(128, 1), 1), a1))\n        }\n    }\n\n    /// Exact-output SELL of 100 IMD during the surcharge window, pool can deliver only 5 IMD\n    /// (price limit reached, or the pool holds only 5 IMD). v4 asks the pool for\n    /// 100 + fee, the pool pays 5, the swapper is charged the full beforeSwap fee of 24.25.\n    function test_exactOutputSell_partialFill_sellerMustNotPayImd() public {\n        bool sellZeroForOne = !imdIs0; // MONEYBACK in, IMD out\n        SwapParams memory params = SwapParams({zeroForOne: sellZeroForOne, amountSpecified: 100e18, sqrtPriceLimitX96: SQRT_1_1});\n\n        int256 feeSpecified;\n        try hook.beforeSwap(address(this), key, params, \"\") returns (bytes4, BeforeSwapDelta bsd, uint24) {\n            feeSpecified = _specified(bsd);\n        } catch {\n            return; // the hook refused the swap: acceptable\n        }\n\n        // the pool delivered only 5 IMD (and took 5.07 MONEYBACK) before hitting the limit / running dry\n        int128 delivered = 5e18;\n        BalanceDelta swapDelta = imdIs0 ? _delta(delivered, -5.07e18) : _delta(-5.07e18, delivered);\n        try hook.afterSwap(address(this), key, params, swapDelta, \"\") returns (bytes4, int128) {}\n        catch {\n            return; // the hook refused the under-filled swap in afterSwap: acceptable\n        }\n\n        // v4 (Hooks.afterSwap): swapDelta = swapDelta - hookDelta, hookDelta.specified = feeSpecified\n        int256 sellerImd = int256(delivered) - feeSpecified;\n        assertGe(sellerImd, 0, \"a SELL made the seller pay IMD: fee charged on 100 IMD the pool never delivered\");\n        assertLe(hook.pending(), uint256(int256(delivered)), \"hook kept more IMD than the pool moved\");\n    }\n\n    /// Exact-input BUY offering 100 IMD, pool consumes only 50 before the price limit: the hook\n    /// has already taken 4.25 (4.25% of 100) so the buyer pays 54.25 for a 50 IMD swap (8.5%).\n    function test_exactInputBuy_partialFill_feeOnlyOnConsumed() public {\n        bool buyZeroForOne = imdIs0; // IMD in\n        SwapParams memory params = SwapParams({zeroForOne: buyZeroForOne, amountSpecified: -100e18, sqrtPriceLimitX96: SQRT_1_1});\n\n        int256 feeSpecified;\n        try hook.beforeSwap(address(this), key, params, \"\") returns (bytes4, BeforeSwapDelta bsd, uint24) {\n            feeSpecified = _specified(bsd);\n        } catch {\n            return;\n        }\n\n        int128 consumed = 50e18; // pool consumed 50 of the 95.75 it was offered\n        BalanceDelta swapDelta = imdIs0 ? _delta(-consumed, 47e18) : _delta(47e18, -consumed);\n        try hook.afterSwap(address(this), key, params, swapDelta, \"\") returns (bytes4, int128) {}\n        catch {\n            return;\n        }\n\n        uint256 charged = hook.pending();\n        assertLe(charged, uint256(int256(consumed)) * 425 / 10_000 + 1, \"fee exceeds 4.25% of the IMD actually swapped\");\n        feeSpecified; // silence\n    }\n}","reproduction":"(a) Low IMD reserve, widest price limit (what routers send): swap(zeroForOne = sell, amountSpecified = +100000e18, sqrtPriceLimitX96 = MIN/MAX). Pool delivers 29,553 IMD for 30,838 MONEYBACK; hook.pending() = 24,250 IMD (24.25% of the 100,000 requested); seller receives 5,303 IMD net: effective fee 82%. Expected: fee <= 24.25% of the 29,553 IMD moved. (b) Tight limit: amountSpecified = +100e18, sqrtPriceLimitX96 = SQRT_1_1 +/- SQRT_1_1/100000. Pool delivers 5 IMD; seller's IMD balance DROPS by 14.25e18 and their MONEYBACK balance drops by 10.13e18; hook.pending() = 24.25e18. Expected: a sell never takes IMD from the seller. (c) Exact-input buy offering 100e18 IMD with the limit one tick away: pool consumes 50.63 IMD, buyer pays 54.88 IMD, hook keeps 4.25e18 (8.4% of what was swapped instead of 4.25%). Proof test (test/scratch/PartialFillFee.t.sol) replays the two callbacks with these deltas: run `forge test --match-path test/scratch/PartialFillFee.t.sol`; both tests fail on this code (-19250000000000000000 < 0 and 4250000000000000000 > 2125000000000000001) and pass once the hook refuses or correctly prices an under-filled swap.","severity":"high","snippet":"        uint256 imdLeg = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);","title":"Hook fee is fixed in beforeSwap on the amount specified, so a partially filled swap over-charges: an exact-output sell can make the seller pay IMD on top of MONEYBACK"},{"citation":"resolved","description":"sellForEth encodes msg.sender into CallbackData and _sell, inside unlockCallback, calls transferFrom with that decoded address as the payer. The keccak gate on the callback data and the PoolManager-only check make it safe in practice (only the router's own unlock reaches unlockCallback, and d.user is always the caller), but statically the payer is an arbitrary decoded value and Slither's arbitrary-send-erc20 detector fires on it. The launch reference states that the verifier runs Slither and rejects a transferFrom from any payer but msg.sender whatever gates it (four rejected attempts cited, 2026-10-06 to 08). Fix for the adapter: pull the tokens in sellForEth itself with transferFrom(msg.sender, address(this), tokens), then in unlockCallback do sync(token), token.transfer(poolManager, amount), settle(), and refund unused MONEYBACK from the router's own balance. Slither also flags _sendAllEth(msg.sender) as arbitrary-send-eth (line 420); that one targets msg.sender and is the ordinary refund pattern, listed here only so the adapter is not surprised.","line":331,"path":"src/MoneyBackRouter.sol","reproduction":"Run `slither . --compile-force-framework foundry` (slither 0.11.6, solc 0.8.26) on the repository. Output: \"Detector: arbitrary-send-erc20 / MoneyBackRouter._sell(MoneyBackRouter.CallbackData) (src/MoneyBackRouter.sol#325-355) uses arbitrary from in transferFrom: ! IERC20(token).transferFrom(d.user,address(poolManager),tokensIn) (src/MoneyBackRouter.sol#331)\". Expected for admission: no arbitrary-send-erc20 result. Functional behaviour is otherwise correct: a direct test of sellForEth with the caller's own allowance succeeds and no third party can drive unlockCallback.","severity":"medium","snippet":"            if (!IERC20(token).transferFrom(d.user, address(poolManager), tokensIn)) revert TransferFromFailed();","title":"Router pulls the seller's MONEYBACK with transferFrom(d.user, ...) decoded from callback data: Slither arbitrary-send-erc20, which the verifier's Slither run rejects"},{"citation":"resolved","description":"The manifest schema accepts kind, hook, token, pool and notes; any other key is dropped and describes nothing. The brief requires the launch to deploy MoneyBackToken, MoneyBackHook, RoundPayout(owner = paying wallet) and MoneyBackRouter(poolManager, hook). RoundPayout and the router exist only inside the unknown \"contracts\" array, and the router's second argument uses \"$contract:MoneyBackHook\", a placeholder the deployer does not define ($poolManager, $token, $owner, $factory are the documented ones). As written, the factory deploys the token and the hook only: the buy-with-ETH path and the payout engine contract the brief asks for never reach the chain, and nothing in the launch flags it because dropped keys are silent. The adapter must either obtain a schema/deployer path for the two extra contracts or state in notes that they are deployed separately, with the router's hook argument resolved after the hook address is mined. Not affected: the hook entry itself (constructorArgs match the constructor in order; permissions match getHookPermissions, flag bits 0x20CC; pool.fee 12500 and tickSpacing 60 are exactly what beforeInitialize accepts).","line":9,"path":"launch.json","reproduction":"Input: this launch.json. Parse it with the schema's keys only (kind, hook, token, pool, notes): the \"contracts\" array is discarded, leaving two contracts to deploy. Expected per the brief: four contracts deployed, RoundPayout owned by $owner and MoneyBackRouter constructed with ($poolManager, <MoneyBackHook address>). Actual: RoundPayout and MoneyBackRouter are never constructed, and \"$contract:MoneyBackHook\" has no resolution rule.","severity":"medium","snippet":"  \"contracts\": [","title":"launch.json puts RoundPayout and MoneyBackRouter under a \"contracts\" key the manifest schema does not have, so neither is deployed by the launch"},{"citation":"resolved","description":"The 20%-decaying sell surcharge is meant to deter dumping in the first 30 minutes, but the hook only prices swaps. A holder can add MONEYBACK-only liquidity just above the current price, let buyers' swaps convert it to IMD, and remove the position: MONEYBACK has been sold for IMD, the position earned the 1.25% LP fee instead of paying anything, and hook.pending() did not move for the seller (the buyers paid their 4.25% as usual). Any address, including the swarm seats that hold 10% of supply, can do this from the first block. This is a known limit of swap-fee hooks; it is reported so the adapter and the requester know the surcharge is not a hard guarantee. Mitigation if wanted: enable beforeAddLiquidity and restrict liquidity changes to the launch factory ($factory) while the surcharge is active, or accept and document the gap.","line":292,"path":"src/MoneyBackHook.sol","reproduction":"Real v4-core PoolManager, hook bound, price 1:1, main liquidity 1e24 over [-600, 600], surcharge at 2000 bps. LP address 0x11 holds 1000e18 MONEYBACK and calls modifyLiquidity(key, {tickLower: 60, tickUpper: 120, liquidityDelta: +100000e18}) (MONEYBACK is currency0 in this deployment; mirror the ticks otherwise): 298.64e18 MONEYBACK deposited, 0 IMD. A buyer swaps 30000e18 IMD exact-input through the range. LP removes the position: receives 305.15e18 IMD and 0 MONEYBACK; hook.pending() unchanged by the LP's two calls; currentSurchargeBps() still 2000. Expected under the brief: a sale of 298.64 MONEYBACK during the window pays 4.25% + 20% of its IMD leg; actual: 0 paid, LP fee earned.","severity":"low","snippet":"            beforeAddLiquidity: false,","title":"Sell surcharge and base fee are bypassable by selling as a single-sided liquidity provider: the hook has no liquidity callbacks"},{"citation":"resolved","description":"The constructor doc (line 268) and the contract header (line 139) say payout is the RoundPayout contract; launch.json passes \"$owner\" and its notes say the paying wallet, which is what the brief asks for. The code does what the brief says; only the comments are stale. Worth fixing so the audit panel and the engine operator do not expect fees to land in RoundPayout automatically: with payout = the wallet, someone must fund RoundPayout by hand (fund()) before any round can pay.","line":219,"path":"src/MoneyBackHook.sol","reproduction":"Deploy per launch.json: MoneyBackHook(manager, token, $owner). hook.payout() == the paying wallet. sweep() sends IMD there; RoundPayout.balance stays 0 until the owner calls fund(). The comment at line 219 says RoundPayout.","severity":"info","snippet":"    /// @notice Where sweep() sends accrued IMD (the RoundPayout contract). Fixed forever.","title":"Hook NatSpec and README name RoundPayout as the sweep destination, but the manifest and the brief make payout the paying wallet ($owner)"},{"citation":"resolved","description":"Recorded as a trust assumption, not a defect: the brief allows RoundPayout to be changeable by its owner. The owner (the engine key) can move the whole IMD balance anywhere at any time (sweep works while paused), pause and unpause payRound and retryFailed, write off failed legs, and pay arbitrary addresses arbitrary amounts up to the balance. None of it touches holder balances, the token or the hook, so it does not conflict with the launch's ban on pausing, freezing or seizing holder funds. Implementation checked: Ownable2Step, nonReentrant on every mover, idempotent rounds, failed legs recorded without reverting the batch, SafeERC20-equivalent return handling; no defect found.","line":197,"path":"src/RoundPayout.sol","reproduction":"owner calls fund(IMD, 1000e18) then sweep(IMD, owner, 1000e18): balance returns to the owner in one call with no timelock or cap. owner calls pause(): payRound and retryFailed revert EnforcedPause until unpause(). These are the intended powers; the finding exists so the report lists them.","severity":"info","snippet":"    function sweep(address token, address to, uint256 amount) external onlyOwner nonReentrant {","title":"RoundPayout owner powers are the launch's only privilege: sweep any token, pause rounds, choose recipients and amounts"},{"citation":"resolved","description":"Both the hook and the router pin IMD = 0x5F7B...7127, and the router pins the public pool key (ETH/IMD, fee 10000, tickSpacing 100, no hook). Checked over RPC against chain 4663, PoolManager 0x8366a39CC670B4001A1121B8F6A443A643e40951: the IMD contract reports symbol IMD, 18 decimals, and the public pool's slot0 is initialized with lpFee 0x2710 (10000), so the router's route exists. Consequence to know: beforeInitialize accepts only a pool whose other currency is that constant, so the protected floor test test_initializesFromTheLaunchFactory passes only when the verifier's IMD_PAIRED_CURRENCY is the manifest's pairedCurrency (it is, 0x5f7b...7127 lowercase), and the contracts cannot be reused on another chain. Not a defect for this launch.","line":65,"path":"src/MoneyBackRouter.sol","reproduction":"cast keccak(abi.encode(address(0), IMD, 10000, 100, address(0))) = 0xd2fc01ee...8f02; extsload of keccak(poolId, 6) on the PoolManager returns 0x0000000027103e83e800dadd00000000000000107785c74a1d54f997c2eb4c57 (nonzero sqrtPriceX96, lpFee 10000). beforeInitialize with any currency pair not containing 0x5F7B...7127 reverts WrongPairedCurrency.","severity":"info","snippet":"    address public constant IMD = 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127;","title":"IMD address and the public ETH/IMD pool are hardcoded; verified live on Robinhood Chain, but the hook refuses any other paired currency"},{"citation":"resolved","description":"Read in full: src/MoneyBackHook.sol, src/MoneyBackToken.sol, src/MoneyBackRouter.sol, src/RoundPayout.sol, launch.json, README.md, foundry.toml, the protected floor tests (Hook.protected.t.sol, Token.protected.t.sol), the Pashov reference and the uniswap-v4-security reference. Skimmed only: the four files under test/ (they use mock PoolManagers; the repository's 183 tests pass). Executed: the hook against a real v4-core PoolManager (local copy, commit 46c6834) for all four swap cases, sweep, partial fills and the LP bypass; slither 0.11.6 on src/; RPC reads of Robinhood Chain (4663) for IMD and the public pool. Not reachable: the launch factory and manifest schema beyond the reference text, the swarm job repositories (57052175, cb7160d0, launch-1178 @ c38bc4a8), and the deployer's placeholder resolver. Checked and found sound: all callbacks PoolManager-only; one pool bound, fee 12500 / spacing 60 accepted; fee maths (425 bps both ways, 2000 bps linear decay to 0 at +1800 s, floored separately) match the brief on full fills; claims minted equal the hook delta so deltas sum to zero; sweep burns and takes only to the immutable payout; no owner, setter, pause, proxy, delegatecall or selfdestruct in token, hook or router; token mints the whole 1e27 to the deployer once with no mint path; router deltas net to zero on buy and sell with dust refunded; quote path is state-neutral.","line":1,"path":"README.md","reproduction":"Not a defect. See the description for the list.","severity":"info","snippet":"# IMD Money Back ($MONEYBACK) · contracts","title":"Coverage: what was read, what was executed, what could not be reached"}],"hash":"7ed742da6edc3c6a2af5adde02ef514fe1ac18c26f15be555712ad18f31118d2","nodeId":"5fb2a699-cb62-4304-8fd0-574d6db52269","outcome":"completed","summary":"The audit is complete. The findings file is written at `.imd-findings.json` with eight entries, and no repository file was changed.\n\n**What I read and ran.** All four contracts in `src/`, `launch.json`, the README, the protected floor tests and both references. I verified the hook against a real v4-core PoolManager in a scratch project outside the repo, ran slither on a copy of the sources, and read Robinhood Chain over RPC to check the router's hardcoded pool. The swarm job repositories and the launch factory's manifest schema were not reachable beyond the reference text.\n\n**Findings, by severity:**\n\n- **High, hook fee on partial fills.** The hook fixes the IMD fee in beforeSwap on the amount specified, before the pool has moved anything. On a partial fill, which is the normal state of a single-sided pool with little IMD, an exact-output sell charges the full fee on IMD never delivered. On the real PoolManager a seller asking for 100,000 IMD from a pool holding about 29,500 gave up 30,838 MONEYBACK, the hook kept 24,250 IMD, and the seller netted 5,303. With a tight price limit the seller's IMD balance actually dropped. Exact-input buys are overcharged the same way. The proof test under `test/scratch/PartialFillFee.t.sol` fails on this code and passes with a fix that refuses under-filled swaps, which I confirmed on a patched copy.\n- **Medium, router transferFrom.** The sell path pulls tokens from a payer decoded from callback data. Slither 0.11.6 reports it as arbitrary-send-erc20, which the launch reference says the verifier rejects outright. The gating is sound in practice, so this is an admission blocker rather than a theft path.\n- **Medium, manifest.** RoundPayout and MoneyBackRouter sit under a `contracts` key the schema does not have, with an undefined `$contract:MoneyBackHook` placeholder. As written the launch deploys only the token and hook.\n- **Low, surcharge bypass.** Selling as a single-sided liquidity provider pays no hook fee or surcharge. Reproduced on the real PoolManager: 298.6 MONEYBACK in, 305 IMD out, surcharge still at 2000 bps, nothing accrued.\n- **Info.** Stale NatSpec naming RoundPayout as the payout, RoundPayout owner powers recorded as trust assumptions, the hardcoded IMD and public pool (confirmed live on chain 4663), and a coverage statement.\n\n**Found sound:** PoolManager-only callbacks, single bound pool at fee 12500 and spacing 60, fee maths on full fills, zero-sum delta accounting, sweep only to the immutable payout, fixed token supply with no mint path, router dust refunds and quote neutrality, and no proxy, delegatecall, selfdestruct or pause outside RoundPayout.","treeHash":null,"usage":{"cachedInputTokens":2720234,"inputTokens":610,"model":"claude-fable-5-1","outputTokens":56056,"runtime":"claude","turns":46,"wallClockMs":1019905}},{"artifacts":[],"attempt":1,"bundleHash":"20f371d17c53091a49b1aaa8516c0f92b9816ce81e1fe517357e176fd84367db","device":"7b7c27166477ddd8","findings":[],"hash":"83be859e2d338dd3da45ff5fa2ba0ea3ac6aa087612c0325d1d2736fa8203c03","nodeId":"66dfebc5-8b5e-4a6d-8ca0-93799856e148","outcome":"completed","summary":"Prepared the launch files, fee/router repairs, deployment script, umbrella logo, and audit notes. Fixed fees and economics are preserved.\n\nValidation passed: **216 tests**, build, formatting, and IMD launch check.\n\nNo transactions were broadcast. The two auxiliaries require separate deployment. Slither was unavailable locally; the LP surcharge bypass remains documented in [audit notes](/home/imd/.identitymd/work/d6360666-d403-4919-9f2f-d967a365e95a/66dfebc5-8b5e-4a6d-8ca0-93799856e148/docs/AUDIT_RESPONSES.md).","treeHash":"51010581e9277602ff5bf0b547466d1d6d150f6f","usage":{"cachedInputTokens":5003008,"inputTokens":194202,"model":"gpt-6-astra","outputTokens":45309,"runtime":"codex","turns":7,"wallClockMs":1548752}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"357c46e3781993d4","findings":[{"citation":"resolved","description":"Merged from audit_flow, audit_economics, audit_permissions and audit_math (all four reported the same root cause). The brief fixes one rate: 4.25% of the IMD leg on every swap plus a 2000 bps sell surcharge. The hook applies that rate to a different base depending only on the sign of amountSpecified. Cases 1 and 4 (IMD specified, beforeSwap line 397) use the swapper-facing amount: for an exact-input buy that is gross IMD paid (fee included), for an exact-output sell it is net IMD received (fee excluded). Cases 2 and 3 (IMD unspecified, afterSwap line 425) use the IMD the pool actually moved. For the same pool movement P the exact-input sell is charged r*P and the exact-output sell r*P/(1+r). Inside the surcharge window (r = 24.25%) an exact-output seller keeps 100 IMD where an exact-input seller of the identical MONEYBACK keeps 94.12 IMD, and the immutable payout receives 19.5% less fee. The anti-dump surcharge the brief states as 2000 bps is effectively ~1610 bps of pool output for any seller who phrases the sale as exact-output through any generic v4 router (the project router only sends exact-input, so router users pay the higher rate and direct PoolManager users the lower). After decay the gap is 4.25% vs 4.077%. Buys invert: exact-input buyers pay 4.25% of gross (4.44% of pool input), exact-output buyers 4.25% of pool input (4.077% of gross). The README table documents the per-case base, but the brief gives a single rate and two encodings of one trade yield different revenue for the fixed payout and different proceeds for identical sellers. Minimal fix that keeps every fee parameter: compute the fee on one base in all four cases, e.g. for IMD-specified swaps gross the fee so it is bps of the pool-side leg (exact-output sell: fee = A*r/(BPS-r); exact-input buy: fee = A*r/(BPS+r)), and keep the afterSwap PartialFill comparison on the same formula. Reproduced on the vendored v4-core PoolManager in both currency orders (specialist proof re-run here: 2 failed as stated; own scratch test for the buy side).","line":397,"path":"src/MoneyBackHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {MoneyBackHook, IPoolManager as HookManager} from \"src/MoneyBackHook.sol\";\nimport {MoneyBackToken} from \"src/MoneyBackToken.sol\";\nimport {PoolManager} from \"test/vendor/v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"test/vendor/v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"test/vendor/v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"test/vendor/v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {PoolKey} from \"test/vendor/v4-core/src/types/PoolKey.sol\";\nimport {Currency} from \"test/vendor/v4-core/src/types/Currency.sol\";\nimport {BalanceDelta} from \"test/vendor/v4-core/src/types/BalanceDelta.sol\";\nimport {SwapParams, ModifyLiquidityParams} from \"test/vendor/v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"test/vendor/v4-core/src/libraries/TickMath.sol\";\n\ninterface Vm {\n    function etch(address, bytes calldata) external;\n    function warp(uint256) external;\n    function deal(address, uint256) external;\n    function snapshotState() external returns (uint256);\n    function revertToState(uint256) external returns (bool);\n}\n\ninterface IERC20Like {\n    function transfer(address to, uint256 value) external returns (bool);\n}\n\ncontract ScratchIMD {\n    mapping(address => uint256) public balanceOf;\n\n    function mint(address to, uint256 value) external {\n        balanceOf[to] += value;\n    }\n\n    function transfer(address to, uint256 value) external returns (bool) {\n        balanceOf[msg.sender] -= value;\n        balanceOf[to] += value;\n        return true;\n    }\n}\n\n/// @dev Same pool state, same MONEYBACK sold, same IMD leaving the pool: the exact-output route\n///      pays a smaller hook fee than the exact-input route.\ncontract FeeBaseAsymmetryTest is IUnlockCallback {\n    Vm constant vm = Vm(address(uint160(uint256(keccak256(\"hevm cheat code\")))));\n    address constant IMD = 0x5F7Bb59365ce557C26dbcAa4EE9d39A4b95B7127;\n    address constant PAYOUT = address(0xBACC);\n    uint160 constant Q96 = 1 << 96;\n    uint256 constant T0 = 1_750_000_000;\n\n    PoolManager manager;\n    MoneyBackToken token;\n    MoneyBackHook hook;\n    PoolKey key;\n    bool tokenIs0;\n\n    function setUp() public {\n        vm.warp(T0);\n        vm.etch(IMD, address(new ScratchIMD()).code);\n        ScratchIMD(IMD).mint(address(this), 1e30);\n        manager = new PoolManager(address(this));\n        token = new MoneyBackToken();\n        tokenIs0 = address(token) < IMD;\n        bytes32 hookHash = keccak256(\n            abi.encodePacked(type(MoneyBackHook).creationCode, abi.encode(address(manager), address(token), PAYOUT))\n        );\n        for (uint256 salt; salt < 1_000_000; ++salt) {\n            address predicted =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(salt), hookHash)))));\n            if (uint160(predicted) & 0x3FFF == 0x20CC) {\n                hook = new MoneyBackHook{salt: bytes32(salt)}(HookManager(address(manager)), address(token), PAYOUT);\n                break;\n            }\n        }\n        require(address(hook) != address(0), \"hook salt\");\n        key = PoolKey({\n            currency0: Currency.wrap(tokenIs0 ? address(token) : IMD),\n            currency1: Currency.wrap(tokenIs0 ? IMD : address(token)),\n            fee: 12_500,\n            tickSpacing: 60,\n            hooks: IHooks(address(hook))\n        });\n        manager.initialize(key, Q96);\n        manager.unlock(abi.encode(uint8(1), abi.encode(ModifyLiquidityParams(-600, 600, 1e24, 0))));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager), \"manager only\");\n        (uint8 action, bytes memory params) = abi.decode(data, (uint8, bytes));\n        BalanceDelta delta;\n        if (action == 0) {\n            delta = manager.swap(key, abi.decode(params, (SwapParams)), \"\");\n        } else {\n            (delta,) = manager.modifyLiquidity(key, abi.decode(params, (ModifyLiquidityParams)), \"\");\n        }\n        _settle(key.currency0, delta.amount0());\n        _settle(key.currency1, delta.amount1());\n        return abi.encode(delta);\n    }\n\n    function _settle(Currency currency, int128 delta) private {\n        if (delta > 0) {\n            manager.take(currency, address(this), uint128(delta));\n        } else if (delta < 0) {\n            manager.sync(currency);\n            require(IERC20Like(Currency.unwrap(currency)).transfer(address(manager), uint256(-int256(delta))), \"settle\");\n            manager.settle();\n        }\n    }\n\n    function _sell(bool exactInput, uint256 amount) private returns (int256 imdDelta, int256 tokenDelta, uint256 fee) {\n        uint256 before = hook.pending();\n        SwapParams memory p = SwapParams({\n            zeroForOne: tokenIs0,\n            amountSpecified: exactInput ? -int256(amount) : int256(amount),\n            sqrtPriceLimitX96: tokenIs0 ? TickMath.MIN_SQRT_PRICE + 1 : TickMath.MAX_SQRT_PRICE - 1\n        });\n        BalanceDelta d = abi.decode(manager.unlock(abi.encode(uint8(0), abi.encode(p))), (BalanceDelta));\n        imdDelta = tokenIs0 ? d.amount1() : d.amount0();\n        tokenDelta = tokenIs0 ? d.amount0() : d.amount1();\n        fee = hook.pending() - before;\n    }\n\n    /// @notice Inside the surcharge window (2425 bps). Seller A asks for exactly 100 IMD (exact\n    ///         output). Seller B, from the identical pool state, sells exactly the MONEYBACK A sold\n    ///         (exact input). The pool pays out the same IMD either way, but A is charged\n    ///         ~19.5% of it and B 24.25% of it.\n    function test_sameSaleSameOutputDifferentFee() public {\n        uint256 snap = vm.snapshotState();\n\n        // Route A: exact-output sell of 100 IMD.\n        (int256 imdA, int256 tokA, uint256 feeA) = _sell(false, 100e18);\n        require(imdA == 100e18, \"A receives exactly 100 IMD\");\n        uint256 poolOutA = uint256(imdA) + feeA; // IMD that left the pool\n        uint256 soldA = uint256(-tokA);\n\n        vm.revertToState(snap);\n\n        // Route B: exact-input sell of the same MONEYBACK amount from the same state.\n        (int256 imdB,, uint256 feeB) = _sell(true, soldA);\n        uint256 poolOutB = uint256(imdB) + feeB;\n\n        // Same pool movement (pool math rounding only).\n        require(poolOutB + 2 >= poolOutA && poolOutA + 2 >= poolOutB, \"pool paid out the same IMD\");\n\n        // Both routes must charge the same fee for the same IMD leg leaving the pool.\n        // Fails today: feeA = 24.25e18 (19.52% of 124.25e18), feeB = 30.13e18 (24.25% of 124.25e18).\n        require(feeA + 2 >= feeB && feeB + 2 >= feeA, \"exact-output route pays less hook fee than exact-input route\");\n    }\n\n    /// @notice The same gap, stated as a rate on the pool's IMD output.\n    function test_exactOutputSellSurchargeRateOnPoolOutput() public {\n        (int256 imdA,, uint256 feeA) = _sell(false, 100e18);\n        uint256 poolOut = uint256(imdA) + feeA;\n        uint256 bpsOnPoolOutput = feeA * 10_000 / poolOut;\n        // 2425 expected (425 base + 2000 surcharge). Actual: 1951.\n        require(bpsOnPoolOutput == 2425, \"exact-output sell is charged 1951 bps of the IMD the pool paid, not 2425\");\n    }\n}","reproduction":"Real v4-core PoolManager (test/vendor), MONEYBACK/IMD pool at sqrtPrice 2^96, fee 12500, spacing 60, liquidity 1e24 over ticks -600..600, block.timestamp == initializedAt (currentSurchargeBps() == 2000). (A) Exact-output sell: swap(zeroForOne = sell direction, amountSpecified = +100e18, widest limit). Observed: swapper receives exactly 100e18 IMD, hook.pending() grows by 24.25e18, pool IMD outflow 124.25e18, MONEYBACK consumed 125838420233840636979. Fee / pool outflow = 1951 bps. (B) Revert state; exact-input sell of the same 125838420233840636979 MONEYBACK (amountSpecified negative). Observed: pool IMD outflow 124.25e18 (identical), hook.pending() grows by 30.130625e18, swapper receives 94.119375e18. Fee / pool outflow = 2425 bps. Expected: the same pool movement pays the same hook fee. Actual: fee differs by 5.88e18 IMD (19.5%). Buy side (own scratch run, token as currency0): exact-input buy of 100e18 IMD: fee 4.25e18, pool receives 95.75e18 (443 bps of pool input), swapper gets 94544185551805496940 MONEYBACK; revert state; exact-output buy of that same 94544185551805496940 MONEYBACK: swapper pays 99.819375e18 IMD, fee 4.069375e18 (425 bps of pool input, 407.7 bps of what the swapper pays). Expected: 4.25% of the same leg in both forms. Run: forge test --match-path test/scratch/FeeBaseAsymmetry.t.sol (fails on this code with 'exact-output route pays less hook fee than exact-input route' and 'exact-output sell is charged 1951 bps of the IMD the pool paid, not 2425'). The proof imports no forge-std because the repository vendors none.","severity":"medium","snippet":"        uint256 imdLeg = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);","title":"Hook fee base depends on the swap form: exact-output sells pay 1951 bps of the IMD leaving the pool instead of 2425, exact-input buys 443 bps of the IMD entering instead of 425"},{"citation":"resolved","description":"The brief asks the launch to deploy four contracts: MoneyBackToken, MoneyBackHook, RoundPayout(owner = paying wallet) and MoneyBackRouter(poolManager, hook). The factory creates only the contracts the manifest names, and the univ4_hook schema names a token and a hook; nothing else is run to deploy or set anything up. launch.json therefore deploys two contracts, and its notes defer RoundPayout and MoneyBackRouter to script/DeployAuxiliaries.s.sol, whose run() wraps vm.startBroadcast() (line 29) and needs a funded signer, an RPC and the already-mined hook address: the router's constructor needs the deployed hook address, which the manifest has no way to express ($contract resolvers do not exist in the installed schema, as docs/AUDIT_RESPONSES.md item 3 records). This was raised in the previous round (entry d0c5e4ca) and answered with the script, but a contract whose constructor needs what the manifest cannot give it still cannot be deployed by the launch, whatever the author answers. Consequence: after the launch transaction the money-back engine has no payer contract and buyers have no ETH route; the paying wallet receives hook fees via sweep() but has nothing on chain to fund. No funds are at risk and the token and hook deploy correctly, so this is a delivery gap rather than a loss, reported so the requester decides: either accept a two-phase deployment performed by the paying wallet with the script (and say so in the brief), or drop the two auxiliaries from the launch's deliverable. The hook, token and pool are unaffected either way.","line":4,"path":"launch.json","reproduction":"Read launch.json: keys are kind, hook{contract: MoneyBackHook, constructorArgs: [$poolManager,$token,$owner], permissions}, token{contract: MoneyBackToken}, pool, notes. No entry names RoundPayout or MoneyBackRouter and the schema has no field that could. Read script/DeployAuxiliaries.s.sol line 29: run() calls vm.startBroadcast() and takes (manager, deployedHook) as arguments that exist only after the factory launch; validate() reverts PoolNotInitialized/MissingDeployment until then. Expected per brief: four deployed contracts after the launch. Actual: two; the other two exist only if a human later signs forge script ... --broadcast with the paying wallet's key. Verified by test/DeployAuxiliaries.t.sol passing only after the fixture itself deploys and initializes the hook first.","severity":"medium","snippet":"    \"contract\": \"MoneyBackHook\",","title":"The launch deploys only MoneyBackToken and MoneyBackHook: RoundPayout and MoneyBackRouter are left to a separate signed script that the deployment model never runs"},{"citation":"resolved","description":"Merged from write_foundry_tests, audit_flow, audit_permissions and audit_math (same root cause). _payLegs increments failedCount once per failed leg (line 261) while accumulating the owed amount into one failed[roundId][recipient] slot (line 260). retryFailed (line 313) and writeOffFailed (line 328) clear that single slot and subtract exactly 1. With a recipient repeated k times whose legs all fail, failedCount ends k-1 above the number of outstanding entries and nothing can bring it to zero: both clearing functions revert NothingFailed once the slot is empty. The header invariant (line 29, 'failedCount is the number of legs still failed') is then false for that round forever, and an off-chain engine that waits for failedCount == 0 to close a round never does. totalPaid, failed[] and balances stay correct; no funds are lost; owner-only path. Fix: increment failedCount only when failed[roundId][recipient] was zero before the add (count distinct failed recipients) and detect the all-failed case with a separate success counter, or reject duplicate recipients in payRound.","line":260,"path":"src/RoundPayout.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.26;\n\nimport {RoundPayout} from \"src/RoundPayout.sol\";\n\ninterface VmDrift {\n    function prank(address) external;\n    function expectRevert(bytes calldata) external;\n}\n\ncontract BlockingToken {\n    mapping(address => uint256) public balanceOf;\n    mapping(address => bool) public blocked;\n\n    function mint(address to, uint256 v) external {\n        balanceOf[to] += v;\n    }\n\n    function setBlocked(address a, bool b) external {\n        blocked[a] = b;\n    }\n\n    function transfer(address to, uint256 v) external returns (bool) {\n        require(!blocked[to], \"blocked\");\n        balanceOf[msg.sender] -= v;\n        balanceOf[to] += v;\n        return true;\n    }\n}\n\n/// @dev A recipient that fails twice in one batch leaves `failedCount` stuck at 1 after the single\n///      retry that clears everything owed; the round can never reach failedCount == 0.\ncontract FailedCountDriftTest {\n    VmDrift constant vm = VmDrift(address(uint160(uint256(keccak256(\"hevm cheat code\")))));\n    address constant OWNER = address(0x0E0E);\n    address constant A = address(0xA1);\n    address constant B = address(0xB2);\n\n    function test_failedCountNeverReturnsToZeroAfterDuplicateFailedLegs() public {\n        RoundPayout payout = new RoundPayout(OWNER);\n        BlockingToken token = new BlockingToken();\n        token.mint(address(payout), 10);\n        token.setBlocked(A, true);\n        address[] memory to = new address[](3);\n        uint256[] memory amts = new uint256[](3);\n        to[0] = A;\n        to[1] = A;\n        to[2] = B;\n        amts[0] = 3;\n        amts[1] = 4;\n        amts[2] = 1;\n        vm.prank(OWNER);\n        payout.payRound(1, address(token), to, amts, 0, 0, 0);\n        (,,, uint256 failedCount,,) = payout.rounds(1);\n        require(failedCount == 2 && payout.failed(1, A) == 7, \"two legs failed, 7 owed\");\n\n        token.setBlocked(A, false);\n        address[] memory retry = new address[](1);\n        retry[0] = A;\n        vm.prank(OWNER);\n        uint256 paid = payout.retryFailed(1, retry);\n        require(paid == 7 && payout.failed(1, A) == 0, \"everything owed was paid\");\n        (,,, failedCount,,) = payout.rounds(1);\n        // Nothing is owed to anyone for round 1, yet the record says one leg is still failed, and no\n        // call can clear it: retry and write-off both revert NothingFailed.\n        vm.prank(OWNER);\n        vm.expectRevert(abi.encodeWithSelector(RoundPayout.NothingFailed.selector, 1, A));\n        payout.retryFailed(1, retry);\n        vm.prank(OWNER);\n        vm.expectRevert(abi.encodeWithSelector(RoundPayout.NothingFailed.selector, 1, A));\n        payout.writeOffFailed(1, A);\n        require(failedCount == 0, \"failedCount should be 0 once nothing is owed (actual: 1)\");\n    }\n}","reproduction":"Deploy RoundPayout(owner). Token T whose transfer to A reverts (or MoneyBackToken with A = address(0), which reverts ERC20InvalidReceiver); mint 10 to RoundPayout. Owner calls payRound(1, T, [A, A, B], [3, 4, 1], 0, 0, 0). State: rounds(1).failedCount == 2, failed(1, A) == 7, B paid 1. Unblock A; owner calls retryFailed(1, [A]): returns 7, failed(1, A) == 0. Expected: rounds(1).failedCount == 0 (nothing owed to anyone). Actual: 1, and both retryFailed(1, [A]) and writeOffFailed(1, A) now revert NothingFailed(1, A). Run: forge test --match-path test/scratch/FailedCountDrift.t.sol (fails on this code with 'failedCount should be 0 once nothing is owed (actual: 1)').","severity":"low","snippet":"                failed[roundId][recipient] += amount;\n                ++failedCount;","title":"RoundPayout.failedCount counts failed legs but retryFailed/writeOffFailed decrement per recipient, so a recipient that fails twice in one batch leaves the counter stuck above zero forever"},{"citation":"resolved","description":"Merged from audit_flow, audit_economics and audit_math. Leg 2 of a buy is an exact-input IMD swap on the launch pool (hook case 1), and since the partial-fill repair MoneyBackHook.afterSwap reverts PartialFill unless the pool consumes the whole adjusted input. So on every path that returns, r.imdMid == imdOut and imdDust (line 325) is always 0; the buyWithEth NatSpec (lines 197-198, 'any IMD our pool did not consume are refunded') and the quoteBuy NatSpec (line 246, 'Returns 0 when the route yields nothing') describe behaviour that no longer exists. _quote (lines 409-412) bubbles every revert other than QuoteResult, so quoteBuy for a size the launch pool cannot absorb reverts with CustomRevert.WrappedError(hook, afterSwap.selector, PartialFill(), HookCallFailed()) rather than returning 0, and buyWithEth reverts the same way instead of filling what is available. Integration/liveness only: no funds move, the README (line 81) states the revert. Fix: catch the wrapped PartialFill in _quote and return 0 as documented (or correct the three comments and remove the dead refund), optionally fall back to an exact-output buy (case 2, partial fills allowed) when the launch leg cannot fully fill.","line":246,"path":"src/MoneyBackRouter.sol","reproduction":"Real v4-core PoolManager (test/MoneyBackRealV4.t.sol fixture, token as currency0): launch pool liquidity 1e24 over ticks -600..600 at 2^96; public ETH/IMD pool (fee 10000, spacing 100, no hook) at 2^96 with liquidity 1e27 over -600..600. router.quoteBuy(1 ether) returns a positive quote. router.quoteBuy(100000 ether): expected per NatSpec 0; actual: reverts with 260 bytes of revert data, selector 0x90bfb865 (WrappedError) containing PartialFill's selector. router.buyWithEth{value: 100000 ether}(1, block.timestamp): expected per lines 197-198 a partial fill with unconsumed IMD refunded; actual: reverts identically, nothing bought. Verified in test/scratch/Judge.t.sol::test_quoteBuyRevertsOnPartialFill (not kept).","severity":"low","snippet":"    ///         Returns 0 when the route yields nothing (buyWithEth would revert).","title":"Router buy never partially fills: quoteBuy reverts with the hook's wrapped PartialFill instead of returning 0, and the IMD-dust refund and its NatSpec describe behaviour that cannot occur"},{"citation":"resolved","description":"Merged from audit_economics and audit_permissions; already recorded in README 'Scope of the surcharge' and docs/AUDIT_RESPONSES.md item 4. The brief fixes the flags at 0x20CC, which carry no liquidity callbacks, so a holder who adds a MONEYBACK-only position just above spot, lets buyers (who pay 4.25%) move the price through it and removes the position receives IMD having paid no hook fee or surcharge and having earned the 1.25% LP fee, while a direct seller in the same window pays up to 24.25%. Any pool for MONEYBACK created without this hook is likewise fee-free. Not a code defect within the fixed flags; a design limit the requester accepts, recorded so the stated 2000 bps sell-surcharge guarantee is read correctly.","line":297,"path":"src/MoneyBackHook.sol","reproduction":"Existing test test/MoneyBackRealV4.t.sol::test_liquidityConversionIsOutsideSwapSurcharge (both currency orders, real v4-core) at block.timestamp == initializedAt: add 100_000e18 liquidity MONEYBACK-only over ticks 60..120 -> hook.pending() unchanged; a buyer swaps 10_000e18 IMD exact-input -> pending == 425e18 (buyer's fee); remove the position -> delta is IMD-only and pending still 425e18 while currentSurchargeBps() == 2000. Expected under a literal reading of the brief: the LP's conversion carries a surcharge. Actual: 0 bps.","severity":"info","snippet":"            beforeAddLiquidity: false,","title":"Liquidity provision converts MONEYBACK to IMD inside the surcharge window with no hook fee and no surcharge (retained constraint of the fixed 0x20CC flags)"},{"citation":"resolved","description":"From audit_permissions; a privilege map, not a defect. The brief grants RoundPayout its owner (the paying wallet) and the README documents the powers: the owner alone chooses recipients, amounts and token (payRound), may send any held token anywhere at any time including while paused (sweep has no whenNotPaused and no cap), may pause, and may renounce. After renounceOwnership there is no owner and no permissionless path, so every token funded in (by the wallet or by third parties through the permissionless fund()) is unrecoverable. There is no unprivileged amplifier: every state write is onlyOwner + nonReentrant and holders are never credited on chain. No fix required unless the requester wants a disabled renounce or a timelock.","line":73,"path":"src/RoundPayout.sol","reproduction":"Owner calls renounceOwnership() while RoundPayout holds 1_000e18 IMD (e.g. after a third party called fund(IMD, 1_000e18)). Expected per header: no further payRound, sweep, pause or retry. Actual: exactly that; owner() == address(0), every onlyOwner call reverts OwnableUnauthorizedAccount, the 1_000e18 IMD is stranded permanently. Covered by test/RoundPayout.t.sol::test_renounceOwnershipLocksEverything and test_sweepWorksWhilePaused.","severity":"info","snippet":"        _transferOwnership(address(0));","title":"Trust assumption: the RoundPayout owner can pay, sweep (also while paused) or strand every funded token; renounceOwnership locks balances forever"},{"citation":"resolved","description":"From write_foundry_tests; operational note, no change required. The router holds nothing between calls and has no owner, which matches the brief. Two consequences: MONEYBACK or IMD transferred straight to the router by mistake can never be recovered (sellForEth refunds only tokens minus the consumed amount of the current call); and ETH pushed in by SELFDESTRUCT or a pre-funded address is paid out by _sendAllEth to whoever calls buyWithEth or sellForEth next. Neither is exploitable against other users' funds.","line":420,"path":"src/MoneyBackRouter.sol","reproduction":"Transfer 1 MONEYBACK to the router address, then call any router function: the router's balance stays 1 forever (no function moves it). Force 1 wei ETH into the router via selfdestruct (receive() rejects ordinary sends), then have Alice call buyWithEth: Alice receives the extra wei with her dust refund.","severity":"info","snippet":"        uint256 amount = address(this).balance;","title":"MoneyBackRouter has no rescue path: ERC-20 sent directly to it is stuck, ETH forced into it is paid to the next trader"}],"hash":"8c3f4199cfc1ffe28d4e4934b9fee36a4a9a821e59bc069dc6374a04a874d9dc","nodeId":"1a045de1-8896-45b7-bfe5-8a5d50fd7e99","outcome":"completed","summary":"The review is complete. The findings file is at the repository root and the working tree has no other changes.\n\n**Outcome:** 7 findings, 26 coverage rows covering all 24 listed entry points. The code deploys and trades correctly on the real v4-core PoolManager. Nothing is critical or high. Two medium items, two low, three informational.\n\n**What I kept from the specialists, merged and reproduced**\n\n- **Medium, hook fee base depends on the swap form.** All four specialists reported this one root cause. Exact-output sells are charged 24.25% of the net IMD received, which is 19.5% of the IMD the pool paid out, while exact-input sells are charged 24.25% of the pool output. I re-ran the specialist proof on the vendored PoolManager and it fails on this code for exactly the stated reason. My own scratch run also confirmed the buy-side mirror. The proof is attached.\n- **Low, RoundPayout failed counter drifts.** Four reports, one root cause: legs are counted but recipients are cleared, so a recipient that fails twice in one batch leaves the counter stuck at one forever. Proof re-run and attached. No funds are at risk.\n- **Low, router buy is all-or-nothing but its NatSpec promises refunds and a zero quote.** Three reports merged. I reproduced the wrapped PartialFill revert from quoteBuy and buyWithEth at a size the launch pool cannot absorb.\n- **Three info items:** liquidity provision bypasses the surcharge under the fixed 0x20CC flags, the RoundPayout owner's documented powers, and the router's lack of a rescue path.\n\n**What I added that the specialists missed**\n\n- **Medium, the launch deploys only two of the four contracts.** The univ4_hook manifest can name a token and a hook only. RoundPayout and MoneyBackRouter are deferred to a script that needs a signed broadcast and the already-mined hook address, which the deployment model never runs. The requester must decide whether a two-phase deployment by the paying wallet is acceptable or whether the two auxiliaries leave the brief.\n\n**Dropped or downgraded:** nothing was dropped outright. The two reports that rated the fee asymmetry low were merged up to medium because the stated 2000 bps anti-dump surcharge is effectively 1610 bps for any seller who chooses exact-output through a generic router. The static-analysis lines were checked and none became a finding on their own.\n\n**Caveat on proofs:** the repository vendors no forge-std, so both attached proofs use the raw cheatcode interface instead of importing it. They fail on this code and were verified from the findings file's own embedded source.","treeHash":null,"usage":{"cachedInputTokens":1709496,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":34986,"runtime":"claude","turns":25,"wallClockMs":468283}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"3f91b58cf7cd2d45","findings":[{"citation":"resolved","description":"Seam: precision x invariant (Numerical Gap). The brief fixes one rate, 4.25% of the IMD leg on every swap plus a 2000 bps sell surcharge, and the hook's own invariant is that each swap is charged that rate once. But the base the rate is applied to is not the same quantity in the four cases. Cases 2 and 3 (IMD unspecified, afterSwap line 425) charge on the IMD the pool actually moved. Cases 1 and 4 (IMD specified, beforeSwap lines 397-398) charge on the user's specified amount, which for a buy is pool input PLUS fee and for a sell is pool output MINUS fee. The same economic trade therefore pays a different hook fee depending only on the sign of amountSpecified. For sells the exact-output path is strictly cheaper for the seller: with surcharge 2000 bps a seller who specifies the IMD they want nets 100 IMD per 124.25 IMD taken from the pool (fee 19.51% of gross), while a seller who specifies MONEYBACK in nets 94.12 IMD for the identical pool movement (fee 24.25% of gross). The anti-dump surcharge, the launch's stated protection, is thus ~19.5% smaller for anyone who sells through a generic v4 router with exact-output (the project router only does exact-input, so router users pay the higher rate and direct PoolManager users can pay the lower one). Buys invert: exact-input buyers pay 443 bps of the IMD the pool receives, exact-output buyers 425 bps. Nothing in the swap flow (slippage, LP fee, surcharge decay) explains the gap; it is the different fee base. Minimal fix preserving the agreed rates: make all four cases charge r = 425 (+ surcharge) bps of the IMD the pool moves. In beforeSwap, exact-input buy: hookFee = A * r / (BPS + r) (so pool gets A - fee and fee == r/BPS of it); exact-output sell: hookFee = A * r / (BPS - r) rounded up (so gross = A + fee and fee == r/BPS of gross); keep afterSwap's PartialFill check on the same formula. Alternatively charge every case on the swapper-facing amount; either way one base. If the author keeps the current per-case bases as a design decision, the README fee table should state that exact-output sells are surcharged on the net amount, i.e. 20% of what the seller receives rather than 20% of what leaves the pool.","line":397,"path":"src/MoneyBackHook.sol","reproduction":"Real v4-core PoolManager (test/MoneyBackRealV4.t.sol fixture, token as currency0, both currencies 18 decimals, pool price 2^96, liquidity 1e24 in ticks -600..600, block.timestamp == initializedAt so currentSurchargeBps() == 2000). (a) Exact-output sell: swap(key, {zeroForOne: true, amountSpecified: +100e18, sqrtPriceLimitX96: MIN+1}). Pool delta on IMD: +124.25e18; hook mints 24.25e18 IMD claims; swapper receives exactly 100e18 IMD and pays 125838420233840636979 MONEYBACK. Fee as share of IMD leaving the pool: 24.25e18 * 10000 / 124.25e18 = 1951 bps. (b) Fresh identical pool, exact-input sell of that same 125838420233840636979 MONEYBACK: swap(key, {zeroForOne: true, amountSpecified: -125838420233840636979, limit MIN+1}). Pool delta on IMD: +124.25e18 (identical pool movement); hook mints 30.130625e18; seller receives 94.119375e18 IMD. Fee share of IMD leaving the pool: 2425 bps. Expected: identical pool movement pays the identical hook fee (24.25% of 124.25e18 = 30.13e18 in both, or the same net-based figure in both). Actual: the exact-output seller keeps 5.88e18 IMD more, i.e. the surcharge+fee collected is 19.5% lower. Buys: exact-input buy of 100e18 IMD: fee 4.25e18, pool receives 95.75e18, fee = 443 bps of pool input; exact-output buy of the same 94544185551805496940 MONEYBACK: pool receives 95.75e18, fee 4.069375e18 = 425 bps, swapper pays 99.819375e18 instead of 100e18.","severity":"medium","snippet":"        uint256 imdLeg = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n        uint256 hookFee = _accrue(isSell, imdLeg);","title":"Hook fee base differs by which side is specified: exact-output sells pay 1951 bps of the IMD leaving the pool instead of 2425 bps, exact-input buys pay 443 bps of the IMD entering the pool instead of "},{"citation":"resolved","description":"Seam: boundary x invariant. The header invariant (lines 27-29) says rounds(roundId).failedCount is the number of legs still failed. _payLegs increments failedCount once per failed LEG while accumulating the owed amount into one failed[roundId][recipient] slot, but retryFailed (line 313) and writeOffFailed (line 328) subtract exactly 1 when that single slot is cleared. With a recipient that appears k>1 times and fails each time, failedCount ends k-1 above the true number of outstanding legs and nothing can ever bring it to zero: both clearing functions revert NothingFailed once the slot is zero. No funds are lost (the owed amount itself is tracked correctly), but the on-chain reconciliation counter the operator is told to monitor is wrong for that round forever, and off-chain tooling that waits for failedCount == 0 before closing a round never terminates. Minimal fix: in _payLegs increment failedCount only when failed[roundId][recipient] was zero before the add (count distinct failed recipients), and detect the all-failed case with a separate success counter instead of failedCount == n.","line":260,"path":"src/RoundPayout.sol","reproduction":"RoundPayout funded with 100 of a plain ERC-20 that reverts on transfer to address(0) (OpenZeppelin semantics; IMD and MONEYBACK both do). Owner calls payRound(1, token, [B, address(0), address(0)], [1, 10, 20], 0, 0, 0). Result: B paid 1; two legs fail; rounds(1).failedCount == 2; failed(1, address(0)) == 30. Owner calls writeOffFailed(1, address(0)): failed(1, address(0)) == 0 but rounds(1).failedCount == 1. Expected per the stated invariant: 0 legs still failed. Actual: 1, and both writeOffFailed(1, address(0)) and retryFailed(1, [address(0)]) now revert NothingFailed(1, address(0)), so the counter can never reach 0. Verified with a scratch Foundry test (test/scratch/FailedCount.t.sol, not kept).","severity":"low","snippet":"                failed[roundId][recipient] += amount;\n                ++failedCount;","title":"RoundPayout.failedCount counts legs but retryFailed/writeOffFailed decrement per recipient, so a duplicated failing recipient leaves failedCount permanently above zero"},{"citation":"resolved","description":"Seam: boundary x invariant. Leg 2 of a buy is an exact-input IMD swap, which is the hook's case 1: afterSwap reverts PartialFill unless the pool consumes the whole adjusted input. So r.imdMid always equals imdOut on any path that returns, imdDust is always 0, and the comments at lines 197-198 ('any IMD our pool did not consume are refunded to msg.sender in the same call') and 325 describe behaviour that cannot occur. The user-facing consequence is at the liquidity boundary: once a buy's IMD exceeds what the launch pool's remaining range can absorb, the whole transaction reverts (ETH is returned by the revert, nothing is bought) rather than filling what is available and refunding the rest, and quoteBuy for that size reverts too instead of returning 0 as its NatSpec (line 246) states. The README table already says exact-input buys revert on partial fill, so this is a documentation and integration-contract defect, not a loss of funds; a front end that treats a quoteBuy revert as 'route yields nothing' will misreport a merely too-large order. Fix: either correct the three comments (and have the front end bisect on PartialFill), or route buys as exact-output in MONEYBACK (case 2, partial fill allowed, fee on actual IMD moved) when a partial fill is wanted.","line":325,"path":"src/MoneyBackRouter.sol","reproduction":"Real v4-core PoolManager fixture (token as currency0): launch pool liquidity 1e24 in ticks -600..600 at price 2^96; public ETH/IMD pool initialized at 2^96 with liquidity 1e27 in ticks -600..600. quoteBuy(1 ether) returns 936075060335946014 MONEYBACK. quoteBuy(100000 ether): expected 0 or a partial amount per NatSpec; actual: reverts with 260 bytes of revert data, v4 WrappedError(hook, afterSwap.selector, PartialFill(), HookCallFailed()). buyWithEth{value: 100000 ether}(1, block.timestamp): expected per lines 197-198 a partial fill with the unconsumed IMD refunded; actual: reverts with the same WrappedError (selector 0x90bfb865), no MONEYBACK bought. Verified with a scratch Foundry test (test/scratch/RouterBuyPartial.t.sol, not kept).","severity":"low","snippet":"        uint256 imdDust = imdOut - r.imdMid; // leg 2 can consume less than it was offered","title":"MoneyBackRouter.buyWithEth never partially fills and never refunds IMD: a buy larger than the launch pool can absorb reverts with the hook's PartialFill, and quoteBuy reverts instead of returning 0, c"}],"hash":"bffda3786b54401c0af4d005ccd8f3eab9d707c766b33cfabfec4acf6d18047d","nodeId":"8efeab3b-df8d-4f64-9e70-2dbc0cd87db2","outcome":"completed","summary":"The review is complete. The findings file is at `.imd-findings.json` with 3 findings and 28 coverage rows (all 24 listed entry points plus 4 invariants); the scratch tests were removed and no source file was changed.\n\n**What I found**\n\n- **Medium, hook fee base is inconsistent across the four swap cases.** The brief fixes one rate (4.25% plus the 2000 bps sell surcharge) on \"the IMD leg\", but cases 1 and 4 charge it on the user's specified amount while cases 2 and 3 charge it on the IMD the pool actually moves. Measured on the real v4 PoolManager: selling the same 125.84 MONEYBACK moves 124.25 IMD out of the pool either way, yet the exact-output seller pays 24.25 IMD in fees and the exact-input seller pays 30.13. The surcharge collected is about 19.5% lower for anyone selling exact-output through a generic v4 router. Buys invert (443 bps versus 425 bps of pool input). The fix formulas that preserve the agreed rates are in the finding.\n- **Low, RoundPayout's failed-leg counter can never reach zero after a duplicated failing recipient.** The counter increments per leg but retry and write-off decrement once per recipient. A round paid to `[B, 0x0, 0x0]` ends with the counter stuck at 1 after the write-off, and both clearing functions then revert.\n- **Low, router buys never partially fill.** Leg 2 is the hook's exact-input case, so an oversize buy reverts with the hook's PartialFill and quoteBuy reverts rather than returning 0, contradicting the router's comments about refunding unconsumed IMD. No funds at risk.\n\n**What held in my area.** Delta packing and sign extension, int128 bounds, the PartialFill equality against v4's exact remaining-amount accounting, surcharge decay at 0/900/1799/1800 s, zero-fee rounding below 24 wei, the hook's net-zero PoolManager delta, router settlement and dust refunds, and RoundPayout's checked sums and balance guards. None of the findings rises to critical or high, so no proof test files were attached.","treeHash":null,"usage":{"cachedInputTokens":2112135,"inputTokens":514,"model":"claude-fable-5-1","outputTokens":44421,"runtime":"claude","turns":36,"wallClockMs":648208}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"79373c79d1351eba","findings":[{"citation":"resolved","description":"The brief fixes the hook fee at 4.25% of the IMD leg plus a 2000 bps sell surcharge. The hook uses two different definitions of 'IMD leg' depending on how the swapper phrases the same trade. For an exact-output sell (case 4, IMD specified) imdLeg is the NET IMD the swapper asked for (amountSpecified), and the pool is then asked to pay amountSpecified + fee, so fee/grossPoolOutput = bps/(10000+bps). For an exact-input sell (case 3) imdLeg is the GROSS IMD the pool paid (afterSwap line 425), so fee/grossPoolOutput = bps/10000. The same asymmetry exists on buys: exact-input buy charges 4.25% of what the swapper pays; exact-output buy (line 425, imdLeg = pool IMD input) charges 4.25% of the pool input, i.e. 4.08% of what the swapper pays. Any seller who phrases the sale as exact-output pays 24.25/124.25 = 19.5% of the pool's IMD output at t=0 instead of 24.25%, and 4.08% instead of 4.25% after decay; the payout wallet receives correspondingly less. Traced against the vendored v4-core PoolManager (test/vendor) in both currency orders of the hook's own suite; the figures below are from a real-PoolManager run. This is deterministic and reachable by any swapper through any router that supports exact-output (Universal Router does), so it is a concrete, permanent under-collection relative to the stated rate rather than a loss of funds. Minimal fix that preserves the design: for IMD-specified swaps gross the fee up so it is bps of the gross leg, e.g. fee = imdLeg * bps / (BPS - bps) (floored per part), and keep the afterSwap PartialFill comparison using the same grossed-up fee.","line":397,"path":"src/MoneyBackHook.sol","reproduction":"Real v4-core PoolManager, launch pool initialised at sqrtPrice 2^96 with 1e24 liquidity at ticks [-600,600], block.timestamp = initializedAt (surcharge 2000 bps). (A) Exact-output sell: swap(zeroForOne = sell direction, amountSpecified = +100e18 IMD, widest limit). Observed: swapper receives 100e18 IMD, hook pending += 24.25e18, pool paid 124.25e18 IMD, MONEYBACK consumed 125838420233840636979. (B) Revert state, exact-input sell of the same 125838420233840636979 MONEYBACK (amountSpecified = -125838420233840636979). Observed: pool paid the same 124.25e18 IMD, hook pending += 30.130625e18, swapper receives 94.119375e18. Expected per brief: both forms move the same IMD leg out of the pool and should accrue the same fee (24.25% of 124.25e18 = 30.13e18). Actual: form A accrues 5.88e18 IMD (473 bps of pool output) less than form B for an identical pool movement.","severity":"low","snippet":"        uint256 imdLeg = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n        uint256 hookFee = _accrue(isSell, imdLeg);","title":"Hook fee basis differs by swap form: exact-output sells (and exact-output buys) pay a lower effective hook fee than the exact-input form of the same trade"},{"citation":"resolved","description":"payRound counts one failedCount per failed LEG but accumulates the owed amount per RECIPIENT (failed[roundId][to] += amount). retryFailed (line 313: round.failedCount -= 1) and writeOffFailed (line 328: rounds[roundId].failedCount -= 1) clear a recipient's whole accumulated entry while decrementing the counter by exactly one. After a duplicate recipient with two failed legs is retried or written off, failed[roundId][to] is 0 for every address yet rounds(roundId).failedCount is still 1, and nothing can ever bring it to zero: both retryFailed and writeOffFailed revert NothingFailed for that address. The contract's own invariant comment (line 29: 'rounds(roundId).failedCount is the number of legs still failed') and the README's reading of the record are wrong for this state. No funds are lost; the off-chain engine and any monitoring that gates on failedCount == 0 to consider a round settled would wait forever on such a round. Fix: count distinct recipients (only ++failedCount when failed[roundId][recipient] was 0 before the add), or decrement by the number of legs cleared.","line":260,"path":"src/RoundPayout.sol","reproduction":"Deploy MoneyBackToken t and RoundPayout p(owner = this). t.approve(p, 10e18); p.fund(t, 10e18). Call p.payRound(1, t, [address(0), address(0), 0xA11CE], [1e18, 2e18, 3e18], 0, 0, 0): the two legs to address(0) revert inside MoneyBackToken (ERC20InvalidReceiver) and are recorded; the third pays. State: rounds(1).failedCount == 2, failed(1, address(0)) == 3e18. Call p.writeOffFailed(1, address(0)). Expected: no failed legs remain, failedCount == 0. Actual: failed(1, address(0)) == 0 but rounds(1).failedCount == 1; a second writeOffFailed(1, address(0)) and retryFailed(1, [address(0)]) both revert NothingFailed, so the counter is stuck at 1 forever. Verified with a Foundry run against the unmodified contract.","severity":"low","snippet":"                failed[roundId][recipient] += amount;\n                ++failedCount;","title":"RoundPayout.failedCount is permanently desynchronised from the failed ledger when a recipient repeats in a batch and both legs fail"},{"citation":"resolved","description":"Leg 2 of a buy is an exact-input IMD swap on the launch pool (hook case 1). Since the partial-fill repair, MoneyBackHook.afterSwap reverts PartialFill unless the pool consumed exactly amountSpecified + fee, and the router's returned delta is then swapDelta - hookDelta = -imdOut exactly, so r.imdMid == imdOut on every path that does not revert and imdDust is always 0. The comment on line 325, the buyWithEth NatSpec (lines 197-198: 'any IMD our pool did not consume are refunded to msg.sender') and the quoteBuy NatSpec (line 246: 'Returns 0 when the route yields nothing (buyWithEth would revert)') describe behaviour that no longer exists: a buy whose IMD the launch pool cannot fully absorb reverts in its entirety, and quoteBuy for the same input reverts with the hook's wrapped PartialFill instead of returning 0. The README (line 81) does state the revert, so this is a documentation/dead-code mismatch, not a fund-safety issue; integrators reading the NatSpec to decide whether to catch a revert or test for 0 will get it wrong.","line":325,"path":"src/MoneyBackRouter.sol","reproduction":"Real v4-core PoolManager; public ETH/IMD pool (fee 10000, spacing 100, no hook) initialised at 2^96 with 1e26 liquidity at [-600,600]; launch pool initialised at 2^96 with 1e24 liquidity at [-600,600]. Call router.quoteBuy(1e26). Expected per NatSpec: returns 0 (buyWithEth would revert). Actual: reverts with the PoolManager's WrappedError(hook, afterSwap.selector, PartialFill(), HookCallFailed()). Likewise router.buyWithEth{value: 1e26}(0, block.timestamp) reverts entirely instead of refunding the IMD the launch pool did not consume; no execution path ever reaches poolManager.take(IMD, d.user, imdDust) with imdDust != 0.","severity":"info","snippet":"        uint256 imdDust = imdOut - r.imdMid; // leg 2 can consume less than it was offered\n        if (imdDust != 0) poolManager.take(Currency.wrap(IMD), d.user, imdDust);","title":"Router buy path: unreachable IMD-dust refund and NatSpec that promise a partial-fill refund the hook now forbids; quoteBuy reverts rather than returning 0 for that case"}],"hash":"c73db270bd31e603c6ff031faded1d80931374145d314671cd71094f0e99040d","nodeId":"74c9af46-3369-4c72-b68c-a3fcd67c7271","outcome":"completed","summary":"All three snippets match the tree exactly, the JSON is valid, the scratch directory is removed, and git shows nothing changed in the tree beyond the findings file (which is untracked by design).\n\n## Review complete\n\nI audited the control flow of the four contracts and the manifest against the Execution Trace, Periphery and First Principles guides, tracing every hook callback and router leg against the vendored v4-core PoolManager. The findings are in `.imd-findings.json` with a coverage row for each of the 24 listed entry points plus three invariant rows.\n\n**What held up.** The hook's delta accounting is correct in all four swap cases and both currency orders: the ERC-6909 mint inside the callback is netted by v4's hook credit, the PartialFill guard compares against the exact pool delta v4 passes, and claims are always backed by the time an unlock ends. Callback gating, the single-bind initialisation, the sweep lock (which cannot run inside another unlock), and the router's hash-gated callback, pre-funded settlement and full-refund paths all traced clean. The manifest's constructor arguments, permissions, paired currency, fee and tick spacing agree with the implementation.\n\n**Findings, all reproduced on real v4-core:**\n\n- **Low, fee basis asymmetry.** Exact-output sells are charged on the net IMD received rather than the gross IMD the pool paid, so the same trade phrased as exact-output pays 24.25e18 instead of 30.13e18 at t=0 for the same pool movement (473 bps of pool output less to the payout wallet). Exact-output buys have the same shape. Any swapper using a router that supports exact-output can choose the cheaper form.\n- **Low, RoundPayout counter desync.** `failedCount` counts legs but the ledger accumulates per recipient, so a duplicated failed recipient leaves `failedCount` stuck at 1 after a write-off, with nothing owed and no call that can clear it.\n- **Info, router dead code and stale NatSpec.** Since the partial-fill repair, the buy path's IMD-dust refund is unreachable and `quoteBuy` reverts rather than returning 0 for an unfillable leg, contrary to its NatSpec.\n\n**Not reached.** I did not exercise the live Robinhood Chain public ETH/IMD pool or the launch factory's seeding logic; the on-chain observations in the docs were taken as given. No finding reached high severity, so no proof test files were attached.","treeHash":null,"usage":{"cachedInputTokens":1361383,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":49522,"runtime":"claude","turns":24,"wallClockMs":688904}},{"artifacts":[],"attempt":2,"bundleHash":null,"device":"cd33e6f138d75a0c","findings":[],"hash":"cea6ea728158f106abeaa59f9a9192607c18e73f73573dbf8ba8b0b623f055e7","nodeId":"c699d681-d6f8-494c-b8e6-5446fde23aa2","outcome":"failed","summary":"needs input: Finding de047bb64864aac3bea21b7356af302d124d6ea2dc9a30151500f38b65fb2d72 reproduces. The installed univ4_hook schema and manifestContracts implementation select only MoneyBackToken and MoneyBackHook. Neither constructor deploys RoundPayout or MoneyBackRouter. The auxiliary script requires the already deployed and initialized hook and a separately signed broadcast. No change confined to launch.json can deploy all four contracts. The brief has not authorized a two-phase deployment or dropping the auxiliaries; choosing either would change the requested deliverable. The existing manifest is unchanged. forge build, forge test (231 passed), and imd launch check all passed locally, with build artifacts confined to test/scratch. — Should the brief explicitly authorize the paying wallet to deploy RoundPayout and MoneyBackRouter after the factory launch using the existing script, remove those auxiliaries from the launch deliverable, or retain the four-contract requirement and expand the assignme","treeHash":null,"usage":{"cachedInputTokens":546560,"inputTokens":56952,"model":"gpt-6.1-sol","outputTokens":4020,"runtime":"codex","turns":4,"wallClockMs":138515}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"4ebee02fc506f27c","findings":[{"citation":"resolved","description":"Asymmetry / Trust Gap seam economics x asymmetry (pair: beforeSwap case 4 vs afterSwap case 3; actor: any seller choosing the swap shape). For a sell the hook fee (425 bps + decaying surcharge up to 2000 bps) is applied to `imdLeg`. In case 3 (exact-input sell, afterSwap line 425-426) imdLeg is the IMD the pool actually paid out, so the fee is bps/10000 of the pool leg. In case 4 (exact-output sell, beforeSwap line 397) imdLeg is `amountSpecified`, the NET IMD the seller asked for; the pool is then asked for amountSpecified + fee (line 418-421). The fee is therefore bps/(10000+bps) of the pool leg, i.e. 2425/12425 = 19.52% instead of 24.25% at t=0, and 425/10425 = 4.08% instead of 4.25% after the window. The two sells move the same MONEYBACK into the pool and the same IMD out of it, but the swapper who specifies output keeps ~6.2% more IMD and the immutable `payout` wallet receives ~19.5% less fee. The brief's '2000 bps surcharge' deterrent is thus effectively 1610 bps for a seller who specifies the output (and the mirror holds for buys: exact-input buys pay 4.25% of the gross including the fee, i.e. 4.44% of the pool leg, while exact-output buys pay 4.25% of the pool leg). The header NatSpec documents the per-case imdLeg, so this may be accepted design; it is reported because the same economic action is charged two different rates depending only on how the swap is encoded, and sellers (not the payout) pick the favourable side. Minimal fix preserving intent: in case 4 compute the fee on the gross leg, fee = amountSpecified * bps / (BPS - bps) (so fee/(amountSpecified+fee) == bps/BPS), and keep the afterSwap PartialFill check consistent with it; or state in the README that fees are on the specified amount.","line":397,"path":"src/MoneyBackHook.sol","reproduction":"Real v4-core PoolManager (test/MoneyBackRealV4.t.sol fixture, MONEYBACK as currency0, 1:1 price, liquidity 1e24 over [-600,600]), block.timestamp == initializedAt (surcharge 2000 bps). Sell A: swap(zeroForOne=true, amountSpecified=-10_000e18, limit MIN+1) -> pool IMD leg 9_778.437925485827453892e18, hook fee 2_371.271196930313157568e18 (2424 bps of leg), seller receives 7_407.166728555514296324e18. Reset state. Sell B: swap(zeroForOne=true, amountSpecified=+7_869.970161356802779792e18 (= legA*10000/12425), limit MIN+1) -> MONEYBACK in 9_999.999999999999999999e18, pool IMD leg 9_778.437925485827453891e18 (identical), hook fee 1_908.467764129024674099e18 (1951 bps of leg), seller receives 7_869.970161356802779792e18. Expected: same MONEYBACK sold and same IMD out of the pool => same fee. Actual: seller B pays 462.80e18 IMD less fee and the payout wallet gets that much less. Scratch test used: test/scratch/Asym.t.sol::AsymSellTest.test_sellBasisAsymmetry (logs above).","severity":"low","snippet":"        uint256 imdLeg = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);","title":"Sell fee basis differs by swap shape: exact-output sells pay ~19.5% of the pool's IMD leg where exact-input sells pay 24.25%"},{"citation":"resolved","description":"Asymmetry (storage-write symmetry: writer `+=`/`++` in _payLegs vs `delete`/`-= 1` in retryFailed line 312-313 and writeOffFailed line 327-328). _payLegs increments failedCount once per failed LEG while accumulating the owed amount into one `failed[roundId][to]` ENTRY. retryFailed and writeOffFailed clear one entry and decrement failedCount by exactly one. When `to` contains the same address twice and both legs fail, failedCount becomes 2 but only one entry exists; clearing it leaves `rounds(roundId).failedCount == 1` forever, with no path back to zero (a further writeOff/retry reverts NothingFailed). The header invariant 'rounds(roundId).failedCount is the number of legs still failed' is broken and the off-chain engine that keys on failedCount == 0 to consider a round closed can never close it. No funds are at risk (totalPaid and failed[] stay correct), owner-only path, so low. Fix: count failed recipients rather than legs (increment failedCount only when `failed[roundId][recipient]` was zero before the add), or reject duplicate recipients in payRound.","line":260,"path":"src/RoundPayout.sol","reproduction":"Deploy RoundPayout(owner). Token T: ERC-20 whose transfer reverts for recipient A (blacklist/reverting contract), mint 100 to RoundPayout. Owner calls payRound(1, T, [A, A, B], [10, 20, 5], 0, 0, 0). Result: failed[1][A] == 30, rounds(1).failedCount == 2, Paid(1,B,5). Owner calls writeOffFailed(1, A) (or retryFailed(1,[A]) after A is unblocked). Expected: failedCount == 0 and nothing owed. Actual: failed[1][A] == 0 but rounds(1).failedCount == 1; writeOffFailed(1, A) again reverts NothingFailed(1, A) and nothing can ever decrement it. Scratch: test/scratch/Asym.t.sol::FailedCountTest.","severity":"low","snippet":"                failed[roundId][recipient] += amount;\n                ++failedCount;","title":"RoundPayout.failedCount drifts permanently when the same recipient fails twice in one round"},{"citation":"resolved","description":"Access Control map, recorded as a trust assumption and not a defect (the brief grants RoundPayout owner powers and the README documents them). The owner alone decides recipients and amounts (payRound), can send any token the contract holds to any address at any time including while paused (sweep, line 197, has no whenNotPaused and no cap), can pause rounds, and can renounce. After renounceOwnership there is no owner and no permissionless path for anything, so every token funded into RoundPayout (by the wallet or by third parties via the permissionless fund()) is unrecoverable. There is no unprivileged amplifier: no race (every state write is onlyOwner + nonReentrant), no retroactive sweep of user-credited value (holders are never credited on-chain), no access gap. Listed so the judge has the privilege map; no fix required unless the requester wants a timelock or a disabled renounce.","line":73,"path":"src/RoundPayout.sol","reproduction":"Owner calls renounceOwnership() while RoundPayout holds 1_000e18 IMD (e.g. after a third party called fund(IMD, 1_000e18)). Expected per header: 'no further payRound, sweep, pause or retry'. Actual: exactly that; owner() == address(0), every onlyOwner call reverts OwnableUnauthorizedAccount, the 1_000e18 IMD is stranded permanently. Likewise sweep(IMD, anyAddr, balance) succeeds while paused.","severity":"info","snippet":"        _transferOwnership(address(0));","title":"Trust assumption: RoundPayout owner (the paying wallet) can move or strand every funded token; renounceOwnership locks balances forever"},{"citation":"resolved","description":"Trust Gap seam economics x asymmetry, already reported to the requester in the imported audit and retained because the brief fixes flags at 0x20CC. Recorded here for completeness of the Asymmetry coverage, not as a new defect. A holder adds a single-sided MONEYBACK position just above spot, lets buyers (who pay 4.25%) swap through it, then removes the position as IMD: the holder has sold MONEYBACK for IMD paying 0 hook fee and 0 surcharge inside the window, while a swapper selling the same amount pays up to 24.25%. Any mitigation (liquidity callbacks or a position gate) changes the explicit 0x20CC requirement, so it is a scope decision for the requester, not a fix the author can apply.","line":297,"path":"src/MoneyBackHook.sol","reproduction":"test/MoneyBackRealV4.t.sol::test_liquidityConversionIsOutsideSwapSurcharge (both currency orders): modifyLiquidity(+100_000e18 MONEYBACK-only across ticks 60/120) -> hook.pending() unchanged; buy 10_000e18 IMD exact-input -> pending == 425e18 (buyer paid); modifyLiquidity(-100_000e18) returns IMD only -> pending still 425e18 while currentSurchargeBps() == 2000. Expected under the brief's reading 'surcharge on every sell in the window': the LP's conversion pays something. Actual: zero.","severity":"info","snippet":"            beforeAddLiquidity: false,","title":"Known retained constraint: liquidity provision converts MONEYBACK to IMD with no hook fee and no surcharge (documented in docs/AUDIT_RESPONSES.md item 4)"}],"hash":"d48e63ba7ab416ca76b690745270f4a95cb20d3011402ec9c071003f669240f2","nodeId":"12d3a277-2849-42df-adaa-d4710c48bcbf","outcome":"completed","summary":"The review is complete. The findings file is valid JSON, the scratch tests are removed, and the source tree is unchanged.\n\n**Outcome.** No critical or high defects in the Access Control, Trust Gap or Asymmetry area. The permission model matches the brief: the hook, router and token have no owner, setter, pause or upgrade path, every hook callback and both unlock callbacks refuse any caller but the PoolManager, and only RoundPayout has owner powers. The manifest's constructor arguments, permissions, fee tier and paired currency all agree with the implementation.\n\n**Findings written to .imd-findings.json** (two low, two info), each with a concrete reproduction:\n\n- **Low, sell fee basis asymmetry.** An exact-output sell is charged on the net IMD the seller asks for, an exact-input sell on the pool's full IMD leg. Against real v4 math, two sellers moving the same 9,778 IMD out of the pool pay 1,908 versus 2,371 IMD in fees, so the informed seller keeps 463 IMD more and the payout wallet gets ~19.5% less. The effective surcharge drops from 20% to about 16% of the pool leg. The NatSpec documents this per-case basis, so it may be accepted design, but the choice sits with sellers rather than the payout.\n- **Low, RoundPayout failedCount drift.** A recipient that fails twice in one round bumps failedCount by two but creates one owed entry. Clearing it leaves failedCount stuck at one forever, breaking the header invariant the off-chain engine may key on. No funds affected.\n- **Info, RoundPayout owner trust assumptions.** Uncapped sweep while paused and renounceOwnership stranding all balances. No unprivileged amplifier, recorded for the judge's privilege map.\n- **Info, LP conversion bypasses hook fee and surcharge.** Already in docs/AUDIT_RESPONSES.md item 4 and retained because flags are fixed at 0x20CC. Listed for coverage completeness.\n\n**Coverage.** All 24 verifier entry points have rows, plus rows for the manifest and two protocol invariants. Static-analysis leads were checked: the shift-order, arbitrary-send-eth, reentrancy and strict-equality lines are false positives on this code. Not reached within budget: a fork rehearsal of the actual factory allocation path, which is outside the delivered tree.","treeHash":null,"usage":{"cachedInputTokens":1298112,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":34185,"runtime":"claude","turns":25,"wallClockMs":547040}},{"artifacts":[],"attempt":1,"bundleHash":"f02b4a936dcf2aa3cde41ebbe7687a8178381d5a866673296c3e4d9c07099dc0","device":"59b51c92352647b4","findings":[],"hash":"dfbad1ae62c24c7634937e2f536ce1ad05c8a05c50428eff5c3feb92746fce77","nodeId":"66dfebc5-8b5e-4a6d-8ca0-93799856e148","outcome":"completed","summary":"Fixed fee-base consistency, duplicate payout-failure counting, and router documentation. Fee parameters remain unchanged.\n\nAll 231 tests pass, along with build, formatting, and manifest checks. All 14 findings are answered in `.imd-responses.json`.\n\nSlither was unavailable. No deployment transactions were broadcast.","treeHash":"aa3b4fd45a85fe09d040aed45bcdb8f7c96082b2","usage":{"cachedInputTokens":3868416,"inputTokens":187826,"model":"gpt-6-astra","outputTokens":36673,"runtime":"codex","turns":8,"wallClockMs":1239262}}],"verification":[{"checks":[{"durationMs":4229,"exitCode":0,"name":"build","output":"Compiling 57 files with Solc 0.8.26\nSolc 0.8.26 finished in 4.09s\nCompiler run successful!\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/MoneyBackHook.sol:344:13\n    │\n344 │         if (elapsed >= SURCHARGE_DURATION) return 0;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MoneyBackHook.sol:376:27\n    │\n376 │         _imdIsCurrency0 = imdIsCurrency0;\n    │                           ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:397:39\n    │\n397 │         uint256 imdLeg = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:397:74\n    │\n397 │         uint256 imdLeg = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n    │                                                                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/RoundPayout.sol:61:32\n   │\n61 │     function transferOwnership(address newOwner) external onlyOwner {\n   │                                ━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MoneyBackHook.sol:481:9\n    │\n481 │         emit FeeAccrued(isSell, baseFee, surcharge, imdLeg);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:416:46\n    │\n416 │             uint256 specified = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n    │                                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:416:81\n    │\n416 │             uint256 specified = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n    │                                                                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:421:54\n    │\n421 │             if (imdDelta != params.amountSpecified + int256(baseFee + surcharge)) revert PartialFill();\n    │                                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:425:41\n    │\n425 │         uint256 imdLeg = imdDelta < 0 ? uint256(-imdDelta) : uint256(imdDelta);\n    │                                         ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:425:62\n    │\n425 │         uint256 imdLeg = imdDelta < 0 ? uint256(-imdDelta) : uint256(imdDelta);\n    │                                                              ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackHook.sol:439:9\n    │\n439 │         poolManager.unlock(\"\");\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/MoneyBackHook.sol:439:9\n    │\n439 │         poolManager.unlock(\"\");\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MoneyBackHook.sol:453:9\n    │\n453 │         emit Swept(amount, payout);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:490:17\n    │\n490 │         if (x > uint256(uint128(type(int128).max))) revert AmountOverflow();\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/MoneyBackHook.sol:490:25\n    │\n490 │         if (x > uint256(uint128(type(int128).max))) revert AmountOverflow();\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:491:16\n    │\n491 │         return int128(uint128(x));\n    │                ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:491:23\n    │\n491 │         return int128(uint128(x));\n    │                       ━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/RoundPayout.sol:360:40\n    │\n360 │         (bool ok, bytes memory data) = token.call(abi.encodeCall(IERC20.transferFrom, (from, to, amount)));\n    │                                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RoundPayout.sol:193:9\n    │\n193 │         emit Funded(token, msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/RoundPayout.sol:354:40\n    │\n354 │         (bool ok, bytes memory data) = token.call(abi.encodeCall(IERC20.transfer, (to, amount)));\n    │                                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/RoundPayout.sol:197:82\n    │\n197 │     function sweep(address token, address to, uint256 amount) external onlyOwner nonReentrant {\n    │                                                                                  ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RoundPayout.sol:200:9\n    │\n200 │         emit Swept(token, to, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/RoundPayout.sol:348:22\n    │\n348 │         (ok, data) = token.call(abi.encodeCall(IERC20.transfer, (to, amount)));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/RoundPayout.sol:223:40\n    │\n223 │     ) external onlyOwner whenNotPaused nonReentrant returns (uint256 totalPaid) {\n    │                                        ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RoundPayout.sol:244:9\n    │\n244 │         emit RoundPaid(roundId, token, ledgerHash, twapCloseX96, totalEligibleLoss, totalPaid, n);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/RoundPayout.sol:348:22\n    │\n348 │         (ok, data) = token.call(abi.encodeCall(IERC20.transfer, (to, amount)));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RoundPayout.sol:258:17\n    │\n258 │                 emit Paid(roundId, recipient, 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/RoundPayout.sol:262:17\n    │\n262 │                 emit PayFailed(roundId, recipient, amount, reason);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `failed` is updated\n    ╭▸ src/RoundPayout.sol:348:22\n    │\n348 │         (ok, data) = token.call(abi.encodeCall(IERC20.transfer, (to, amount)));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/RoundPayout.sol:302:13\n    │\n302 │             needed += owed;\n    │             ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/RoundPayout.sol:288:9\n    │\n288 │         nonReentrant\n    │         ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RoundPayout.sol:316:17\n    │\n316 │                 emit Paid(roundId, recipient, 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/RoundPayout.sol:318:17\n    │\n318 │                 emit PayFailed(roundId, recipient, amount, reason);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/RoundPayout.sol:301:28\n    │\n301 │             if (owed == 0) revert NothingFailed(roundId, to[i]);\n    │                            ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/RoundPayout.sol:309:30\n    │\n309 │             if (amount == 0) revert NothingFailed(roundId, recipient);\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/RoundPayout.sol:324:77\n    │\n324 │     function writeOffFailed(uint256 roundId, address to) external onlyOwner nonReentrant {\n    │                                                                             ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackRouter.sol:389:24\n    │\n389 │         r = abi.decode(poolManager.unlock(data), (Result));\n    │                        ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackRouter.sol:422:22\n    │\n422 │         (bool ok,) = to.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/MoneyBackRouter.sol:204:13\n    │\n204 │         if (block.timestamp > deadline) revert Expired();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MoneyBackRouter.sol:212:9\n    │\n212 │         emit BoughtWithEth(msg.sender, r.amountIn, r.imdMid, tokensOut);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackRouter.sol:229:14\n    │\n229 │         if (!IERC20(token).transferFrom(msg.sender, address(this), tokens)) revert TransferFromFailed();\n    │              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackRouter.sol:235:29\n    │\n235 │         if (unused != 0 && !IERC20(token).transfer(msg.sender, unused)) revert TransferFailed();\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/MoneyBackRouter.sol:225:13\n    │\n225 │         if (block.timestamp > deadline) revert Expired();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MoneyBackRouter.sol:237:9\n    │\n237 │         emit SoldForEth(msg.sender, r.amountIn, r.imdMid, ethOut);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:227:22\n    │\n227 │         if (tokens > uint256(uint128(type(int128).max))) revert AmountOverflow();\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/MoneyBackRouter.sol:227:30\n    │\n227 │         if (tokens > uint256(uint128(type(int128).max))) revert AmountOverflow();\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackRouter.sol:400:13\n    │\n400 │         try poolManager.unlock(data) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:306:22\n    │\n306 │         r.amountIn = uint256(-a0);\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/MoneyBackRouter.sol:307:26\n    │\n307 │         uint256 imdOut = uint256(a1);\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[unused-return]: return value of an external call is not used\n    ╭▸ src/MoneyBackRouter.sol:312:13\n    │\n312 │             poolManager.settle{value: r.amountIn}();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:320:20\n    │\n320 │         r.imdMid = uint256(-imdDelta);\n    │                    ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:321:23\n    │\n321 │         r.amountOut = uint256(tokenDelta);\n    │                       ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:339:22\n    │\n339 │         r.amountIn = uint256(-tokenDelta);\n    │                      ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/MoneyBackRouter.sol:343:13\n    │\n343 │             poolManager.settle();\n    │             ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:345:26\n    │\n345 │         uint256 imdOut = uint256(imdDelta);\n    │                          ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:350:20\n    │\n350 │         r.imdMid = uint256(-a1);\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/MoneyBackRouter.sol:351:23\n    │\n351 │         r.amountOut = uint256(a0);\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/MoneyBackRouter.sol:362:24\n    │\n362 │         if (amountIn > uint256(uint128(type(int128).max))) revert AmountOverflow();\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/MoneyBackRouter.sol:362:32\n    │\n362 │         if (amountIn > uint256(uint128(type(int128).max))) revert AmountOverflow();\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:367:35\n    │\n367 │                 amountSpecified: -int256(amountIn),\n    │                                   ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:405:40\n    │\n405 │             if (reason.length == 36 && bytes4(reason) == QuoteResult.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[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/MoneyBackRouter.sol:422:22\n    │\n422 │         (bool ok,) = to.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n     ╭▸ test/MoneyBackRouter.t.sol:1132:24\n     │\n1132 │         uint256 now_ = block.timestamp;\n     │                        ━━━━━━━━━━━━━━━\n1133 │         vm.warp(ts);\n     │         ─────────── `vm.warp` changes this environment here\n     │\n     ├ help: capture it with `vm.getBlockTimestamp()` instead\n     ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n     ╭▸ test/MoneyBackRouter.t.sol:1159:53\n     │\n1159 │         uint256 bought = _buyAs(ALICE, 10 ether, 0, block.timestamp);\n     │                                                     ━━━━━━━━━━━━━━━\n1160 │         vm.warp(T0 + 3600);\n     │         ────────────────── `vm.warp` changes this environment here\n     │\n     ├ help: capture it with `vm.getBlockTimestamp()` instead\n     ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n     ╭▸ test/MoneyBackRouter.t.sol:1518:49\n     │\n1518 │         assertEq(_sellAs(ALICE, tokens, quoted, block.timestamp), quoted, \"sell delivers exactly the quote\");\n     │                                                 ━━━━━━━━━━━━━━━\n1519 │         vm.warp(T0 + 1800);\n     │         ────────────────── `vm.warp` changes this environment here\n     │\n     ├ help: capture it with `vm.getBlockTimestamp()` instead\n     ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":893,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/DeployAuxiliaries.t.sol:DeployAuxiliariesTest\n[PASS] test_deploysBothAuxiliariesWithPayingWalletOwner() (gas: 2838233)\n[PASS] test_uninitializedHookRefused() (gas: 21545030)\n[PASS] test_unresolvedAddressesRefused() (gas: 61999)\n[PASS] test_wrongChainRefused() (gas: 35325)\n[PASS] test_wrongManagerRefused() (gas: 41055)\n[PASS] test_wrongWalletRefused() (gas: 4940831)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 60.89ms (53.07ms CPU time)\n\nRan 12 tests for test/MoneyBackRealV4.t.sol:MoneyBackRealV4Token0Test\n[PASS] testFuzz_fullFillSpecifiedFee(uint96,uint32,bool) (runs: 256, μ: 200313, ~: 206711)\n[PASS] test_exactInputBuyExhaustedLiquidityReverts() (gas: 594713)\n[PASS] test_exactInputBuyTightLimitRevertsWithoutOvercharge() (gas: 216118)\n[PASS] test_exactOutputSellExhaustedLiquidityReverts() (gas: 592318)\n[PASS] test_exactOutputSellTightLimitRevertsWithoutTakingIMD() (gas: 222865)\n[PASS] test_freshSingleSidedPoolCanBuyAndSweepWithNoIMDReserve() (gas: 7620419)\n[PASS] test_fullFillAllFourCasesAndSweep() (gas: 2255552)\n[PASS] test_liquidityConversionIsOutsideSwapSurcharge() (gas: 565795)\n[PASS] test_partialExactOutputBuyChargesActualIMD() (gas: 565447)\n[PASS] test_partialUnspecifiedIMDStillChargesActualFill() (gas: 590858)\n[PASS] test_routerTradesAgainstRealManagerAndRefundsPartialSell() (gas: 2048465)\n[PASS] test_singleSidedPoolRejectsUnfillableExactOutputSell() (gas: 7878714)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 115.49ms (141.43ms CPU time)\n\nRan 12 tests for test/MoneyBackRealV4.t.sol:MoneyBackRealV4Token1Test\n[PASS] testFuzz_fullFillSpecifiedFee(uint96,uint32,bool) (runs: 256, μ: 201021, ~: 201842)\n[PASS] test_exactInputBuyExhaustedLiquidityReverts() (gas: 601438)\n[PASS] test_exactInputBuyTightLimitRevertsWithoutOvercharge() (gas: 222281)\n[PASS] test_exactOutputSellExhaustedLiquidityReverts() (gas: 585713)\n[PASS] test_exactOutputSellTightLimitRevertsWithoutTakingIMD() (gas: 216822)\n[PASS] test_freshSingleSidedPoolCanBuyAndSweepWithNoIMDReserve() (gas: 5560037)\n[PASS] test_fullFillAllFourCasesAndSweep() (gas: 2256502)\n[PASS] test_liquidityConversionIsOutsideSwapSurcharge() (gas: 579232)\n[PASS] test_partialExactOutputBuyChargesActualIMD() (gas: 572071)\n[PASS] test_partialUnspecifiedIMDStillChargesActualFill() (gas: 584148)\n[PASS] test_routerTradesAgainstRealManagerAndRefundsPartialSell() (gas: 2047133)\n[PASS] test_singleSidedPoolRejectsUnfillableExactOutputSell() (gas: 5746588)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 119.22ms (104.77ms CPU time)\n\nRan 27 tests for test/MoneyBackToken.t.sol:MoneyBackTokenTest\n[PASS]\nMoneyBackTokenTest invariants:\n[PASS] invariant_balancesSumToSupply\n[PASS] invariant_totalSupplyNeverChanges\n[PASS] invariant_zeroAddressHoldsNothing\n MoneyBackTokenTest invariants (runs: 128, calls: 4096, reverts: 0)\n\n╭--------------+--------------+-------+---------+----------╮\n| Contract     | Selector     | Calls | Reverts | Discards |\n+==========================================================+\n| TokenHandler | approve      | 1361  | 0       | 0        |\n|--------------+--------------+-------+---------+----------|\n| TokenHandler | transfer     | 1351  | 0       | 0        |\n|--------------+--------------+-------+---------+----------|\n| TokenHandler | transferFrom | 1384  | 0       | 0        |\n╰--------------+--------------+-------+---------+----------╯\n\n[PASS] testFuzz_allowanceAccounting(uint256,uint256) (runs: 512, μ: 118874, ~: 134078)\n[PASS] testFuzz_transferAboveBalanceAlwaysReverts(uint256) (runs: 512, μ: 40842, ~: 40840)\n[PASS] testFuzz_transferConservesSupplyAndBalances(address,uint256) (runs: 512, μ: 92823, ~: 93559)\n[PASS] test_approveSetsAllowanceAndEmits() (gas: 97003)\n[PASS] test_approveZeroSpenderReverts() (gas: 30352)\n[PASS] test_constructorMintsWholeSupplyToDeployer() (gas: 17042)\n[PASS] test_deployerIsMsgSenderNotTxOrigin() (gas: 188189)\n[PASS] test_metadata() (gas: 17630)\n[PASS] test_noPrivilegedSurface() (gas: 165545)\n[PASS] test_rejectsEth() (gas: 32817)\n[PASS] test_supplyIsExactlyOneBillionWithEighteenDecimals() (gas: 13317)\n[PASS] test_transferFromDoesNotEmitApprovalOnSpend() (gas: 115689)\n[PASS] test_transferFromEmptyAccountRevertsEvenForOneWei() (gas: 33309)\n[PASS] test_transferFromExactAllowanceLeavesZero() (gas: 144349)\n[PASS] test_transferFromInfiniteAllowanceIsNotDecremented() (gas: 112829)\n[PASS] test_transferFromRevertsWhenAllowanceBelowAmount() (gas: 80374)\n[PASS] test_transferFromRevertsWhenBalanceBelowAmountEvenWithAllowance() (gas: 86141)\n[PASS] test_transferFromRevertsWithoutAllowance() (gas: 33778)\n[PASS] test_transferFromSpendsAllowance() (gas: 125597)\n[PASS] test_transferFromToZeroAddressReverts() (gas: 83503)\n[PASS] test_transferMovesBalanceAndEmits() (gas: 87232)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 40643)\n[PASS] test_transferToSelfKeepsBalance() (gas: 42924)\n[PASS] test_transferToZeroAddressReverts() (gas: 30391)\n[PASS] test_transferWholeBalanceLeavesZero() (gas: 70165)\n[PASS] test_transferZeroAmountSucceeds() (gas: 37089)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 295.82ms (669.07ms CPU time)\n\nRan 58 tests for test/MoneyBackHook.t.sol:MoneyBackHookTest\n[PASS]\nMoneyBackHookTest invariants:\n[PASS] invariant_hookNeverHoldsMoneybackClaims\n[PASS] invariant_lpFeeNeverOverriddenAndManagerLocked\n[PASS] invariant_pendingEqualsAccruedMinusSwept\n[PASS] invariant_sweepPaysOnlyPayout\n MoneyBackHookTest invariants (runs: 64, calls: 2560, reverts: 0)\n\n╭-------------+----------+-------+---------+----------╮\n| Contract    | Selector | Calls | Reverts | Discards |\n+=====================================================+\n| HookHandler | swap     | 862   | 0       | 0        |\n|-------------+----------+-------+---------+----------|\n| HookHandler | sweep    | 851   | 0       | 0        |\n|-------------+----------+-------+---------+----------|\n| HookHandler | warp     | 847   | 0       | 0        |\n╰-------------+----------+-------+---------+----------╯\n\n[PASS] testFuzz_feeMathsAllCases(uint8,uint256,uint32) (runs: 1000, μ: 171956, ~: 174190)\n[PASS] testFuzz_sequenceAccumulatesPending(uint256,uint256,uint256,uint32) (runs: 256, μ: 325204, ~: 328043)\n[PASS] testFuzz_specifiedOneWeiUnderfillAlwaysRefused(uint96,uint32,bool) (runs: 256, μ: 60281, ~: 64821)\n[PASS] testFuzz_surchargeFormula(uint32) (runs: 1000, μ: 18962, ~: 18677)\n[PASS] testFuzz_surchargeIsMonotoneNonIncreasing(uint32,uint32) (runs: 512, μ: 21699, ~: 21762)\n[PASS] testFuzz_tinyAmountsRoundDownToZeroFee(uint8,uint256,uint32) (runs: 512, μ: 113658, ~: 107500)\n[PASS] test_acceptanceSellCostAtT0AndAfterDecay() (gas: 309520)\n[PASS] test_afterSwapWithZeroPoolDeltaAccruesNothing() (gas: 64225)\n[PASS] test_allCasesWithImdAsCurrency1() (gas: 7198384)\n[PASS] test_buysNeverSurchargedAtAnyTime() (gas: 499297)\n[PASS] test_callbacksRejectNonPoolManager() (gas: 159908)\n[PASS] test_case1_exactInputBuy_t0() (gas: 173991)\n[PASS] test_case1_exactInputBuy_t1800() (gas: 173514)\n[PASS] test_case1_exactInputBuy_t1day() (gas: 173555)\n[PASS] test_case1_exactInputBuy_t900() (gas: 174001)\n[PASS] test_case2_exactOutputBuy_t0() (gas: 172741)\n[PASS] test_case2_exactOutputBuy_t1800() (gas: 172288)\n[PASS] test_case2_exactOutputBuy_t1day() (gas: 172240)\n[PASS] test_case2_exactOutputBuy_t900() (gas: 172795)\n[PASS] test_case3_exactInputSell_t0() (gas: 173564)\n[PASS] test_case3_exactInputSell_t1800() (gas: 172953)\n[PASS] test_case3_exactInputSell_t1day() (gas: 172994)\n[PASS] test_case3_exactInputSell_t900() (gas: 173565)\n[PASS] test_case4_exactOutputSell_t0() (gas: 174286)\n[PASS] test_case4_exactOutputSell_t1800() (gas: 173460)\n[PASS] test_case4_exactOutputSell_t1day() (gas: 137777)\n[PASS] test_case4_exactOutputSell_t900() (gas: 174276)\n[PASS] test_constructorRejectsImdAsLaunchToken() (gas: 63906)\n[PASS] test_constructorRejectsWrongFlagBits() (gas: 35206594)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 182420)\n[PASS] test_constructorStoresImmutablesAndConstants() (gas: 58941)\n[PASS] test_deploymentAddressFlagBitsMatchPermissions() (gas: 18443)\n[PASS] test_emptySweepSucceedsAndEmitsZero() (gas: 75505)\n[PASS] test_feeAccruedEventsPerCase() (gas: 407394)\n[PASS] test_fixedCallbacksOnlyChargeTheirOwnCases() (gas: 163278)\n[PASS] test_hookNeverMintsMoneybackClaimsEvenIfGiftedImdClaims() (gas: 295107)\n[PASS] test_hookRejectsEthAndHasNoAdminSurface() (gas: 169036)\n[PASS] test_hugeSpecifiedAmountRevertsAmountOverflow() (gas: 148943)\n[PASS] test_initializeAcceptsEitherCurrencyOrder() (gas: 5324174)\n[PASS] test_initializeBindsPoolAndRecordsTimestamp() (gas: 58390)\n[PASS] test_initializeEmitsPoolBound() (gas: 5329148)\n[PASS] test_initializeRejectsSecondPool() (gas: 74559)\n[PASS] test_initializeRejectsWrongKeys() (gas: 5837639)\n[PASS] test_specifiedZeroFeeUnderfillRefused() (gas: 53203)\n[PASS] test_surchargeAt1799IsStillChargedOnSells() (gas: 134690)\n[PASS] test_surchargeAt1800IsZeroOnSells() (gas: 134179)\n[PASS] test_surchargeBoundaries() (gas: 82994)\n[PASS] test_swapBeforeInitializationReverts() (gas: 5243025)\n[PASS] test_swapRejectsWrongPoolKey() (gas: 260897)\n[PASS] test_sweepAccumulatesAcrossRounds() (gas: 453223)\n[PASS] test_sweepCannotBeNestedInsideAnotherUnlock() (gas: 774133)\n[PASS] test_sweepReentrancyFromPayoutTransferIsRejected() (gas: 758757)\n[PASS] test_sweepRevertsCleanlyIfTakeFails() (gas: 453097)\n[PASS] test_sweepTakesAllImdClaimsToPayout() (gas: 452081)\n[PASS] test_unlockCallbackOutsideSweepIsRejected() (gas: 166965)\n[PASS] test_viewsBeforeInitialization() (gas: 5182752)\n[PASS] test_zeroAmountSwapAccruesNothing() (gas: 204295)\nSuite result: ok. 58 passed; 0 failed; 0 skipped; finished in 413.20ms (975.99ms CPU time)\n\nRan 51 tests for test/RoundPayout.t.sol:RoundPayoutTest\n[PASS]\nRoundPayoutTest invariants:\n[PASS] invariant_balanceEqualsFundedMinusPaidMinusSwept\n[PASS] invariant_ownerNeverChangesWithoutHandoff\n[PASS] invariant_roundRecordsMatchLedger\n[PASS] invariant_sumPaidNeverExceedsFunded\n RoundPayoutTest invariants (runs: 96, calls: 3840, reverts: 31)\n\n╭---------------+---------------+-------+---------+----------╮\n| Contract      | Selector      | Calls | Reverts | Discards |\n+============================================================+\n| PayoutHandler | fund          | 469   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | pay           | 412   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | replay        | 401   | 31      | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | retry         | 399   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | setBlocked    | 442   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | strangerCalls | 430   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | sweep         | 448   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | togglePause   | 426   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | writeOff      | 413   | 0       | 0        |\n╰---------------+---------------+-------+---------+----------╯\n\n[PASS] testFuzz_batchSizeBoundary(uint16) (runs: 256, μ: 1975528, ~: 1095624)\n[PASS] testFuzz_payRoundAccounting(uint256,uint256,uint256,uint256,uint8) (runs: 512, μ: 302820, ~: 231297)\n[PASS] test_allTransfersFailedReverts() (gas: 348572)\n[PASS] test_constructorRejectsZeroOwner() (gas: 4122)\n[PASS] test_constructorSetsOwner() (gas: 22600)\n[PASS] test_duplicateFailedRecipientAccumulates() (gas: 386848)\n[PASS] test_fundAcceptsNoReturnToken() (gas: 476904)\n[PASS] test_fundPullsTokensFromAnyone() (gas: 174875)\n[PASS] test_fundRejectsEoaToken() (gas: 38646)\n[PASS] test_fundRejectsFalseReturningToken() (gas: 196876)\n[PASS] test_fundWithoutApprovalReverts() (gas: 96340)\n[PASS] test_fundZeroAmountSucceeds() (gas: 47919)\n[PASS] test_ownable2StepHandoff() (gas: 285132)\n[PASS] test_partialFailureIsRecordedAndBatchContinues() (gas: 430479)\n[PASS] test_partialFailureSilentRevertAndFalseReturn() (gas: 453285)\n[PASS] test_pauseTwiceAndUnpauseWhenRunningRevert() (gas: 108689)\n[PASS] test_payRoundBatchTooLarge() (gas: 283154)\n[PASS] test_payRoundBlockedWhilePaused() (gas: 388439)\n[PASS] test_payRoundEmptyBatch() (gas: 50369)\n[PASS] test_payRoundExactBalanceSucceeds() (gas: 329406)\n[PASS] test_payRoundIdempotent() (gas: 668330)\n[PASS] test_payRoundInsufficientBalanceUpFront() (gas: 127472)\n[PASS] test_payRoundIsTokenAgnosticPerRound() (gas: 821050)\n[PASS] test_payRoundLengthMismatch() (gas: 47215)\n[PASS] test_payRoundMaxBatchOf500() (gas: 17384176)\n[PASS] test_payRoundPaysEveryLegAndRecords() (gas: 404630)\n[PASS] test_payRoundRoundIdZeroIsOrdinary() (gas: 287427)\n[PASS] test_payRoundSumOverflowReverts() (gas: 48383)\n[PASS] test_payRoundWithEoaTokenReverts() (gas: 47802)\n[PASS] test_payRoundWithNoReturnToken() (gas: 539417)\n[PASS] test_payRoundZeroAmountLegIsPaid() (gas: 162328)\n[PASS] test_payRoundZeroToken() (gas: 45016)\n[PASS] test_privilegedFunctionsRejectNonOwner() (gas: 165748)\n[PASS] test_reentrantTokenCannotReenterFundOrSweepDuringPayRound() (gas: 839289)\n[PASS] test_reentrantTokenCannotReenterPayRound() (gas: 699402)\n[PASS] test_renounceOwnershipLocksEverything() (gas: 150827)\n[PASS] test_retryFailedInsufficientBalanceAfterSweep() (gas: 525300)\n[PASS] test_retryFailedPaysAndClears() (gas: 573545)\n[PASS] test_retryFailedRevertsForUnpaidRound() (gas: 42949)\n[PASS] test_retryFailedShapeChecks() (gas: 806485)\n[PASS] test_retryFailedStillFailingKeepsRecord() (gas: 466851)\n[PASS] test_singleLegFailureIsAllTransfersFailed() (gas: 187742)\n[PASS] test_sweepAboveBalanceReverts() (gas: 115956)\n[PASS] test_sweepByOwnerMovesTokens() (gas: 155304)\n[PASS] test_sweepRejectsShortReturnData() (gas: 174911)\n[PASS] test_sweepRejectsZeroRecipient() (gas: 101056)\n[PASS] test_sweepWorksWhilePaused() (gas: 185829)\n[PASS] test_transferOwnershipOnlyOwnerAndCancelable() (gas: 142679)\n[PASS] test_writeOffFailedClearsWithoutPaying() (gas: 517226)\n[PASS] test_writeOffFailedNothingOwedReverts() (gas: 40180)\nSuite result: ok. 51 passed; 0 failed; 0 skipped; finished in 499.72ms (777.55ms CPU time)\n\nRan 50 tests for test/MoneyBackRouter.t.sol:MoneyBackRouterTest\n[PASS]\nMoneyBackRouterTest invariants:\n[PASS] invariant_bindingsNeverChange\n[PASS] invariant_hookPendingEqualsRouterFeesMinusSwept\n[PASS] invariant_routerHoldsNothing\n[PASS] invariant_valueIsConserved\n MoneyBackRouterTest invariants (runs: 64, calls: 2560, reverts: 0)\n\n╭---------------+----------+-------+---------+----------╮\n| Contract      | Selector | Calls | Reverts | Discards |\n+=======================================================+\n| RouterHandler | buy      | 490   | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RouterHandler | quote    | 500   | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RouterHandler | sell     | 510   | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RouterHandler | sweep    | 551   | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RouterHandler | warp     | 509   | 0       | 0        |\n╰---------------+----------+-------+---------+----------╯\n\n[PASS] testFuzz_buyMatchesFormulaAndLeavesNothing(uint256,uint32,uint256,uint256) (runs: 600, μ: 1307390, ~: 1341839)\n[PASS] testFuzz_sellMatchesFormulaAndLeavesNothing(uint256,uint32,uint256,uint256) (runs: 600, μ: 1497706, ~: 1525349)\n[PASS] test_acceptanceRouterBuyPaysHookExactlyLikeDirectImdBuy() (gas: 1742077)\n[PASS] test_beforePoolBoundEverythingReverts() (gas: 13575982)\n[PASS] test_buyAboveInt128RevertsAmountOverflow() (gas: 1026304)\n[PASS] test_buyAndSellRevertOnDeadline() (gas: 1396112)\n[PASS] test_buyHappyPath() (gas: 753102)\n[PASS] test_buyRefundsEthDustWhenPublicPoolFillsPartially() (gas: 737941)\n[PASS] test_buyRejectsPartialLaunchFillAndRollsBackBothLegs() (gas: 768895)\n[PASS] test_buyRejectsPartialLaunchFillEvenWithPartialPublicFill() (gas: 815857)\n[PASS] test_buyRevertsOnMinOut() (gas: 2017535)\n[PASS] test_buySwapLegsAreExactInputWithWidestLimitsAndRouterAsSender() (gas: 632417)\n[PASS] test_buyThenSellRoundTrip() (gas: 1302049)\n[PASS] test_buyTwiceAndByTwoUsers() (gas: 1750389)\n[PASS] test_buyWorksWithImdAsCurrency1() (gas: 12954225)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 1005946)\n[PASS] test_constructorStoresImmutablesFromHook() (gas: 39375)\n[PASS] test_contractWithoutReceiveCanBuyFullFillOnly() (gas: 2497010)\n[PASS] test_deadlineIsCheckedBeforeAmount() (gas: 65505)\n[PASS] test_feesFromRouterTradesAreSweepableToPayout() (gas: 1464894)\n[PASS] test_guardResetsAfterReverts() (gas: 2104653)\n[PASS] test_launchPoolKeyComesFromHook() (gas: 24012)\n[PASS] test_managerThatNeverCallsBackIsRejected() (gas: 320482)\n[PASS] test_publicPoolKeyIsHardcoded() (gas: 7313)\n[PASS] test_quoteBubblesUpForeignReverts() (gas: 24314210)\n[PASS] test_quoteBuyMatchesBuyAndChangesNothing() (gas: 3109047)\n[PASS] test_quoteCannotBeCalledWhileLocked() (gas: 838873)\n[PASS] test_quoteSellMatchesSellAndChangesNothing() (gas: 3300864)\n[PASS] test_quoteZeroReturnsZeroWithoutTouchingTheManager() (gas: 61498)\n[PASS] test_receiveRejectsEthFromAnyoneButTheManager() (gas: 774478)\n[PASS] test_reentrancyFromBuyDustRefundIsBlocked() (gas: 5516137)\n[PASS] test_reentrancyFromSellProceedsIsBlocked() (gas: 5538800)\n[PASS] test_routerHasNoAdminSurface() (gas: 163673)\n[PASS] test_sellAfterSurchargeDecayPaysOnlyBaseFee() (gas: 856071)\n[PASS] test_sellHappyPathAtT0WithSurcharge() (gas: 855699)\n[PASS] test_sellPrefundsRouterThenSettlesAndRefundsFromItsOwnBalance() (gas: 827297)\n[PASS] test_sellPullsExactlyTokensAndKeepsNoAllowance() (gas: 694640)\n[PASS] test_sellRefundsBothDusts() (gas: 924977)\n[PASS] test_sellRefundsImdDustWhenPublicPoolFillsPartially() (gas: 876869)\n[PASS] test_sellRefundsTokenDustWhenOurPoolFillsPartially() (gas: 863132)\n[PASS] test_sellRevertsOnMinOut() (gas: 1570771)\n[PASS] test_sellSwapLegsAreExactInputWithWidestLimits() (gas: 745717)\n[PASS] test_sellWhereSecondLegYieldsNothingRefundsNothingAndReverts() (gas: 942355)\n[PASS] test_sellWithoutAllowanceOrBalanceReverts() (gas: 334630)\n[PASS] test_tamperedCallbackDataIsRejected() (gas: 1061457)\n[PASS] test_tinyBuyThatYieldsNothingReverts() (gas: 2158006)\n[PASS] test_unlockCallbackRejectsManagerWithNoCallInFlight() (gas: 120933)\n[PASS] test_unlockCallbackRejectsNonManager() (gas: 59782)\n[PASS] test_zeroValueBuyAndZeroTokenSellRevert() (gas: 237184)\nSuite result: ok. 50 passed; 0 failed; 0 skipped; finished in 806.64ms (1.07s CPU time)\n\nRan 7 test suites in 812.56ms (2.31s CPU time): 216 tests passed, 0 failed, 0 skipped (216 total tests)\n","passed":true},{"durationMs":70,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"MoneyBackHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"MoneyBackHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"MoneyBackHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"MoneyBackHook.sweep()\",\"MoneyBackHook.unlockCallback(bytes)\",\"MoneyBackRouter.buyWithEth(uint256,uint256)\",\"MoneyBackRouter.quoteBuy(uint256)\",\"MoneyBackRouter.quoteSell(uint256)\",\"MoneyBackRouter.receive()\",\"MoneyBackRouter.sellForEth(uint256,uint256,uint256)\",\"MoneyBackRouter.unlockCallback(bytes)\",\"MoneyBackToken.approve(address,uint256)\",\"MoneyBackToken.transfer(address,uint256)\",\"MoneyBackToken.transferFrom(address,address,uint256)\",\"RoundPayout.acceptOwnership()\",\"RoundPayout.fund(address,uint256)\",\"RoundPayout.pause()\",\"RoundPayout.payRound(uint256,address,address[],uint256[],bytes32,uint256,uint256)\",\"RoundPayout.renounceOwnership()\",\"RoundPayout.retryFailed(uint256,address[])\",\"RoundPayout.sweep(address,address,uint256)\",\"RoundPayout.transferOwnership(address)\",\"RoundPayout.unpause()\",\"RoundPayout.writeOffFailed(uint256,address)\"],\"files\":{\"README.md\":118,\"assets/moneyback.png\":39,\"assets/moneyback.svg\":8,\"docs/AUDIT_RESPONSES.md\":97,\"docs/network-observations.json\":36,\"docs/requester-share.json\":4,\"foundry.toml\":13,\"launch.json\":31,\"script/DeployAuxiliaries.s.sol\":64,\"src/MoneyBackHook.sol\":515,\"src/MoneyBackRouter.sol\":446,\"src/MoneyBackToken.sol\":153,\"src/RoundPayout.sol\":373,\"test/DeployAuxiliaries.t.sol\":73,\"test/MoneyBackHook.t.sol\":1319,\"test/MoneyBackRealV4.t.sol\":342,\"test/MoneyBackRouter.t.sol\":1840,\"test/MoneyBackToken.t.sol\":372,\"test/RoundPayout.t.sol\":1241,\"test/vendor/README.md\":8,\"test/vendor/solmate/LICENSE\":661,\"test/vendor/solmate/src/auth/Owned.sol\":44,\"test/vendor/v4-core/licenses/BUSL_LICENSE\":63,\"test/vendor/v4-core/licenses/MIT_LICENSE\":7,\"test/vendor/v4-core/src/ERC6909.sol\":90,\"test/vendor/v4-core/src/ERC6909Claims.sol\":23,\"test/vendor/v4-core/src/Extsload.sol\":64,\"test/vendor/v4-core/src/Exttload.sol\":40,\"test/vendor/v4-core/src/NoDelegateCall.sol\":33,\"test/vendor/v4-core/src/PoolManager.sol\":394,\"test/vendor/v4-core/src/ProtocolFees.sol\":71,\"test/vendor/v4-core/src/interfaces/IExtsload.sol\":21,\"test/vendor/v4-core/src/interfaces/IExttload.sol\":15,\"test/vendor/v4-core/src/interfaces/IHooks.sol\":152,\"test/vendor/v4-core/src/interfaces/IPoolManager.sol\":217,\"test/vendor/v4-core/src/interfaces/IProtocolFees.sol\":52,\"test/vendor/v4-core/src/interfaces/callback/IUnlockCallback.sol\":10,\"test/vendor/v4-core/src/interfaces/external/IERC20Minimal.sol\":48,\"test/vendor/v4-core/src/interfaces/external/IERC6909Claims.sol\":66,\"test/vendor/v4-core/src/libraries/BitMath.sol\":49,\"test/vendor/v4-core/src/libraries/CurrencyDelta.sol\":42,\"test/vendor/v4-core/src/libraries/CurrencyReserves.sol\":39,\"test/vendor/v4-core/src/libraries/CustomRevert.sol\":120,\"test/vendor/v4-core/src/libraries/FixedPoint128.sol\":8,\"test/vendor/v4-core/src/libraries/FixedPoint96.sol\":10,\"test/vendor/v4-core/src/libraries/FullMath.sol\":117,\"test/vendor/v4-core/src/libraries/Hooks.sol\":344,\"test/vendor/v4-core/src/libraries/LPFeeLibrary.sol\":79,\"test/vendor/v4-core/src/libraries/LiquidityMath.sol\":20,\"test/vendor/v4-core/src/libraries/Lock.sol\":28,\"test/vendor/v4-core/src/libraries/NonzeroDeltaCount.sol\":35,\"test/vendor/v4-core/src/libraries/ParseBytes.sol\":29,\"test/vendor/v4-core/src/libraries/Pool.sol\":620,\"test/vendor/v4-core/src/libraries/Position.sol\":103,\"test/vendor/v4-core/src/libraries/ProtocolFeeLibrary.sol\":47,\"test/vendor/v4-core/src/libraries/SafeCast.sol\":60,\"test/vendor/v4-core/src/libraries/SqrtPriceMath.sol\":292,\"test/vendor/v4-core/src/libraries/StateLibrary.sol\":345,\"test/vendor/v4-core/src/libraries/SwapMath.sol\":108,\"test/vendor/v4-core/src/libraries/TickBitmap.sol\":121,\"test/vendor/v4-core/src/libraries/TickMath.sol\":238,\"test/vendor/v4-core/src/libraries/UnsafeMath.sol\":29,\"test/vendor/v4-core/src/types/BalanceDelta.sol\":72,\"test/vendor/v4-core/src/types/BeforeSwapDelta.sol\":38,\"test/vendor/v4-core/src/types/Currency.sol\":118,\"test/vendor/v4-core/src/types/PoolId.sol\":17,\"test/vendor/v4-core/src/types/PoolKey.sol\":22,\"test/vendor/v4-core/src/types/PoolOperation.sol\":26,\"test/vendor/v4-core/src/types/Slot0.sol\":95},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"2e55e0c8acda2d9204c90b2db9f2d4b113923d7f4eb2ec26a30b57acdc3fb5f6","verifiedTreeHash":"377dde50dc81819a84d2bd5eb4950b791aad7d39","verifierVersion":"0.1.0+0e08a464"},{"checks":[{"durationMs":4855,"exitCode":0,"name":"build","output":"Compiling 58 files with Solc 0.8.26\nSolc 0.8.26 finished in 4.72s\nCompiler run successful!\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackRouter.sol:389:24\n    │\n389 │         r = abi.decode(poolManager.unlock(data), (Result));\n    │                        ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackRouter.sol:422:22\n    │\n422 │         (bool ok,) = to.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/MoneyBackRouter.sol:204:13\n    │\n204 │         if (block.timestamp > deadline) revert Expired();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MoneyBackRouter.sol:212:9\n    │\n212 │         emit BoughtWithEth(msg.sender, r.amountIn, r.imdMid, tokensOut);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/RoundPayout.sol:61:32\n   │\n61 │     function transferOwnership(address newOwner) external onlyOwner {\n   │                                ━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackRouter.sol:229:14\n    │\n229 │         if (!IERC20(token).transferFrom(msg.sender, address(this), tokens)) revert TransferFromFailed();\n    │              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackRouter.sol:235:29\n    │\n235 │         if (unused != 0 && !IERC20(token).transfer(msg.sender, unused)) revert TransferFailed();\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/MoneyBackRouter.sol:225:13\n    │\n225 │         if (block.timestamp > deadline) revert Expired();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MoneyBackRouter.sol:237:9\n    │\n237 │         emit SoldForEth(msg.sender, r.amountIn, r.imdMid, ethOut);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:227:22\n    │\n227 │         if (tokens > uint256(uint128(type(int128).max))) revert AmountOverflow();\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/MoneyBackRouter.sol:227:30\n    │\n227 │         if (tokens > uint256(uint128(type(int128).max))) revert AmountOverflow();\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackRouter.sol:400:13\n    │\n400 │         try poolManager.unlock(data) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/RoundPayout.sol:360:40\n    │\n360 │         (bool ok, bytes memory data) = token.call(abi.encodeCall(IERC20.transferFrom, (from, to, amount)));\n    │                                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RoundPayout.sol:193:9\n    │\n193 │         emit Funded(token, msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/RoundPayout.sol:354:40\n    │\n354 │         (bool ok, bytes memory data) = token.call(abi.encodeCall(IERC20.transfer, (to, amount)));\n    │                                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/RoundPayout.sol:197:82\n    │\n197 │     function sweep(address token, address to, uint256 amount) external onlyOwner nonReentrant {\n    │                                                                                  ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RoundPayout.sol:200:9\n    │\n200 │         emit Swept(token, to, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:306:22\n    │\n306 │         r.amountIn = uint256(-a0);\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/MoneyBackRouter.sol:307:26\n    │\n307 │         uint256 imdOut = uint256(a1);\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[unused-return]: return value of an external call is not used\n    ╭▸ src/MoneyBackRouter.sol:312:13\n    │\n312 │             poolManager.settle{value: r.amountIn}();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/RoundPayout.sol:348:22\n    │\n348 │         (ok, data) = token.call(abi.encodeCall(IERC20.transfer, (to, amount)));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:320:20\n    │\n320 │         r.imdMid = uint256(-imdDelta);\n    │                    ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:321:23\n    │\n321 │         r.amountOut = uint256(tokenDelta);\n    │                       ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/RoundPayout.sol:223:40\n    │\n223 │     ) external onlyOwner whenNotPaused nonReentrant returns (uint256 totalPaid) {\n    │                                        ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RoundPayout.sol:244:9\n    │\n244 │         emit RoundPaid(roundId, token, ledgerHash, twapCloseX96, totalEligibleLoss, totalPaid, n);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:339:22\n    │\n339 │         r.amountIn = uint256(-tokenDelta);\n    │                      ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/MoneyBackRouter.sol:343:13\n    │\n343 │             poolManager.settle();\n    │             ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:345:26\n    │\n345 │         uint256 imdOut = uint256(imdDelta);\n    │                          ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:350:20\n    │\n350 │         r.imdMid = uint256(-a1);\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/MoneyBackRouter.sol:351:23\n    │\n351 │         r.amountOut = uint256(a0);\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[calls-loop]: external call inside a loop\n    ╭▸ src/RoundPayout.sol:348:22\n    │\n348 │         (ok, data) = token.call(abi.encodeCall(IERC20.transfer, (to, amount)));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RoundPayout.sol:258:17\n    │\n258 │                 emit Paid(roundId, recipient, 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/RoundPayout.sol:262:17\n    │\n262 │                 emit PayFailed(roundId, recipient, amount, reason);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:362:24\n    │\n362 │         if (amountIn > uint256(uint128(type(int128).max))) revert AmountOverflow();\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/MoneyBackRouter.sol:362:32\n    │\n362 │         if (amountIn > uint256(uint128(type(int128).max))) revert AmountOverflow();\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:367:35\n    │\n367 │                 amountSpecified: -int256(amountIn),\n    │                                   ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:405:40\n    │\n405 │             if (reason.length == 36 && bytes4(reason) == QuoteResult.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[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/MoneyBackRouter.sol:422:22\n    │\n422 │         (bool ok,) = to.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `failed` is updated\n    ╭▸ src/RoundPayout.sol:348:22\n    │\n348 │         (ok, data) = token.call(abi.encodeCall(IERC20.transfer, (to, amount)));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/RoundPayout.sol:302:13\n    │\n302 │             needed += owed;\n    │             ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/RoundPayout.sol:288:9\n    │\n288 │         nonReentrant\n    │         ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RoundPayout.sol:316:17\n    │\n316 │                 emit Paid(roundId, recipient, 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/RoundPayout.sol:318:17\n    │\n318 │                 emit PayFailed(roundId, recipient, amount, reason);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/RoundPayout.sol:301:28\n    │\n301 │             if (owed == 0) revert NothingFailed(roundId, to[i]);\n    │                            ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/RoundPayout.sol:309:30\n    │\n309 │             if (amount == 0) revert NothingFailed(roundId, recipient);\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/MoneyBackHook.sol:344:13\n    │\n344 │         if (elapsed >= SURCHARGE_DURATION) return 0;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MoneyBackHook.sol:376:27\n    │\n376 │         _imdIsCurrency0 = imdIsCurrency0;\n    │                           ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/RoundPayout.sol:324:77\n    │\n324 │     function writeOffFailed(uint256 roundId, address to) external onlyOwner nonReentrant {\n    │                                                                             ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:397:39\n    │\n397 │         uint256 imdLeg = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:397:74\n    │\n397 │         uint256 imdLeg = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n    │                                                                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MoneyBackHook.sol:481:9\n    │\n481 │         emit FeeAccrued(isSell, baseFee, surcharge, imdLeg);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:416:46\n    │\n416 │             uint256 specified = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n    │                                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:416:81\n    │\n416 │             uint256 specified = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n    │                                                                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:421:54\n    │\n421 │             if (imdDelta != params.amountSpecified + int256(baseFee + surcharge)) revert PartialFill();\n    │                                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:425:41\n    │\n425 │         uint256 imdLeg = imdDelta < 0 ? uint256(-imdDelta) : uint256(imdDelta);\n    │                                         ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:425:62\n    │\n425 │         uint256 imdLeg = imdDelta < 0 ? uint256(-imdDelta) : uint256(imdDelta);\n    │                                                              ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackHook.sol:439:9\n    │\n439 │         poolManager.unlock(\"\");\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/MoneyBackHook.sol:439:9\n    │\n439 │         poolManager.unlock(\"\");\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MoneyBackHook.sol:453:9\n    │\n453 │         emit Swept(amount, payout);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:490:17\n    │\n490 │         if (x > uint256(uint128(type(int128).max))) revert AmountOverflow();\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/MoneyBackHook.sol:490:25\n    │\n490 │         if (x > uint256(uint128(type(int128).max))) revert AmountOverflow();\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:491:16\n    │\n491 │         return int128(uint128(x));\n    │                ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:491:23\n    │\n491 │         return int128(uint128(x));\n    │                       ━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/MoneyBackRealV4Invariants.t.sol:427:17\n    │\n427 │         vm.warp(block.timestamp + warpBy % 3000);\n    │         ────────━━━━━━━━━━━━━━━───────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/MoneyBackRealV4Invariants.t.sol:586:17\n    │\n586 │         vm.warp(block.timestamp + by % 5000);\n    │         ────────━━━━━━━━━━━━━━━───────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/MoneyBackRealV4Invariants.t.sol:859:17\n    │\n859 │         vm.warp(block.timestamp + warpBy % 3000);\n    │         ────────━━━━━━━━━━━━━━━───────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n    ╭▸ test/MoneyBackRealV4Invariants.t.sol:891:17\n    │\n891 │         vm.warp(block.timestamp + warpBy % 3000);\n    │         ────────━━━━━━━━━━━━━━━───────────────── `vm.warp` changes this environment here\n    │\n    ├ help: capture it with `vm.getBlockTimestamp()` instead\n    ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n     ╭▸ test/MoneyBackRouter.t.sol:1132:24\n     │\n1132 │         uint256 now_ = block.timestamp;\n     │                        ━━━━━━━━━━━━━━━\n1133 │         vm.warp(ts);\n     │         ─────────── `vm.warp` changes this environment here\n     │\n     ├ help: capture it with `vm.getBlockTimestamp()` instead\n     ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n     ╭▸ test/MoneyBackRouter.t.sol:1159:53\n     │\n1159 │         uint256 bought = _buyAs(ALICE, 10 ether, 0, block.timestamp);\n     │                                                     ━━━━━━━━━━━━━━━\n1160 │         vm.warp(T0 + 3600);\n     │         ────────────────── `vm.warp` changes this environment here\n     │\n     ├ help: capture it with `vm.getBlockTimestamp()` instead\n     ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n     ╭▸ test/MoneyBackRouter.t.sol:1518:49\n     │\n1518 │         assertEq(_sellAs(ALICE, tokens, quoted, block.timestamp), quoted, \"sell delivers exactly the quote\");\n     │                                                 ━━━━━━━━━━━━━━━\n1519 │         vm.warp(T0 + 1800);\n     │         ────────────────── `vm.warp` changes this environment here\n     │\n     ├ help: capture it with `vm.getBlockTimestamp()` instead\n     ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":954,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/MoneyBackRealV4Invariants.t.sol:RealV4EdgeCasesTest\n[PASS] test_noOneCanRebindOrRetarget() (gas: 167534)\n[PASS] test_oneWeiImdLegsChargeNothing() (gas: 313467)\n[PASS] test_outsiderClaimsAreSweptToPayoutOnlyWhenTheyAreImd() (gas: 384471)\n[PASS] test_protocolFeeDoesNotDisturbFullFillGuardOrFeeMaths() (gas: 919409)\n[PASS] test_surchargeWindowEdgeOnRealPool() (gas: 379444)\n[PASS] test_twoSwapsInOneUnlockChargeEachOnceAndRollBackTogether() (gas: 473651)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 14.75ms (6.46ms CPU time)\n\nRan 6 tests for test/DeployAuxiliaries.t.sol:DeployAuxiliariesTest\n[PASS] test_deploysBothAuxiliariesWithPayingWalletOwner() (gas: 2838233)\n[PASS] test_uninitializedHookRefused() (gas: 21545030)\n[PASS] test_unresolvedAddressesRefused() (gas: 61999)\n[PASS] test_wrongChainRefused() (gas: 35325)\n[PASS] test_wrongManagerRefused() (gas: 41055)\n[PASS] test_wrongWalletRefused() (gas: 4940831)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 65.15ms (38.11ms CPU time)\n\nRan 12 tests for test/MoneyBackRealV4.t.sol:MoneyBackRealV4Token1Test\n[PASS] testFuzz_fullFillSpecifiedFee(uint96,uint32,bool) (runs: 256, μ: 200976, ~: 201162)\n[PASS] test_exactInputBuyExhaustedLiquidityReverts() (gas: 601438)\n[PASS] test_exactInputBuyTightLimitRevertsWithoutOvercharge() (gas: 222281)\n[PASS] test_exactOutputSellExhaustedLiquidityReverts() (gas: 585713)\n[PASS] test_exactOutputSellTightLimitRevertsWithoutTakingIMD() (gas: 216822)\n[PASS] test_freshSingleSidedPoolCanBuyAndSweepWithNoIMDReserve() (gas: 5560037)\n[PASS] test_fullFillAllFourCasesAndSweep() (gas: 2256502)\n[PASS] test_liquidityConversionIsOutsideSwapSurcharge() (gas: 579232)\n[PASS] test_partialExactOutputBuyChargesActualIMD() (gas: 572071)\n[PASS] test_partialUnspecifiedIMDStillChargesActualFill() (gas: 584148)\n[PASS] test_routerTradesAgainstRealManagerAndRefundsPartialSell() (gas: 2047133)\n[PASS] test_singleSidedPoolRejectsUnfillableExactOutputSell() (gas: 5746588)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 122.31ms (105.96ms CPU time)\n\nRan 12 tests for test/MoneyBackRealV4.t.sol:MoneyBackRealV4Token0Test\n[PASS] testFuzz_fullFillSpecifiedFee(uint96,uint32,bool) (runs: 256, μ: 200159, ~: 206331)\n[PASS] test_exactInputBuyExhaustedLiquidityReverts() (gas: 594713)\n[PASS] test_exactInputBuyTightLimitRevertsWithoutOvercharge() (gas: 216118)\n[PASS] test_exactOutputSellExhaustedLiquidityReverts() (gas: 592318)\n[PASS] test_exactOutputSellTightLimitRevertsWithoutTakingIMD() (gas: 222865)\n[PASS] test_freshSingleSidedPoolCanBuyAndSweepWithNoIMDReserve() (gas: 7620419)\n[PASS] test_fullFillAllFourCasesAndSweep() (gas: 2255552)\n[PASS] test_liquidityConversionIsOutsideSwapSurcharge() (gas: 565795)\n[PASS] test_partialExactOutputBuyChargesActualIMD() (gas: 565447)\n[PASS] test_partialUnspecifiedIMDStillChargesActualFill() (gas: 590858)\n[PASS] test_routerTradesAgainstRealManagerAndRefundsPartialSell() (gas: 2048465)\n[PASS] test_singleSidedPoolRejectsUnfillableExactOutputSell() (gas: 7878714)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 129.76ms (123.59ms CPU time)\n\nRan 27 tests for test/MoneyBackToken.t.sol:MoneyBackTokenTest\n[PASS]\nMoneyBackTokenTest invariants:\n[PASS] invariant_balancesSumToSupply\n[PASS] invariant_totalSupplyNeverChanges\n[PASS] invariant_zeroAddressHoldsNothing\n MoneyBackTokenTest invariants (runs: 128, calls: 4096, reverts: 0)\n\n╭--------------+--------------+-------+---------+----------╮\n| Contract     | Selector     | Calls | Reverts | Discards |\n+==========================================================+\n| TokenHandler | approve      | 1349  | 0       | 0        |\n|--------------+--------------+-------+---------+----------|\n| TokenHandler | transfer     | 1335  | 0       | 0        |\n|--------------+--------------+-------+---------+----------|\n| TokenHandler | transferFrom | 1412  | 0       | 0        |\n╰--------------+--------------+-------+---------+----------╯\n\n[PASS] testFuzz_allowanceAccounting(uint256,uint256) (runs: 512, μ: 118773, ~: 134114)\n[PASS] testFuzz_transferAboveBalanceAlwaysReverts(uint256) (runs: 512, μ: 40841, ~: 40840)\n[PASS] testFuzz_transferConservesSupplyAndBalances(address,uint256) (runs: 512, μ: 92627, ~: 93511)\n[PASS] test_approveSetsAllowanceAndEmits() (gas: 97003)\n[PASS] test_approveZeroSpenderReverts() (gas: 30352)\n[PASS] test_constructorMintsWholeSupplyToDeployer() (gas: 17042)\n[PASS] test_deployerIsMsgSenderNotTxOrigin() (gas: 188189)\n[PASS] test_metadata() (gas: 17630)\n[PASS] test_noPrivilegedSurface() (gas: 165545)\n[PASS] test_rejectsEth() (gas: 32817)\n[PASS] test_supplyIsExactlyOneBillionWithEighteenDecimals() (gas: 13317)\n[PASS] test_transferFromDoesNotEmitApprovalOnSpend() (gas: 115689)\n[PASS] test_transferFromEmptyAccountRevertsEvenForOneWei() (gas: 33309)\n[PASS] test_transferFromExactAllowanceLeavesZero() (gas: 144349)\n[PASS] test_transferFromInfiniteAllowanceIsNotDecremented() (gas: 112829)\n[PASS] test_transferFromRevertsWhenAllowanceBelowAmount() (gas: 80374)\n[PASS] test_transferFromRevertsWhenBalanceBelowAmountEvenWithAllowance() (gas: 86141)\n[PASS] test_transferFromRevertsWithoutAllowance() (gas: 33778)\n[PASS] test_transferFromSpendsAllowance() (gas: 125597)\n[PASS] test_transferFromToZeroAddressReverts() (gas: 83503)\n[PASS] test_transferMovesBalanceAndEmits() (gas: 87232)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 40643)\n[PASS] test_transferToSelfKeepsBalance() (gas: 42924)\n[PASS] test_transferToZeroAddressReverts() (gas: 30391)\n[PASS] test_transferWholeBalanceLeavesZero() (gas: 70165)\n[PASS] test_transferZeroAmountSucceeds() (gas: 37089)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 311.49ms (719.38ms CPU time)\n\nRan 58 tests for test/MoneyBackHook.t.sol:MoneyBackHookTest\n[PASS]\nMoneyBackHookTest invariants:\n[PASS] invariant_hookNeverHoldsMoneybackClaims\n[PASS] invariant_lpFeeNeverOverriddenAndManagerLocked\n[PASS] invariant_pendingEqualsAccruedMinusSwept\n[PASS] invariant_sweepPaysOnlyPayout\n MoneyBackHookTest invariants (runs: 64, calls: 2560, reverts: 0)\n\n╭-------------+----------+-------+---------+----------╮\n| Contract    | Selector | Calls | Reverts | Discards |\n+=====================================================+\n| HookHandler | swap     | 857   | 0       | 0        |\n|-------------+----------+-------+---------+----------|\n| HookHandler | sweep    | 839   | 0       | 0        |\n|-------------+----------+-------+---------+----------|\n| HookHandler | warp     | 864   | 0       | 0        |\n╰-------------+----------+-------+---------+----------╯\n\n[PASS] testFuzz_feeMathsAllCases(uint8,uint256,uint32) (runs: 1000, μ: 171806, ~: 174190)\n[PASS] testFuzz_sequenceAccumulatesPending(uint256,uint256,uint256,uint32) (runs: 256, μ: 324345, ~: 328043)\n[PASS] testFuzz_specifiedOneWeiUnderfillAlwaysRefused(uint96,uint32,bool) (runs: 256, μ: 60151, ~: 64809)\n[PASS] testFuzz_surchargeFormula(uint32) (runs: 1000, μ: 18955, ~: 18677)\n[PASS] testFuzz_surchargeIsMonotoneNonIncreasing(uint32,uint32) (runs: 512, μ: 21703, ~: 21762)\n[PASS] testFuzz_tinyAmountsRoundDownToZeroFee(uint8,uint256,uint32) (runs: 512, μ: 114799, ~: 107500)\n[PASS] test_acceptanceSellCostAtT0AndAfterDecay() (gas: 309520)\n[PASS] test_afterSwapWithZeroPoolDeltaAccruesNothing() (gas: 64225)\n[PASS] test_allCasesWithImdAsCurrency1() (gas: 7198384)\n[PASS] test_buysNeverSurchargedAtAnyTime() (gas: 499297)\n[PASS] test_callbacksRejectNonPoolManager() (gas: 159908)\n[PASS] test_case1_exactInputBuy_t0() (gas: 173991)\n[PASS] test_case1_exactInputBuy_t1800() (gas: 173514)\n[PASS] test_case1_exactInputBuy_t1day() (gas: 173555)\n[PASS] test_case1_exactInputBuy_t900() (gas: 174001)\n[PASS] test_case2_exactOutputBuy_t0() (gas: 172741)\n[PASS] test_case2_exactOutputBuy_t1800() (gas: 172288)\n[PASS] test_case2_exactOutputBuy_t1day() (gas: 172240)\n[PASS] test_case2_exactOutputBuy_t900() (gas: 172795)\n[PASS] test_case3_exactInputSell_t0() (gas: 173564)\n[PASS] test_case3_exactInputSell_t1800() (gas: 172953)\n[PASS] test_case3_exactInputSell_t1day() (gas: 172994)\n[PASS] test_case3_exactInputSell_t900() (gas: 173565)\n[PASS] test_case4_exactOutputSell_t0() (gas: 174286)\n[PASS] test_case4_exactOutputSell_t1800() (gas: 173460)\n[PASS] test_case4_exactOutputSell_t1day() (gas: 137777)\n[PASS] test_case4_exactOutputSell_t900() (gas: 174276)\n[PASS] test_constructorRejectsImdAsLaunchToken() (gas: 63906)\n[PASS] test_constructorRejectsWrongFlagBits() (gas: 35206594)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 182420)\n[PASS] test_constructorStoresImmutablesAndConstants() (gas: 58941)\n[PASS] test_deploymentAddressFlagBitsMatchPermissions() (gas: 18443)\n[PASS] test_emptySweepSucceedsAndEmitsZero() (gas: 75505)\n[PASS] test_feeAccruedEventsPerCase() (gas: 407394)\n[PASS] test_fixedCallbacksOnlyChargeTheirOwnCases() (gas: 163278)\n[PASS] test_hookNeverMintsMoneybackClaimsEvenIfGiftedImdClaims() (gas: 295107)\n[PASS] test_hookRejectsEthAndHasNoAdminSurface() (gas: 169036)\n[PASS] test_hugeSpecifiedAmountRevertsAmountOverflow() (gas: 148943)\n[PASS] test_initializeAcceptsEitherCurrencyOrder() (gas: 5324174)\n[PASS] test_initializeBindsPoolAndRecordsTimestamp() (gas: 58390)\n[PASS] test_initializeEmitsPoolBound() (gas: 5329148)\n[PASS] test_initializeRejectsSecondPool() (gas: 74559)\n[PASS] test_initializeRejectsWrongKeys() (gas: 5837639)\n[PASS] test_specifiedZeroFeeUnderfillRefused() (gas: 53203)\n[PASS] test_surchargeAt1799IsStillChargedOnSells() (gas: 134690)\n[PASS] test_surchargeAt1800IsZeroOnSells() (gas: 134179)\n[PASS] test_surchargeBoundaries() (gas: 82994)\n[PASS] test_swapBeforeInitializationReverts() (gas: 5243025)\n[PASS] test_swapRejectsWrongPoolKey() (gas: 260897)\n[PASS] test_sweepAccumulatesAcrossRounds() (gas: 453223)\n[PASS] test_sweepCannotBeNestedInsideAnotherUnlock() (gas: 774133)\n[PASS] test_sweepReentrancyFromPayoutTransferIsRejected() (gas: 758757)\n[PASS] test_sweepRevertsCleanlyIfTakeFails() (gas: 453097)\n[PASS] test_sweepTakesAllImdClaimsToPayout() (gas: 452081)\n[PASS] test_unlockCallbackOutsideSweepIsRejected() (gas: 166965)\n[PASS] test_viewsBeforeInitialization() (gas: 5182752)\n[PASS] test_zeroAmountSwapAccruesNothing() (gas: 204295)\nSuite result: ok. 58 passed; 0 failed; 0 skipped; finished in 391.71ms (956.90ms CPU time)\n\nRan 51 tests for test/RoundPayout.t.sol:RoundPayoutTest\n[PASS]\nRoundPayoutTest invariants:\n[PASS] invariant_balanceEqualsFundedMinusPaidMinusSwept\n[PASS] invariant_ownerNeverChangesWithoutHandoff\n[PASS] invariant_roundRecordsMatchLedger\n[PASS] invariant_sumPaidNeverExceedsFunded\n RoundPayoutTest invariants (runs: 96, calls: 3840, reverts: 37)\n\n╭---------------+---------------+-------+---------+----------╮\n| Contract      | Selector      | Calls | Reverts | Discards |\n+============================================================+\n| PayoutHandler | fund          | 420   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | pay           | 442   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | replay        | 445   | 37      | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | retry         | 427   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | setBlocked    | 396   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | strangerCalls | 426   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | sweep         | 424   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | togglePause   | 435   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | writeOff      | 425   | 0       | 0        |\n╰---------------+---------------+-------+---------+----------╯\n\n[PASS] testFuzz_batchSizeBoundary(uint16) (runs: 256, μ: 2405784, ~: 1916649)\n[PASS] testFuzz_payRoundAccounting(uint256,uint256,uint256,uint256,uint8) (runs: 512, μ: 306020, ~: 231291)\n[PASS] test_allTransfersFailedReverts() (gas: 348572)\n[PASS] test_constructorRejectsZeroOwner() (gas: 4122)\n[PASS] test_constructorSetsOwner() (gas: 22600)\n[PASS] test_duplicateFailedRecipientAccumulates() (gas: 386848)\n[PASS] test_fundAcceptsNoReturnToken() (gas: 476904)\n[PASS] test_fundPullsTokensFromAnyone() (gas: 174875)\n[PASS] test_fundRejectsEoaToken() (gas: 38646)\n[PASS] test_fundRejectsFalseReturningToken() (gas: 196876)\n[PASS] test_fundWithoutApprovalReverts() (gas: 96340)\n[PASS] test_fundZeroAmountSucceeds() (gas: 47919)\n[PASS] test_ownable2StepHandoff() (gas: 285132)\n[PASS] test_partialFailureIsRecordedAndBatchContinues() (gas: 430479)\n[PASS] test_partialFailureSilentRevertAndFalseReturn() (gas: 453285)\n[PASS] test_pauseTwiceAndUnpauseWhenRunningRevert() (gas: 108689)\n[PASS] test_payRoundBatchTooLarge() (gas: 283154)\n[PASS] test_payRoundBlockedWhilePaused() (gas: 388439)\n[PASS] test_payRoundEmptyBatch() (gas: 50369)\n[PASS] test_payRoundExactBalanceSucceeds() (gas: 329406)\n[PASS] test_payRoundIdempotent() (gas: 668330)\n[PASS] test_payRoundInsufficientBalanceUpFront() (gas: 127472)\n[PASS] test_payRoundIsTokenAgnosticPerRound() (gas: 821050)\n[PASS] test_payRoundLengthMismatch() (gas: 47215)\n[PASS] test_payRoundMaxBatchOf500() (gas: 17384176)\n[PASS] test_payRoundPaysEveryLegAndRecords() (gas: 404630)\n[PASS] test_payRoundRoundIdZeroIsOrdinary() (gas: 287427)\n[PASS] test_payRoundSumOverflowReverts() (gas: 48383)\n[PASS] test_payRoundWithEoaTokenReverts() (gas: 47802)\n[PASS] test_payRoundWithNoReturnToken() (gas: 539417)\n[PASS] test_payRoundZeroAmountLegIsPaid() (gas: 162328)\n[PASS] test_payRoundZeroToken() (gas: 45016)\n[PASS] test_privilegedFunctionsRejectNonOwner() (gas: 165748)\n[PASS] test_reentrantTokenCannotReenterFundOrSweepDuringPayRound() (gas: 839289)\n[PASS] test_reentrantTokenCannotReenterPayRound() (gas: 699402)\n[PASS] test_renounceOwnershipLocksEverything() (gas: 150827)\n[PASS] test_retryFailedInsufficientBalanceAfterSweep() (gas: 525300)\n[PASS] test_retryFailedPaysAndClears() (gas: 573545)\n[PASS] test_retryFailedRevertsForUnpaidRound() (gas: 42949)\n[PASS] test_retryFailedShapeChecks() (gas: 806485)\n[PASS] test_retryFailedStillFailingKeepsRecord() (gas: 466851)\n[PASS] test_singleLegFailureIsAllTransfersFailed() (gas: 187742)\n[PASS] test_sweepAboveBalanceReverts() (gas: 115956)\n[PASS] test_sweepByOwnerMovesTokens() (gas: 155304)\n[PASS] test_sweepRejectsShortReturnData() (gas: 174911)\n[PASS] test_sweepRejectsZeroRecipient() (gas: 101056)\n[PASS] test_sweepWorksWhilePaused() (gas: 185829)\n[PASS] test_transferOwnershipOnlyOwnerAndCancelable() (gas: 142679)\n[PASS] test_writeOffFailedClearsWithoutPaying() (gas: 517226)\n[PASS] test_writeOffFailedNothingOwedReverts() (gas: 40180)\nSuite result: ok. 51 passed; 0 failed; 0 skipped; finished in 536.29ms (829.22ms CPU time)\n\nRan 1 test for test/MoneyBackRealV4Invariants.t.sol:RealV4HookInvariantsImdIsCurrency0\n[PASS]\nRealV4HookInvariantsImdIsCurrency0 invariants:\n[PASS] invariant_bindingAndParametersAreFinal\n[PASS] invariant_claimsAreBackedByManagerBalance\n[PASS] invariant_hookHoldsNothingButImdClaims\n[PASS] invariant_noHandlerViolations\n[PASS] invariant_payoutGetsExactlyWhatWasSweptAndNothingElse\n[PASS] invariant_pendingEqualsAccruedPlusGiftedMinusSwept\n[PASS] invariant_valueIsConserved\n RealV4HookInvariantsImdIsCurrency0 invariants (runs: 48, calls: 1536, reverts: 0)\n\n╭-------------------+-------------------+-------+---------+----------╮\n| Contract          | Selector          | Calls | Reverts | Discards |\n+====================================================================+\n| RealV4HookHandler | addLiquidity      | 148   | 0       | 0        |\n|-------------------+-------------------+-------+---------+----------|\n| RealV4HookHandler | donate            | 142   | 0       | 0        |\n|-------------------+-------------------+-------+---------+----------|\n| RealV4HookHandler | giftClaims        | 143   | 0       | 0        |\n|-------------------+-------------------+-------+---------+----------|\n| RealV4HookHandler | giftTokenClaims   | 163   | 0       | 0        |\n|-------------------+-------------------+-------+---------+----------|\n| RealV4HookHandler | reinitialize      | 165   | 0       | 0        |\n|-------------------+-------------------+-------+---------+----------|\n| RealV4HookHandler | removeLiquidity   | 161   | 0       | 0        |\n|-------------------+-------------------+-------+---------+----------|\n| RealV4HookHandler | strangerCallbacks | 137   | 0       | 0        |\n|-------------------+-------------------+-------+---------+----------|\n| RealV4HookHandler | swap              | 169   | 0       | 0        |\n|-------------------+-------------------+-------+---------+----------|\n| RealV4HookHandler | sweep             | 158   | 0       | 0        |\n|-------------------+-------------------+-------+---------+----------|\n| RealV4HookHandler | warp              | 150   | 0       | 0        |\n╰-------------------+-------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 540.72ms (534.74ms CPU time)\n\nRan 1 test for test/MoneyBackRealV4Invariants.t.sol:RealV4HookInvariantsImdIsCurrency1\n[PASS]\nRealV4HookInvariantsImdIsCurrency1 invariants:\n[PASS] invariant_bindingAndParametersAreFinal\n[PASS] invariant_claimsAreBackedByManagerBalance\n[PASS] invariant_hookHoldsNothingButImdClaims\n[PASS] invariant_noHandlerViolations\n[PASS] invariant_payoutGetsExactlyWhatWasSweptAndNothingElse\n[PASS] invariant_pendingEqualsAccruedPlusGiftedMinusSwept\n[PASS] invariant_valueIsConserved\n RealV4HookInvariantsImdIsCurrency1 invariants (runs: 48, calls: 1536, reverts: 0)\n\n╭-------------------+-------------------+-------+---------+----------╮\n| Contract          | Selector          | Calls | Reverts | Discards |\n+====================================================================+\n| RealV4HookHandler | addLiquidity      | 148   | 0       | 0        |\n|-------------------+-------------------+-------+---------+----------|\n| RealV4HookHandler | donate            | 142   | 0       | 0        |\n|-------------------+-------------------+-------+---------+----------|\n| RealV4HookHandler | giftClaims        | 143   | 0       | 0        |\n|-------------------+-------------------+-------+---------+----------|\n| RealV4HookHandler | giftTokenClaims   | 163   | 0       | 0        |\n|-------------------+-------------------+-------+---------+----------|\n| RealV4HookHandler | reinitialize      | 165   | 0       | 0        |\n|-------------------+-------------------+-------+---------+----------|\n| RealV4HookHandler | removeLiquidity   | 161   | 0       | 0        |\n|-------------------+-------------------+-------+---------+----------|\n| RealV4HookHandler | strangerCallbacks | 137   | 0       | 0        |\n|-------------------+-------------------+-------+---------+----------|\n| RealV4HookHandler | swap              | 169   | 0       | 0        |\n|-------------------+-------------------+-------+---------+----------|\n| RealV4HookHandler | sweep             | 158   | 0       | 0        |\n|-------------------+-------------------+-------+---------+----------|\n| RealV4HookHandler | warp              | 150   | 0       | 0        |\n╰-------------------+-------------------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 540.70ms (509.69ms CPU time)\n\nRan 50 tests for test/MoneyBackRouter.t.sol:MoneyBackRouterTest\n[PASS]\nMoneyBackRouterTest invariants:\n[PASS] invariant_bindingsNeverChange\n[PASS] invariant_hookPendingEqualsRouterFeesMinusSwept\n[PASS] invariant_routerHoldsNothing\n[PASS] invariant_valueIsConserved\n MoneyBackRouterTest invariants (runs: 64, calls: 2560, reverts: 0)\n\n╭---------------+----------+-------+---------+----------╮\n| Contract      | Selector | Calls | Reverts | Discards |\n+=======================================================+\n| RouterHandler | buy      | 513   | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RouterHandler | quote    | 523   | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RouterHandler | sell     | 513   | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RouterHandler | sweep    | 484   | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RouterHandler | warp     | 527   | 0       | 0        |\n╰---------------+----------+-------+---------+----------╯\n\n[PASS] testFuzz_buyMatchesFormulaAndLeavesNothing(uint256,uint32,uint256,uint256) (runs: 600, μ: 1308910, ~: 1341817)\n[PASS] testFuzz_sellMatchesFormulaAndLeavesNothing(uint256,uint32,uint256,uint256) (runs: 600, μ: 1502047, ~: 1525565)\n[PASS] test_acceptanceRouterBuyPaysHookExactlyLikeDirectImdBuy() (gas: 1742077)\n[PASS] test_beforePoolBoundEverythingReverts() (gas: 13575982)\n[PASS] test_buyAboveInt128RevertsAmountOverflow() (gas: 1026304)\n[PASS] test_buyAndSellRevertOnDeadline() (gas: 1396112)\n[PASS] test_buyHappyPath() (gas: 753102)\n[PASS] test_buyRefundsEthDustWhenPublicPoolFillsPartially() (gas: 737941)\n[PASS] test_buyRejectsPartialLaunchFillAndRollsBackBothLegs() (gas: 768895)\n[PASS] test_buyRejectsPartialLaunchFillEvenWithPartialPublicFill() (gas: 815857)\n[PASS] test_buyRevertsOnMinOut() (gas: 2017535)\n[PASS] test_buySwapLegsAreExactInputWithWidestLimitsAndRouterAsSender() (gas: 632417)\n[PASS] test_buyThenSellRoundTrip() (gas: 1302049)\n[PASS] test_buyTwiceAndByTwoUsers() (gas: 1750389)\n[PASS] test_buyWorksWithImdAsCurrency1() (gas: 12954225)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 1005946)\n[PASS] test_constructorStoresImmutablesFromHook() (gas: 39375)\n[PASS] test_contractWithoutReceiveCanBuyFullFillOnly() (gas: 2497010)\n[PASS] test_deadlineIsCheckedBeforeAmount() (gas: 65505)\n[PASS] test_feesFromRouterTradesAreSweepableToPayout() (gas: 1464894)\n[PASS] test_guardResetsAfterReverts() (gas: 2104653)\n[PASS] test_launchPoolKeyComesFromHook() (gas: 24012)\n[PASS] test_managerThatNeverCallsBackIsRejected() (gas: 320482)\n[PASS] test_publicPoolKeyIsHardcoded() (gas: 7313)\n[PASS] test_quoteBubblesUpForeignReverts() (gas: 24314210)\n[PASS] test_quoteBuyMatchesBuyAndChangesNothing() (gas: 3109047)\n[PASS] test_quoteCannotBeCalledWhileLocked() (gas: 838873)\n[PASS] test_quoteSellMatchesSellAndChangesNothing() (gas: 3300864)\n[PASS] test_quoteZeroReturnsZeroWithoutTouchingTheManager() (gas: 61498)\n[PASS] test_receiveRejectsEthFromAnyoneButTheManager() (gas: 774478)\n[PASS] test_reentrancyFromBuyDustRefundIsBlocked() (gas: 5516137)\n[PASS] test_reentrancyFromSellProceedsIsBlocked() (gas: 5538800)\n[PASS] test_routerHasNoAdminSurface() (gas: 163673)\n[PASS] test_sellAfterSurchargeDecayPaysOnlyBaseFee() (gas: 856071)\n[PASS] test_sellHappyPathAtT0WithSurcharge() (gas: 855699)\n[PASS] test_sellPrefundsRouterThenSettlesAndRefundsFromItsOwnBalance() (gas: 827297)\n[PASS] test_sellPullsExactlyTokensAndKeepsNoAllowance() (gas: 694640)\n[PASS] test_sellRefundsBothDusts() (gas: 924977)\n[PASS] test_sellRefundsImdDustWhenPublicPoolFillsPartially() (gas: 876869)\n[PASS] test_sellRefundsTokenDustWhenOurPoolFillsPartially() (gas: 863132)\n[PASS] test_sellRevertsOnMinOut() (gas: 1570771)\n[PASS] test_sellSwapLegsAreExactInputWithWidestLimits() (gas: 745717)\n[PASS] test_sellWhereSecondLegYieldsNothingRefundsNothingAndReverts() (gas: 942355)\n[PASS] test_sellWithoutAllowanceOrBalanceReverts() (gas: 334630)\n[PASS] test_tamperedCallbackDataIsRejected() (gas: 1061457)\n[PASS] test_tinyBuyThatYieldsNothingReverts() (gas: 2158006)\n[PASS] test_unlockCallbackRejectsManagerWithNoCallInFlight() (gas: 120933)\n[PASS] test_unlockCallbackRejectsNonManager() (gas: 59782)\n[PASS] test_zeroValueBuyAndZeroTokenSellRevert() (gas: 237184)\nSuite result: ok. 50 passed; 0 failed; 0 skipped; finished in 831.57ms (1.10s CPU time)\n\nRan 1 test for test/MoneyBackRealV4Invariants.t.sol:RealV4RouterInvariants\n[PASS]\nRealV4RouterInvariants invariants:\n[PASS] invariant_feesStayInImdAndReachOnlyPayout\n[PASS] invariant_noRouterHandlerViolations\n[PASS] invariant_routerHoldsNothing\n[PASS] invariant_valueIsConservedAcrossBothPools\n RealV4RouterInvariants invariants (runs: 48, calls: 1536, reverts: 0)\n\n╭---------------------+----------+-------+---------+----------╮\n| Contract            | Selector | Calls | Reverts | Discards |\n+=============================================================+\n| RealV4RouterHandler | buy      | 296   | 0       | 0        |\n|---------------------+----------+-------+---------+----------|\n| RealV4RouterHandler | quote    | 328   | 0       | 0        |\n|---------------------+----------+-------+---------+----------|\n| RealV4RouterHandler | sell     | 310   | 0       | 0        |\n|---------------------+----------+-------+---------+----------|\n| RealV4RouterHandler | sweep    | 307   | 0       | 0        |\n|---------------------+----------+-------+---------+----------|\n| RealV4RouterHandler | warp     | 295   | 0       | 0        |\n╰---------------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 865.45ms (835.01ms CPU time)\n\nRan 11 test suites in 870.59ms (4.35s CPU time): 225 tests passed, 0 failed, 0 skipped (225 total tests)\n","passed":true},{"durationMs":49,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"MoneyBackHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"MoneyBackHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"MoneyBackHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"MoneyBackHook.sweep()\",\"MoneyBackHook.unlockCallback(bytes)\",\"MoneyBackRouter.buyWithEth(uint256,uint256)\",\"MoneyBackRouter.quoteBuy(uint256)\",\"MoneyBackRouter.quoteSell(uint256)\",\"MoneyBackRouter.receive()\",\"MoneyBackRouter.sellForEth(uint256,uint256,uint256)\",\"MoneyBackRouter.unlockCallback(bytes)\",\"MoneyBackToken.approve(address,uint256)\",\"MoneyBackToken.transfer(address,uint256)\",\"MoneyBackToken.transferFrom(address,address,uint256)\",\"RoundPayout.acceptOwnership()\",\"RoundPayout.fund(address,uint256)\",\"RoundPayout.pause()\",\"RoundPayout.payRound(uint256,address,address[],uint256[],bytes32,uint256,uint256)\",\"RoundPayout.renounceOwnership()\",\"RoundPayout.retryFailed(uint256,address[])\",\"RoundPayout.sweep(address,address,uint256)\",\"RoundPayout.transferOwnership(address)\",\"RoundPayout.unpause()\",\"RoundPayout.writeOffFailed(uint256,address)\"],\"files\":{\"README.md\":118,\"assets/moneyback.png\":39,\"assets/moneyback.svg\":8,\"docs/AUDIT_RESPONSES.md\":97,\"docs/network-observations.json\":36,\"docs/requester-share.json\":4,\"foundry.toml\":13,\"launch.json\":31,\"script/DeployAuxiliaries.s.sol\":64,\"src/MoneyBackHook.sol\":515,\"src/MoneyBackRouter.sol\":446,\"src/MoneyBackToken.sol\":153,\"src/RoundPayout.sol\":373,\"test/DeployAuxiliaries.t.sol\":73,\"test/MoneyBackHook.t.sol\":1319,\"test/MoneyBackRealV4.t.sol\":342,\"test/MoneyBackRealV4Invariants.t.sol\":1172,\"test/MoneyBackRouter.t.sol\":1840,\"test/MoneyBackToken.t.sol\":372,\"test/RoundPayout.t.sol\":1241,\"test/vendor/README.md\":8,\"test/vendor/solmate/LICENSE\":661,\"test/vendor/solmate/src/auth/Owned.sol\":44,\"test/vendor/v4-core/licenses/BUSL_LICENSE\":63,\"test/vendor/v4-core/licenses/MIT_LICENSE\":7,\"test/vendor/v4-core/src/ERC6909.sol\":90,\"test/vendor/v4-core/src/ERC6909Claims.sol\":23,\"test/vendor/v4-core/src/Extsload.sol\":64,\"test/vendor/v4-core/src/Exttload.sol\":40,\"test/vendor/v4-core/src/NoDelegateCall.sol\":33,\"test/vendor/v4-core/src/PoolManager.sol\":394,\"test/vendor/v4-core/src/ProtocolFees.sol\":71,\"test/vendor/v4-core/src/interfaces/IExtsload.sol\":21,\"test/vendor/v4-core/src/interfaces/IExttload.sol\":15,\"test/vendor/v4-core/src/interfaces/IHooks.sol\":152,\"test/vendor/v4-core/src/interfaces/IPoolManager.sol\":217,\"test/vendor/v4-core/src/interfaces/IProtocolFees.sol\":52,\"test/vendor/v4-core/src/interfaces/callback/IUnlockCallback.sol\":10,\"test/vendor/v4-core/src/interfaces/external/IERC20Minimal.sol\":48,\"test/vendor/v4-core/src/interfaces/external/IERC6909Claims.sol\":66,\"test/vendor/v4-core/src/libraries/BitMath.sol\":49,\"test/vendor/v4-core/src/libraries/CurrencyDelta.sol\":42,\"test/vendor/v4-core/src/libraries/CurrencyReserves.sol\":39,\"test/vendor/v4-core/src/libraries/CustomRevert.sol\":120,\"test/vendor/v4-core/src/libraries/FixedPoint128.sol\":8,\"test/vendor/v4-core/src/libraries/FixedPoint96.sol\":10,\"test/vendor/v4-core/src/libraries/FullMath.sol\":117,\"test/vendor/v4-core/src/libraries/Hooks.sol\":344,\"test/vendor/v4-core/src/libraries/LPFeeLibrary.sol\":79,\"test/vendor/v4-core/src/libraries/LiquidityMath.sol\":20,\"test/vendor/v4-core/src/libraries/Lock.sol\":28,\"test/vendor/v4-core/src/libraries/NonzeroDeltaCount.sol\":35,\"test/vendor/v4-core/src/libraries/ParseBytes.sol\":29,\"test/vendor/v4-core/src/libraries/Pool.sol\":620,\"test/vendor/v4-core/src/libraries/Position.sol\":103,\"test/vendor/v4-core/src/libraries/ProtocolFeeLibrary.sol\":47,\"test/vendor/v4-core/src/libraries/SafeCast.sol\":60,\"test/vendor/v4-core/src/libraries/SqrtPriceMath.sol\":292,\"test/vendor/v4-core/src/libraries/StateLibrary.sol\":345,\"test/vendor/v4-core/src/libraries/SwapMath.sol\":108,\"test/vendor/v4-core/src/libraries/TickBitmap.sol\":121,\"test/vendor/v4-core/src/libraries/TickMath.sol\":238,\"test/vendor/v4-core/src/libraries/UnsafeMath.sol\":29,\"test/vendor/v4-core/src/types/BalanceDelta.sol\":72,\"test/vendor/v4-core/src/types/BeforeSwapDelta.sol\":38,\"test/vendor/v4-core/src/types/Currency.sol\":118,\"test/vendor/v4-core/src/types/PoolId.sol\":17,\"test/vendor/v4-core/src/types/PoolKey.sol\":22,\"test/vendor/v4-core/src/types/PoolOperation.sol\":26,\"test/vendor/v4-core/src/types/Slot0.sol\":95},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"38756658f74398fc88c1d8fbdfe84ee609dde5bffb8e8a38aaddef978a5770f4","verifiedTreeHash":"9dd8168c9ca940a6ee83726824561f4c9748a1e4","verifierVersion":"0.1.0+0e08a464"},{"checks":[{"durationMs":4996,"exitCode":0,"name":"build","output":"Compiling 57 files with Solc 0.8.26\nSolc 0.8.26 finished in 4.83s\nCompiler run successful!\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/RoundPayout.sol:61:32\n   │\n61 │     function transferOwnership(address newOwner) external onlyOwner {\n   │                                ━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackRouter.sol:389:24\n    │\n389 │         r = abi.decode(poolManager.unlock(data), (Result));\n    │                        ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackRouter.sol:422:22\n    │\n422 │         (bool ok,) = to.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/MoneyBackRouter.sol:204:13\n    │\n204 │         if (block.timestamp > deadline) revert Expired();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MoneyBackRouter.sol:212:9\n    │\n212 │         emit BoughtWithEth(msg.sender, r.amountIn, r.imdMid, tokensOut);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackRouter.sol:229:14\n    │\n229 │         if (!IERC20(token).transferFrom(msg.sender, address(this), tokens)) revert TransferFromFailed();\n    │              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackRouter.sol:235:29\n    │\n235 │         if (unused != 0 && !IERC20(token).transfer(msg.sender, unused)) revert TransferFailed();\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/MoneyBackRouter.sol:225:13\n    │\n225 │         if (block.timestamp > deadline) revert Expired();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MoneyBackRouter.sol:237:9\n    │\n237 │         emit SoldForEth(msg.sender, r.amountIn, r.imdMid, ethOut);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:227:22\n    │\n227 │         if (tokens > uint256(uint128(type(int128).max))) revert AmountOverflow();\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/MoneyBackRouter.sol:227:30\n    │\n227 │         if (tokens > uint256(uint128(type(int128).max))) revert AmountOverflow();\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackRouter.sol:400:13\n    │\n400 │         try poolManager.unlock(data) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/RoundPayout.sol:360:40\n    │\n360 │         (bool ok, bytes memory data) = token.call(abi.encodeCall(IERC20.transferFrom, (from, to, amount)));\n    │                                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RoundPayout.sol:193:9\n    │\n193 │         emit Funded(token, msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/RoundPayout.sol:354:40\n    │\n354 │         (bool ok, bytes memory data) = token.call(abi.encodeCall(IERC20.transfer, (to, amount)));\n    │                                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/RoundPayout.sol:197:82\n    │\n197 │     function sweep(address token, address to, uint256 amount) external onlyOwner nonReentrant {\n    │                                                                                  ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RoundPayout.sol:200:9\n    │\n200 │         emit Swept(token, to, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/RoundPayout.sol:348:22\n    │\n348 │         (ok, data) = token.call(abi.encodeCall(IERC20.transfer, (to, amount)));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/RoundPayout.sol:223:40\n    │\n223 │     ) external onlyOwner whenNotPaused nonReentrant returns (uint256 totalPaid) {\n    │                                        ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RoundPayout.sol:244:9\n    │\n244 │         emit RoundPaid(roundId, token, ledgerHash, twapCloseX96, totalEligibleLoss, totalPaid, n);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/RoundPayout.sol:348:22\n    │\n348 │         (ok, data) = token.call(abi.encodeCall(IERC20.transfer, (to, amount)));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RoundPayout.sol:258:17\n    │\n258 │                 emit Paid(roundId, recipient, 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/RoundPayout.sol:262:17\n    │\n262 │                 emit PayFailed(roundId, recipient, amount, reason);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `failed` is updated\n    ╭▸ src/RoundPayout.sol:348:22\n    │\n348 │         (ok, data) = token.call(abi.encodeCall(IERC20.transfer, (to, amount)));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:306:22\n    │\n306 │         r.amountIn = uint256(-a0);\n    │                      ━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/RoundPayout.sol:302:13\n    │\n302 │             needed += owed;\n    │             ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:307:26\n    │\n307 │         uint256 imdOut = uint256(a1);\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[unused-return]: return value of an external call is not used\n    ╭▸ src/MoneyBackRouter.sol:312:13\n    │\n312 │             poolManager.settle{value: r.amountIn}();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/RoundPayout.sol:288:9\n    │\n288 │         nonReentrant\n    │         ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:320:20\n    │\n320 │         r.imdMid = uint256(-imdDelta);\n    │                    ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RoundPayout.sol:316:17\n    │\n316 │                 emit Paid(roundId, recipient, amount);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:321:23\n    │\n321 │         r.amountOut = uint256(tokenDelta);\n    │                       ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RoundPayout.sol:318:17\n    │\n318 │                 emit PayFailed(roundId, recipient, amount, reason);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/RoundPayout.sol:301:28\n    │\n301 │             if (owed == 0) revert NothingFailed(roundId, to[i]);\n    │                            ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/RoundPayout.sol:309:30\n    │\n309 │             if (amount == 0) revert NothingFailed(roundId, recipient);\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:339:22\n    │\n339 │         r.amountIn = uint256(-tokenDelta);\n    │                      ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/MoneyBackRouter.sol:343:13\n    │\n343 │             poolManager.settle();\n    │             ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:345:26\n    │\n345 │         uint256 imdOut = uint256(imdDelta);\n    │                          ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:350:20\n    │\n350 │         r.imdMid = uint256(-a1);\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/MoneyBackRouter.sol:351:23\n    │\n351 │         r.amountOut = uint256(a0);\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/MoneyBackRouter.sol:362:24\n    │\n362 │         if (amountIn > uint256(uint128(type(int128).max))) revert AmountOverflow();\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/MoneyBackRouter.sol:362:32\n    │\n362 │         if (amountIn > uint256(uint128(type(int128).max))) revert AmountOverflow();\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:367:35\n    │\n367 │                 amountSpecified: -int256(amountIn),\n    │                                   ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/RoundPayout.sol:324:77\n    │\n324 │     function writeOffFailed(uint256 roundId, address to) external onlyOwner nonReentrant {\n    │                                                                             ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:405:40\n    │\n405 │             if (reason.length == 36 && bytes4(reason) == QuoteResult.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[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/MoneyBackRouter.sol:422:22\n    │\n422 │         (bool ok,) = to.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/MoneyBackHook.sol:344:13\n    │\n344 │         if (elapsed >= SURCHARGE_DURATION) return 0;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MoneyBackHook.sol:376:27\n    │\n376 │         _imdIsCurrency0 = imdIsCurrency0;\n    │                           ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:397:39\n    │\n397 │         uint256 imdLeg = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n    │                                       ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:397:74\n    │\n397 │         uint256 imdLeg = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n    │                                                                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MoneyBackHook.sol:481:9\n    │\n481 │         emit FeeAccrued(isSell, baseFee, surcharge, imdLeg);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:416:46\n    │\n416 │             uint256 specified = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n    │                                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:416:81\n    │\n416 │             uint256 specified = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n    │                                                                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:421:54\n    │\n421 │             if (imdDelta != params.amountSpecified + int256(baseFee + surcharge)) revert PartialFill();\n    │                                                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:425:41\n    │\n425 │         uint256 imdLeg = imdDelta < 0 ? uint256(-imdDelta) : uint256(imdDelta);\n    │                                         ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:425:62\n    │\n425 │         uint256 imdLeg = imdDelta < 0 ? uint256(-imdDelta) : uint256(imdDelta);\n    │                                                              ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackHook.sol:439:9\n    │\n439 │         poolManager.unlock(\"\");\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/MoneyBackHook.sol:439:9\n    │\n439 │         poolManager.unlock(\"\");\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MoneyBackHook.sol:453:9\n    │\n453 │         emit Swept(amount, payout);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:490:17\n    │\n490 │         if (x > uint256(uint128(type(int128).max))) revert AmountOverflow();\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/MoneyBackHook.sol:490:25\n    │\n490 │         if (x > uint256(uint128(type(int128).max))) revert AmountOverflow();\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:491:16\n    │\n491 │         return int128(uint128(x));\n    │                ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:491:23\n    │\n491 │         return int128(uint128(x));\n    │                       ━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n     ╭▸ test/MoneyBackRouter.t.sol:1132:24\n     │\n1132 │         uint256 now_ = block.timestamp;\n     │                        ━━━━━━━━━━━━━━━\n1133 │         vm.warp(ts);\n     │         ─────────── `vm.warp` changes this environment here\n     │\n     ├ help: capture it with `vm.getBlockTimestamp()` instead\n     ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n     ╭▸ test/MoneyBackRouter.t.sol:1159:53\n     │\n1159 │         uint256 bought = _buyAs(ALICE, 10 ether, 0, block.timestamp);\n     │                                                     ━━━━━━━━━━━━━━━\n1160 │         vm.warp(T0 + 3600);\n     │         ────────────────── `vm.warp` changes this environment here\n     │\n     ├ help: capture it with `vm.getBlockTimestamp()` instead\n     ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n     ╭▸ test/MoneyBackRouter.t.sol:1518:49\n     │\n1518 │         assertEq(_sellAs(ALICE, tokens, quoted, block.timestamp), quoted, \"sell delivers exactly the quote\");\n     │                                                 ━━━━━━━━━━━━━━━\n1519 │         vm.warp(T0 + 1800);\n     │         ────────────────── `vm.warp` changes this environment here\n     │\n     ├ help: capture it with `vm.getBlockTimestamp()` instead\n     ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":1122,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/DeployAuxiliaries.t.sol:DeployAuxiliariesTest\n[PASS] test_deploysBothAuxiliariesWithPayingWalletOwner() (gas: 2838233)\n[PASS] test_uninitializedHookRefused() (gas: 21545030)\n[PASS] test_unresolvedAddressesRefused() (gas: 61999)\n[PASS] test_wrongChainRefused() (gas: 35325)\n[PASS] test_wrongManagerRefused() (gas: 41055)\n[PASS] test_wrongWalletRefused() (gas: 4940831)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 55.20ms (39.38ms CPU time)\n\nRan 12 tests for test/MoneyBackRealV4.t.sol:MoneyBackRealV4Token1Test\n[PASS] testFuzz_fullFillSpecifiedFee(uint96,uint32,bool) (runs: 256, μ: 200939, ~: 199703)\n[PASS] test_exactInputBuyExhaustedLiquidityReverts() (gas: 601438)\n[PASS] test_exactInputBuyTightLimitRevertsWithoutOvercharge() (gas: 222281)\n[PASS] test_exactOutputSellExhaustedLiquidityReverts() (gas: 585713)\n[PASS] test_exactOutputSellTightLimitRevertsWithoutTakingIMD() (gas: 216822)\n[PASS] test_freshSingleSidedPoolCanBuyAndSweepWithNoIMDReserve() (gas: 5560037)\n[PASS] test_fullFillAllFourCasesAndSweep() (gas: 2256502)\n[PASS] test_liquidityConversionIsOutsideSwapSurcharge() (gas: 579232)\n[PASS] test_partialExactOutputBuyChargesActualIMD() (gas: 572071)\n[PASS] test_partialUnspecifiedIMDStillChargesActualFill() (gas: 584148)\n[PASS] test_routerTradesAgainstRealManagerAndRefundsPartialSell() (gas: 2047133)\n[PASS] test_singleSidedPoolRejectsUnfillableExactOutputSell() (gas: 5746588)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 110.56ms (103.68ms CPU time)\n\nRan 12 tests for test/MoneyBackRealV4.t.sol:MoneyBackRealV4Token0Test\n[PASS] testFuzz_fullFillSpecifiedFee(uint96,uint32,bool) (runs: 256, μ: 199727, ~: 193256)\n[PASS] test_exactInputBuyExhaustedLiquidityReverts() (gas: 594713)\n[PASS] test_exactInputBuyTightLimitRevertsWithoutOvercharge() (gas: 216118)\n[PASS] test_exactOutputSellExhaustedLiquidityReverts() (gas: 592318)\n[PASS] test_exactOutputSellTightLimitRevertsWithoutTakingIMD() (gas: 222865)\n[PASS] test_freshSingleSidedPoolCanBuyAndSweepWithNoIMDReserve() (gas: 7620419)\n[PASS] test_fullFillAllFourCasesAndSweep() (gas: 2255552)\n[PASS] test_liquidityConversionIsOutsideSwapSurcharge() (gas: 565795)\n[PASS] test_partialExactOutputBuyChargesActualIMD() (gas: 565447)\n[PASS] test_partialUnspecifiedIMDStillChargesActualFill() (gas: 590858)\n[PASS] test_routerTradesAgainstRealManagerAndRefundsPartialSell() (gas: 2048465)\n[PASS] test_singleSidedPoolRejectsUnfillableExactOutputSell() (gas: 7878714)\nSuite result: ok. 12 passed; 0 failed; 0 skipped; finished in 114.29ms (74.44ms CPU time)\n\nRan 27 tests for test/MoneyBackToken.t.sol:MoneyBackTokenTest\n[PASS]\nMoneyBackTokenTest invariants:\n[PASS] invariant_balancesSumToSupply\n[PASS] invariant_totalSupplyNeverChanges\n[PASS] invariant_zeroAddressHoldsNothing\n MoneyBackTokenTest invariants (runs: 128, calls: 4096, reverts: 0)\n\n╭--------------+--------------+-------+---------+----------╮\n| Contract     | Selector     | Calls | Reverts | Discards |\n+==========================================================+\n| TokenHandler | approve      | 1335  | 0       | 0        |\n|--------------+--------------+-------+---------+----------|\n| TokenHandler | transfer     | 1356  | 0       | 0        |\n|--------------+--------------+-------+---------+----------|\n| TokenHandler | transferFrom | 1405  | 0       | 0        |\n╰--------------+--------------+-------+---------+----------╯\n\n[PASS] testFuzz_allowanceAccounting(uint256,uint256) (runs: 512, μ: 118431, ~: 134078)\n[PASS] testFuzz_transferAboveBalanceAlwaysReverts(uint256) (runs: 512, μ: 40842, ~: 40840)\n[PASS] testFuzz_transferConservesSupplyAndBalances(address,uint256) (runs: 512, μ: 92909, ~: 93547)\n[PASS] test_approveSetsAllowanceAndEmits() (gas: 97003)\n[PASS] test_approveZeroSpenderReverts() (gas: 30352)\n[PASS] test_constructorMintsWholeSupplyToDeployer() (gas: 17042)\n[PASS] test_deployerIsMsgSenderNotTxOrigin() (gas: 188189)\n[PASS] test_metadata() (gas: 17630)\n[PASS] test_noPrivilegedSurface() (gas: 165545)\n[PASS] test_rejectsEth() (gas: 32817)\n[PASS] test_supplyIsExactlyOneBillionWithEighteenDecimals() (gas: 13317)\n[PASS] test_transferFromDoesNotEmitApprovalOnSpend() (gas: 115689)\n[PASS] test_transferFromEmptyAccountRevertsEvenForOneWei() (gas: 33309)\n[PASS] test_transferFromExactAllowanceLeavesZero() (gas: 144349)\n[PASS] test_transferFromInfiniteAllowanceIsNotDecremented() (gas: 112829)\n[PASS] test_transferFromRevertsWhenAllowanceBelowAmount() (gas: 80374)\n[PASS] test_transferFromRevertsWhenBalanceBelowAmountEvenWithAllowance() (gas: 86141)\n[PASS] test_transferFromRevertsWithoutAllowance() (gas: 33778)\n[PASS] test_transferFromSpendsAllowance() (gas: 125597)\n[PASS] test_transferFromToZeroAddressReverts() (gas: 83503)\n[PASS] test_transferMovesBalanceAndEmits() (gas: 87232)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 40643)\n[PASS] test_transferToSelfKeepsBalance() (gas: 42924)\n[PASS] test_transferToZeroAddressReverts() (gas: 30391)\n[PASS] test_transferWholeBalanceLeavesZero() (gas: 70165)\n[PASS] test_transferZeroAmountSucceeds() (gas: 37089)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 338.22ms (782.00ms CPU time)\n\nRan 58 tests for test/MoneyBackHook.t.sol:MoneyBackHookTest\n[PASS]\nMoneyBackHookTest invariants:\n[PASS] invariant_hookNeverHoldsMoneybackClaims\n[PASS] invariant_lpFeeNeverOverriddenAndManagerLocked\n[PASS] invariant_pendingEqualsAccruedMinusSwept\n[PASS] invariant_sweepPaysOnlyPayout\n MoneyBackHookTest invariants (runs: 64, calls: 2560, reverts: 0)\n\n╭-------------+----------+-------+---------+----------╮\n| Contract    | Selector | Calls | Reverts | Discards |\n+=====================================================+\n| HookHandler | swap     | 855   | 0       | 0        |\n|-------------+----------+-------+---------+----------|\n| HookHandler | sweep    | 861   | 0       | 0        |\n|-------------+----------+-------+---------+----------|\n| HookHandler | warp     | 844   | 0       | 0        |\n╰-------------+----------+-------+---------+----------╯\n\n[PASS] testFuzz_feeMathsAllCases(uint8,uint256,uint32) (runs: 1000, μ: 172160, ~: 174190)\n[PASS] testFuzz_sequenceAccumulatesPending(uint256,uint256,uint256,uint32) (runs: 256, μ: 325390, ~: 328043)\n[PASS] testFuzz_specifiedOneWeiUnderfillAlwaysRefused(uint96,uint32,bool) (runs: 256, μ: 59836, ~: 55030)\n[PASS] testFuzz_surchargeFormula(uint32) (runs: 1000, μ: 18948, ~: 18677)\n[PASS] testFuzz_surchargeIsMonotoneNonIncreasing(uint32,uint32) (runs: 512, μ: 21710, ~: 21762)\n[PASS] testFuzz_tinyAmountsRoundDownToZeroFee(uint8,uint256,uint32) (runs: 512, μ: 113741, ~: 107500)\n[PASS] test_acceptanceSellCostAtT0AndAfterDecay() (gas: 309520)\n[PASS] test_afterSwapWithZeroPoolDeltaAccruesNothing() (gas: 64225)\n[PASS] test_allCasesWithImdAsCurrency1() (gas: 7198384)\n[PASS] test_buysNeverSurchargedAtAnyTime() (gas: 499297)\n[PASS] test_callbacksRejectNonPoolManager() (gas: 159908)\n[PASS] test_case1_exactInputBuy_t0() (gas: 173991)\n[PASS] test_case1_exactInputBuy_t1800() (gas: 173514)\n[PASS] test_case1_exactInputBuy_t1day() (gas: 173555)\n[PASS] test_case1_exactInputBuy_t900() (gas: 174001)\n[PASS] test_case2_exactOutputBuy_t0() (gas: 172741)\n[PASS] test_case2_exactOutputBuy_t1800() (gas: 172288)\n[PASS] test_case2_exactOutputBuy_t1day() (gas: 172240)\n[PASS] test_case2_exactOutputBuy_t900() (gas: 172795)\n[PASS] test_case3_exactInputSell_t0() (gas: 173564)\n[PASS] test_case3_exactInputSell_t1800() (gas: 172953)\n[PASS] test_case3_exactInputSell_t1day() (gas: 172994)\n[PASS] test_case3_exactInputSell_t900() (gas: 173565)\n[PASS] test_case4_exactOutputSell_t0() (gas: 174286)\n[PASS] test_case4_exactOutputSell_t1800() (gas: 173460)\n[PASS] test_case4_exactOutputSell_t1day() (gas: 137777)\n[PASS] test_case4_exactOutputSell_t900() (gas: 174276)\n[PASS] test_constructorRejectsImdAsLaunchToken() (gas: 63906)\n[PASS] test_constructorRejectsWrongFlagBits() (gas: 35206594)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 182420)\n[PASS] test_constructorStoresImmutablesAndConstants() (gas: 58941)\n[PASS] test_deploymentAddressFlagBitsMatchPermissions() (gas: 18443)\n[PASS] test_emptySweepSucceedsAndEmitsZero() (gas: 75505)\n[PASS] test_feeAccruedEventsPerCase() (gas: 407394)\n[PASS] test_fixedCallbacksOnlyChargeTheirOwnCases() (gas: 163278)\n[PASS] test_hookNeverMintsMoneybackClaimsEvenIfGiftedImdClaims() (gas: 295107)\n[PASS] test_hookRejectsEthAndHasNoAdminSurface() (gas: 169036)\n[PASS] test_hugeSpecifiedAmountRevertsAmountOverflow() (gas: 148943)\n[PASS] test_initializeAcceptsEitherCurrencyOrder() (gas: 5324174)\n[PASS] test_initializeBindsPoolAndRecordsTimestamp() (gas: 58390)\n[PASS] test_initializeEmitsPoolBound() (gas: 5329148)\n[PASS] test_initializeRejectsSecondPool() (gas: 74559)\n[PASS] test_initializeRejectsWrongKeys() (gas: 5837639)\n[PASS] test_specifiedZeroFeeUnderfillRefused() (gas: 53203)\n[PASS] test_surchargeAt1799IsStillChargedOnSells() (gas: 134690)\n[PASS] test_surchargeAt1800IsZeroOnSells() (gas: 134179)\n[PASS] test_surchargeBoundaries() (gas: 82994)\n[PASS] test_swapBeforeInitializationReverts() (gas: 5243025)\n[PASS] test_swapRejectsWrongPoolKey() (gas: 260897)\n[PASS] test_sweepAccumulatesAcrossRounds() (gas: 453223)\n[PASS] test_sweepCannotBeNestedInsideAnotherUnlock() (gas: 774133)\n[PASS] test_sweepReentrancyFromPayoutTransferIsRejected() (gas: 758757)\n[PASS] test_sweepRevertsCleanlyIfTakeFails() (gas: 453097)\n[PASS] test_sweepTakesAllImdClaimsToPayout() (gas: 452081)\n[PASS] test_unlockCallbackOutsideSweepIsRejected() (gas: 166965)\n[PASS] test_viewsBeforeInitialization() (gas: 5182752)\n[PASS] test_zeroAmountSwapAccruesNothing() (gas: 204295)\nSuite result: ok. 58 passed; 0 failed; 0 skipped; finished in 420.19ms (1.02s CPU time)\n\nRan 51 tests for test/RoundPayout.t.sol:RoundPayoutTest\n[PASS]\nRoundPayoutTest invariants:\n[PASS] invariant_balanceEqualsFundedMinusPaidMinusSwept\n[PASS] invariant_ownerNeverChangesWithoutHandoff\n[PASS] invariant_roundRecordsMatchLedger\n[PASS] invariant_sumPaidNeverExceedsFunded\n RoundPayoutTest invariants (runs: 96, calls: 3840, reverts: 27)\n\n╭---------------+---------------+-------+---------+----------╮\n| Contract      | Selector      | Calls | Reverts | Discards |\n+============================================================+\n| PayoutHandler | fund          | 423   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | pay           | 431   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | replay        | 418   | 27      | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | retry         | 439   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | setBlocked    | 434   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | strangerCalls | 453   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | sweep         | 432   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | togglePause   | 407   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | writeOff      | 403   | 0       | 0        |\n╰---------------+---------------+-------+---------+----------╯\n\n[PASS] testFuzz_batchSizeBoundary(uint16) (runs: 256, μ: 2506888, ~: 1545339)\n[PASS] testFuzz_payRoundAccounting(uint256,uint256,uint256,uint256,uint8) (runs: 512, μ: 320199, ~: 231453)\n[PASS] test_allTransfersFailedReverts() (gas: 348572)\n[PASS] test_constructorRejectsZeroOwner() (gas: 4122)\n[PASS] test_constructorSetsOwner() (gas: 22600)\n[PASS] test_duplicateFailedRecipientAccumulates() (gas: 386848)\n[PASS] test_fundAcceptsNoReturnToken() (gas: 476904)\n[PASS] test_fundPullsTokensFromAnyone() (gas: 174875)\n[PASS] test_fundRejectsEoaToken() (gas: 38646)\n[PASS] test_fundRejectsFalseReturningToken() (gas: 196876)\n[PASS] test_fundWithoutApprovalReverts() (gas: 96340)\n[PASS] test_fundZeroAmountSucceeds() (gas: 47919)\n[PASS] test_ownable2StepHandoff() (gas: 285132)\n[PASS] test_partialFailureIsRecordedAndBatchContinues() (gas: 430479)\n[PASS] test_partialFailureSilentRevertAndFalseReturn() (gas: 453285)\n[PASS] test_pauseTwiceAndUnpauseWhenRunningRevert() (gas: 108689)\n[PASS] test_payRoundBatchTooLarge() (gas: 283154)\n[PASS] test_payRoundBlockedWhilePaused() (gas: 388439)\n[PASS] test_payRoundEmptyBatch() (gas: 50369)\n[PASS] test_payRoundExactBalanceSucceeds() (gas: 329406)\n[PASS] test_payRoundIdempotent() (gas: 668330)\n[PASS] test_payRoundInsufficientBalanceUpFront() (gas: 127472)\n[PASS] test_payRoundIsTokenAgnosticPerRound() (gas: 821050)\n[PASS] test_payRoundLengthMismatch() (gas: 47215)\n[PASS] test_payRoundMaxBatchOf500() (gas: 17384176)\n[PASS] test_payRoundPaysEveryLegAndRecords() (gas: 404630)\n[PASS] test_payRoundRoundIdZeroIsOrdinary() (gas: 287427)\n[PASS] test_payRoundSumOverflowReverts() (gas: 48383)\n[PASS] test_payRoundWithEoaTokenReverts() (gas: 47802)\n[PASS] test_payRoundWithNoReturnToken() (gas: 539417)\n[PASS] test_payRoundZeroAmountLegIsPaid() (gas: 162328)\n[PASS] test_payRoundZeroToken() (gas: 45016)\n[PASS] test_privilegedFunctionsRejectNonOwner() (gas: 165748)\n[PASS] test_reentrantTokenCannotReenterFundOrSweepDuringPayRound() (gas: 839289)\n[PASS] test_reentrantTokenCannotReenterPayRound() (gas: 699402)\n[PASS] test_renounceOwnershipLocksEverything() (gas: 150827)\n[PASS] test_retryFailedInsufficientBalanceAfterSweep() (gas: 525300)\n[PASS] test_retryFailedPaysAndClears() (gas: 573545)\n[PASS] test_retryFailedRevertsForUnpaidRound() (gas: 42949)\n[PASS] test_retryFailedShapeChecks() (gas: 806485)\n[PASS] test_retryFailedStillFailingKeepsRecord() (gas: 466851)\n[PASS] test_singleLegFailureIsAllTransfersFailed() (gas: 187742)\n[PASS] test_sweepAboveBalanceReverts() (gas: 115956)\n[PASS] test_sweepByOwnerMovesTokens() (gas: 155304)\n[PASS] test_sweepRejectsShortReturnData() (gas: 174911)\n[PASS] test_sweepRejectsZeroRecipient() (gas: 101056)\n[PASS] test_sweepWorksWhilePaused() (gas: 185829)\n[PASS] test_transferOwnershipOnlyOwnerAndCancelable() (gas: 142679)\n[PASS] test_writeOffFailedClearsWithoutPaying() (gas: 517226)\n[PASS] test_writeOffFailedNothingOwedReverts() (gas: 40180)\nSuite result: ok. 51 passed; 0 failed; 0 skipped; finished in 689.43ms (1.00s CPU time)\n\nRan 50 tests for test/MoneyBackRouter.t.sol:MoneyBackRouterTest\n[PASS]\nMoneyBackRouterTest invariants:\n[PASS] invariant_bindingsNeverChange\n[PASS] invariant_hookPendingEqualsRouterFeesMinusSwept\n[PASS] invariant_routerHoldsNothing\n[PASS] invariant_valueIsConserved\n MoneyBackRouterTest invariants (runs: 64, calls: 2560, reverts: 0)\n\n╭---------------+----------+-------+---------+----------╮\n| Contract      | Selector | Calls | Reverts | Discards |\n+=======================================================+\n| RouterHandler | buy      | 512   | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RouterHandler | quote    | 478   | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RouterHandler | sell     | 517   | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RouterHandler | sweep    | 517   | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RouterHandler | warp     | 536   | 0       | 0        |\n╰---------------+----------+-------+---------+----------╯\n\n[PASS] testFuzz_buyMatchesFormulaAndLeavesNothing(uint256,uint32,uint256,uint256) (runs: 600, μ: 1300450, ~: 1341735)\n[PASS] testFuzz_sellMatchesFormulaAndLeavesNothing(uint256,uint32,uint256,uint256) (runs: 600, μ: 1496948, ~: 1525645)\n[PASS] test_acceptanceRouterBuyPaysHookExactlyLikeDirectImdBuy() (gas: 1742077)\n[PASS] test_beforePoolBoundEverythingReverts() (gas: 13575982)\n[PASS] test_buyAboveInt128RevertsAmountOverflow() (gas: 1026304)\n[PASS] test_buyAndSellRevertOnDeadline() (gas: 1396112)\n[PASS] test_buyHappyPath() (gas: 753102)\n[PASS] test_buyRefundsEthDustWhenPublicPoolFillsPartially() (gas: 737941)\n[PASS] test_buyRejectsPartialLaunchFillAndRollsBackBothLegs() (gas: 768895)\n[PASS] test_buyRejectsPartialLaunchFillEvenWithPartialPublicFill() (gas: 815857)\n[PASS] test_buyRevertsOnMinOut() (gas: 2017535)\n[PASS] test_buySwapLegsAreExactInputWithWidestLimitsAndRouterAsSender() (gas: 632417)\n[PASS] test_buyThenSellRoundTrip() (gas: 1302049)\n[PASS] test_buyTwiceAndByTwoUsers() (gas: 1750389)\n[PASS] test_buyWorksWithImdAsCurrency1() (gas: 12954225)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 1005946)\n[PASS] test_constructorStoresImmutablesFromHook() (gas: 39375)\n[PASS] test_contractWithoutReceiveCanBuyFullFillOnly() (gas: 2497010)\n[PASS] test_deadlineIsCheckedBeforeAmount() (gas: 65505)\n[PASS] test_feesFromRouterTradesAreSweepableToPayout() (gas: 1464894)\n[PASS] test_guardResetsAfterReverts() (gas: 2104653)\n[PASS] test_launchPoolKeyComesFromHook() (gas: 24012)\n[PASS] test_managerThatNeverCallsBackIsRejected() (gas: 320482)\n[PASS] test_publicPoolKeyIsHardcoded() (gas: 7313)\n[PASS] test_quoteBubblesUpForeignReverts() (gas: 24314210)\n[PASS] test_quoteBuyMatchesBuyAndChangesNothing() (gas: 3109047)\n[PASS] test_quoteCannotBeCalledWhileLocked() (gas: 838873)\n[PASS] test_quoteSellMatchesSellAndChangesNothing() (gas: 3300864)\n[PASS] test_quoteZeroReturnsZeroWithoutTouchingTheManager() (gas: 61498)\n[PASS] test_receiveRejectsEthFromAnyoneButTheManager() (gas: 774478)\n[PASS] test_reentrancyFromBuyDustRefundIsBlocked() (gas: 5516137)\n[PASS] test_reentrancyFromSellProceedsIsBlocked() (gas: 5538800)\n[PASS] test_routerHasNoAdminSurface() (gas: 163673)\n[PASS] test_sellAfterSurchargeDecayPaysOnlyBaseFee() (gas: 856071)\n[PASS] test_sellHappyPathAtT0WithSurcharge() (gas: 855699)\n[PASS] test_sellPrefundsRouterThenSettlesAndRefundsFromItsOwnBalance() (gas: 827297)\n[PASS] test_sellPullsExactlyTokensAndKeepsNoAllowance() (gas: 694640)\n[PASS] test_sellRefundsBothDusts() (gas: 924977)\n[PASS] test_sellRefundsImdDustWhenPublicPoolFillsPartially() (gas: 876869)\n[PASS] test_sellRefundsTokenDustWhenOurPoolFillsPartially() (gas: 863132)\n[PASS] test_sellRevertsOnMinOut() (gas: 1570771)\n[PASS] test_sellSwapLegsAreExactInputWithWidestLimits() (gas: 745717)\n[PASS] test_sellWhereSecondLegYieldsNothingRefundsNothingAndReverts() (gas: 942355)\n[PASS] test_sellWithoutAllowanceOrBalanceReverts() (gas: 334630)\n[PASS] test_tamperedCallbackDataIsRejected() (gas: 1061457)\n[PASS] test_tinyBuyThatYieldsNothingReverts() (gas: 2158006)\n[PASS] test_unlockCallbackRejectsManagerWithNoCallInFlight() (gas: 120933)\n[PASS] test_unlockCallbackRejectsNonManager() (gas: 59782)\n[PASS] test_zeroValueBuyAndZeroTokenSellRevert() (gas: 237184)\nSuite result: ok. 50 passed; 0 failed; 0 skipped; finished in 1.02s (1.25s CPU time)\n\nRan 7 test suites in 1.03s (2.75s CPU time): 216 tests passed, 0 failed, 0 skipped (216 total tests)\n","passed":true},{"durationMs":61,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"MoneyBackHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"MoneyBackHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"MoneyBackHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"MoneyBackHook.sweep()\",\"MoneyBackHook.unlockCallback(bytes)\",\"MoneyBackRouter.buyWithEth(uint256,uint256)\",\"MoneyBackRouter.quoteBuy(uint256)\",\"MoneyBackRouter.quoteSell(uint256)\",\"MoneyBackRouter.receive()\",\"MoneyBackRouter.sellForEth(uint256,uint256,uint256)\",\"MoneyBackRouter.unlockCallback(bytes)\",\"MoneyBackToken.approve(address,uint256)\",\"MoneyBackToken.transfer(address,uint256)\",\"MoneyBackToken.transferFrom(address,address,uint256)\",\"RoundPayout.acceptOwnership()\",\"RoundPayout.fund(address,uint256)\",\"RoundPayout.pause()\",\"RoundPayout.payRound(uint256,address,address[],uint256[],bytes32,uint256,uint256)\",\"RoundPayout.renounceOwnership()\",\"RoundPayout.retryFailed(uint256,address[])\",\"RoundPayout.sweep(address,address,uint256)\",\"RoundPayout.transferOwnership(address)\",\"RoundPayout.unpause()\",\"RoundPayout.writeOffFailed(uint256,address)\"],\"files\":{\"README.md\":118,\"assets/moneyback.png\":39,\"assets/moneyback.svg\":8,\"docs/AUDIT_RESPONSES.md\":97,\"docs/network-observations.json\":36,\"docs/requester-share.json\":4,\"foundry.toml\":13,\"launch.json\":31,\"script/DeployAuxiliaries.s.sol\":64,\"src/MoneyBackHook.sol\":515,\"src/MoneyBackRouter.sol\":446,\"src/MoneyBackToken.sol\":153,\"src/RoundPayout.sol\":373,\"test/DeployAuxiliaries.t.sol\":73,\"test/MoneyBackHook.t.sol\":1319,\"test/MoneyBackRealV4.t.sol\":342,\"test/MoneyBackRouter.t.sol\":1840,\"test/MoneyBackToken.t.sol\":372,\"test/RoundPayout.t.sol\":1241,\"test/vendor/README.md\":8,\"test/vendor/solmate/LICENSE\":661,\"test/vendor/solmate/src/auth/Owned.sol\":44,\"test/vendor/v4-core/licenses/BUSL_LICENSE\":63,\"test/vendor/v4-core/licenses/MIT_LICENSE\":7,\"test/vendor/v4-core/src/ERC6909.sol\":90,\"test/vendor/v4-core/src/ERC6909Claims.sol\":23,\"test/vendor/v4-core/src/Extsload.sol\":64,\"test/vendor/v4-core/src/Exttload.sol\":40,\"test/vendor/v4-core/src/NoDelegateCall.sol\":33,\"test/vendor/v4-core/src/PoolManager.sol\":394,\"test/vendor/v4-core/src/ProtocolFees.sol\":71,\"test/vendor/v4-core/src/interfaces/IExtsload.sol\":21,\"test/vendor/v4-core/src/interfaces/IExttload.sol\":15,\"test/vendor/v4-core/src/interfaces/IHooks.sol\":152,\"test/vendor/v4-core/src/interfaces/IPoolManager.sol\":217,\"test/vendor/v4-core/src/interfaces/IProtocolFees.sol\":52,\"test/vendor/v4-core/src/interfaces/callback/IUnlockCallback.sol\":10,\"test/vendor/v4-core/src/interfaces/external/IERC20Minimal.sol\":48,\"test/vendor/v4-core/src/interfaces/external/IERC6909Claims.sol\":66,\"test/vendor/v4-core/src/libraries/BitMath.sol\":49,\"test/vendor/v4-core/src/libraries/CurrencyDelta.sol\":42,\"test/vendor/v4-core/src/libraries/CurrencyReserves.sol\":39,\"test/vendor/v4-core/src/libraries/CustomRevert.sol\":120,\"test/vendor/v4-core/src/libraries/FixedPoint128.sol\":8,\"test/vendor/v4-core/src/libraries/FixedPoint96.sol\":10,\"test/vendor/v4-core/src/libraries/FullMath.sol\":117,\"test/vendor/v4-core/src/libraries/Hooks.sol\":344,\"test/vendor/v4-core/src/libraries/LPFeeLibrary.sol\":79,\"test/vendor/v4-core/src/libraries/LiquidityMath.sol\":20,\"test/vendor/v4-core/src/libraries/Lock.sol\":28,\"test/vendor/v4-core/src/libraries/NonzeroDeltaCount.sol\":35,\"test/vendor/v4-core/src/libraries/ParseBytes.sol\":29,\"test/vendor/v4-core/src/libraries/Pool.sol\":620,\"test/vendor/v4-core/src/libraries/Position.sol\":103,\"test/vendor/v4-core/src/libraries/ProtocolFeeLibrary.sol\":47,\"test/vendor/v4-core/src/libraries/SafeCast.sol\":60,\"test/vendor/v4-core/src/libraries/SqrtPriceMath.sol\":292,\"test/vendor/v4-core/src/libraries/StateLibrary.sol\":345,\"test/vendor/v4-core/src/libraries/SwapMath.sol\":108,\"test/vendor/v4-core/src/libraries/TickBitmap.sol\":121,\"test/vendor/v4-core/src/libraries/TickMath.sol\":238,\"test/vendor/v4-core/src/libraries/UnsafeMath.sol\":29,\"test/vendor/v4-core/src/types/BalanceDelta.sol\":72,\"test/vendor/v4-core/src/types/BeforeSwapDelta.sol\":38,\"test/vendor/v4-core/src/types/Currency.sol\":118,\"test/vendor/v4-core/src/types/PoolId.sol\":17,\"test/vendor/v4-core/src/types/PoolKey.sol\":22,\"test/vendor/v4-core/src/types/PoolOperation.sol\":26,\"test/vendor/v4-core/src/types/Slot0.sol\":95},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1451,"exitCode":0,"name":"slither","output":"[high/medium] arbitrary-send-eth at src/MoneyBackRouter.sol:419: MoneyBackRouter._sendAllEth(address) (src/MoneyBackRouter.sol#419-424) sends eth to arbitrary user\n[high/medium] reentrancy-eth at src/MoneyBackRouter.sol:223: Reentrancy in MoneyBackRouter.sellForEth(uint256,uint256,uint256) (src/MoneyBackRouter.sol#223-239):\n[high/medium] reentrancy-eth at src/MoneyBackRouter.sol:202: Reentrancy in MoneyBackRouter.buyWithEth(uint256,uint256) (src/MoneyBackRouter.sol#202-214):\n[medium/high] incorrect-equality at src/MoneyBackHook.sol:465: MoneyBackHook._onlyBoundPool(PoolKey) (src/MoneyBackHook.sol#465-467) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/MoneyBackHook.sol:340: MoneyBackHook.currentSurchargeBps() (src/MoneyBackHook.sol#340-346) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/MoneyBackRouter.sol:419: MoneyBackRouter._sendAllEth(address) (src/MoneyBackRouter.sol#419-424) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/MoneyBackRouter.sol:259: Reentrancy in MoneyBackRouter.quoteSell(uint256) (src/MoneyBackRouter.sol#259-263):\n[medium/medium] reentrancy-no-eth at src/RoundPayout.sol:284: Reentrancy in RoundPayout.retryFailed(uint256,address[]) (src/RoundPayout.sol#284-321):\n[medium/medium] reentrancy-no-eth at src/RoundPayout.sol:215: Reentrancy in RoundPayout.payRound(uint256,address,address[],uint256[],bytes32,uint256,uint256) (src/RoundPayout.sol#215-245):\n[medium/medium] reentrancy-no-eth at src/MoneyBackHook.sol:436: Reentrancy in MoneyBackHook.sweep() (src/MoneyBackHook.sol#436-441):\n[medium/medium] reentrancy-no-eth at src/MoneyBackRouter.sol:250: Reentrancy in MoneyBackRouter.quoteBuy(uint256) (src/MoneyBackRouter.sol#250-254):\n[medium/medium] reentrancy-no-eth at src/RoundPayout.sol:248: Reentrancy in RoundPayout._payLegs(uint256,address,address[],uint256[]) (src/RoundPayout.sol#248-265):\n[medium/medium] uninitialized-local at src/RoundPayout.sol:298: RoundPayout.retryFailed(uint256,address[]).needed (src/RoundPayout.sol#298) is a local variable never initialized\n[medium/medium] uninitialized-local at src/MoneyBackHook.sol:363: MoneyBackHook.beforeInitialize(address,PoolKey,uint160).imdIsCurrency0 (src/MoneyBackHook.sol#363) is a local variable never initialized\n[medium/medium] unused-return at src/MoneyBackRouter.sol:332: MoneyBackRouter._sell(MoneyBackRouter.CallbackData) (src/MoneyBackRouter.sol#332-357) ignores return value by poolManager.settle() (src/MoneyBackRouter.sol#343)\n[medium/medium] unused-return at src/MoneyBackRouter.sol:396: MoneyBackRouter._quote(MoneyBackRouter.CallbackData) (src/MoneyBackRouter.sol#396-415) ignores return value by poolManager.unlock(data) (src/MoneyBackRouter.sol#400-414)\n[medium/medium] unused-return at src/MoneyBackRouter.sol:301: MoneyBackRouter._buy(MoneyBackRouter.CallbackData) (src/MoneyBackRouter.sol#301-327) ignores return value by poolManager.settle{value: r.amountIn}() (src/MoneyBackRouter.sol#312)\n[medium/medium] unused-return at src/MoneyBackHook.sol:436: MoneyBackHook.sweep() (src/MoneyBackHook.sol#436-441) ignores return value by poolManager.unlock() (src/MoneyBackHook.sol#439)\n[low/medium] missing-zero-check at src/RoundPayout.sol:61: RoundPayout.transferOwnership(address).newOwner (src/RoundPayout.sol#61) lacks a zero-check on :\n[low/medium] calls-loop at src/RoundPayout.sol:347: RoundPayout._tryTransfer(address,address,uint256) (src/RoundPayout.sol#347-350) has external calls inside a loop: (ok,data) = token.call(abi.encodeCall(IERC20.transfer,(to,amount))) (src/RoundPayout.sol#348)\n[low/medium] calls-loop at src/RoundPayout.sol:347: RoundPayout._tryTransfer(address,address,uint256) (src/RoundPayout.sol#347-350) has external calls inside a loop: (ok,data) = token.call(abi.encodeCall(IERC20.transfer,(to,amount))) (src/RoundPayout.sol#348)\n[low/medium] reentrancy-benign at src/MoneyBackRouter.sol:396: Reentrancy in MoneyBackRouter._quote(MoneyBackRouter.CallbackData) (src/MoneyBackRouter.sol#396-415):\n[low/medium] reentrancy-events at src/MoneyBackRouter.sol:223: Reentrancy in MoneyBackRouter.sellForEth(uint256,uint256,uint256) (src/MoneyBackRouter.sol#223-239):\n[low/medium] reentrancy-events at src/MoneyBackHook.sol:477: Reentrancy in MoneyBackHook._accrue(bool,uint256) (src/MoneyBackHook.sol#477-482):\n[low/medium] reentrancy-events at src/MoneyBackHook.sol:444: Reentrancy in MoneyBackHook.unlockCallback(bytes) (src/MoneyBackHook.sol#444-455):\n[low/medium] reentrancy-events at src/MoneyBackRouter.sol:202: Reentrancy in MoneyBackRouter.buyWithEth(uint256,uint256) (src/MoneyBackRouter.sol#202-214):\n[low/medium] timestamp at src/RoundPayout.sol:337: RoundPayout.isPaid(uint256) (src/RoundPayout.sol#337-339) uses timestamp for comparisons\n[low/medium] timestamp at src/MoneyBackHook.sol:477: MoneyBackHook._accrue(bool,uint256) (src/MoneyBackHook.sol#477-482) uses timestamp for comparisons\n[low/medium] timestamp at src/MoneyBackHook.sol:489: MoneyBackHook._toInt128(uint256) (src/MoneyBackHook.sol#489-492) uses timestamp for comparisons\n[low/medium] timestamp at src/MoneyBackRouter.sol:202: MoneyBackRouter.buyWithEth(uint256,uint256) (src/MoneyBackRouter.sol#202-214) uses timestamp for comparisons\n[low/medium] timestamp at src/MoneyBackHook.sol:340: MoneyBackHook.currentSurchargeBps() (src/MoneyBackHook.sol#340-346) uses timestamp for comparisons\n[low/medium] timestamp at src/MoneyBackRouter.sol:223: MoneyBackRouter.sellForEth(uint256,uint256,uint256) (src/MoneyBackRouter.sol#223-239) uses timestamp for comparisons\n[low/medium] timestamp at src/MoneyBackHook.sol:405: MoneyBackHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/MoneyBackHook.sol#405-428) uses timestamp for comparisons\n[low/medium] timestamp at src/MoneyBackHook.sol:465: MoneyBackHook._onlyBoundPool(PoolKey) (src/MoneyBackHook.sol#465-467) uses timestamp for comparisons\n[low/medium] timestamp at src/MoneyBackHook.sol:354: MoneyBackHook.beforeInitialize(address,PoolKey,uint160) (src/MoneyBackHook.sol#354-380) uses timestamp for comparisons","passed":true},{"durationMs":312,"exitCode":0,"name":"aderyn","output":"[high] eth-send-unchecked-address at src/MoneyBackRouter.sol:202: ETH transferred without address checks (2 places)\n[high] incorrect-shift-order at src/MoneyBackHook.sol:500: Incorrect Assembly Shift Parameter Order\n[high] reentrancy-state-change at src/MoneyBackHook.sol:439: Reentrancy: State change after external call (2 places)\n[high] reused-contract-name at src/MoneyBackRouter.sol:16: Contract Name Reused in Different Files (2 places)\n[low] centralization-risk at src/RoundPayout.sol:61: Centralization Risk (8 places)\n[low] costly-loop at src/RoundPayout.sol:252: Costly operations inside loop (2 places)\n[low] internal-function-used-once at src/MoneyBackHook.sol:107: Internal Function Used Only Once\n[low] large-numeric-literal at src/MoneyBackHook.sol:201: Large Numeric Literal (2 places)\n[low] missing-inheritance at src/MoneyBackToken.sol:14: Missing Inheritance\n[low] non-reentrant-not-first at src/RoundPayout.sol:197: `nonReentrant` is Not the First Modifier (4 places)\n[low] push-zero-opcode at src/MoneyBackHook.sol:2: PUSH0 Opcode (4 places)\n[low] require-revert-in-loop at src/RoundPayout.sol:299: Loop Contains `require`/`revert` (2 places)\n[low] state-change-without-event at src/MoneyBackHook.sol:436: State Change Without Event (4 places)\n[low] state-no-address-check at src/RoundPayout.sol:62: Address State Variable Set Without Checks (2 places)\n[low] unchecked-return at src/MoneyBackHook.sol:439: Unchecked Return (2 places)\n[low] unsafe-erc20-operation at src/MoneyBackRouter.sol:229: Unsafe ERC20 Operation (6 places)\n[low] unspecific-solidity-pragma at src/MoneyBackRouter.sol:2: Unspecific Solidity Pragma (3 places)\n[low] unused-public-function at src/MoneyBackHook.sol:328: Public Function Not Used Internally (13 places)","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"83be859e2d338dd3da45ff5fa2ba0ea3ac6aa087612c0325d1d2736fa8203c03","verifiedTreeHash":"51010581e9277602ff5bf0b547466d1d6d150f6f","verifierVersion":"0.1.0+0e08a464"},{"checks":[{"durationMs":4268,"exitCode":0,"name":"build","output":"Compiling 57 files with Solc 0.8.26\nSolc 0.8.26 finished in 4.14s\nCompiler run successful!\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/MoneyBackHook.sol:350:13\n    │\n350 │         if (elapsed >= SURCHARGE_DURATION) return 0;\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/MoneyBackHook.sol:382:27\n    │\n382 │         _imdIsCurrency0 = imdIsCurrency0;\n    │                           ━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[missing-zero-check]: address parameter is used in a state write or value transfer without a zero-address check\n   ╭▸ src/RoundPayout.sol:62:32\n   │\n62 │     function transferOwnership(address newOwner) external onlyOwner {\n   │                                ━━━━━━━━━━━━━━━━\n   │\n   ╰ help: https://getfoundry.sh/forge/linting/missing-zero-check\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:403:42\n    │\n403 │         uint256 specified = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n    │                                          ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:403:77\n    │\n403 │         uint256 specified = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n    │                                                                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MoneyBackHook.sol:490:9\n    │\n490 │         emit FeeAccrued(isSell, baseFee, surcharge, imdLeg);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:423:46\n    │\n423 │             uint256 specified = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n    │                                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:423:81\n    │\n423 │             uint256 specified = exactInput ? uint256(-params.amountSpecified) : uint256(params.amountSpecified);\n    │                                                                                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:428:39\n    │\n428 │             if (imdDelta != (isSell ? int256(expectedLeg) : -int256(expectedLeg))) revert PartialFill();\n    │                                       ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:428:62\n    │\n428 │             if (imdDelta != (isSell ? int256(expectedLeg) : -int256(expectedLeg))) revert PartialFill();\n    │                                                              ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:432:41\n    │\n432 │         uint256 imdLeg = imdDelta < 0 ? uint256(-imdDelta) : uint256(imdDelta);\n    │                                         ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:432:62\n    │\n432 │         uint256 imdLeg = imdDelta < 0 ? uint256(-imdDelta) : uint256(imdDelta);\n    │                                                              ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackHook.sol:447:9\n    │\n447 │         poolManager.unlock(\"\");\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/MoneyBackHook.sol:447:9\n    │\n447 │         poolManager.unlock(\"\");\n    │         ━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MoneyBackHook.sol:461:9\n    │\n461 │         emit Swept(amount, payout);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:509:25\n    │\n509 │         if (specified > uint256(uint128(type(int128).max))) revert AmountOverflow();\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/MoneyBackHook.sol:509:33\n    │\n509 │         if (specified > uint256(uint128(type(int128).max))) revert AmountOverflow();\n    │                                 ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:516:22\n    │\n516 │         if (imdLeg > uint256(uint128(type(int128).max))) revert AmountOverflow();\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/MoneyBackHook.sol:516:30\n    │\n516 │         if (imdLeg > uint256(uint128(type(int128).max))) revert AmountOverflow();\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:522:17\n    │\n522 │         if (x > uint256(uint128(type(int128).max))) revert AmountOverflow();\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/MoneyBackHook.sol:522:25\n    │\n522 │         if (x > uint256(uint128(type(int128).max))) revert AmountOverflow();\n    │                         ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:523:16\n    │\n523 │         return int128(uint128(x));\n    │                ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackHook.sol:523:23\n    │\n523 │         return int128(uint128(x));\n    │                       ━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/RoundPayout.sol:370:40\n    │\n370 │         (bool ok, bytes memory data) = token.call(abi.encodeCall(IERC20.transferFrom, (from, to, amount)));\n    │                                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RoundPayout.sol:194:9\n    │\n194 │         emit Funded(token, msg.sender, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/RoundPayout.sol:364:40\n    │\n364 │         (bool ok, bytes memory data) = token.call(abi.encodeCall(IERC20.transfer, (to, amount)));\n    │                                        ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/RoundPayout.sol:198:82\n    │\n198 │     function sweep(address token, address to, uint256 amount) external onlyOwner nonReentrant {\n    │                                                                                  ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RoundPayout.sol:201:9\n    │\n201 │         emit Swept(token, to, amount);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackRouter.sol:388:24\n    │\n388 │         r = abi.decode(poolManager.unlock(data), (Result));\n    │                        ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-eth]: uncapped ETH transfer can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackRouter.sol:421:22\n    │\n421 │         (bool ok,) = to.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/MoneyBackRouter.sol:204:13\n    │\n204 │         if (block.timestamp > deadline) revert Expired();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MoneyBackRouter.sol:212:9\n    │\n212 │         emit BoughtWithEth(msg.sender, r.amountIn, r.imdMid, tokensOut);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackRouter.sol:229:14\n    │\n229 │         if (!IERC20(token).transferFrom(msg.sender, address(this), tokens)) revert TransferFromFailed();\n    │              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackRouter.sol:235:29\n    │\n235 │         if (unused != 0 && !IERC20(token).transfer(msg.sender, unused)) revert TransferFailed();\n    │                             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[block-timestamp]: usage of `block.timestamp` in a comparison may be manipulated by validators\n    ╭▸ src/MoneyBackRouter.sol:225:13\n    │\n225 │         if (block.timestamp > deadline) revert Expired();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/block-timestamp\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/MoneyBackRouter.sol:237:9\n    │\n237 │         emit SoldForEth(msg.sender, r.amountIn, r.imdMid, ethOut);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:227:22\n    │\n227 │         if (tokens > uint256(uint128(type(int128).max))) revert AmountOverflow();\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/MoneyBackRouter.sol:227:30\n    │\n227 │         if (tokens > uint256(uint128(type(int128).max))) revert AmountOverflow();\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/MoneyBackRouter.sol:399:13\n    │\n399 │         try poolManager.unlock(data) {\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[reentrancy-no-eth]: external call can be reentered before `_status` is updated\n    ╭▸ src/RoundPayout.sol:358:22\n    │\n358 │         (ok, data) = token.call(abi.encodeCall(IERC20.transfer, (to, amount)));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/RoundPayout.sol:224:40\n    │\n224 │     ) external onlyOwner whenNotPaused nonReentrant returns (uint256 totalPaid) {\n    │                                        ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RoundPayout.sol:244:9\n    │\n244 │         emit RoundPaid(roundId, token, ledgerHash, twapCloseX96, totalEligibleLoss, totalPaid, n);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/RoundPayout.sol:265:14\n    │\n265 │         if (!anySucceeded) revert AllTransfersFailed(roundId);\n    │              ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[calls-loop]: external call inside a loop\n    ╭▸ src/RoundPayout.sol:358:22\n    │\n358 │         (ok, data) = token.call(abi.encodeCall(IERC20.transfer, (to, amount)));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/calls-loop\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RoundPayout.sol:260:17\n    │\n260 │                 emit Paid(roundId, recipient, 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/RoundPayout.sol:274:9\n    │\n274 │         emit PayFailed(roundId, recipient, amount, reason);\n    │         ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[reentrancy-no-eth]: external call can be reentered before `failed` is updated\n    ╭▸ src/RoundPayout.sol:358:22\n    │\n358 │         (ok, data) = token.call(abi.encodeCall(IERC20.transfer, (to, amount)));\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-no-eth\n\nwarning[uninitialized-local]: local variable is read before being initialized\n    ╭▸ src/RoundPayout.sol:312:13\n    │\n312 │             needed += owed;\n    │             ━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/uninitialized-local\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/RoundPayout.sol:298:9\n    │\n298 │         nonReentrant\n    │         ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[reentrancy-events]: event emitted after an external call; reentrancy can reorder or fabricate logs that off-chain consumers rely on\n    ╭▸ src/RoundPayout.sol:326:17\n    │\n326 │                 emit Paid(roundId, recipient, 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/RoundPayout.sol:328:17\n    │\n328 │                 emit PayFailed(roundId, recipient, amount, reason);\n    │                 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/reentrancy-events\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/RoundPayout.sol:311:28\n    │\n311 │             if (owed == 0) revert NothingFailed(roundId, to[i]);\n    │                            ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[require-revert-in-loop]: `require` or `revert` inside a loop\n    ╭▸ src/RoundPayout.sol:319:30\n    │\n319 │             if (amount == 0) revert NothingFailed(roundId, recipient);\n    │                              ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/require-revert-in-loop\n\nwarning[non-reentrant-not-first]: `nonReentrant` is not the first modifier\n    ╭▸ src/RoundPayout.sol:334:77\n    │\n334 │     function writeOffFailed(uint256 roundId, address to) external onlyOwner nonReentrant {\n    │                                                                             ━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/non-reentrant-not-first\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:307:22\n    │\n307 │         r.amountIn = uint256(-a0);\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/MoneyBackRouter.sol:308:26\n    │\n308 │         uint256 imdOut = uint256(a1);\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[unused-return]: return value of an external call is not used\n    ╭▸ src/MoneyBackRouter.sol:313:13\n    │\n313 │             poolManager.settle{value: r.amountIn}();\n    │             ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:321:20\n    │\n321 │         r.imdMid = uint256(-imdDelta);\n    │                    ━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:322:23\n    │\n322 │         r.amountOut = uint256(tokenDelta);\n    │                       ━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:338:22\n    │\n338 │         r.amountIn = uint256(-tokenDelta);\n    │                      ━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unused-return]: return value of an external call is not used\n    ╭▸ src/MoneyBackRouter.sol:342:13\n    │\n342 │             poolManager.settle();\n    │             ━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/unused-return\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:344:26\n    │\n344 │         uint256 imdOut = uint256(imdDelta);\n    │                          ━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:349:20\n    │\n349 │         r.imdMid = uint256(-a1);\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/MoneyBackRouter.sol:350:23\n    │\n350 │         r.amountOut = uint256(a0);\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/MoneyBackRouter.sol:361:24\n    │\n361 │         if (amountIn > uint256(uint128(type(int128).max))) revert AmountOverflow();\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/MoneyBackRouter.sol:361:32\n    │\n361 │         if (amountIn > uint256(uint128(type(int128).max))) revert AmountOverflow();\n    │                                ━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'uint128' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:366:35\n    │\n366 │                 amountSpecified: -int256(amountIn),\n    │                                   ━━━━━━━━━━━━━━━━\n    │\n    ├ note: consider disabling this lint if you're certain the cast is safe\n    │       \n    │       // casting to 'int256' is safe because [explain why]\n    │       // forge-lint: disable-next-line(unsafe-typecast)\n    │       \n    │       \n    ╰ help: https://getfoundry.sh/forge/linting/unsafe-typecast\n\nwarning[unsafe-typecast]: typecast can truncate values\n    ╭▸ src/MoneyBackRouter.sol:404:40\n    │\n404 │             if (reason.length == 36 && bytes4(reason) == QuoteResult.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[arbitrary-send-eth]: ETH is sent to a user-controlled destination; restrict the destination or the caller\n    ╭▸ src/MoneyBackRouter.sol:421:22\n    │\n421 │         (bool ok,) = to.call{value: amount}(\"\");\n    │                      ━━━━━━━━━━━━━━━━━━━━━━━━━━\n    │\n    ╰ help: https://getfoundry.sh/forge/linting/arbitrary-send-eth\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n     ╭▸ test/MoneyBackRouter.t.sol:1132:24\n     │\n1132 │         uint256 now_ = block.timestamp;\n     │                        ━━━━━━━━━━━━━━━\n1133 │         vm.warp(ts);\n     │         ─────────── `vm.warp` changes this environment here\n     │\n     ├ help: capture it with `vm.getBlockTimestamp()` instead\n     ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n     ╭▸ test/MoneyBackRouter.t.sol:1179:53\n     │\n1179 │         uint256 bought = _buyAs(ALICE, 10 ether, 0, block.timestamp);\n     │                                                     ━━━━━━━━━━━━━━━\n1180 │         vm.warp(T0 + 3600);\n     │         ────────────────── `vm.warp` changes this environment here\n     │\n     ├ help: capture it with `vm.getBlockTimestamp()` instead\n     ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\nwarning[environment-read-across-mutation]: `block.timestamp` may be reused across `vm.warp`\n     ╭▸ test/MoneyBackRouter.t.sol:1538:49\n     │\n1538 │         assertEq(_sellAs(ALICE, tokens, quoted, block.timestamp), quoted, \"sell delivers exactly the quote\");\n     │                                                 ━━━━━━━━━━━━━━━\n1539 │         vm.warp(T0 + 1800);\n     │         ────────────────── `vm.warp` changes this environment here\n     │\n     ├ help: capture it with `vm.getBlockTimestamp()` instead\n     ╰ help: https://getfoundry.sh/forge/linting/environment-read-across-mutation\n\n","passed":true},{"durationMs":860,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 6 tests for test/DeployAuxiliaries.t.sol:DeployAuxiliariesTest\n[PASS] test_deploysBothAuxiliariesWithPayingWalletOwner() (gas: 2849445)\n[PASS] test_uninitializedHookRefused() (gas: 71375001)\n[PASS] test_unresolvedAddressesRefused() (gas: 61999)\n[PASS] test_wrongChainRefused() (gas: 35325)\n[PASS] test_wrongManagerRefused() (gas: 41055)\n[PASS] test_wrongWalletRefused() (gas: 44951506)\nSuite result: ok. 6 passed; 0 failed; 0 skipped; finished in 129.91ms (66.31ms CPU time)\n\nRan 15 tests for test/MoneyBackRealV4.t.sol:MoneyBackRealV4Token1Test\n[PASS] testFuzz_equivalentSwapForms(uint96,uint32,bool) (runs: 256, μ: 422175, ~: 427222)\n[PASS] testFuzz_fullFillSpecifiedFee(uint96,uint32,bool) (runs: 256, μ: 206819, ~: 210969)\n[PASS] test_equivalentBuysAndSellsAtDecayBoundaries() (gas: 3631686)\n[PASS] test_exactInputBuyExhaustedLiquidityReverts() (gas: 602468)\n[PASS] test_exactInputBuyTightLimitRevertsWithoutOvercharge() (gas: 223289)\n[PASS] test_exactOutputSellExhaustedLiquidityReverts() (gas: 586906)\n[PASS] test_exactOutputSellTightLimitRevertsWithoutTakingIMD() (gas: 218015)\n[PASS] test_freshSingleSidedPoolCanBuyAndSweepWithNoIMDReserve() (gas: 30394489)\n[PASS] test_fullFillAllFourCasesAndSweep() (gas: 2289699)\n[PASS] test_liquidityConversionIsOutsideSwapSurcharge() (gas: 580745)\n[PASS] test_partialExactOutputBuyChargesActualIMD() (gas: 572068)\n[PASS] test_partialUnspecifiedIMDStillChargesActualFill() (gas: 584145)\n[PASS] test_routerTradesAgainstRealManagerAndRefundsPartialSell() (gas: 2049000)\n[PASS] test_routerUnfillableBuyAndQuoteRevertAtomically() (gas: 2016450)\n[PASS] test_singleSidedPoolRejectsUnfillableExactOutputSell() (gas: 30677990)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 129.91ms (198.09ms CPU time)\n\nRan 15 tests for test/MoneyBackRealV4.t.sol:MoneyBackRealV4Token0Test\n[PASS] testFuzz_equivalentSwapForms(uint96,uint32,bool) (runs: 256, μ: 423005, ~: 438805)\n[PASS] testFuzz_fullFillSpecifiedFee(uint96,uint32,bool) (runs: 256, μ: 206372, ~: 215838)\n[PASS] test_equivalentBuysAndSellsAtDecayBoundaries() (gas: 3631633)\n[PASS] test_exactInputBuyExhaustedLiquidityReverts() (gas: 595743)\n[PASS] test_exactInputBuyTightLimitRevertsWithoutOvercharge() (gas: 217126)\n[PASS] test_exactOutputSellExhaustedLiquidityReverts() (gas: 593511)\n[PASS] test_exactOutputSellTightLimitRevertsWithoutTakingIMD() (gas: 224058)\n[PASS] test_freshSingleSidedPoolCanBuyAndSweepWithNoIMDReserve() (gas: 92915702)\n[PASS] test_fullFillAllFourCasesAndSweep() (gas: 2288731)\n[PASS] test_liquidityConversionIsOutsideSwapSurcharge() (gas: 567308)\n[PASS] test_partialExactOutputBuyChargesActualIMD() (gas: 565444)\n[PASS] test_partialUnspecifiedIMDStillChargesActualFill() (gas: 590855)\n[PASS] test_routerTradesAgainstRealManagerAndRefundsPartialSell() (gas: 2050332)\n[PASS] test_routerUnfillableBuyAndQuoteRevertAtomically() (gas: 1988854)\n[PASS] test_singleSidedPoolRejectsUnfillableExactOutputSell() (gas: 93358832)\nSuite result: ok. 15 passed; 0 failed; 0 skipped; finished in 130.01ms (354.49ms CPU time)\n\nRan 27 tests for test/MoneyBackToken.t.sol:MoneyBackTokenTest\n[PASS]\nMoneyBackTokenTest invariants:\n[PASS] invariant_balancesSumToSupply\n[PASS] invariant_totalSupplyNeverChanges\n[PASS] invariant_zeroAddressHoldsNothing\n MoneyBackTokenTest invariants (runs: 128, calls: 4096, reverts: 0)\n\n╭--------------+--------------+-------+---------+----------╮\n| Contract     | Selector     | Calls | Reverts | Discards |\n+==========================================================+\n| TokenHandler | approve      | 1352  | 0       | 0        |\n|--------------+--------------+-------+---------+----------|\n| TokenHandler | transfer     | 1357  | 0       | 0        |\n|--------------+--------------+-------+---------+----------|\n| TokenHandler | transferFrom | 1387  | 0       | 0        |\n╰--------------+--------------+-------+---------+----------╯\n\n[PASS] testFuzz_allowanceAccounting(uint256,uint256) (runs: 512, μ: 117532, ~: 134096)\n[PASS] testFuzz_transferAboveBalanceAlwaysReverts(uint256) (runs: 512, μ: 40841, ~: 40840)\n[PASS] testFuzz_transferConservesSupplyAndBalances(address,uint256) (runs: 512, μ: 92936, ~: 93547)\n[PASS] test_approveSetsAllowanceAndEmits() (gas: 97003)\n[PASS] test_approveZeroSpenderReverts() (gas: 30352)\n[PASS] test_constructorMintsWholeSupplyToDeployer() (gas: 17042)\n[PASS] test_deployerIsMsgSenderNotTxOrigin() (gas: 188189)\n[PASS] test_metadata() (gas: 17630)\n[PASS] test_noPrivilegedSurface() (gas: 165545)\n[PASS] test_rejectsEth() (gas: 32817)\n[PASS] test_supplyIsExactlyOneBillionWithEighteenDecimals() (gas: 13317)\n[PASS] test_transferFromDoesNotEmitApprovalOnSpend() (gas: 115689)\n[PASS] test_transferFromEmptyAccountRevertsEvenForOneWei() (gas: 33309)\n[PASS] test_transferFromExactAllowanceLeavesZero() (gas: 144349)\n[PASS] test_transferFromInfiniteAllowanceIsNotDecremented() (gas: 112829)\n[PASS] test_transferFromRevertsWhenAllowanceBelowAmount() (gas: 80374)\n[PASS] test_transferFromRevertsWhenBalanceBelowAmountEvenWithAllowance() (gas: 86141)\n[PASS] test_transferFromRevertsWithoutAllowance() (gas: 33778)\n[PASS] test_transferFromSpendsAllowance() (gas: 125597)\n[PASS] test_transferFromToZeroAddressReverts() (gas: 83503)\n[PASS] test_transferMovesBalanceAndEmits() (gas: 87232)\n[PASS] test_transferRevertsOnInsufficientBalance() (gas: 40643)\n[PASS] test_transferToSelfKeepsBalance() (gas: 42924)\n[PASS] test_transferToZeroAddressReverts() (gas: 30391)\n[PASS] test_transferWholeBalanceLeavesZero() (gas: 70165)\n[PASS] test_transferZeroAmountSucceeds() (gas: 37089)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 273.86ms (471.70ms CPU time)\n\nRan 60 tests for test/MoneyBackHook.t.sol:MoneyBackHookTest\n[PASS]\nMoneyBackHookTest invariants:\n[PASS] invariant_hookNeverHoldsMoneybackClaims\n[PASS] invariant_lpFeeNeverOverriddenAndManagerLocked\n[PASS] invariant_pendingEqualsAccruedMinusSwept\n[PASS] invariant_sweepPaysOnlyPayout\n MoneyBackHookTest invariants (runs: 64, calls: 2560, reverts: 0)\n\n╭-------------+----------+-------+---------+----------╮\n| Contract    | Selector | Calls | Reverts | Discards |\n+=====================================================+\n| HookHandler | swap     | 825   | 0       | 0        |\n|-------------+----------+-------+---------+----------|\n| HookHandler | sweep    | 867   | 0       | 0        |\n|-------------+----------+-------+---------+----------|\n| HookHandler | warp     | 868   | 0       | 0        |\n╰-------------+----------+-------+---------+----------╯\n\n[PASS] testFuzz_feeMathsAllCases(uint8,uint256,uint32) (runs: 1000, μ: 173000, ~: 175196)\n[PASS] testFuzz_sequenceAccumulatesPending(uint256,uint256,uint256,uint32) (runs: 256, μ: 327466, ~: 330090)\n[PASS] testFuzz_specifiedOneWeiUnderfillAlwaysRefused(uint96,uint32,bool) (runs: 256, μ: 65797, ~: 65734)\n[PASS] testFuzz_surchargeFormula(uint32) (runs: 1000, μ: 18992, ~: 18699)\n[PASS] testFuzz_surchargeIsMonotoneNonIncreasing(uint32,uint32) (runs: 512, μ: 21741, ~: 21807)\n[PASS] testFuzz_tinyAmountsRespectRoundingBounds(uint8,uint256,uint32) (runs: 512, μ: 125737, ~: 116104)\n[PASS] test_acceptanceSellCostAtT0AndAfterDecay() (gas: 309530)\n[PASS] test_afterSwapWithZeroPoolDeltaAccruesNothing() (gas: 64200)\n[PASS] test_allCasesWithImdAsCurrency1() (gas: 36104180)\n[PASS] test_buysNeverSurchargedAtAnyTime() (gas: 504482)\n[PASS] test_callbacksRejectNonPoolManager() (gas: 159864)\n[PASS] test_case1_exactInputBuy_t0() (gas: 175005)\n[PASS] test_case1_exactInputBuy_t1800() (gas: 174616)\n[PASS] test_case1_exactInputBuy_t1day() (gas: 174635)\n[PASS] test_case1_exactInputBuy_t900() (gas: 175037)\n[PASS] test_case2_exactOutputBuy_t0() (gas: 172835)\n[PASS] test_case2_exactOutputBuy_t1800() (gas: 172381)\n[PASS] test_case2_exactOutputBuy_t1day() (gas: 172312)\n[PASS] test_case2_exactOutputBuy_t900() (gas: 172778)\n[PASS] test_case3_exactInputSell_t0() (gas: 173663)\n[PASS] test_case3_exactInputSell_t1800() (gas: 172962)\n[PASS] test_case3_exactInputSell_t1day() (gas: 173003)\n[PASS] test_case3_exactInputSell_t900() (gas: 173682)\n[PASS] test_case4_exactOutputSell_t0() (gas: 175682)\n[PASS] test_case4_exactOutputSell_t1800() (gas: 174600)\n[PASS] test_case4_exactOutputSell_t1day() (gas: 174575)\n[PASS] test_case4_exactOutputSell_t900() (gas: 175715)\n[PASS] test_constructorRejectsImdAsLaunchToken() (gas: 63997)\n[PASS] test_constructorRejectsWrongFlagBits() (gas: 115010453)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 182731)\n[PASS] test_constructorStoresImmutablesAndConstants() (gas: 58919)\n[PASS] test_deploymentAddressFlagBitsMatchPermissions() (gas: 18488)\n[PASS] test_emptySweepSucceedsAndEmitsZero() (gas: 75461)\n[PASS] test_exactOutputGrossLegOverflowRevertsBeforeMint() (gas: 107257)\n[PASS] test_feeAccruedEventsPerCase() (gas: 410040)\n[PASS] test_fixedCallbacksOnlyChargeTheirOwnCases() (gas: 165507)\n[PASS] test_hookNeverMintsMoneybackClaimsEvenIfGiftedImdClaims() (gas: 295089)\n[PASS] test_hookRejectsEthAndHasNoAdminSurface() (gas: 169036)\n[PASS] test_hugeSpecifiedAmountRevertsAmountOverflow() (gas: 98731)\n[PASS] test_initializeAcceptsEitherCurrencyOrder() (gas: 46362180)\n[PASS] test_initializeBindsPoolAndRecordsTimestamp() (gas: 58346)\n[PASS] test_initializeEmitsPoolBound() (gas: 46367154)\n[PASS] test_initializeRejectsSecondPool() (gas: 74581)\n[PASS] test_initializeRejectsWrongKeys() (gas: 47724457)\n[PASS] test_specifiedFeesAtInt128Boundary() (gas: 208889)\n[PASS] test_specifiedZeroFeeUnderfillRefused() (gas: 53628)\n[PASS] test_surchargeAt1799IsStillChargedOnSells() (gas: 136266)\n[PASS] test_surchargeAt1800IsZeroOnSells() (gas: 135401)\n[PASS] test_surchargeBoundaries() (gas: 83038)\n[PASS] test_swapBeforeInitializationReverts() (gas: 46393621)\n[PASS] test_swapRejectsWrongPoolKey() (gas: 260875)\n[PASS] test_sweepAccumulatesAcrossRounds() (gas: 454150)\n[PASS] test_sweepCannotBeNestedInsideAnotherUnlock() (gas: 774909)\n[PASS] test_sweepReentrancyFromPayoutTransferIsRejected() (gas: 760265)\n[PASS] test_sweepRevertsCleanlyIfTakeFails() (gas: 453897)\n[PASS] test_sweepTakesAllImdClaimsToPayout() (gas: 452883)\n[PASS] test_unlockCallbackOutsideSweepIsRejected() (gas: 167674)\n[PASS] test_viewsBeforeInitialization() (gas: 46220834)\n[PASS] test_zeroAmountSwapAccruesNothing() (gas: 205472)\nSuite result: ok. 60 passed; 0 failed; 0 skipped; finished in 387.63ms (913.85ms CPU time)\n\nRan 56 tests for test/RoundPayout.t.sol:RoundPayoutTest\n[PASS]\nRoundPayoutTest invariants:\n[PASS] invariant_balanceEqualsFundedMinusPaidMinusSwept\n[PASS] invariant_ownerNeverChangesWithoutHandoff\n[PASS] invariant_roundRecordsMatchLedger\n[PASS] invariant_sumPaidNeverExceedsFunded\n RoundPayoutTest invariants (runs: 96, calls: 3840, reverts: 21)\n\n╭---------------+---------------+-------+---------+----------╮\n| Contract      | Selector      | Calls | Reverts | Discards |\n+============================================================+\n| PayoutHandler | fund          | 385   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | pay           | 431   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | replay        | 420   | 21      | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | retry         | 461   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | setBlocked    | 423   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | strangerCalls | 425   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | sweep         | 433   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | togglePause   | 429   | 0       | 0        |\n|---------------+---------------+-------+---------+----------|\n| PayoutHandler | writeOff      | 433   | 0       | 0        |\n╰---------------+---------------+-------+---------+----------╯\n\n[PASS] testFuzz_batchSizeBoundary(uint16) (runs: 256, μ: 2127348, ~: 1438969)\n[PASS] testFuzz_payRoundAccounting(uint256,uint256,uint256,uint256,uint8) (runs: 512, μ: 313742, ~: 231425)\n[PASS] test_allDuplicateFailuresStillRevert() (gas: 226537)\n[PASS] test_allTransfersFailedReverts() (gas: 349732)\n[PASS] test_constructorRejectsZeroOwner() (gas: 4100)\n[PASS] test_constructorSetsOwner() (gas: 22667)\n[PASS] test_duplicateFailedRecipientAccumulates() (gas: 387635)\n[PASS] test_duplicateFailedRecipientRetryClearsWholeDebtAndCount() (gas: 683078)\n[PASS] test_duplicateFailedRecipientWriteOffClearsWholeDebtAndCount() (gas: 500642)\n[PASS] test_fundAcceptsNoReturnToken() (gas: 476949)\n[PASS] test_fundPullsTokensFromAnyone() (gas: 174918)\n[PASS] test_fundRejectsEoaToken() (gas: 38646)\n[PASS] test_fundRejectsFalseReturningToken() (gas: 196919)\n[PASS] test_fundWithoutApprovalReverts() (gas: 96318)\n[PASS] test_fundZeroAmountSucceeds() (gas: 47919)\n[PASS] test_ownable2StepHandoff() (gas: 285110)\n[PASS] test_partialFailureIsRecordedAndBatchContinues() (gas: 430837)\n[PASS] test_partialFailureSilentRevertAndFalseReturn() (gas: 454037)\n[PASS] test_pauseTwiceAndUnpauseWhenRunningRevert() (gas: 108689)\n[PASS] test_payRoundBatchTooLarge() (gas: 283154)\n[PASS] test_payRoundBlockedWhilePaused() (gas: 388431)\n[PASS] test_payRoundEmptyBatch() (gas: 50414)\n[PASS] test_payRoundExactBalanceSucceeds() (gas: 329414)\n[PASS] test_payRoundIdempotent() (gas: 668322)\n[PASS] test_payRoundInsufficientBalanceUpFront() (gas: 127472)\n[PASS] test_payRoundIsTokenAgnosticPerRound() (gas: 821078)\n[PASS] test_payRoundLengthMismatch() (gas: 47260)\n[PASS] test_payRoundMaxBatchOf500() (gas: 17388182)\n[PASS] test_payRoundPaysEveryLegAndRecords() (gas: 404660)\n[PASS] test_payRoundRoundIdZeroIsOrdinary() (gas: 287441)\n[PASS] test_payRoundSumOverflowReverts() (gas: 48383)\n[PASS] test_payRoundWithEoaTokenReverts() (gas: 47866)\n[PASS] test_payRoundWithNoReturnToken() (gas: 539387)\n[PASS] test_payRoundZeroAmountLegIsPaid() (gas: 162387)\n[PASS] test_payRoundZeroToken() (gas: 44994)\n[PASS] test_privilegedFunctionsRejectNonOwner() (gas: 165748)\n[PASS] test_reentrantTokenCannotReenterFundOrSweepDuringPayRound() (gas: 840049)\n[PASS] test_reentrantTokenCannotReenterPayRound() (gas: 699804)\n[PASS] test_renounceOwnershipLocksEverything() (gas: 254602)\n[PASS] test_retryFailedInsufficientBalanceAfterSweep() (gas: 525680)\n[PASS] test_retryFailedPaysAndClears() (gas: 574104)\n[PASS] test_retryFailedRevertsForUnpaidRound() (gas: 43015)\n[PASS] test_retryFailedShapeChecks() (gas: 806977)\n[PASS] test_retryFailedStillFailingKeepsRecord() (gas: 467231)\n[PASS] test_singleLegFailureIsAllTransfersFailed() (gas: 188077)\n[PASS] test_sweepAboveBalanceReverts() (gas: 116001)\n[PASS] test_sweepByOwnerMovesTokens() (gas: 155282)\n[PASS] test_sweepRejectsShortReturnData() (gas: 174911)\n[PASS] test_sweepRejectsZeroRecipient() (gas: 101034)\n[PASS] test_sweepWorksWhilePaused() (gas: 185829)\n[PASS] test_transferOwnershipOnlyOwnerAndCancelable() (gas: 142657)\n[PASS] test_writeOffFailedClearsWithoutPaying() (gas: 517696)\n[PASS] test_writeOffFailedNothingOwedReverts() (gas: 40337)\n[PASS] test_zeroAmountFailuresCreateNoOutstandingRecipient() (gas: 342693)\n[PASS] test_zeroAmountSuccessPreventsAllFailedRevert() (gas: 352531)\nSuite result: ok. 56 passed; 0 failed; 0 skipped; finished in 491.40ms (656.41ms CPU time)\n\nRan 52 tests for test/MoneyBackRouter.t.sol:MoneyBackRouterTest\n[PASS]\nMoneyBackRouterTest invariants:\n[PASS] invariant_bindingsNeverChange\n[PASS] invariant_hookPendingEqualsRouterFeesMinusSwept\n[PASS] invariant_routerHoldsNothing\n[PASS] invariant_valueIsConserved\n MoneyBackRouterTest invariants (runs: 64, calls: 2560, reverts: 0)\n\n╭---------------+----------+-------+---------+----------╮\n| Contract      | Selector | Calls | Reverts | Discards |\n+=======================================================+\n| RouterHandler | buy      | 533   | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RouterHandler | quote    | 512   | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RouterHandler | sell     | 483   | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RouterHandler | sweep    | 507   | 0       | 0        |\n|---------------+----------+-------+---------+----------|\n| RouterHandler | warp     | 525   | 0       | 0        |\n╰---------------+----------+-------+---------+----------╯\n\n[PASS] testFuzz_buyMatchesFormulaAndLeavesNothing(uint256,uint32,uint256,uint256) (runs: 600, μ: 1307472, ~: 1343789)\n[PASS] testFuzz_sellMatchesFormulaAndLeavesNothing(uint256,uint32,uint256,uint256) (runs: 600, μ: 1494924, ~: 1525655)\n[PASS] test_acceptanceRouterBuyPaysHookExactlyLikeDirectImdBuy() (gas: 1743717)\n[PASS] test_beforePoolBoundEverythingReverts() (gas: 10489440)\n[PASS] test_buyAboveInt128RevertsAmountOverflow() (gas: 1027153)\n[PASS] test_buyAndSellRevertOnDeadline() (gas: 1396894)\n[PASS] test_buyHappyPath() (gas: 754133)\n[PASS] test_buyRefundsEthDustWhenPublicPoolFillsPartially() (gas: 738812)\n[PASS] test_buyRejectsPartialLaunchFillAndRollsBackBothLegs() (gas: 770020)\n[PASS] test_buyRejectsPartialLaunchFillEvenWithPartialPublicFill() (gas: 816982)\n[PASS] test_buyRevertsOnMinOut() (gas: 2020115)\n[PASS] test_buySwapLegsAreExactInputWithWidestLimitsAndRouterAsSender() (gas: 633160)\n[PASS] test_buyThenSellRoundTrip() (gas: 1302853)\n[PASS] test_buyTwiceAndByTwoUsers() (gas: 1752780)\n[PASS] test_buyWorksWithImdAsCurrency1() (gas: 9937027)\n[PASS] test_constructorRejectsZeroAddresses() (gas: 1005804)\n[PASS] test_constructorStoresImmutablesFromHook() (gas: 39385)\n[PASS] test_contractWithoutReceiveCanBuyFullFillOnly() (gas: 2498756)\n[PASS] test_deadlineIsCheckedBeforeAmount() (gas: 65550)\n[PASS] test_directTokenGiftsRemainUnrecoverable() (gas: 2374002)\n[PASS] test_feesFromRouterTradesAreSweepableToPayout() (gas: 1465812)\n[PASS] test_forcedEthGoesToNextTrader() (gas: 549206)\n[PASS] test_guardResetsAfterReverts() (gas: 2106373)\n[PASS] test_launchPoolKeyComesFromHook() (gas: 24076)\n[PASS] test_managerThatNeverCallsBackIsRejected() (gas: 320438)\n[PASS] test_publicPoolKeyIsHardcoded() (gas: 7323)\n[PASS] test_quoteBubblesUpForeignReverts() (gas: 15859555)\n[PASS] test_quoteBuyMatchesBuyAndChangesNothing() (gas: 3113722)\n[PASS] test_quoteCannotBeCalledWhileLocked() (gas: 839997)\n[PASS] test_quoteSellMatchesSellAndChangesNothing() (gas: 3301026)\n[PASS] test_quoteZeroReturnsZeroWithoutTouchingTheManager() (gas: 61476)\n[PASS] test_receiveRejectsEthFromAnyoneButTheManager() (gas: 775245)\n[PASS] test_reentrancyFromBuyDustRefundIsBlocked() (gas: 5521303)\n[PASS] test_reentrancyFromSellProceedsIsBlocked() (gas: 5539511)\n[PASS] test_routerHasNoAdminSurface() (gas: 163696)\n[PASS] test_sellAfterSurchargeDecayPaysOnlyBaseFee() (gas: 856182)\n[PASS] test_sellHappyPathAtT0WithSurcharge() (gas: 855788)\n[PASS] test_sellPrefundsRouterThenSettlesAndRefundsFromItsOwnBalance() (gas: 827408)\n[PASS] test_sellPullsExactlyTokensAndKeepsNoAllowance() (gas: 694656)\n[PASS] test_sellRefundsBothDusts() (gas: 925165)\n[PASS] test_sellRefundsImdDustWhenPublicPoolFillsPartially() (gas: 877077)\n[PASS] test_sellRefundsTokenDustWhenOurPoolFillsPartially() (gas: 863321)\n[PASS] test_sellRevertsOnMinOut() (gas: 1570902)\n[PASS] test_sellSwapLegsAreExactInputWithWidestLimits() (gas: 745732)\n[PASS] test_sellWhereSecondLegYieldsNothingRefundsNothingAndReverts() (gas: 942502)\n[PASS] test_sellWithoutAllowanceOrBalanceReverts() (gas: 334704)\n[PASS] test_tamperedCallbackDataIsRejected() (gas: 1062246)\n[PASS] test_tinyBuyThatYieldsNothingReverts() (gas: 2158204)\n[PASS] test_unlockCallbackRejectsManagerWithNoCallInFlight() (gas: 120933)\n[PASS] test_unlockCallbackRejectsNonManager() (gas: 59848)\n[PASS] test_zeroValueBuyAndZeroTokenSellRevert() (gas: 237280)\nSuite result: ok. 52 passed; 0 failed; 0 skipped; finished in 788.76ms (1.04s CPU time)\n\nRan 7 test suites in 791.86ms (2.33s CPU time): 231 tests passed, 0 failed, 0 skipped (231 total tests)\n","passed":true},{"durationMs":54,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"MoneyBackHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"MoneyBackHook.beforeInitialize(address,(address,address,uint24,int24,address),uint160)\",\"MoneyBackHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"MoneyBackHook.sweep()\",\"MoneyBackHook.unlockCallback(bytes)\",\"MoneyBackRouter.buyWithEth(uint256,uint256)\",\"MoneyBackRouter.quoteBuy(uint256)\",\"MoneyBackRouter.quoteSell(uint256)\",\"MoneyBackRouter.receive()\",\"MoneyBackRouter.sellForEth(uint256,uint256,uint256)\",\"MoneyBackRouter.unlockCallback(bytes)\",\"MoneyBackToken.approve(address,uint256)\",\"MoneyBackToken.transfer(address,uint256)\",\"MoneyBackToken.transferFrom(address,address,uint256)\",\"RoundPayout.acceptOwnership()\",\"RoundPayout.fund(address,uint256)\",\"RoundPayout.pause()\",\"RoundPayout.payRound(uint256,address,address[],uint256[],bytes32,uint256,uint256)\",\"RoundPayout.renounceOwnership()\",\"RoundPayout.retryFailed(uint256,address[])\",\"RoundPayout.sweep(address,address,uint256)\",\"RoundPayout.transferOwnership(address)\",\"RoundPayout.unpause()\",\"RoundPayout.writeOffFailed(uint256,address)\"],\"files\":{\"README.md\":136,\"assets/moneyback.png\":39,\"assets/moneyback.svg\":8,\"docs/AUDIT_RESPONSES.md\":99,\"docs/REVISION.md\":78,\"docs/network-observations.json\":36,\"docs/requester-share.json\":4,\"foundry.toml\":13,\"launch.json\":31,\"script/DeployAuxiliaries.s.sol\":64,\"src/MoneyBackHook.sol\":547,\"src/MoneyBackRouter.sol\":445,\"src/MoneyBackToken.sol\":153,\"src/RoundPayout.sol\":383,\"test/DeployAuxiliaries.t.sol\":73,\"test/MoneyBackHook.t.sol\":1360,\"test/MoneyBackRealV4.t.sol\":424,\"test/MoneyBackRouter.t.sol\":1860,\"test/MoneyBackToken.t.sol\":372,\"test/RoundPayout.t.sol\":1318,\"test/vendor/README.md\":8,\"test/vendor/solmate/LICENSE\":661,\"test/vendor/solmate/src/auth/Owned.sol\":44,\"test/vendor/v4-core/licenses/BUSL_LICENSE\":63,\"test/vendor/v4-core/licenses/MIT_LICENSE\":7,\"test/vendor/v4-core/src/ERC6909.sol\":90,\"test/vendor/v4-core/src/ERC6909Claims.sol\":23,\"test/vendor/v4-core/src/Extsload.sol\":64,\"test/vendor/v4-core/src/Exttload.sol\":40,\"test/vendor/v4-core/src/NoDelegateCall.sol\":33,\"test/vendor/v4-core/src/PoolManager.sol\":394,\"test/vendor/v4-core/src/ProtocolFees.sol\":71,\"test/vendor/v4-core/src/interfaces/IExtsload.sol\":21,\"test/vendor/v4-core/src/interfaces/IExttload.sol\":15,\"test/vendor/v4-core/src/interfaces/IHooks.sol\":152,\"test/vendor/v4-core/src/interfaces/IPoolManager.sol\":217,\"test/vendor/v4-core/src/interfaces/IProtocolFees.sol\":52,\"test/vendor/v4-core/src/interfaces/callback/IUnlockCallback.sol\":10,\"test/vendor/v4-core/src/interfaces/external/IERC20Minimal.sol\":48,\"test/vendor/v4-core/src/interfaces/external/IERC6909Claims.sol\":66,\"test/vendor/v4-core/src/libraries/BitMath.sol\":49,\"test/vendor/v4-core/src/libraries/CurrencyDelta.sol\":42,\"test/vendor/v4-core/src/libraries/CurrencyReserves.sol\":39,\"test/vendor/v4-core/src/libraries/CustomRevert.sol\":120,\"test/vendor/v4-core/src/libraries/FixedPoint128.sol\":8,\"test/vendor/v4-core/src/libraries/FixedPoint96.sol\":10,\"test/vendor/v4-core/src/libraries/FullMath.sol\":117,\"test/vendor/v4-core/src/libraries/Hooks.sol\":344,\"test/vendor/v4-core/src/libraries/LPFeeLibrary.sol\":79,\"test/vendor/v4-core/src/libraries/LiquidityMath.sol\":20,\"test/vendor/v4-core/src/libraries/Lock.sol\":28,\"test/vendor/v4-core/src/libraries/NonzeroDeltaCount.sol\":35,\"test/vendor/v4-core/src/libraries/ParseBytes.sol\":29,\"test/vendor/v4-core/src/libraries/Pool.sol\":620,\"test/vendor/v4-core/src/libraries/Position.sol\":103,\"test/vendor/v4-core/src/libraries/ProtocolFeeLibrary.sol\":47,\"test/vendor/v4-core/src/libraries/SafeCast.sol\":60,\"test/vendor/v4-core/src/libraries/SqrtPriceMath.sol\":292,\"test/vendor/v4-core/src/libraries/StateLibrary.sol\":345,\"test/vendor/v4-core/src/libraries/SwapMath.sol\":108,\"test/vendor/v4-core/src/libraries/TickBitmap.sol\":121,\"test/vendor/v4-core/src/libraries/TickMath.sol\":238,\"test/vendor/v4-core/src/libraries/UnsafeMath.sol\":29,\"test/vendor/v4-core/src/types/BalanceDelta.sol\":72,\"test/vendor/v4-core/src/types/BeforeSwapDelta.sol\":38,\"test/vendor/v4-core/src/types/Currency.sol\":118,\"test/vendor/v4-core/src/types/PoolId.sol\":17,\"test/vendor/v4-core/src/types/PoolKey.sol\":22,\"test/vendor/v4-core/src/types/PoolOperation.sol\":26,\"test/vendor/v4-core/src/types/Slot0.sol\":95},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1118,"exitCode":0,"name":"slither","output":"[high/medium] arbitrary-send-eth at src/MoneyBackRouter.sol:418: MoneyBackRouter._sendAllEth(address) (src/MoneyBackRouter.sol#418-423) sends eth to arbitrary user\n[high/medium] reentrancy-eth at src/MoneyBackRouter.sol:202: Reentrancy in MoneyBackRouter.buyWithEth(uint256,uint256) (src/MoneyBackRouter.sol#202-214):\n[high/medium] reentrancy-eth at src/MoneyBackRouter.sol:223: Reentrancy in MoneyBackRouter.sellForEth(uint256,uint256,uint256) (src/MoneyBackRouter.sol#223-239):\n[medium/high] incorrect-equality at src/MoneyBackHook.sol:473: MoneyBackHook._onlyBoundPool(PoolKey) (src/MoneyBackHook.sol#473-475) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/MoneyBackHook.sol:504: MoneyBackHook._specifiedFeeParts(bool,uint256) (src/MoneyBackHook.sol#504-519) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/MoneyBackRouter.sol:418: MoneyBackRouter._sendAllEth(address) (src/MoneyBackRouter.sol#418-423) uses a dangerous strict equality:\n[medium/high] incorrect-equality at src/MoneyBackHook.sol:346: MoneyBackHook.currentSurchargeBps() (src/MoneyBackHook.sol#346-352) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/MoneyBackRouter.sol:251: Reentrancy in MoneyBackRouter.quoteBuy(uint256) (src/MoneyBackRouter.sol#251-255):\n[medium/medium] reentrancy-no-eth at src/RoundPayout.sol:294: Reentrancy in RoundPayout.retryFailed(uint256,address[]) (src/RoundPayout.sol#294-331):\n[medium/medium] reentrancy-no-eth at src/RoundPayout.sol:216: Reentrancy in RoundPayout.payRound(uint256,address,address[],uint256[],bytes32,uint256,uint256) (src/RoundPayout.sol#216-245):\n[medium/medium] reentrancy-no-eth at src/RoundPayout.sol:248: Reentrancy in RoundPayout._payLegs(uint256,address,address[],uint256[]) (src/RoundPayout.sol#248-266):\n[medium/medium] reentrancy-no-eth at src/MoneyBackRouter.sol:260: Reentrancy in MoneyBackRouter.quoteSell(uint256) (src/MoneyBackRouter.sol#260-264):\n[medium/medium] reentrancy-no-eth at src/MoneyBackHook.sol:444: Reentrancy in MoneyBackHook.sweep() (src/MoneyBackHook.sol#444-449):\n[medium/medium] uninitialized-local at src/RoundPayout.sol:308: RoundPayout.retryFailed(uint256,address[]).needed (src/RoundPayout.sol#308) is a local variable never initialized\n[medium/medium] uninitialized-local at src/MoneyBackHook.sol:369: MoneyBackHook.beforeInitialize(address,PoolKey,uint160).imdIsCurrency0 (src/MoneyBackHook.sol#369) is a local variable never initialized\n[medium/medium] uninitialized-local at src/RoundPayout.sol:252: RoundPayout._payLegs(uint256,address,address[],uint256[]).anySucceeded (src/RoundPayout.sol#252) is a local variable never initialized\n[medium/medium] unused-return at src/MoneyBackRouter.sol:331: MoneyBackRouter._sell(MoneyBackRouter.CallbackData) (src/MoneyBackRouter.sol#331-356) ignores return value by poolManager.settle() (src/MoneyBackRouter.sol#342)\n[medium/medium] unused-return at src/MoneyBackRouter.sol:395: MoneyBackRouter._quote(MoneyBackRouter.CallbackData) (src/MoneyBackRouter.sol#395-414) ignores return value by poolManager.unlock(data) (src/MoneyBackRouter.sol#399-413)\n[medium/medium] unused-return at src/MoneyBackHook.sol:444: MoneyBackHook.sweep() (src/MoneyBackHook.sol#444-449) ignores return value by poolManager.unlock() (src/MoneyBackHook.sol#447)\n[medium/medium] unused-return at src/MoneyBackRouter.sol:302: MoneyBackRouter._buy(MoneyBackRouter.CallbackData) (src/MoneyBackRouter.sol#302-326) ignores return value by poolManager.settle{value: r.amountIn}() (src/MoneyBackRouter.sol#313)\n[low/medium] missing-zero-check at src/RoundPayout.sol:62: RoundPayout.transferOwnership(address).newOwner (src/RoundPayout.sol#62) lacks a zero-check on :\n[low/medium] calls-loop at src/RoundPayout.sol:357: RoundPayout._tryTransfer(address,address,uint256) (src/RoundPayout.sol#357-360) has external calls inside a loop: (ok,data) = token.call(abi.encodeCall(IERC20.transfer,(to,amount))) (src/RoundPayout.sol#358)\n[low/medium] calls-loop at src/RoundPayout.sol:357: RoundPayout._tryTransfer(address,address,uint256) (src/RoundPayout.sol#357-360) has external calls inside a loop: (ok,data) = token.call(abi.encodeCall(IERC20.transfer,(to,amount))) (src/RoundPayout.sol#358)\n[low/medium] reentrancy-benign at src/MoneyBackRouter.sol:395: Reentrancy in MoneyBackRouter._quote(MoneyBackRouter.CallbackData) (src/MoneyBackRouter.sol#395-414):\n[low/medium] reentrancy-events at src/MoneyBackHook.sol:484: Reentrancy in MoneyBackHook._accrue(bool,uint256,uint256,uint256) (src/MoneyBackHook.sol#484-491):\n[low/medium] reentrancy-events at src/MoneyBackRouter.sol:202: Reentrancy in MoneyBackRouter.buyWithEth(uint256,uint256) (src/MoneyBackRouter.sol#202-214):\n[low/medium] reentrancy-events at src/MoneyBackRouter.sol:223: Reentrancy in MoneyBackRouter.sellForEth(uint256,uint256,uint256) (src/MoneyBackRouter.sol#223-239):\n[low/medium] reentrancy-events at src/MoneyBackHook.sol:452: Reentrancy in MoneyBackHook.unlockCallback(bytes) (src/MoneyBackHook.sol#452-463):\n[low/medium] timestamp at src/RoundPayout.sol:347: RoundPayout.isPaid(uint256) (src/RoundPayout.sol#347-349) uses timestamp for comparisons\n[low/medium] timestamp at src/MoneyBackHook.sol:484: MoneyBackHook._accrue(bool,uint256,uint256,uint256) (src/MoneyBackHook.sol#484-491) uses timestamp for comparisons\n[low/medium] timestamp at src/MoneyBackHook.sol:360: MoneyBackHook.beforeInitialize(address,PoolKey,uint160) (src/MoneyBackHook.sol#360-386) uses timestamp for comparisons\n[low/medium] timestamp at src/MoneyBackRouter.sol:202: MoneyBackRouter.buyWithEth(uint256,uint256) (src/MoneyBackRouter.sol#202-214) uses timestamp for comparisons\n[low/medium] timestamp at src/MoneyBackHook.sol:473: MoneyBackHook._onlyBoundPool(PoolKey) (src/MoneyBackHook.sol#473-475) uses timestamp for comparisons\n[low/medium] timestamp at src/MoneyBackRouter.sol:223: MoneyBackRouter.sellForEth(uint256,uint256,uint256) (src/MoneyBackRouter.sol#223-239) uses timestamp for comparisons\n[low/medium] timestamp at src/MoneyBackHook.sol:346: MoneyBackHook.currentSurchargeBps() (src/MoneyBackHook.sol#346-352) uses timestamp for comparisons\n[low/medium] timestamp at src/MoneyBackHook.sol:521: MoneyBackHook._toInt128(uint256) (src/MoneyBackHook.sol#521-524) uses timestamp for comparisons\n[low/medium] timestamp at src/MoneyBackHook.sol:504: MoneyBackHook._specifiedFeeParts(bool,uint256) (src/MoneyBackHook.sol#504-519) uses timestamp for comparisons\n[low/medium] timestamp at src/MoneyBackHook.sol:412: MoneyBackHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/MoneyBackHook.sol#412-436) uses timestamp for comparisons","passed":true},{"durationMs":283,"exitCode":0,"name":"aderyn","output":"[high] eth-send-unchecked-address at src/MoneyBackRouter.sol:202: ETH transferred without address checks (2 places)\n[high] incorrect-shift-order at src/MoneyBackHook.sol:532: Incorrect Assembly Shift Parameter Order\n[high] reentrancy-state-change at src/MoneyBackHook.sol:447: Reentrancy: State change after external call (2 places)\n[high] reused-contract-name at src/MoneyBackRouter.sol:16: Contract Name Reused in Different Files (2 places)\n[low] centralization-risk at src/RoundPayout.sol:62: Centralization Risk (8 places)\n[low] costly-loop at src/RoundPayout.sol:253: Costly operations inside loop (2 places)\n[low] internal-function-used-once at src/MoneyBackHook.sol:107: Internal Function Used Only Once\n[low] large-numeric-literal at src/MoneyBackHook.sol:207: Large Numeric Literal (2 places)\n[low] missing-inheritance at src/MoneyBackToken.sol:14: Missing Inheritance\n[low] non-reentrant-not-first at src/RoundPayout.sol:198: `nonReentrant` is Not the First Modifier (4 places)\n[low] push-zero-opcode at src/MoneyBackHook.sol:2: PUSH0 Opcode (4 places)\n[low] require-revert-in-loop at src/RoundPayout.sol:309: Loop Contains `require`/`revert` (2 places)\n[low] state-change-without-event at src/MoneyBackHook.sol:444: State Change Without Event (4 places)\n[low] state-no-address-check at src/RoundPayout.sol:63: Address State Variable Set Without Checks (2 places)\n[low] unchecked-return at src/MoneyBackHook.sol:447: Unchecked Return (2 places)\n[low] unsafe-erc20-operation at src/MoneyBackRouter.sol:229: Unsafe ERC20 Operation (6 places)\n[low] unspecific-solidity-pragma at src/MoneyBackRouter.sol:2: Unspecific Solidity Pragma (3 places)\n[low] unused-public-function at src/MoneyBackHook.sol:334: Public Function Not Used Internally (13 places)","passed":true},{"durationMs":937,"exitCode":0,"name":"proof 9349b4cd221d","output":"Compiling 51 files with Solc 0.8.26\nSolc 0.8.26 finished in 799.87ms\nCompiler run successful!\n\nRan 2 tests for test/imd-proof-7b13932a/Proof_9349b4cd221d.t.sol:FeeBaseAsymmetryTest\n[PASS] test_exactOutputSellSurchargeRateOnPoolOutput() (gas: 206512)\n[PASS] test_sameSaleSameOutputDifferentFee() (gas: 417217)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 3.75ms (839.58µs CPU time)\n\nRan 1 test suite in 4.32ms (3.75ms 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":"dfbad1ae62c24c7634937e2f536ce1ad05c8a05c50428eff5c3feb92746fce77","verifiedTreeHash":"aa3b4fd45a85fe09d040aed45bcdb8f7c96082b2","verifierVersion":"0.1.0+0e08a464"}]}