{"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":"3622d85b-c97e-441b-98d5-4503fcf5e5ba","kind":"shape:chain","nodes":[{"acceptedSubmissionHash":"06d9e496034cadce393b5338395248246c0b1d3c361b143c5cb60d53dc26576a","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":"5f7ce4026e9de0e4496219ca87137f4af81c30e939110aca49458f6d7d323271","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":"a8027a12dd797e14913f2f25f6117f91416152ef99cdcb44b33456d6d102e2fa","dependsOn":["build_contract_project","write_foundry_tests","manifest","audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"747f191ce96dc718678dc7d811e6c81f60af79e7a8614a4f8c03aab81671166d","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":"6d0132a1d379cf98ae7c4b1187f12864428d5e745aa4aa0ce834b026c2c41b9e","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":"d0b0a5a676165dd0980f592bc9593b0906c3d42ceab1384fc5a1456d66911a45","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"d59561da3b378461b7c749f6bd5084477d44993aa94972ddea90f542535751df","skillId":"build-contract-project","tools":[]},"key":"build_contract_project","kind":"code","role":"implement","skillHash":"d59561da3b378461b7c749f6bd5084477d44993aa94972ddea90f542535751df","skillId":"build-contract-project","state":"accepted"},{"acceptedSubmissionHash":"9b98bccd37a649e5af698892c53f6277343eb0f54148696171a44b4bea07610e","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"tools":[]},"key":"manifest","kind":"code","role":"integrate","skillHash":null,"skillId":null,"state":"accepted"},{"acceptedSubmissionHash":"f1d1007ecb2f9db118079340391403ee0d7f6c2a6cd7c6f9f31b1c3269a4c74b","dependsOn":["build_contract_project"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"4c76766b00591dfe982fe85d3de589fdb2f810e5137c301155d1c5e36a1d3b23","skillId":"write-foundry-tests","tools":[]},"key":"write_foundry_tests","kind":"code","role":"tests","skillHash":"4c76766b00591dfe982fe85d3de589fdb2f810e5137c301155d1c5e36a1d3b23","skillId":"write-foundry-tests","state":"accepted"}],"objective":"Found this game on Robinhood Chain: Sherwood, a PvP arena that lives inside a Uniswap v4 hook (sherwood.pfwa.fun). Every buy of $IMD in its pool is a move, the top players split the ETH pot each round, and King of the Hill takes a share. Let’s build a v4 hook that plays it for us: a small cut of every trade of this coin goes into a jar, and when it fills up, it buys into Sherwood. Whoever’s trade tips the jar over becomes the player for that buy, so anyone trading the coin can end up on the Sherwood board. The $IMD it buys gets burned.\n\nOn every swap, take 1% of the ETH side into the hook. When the hook holds at least 0.005 ETH, the next swap also runs the buyback: it swaps all of that ETH for $IMD through the Sherwood v2 Uniswap v4 pool on Robinhood Chain (native ETH / $IMD 0x5f7bb59365ce557c26dbcaa4ee9d39a4b95b7127, fee 9000, tick spacing 180, hook 0xc33c907127b94303F578d6Be551C36F45939e0cC) as an exact-input swap with no price limit, then sends the $IMD it receives to 0x000000000000000000000000000000000000dEaD. If the buyback fails, the swap still succeeds and the buyback retries on a later swap. The hook has no owner and no admin functions; nothing can be changed after launch.\n\nToken name: Sherwood Player\nToken symbol: QUIVER","parentJobId":null,"planHash":"687e54ab28e26f73dacdd3b95c1f714c8461b19a03136a968688bb140b3d1525","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"3622d85b-c97e-441b-98d5-4503fcf5e5ba","publication":{"commit":null,"deliveredAt":null,"repoUrl":"https://github.com/identity-md-launches/launch-2279-sherwood-player"},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"51490","feedbackHash":"7b1a5d5fa545d18c8fadde38c5a21a4cbc883801aacfca46f865bfdd324f5a62","nodeKey":"audit_economics","submissionHash":"06d9e496034cadce393b5338395248246c0b1d3c361b143c5cb60d53dc26576a","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51216","feedbackHash":"e6f667b491083abb94af174799df920a9e457fe4253a1557fc3e625d6a6609f5","nodeKey":"audit_flow","submissionHash":"5f7ce4026e9de0e4496219ca87137f4af81c30e939110aca49458f6d7d323271","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51143","feedbackHash":"fb2285840e2b4100933d1830bca0f593d38015a21ea22ddeff09b62608bb87fc","nodeKey":"audit_judge","submissionHash":"a8027a12dd797e14913f2f25f6117f91416152ef99cdcb44b33456d6d102e2fa","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51492","feedbackHash":"126009611a3a5d4829ac0b8fc33ced68f1c5db6791815f8eec216aea0944c611","nodeKey":"audit_judge","submissionHash":"fad57b890d2f11cc719a565190384c8d81e366a21e352f51532e3ed332c94a6a","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52138","feedbackHash":"e13b2ebcb45c5af665a86c16c35d021d21862582735f12e9f7ea6d1efe4b0188","nodeKey":"audit_math","submissionHash":"747f191ce96dc718678dc7d811e6c81f60af79e7a8614a4f8c03aab81671166d","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51877","feedbackHash":"fd7e24d6fdf9a20b07974b57bfe837f7d9ba1f8a3315b31787772bee2707075a","nodeKey":"audit_permissions","submissionHash":"6d0132a1d379cf98ae7c4b1187f12864428d5e745aa4aa0ce834b026c2c41b9e","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51032","feedbackHash":"95de0d25e9f2eb7ccb4566caee18712b60facb3c24ccd8a54aa7d7e5ee4ea165","nodeKey":"build_contract_project","submissionHash":"d0b0a5a676165dd0980f592bc9593b0906c3d42ceab1384fc5a1456d66911a45","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52025","feedbackHash":"87dd5cf0844b0c5ce2c71b0387d3d1b74bc44a9adeef6261782333e9b78bcbdb","nodeKey":"build_contract_project","submissionHash":"b35012b63149c60a55f91f834cc0d35e6cff85cf536d5c0f98ef29be3b628eac","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51882","feedbackHash":"0b371be7b71051adc94b84a8af0ccdf9bc4cd3cb8065310d7a7bffb5131d53f5","nodeKey":"manifest","submissionHash":"9b98bccd37a649e5af698892c53f6277343eb0f54148696171a44b4bea07610e","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"51724","feedbackHash":"d0b4b943e3b5d9dece23d4fb7293a2cffae06ff2f5e45df4b94e0943a1b15ced","nodeKey":"manifest","submissionHash":"377e7f9f5f76d61c51cbb895dc5729a9e0a67fd31801ce925ac7a471ca8802ff","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"50999","feedbackHash":"a819494f83e30dde017cf167d0fd6c88088422070620d9bba039edef9b2739e7","nodeKey":"write_foundry_tests","submissionHash":"22d08b13bbd098487a12357414a2bffe8db1d5fe5acc0de6d11554409c51f7f4","tag1":"verification:checks","tag2":"acceptance-v2","value":1},{"agentId":"52680","feedbackHash":"5a2c56dfa86c9da97035e7f5c35fd10f9df3bd86778fd430b88ad7b04fcb2c10","nodeKey":"write_foundry_tests","submissionHash":"f1d1007ecb2f9db118079340391403ee0d7f6c2a6cd7c6f9f31b1c3269a4c74b","tag1":"verification:checks","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"d2dec9b9b09bc55cdf416c1c34bfa6069b7baa88d19e90025ff75a71122c2563","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"7f1df2a55552de06","findings":[],"hash":"06d9e496034cadce393b5338395248246c0b1d3c361b143c5cb60d53dc26576a","nodeId":"53d3d415-e730-4fdb-a2c3-844a15e08982","outcome":"completed","summary":"I found no defects I could back with a reproduction, so `.imd-findings.json` has an empty findings list. Its coverage record has a row for each of the 9 entry points, and every row is `holds`. No repository files were changed, and the existing suite passes (28/28).\n\nWhat I checked, for the economics area:\n\n- **Fee on each swap direction:** the hook takes 1% of the ETH side in all four cases.\n  - **Exact-input buys and exact-output sells:** ETH is the amount the trader specifies. `beforeSwap` takes the fee from that amount, and `afterSwap` refuses a partial fill instead of charging fee on ETH that never traded.\n  - **Exact-input sells and exact-output buys:** ETH is the other side. `afterSwap` takes the fee from the ETH that actually changed hands.\n  - **Balances:** every path settles to zero.\n- **Fresh pool manager with no ETH:** when the manager can't cover the fee, the hook records an ERC-6909 claim instead of transferring. `_redeem` and `redeemFees` later turn the claim into ETH, never more than the manager holds. Nobody can use this to pick the Sherwood player.\n- **Buyback:** it fires once the hook holds at least 0.005 ETH, and only through a call the hook makes to itself. It pays the ETH in first, then does an exact-input swap with no price limit on the pool from the brief (fee 9000, tick spacing 180, hook 0xc33c…). All the $IMD goes to 0x…dEaD. A failed buyback is rolled back and its gas is capped, so the trader's swap still succeeds and the next swap retries.\n- **Strict balance equality (flagged by both slither and aderyn):** not exploitable. The hook reads its balance immediately before the check, and ETH sent to it with `receive()` just adds to the jar.\n\nThe scanner flags on reentrancy and \"locks Ether\" didn't hold up: the busy flag guards re-entry, and having no withdrawal is what the brief asks for.\n\nThere is one real exposure, but it is by design. With no price limit, the buyback can be sandwiched. Someone can buy $IMD on the Sherwood pool, make the trade that tips the jar, then sell, and the jar takes the loss. The brief requires no price limit, and README line 65 and SECURITY.md already say so, so I didn't file it as a defect.\n\nTwo things I couldn't check without a fork of Robinhood Chain:\n- whether Sherwood's swap fits in the 2,000,000 gas budget. If it doesn't, every buyback fails and the ETH collects in the hook with no way out.\n- whether Sherwood really credits the move to `tx.origin`. If it does, a relayer or bundler that submits someone else's trade becomes the player, not the trader.","treeHash":null,"usage":{"cachedInputTokens":235153,"inputTokens":10,"model":"claude-opus-5-5","outputTokens":6348,"runtime":"claude","turns":5,"wallClockMs":101151}},{"artifacts":[],"attempt":1,"bundleHash":"6b9a0acf1d8f24151d0219ff5d38077bd651ca5fe67f9f7a1006e4b332cc0c79","device":"59b51c92352647b4","findings":[],"hash":"22d08b13bbd098487a12357414a2bffe8db1d5fe5acc0de6d11554409c51f7f4","nodeId":"a427b596-6fb6-486d-8de1-5c75cac1c630","outcome":"completed","summary":"Added failure-path, fuzz, and stateful invariant tests under `test/`.\n\nVerified with the required seed: **58 tests passed, 0 failed**, including **45,056 invariant calls**. `forge build` also passes.\n\nNo confirmed implementation defects found. Sherwood is modeled locally; live-chain integration remains unverified.","treeHash":"9730d178e60ad7ef6a4db2a918794845643aed1a","usage":{"cachedInputTokens":3246080,"inputTokens":167597,"model":"gpt-6-astra","outputTokens":32013,"runtime":"codex","turns":7,"wallClockMs":1022624}},{"artifacts":[],"attempt":1,"bundleHash":"b2a4cb09b7630d7cf2a8c587806d9f64f642882ce58bba3f770da72ca442e578","device":"e1d2139b33ec4bd9","findings":[],"hash":"377e7f9f5f76d61c51cbb895dc5729a9e0a67fd31801ce925ac7a471ca8802ff","nodeId":"e0ebd79e-3364-4bfb-a647-18977e0bd4f5","outcome":"completed","summary":"Created [launch.json](/home/imd/.identitymd-1/work/3622d85b-c97e-441b-98d5-4503fcf5e5ba/e0ebd79e-3364-4bfb-a647-18977e0bd4f5/launch.json) matching the contracts, constructor arguments, five permissions, and required pool settings.\n\n`forge build` succeeded with existing lint warnings. All 28 tests passed using the required fuzz seed. Only `launch.json` changed for submission.","treeHash":"b326f290bedc0c7c22d6c6a9d0323524e5164e2e","usage":{"cachedInputTokens":363520,"inputTokens":57684,"model":"gpt-6-astra","outputTokens":3419,"runtime":"codex","turns":3,"wallClockMs":106842}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"fa2b7fcb5f53535a","findings":[{"citation":"resolved","description":"executeBuyback runs inside the caller's unlock, in the middle of PoolManager.swap, and starts by calling poolManager.sync(address(0)). In v4-core (PoolManager.sync lines 278-287) that writes 0 into the single transient CURRENCY_SLOT that the caller's own sync/settle sequence relies on. A router that follows v4's documented ERC-20 payment order sync(token) -> transfer(token) -> ... -> settle(), with the swap between the transfer and the settle, has its snapshot destroyed: its later settle() reads a zero synced currency, treats the call as a native settle (msg.value = 0) and credits nothing, so its currency1 debt is never settled and unlock reverts with CurrencyNotSettled. The brief requires 'If the buyback fails, the swap still succeeds'; here the buyback succeeds and the user's swap fails. Because the revert rolls the jar back, every retry by that router hits the same state until a differently-ordered router spends the jar. Routers that pre-pay are legitimate (prepaying is how a router avoids a second ERC-20 transfer for refund flows, and nothing in v4 forbids swapping between sync and settle). The only alternative the hook has is to make its own settle fail (without the sync, settle{value} would revert NonzeroNativeValue and the buyback would be skipped by the self-call boundary), which is the behaviour the brief asks for. Fix: before attempting the buyback read poolManager.exttload(CurrencyReserves.CURRENCY_SLOT) (0x27e098c505d44ec3574004bca052aabf76bd35004c182099d8c575fb238593b9); if it is non-zero, skip the attempt (the jar is retried on the next swap) instead of calling sync. Verified: with that two-line change the proof below passes and the existing suite is unaffected.","line":154,"path":"src/SherwoodPlayerHook.sol","reproduction":"State: hook holds 0.005 ETH (jar full, e.g. after a 0.5 ETH exact-input buy whose game call failed). Call sequence by a router inside its unlockCallback: poolManager.sync(QUIVER); QUIVER.transfer(poolManager, 0.1e18); poolManager.swap(key, SwapParams(false, -0.1e18, MAX_SQRT_PRICE-1), \"\"); poolManager.settle(); poolManager.take(ETH, router, out). Expected: swap succeeds, router receives ~0.0987 ETH, the buyback plays with 0.005987 ETH. Actual: inside afterSwap -> executeBuyback -> sync(address(0)) resets the synced currency; the buyback itself settles cleanly, but the router's settle() returns 0, its -0.1e18 QUIVER delta stays open and PoolManager.unlock reverts CurrencyNotSettled. The same router call with the jar below threshold (no buyback) succeeds. Run: forge test --match-path test/scratch/SyncClobberProof.t.sol (fails on this code with CurrencyNotSettled).","severity":"medium","snippet":"        poolManager.sync(Currency.wrap(address(0)));","title":"Buyback's poolManager.sync(address(0)) resets the swapping router's synced currency, so a router that pays its ERC-20 input before swap() reverts with CurrencyNotSettled whenever the jar is full"},{"citation":"resolved","description":"The buyback is an exact-input ETH->IMD swap made with the trader's tx.origin, and the published SherwoodV2 (commit 754206e, the source the README cites) credits the play and the purchase to tx.origin: beforeSwap calls _play(round, tx.origin, ethIn, hookData) and afterSwap does bought[currentRound()][tx.origin] += tokenOut. At settlement (SherwoodV2.settle, 'owed = bought[round][winner]; balance = token.balanceOf(winner); if (balance < owed) { kept = prize * balance / owed; ... }') and again at claim ('if (balance < r.held[i]) revert MustHoldToClaim()'), the player must still hold every IMD recorded as bought in that round. This hook immediately sends the whole output to 0x...dEaD, so the trader can never hold it. Two consequences the code's reasoning misses: (a) a trader whose only Sherwood activity is tipping this jar holds 0 IMD, so kept = prize * 0 / owed = 0: they are 'on the board' but every prize they would win is forfeited to the rollover, which defeats the brief's purpose ('the top players split the ETH pot'); (b) a trader who is already a legitimate Sherwood player this round (holding everything they bought) and who tips the jar has the burned amount added to their owed total without any balance, so a prize they had fully earned is reduced by burned/(held+burned), and the only way to restore it is to buy that many IMD themselves before settlement. The README mentions (a) in passing but not (b), and neither is a decision the trader makes: the buyback is attributed to whoever's trade happens to tip the jar. This is a design conflict between 'the IMD it buys gets burned' and Sherwood's holding rule; the requester should decide (e.g. deliver the bought IMD to tx.origin instead of burning, which also satisfies the holding rule, or keep the burn and state the forfeiture as the product's behaviour). Reported because the loss falls on an identifiable third party who did not opt in.","line":163,"path":"src/SherwoodPlayerHook.sol","reproduction":"State (Robinhood Chain, Sherwood round R): Alice bought 1,000,000 IMD through Sherwood this round and still holds it; she is #1 with first-prize share P of the pot. Jar holds 0.0049 ETH. Alice then buys 0.02 ETH of QUIVER from her EOA: fee 0.0002 ETH tips the jar to 0.0051 ETH; afterSwap -> executeBuyback swaps 0.0051 ETH for, say, 50,000 IMD, Sherwood records bought[R][Alice] += 50,000, the hook sends the 50,000 IMD to 0x...dEaD. At settle(): owed = 1,050,000, balance = 1,000,000, kept = P * 1,000,000 / 1,050,000, so Alice loses 4.76% of P (PrizeForfeited emitted) although she sold nothing. Variant: Bob has never played Sherwood and holds 0 IMD; his QUIVER trade tips the jar, he is credited points for a 0.0051 ETH buy and 50,000 IMD bought; if he places in the top 3 or as King, kept = prize * 0 / 50,000 = 0, and claim() would also revert MustHoldToClaim. Expected per brief: the tipping trader 'ends up on the Sherwood board' and can 'split the ETH pot'. Actual: the board entry carries no claimable prize (Bob) or reduces an existing one (Alice).","severity":"medium","snippet":"        poolManager.take(Currency.wrap(IMD), BURN, output);","title":"Burning the IMD bought in the trader's name makes Sherwood count it as sold: a tipping trader with no IMD forfeits the whole prize and an existing Sherwood winner who trades QUIVER has their prize cut"},{"citation":"resolved","description":"Every buyback is a Sherwood play, and in the published SherwoodV2 a play first runs _resumeScan/_currentLeader. When the cached #1 has lost points (a sell by the leader wipes their points in afterSwap, or a STEAL/ROBIN_HOOD/FLIP debits them) the next play rescans the whole board with _scan(round, true) for boards of up to MAX_LEADER_SCAN = 1500 players; with all = true the scan ignores its 1,000,000-gas reserve and reads two cold storage slots per player (players[i] and points[round][a], ~4,200 gas), i.e. about 4.2M gas at 1,000 players and 2.1M at 500. The hook forwards at most min(gasleft - 150,000, 2,000,000) to executeBuyback, so in that state the buyback runs out of gas, BuybackFailed is emitted, and every later attempt fails the same way until some other player's move (with a normal 10M+ gas limit) completes the rescan. The jar keeps growing meanwhile, which makes the eventual buyback larger and, with no price bound (finding 5), more profitable to sandwich. This is a liveness defect of the fixed, immutable budget rather than a fund loss; the README's statement that 'a game whose execution permanently exceeds the fixed budget cannot be played' understates that the condition recurs every time the leader changes by losing points on an active board.","line":38,"path":"src/SherwoodPlayerHook.sol","reproduction":"State: Sherwood round with 600 registered players and _leaderStale[round] == true (the leader just sold). Jar holds >= 0.005 ETH. Any QUIVER swap: afterSwap -> _attemptBuyback forwards 2,000,000 gas -> executeBuyback -> poolManager.swap(sherwoodPool) -> SherwoodV2.beforeSwap -> _play -> _resumeScan -> _scan(round, true) iterates 600 players at ~4,200 gas each (~2.5M) and the self-call runs out of gas. Expected: the tipping trader becomes the player. Actual: BuybackFailed(tx.origin, amount); the outer swap succeeds; repeated on every swap until an external play finishes the rescan. Locally the same mechanism is exercised by test_gameOutOfGasDoesNotRevertTrade (MockSherwood mode 2), which shows a game needing more than the budget simply never gets played.","severity":"low","snippet":"    uint256 public constant BUYBACK_GAS = 2_000_000;","title":"2,000,000 gas budget is below Sherwood's full leader rescan, so the buyback fails on every attempt while the board's cached leader is stale"},{"citation":"resolved","description":"Because the buyback is skipped when little gas is left and tolerated when it runs out of gas, the transaction succeeds at a far lower gas limit than the one the buyback needs. eth_estimateGas binary-searches for the smallest limit at which the call succeeds, so it returns the skip/fail limit; the 10-50% buffers wallets add do not bridge a gap of several hundred thousand gas (and more with the real Sherwood, whose _play alone writes several storage slots and makes two balanceOf calls). The practical effect contradicts the brief's 'whoever's trade tips the jar over becomes the player': ordinary users' trades tip the jar and get BuybackFailed (or a silent skip, which emits nothing), and the jar is eventually spent by whoever deliberately sends a high gas limit, i.e. a searcher who can also choose the moment to sandwich it. The README acknowledges the estimator issue; it is reported here with the measured numbers so the requester can decide whether to accept it. There is no hook-side fix that keeps 'the swap still succeeds'; mitigations are documenting a recommended gas limit for routers/frontends and emitting an event on the low-gas skip so the condition is observable.","line":195,"path":"src/SherwoodPlayerHook.sol","reproduction":"Measured on the project's fixture (test/scratch/GasEstimate.t.sol, MockSherwood, PoolDriver): a 0.5 ETH exact-input buy that tips the jar succeeds from a 123,113 gas limit upward, with game.plays() == 0 at that limit; the buyback first plays at a 503,113 gas limit. An estimator therefore returns ~123k; with a 50% buffer (185k) the swap still succeeds and the buyback still fails. Sequence: vm.prank(player, player); driver.swap{gas: 185_000}(key, SwapParams(true, -0.5e18, MIN_SQRT_PRICE+1)) -> success, hook balance stays 0.005 ETH, BuybackFailed or no event. Expected per brief: the player is credited the buy.","severity":"low","snippet":"        if (amount < THRESHOLD || gasleft() <= GAS_RESERVE) return;","title":"A standard eth_estimateGas limit lands on the failed-buyback path, so wallet-originated tipping trades almost never play"},{"citation":"resolved","description":"The brief asks for an exact-input swap with no price limit and the README documents the exposure; this row records the concrete shape so the judge does not need to re-derive it. Anyone can fund the jar through receive() and trigger the buyback in their own transaction (a 100-wei swap suffices), so the attacker chooses the block and is the tx.origin credited by Sherwood. Sherwood's 5% fee on the attacker's own buy and sell plus the 0.9% LP fee each way (~11.8% of attacker volume) bound the profit, so a 0.005 ETH jar is not worth attacking, but every failed buyback (findings 3 and 4) lets the jar grow without limit while the trade size and price bound stay fixed. No change is requested unless the requester wants a cap on the amount spent per buyback.","line":158,"path":"src/SherwoodPlayerHook.sol","reproduction":"State: jar = J ETH >= 0.005 after several failed attempts. Attacker tx: (1) buy IMD in the Sherwood pool pushing the price up, (2) send 1 wei to the hook if needed and swap 100 wei of ETH for QUIVER so afterSwap spends J ETH at the inflated price, (3) sell the IMD back. Hook pays J, receives output below fair value; the loss is the fee payers' (the jar). Expected: by brief, no slippage protection; actual matches the brief.","severity":"info","snippet":"            SwapParams(true, -int256(amount), TickMath.MIN_SQRT_PRICE + 1),","title":"Buyback has no price bound or minimum output, so a searcher can time and sandwich the jar (requested by the brief, noted for the record)"}],"hash":"5f7ce4026e9de0e4496219ca87137f4af81c30e939110aca49458f6d7d323271","nodeId":"d2acabe6-e7e9-44f3-94ea-e673e5a63275","outcome":"completed","summary":"The review is complete. `.imd-findings.json` holds five findings and a full coverage record, the working tree is unchanged, and the one proof test fails on the current code and passes under a candidate fix.\n\n**Findings written**\n\n1. **Medium, `executeBuyback` line 154.** The buyback calls `poolManager.sync(address(0))` inside the caller's unlock. That resets the transient synced-currency slot, so a router that pays its ERC-20 input before calling swap gets nothing credited from its later settle and the whole trade reverts with CurrencyNotSettled. The brief says the swap must still succeed. The jar rolls back, so that router stays blocked until a differently ordered router spends it. Proof test included. A two-line fix (skip the attempt when a currency is synced) makes the proof pass and leaves the existing 28 tests green.\n\n2. **Medium, line 163.** The published Sherwood v2 source records the burned IMD as bought by `tx.origin` and scales prizes by `balance / owed` at settlement and requires holding at claim. A trader with no IMD who tips the jar forfeits the entire prize. An existing Sherwood winner who trades QUIVER has their earned prize cut pro rata by tokens they never received. This is a design conflict between the burn and Sherwood's holding rule, reported for the requester to decide.\n\n3. **Low, line 38.** Sherwood's full leader rescan costs about 4,200 gas per player and ignores its gas reserve for boards up to 1,500 players. Above roughly 450 players the 2,000,000 gas budget is exceeded, so every buyback attempt fails until another player's move finishes the rescan.\n\n4. **Low, line 195.** Measured on the fixture, the tipping swap succeeds from a 123k gas limit while the buyback needs 503k. Estimators with a 50% buffer (185k, verified) still produce a failed buyback, so ordinary wallet trades rarely become the player.\n\n5. **Info, line 158.** No price bound or minimum output, as the brief requested. Recorded because findings 3 and 4 let the jar grow, which raises the sandwich value.\n\n**Coverage**\n\nAll nine entry points have rows. `beforeSwap`, `receive`, `redeemFees`, `unlockCallback` and the three token functions hold after tracing their delta accounting and guards. `afterSwap` and `executeBuyback` point to findings. Extra rows record the zero-delta invariant, the vendored v4-core matching upstream apart from formatting, the permission flags, and the Sherwood source checks. Live Robinhood Chain bytecode was not verified because no RPC was available.","treeHash":null,"usage":{"cachedInputTokens":4763056,"inputTokens":962,"model":"claude-fable-5-1","outputTokens":61551,"runtime":"claude","turns":53,"wallClockMs":1016848}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"f494611affb5524c","findings":[{"citation":"resolved","description":"_attemptBuyback forwards at most BUYBACK_GAS = 2_000_000 gas (line 38, applied at line 201) to the self-call that plays Sherwood, regardless of how much gas the trader supplied. The published Sherwood v2 (MirakoolDev/sherwood@754206e, SherwoodV2.sol) runs `_resumeScan(round)` as the first line of every play and `_updateReign` -> `_currentLeader` as the last; while `_leaderStale[round]` is true (set whenever the cached #1 is debited: a sell on the Sherwood pool by the leader, a Steal/Robin Hood hit, a lost flip) and the board has <= MAX_LEADER_SCAN = 1500 players, `_scan(round, true)` walks the whole `_players[round]` array with two cold SLOADs per player (`players[i]`, `points[round][a]`) and the `gasleft() < LEADER_SCAN_RESERVE` break is disabled (`atLeast = n`). At ~4,400 gas per player plus the AMM swap, the 5% pot `take`, Sherwood's own bookkeeping and the IMD transfer to 0xdEaD, the 2,000,000 budget is exhausted at roughly 350-400 players; a 1,500-player board needs ~6.6M gas. Sherwood itself sizes LEADER_SCAN_RESERVE at 1,000,000 gas, i.e. it expects its players to bring far more than this hook ever forwards. Every attempt from this hook then reverts OOG inside the capped call, `BuybackFailed` is emitted, and the jar keeps growing; nothing on this hook can change BUYBACK_GAS (no owner, no admin), so a trader who deliberately brings 10M+ gas still cannot play. The cap is also unnecessary: GAS_RESERVE (150,000) already protects the parent swap, the return data is already discarded, and the 63/64 rule bounds what the call can take. This is a trust-gap (access x economics) at the periphery boundary: the trader is the only party allowed to fund the play, yet a constant in the hook, not the trader's gas limit, decides whether the game is reachable. Fix: forward `gasleft() - GAS_RESERVE` (no BUYBACK_GAS clamp), or at minimum raise the cap to cover Sherwood's documented worst case (~8M) and document the needed gas limit.","line":201,"path":"src/SherwoodPlayerHook.sol","reproduction":"State: QUIVER pool live; Sherwood pool live with a 600-player board whose leader cache is stale (in the real game: the current #1 just sold on the Sherwood pool). Jar holds 0 ETH. Call: trader PLAYER (EOA, tx.origin) swaps exact-input 0.5 ETH -> QUIVER through any router with a 30M gas limit. beforeSwap takes 0.005 ETH; afterSwap: balance >= THRESHOLD, _attemptBuyback forwards min(gasleft-150k, 2,000,000) = 2,000,000 to executeBuyback; Sherwood's beforeSwap rescans 600 players (~2.6M gas) -> OOG -> self-call returns false -> `BuybackFailed(PLAYER, 0.005 ether)`; game.plays() == 0, hook balance stays 0.005 ETH, nothing burned. Expected: the trade that tipped the jar plays Sherwood and burns the IMD. With the clamp removed (forward gasleft()-GAS_RESERVE) the same call plays, burns, and empties the jar (verified locally: the proof passes once line 201 is deleted; the full trade then costs ~3.1M gas). Proof: test/scratch/BuybackGasCap.t.sol.","severity":"medium","snippet":"        if (budget > BUYBACK_GAS) budget = BUYBACK_GAS;","title":"Hardcoded 2,000,000 gas cap starves the Sherwood buyback whenever the board rescan is pending, so the hook's core feature fails on any busy round and no one can raise the cap"},{"citation":"resolved","description":"PoolManager.sync(address(0)) calls CurrencyReserves.resetCurrency(), clearing the transient synced-currency slot that belongs to whoever is driving the outer unlock. A router that uses the v4-supported order sync(token) -> transfer(token to manager) -> swap -> settle() (one transfer, no pull-after-swap) has its synced currency erased mid-swap by this hook's afterSwap. Its later settle() then sees currency == address(0), credits `paid = msg.value = 0` instead of the QUIVER it already transferred, and the unlock reverts with CurrencyNotSettled. Because the reset is only rolled back when the buyback FAILS, the router is broken precisely when the jar is full and the buyback succeeds, and it stays broken (the jar stays full) until a differently-shaped router triggers the buyback. Access/asymmetry seam: the hook's own settlement choice silently changes the accounting context of an unprivileged third party. Fix: do not call sync; read the synced currency via TransientStateLibrary.getSyncedCurrency(poolManager) and skip the attempt (leave the jar for the next swap) when it is non-zero, or settle the prepayment with `settle{value}` only when nothing is synced.","line":154,"path":"src/SherwoodPlayerHook.sol","reproduction":"State: jar holds >= 0.005 ETH (e.g. after a prior failed attempt or donation). Call: a router whose unlockCallback does `manager.sync(QUIVER); QUIVER.transfer(manager, 1e18); manager.swap(key, SwapParams(false, -1e18, MAX_SQRT_PRICE-1), \"\"); manager.settle(); manager.take(ETH, ...)` (exact-input sell paid up front). During swap, afterSwap -> executeBuyback -> `poolManager.sync(address(0))` resets the synced currency -> buyback succeeds -> router's `settle()` returns 0 paid -> router still owes 1e18 QUIVER -> `CurrencyNotSettled` revert of the entire transaction. Expected: the sell settles with the pre-synced transfer as v4 permits. Actual: every sell from this router reverts while the jar is full. Without the jar being full the identical call succeeds.","severity":"low","snippet":"        poolManager.sync(Currency.wrap(address(0)));","title":"executeBuyback calls poolManager.sync(native), which resets a router's pre-synced ERC-20 reserves and makes that router's whole swap revert whenever the buyback succeeds"},{"citation":"resolved","description":"Sherwood v2's afterSwap records `bought[currentRound()][tx.origin] += tokenOut` for every qualifying buy, `settle()` scales a winner's prize by `balance / owed` when their IMD balance is below what they bought (`PrizeForfeited`), and `claim()` reverts with MustHoldToClaim if the balance drops below the amount held at settlement. The brief asks for the IMD to be burned, so the hook sends the output to 0xdEaD and the player never holds it. Consequences, with the published game: (a) a trader with no other IMD who tips the jar is placed on the board but their prize is scaled to 0; (b) a trader who already plays Sherwood and holds exactly what they bought (balance == owed, full prize) gets `owed` increased by the burned amount and their prize cut to balance/(balance+burned); nothing in this hook lets them opt out other than not trading QUIVER. The README documents (a) but not (b). This is a trust-gap between the hook's burn and the game's holdings rule, not an authorization bypass; it is reported so the requester can decide whether the spec ('top players split the ETH pot') is actually met by burning. Fix options need a scope decision: send the IMD to the player instead of burning (meets 'player gets on the board and can win'), or keep the burn and state in the README that a hook-made move can never collect a prize and reduces an existing player's prize.","line":163,"path":"src/SherwoodPlayerHook.sol","reproduction":"State (published Sherwood rules): Alice bought 1000 IMD on Sherwood this round and holds 1000 (bought=1000). Jar at 0.0049 ETH. Call: Alice swaps 0.01 ETH -> QUIVER (fee 0.0001 ETH tips the jar to 0.005). afterSwap -> executeBuyback -> Sherwood beforeSwap `_play(round, tx.origin=Alice, 0.005 ETH)` -> afterSwap `bought[round][Alice] += tokenOut` (say 50 IMD) -> hook takes the 50 IMD to 0xdEaD. At settle(): balance 1000 < owed 1050 -> `kept = prize * 1000 / 1050`, 4.76% of Alice's prize forfeited to the pot. Expected per the brief: trading QUIVER puts you on the board with a chance to win; actual: it can only lower what you win, and a newcomer with zero IMD wins nothing (balance 0 -> kept 0).","severity":"low","snippet":"        poolManager.take(Currency.wrap(IMD), BURN, output);","title":"Burned buyback IMD is still booked to the player's `bought` in Sherwood, so a hook-made player forfeits the prize and an existing Sherwood leader who trades QUIVER dilutes their own prize"},{"citation":"resolved","description":"Documented in the README and explicitly requested by the brief ('exact-input swap with no price limit'), so recorded as a trust assumption rather than a defect. Two consequences follow for the requester to accept knowingly: (1) the jar (other traders' fees) is sold at whatever price the Sherwood pool shows in that block; a searcher who sees the jar at >= 0.005 ETH can push the IMD price up, trigger the buyback with their own 1-wei QUIVER swap (any swap on the pool runs _attemptBuyback, fee 0 is fine), and sell back; only `delta.amount1() > 0` is checked. (2) The same 1-wei swap makes the searcher tx.origin and therefore the Sherwood player for a move funded entirely by other traders' fees, including after `redeemFees()` or an ETH donation to receive() lifts the balance over the threshold. Both are inherent to 'whoever's trade tips the jar'. No code change is proposed; if the requester wants protection, a minimum-output derived from the Sherwood pool's observed price would need to be added, which changes the agreed design.","line":158,"path":"src/SherwoodPlayerHook.sol","reproduction":"State: jar 0.0049 ETH. Searcher bundle: (1) buy IMD on the Sherwood pool to move price; (2) swap 1 wei QUIVER->ETH... any direction on the QUIVER pool (fee 0 wei); afterSwap runs _attemptBuyback only if balance >= 0.005, so first donate 0.0001 ETH to the hook via receive(); (3) sell IMD on Sherwood. Outcome: the jar's 0.005 ETH buys IMD at the manipulated price, the searcher is credited as the Sherwood player, loss is borne by the jar (the fee payers) and the IMD burned is less than at the fair price. Expected by the brief: exactly this behaviour (no price limit).","severity":"info","snippet":"            SwapParams(true, -int256(amount), TickMath.MIN_SQRT_PRICE + 1),","title":"Buyback is an exact-input swap with the protocol's extreme price bound and no minimum output; anyone can sandwich it, and anyone can snipe the player seat with a dust trade once the jar is full"}],"hash":"6d0132a1d379cf98ae7c4b1187f12864428d5e745aa4aa0ce834b026c2c41b9e","nodeId":"4411891d-d8da-4dfe-9696-ce47bcc4bdc9","outcome":"completed","summary":"The review is complete. No source files were changed; the findings are in `.imd-findings.json` and the one proof test is under `test/scratch/`.\n\n**Area covered: Access Control, Trust Gap, Asymmetry.** I mapped every entry point's guard (manager-only callbacks, self-only `executeBuyback`, public `redeemFees` and `receive`), diffed the paired paths (take vs mint, burn vs take, specified vs unspecified fee branches, `redeemFees` vs `_attemptBuyback` busy handling), and traced the delta accounting against v4-core's `Hooks.afterSwap` credit. The permission model itself holds: no escalation path, no unguarded writer of `busy`, claims can only flow back into the hook's own jar, and the published Sherwood v2 source confirms `tx.origin` attribution with a 0.001 ETH minimum below the hook's 0.005 ETH threshold.\n\n**Findings, most severe first:**\n\n- **Medium, gas cap (line 201).** The hook forwards at most 2,000,000 gas to the buyback no matter what the trader supplies. Sherwood v2 rescans the whole board inside every play when its leader cache is stale, with no gas break for boards up to 1,500 players. At roughly 350 to 400 players the buyback always runs out of gas, the jar keeps growing, and nothing can raise the constant. The scratch test `BuybackGasCap.t.sol` fails on the current code and passes with the clamp removed.\n- **Low, sync reset (line 154).** `executeBuyback` calls `sync(native)`, which erases a router's pre-synced ERC-20 reserves. A router that syncs and transfers before swapping reverts with `CurrencyNotSettled` whenever the buyback succeeds.\n- **Low, burn vs holdings rule (line 163).** Sherwood books the burned IMD to the player's `bought`, so a hook-made player forfeits the prize and an existing Sherwood leader who trades QUIVER cuts their own prize. The brief mandates burning, so this needs a scope decision rather than a silent fix.\n- **Info, unbounded buyback price (line 158).** Sandwichable and seat-snipeable by design; recorded as a trust assumption since the brief asks for no price limit.\n\n**Coverage:** all nine listed entry points have rows (two marked `finding`, seven `holds`), plus four invariant rows. None are `unreached`. Static-analysis leads were all checked and none promoted.\n\n**Not verified:** the live Sherwood and IMD bytecode on Robinhood Chain. The explorer and every RPC I tried were blocked, so the game behaviour is taken from the published commit the README links.","treeHash":null,"usage":{"cachedInputTokens":2500002,"inputTokens":546,"model":"claude-fable-5-1","outputTokens":50500,"runtime":"claude","turns":47,"wallClockMs":800048}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"62491201fe455e89","findings":[{"citation":"resolved","description":"Boundary: the external call poolManager.sync(address(0)) inside the nested self-call. Assumption: no other party has a pending ERC-20 sync when the buyback runs. Actual: PoolManager.sync(address(0)) calls CurrencyReserves.resetCurrency(), which clears the transient syncedCurrency/reserves slot owned by whoever synced last. A router that follows the documented v4 sequence sync(currency) -> transfer -> swap -> settle() (prefunding its ERC-20 input before calling swap) finds the slot reset by the time it calls settle(): _settle() then reads the native branch, paid = msg.value = 0, its token delta stays negative and unlock() reverts CurrencyNotSettled. The outer swap therefore fails, but only on the swap whose afterSwap runs the buyback (jar >= 0.005 ETH); the identical swap succeeds when the jar is below the threshold, and succeeds for a router that syncs after the swap (PoolDriver). For the affected router the failure repeats on every attempt until some other router's swap plays the jar down. The brief requires that a failing buyback never breaks the swap; here the buyback does not fail, the settlement of the swapper does. Since v4 cannot restore a cleared ERC-20 sync (re-syncing would read the post-transfer balance and credit 0), the hook cannot be made safe for that pattern; the README should state that routers must not keep a sync open across swap(), or the hook should settle its prepayment without resetting the synced currency (e.g. use its ETH claim instead of settle{value}).","line":154,"path":"src/SherwoodPlayerHook.sol","reproduction":"State: launch pool initialized and seeded as in test/helpers/HookFixture.sol; a router whose unlockCallback does sync(QUIVER); QUIVER.transfer(manager, 0.1e18); swap(exact-input sell 0.1 QUIVER); settle(); take(ETH). Step 1: jar empty -> sell succeeds, hook balance == (net+fee)/100. Step 2: vm.deal(hook, 0.005 ether) (any jar >= THRESHOLD) -> the same sell reverts with CurrencyNotSettled() because executeBuyback reset the router's sync. Step 3: same jar, PoolDriver (sync after swap) sells 0.1 QUIVER -> succeeds and game.plays()==1. Expected: the swap succeeds for every settlement order v4 permits, independent of the jar; actual: reverts only when the jar is full. Proof test test_prepayRouterSwapSucceedsWhenJarFull (self-contained, test/scratch/PrepaySyncProof.t.sol) fails on the current code with CurrencyNotSettled() and passes once the prepayment no longer resets the swapper's sync.","severity":"low","snippet":"        poolManager.sync(Currency.wrap(address(0)));","title":"executeBuyback's native sync() reset breaks any router holding an open ERC-20 sync across the swap, so the jar-tipping swap reverts with CurrencyNotSettled for that router"},{"citation":"resolved","description":"Numerical gap (precision x invariant): two formulas that the brief treats as one ('take 1% of the ETH side') use different bases. For an exact-input sell afterSwap charges fee = grossAMMOutput/100 (line 126), i.e. 1.000% of the ETH leaving the pool. For an exact-output sell beforeSwap charges fee = requestedNet/100 (line 97) and asks the AMM for requestedNet + fee, so the fee is 1/101 = 0.990% of the ETH leaving the pool. Likewise an exact-input buy pays 1% of total ETH spent while an exact-output buy pays 1% of the AMM input, i.e. 0.990% of total ETH spent. A trader can always choose the cheaper mode; the jar collects ~1% less than specified on those paths. The README documents the choice, but it contradicts a literal reading of the brief and makes the 1% rate mode-dependent. Fix: on specified-ETH paths reserve ceil(specified*1/99) (sell) / keep specified (buy) so that the fee is 1% of the gross ETH side on all four paths, or document the 0.99% effective rate as intended.","line":97,"path":"src/SherwoodPlayerHook.sol","reproduction":"Fixture test/helpers/HookFixture.sol at price 1:1 with 10000e18 liquidity. (a) exact-input sell of 0.1 QUIVER: AMM ETH out = 98,749,024,853,379,572 wei, fee = 987,490,248,533,795 wei (1.000% of gross), net to seller = 97,761,534,604,845,777 wei. (b) snapshot-revert, then exact-output sell requesting exactly that net 97,761,534,604,845,777 wei: fee = 977,615,346,048,457 wei (0.990% of the 98,739,149,950,894,234 wei gross the AMM paid), seller spends 99,989,999,901,259,875 wei QUIVER instead of 100,000,000,000,000,000 wei. Same net ETH to the seller, 9,874,902,485,338 wei (1.0%) less fee to the jar and 10,000,098,740,125 wei fewer tokens sold. Expected: identical 1% of the ETH side in both modes; actual: 0.990% vs 1.000%.","severity":"low","snippet":"            fee = amount / FEE_DENOMINATOR;","title":"Fee is 1% of the specified amount on specified-ETH paths but 1% of the AMM amount on unspecified paths, so exact-output sells and exact-output buys pay ~1% less hook fee than the exact-input form of t"},{"citation":"resolved","description":"Math precision: fee = amount / 100 floors, so every swap under-pays by up to 99 wei and any ETH leg below 100 wei pays nothing. Economically irrelevant (gas is many orders of magnitude larger than 99 wei), so no exploit; reported as a rounding-direction note only. If the author wants fees to round up: fee = (amount + 99) / 100, keeping the int128 bound check and the amountToSwap sign check (fee < amount still holds for amount >= 2; amount == 1 would need fee capped to 0).","line":126,"path":"src/SherwoodPlayerHook.sol","reproduction":"Exact-input buy with amountSpecified = -99: delta.amount0() == -99, hook balance stays 0, no claim minted. amountSpecified = -199 pays 1 wei (0.50%), -100 pays 1 wei exactly.","severity":"info","snippet":"            fee = amount / FEE_DENOMINATOR;","title":"Fee rounds down and truncates to zero for swaps under 100 wei of ETH; guide expects fees to round up"},{"citation":"resolved","description":"Boundary of the external call (gas). Published SherwoodV2.sol (commit 754206e, the README's reference) runs _resumeScan in _play: when _leaderStale[round] is set, _scan iterates the player list and only stops early when i >= atLeast && gasleft() < LEADER_SCAN_RESERVE (1,000,000), where atLeast = i + LEADER_SCAN_CHUNK (200) or the whole list when players <= MAX_LEADER_SCAN. Each iteration reads a player address and its points (two cold SLOADs, ~4.3k gas). Arithmetic: the self-call receives at most 2,000,000 gas; prepayment, swap entry and Sherwood's prelude use ~150k, leaving ~1.85M; a mandatory full scan of n players costs ~4.3k*n, so n >= ~430 stale players exhausts the budget before the swap executes, and even the 200-entry minimum (~860k) plus the 1M reserve leaves nothing for the pool swap, afterSwap and the IMD burn transfer when the list is long. A direct player's buy with a 30M gas limit completes the rescan, after which the hook's next attempt succeeds, so the failure is transient, but on each attempt the tipping swapper pays the forwarded gas and BuybackFailed is emitted. Not reproducible here (deployed bytecode unverified, constants MAX_LEADER_SCAN unknown); lead for the fork rehearsal. Mitigation if confirmed: raise BUYBACK_GAS (it is only a cap; callers still choose their gas limit) or skip the attempt when gasleft() is below a realistic floor instead of attempting with a budget that cannot succeed.","line":38,"path":"src/SherwoodPlayerHook.sol","reproduction":"State on Robinhood Chain: Sherwood round with _leaderStale[round] == true and >= ~430 players (or any list where the mandatory scan plus reserve exceeds ~1.85M gas). Any QUIVER swap with the jar >= 0.005 ETH: afterSwap forwards min(gasleft()-150k, 2,000,000) to executeBuyback, Sherwood's _scan runs out of gas, the self-call reverts, BuybackFailed is emitted and the jar stays full; the swapper paid the gas. Expected: an attempt that cannot succeed is skipped or the budget is sized to the game; actual: up to 2M gas burnt per triggering swap until an external buy refreshes the leader.","severity":"info","snippet":"    uint256 public constant BUYBACK_GAS = 2_000_000;","title":"Sherwood v2's leader rescan can need more than the fixed 2,000,000 gas buyback budget when the leader is stale and the round has several hundred players, making the triggering swapper pay up to ~2M ga"}],"hash":"747f191ce96dc718678dc7d811e6c81f60af79e7a8614a4f8c03aab81671166d","nodeId":"86b6b553-fc40-4d93-be8b-646026284ccd","outcome":"completed","summary":"The review is complete. The findings and coverage record are in `.imd-findings.json` at the repository root. No source file was changed; only `test/scratch/` holds my scratch and proof tests.\n\n**Outcome.** The fee and delta arithmetic in `SherwoodPlayerHook` holds up under the Math Precision, Boundary and Numerical Gap checks. I found no critical or high defect. Four findings are recorded, two low and two informational, each with a concrete input or state.\n\n**Findings**\n\n1. **Low, line 154.** The buyback's `poolManager.sync(address(0))` clears the transient sync slot of whoever synced last. A router that follows the sequence sync, transfer, swap, settle for its ERC-20 input has its settlement credited as zero and the whole swap reverts with `CurrencyNotSettled`, but only on the swap whose jar is at or above the threshold. The same swap succeeds with the jar below the threshold or with a sync-after-swap router. A self-contained Foundry proof fails on the current code and is attached.\n2. **Low, line 97.** The fee base differs by swap mode. Exact-input sells and buys pay a full 1% of the gross ETH side, while exact-output sells and buys pay 1/101 of it, about 0.990%. For the same net ETH to a seller the jar receives 1% less fee on the exact-output path. Numbers from the fixture are in the reproduction.\n3. **Info, line 126.** The fee floors, so swaps under 100 wei of ETH pay nothing. Gas dwarfs the loss, so it is a rounding-direction note only.\n4. **Info, line 38.** From the published Sherwood v2 source, the leader rescan loop runs at least 200 entries and keeps going until 1,000,000 gas remains. With a stale leader and a few hundred players it can exceed the fixed 2,000,000 gas budget, so the tipping swapper pays for a failed attempt until an external buy refreshes the leader. Not reproducible locally. It is a lead for the fork rehearsal.\n\n**Coverage.** All nine listed entry points have a row. Both token transfers, `receive`, `redeemFees` and `unlockCallback` hold. `beforeSwap`, `afterSwap` and `executeBuyback` carry findings. Three invariant rows cover zero hook deltas, the int128 fee bound, and every static-analysis lead, each of which I traced and rejected.\n\n**Checked and found sound.** The absolute-value expression for `int256.min`, the hook delta sign conventions against v4-core's `Hooks.beforeSwap` and `afterSwap`, the PartialFill equality against `SwapMath` full-consumption arithmetic in all four modes, the strict balance equality in the self-call, the claim mint and redeem accounting, and that the 0.005 ETH threshold clears Sherwood's 0.001 ETH minimum buy.\n\n**Not reached.** Deployed Sherwood bytecode on Robinhood Chain, which the README also states was not verified, and the exact value of Sherwood's `MAX_LEADER_SCAN` constant.","treeHash":null,"usage":{"cachedInputTokens":1815279,"inputTokens":514,"model":"claude-fable-5-1","outputTokens":43260,"runtime":"claude","turns":30,"wallClockMs":664728}},{"artifacts":[],"attempt":1,"bundleHash":"72401d526d8b3891c7eecb955b91ad61fc4a605e282a01088332d198899d72e3","device":"5c1e4764d4e20ef5","findings":[],"hash":"9b98bccd37a649e5af698892c53f6277343eb0f54148696171a44b4bea07610e","nodeId":"e0ebd79e-3364-4bfb-a647-18977e0bd4f5","outcome":"completed","summary":"The existing launch.json already satisfies every criterion, so no change was needed and the working tree stays clean.\n\n**What I verified**\n\n- **Contract names** match the repository: hook `SherwoodPlayerHook`, token `Quiver` with name \"Sherwood Player\", symbol \"QUIVER\", 18 decimals.\n- **Constructor arguments** match the hook's declaration order, pool manager then token, written as \"$poolManager\" and \"$token\". The hook has no owner, factory or launch-number parameter, so no other placeholder applies.\n- **Permissions** match what getHookPermissions returns exactly: beforeInitialize, beforeSwap, afterSwap, beforeSwapReturnDelta, afterSwapReturnDelta.\n- **Pool fields** are the launch's own: native ETH as the zero-address paired currency, fee 12500, tick spacing 60, and the one-to-one initial price. The hook's own constants accept that fee and spacing in beforeInitialize.\n- **Notes** are one string of 2139 characters, under the 4000 cap, and every address in the manifest is lowercase. The first field is `\"kind\": \"univ4_hook\"`.\n\n**Checks run**\n\n```\nforge build: ok (one lint note only)\nFOUNDRY_FUZZ_SEED=0xee01…42cd forge test: 31 passed, 0 failed\n```\n\nNo file was modified, and no findings were attached to this revision that called for a manifest change.","treeHash":"e1821c959f89003ed88b76fdc5f75fc3b60d9a78","usage":{"cachedInputTokens":289990,"inputTokens":194,"model":"claude-fable-5-1","outputTokens":2616,"runtime":"claude","turns":7,"wallClockMs":54674}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"a4c81f495eb81dd0","findings":[{"citation":"resolved","description":"New code read as new. _attemptBuyback returns early at line 204 when gasleft() <= GAS_RESERVE (150,000), but between that read and the checked subtraction at line 216 the function now spends gas: the exttload staticcall for getSyncedCurrency() (line 208), the cold SSTORE of busy = true (line 212, ~22,100 gas) and abi.encodeCall. When gasleft() at line 204 was in (150,000, ~173,000], gasleft() at line 216 is below 150,000 and the checked subtraction reverts with Panic(0x11). v4's Hooks.callHook wraps it as WrappedError(hook, afterSwap.selector, Panic(0x11), HookCallFailed) and the whole swap reverts. The design (README, line 204 comment) is that low remaining gas skips the attempt and the swap succeeds; instead there is a band of gas limits, just above the band where the skip works, in which every swap whose fee tips the jar reverts. It is not a loss of funds (the transaction is atomic and the trader retries with a different limit), but it contradicts 'the swap still succeeds' and is exactly the region a wallet can land in after adding its buffer to an eth_estimateGas result, which returns the skip-path minimum (~123k on the fixture). The window existed in the previous round too (busy SSTORE alone) and the exttload call widened it; I did not catch it then. Fix: read gasleft() once right before the subtraction and skip (resetting busy) when it is <= GAS_RESERVE, e.g. `uint256 left = gasleft(); if (left <= GAS_RESERVE) { busy = false; return; } uint256 budget = left - GAS_RESERVE;`, or do the exttload/busy work before the line 204 check. Verified: with that patch applied to a copy, the proof passes and the window disappears.","line":216,"path":"src/SherwoodPlayerHook.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {Quiver} from \"src/Quiver.sol\";\nimport {SherwoodPlayerHook} from \"src/SherwoodPlayerHook.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\nimport {IHooks} from \"v4-core/src/interfaces/IHooks.sol\";\nimport {IUnlockCallback} from \"v4-core/src/interfaces/callback/IUnlockCallback.sol\";\nimport {Hooks} from \"v4-core/src/libraries/Hooks.sol\";\nimport {Currency} from \"v4-core/src/types/Currency.sol\";\nimport {PoolKey} from \"v4-core/src/types/PoolKey.sol\";\nimport {BalanceDelta} from \"v4-core/src/types/BalanceDelta.sol\";\nimport {ModifyLiquidityParams, SwapParams} from \"v4-core/src/types/PoolOperation.sol\";\nimport {TickMath} from \"v4-core/src/libraries/TickMath.sol\";\nimport {IERC20} from \"@openzeppelin/contracts/token/ERC20/IERC20.sol\";\n\n/// @dev Minimal prefunded router: swap, then settle both deltas.\ncontract Router is IUnlockCallback {\n    IPoolManager immutable manager;\n\n    constructor(IPoolManager m) {\n        manager = m;\n    }\n\n    function modify(PoolKey memory key, int24 lower, int24 upper, int256 liquidity) external {\n        manager.unlock(abi.encode(true, key, abi.encode(lower, upper, liquidity)));\n    }\n\n    function swap(PoolKey memory key, SwapParams memory params) external {\n        manager.unlock(abi.encode(false, key, abi.encode(params)));\n    }\n\n    function unlockCallback(bytes calldata data) external returns (bytes memory) {\n        require(msg.sender == address(manager));\n        (bool liquidity, PoolKey memory key, bytes memory args) = abi.decode(data, (bool, PoolKey, bytes));\n        BalanceDelta delta;\n        if (liquidity) {\n            (int24 lower, int24 upper, int256 amount) = abi.decode(args, (int24, int24, int256));\n            (delta,) = manager.modifyLiquidity(key, ModifyLiquidityParams(lower, upper, amount, bytes32(0)), \"\");\n        } else {\n            delta = manager.swap(key, abi.decode(args, (SwapParams)), \"\");\n        }\n        _settle(key.currency0, delta.amount0());\n        _settle(key.currency1, delta.amount1());\n        return \"\";\n    }\n\n    function _settle(Currency currency, int128 amount) private {\n        if (amount < 0) {\n            uint256 owed = uint256(-int256(amount));\n            manager.sync(currency);\n            if (currency.isAddressZero()) {\n                manager.settle{value: owed}();\n            } else {\n                require(IERC20(Currency.unwrap(currency)).transfer(address(manager), owed));\n                manager.settle();\n            }\n        } else if (amount > 0) {\n            manager.take(currency, address(this), uint256(uint128(amount)));\n        }\n    }\n\n    receive() external payable {}\n}\n\n/// @notice A swap that tips the jar must never revert because of the hook's own gas bookkeeping.\n/// On the current code, transaction gas limits in a ~23k-wide window (about 250,500 to 273,500 on\n/// this fixture) make `gasleft() - GAS_RESERVE` in _attemptBuyback underflow: the early return at\n/// `gasleft() <= GAS_RESERVE` passed, then the exttload call and the cold `busy` SSTORE consumed\n/// gas, and the checked subtraction reverts with Panic(0x11), which v4 wraps in HookCallFailed and\n/// the whole swap reverts. Below the window the attempt is skipped and the swap succeeds; above it\n/// the attempt runs. Fix: re-read gasleft() right before the subtraction (or guard it) so a low\n/// budget skips instead of reverting.\ncontract GasReserveUnderflowTest is Test {\n    IPoolManager manager;\n    Quiver token;\n    SherwoodPlayerHook hook;\n    Router router;\n    PoolKey key;\n    address constant PLAYER = address(0xA11CE);\n\n    function setUp() public {\n        manager = new PoolManager(address(this));\n        token = new Quiver();\n        hook = _deployHook(manager, address(token));\n        router = new Router(manager);\n        key = PoolKey(Currency.wrap(address(0)), Currency.wrap(address(token)), 12500, 60, IHooks(address(hook)));\n        manager.initialize(key, 79228162514264337593543950336);\n        vm.deal(address(router), 100_000 ether);\n        token.transfer(address(router), 500_000_000 ether);\n        router.modify(key, -60000, 60000, 10000 ether);\n        // The Sherwood pool is left uninitialized: a buyback attempt that runs simply fails and is\n        // swallowed. The defect is in the budget arithmetic before any attempt.\n    }\n\n    function test_noGasLimitMakesATippingSwapRevertWithArithmeticPanic() public {\n        vm.deal(address(hook), 0.005 ether);\n        uint256 snap = vm.snapshotState();\n        uint256 successes;\n        for (uint256 g = 150_000; g <= 400_000; g += 500) {\n            vm.revertToState(snap);\n            snap = vm.snapshotState();\n            vm.prank(PLAYER, PLAYER);\n            try router.swap{gas: g}(key, SwapParams(true, -0.5 ether, TickMath.MIN_SQRT_PRICE + 1)) {\n                ++successes;\n            } catch (bytes memory data) {\n                assertFalse(_containsPanic11(data), string.concat(\"swap reverted with Panic(0x11) at gas limit \", vm.toString(g)));\n            }\n        }\n        assertGt(successes, 0, \"fixture never completes a swap\");\n    }\n\n    function _containsPanic11(bytes memory data) private pure returns (bool) {\n        // Panic(uint256) selector 0x4e487b71 followed by code 0x11.\n        bytes memory needle = abi.encodeWithSelector(0x4e487b71, uint256(0x11));\n        if (data.length < needle.length) return false;\n        for (uint256 i; i + needle.length <= data.length; ++i) {\n            bool same = true;\n            for (uint256 j; j < needle.length; ++j) {\n                if (data[i + j] != needle[j]) {\n                    same = false;\n                    break;\n                }\n            }\n            if (same) return true;\n        }\n        return false;\n    }\n\n    function _deployHook(IPoolManager m, address t) internal returns (SherwoodPlayerHook deployed) {\n        bytes memory code = abi.encodePacked(type(SherwoodPlayerHook).creationCode, abi.encode(m, t));\n        bytes32 hash = keccak256(code);\n        for (uint256 i; i < 200000; ++i) {\n            address predicted =\n                address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xff), address(this), bytes32(i), hash)))));\n            if (uint160(predicted) & Hooks.ALL_HOOK_MASK != 0x20cc) continue;\n            bytes32 salt = bytes32(i);\n            address at;\n            assembly (\"memory-safe\") {\n                at := create2(0, add(code, 32), mload(code), salt)\n            }\n            require(at != address(0));\n            return SherwoodPlayerHook(payable(at));\n        }\n        revert(\"salt exhausted\");\n    }\n}","reproduction":"Fixture test/helpers/HookFixture.sol (PoolDriver router, 1:1 price, 10000e18 liquidity). vm.deal(address(hook), 0.005 ether); vm.prank(PLAYER, PLAYER); driver.swap{gas: G}(key, SwapParams(true, -0.5 ether, MIN_SQRT_PRICE+1)). Sweeping G in 500-gas steps: G in [100,000, 123,500) reverts with empty data (plain out of gas); G in [123,500, 250,500) succeeds with the attempt skipped (game.plays()==0); G in [250,500, 273,500) REVERTS with WrappedError(hook, 0xb47b2fb1, 0x4e487b71...0011, 0xa9e35b2f), i.e. Panic(0x11) from line 216; G in [273,500, 500,000) succeeds with a failed attempt; G >= 500,000 succeeds and plays. Expected per the brief and the line 204 comment: no gas limit at which the trade itself reverts because of the hook's budget arithmetic; a short budget skips. Actual: a ~23k-wide band of limits reverts the user's swap. Proof test/scratch/GasReserveUnderflow.t.sol (self-contained, Sherwood pool left uninitialized so any attempt just fails and is swallowed) sweeps 150k..400k and fails on this code at G=250,500 with 'swap reverted with Panic(0x11)'; it passes once the subtraction is guarded.","severity":"low","snippet":"        uint256 budget = gasleft() - GAS_RESERVE;","title":"Checked gasleft() - GAS_RESERVE in _attemptBuyback underflows for a ~23k-gas window of transaction gas limits, so a jar-tipping swap reverts with Panic(0x11) instead of skipping the attempt"},{"citation":"resolved","description":"Same observation as the independent tester's info finding (write_foundry_tests). The fix for finding bd9fb3ea is correct and the right trade-off inside this design (v4 has one transient synced-currency slot and the hook cannot restore a router's ERC-20 sync), and it is documented in README and SECURITY.md. The consequence for the requester to accept knowingly: a venue whose router always does sync(token) -> transfer -> swap -> settle (a legitimate v4 order) can never put its own traders on the Sherwood board; their fees accumulate and the jar is played, as a larger single move with a looser effective price, by whoever next trades through a router that leaves the slot clear. The only design that would also credit those traders is to hold fees as ERC-6909 ETH claims and prepay the buyback by burning claims (no sync or settle{value} needed), which is a scope decision, not a defect in this code. No change requested.","line":209,"path":"src/SherwoodPlayerHook.sol","reproduction":"test/SherwoodAdversarial.t.sol:test_routerThatSyncsBeforeEverySwapStillSettlesAndTheJarWaits (passes on this code): PLAYER makes two 0.5 ETH exact-input buys through SettlementProbe with preSync=true; each emits BuybackDeferred(PLAYER, 0.005 ether / 0.01 ether), game.plays() stays 0, jar holds 0.01 ether, target pool untouched. A later address's 100-wei buy through PoolDriver plays 0.01 ether + 1 wei with game.player() == later, not PLAYER. Brief: 'whoever's trade tips the jar over becomes the player'; actual: the later trader is.","severity":"info","snippet":"            emit BuybackDeferred(tx.origin, amount);","title":"Deferral for pre-syncing routers (the fix for the prior CurrencyNotSettled finding) means the trader whose swap tips the jar through such a router is never the player; the next clear-slot trader is"}],"hash":"a8027a12dd797e14913f2f25f6117f91416152ef99cdcb44b33456d6d102e2fa","nodeId":"87e3ac76-703e-4bd8-8c6c-7cf0076fe49c","outcome":"completed","summary":"The findings file exists and is valid. It is hidden from `git status` by the repository's own exclude rule, and no other file was created or changed.\n\n**Both prior findings are fixed and confirmed.**\n\n- **Sync clobber (medium, bd9fb3ea):** `_attemptBuyback` now reads the manager's transient synced-currency slot, the same slot `_settle` consults, and defers with `BuybackDeferred` when a currency is synced. My original reproduction, a router doing sync, transfer, swap, settle, take with the jar at the threshold, now completes with all deltas settled. The author's test covers it exactly, and the full suite of 63 tests passes.\n- **Fee base (low, 063c5bea):** exact-output legs now charge `amount/99`, which equals `floor((amount+fee)/100)` exactly under floor division, so every mode pays 1% of the gross ETH leg. The partial-fill check uses the same helper, and the fuzz conservation test asserts it.\n\n**One new reproducible defect, rated low, with a proof.** Between the early return at the `gasleft() <= GAS_RESERVE` check and the checked subtraction `gasleft() - GAS_RESERVE`, the function now spends gas on the exttload call and the cold `busy` store. For a band of transaction gas limits about 23k wide, roughly 250,500 to 273,500 on the fixture, the subtraction underflows and the trader's swap reverts with a wrapped Panic(0x11) instead of skipping the attempt. Below that band the swap succeeds with the attempt skipped. Above it the swap succeeds with the attempt running. No funds are lost, but it breaks \"the swap still succeeds\" in exactly the region a wallet's buffer over an estimateGas result can land. The embedded proof sweeps gas limits, fails on this code at the first limit in the band, and passes on a copy patched to re-read `gasleft()` right before the subtraction. The band also existed last round with the `busy` store alone, which I missed then; the new call widened it.\n\n**One info note** records the tester's observation that a trader whose swap tips the jar through a router that pre-syncs its ERC-20 input is never the player, since the deferral hands the jar to the next clear-slot trader. That is the documented trade-off of the fix I asked for, and no change is requested.\n\nCoverage answers all nine entry points, plus rows for the two settled findings and the hook-delta invariant. The specialists' other leads from last round, the Sherwood gas cap and the burn-forfeit interaction, depend on live Sherwood code not in the tree and remain dropped as before.","treeHash":null,"usage":{"cachedInputTokens":1341761,"inputTokens":418,"model":"claude-fable-5-1","outputTokens":30383,"runtime":"claude","turns":27,"wallClockMs":504144}},{"artifacts":[],"attempt":1,"bundleHash":"c21941c4a6872eedbc62735c750b2e9b0088853861e88531a52a95aad9b377b7","device":"d6981b1e15db2f76","findings":[],"hash":"b35012b63149c60a55f91f834cc0d35e6cff85cf536d5c0f98ef29be3b628eac","nodeId":"e5c8a5eb-8cec-488d-a823-d5c0a524233a","outcome":"completed","summary":"Implemented QUIVER and the ownerless hook with ETH fees, threshold buybacks, IMD burning, deferred fee claims, and failure retries.\n\nVerified: `forge build`, `forge fmt --check`, and all 28 tests pass with the supplied fuzz seed.\n\nDeployment and operational details are in [README.md](README.md). Key caveat: Sherwood’s holding rules may forfeit prizes when purchased IMD is burned. Live-chain fork validation remains outstanding.","treeHash":"ee2edd0cf5b3697b678eee302b72ed6233bd0cb6","usage":{"cachedInputTokens":1952640,"inputTokens":115793,"model":"gpt-6.1-sol","outputTokens":23751,"runtime":"codex","turns":6,"wallClockMs":629698}},{"artifacts":[],"attempt":1,"bundleHash":"4516c3dfcbc87cebac5387f04775b5ffb742605addb2f98fb2f6291bf2ed8399","device":"2a9662a76cb5f51d","findings":[],"hash":"d0b0a5a676165dd0980f592bc9593b0906c3d42ceab1384fc5a1456d66911a45","nodeId":"e5c8a5eb-8cec-488d-a823-d5c0a524233a","outcome":"completed","summary":"Both findings reproduced and are fixed. Build, the full suite with the verifier's fuzz seed, and `forge fmt --check` all pass.\n\n**Finding 1 (medium, synced-currency reset): fixed.** I reproduced it with a router that syncs and transfers its QUIVER input before the swap and settles afterwards. With the jar at the threshold the sell reverted `CurrencyNotSettled`; with an empty jar it passed. The manager cannot be re-synced to the router's pre-transfer reserves, so nothing inside `executeBuyback` can repair the slot. The hook now reads the synced currency through TransientStateLibrary before the self-call. When a currency is synced it emits `BuybackDeferred` and returns, leaving the jar intact for the next swap that arrives with the slot clear. The prepaying router now sells successfully on a full jar, and a following ordinary swap plays the whole jar.\n\n**Finding 2 (low, fee base by swap mode): fixed.** Exact-output forms charged 1/101 of the gross ETH leg while exact-input forms charged 1/100. A single `_fee` helper now divides by 100 on exact input and by 99 on exact output, which equals 1% of the gross leg exactly under floor division. The afterSwap partial-fill check uses the same helper.\n\nFiles changed:\n- `src/SherwoodPlayerHook.sol`: deferral check, new event, fee helper.\n- `test/helpers/PrepayRouter.sol`: new pre-syncing router.\n- `test/SherwoodPlayerHook.t.sol`: two prepaying-router tests, a cross-mode fee-equivalence test, and updated exact-output and fuzz assertions.\n- `README.md` and `SECURITY.md`: fee table, deferral behaviour, monitoring, and the limitation that a venue whose router always pre-syncs cannot tip the jar itself.\n- `.imd-responses.json`: both findings answered as fixed.\n\nTest results:\n\n| Suite | Result |\n| --- | --- |\n| 33 tests across 4 suites, seed 0xee01…42cd | all pass |\n\nNothing was committed, per the task rules.","treeHash":"8b71e3b9757b1af398cb69093efa0cc2b3aa0c17","usage":{"cachedInputTokens":1435504,"inputTokens":546,"model":"claude-fable-5-1","outputTokens":25709,"runtime":"claude","turns":38,"wallClockMs":357024}},{"artifacts":[],"attempt":1,"bundleHash":"1cecca0fce59180a4b017534e5a86d69c399525255c897c01db7dc74d2a548f4","device":"d7e6877fd9e388bb","findings":[{"description":"The brief says whoever's trade tips the jar over becomes the player. When the router has a currency in the PoolManager's synced-currency slot during the swap, _attemptBuyback emits BuybackDeferred and returns. The next swap that arrives with a clear slot spends the jar and attributes the move to its own tx.origin. This is a deliberate, documented trade-off (README 'Routers that sync before swapping'): without it the native prepayment's sync(address(0)) would make such a router revert CurrencyNotSettled. Tests assert the documented behaviour. It is reported so the requester knows that venues whose routers always pre-sync can never put their traders on the board.","line":208,"path":"src/SherwoodPlayerHook.sol","reproduction":"test/SherwoodAdversarial.t.sol:test_routerThatSyncsBeforeEverySwapStillSettlesAndTheJarWaits. PLAYER makes two 0.5 ETH exact-input buys through a router that calls sync(QUIVER) before each swap. Each emits BuybackDeferred(PLAYER, 0.005 ether / 0.01 ether), game.plays() stays 0, and the jar holds 0.01 ether. A later trader's 100-wei buy through a plain router plays 0.01 ether + 1 wei as that later trader. Per the brief, PLAYER should be the player. Actually the later trader is.","severity":"info","title":"A trade that tips the jar through a pre-syncing router does not make that trader the player"}],"hash":"f1d1007ecb2f9db118079340391403ee0d7f6c2a6cd7c6f9f31b1c3269a4c74b","nodeId":"a427b596-6fb6-486d-8de1-5c75cac1c630","outcome":"completed","summary":"I updated my earlier tests to match the revised implementation, and all 63 tests now pass with `forge build` and `forge test` under the verifier's fuzz seed (0 failed, 0 skipped).\n\n**Why five tests were failing.** The implementer changed two things since my last round, and my tests still expected the old behaviour:\n- **Fee:** exact-output swaps now charge `amount / 99` instead of `/ 100`, so the fee is 1% of the total ETH leg (the ETH a buyer pays including the fee, or what the pool pays out before the fee comes off a sell).\n- **Deferral:** if the trader's router has already called `sync` on a currency during the swap, the hook now skips the buyback and emits `BuybackDeferred`. The jar is kept and a later swap spends it.\n\n**What I changed, all under `test/`:**\n- **Fee checks** (`SherwoodAdversarial.t.sol`, `SherwoodDeferred.t.sol`, `helpers/JarHandler.sol`): the expected fee is now 1% of that total, rounded down. The tests work out that total from the PoolManager's `Swap` event and the trader's balance change, without copying the hook's `/100` and `/99` formulas. One new check confirms the fee is added on top of buys and taken out of sells.\n- **Pre-sync router tests** (`SherwoodAdversarial.t.sol`): I split my old two-buybacks test in two.\n  - One test keeps the plain router and checks that two buybacks can happen inside one transaction.\n  - The new test uses a router that syncs before each swap. It checks that the router still settles, each full-jar swap emits `BuybackDeferred` with the right amount, the Sherwood pool is untouched and the whole jar is kept. The next ordinary trader then plays the entire jar.\n  - A third test checks that nothing is deferred below 0.005 ETH.\n- **Invariant tests:** the handler's `trade` now randomly picks routers with or without the pre-swap sync. When the jar is full, a pre-synced swap must defer and keep every wei. The fixed scenario test now walks through a deferral too. I split `trade` into helpers to get past a \"stack too deep\" compiler error.\n- **`test/README.md`:** updated for the new fee rule and the deferral tests.\n\n**One info-level finding, in `.imd-findings.json`.** The brief says whoever's trade tips the jar becomes the player. A trader whose router syncs before swapping never does: their swap is deferred and the next trader through an ordinary router plays the whole jar instead. The implementer documents this as deliberate, because without the deferral those routers' transactions would revert. My tests check that documented behaviour, and the new pre-sync test shows it happening. The consequence for the requester: trading venues whose routers always sync first can never put their traders on the Sherwood board.\n\nI didn't change anything outside `test/` except that findings file, and I didn't commit.","treeHash":"abdbeb7f9fce140a0ed34248ba8fe7ff54b5c096","usage":{"cachedInputTokens":1572951,"inputTokens":42,"model":"claude-opus-5-5","outputTokens":19835,"runtime":"claude","turns":25,"wallClockMs":260939}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"be85220723d9ce9d","findings":[{"citation":"resolved","description":"Merged from audit_flow, audit_permissions and audit_math (same root cause). executeBuyback runs inside the swapper's unlock, mid-swap. PoolManager.sync(address(0)) (lib v4-core PoolManager.sol:280-282) calls CurrencyReserves.resetCurrency(), wiping the single transient synced-currency slot. A router that pays its ERC-20 input before swap (sync(QUIVER) -> transfer -> swap -> settle()) then has settle() take the native branch with msg.value 0, crediting nothing; unlock reverts CurrencyNotSettled. The buyback only commits on success, so this breaks the user's swap exactly when the jar is full and stays broken for that router until another router spends the jar, contrary to 'the swap still succeeds'. Fix: read the synced currency via TransientStateLibrary.getSyncedCurrency(poolManager) and skip the attempt (retry next swap) when it is non-zero.","line":154,"path":"src/SherwoodPlayerHook.sol","reproduction":"Fixture HookFixture; vm.deal(hook, 0.005 ether). Router unlockCallback: manager.sync(QUIVER); QUIVER.transfer(manager, 0.1e18); manager.swap(key, SwapParams(false, -0.1e18, MAX_SQRT_PRICE-1), \"\"); manager.settle(); manager.take(ETH, router, out). Expected: sell succeeds. Actual: afterSwap -> _attemptBuyback -> executeBuyback -> sync(address(0)) resets slot; buyback succeeds; router settle() pays 0; unlock reverts CurrencyNotSettled(). Same call with hook balance < 0.005 ETH succeeds.","severity":"medium","snippet":"        poolManager.sync(Currency.wrap(address(0)));","title":"executeBuyback's sync(address(0)) clears the outer router's synced currency, so a prepaying router's swap reverts CurrencyNotSettled whenever the buyback succeeds"},{"citation":"resolved","description":"On native-specified exact-output sells, beforeSwap charges specified/100 and the AMM delivers specified+fee, so the fee is 1/101 of the ETH leaving the pool; afterSwap's exact-input sell path (line 126) charges 1% of the gross ETH. Traders can pick the cheaper mode; the jar collects ~1% less than the brief's '1% of the ETH side' on those paths.","line":97,"path":"src/SherwoodPlayerHook.sol","reproduction":"HookFixture, 1:1 price, 10000e18 liquidity. (a) exact-input sell 0.1e18 QUIVER: gross ETH out 98,749,024,853,379,572, fee 987,490,248,533,795 (1.000%). (b) revert, exact-output sell for net 97,761,534,604,845,777 wei: fee 977,615,346,048,457 = 0.990% of gross 98,739,149,950,894,234. Same net to seller, ~9.87e12 wei less to the jar.","severity":"low","snippet":"            fee = amount / FEE_DENOMINATOR;","title":"Hook fee base differs by swap mode: exact-output sells/buys pay 1% of the net amount (0.990% of the ETH side) while exact-input forms pay 1.000%"}],"hash":"fad57b890d2f11cc719a565190384c8d81e366a21e352f51532e3ed332c94a6a","nodeId":"87e3ac76-703e-4bd8-8c6c-7cf0076fe49c","outcome":"completed","summary":"The review kept two findings and dropped the rest. Both are in `.imd-findings.json`, along with a coverage row for each of the 9 entry points. I ran no Foundry test. The first finding is checked against the v4-core source; the second rests on numbers from the math specialist that I did not re-run. Neither finding carries a proof file, since proofs are only required for critical or high findings.\n\n**Kept:**\n1. **Medium: the buyback can make a valid swap revert** (`src/SherwoodPlayerHook.sol:154`). Three specialists reported this, so it is merged into one finding. `executeBuyback` calls `poolManager.sync(address(0))`, which in v4-core clears the record of which currency the trader's router has paid in (`PoolManager.sol:280-282`). Some routers pay their tokens in before calling `swap()` and settle afterwards. With the slot cleared, their `settle()` credits nothing and the whole transaction reverts with `CurrencyNotSettled`. This happens only when the jar is full and the buyback works, which breaks \"the swap still succeeds\". It keeps happening for that router until another router's swap spends the jar. Fix: if a currency is already synced, skip the buyback and retry on a later swap.\n2. **Low: the fee depends on how the swap is specified** (`src/SherwoodPlayerHook.sol:97`). On exact-output swaps, the 1% is taken from the net amount, so the jar gets about 0.99% of the ETH side instead of 1%. Traders can pick the cheaper form.\n\n**Dropped:**\n- **2M gas cap and Sherwood's board rescan:** whether the buyback runs out of gas depends on the live Sherwood contract and its board size, which I can't reproduce here.\n- **Burned IMD costing the player their prize:** this follows from Sherwood's own holding rule combined with the burn the brief explicitly asks for.\n- **No price limit on the buyback (sandwich risk):** the brief asks for an exact-input swap with no price limit, and the README documents it.\n- **Rounding, wallet gas estimates, and the static-analysis leads:** none of them showed a reproducible defect.\n\n**Coverage:** the three `Quiver` ERC-20 functions, `receive()`, `redeemFees()` and `unlockCallback()` hold. `afterSwap` and `executeBuyback` point to finding 1, and `beforeSwap` to finding 2.","treeHash":null,"usage":{"cachedInputTokens":236720,"inputTokens":8,"model":"claude-opus-5-5","outputTokens":4171,"runtime":"claude","turns":4,"wallClockMs":47889}}],"verification":[{"checks":[{"durationMs":4447,"exitCode":0,"name":"build","output":"Compiling 85 files with Solc 0.8.26\nSolc 0.8.26 finished in 4.35s\nCompiler run successful!\n","passed":true},{"durationMs":4459,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 8 tests for test/Quiver.t.sol:QuiverTest\n[PASS] testFuzz_balanceFailureDoesNotConsumeAllowance(uint96) (runs: 1000, μ: 177029, ~: 176942)\n[PASS] testFuzz_transferAndAllowance(uint96) (runs: 256, μ: 180184, ~: 180895)\n[PASS] test_infiniteApprovalAndFullSupplyRoundTrip() (gas: 189241)\n[PASS] test_metadataAndSupply() (gas: 51529)\n[PASS] test_noMintOrAdmin() (gas: 51963)\n[PASS] test_transferAndApproveRejectZeroRecipients() (gas: 73486)\n[PASS] test_unapprovedCallerCannotMoveAnotherHoldersTokens() (gas: 54264)\n[PASS] test_zeroTransfersAndSelfTransfersCannotChangeSupplyOrBalances() (gas: 91262)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 95.10ms (190.44ms CPU time)\n\nRan 4 tests for test/SherwoodDeferred.t.sol:SherwoodDeferredTest\n[PASS] testFuzz_firstBuyClaimIsBackedAndRedemptionIsIdempotent(uint96,bool) (runs: 1000, μ: 377054, ~: 376549)\n[PASS] test_batchAccumulatesClaimsUntilTheRouterSettles() (gas: 1921017)\n[PASS] test_nextTraderRealizesDeferredFeesAndPlays() (gas: 687934)\n[PASS] test_prepaidBuybackWorksWithNoManagerETHBeforeTheTrade() (gas: 592985)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 112.48ms (107.77ms CPU time)\n\nRan 24 tests for test/SherwoodPlayerHook.t.sol:SherwoodPlayerHookTest\n[PASS] testFuzz_feeConservation(bool,bool,uint96) (runs: 256, μ: 245586, ~: 245710)\n[PASS] test_donationsIncludedAndRedemptionCannotTriggerBuyback() (gas: 465264)\n[PASS] test_exactInputBuyFee() (gas: 193311)\n[PASS] test_exactInputSellFee() (gas: 186410)\n[PASS] test_exactOutputBuyFee() (gas: 183976)\n[PASS] test_exactOutputSellFee() (gas: 182392)\n[PASS] test_exactThresholdTriggers() (gas: 420285)\n[PASS] test_factoryZeroLiquidityFeeCollection() (gas: 286196)\n[PASS] test_failedBurnRollsBackGameAndSettlement() (gas: 798305)\n[PASS] test_failureKeepsJarAndNextTraderRetries() (gas: 683900)\n[PASS] test_freshTokenOnlyPoolDefersFeeWithoutReverting() (gas: 9477724)\n[PASS] test_gameOutOfGasDoesNotRevertTrade() (gas: 2182943)\n[PASS] test_hookHasNoOwnerOrAdmin() (gas: 70416)\n[PASS] test_laterSwapAutomaticallyRedeemsDeferredFee() (gas: 9576970)\n[PASS] test_missingSherwoodPoolIsRetriable() (gas: 9646649)\n[PASS] test_partialClaimRedemptionAndNoKeeperPlayer() (gas: 9515290)\n[PASS] test_permissionsAndInitialization() (gas: 14076)\n[PASS] test_reentrantRedemptionIsIsolated() (gas: 748034)\n[PASS] test_specifiedNativePartialFillRevertsAtomically() (gas: 137775)\n[PASS] test_thresholdCrossingBuysAllAndBurns() (gas: 642921)\n[PASS] test_unauthorizedCallbacksAndHelpers() (gas: 133533)\n[PASS] test_unspecifiedNativePartialSellUsesActualETH() (gas: 156977)\n[PASS] test_wrongPoolRejected() (gas: 76038)\n[PASS] test_zeroFeeDustDoesNotCreateCredit() (gas: 181336)\nSuite result: ok. 24 passed; 0 failed; 0 skipped; finished in 112.43ms (140.47ms CPU time)\n\nRan 17 tests for test/SherwoodAdversarial.t.sol:SherwoodAdversarialTest\n[PASS] testFuzz_buyThenSellCannotCreateETH(uint96,bool) (runs: 1000, μ: 512042, ~: 435964)\n[PASS] testFuzz_everyPoolKeyFieldIsAuthenticated(uint8) (runs: 1000, μ: 96316, ~: 96889)\n[PASS] testFuzz_executedSwapAndWalletBalancesExplainEveryFee(bool,bool,uint96) (runs: 1000, μ: 245728, ~: 245960)\n[PASS] test_buybackRejectsFullETHConsumptionWithoutAnyIMD() (gas: 882289)\n[PASS] test_constructorRejectsInvalidInputsAndIncorrectAddressFlags() (gas: 544960)\n[PASS] test_falseReturnFromBurnTransferRollsBackTheAMMAndGame() (gas: 899178)\n[PASS] test_gameCannotRecursivelyTradeTheProjectPool() (gas: 1009396)\n[PASS] test_lateAndMalformedGameFailuresRollBackThenRetry() (gas: 3596738)\n[PASS] test_nativeFeeRejectsSignedExtremesWithoutPanicking() (gas: 86875)\n[PASS] test_oneWeiBelowThresholdAndOneWeiFeeBoundary() (gas: 575178)\n[PASS] test_oneWeiTradeRetriesAFundedJarWithoutChargingDust() (gas: 390173)\n[PASS] test_partialBuybackIsAtomicAndRecoversWhenLiquidityReturns() (gas: 1533925)\n[PASS] test_poolParametersAndAllPermissionBitsArePinned() (gas: 47927)\n[PASS] test_repeatedBuybacksWithinOneUnlockSettleEvenAfterERC20Sync() (gas: 1729539)\n[PASS] test_successEventMatchesTheFullJarAndActualBurn() (gas: 457016)\n[PASS] test_unlockCallbackRequiresAnActiveRedemption() (gas: 36862)\n[PASS] test_zeroSwapDoesNotRedeemOrSpendAnExistingJar() (gas: 59377)\nSuite result: ok. 17 passed; 0 failed; 0 skipped; finished in 115.54ms (257.26ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:SherwoodInvariants\n[PASS]\nSherwoodInvariants invariants:\n[PASS] invariant_ETHConservedAcrossAllPoolsJarAndGame\n[PASS] invariant_allFlashAccountingSettled\n[PASS] invariant_tokensConservedAndNoIMDRetained\n SherwoodInvariants invariants (runs: 64, calls: 4096, reverts: 0)\n\n╭-------------+----------+-------+---------+----------╮\n| Contract    | Selector | Calls | Reverts | Discards |\n+=====================================================+\n| SwapHandler | trade    | 4096  | 0       | 0        |\n╰-------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.31s (1.29s CPU time)\n\nRan 2 tests for test/QuiverStateful.t.sol:QuiverStatefulTest\n[PASS]\nQuiverStatefulTest invariants:\n[PASS] invariant_allowancesFollowApprovalsSpendsAndRevocations\n[PASS] invariant_fixedSupplyMatchesEveryTrackedBalance\n QuiverStatefulTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭---------------+-----------------+-------+---------+----------╮\n| Contract      | Selector        | Calls | Reverts | Discards |\n+==============================================================+\n| QuiverHandler | approve         | 3265  | 0       | 0        |\n|---------------+-----------------+-------+---------+----------|\n| QuiverHandler | exceedAllowance | 3336  | 0       | 0        |\n|---------------+-----------------+-------+---------+----------|\n| QuiverHandler | overspend       | 3294  | 0       | 0        |\n|---------------+-----------------+-------+---------+----------|\n| QuiverHandler | spend           | 3275  | 0       | 0        |\n|---------------+-----------------+-------+---------+----------|\n| QuiverHandler | transfer        | 3214  | 0       | 0        |\n╰---------------+-----------------+-------+---------+----------╯\n\n[PASS] test_handlerTransfersAndSpendsTheFullSupply() (gas: 780767)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.81s (2.81s CPU time)\n\nRan 2 tests for test/JarAccounting.invariant.t.sol:JarAccountingInvariantTest\n[PASS]\nJarAccountingInvariantTest invariants:\n[PASS] invariant_everySuccessfulBuyBurnsItsOutputAndNoOtherCallDoes\n[PASS] invariant_everyWeiIsStillInTheJarAClaimOrASuccessfulBuy\n[PASS] invariant_projectSupplyAndBalancesAreConserved\n JarAccountingInvariantTest invariants (runs: 256, calls: 24576, reverts: 0)\n\n╭------------+-----------------+-------+---------+----------╮\n| Contract   | Selector        | Calls | Reverts | Discards |\n+===========================================================+\n| JarHandler | collectLPFees   | 4036  | 0       | 0        |\n|------------+-----------------+-------+---------+----------|\n| JarHandler | donateClaims    | 4153  | 0       | 0        |\n|------------+-----------------+-------+---------+----------|\n| JarHandler | donateETH       | 4218  | 0       | 0        |\n|------------+-----------------+-------+---------+----------|\n| JarHandler | redeem          | 4030  | 0       | 0        |\n|------------+-----------------+-------+---------+----------|\n| JarHandler | trade           | 4147  | 0       | 0        |\n|------------+-----------------+-------+---------+----------|\n| JarHandler | unauthorizedBuy | 3992  | 0       | 0        |\n╰------------+-----------------+-------+---------+----------╯\n\n[PASS] test_handlerExercisesFailuresClaimsRedemptionsAndRecovery() (gas: 1487901)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 4.36s (4.35s CPU time)\n\nRan 7 test suites in 4.36s (8.91s CPU time): 58 tests passed, 0 failed, 0 skipped (58 total tests)\n","passed":true},{"durationMs":43,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Quiver.approve(address,uint256)\",\"Quiver.transfer(address,uint256)\",\"Quiver.transferFrom(address,address,uint256)\",\"SherwoodPlayerHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SherwoodPlayerHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SherwoodPlayerHook.executeBuyback(uint256)\",\"SherwoodPlayerHook.receive()\",\"SherwoodPlayerHook.redeemFees()\",\"SherwoodPlayerHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"DEPENDENCIES.md\":12,\"README.md\":85,\"SECURITY.md\":23,\"foundry.toml\":17,\"remappings.txt\":4,\"src/Quiver.sol\":11,\"src/SherwoodPlayerHook.sol\":210,\"test/Invariants.t.sol\":79,\"test/JarAccounting.invariant.t.sol\":95,\"test/Quiver.t.sol\":108,\"test/QuiverStateful.t.sol\":134,\"test/README.md\":54,\"test/SherwoodAdversarial.t.sol\":363,\"test/SherwoodDeferred.t.sol\":111,\"test/SherwoodPlayerHook.t.sol\":281,\"test/helpers/HookFixture.sol\":79,\"test/helpers/JarHandler.sol\":206,\"test/helpers/MockSherwood.sol\":90,\"test/helpers/PoolDriver.sol\":59,\"test/helpers/SettlementProbe.sol\":82},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"22d08b13bbd098487a12357414a2bffe8db1d5fe5acc0de6d11554409c51f7f4","verifiedTreeHash":"9730d178e60ad7ef6a4db2a918794845643aed1a","verifierVersion":"0.1.0+6a374fcd"},{"checks":[{"durationMs":1924,"exitCode":0,"name":"build","output":"Compiling 79 files with Solc 0.8.26\nSolc 0.8.26 finished in 1.85s\nCompiler run successful!\n","passed":true},{"durationMs":1122,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/Quiver.t.sol:QuiverTest\n[PASS] testFuzz_transferAndAllowance(uint96) (runs: 256, μ: 179509, ~: 180563)\n[PASS] test_metadataAndSupply() (gas: 51410)\n[PASS] test_noMintOrAdmin() (gas: 51869)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 4.59ms (4.48ms CPU time)\n\nRan 24 tests for test/SherwoodPlayerHook.t.sol:SherwoodPlayerHookTest\n[PASS] testFuzz_feeConservation(bool,bool,uint96) (runs: 256, μ: 245587, ~: 245710)\n[PASS] test_donationsIncludedAndRedemptionCannotTriggerBuyback() (gas: 464377)\n[PASS] test_exactInputBuyFee() (gas: 193311)\n[PASS] test_exactInputSellFee() (gas: 186410)\n[PASS] test_exactOutputBuyFee() (gas: 183976)\n[PASS] test_exactOutputSellFee() (gas: 182392)\n[PASS] test_exactThresholdTriggers() (gas: 419398)\n[PASS] test_factoryZeroLiquidityFeeCollection() (gas: 286196)\n[PASS] test_failedBurnRollsBackGameAndSettlement() (gas: 796531)\n[PASS] test_failureKeepsJarAndNextTraderRetries() (gas: 683013)\n[PASS] test_freshTokenOnlyPoolDefersFeeWithoutReverting() (gas: 9477724)\n[PASS] test_gameOutOfGasDoesNotRevertTrade() (gas: 2182943)\n[PASS] test_hookHasNoOwnerOrAdmin() (gas: 70416)\n[PASS] test_laterSwapAutomaticallyRedeemsDeferredFee() (gas: 9576970)\n[PASS] test_missingSherwoodPoolIsRetriable() (gas: 9646649)\n[PASS] test_partialClaimRedemptionAndNoKeeperPlayer() (gas: 9515290)\n[PASS] test_permissionsAndInitialization() (gas: 14076)\n[PASS] test_reentrantRedemptionIsIsolated() (gas: 747147)\n[PASS] test_specifiedNativePartialFillRevertsAtomically() (gas: 137775)\n[PASS] test_thresholdCrossingBuysAllAndBurns() (gas: 642034)\n[PASS] test_unauthorizedCallbacksAndHelpers() (gas: 133533)\n[PASS] test_unspecifiedNativePartialSellUsesActualETH() (gas: 156977)\n[PASS] test_wrongPoolRejected() (gas: 76038)\n[PASS] test_zeroFeeDustDoesNotCreateCredit() (gas: 181336)\nSuite result: ok. 24 passed; 0 failed; 0 skipped; finished in 17.85ms (47.65ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:SherwoodInvariants\n[PASS]\nSherwoodInvariants invariants:\n[PASS] invariant_ETHConservedAcrossAllPoolsJarAndGame\n[PASS] invariant_allFlashAccountingSettled\n[PASS] invariant_tokensConservedAndNoIMDRetained\n SherwoodInvariants invariants (runs: 64, calls: 4096, reverts: 0)\n\n╭-------------+----------+-------+---------+----------╮\n| Contract    | Selector | Calls | Reverts | Discards |\n+=====================================================+\n| SwapHandler | trade    | 4096  | 0       | 0        |\n╰-------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.03s (1.03s CPU time)\n\nRan 3 test suites in 1.03s (1.05s CPU time): 28 tests passed, 0 failed, 0 skipped (28 total tests)\n","passed":true},{"durationMs":42,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Quiver.approve(address,uint256)\",\"Quiver.transfer(address,uint256)\",\"Quiver.transferFrom(address,address,uint256)\",\"SherwoodPlayerHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SherwoodPlayerHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SherwoodPlayerHook.executeBuyback(uint256)\",\"SherwoodPlayerHook.receive()\",\"SherwoodPlayerHook.redeemFees()\",\"SherwoodPlayerHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"DEPENDENCIES.md\":12,\"README.md\":85,\"SECURITY.md\":23,\"foundry.toml\":17,\"launch.json\":30,\"remappings.txt\":4,\"src/Quiver.sol\":11,\"src/SherwoodPlayerHook.sol\":210,\"test/Invariants.t.sol\":79,\"test/Quiver.t.sol\":44,\"test/SherwoodPlayerHook.t.sol\":281,\"test/helpers/HookFixture.sol\":79,\"test/helpers/MockSherwood.sol\":76,\"test/helpers/PoolDriver.sol\":59},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"377e7f9f5f76d61c51cbb895dc5729a9e0a67fd31801ce925ac7a471ca8802ff","verifiedTreeHash":"b326f290bedc0c7c22d6c6a9d0323524e5164e2e","verifierVersion":"0.1.0+6a374fcd"},{"checks":[{"durationMs":3635,"exitCode":0,"name":"build","output":"Compiling 80 files with Solc 0.8.26\nSolc 0.8.26 finished in 3.50s\nCompiler run successful!\n","passed":true},{"durationMs":1754,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/Quiver.t.sol:QuiverTest\n[PASS] testFuzz_transferAndAllowance(uint96) (runs: 256, μ: 179509, ~: 180563)\n[PASS] test_metadataAndSupply() (gas: 51410)\n[PASS] test_noMintOrAdmin() (gas: 51869)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 9.22ms (9.17ms CPU time)\n\nRan 27 tests for test/SherwoodPlayerHook.t.sol:SherwoodPlayerHookTest\n[PASS] testFuzz_feeConservation(bool,bool,uint96) (runs: 256, μ: 245873, ~: 245769)\n[PASS] test_donationsIncludedAndRedemptionCannotTriggerBuyback() (gas: 465398)\n[PASS] test_exactInputBuyFee() (gas: 193603)\n[PASS] test_exactInputSellFee() (gas: 186485)\n[PASS] test_exactOutputBuyFee() (gas: 184250)\n[PASS] test_exactOutputSellFee() (gas: 190775)\n[PASS] test_exactThresholdTriggers() (gas: 420419)\n[PASS] test_factoryZeroLiquidityFeeCollection() (gas: 286399)\n[PASS] test_failedBurnRollsBackGameAndSettlement() (gas: 798595)\n[PASS] test_failureKeepsJarAndNextTraderRetries() (gas: 684971)\n[PASS] test_feeBaseIsGrossETHInEveryMode() (gas: 350604)\n[PASS] test_freshTokenOnlyPoolDefersFeeWithoutReverting() (gas: 34054484)\n[PASS] test_gameOutOfGasDoesNotRevertTrade() (gas: 2183964)\n[PASS] test_hookHasNoOwnerOrAdmin() (gas: 70416)\n[PASS] test_laterSwapAutomaticallyRedeemsDeferredFee() (gas: 34153955)\n[PASS] test_missingSherwoodPoolIsRetriable() (gas: 34224474)\n[PASS] test_partialClaimRedemptionAndNoKeeperPlayer() (gas: 34092072)\n[PASS] test_permissionsAndInitialization() (gas: 14054)\n[PASS] test_prepayingRouterSellSucceedsAndDefersBuyback() (gas: 1224512)\n[PASS] test_prepayingRouterSellWithEmptyJarIsUnaffected() (gas: 790684)\n[PASS] test_reentrantRedemptionIsIsolated() (gas: 749233)\n[PASS] test_specifiedNativePartialFillRevertsAtomically() (gas: 137980)\n[PASS] test_thresholdCrossingBuysAllAndBurns() (gas: 643280)\n[PASS] test_unauthorizedCallbacksAndHelpers() (gas: 133533)\n[PASS] test_unspecifiedNativePartialSellUsesActualETH() (gas: 157030)\n[PASS] test_wrongPoolRejected() (gas: 76038)\n[PASS] test_zeroFeeDustDoesNotCreateCredit() (gas: 181539)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 47.45ms (182.06ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:SherwoodInvariants\n[PASS]\nSherwoodInvariants invariants:\n[PASS] invariant_ETHConservedAcrossAllPoolsJarAndGame\n[PASS] invariant_allFlashAccountingSettled\n[PASS] invariant_tokensConservedAndNoIMDRetained\n SherwoodInvariants invariants (runs: 64, calls: 4096, reverts: 0)\n\n╭-------------+----------+-------+---------+----------╮\n| Contract    | Selector | Calls | Reverts | Discards |\n+=====================================================+\n| SwapHandler | trade    | 4096  | 0       | 0        |\n╰-------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.60s (1.58s CPU time)\n\nRan 3 test suites in 1.60s (1.65s CPU time): 31 tests passed, 0 failed, 0 skipped (31 total tests)\n","passed":true},{"durationMs":87,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Quiver.approve(address,uint256)\",\"Quiver.transfer(address,uint256)\",\"Quiver.transferFrom(address,address,uint256)\",\"SherwoodPlayerHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SherwoodPlayerHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SherwoodPlayerHook.executeBuyback(uint256)\",\"SherwoodPlayerHook.receive()\",\"SherwoodPlayerHook.redeemFees()\",\"SherwoodPlayerHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"DEPENDENCIES.md\":12,\"README.md\":87,\"SECURITY.md\":24,\"foundry.toml\":17,\"launch.json\":30,\"remappings.txt\":4,\"src/Quiver.sol\":11,\"src/SherwoodPlayerHook.sol\":226,\"test/Invariants.t.sol\":79,\"test/Quiver.t.sol\":44,\"test/SherwoodPlayerHook.t.sol\":336,\"test/helpers/HookFixture.sol\":79,\"test/helpers/MockSherwood.sol\":76,\"test/helpers/PoolDriver.sol\":59,\"test/helpers/PrepayRouter.sol\":38},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"9b98bccd37a649e5af698892c53f6277343eb0f54148696171a44b4bea07610e","verifiedTreeHash":"e1821c959f89003ed88b76fdc5f75fc3b60d9a78","verifierVersion":"0.1.0+6a374fcd"},{"checks":[{"durationMs":2081,"exitCode":0,"name":"build","output":"Compiling 79 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.00s\nCompiler run successful!\n","passed":true},{"durationMs":1132,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/Quiver.t.sol:QuiverTest\n[PASS] testFuzz_transferAndAllowance(uint96) (runs: 256, μ: 179509, ~: 180563)\n[PASS] test_metadataAndSupply() (gas: 51410)\n[PASS] test_noMintOrAdmin() (gas: 51869)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 6.61ms (6.50ms CPU time)\n\nRan 24 tests for test/SherwoodPlayerHook.t.sol:SherwoodPlayerHookTest\n[PASS] testFuzz_feeConservation(bool,bool,uint96) (runs: 256, μ: 245587, ~: 245710)\n[PASS] test_donationsIncludedAndRedemptionCannotTriggerBuyback() (gas: 464377)\n[PASS] test_exactInputBuyFee() (gas: 193311)\n[PASS] test_exactInputSellFee() (gas: 186410)\n[PASS] test_exactOutputBuyFee() (gas: 183976)\n[PASS] test_exactOutputSellFee() (gas: 182392)\n[PASS] test_exactThresholdTriggers() (gas: 419398)\n[PASS] test_factoryZeroLiquidityFeeCollection() (gas: 286196)\n[PASS] test_failedBurnRollsBackGameAndSettlement() (gas: 796531)\n[PASS] test_failureKeepsJarAndNextTraderRetries() (gas: 683013)\n[PASS] test_freshTokenOnlyPoolDefersFeeWithoutReverting() (gas: 9477724)\n[PASS] test_gameOutOfGasDoesNotRevertTrade() (gas: 2182943)\n[PASS] test_hookHasNoOwnerOrAdmin() (gas: 70416)\n[PASS] test_laterSwapAutomaticallyRedeemsDeferredFee() (gas: 9576970)\n[PASS] test_missingSherwoodPoolIsRetriable() (gas: 9646649)\n[PASS] test_partialClaimRedemptionAndNoKeeperPlayer() (gas: 9515290)\n[PASS] test_permissionsAndInitialization() (gas: 14076)\n[PASS] test_reentrantRedemptionIsIsolated() (gas: 747147)\n[PASS] test_specifiedNativePartialFillRevertsAtomically() (gas: 137775)\n[PASS] test_thresholdCrossingBuysAllAndBurns() (gas: 642034)\n[PASS] test_unauthorizedCallbacksAndHelpers() (gas: 133533)\n[PASS] test_unspecifiedNativePartialSellUsesActualETH() (gas: 156977)\n[PASS] test_wrongPoolRejected() (gas: 76038)\n[PASS] test_zeroFeeDustDoesNotCreateCredit() (gas: 181336)\nSuite result: ok. 24 passed; 0 failed; 0 skipped; finished in 19.91ms (68.88ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:SherwoodInvariants\n[PASS]\nSherwoodInvariants invariants:\n[PASS] invariant_ETHConservedAcrossAllPoolsJarAndGame\n[PASS] invariant_allFlashAccountingSettled\n[PASS] invariant_tokensConservedAndNoIMDRetained\n SherwoodInvariants invariants (runs: 64, calls: 4096, reverts: 0)\n\n╭-------------+----------+-------+---------+----------╮\n| Contract    | Selector | Calls | Reverts | Discards |\n+=====================================================+\n| SwapHandler | trade    | 4096  | 0       | 0        |\n╰-------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.03s (1.02s CPU time)\n\nRan 3 test suites in 1.03s (1.06s CPU time): 28 tests passed, 0 failed, 0 skipped (28 total tests)\n","passed":true},{"durationMs":35,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Quiver.approve(address,uint256)\",\"Quiver.transfer(address,uint256)\",\"Quiver.transferFrom(address,address,uint256)\",\"SherwoodPlayerHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SherwoodPlayerHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SherwoodPlayerHook.executeBuyback(uint256)\",\"SherwoodPlayerHook.receive()\",\"SherwoodPlayerHook.redeemFees()\",\"SherwoodPlayerHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"DEPENDENCIES.md\":12,\"README.md\":85,\"SECURITY.md\":23,\"foundry.toml\":17,\"remappings.txt\":4,\"src/Quiver.sol\":11,\"src/SherwoodPlayerHook.sol\":210,\"test/Invariants.t.sol\":79,\"test/Quiver.t.sol\":44,\"test/SherwoodPlayerHook.t.sol\":281,\"test/helpers/HookFixture.sol\":79,\"test/helpers/MockSherwood.sol\":76,\"test/helpers/PoolDriver.sol\":59},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1052,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/SherwoodPlayerHook.sol:183: SherwoodPlayerHook._redeem() (src/SherwoodPlayerHook.sol#183-191) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/SherwoodPlayerHook.sol:135: Reentrancy in SherwoodPlayerHook.redeemFees() (src/SherwoodPlayerHook.sol#135-140):\n[medium/medium] reentrancy-no-eth at src/SherwoodPlayerHook.sol:105: Reentrancy in SherwoodPlayerHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/SherwoodPlayerHook.sol#105-131):\n[medium/medium] uninitialized-local at src/SherwoodPlayerHook.sol:113: SherwoodPlayerHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes).fee (src/SherwoodPlayerHook.sol#113) is a local variable never initialized\n[medium/medium] uninitialized-local at src/SherwoodPlayerHook.sol:92: SherwoodPlayerHook.beforeSwap(address,PoolKey,SwapParams,bytes).fee (src/SherwoodPlayerHook.sol#92) is a local variable never initialized\n[medium/medium] unused-return at src/SherwoodPlayerHook.sol:135: SherwoodPlayerHook.redeemFees() (src/SherwoodPlayerHook.sol#135-140) ignores return value by poolManager.unlock() (src/SherwoodPlayerHook.sol#138)\n[medium/medium] unused-return at src/SherwoodPlayerHook.sol:150: SherwoodPlayerHook.executeBuyback(uint256) (src/SherwoodPlayerHook.sol#150-165) ignores return value by poolManager.settle{value: amount}() (src/SherwoodPlayerHook.sol#155)\n[low/medium] reentrancy-events at src/SherwoodPlayerHook.sol:183: Reentrancy in SherwoodPlayerHook._redeem() (src/SherwoodPlayerHook.sol#183-191):\n[low/medium] reentrancy-events at src/SherwoodPlayerHook.sol:150: Reentrancy in SherwoodPlayerHook.executeBuyback(uint256) (src/SherwoodPlayerHook.sol#150-165):\n[low/medium] reentrancy-events at src/SherwoodPlayerHook.sol:175: Reentrancy in SherwoodPlayerHook._collect(uint256) (src/SherwoodPlayerHook.sol#175-181):\n[low/medium] reentrancy-events at src/SherwoodPlayerHook.sol:82: Reentrancy in SherwoodPlayerHook.beforeSwap(address,PoolKey,SwapParams,bytes) (src/SherwoodPlayerHook.sol#82-103):\n[low/medium] reentrancy-events at src/SherwoodPlayerHook.sol:105: Reentrancy in SherwoodPlayerHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/SherwoodPlayerHook.sol#105-131):","passed":true},{"durationMs":321,"exitCode":0,"name":"aderyn","output":"[high] contract-locks-ether at src/SherwoodPlayerHook.sol:18: Contract locks Ether without a withdraw function\n[high] reentrancy-state-change at src/SherwoodPlayerHook.sol:138: Reentrancy: State change after external call\n[high] strict-equality-contract-balance at src/SherwoodPlayerHook.sol:152: Dangerous strict equality checks on contract balances\n[high] unsafe-casting at src/SherwoodPlayerHook.sol:130: Unsafe Casting of integers\n[low] large-numeric-literal at src/Quiver.sol:9: Large Numeric Literal (3 places)\n[low] state-change-without-event at src/SherwoodPlayerHook.sol:135: State Change Without Event\n[low] unchecked-return at src/SherwoodPlayerHook.sol:138: Unchecked Return","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"b35012b63149c60a55f91f834cc0d35e6cff85cf536d5c0f98ef29be3b628eac","verifiedTreeHash":"ee2edd0cf5b3697b678eee302b72ed6233bd0cb6","verifierVersion":"0.1.0+6a374fcd"},{"checks":[{"durationMs":2309,"exitCode":0,"name":"build","output":"Compiling 80 files with Solc 0.8.26\nSolc 0.8.26 finished in 2.22s\nCompiler run successful!\n","passed":true},{"durationMs":1269,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 3 tests for test/Quiver.t.sol:QuiverTest\n[PASS] testFuzz_transferAndAllowance(uint96) (runs: 256, μ: 179509, ~: 180563)\n[PASS] test_metadataAndSupply() (gas: 51410)\n[PASS] test_noMintOrAdmin() (gas: 51869)\nSuite result: ok. 3 passed; 0 failed; 0 skipped; finished in 5.52ms (5.46ms CPU time)\n\nRan 27 tests for test/SherwoodPlayerHook.t.sol:SherwoodPlayerHookTest\n[PASS] testFuzz_feeConservation(bool,bool,uint96) (runs: 256, μ: 245873, ~: 245769)\n[PASS] test_donationsIncludedAndRedemptionCannotTriggerBuyback() (gas: 465398)\n[PASS] test_exactInputBuyFee() (gas: 193603)\n[PASS] test_exactInputSellFee() (gas: 186485)\n[PASS] test_exactOutputBuyFee() (gas: 184250)\n[PASS] test_exactOutputSellFee() (gas: 190775)\n[PASS] test_exactThresholdTriggers() (gas: 420419)\n[PASS] test_factoryZeroLiquidityFeeCollection() (gas: 286399)\n[PASS] test_failedBurnRollsBackGameAndSettlement() (gas: 798595)\n[PASS] test_failureKeepsJarAndNextTraderRetries() (gas: 684971)\n[PASS] test_feeBaseIsGrossETHInEveryMode() (gas: 350604)\n[PASS] test_freshTokenOnlyPoolDefersFeeWithoutReverting() (gas: 34054484)\n[PASS] test_gameOutOfGasDoesNotRevertTrade() (gas: 2183964)\n[PASS] test_hookHasNoOwnerOrAdmin() (gas: 70416)\n[PASS] test_laterSwapAutomaticallyRedeemsDeferredFee() (gas: 34153955)\n[PASS] test_missingSherwoodPoolIsRetriable() (gas: 34224474)\n[PASS] test_partialClaimRedemptionAndNoKeeperPlayer() (gas: 34092072)\n[PASS] test_permissionsAndInitialization() (gas: 14054)\n[PASS] test_prepayingRouterSellSucceedsAndDefersBuyback() (gas: 1224512)\n[PASS] test_prepayingRouterSellWithEmptyJarIsUnaffected() (gas: 790684)\n[PASS] test_reentrantRedemptionIsIsolated() (gas: 749233)\n[PASS] test_specifiedNativePartialFillRevertsAtomically() (gas: 137980)\n[PASS] test_thresholdCrossingBuysAllAndBurns() (gas: 643280)\n[PASS] test_unauthorizedCallbacksAndHelpers() (gas: 133533)\n[PASS] test_unspecifiedNativePartialSellUsesActualETH() (gas: 157030)\n[PASS] test_wrongPoolRejected() (gas: 76038)\n[PASS] test_zeroFeeDustDoesNotCreateCredit() (gas: 181539)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 34.54ms (137.77ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:SherwoodInvariants\n[PASS]\nSherwoodInvariants invariants:\n[PASS] invariant_ETHConservedAcrossAllPoolsJarAndGame\n[PASS] invariant_allFlashAccountingSettled\n[PASS] invariant_tokensConservedAndNoIMDRetained\n SherwoodInvariants invariants (runs: 64, calls: 4096, reverts: 0)\n\n╭-------------+----------+-------+---------+----------╮\n| Contract    | Selector | Calls | Reverts | Discards |\n+=====================================================+\n| SwapHandler | trade    | 4096  | 0       | 0        |\n╰-------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.17s (1.16s CPU time)\n\nRan 3 test suites in 1.17s (1.21s CPU time): 31 tests passed, 0 failed, 0 skipped (31 total tests)\n","passed":true},{"durationMs":44,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Quiver.approve(address,uint256)\",\"Quiver.transfer(address,uint256)\",\"Quiver.transferFrom(address,address,uint256)\",\"SherwoodPlayerHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SherwoodPlayerHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SherwoodPlayerHook.executeBuyback(uint256)\",\"SherwoodPlayerHook.receive()\",\"SherwoodPlayerHook.redeemFees()\",\"SherwoodPlayerHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"DEPENDENCIES.md\":12,\"README.md\":87,\"SECURITY.md\":24,\"foundry.toml\":17,\"remappings.txt\":4,\"src/Quiver.sol\":11,\"src/SherwoodPlayerHook.sol\":226,\"test/Invariants.t.sol\":79,\"test/Quiver.t.sol\":44,\"test/SherwoodPlayerHook.t.sol\":336,\"test/helpers/HookFixture.sol\":79,\"test/helpers/MockSherwood.sol\":76,\"test/helpers/PoolDriver.sol\":59,\"test/helpers/PrepayRouter.sol\":38},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true},{"durationMs":1418,"exitCode":0,"name":"slither","output":"[medium/high] incorrect-equality at src/SherwoodPlayerHook.sol:192: SherwoodPlayerHook._redeem() (src/SherwoodPlayerHook.sol#192-200) uses a dangerous strict equality:\n[medium/medium] reentrancy-no-eth at src/SherwoodPlayerHook.sol:105: Reentrancy in SherwoodPlayerHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/SherwoodPlayerHook.sol#105-128):\n[medium/medium] reentrancy-no-eth at src/SherwoodPlayerHook.sol:132: Reentrancy in SherwoodPlayerHook.redeemFees() (src/SherwoodPlayerHook.sol#132-137):\n[medium/medium] uninitialized-local at src/SherwoodPlayerHook.sol:113: SherwoodPlayerHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes).fee (src/SherwoodPlayerHook.sol#113) is a local variable never initialized\n[medium/medium] uninitialized-local at src/SherwoodPlayerHook.sol:95: SherwoodPlayerHook.beforeSwap(address,PoolKey,SwapParams,bytes).fee (src/SherwoodPlayerHook.sol#95) is a local variable never initialized\n[medium/medium] unused-return at src/SherwoodPlayerHook.sol:132: SherwoodPlayerHook.redeemFees() (src/SherwoodPlayerHook.sol#132-137) ignores return value by poolManager.unlock() (src/SherwoodPlayerHook.sol#135)\n[medium/medium] unused-return at src/SherwoodPlayerHook.sol:147: SherwoodPlayerHook.executeBuyback(uint256) (src/SherwoodPlayerHook.sol#147-163) ignores return value by poolManager.settle{value: amount}() (src/SherwoodPlayerHook.sol#153)\n[low/medium] reentrancy-events at src/SherwoodPlayerHook.sol:105: Reentrancy in SherwoodPlayerHook.afterSwap(address,PoolKey,SwapParams,BalanceDelta,bytes) (src/SherwoodPlayerHook.sol#105-128):\n[low/medium] reentrancy-events at src/SherwoodPlayerHook.sol:147: Reentrancy in SherwoodPlayerHook.executeBuyback(uint256) (src/SherwoodPlayerHook.sol#147-163):\n[low/medium] reentrancy-events at src/SherwoodPlayerHook.sol:85: Reentrancy in SherwoodPlayerHook.beforeSwap(address,PoolKey,SwapParams,bytes) (src/SherwoodPlayerHook.sol#85-103):\n[low/medium] reentrancy-events at src/SherwoodPlayerHook.sol:192: Reentrancy in SherwoodPlayerHook._redeem() (src/SherwoodPlayerHook.sol#192-200):\n[low/medium] reentrancy-events at src/SherwoodPlayerHook.sol:184: Reentrancy in SherwoodPlayerHook._collect(uint256) (src/SherwoodPlayerHook.sol#184-190):","passed":true},{"durationMs":434,"exitCode":0,"name":"aderyn","output":"[high] contract-locks-ether at src/SherwoodPlayerHook.sol:19: Contract locks Ether without a withdraw function\n[high] reentrancy-state-change at src/SherwoodPlayerHook.sol:135: Reentrancy: State change after external call\n[high] strict-equality-contract-balance at src/SherwoodPlayerHook.sol:149: Dangerous strict equality checks on contract balances\n[high] unsafe-casting at src/SherwoodPlayerHook.sol:127: Unsafe Casting of integers\n[low] large-numeric-literal at src/Quiver.sol:9: Large Numeric Literal (3 places)\n[low] state-change-without-event at src/SherwoodPlayerHook.sol:132: State Change Without Event\n[low] unchecked-return at src/SherwoodPlayerHook.sol:135: Unchecked Return","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"d0b0a5a676165dd0980f592bc9593b0906c3d42ceab1384fc5a1456d66911a45","verifiedTreeHash":"8b71e3b9757b1af398cb69093efa0cc2b3aa0c17","verifierVersion":"0.1.0+6a374fcd"},{"checks":[{"durationMs":4407,"exitCode":0,"name":"build","output":"Compiling 86 files with Solc 0.8.26\nSolc 0.8.26 finished in 4.31s\nCompiler run successful!\n","passed":true},{"durationMs":4380,"exitCode":0,"name":"test","output":"No files changed, compilation skipped\n\nRan 8 tests for test/Quiver.t.sol:QuiverTest\n[PASS] testFuzz_balanceFailureDoesNotConsumeAllowance(uint96) (runs: 1000, μ: 177029, ~: 176942)\n[PASS] testFuzz_transferAndAllowance(uint96) (runs: 256, μ: 180184, ~: 180895)\n[PASS] test_infiniteApprovalAndFullSupplyRoundTrip() (gas: 189241)\n[PASS] test_metadataAndSupply() (gas: 51529)\n[PASS] test_noMintOrAdmin() (gas: 51963)\n[PASS] test_transferAndApproveRejectZeroRecipients() (gas: 73486)\n[PASS] test_unapprovedCallerCannotMoveAnotherHoldersTokens() (gas: 54264)\n[PASS] test_zeroTransfersAndSelfTransfersCannotChangeSupplyOrBalances() (gas: 91262)\nSuite result: ok. 8 passed; 0 failed; 0 skipped; finished in 115.04ms (126.39ms CPU time)\n\nRan 4 tests for test/SherwoodDeferred.t.sol:SherwoodDeferredTest\n[PASS] testFuzz_firstBuyClaimIsBackedAndRedemptionIsIdempotent(uint96,bool) (runs: 1000, μ: 377210, ~: 376618)\n[PASS] test_batchAccumulatesClaimsUntilTheRouterSettles() (gas: 1922466)\n[PASS] test_nextTraderRealizesDeferredFeesAndPlays() (gas: 689180)\n[PASS] test_prepaidBuybackWorksWithNoManagerETHBeforeTheTrade() (gas: 594028)\nSuite result: ok. 4 passed; 0 failed; 0 skipped; finished in 115.07ms (105.06ms CPU time)\n\nRan 27 tests for test/SherwoodPlayerHook.t.sol:SherwoodPlayerHookTest\n[PASS] testFuzz_feeConservation(bool,bool,uint96) (runs: 256, μ: 245856, ~: 245769)\n[PASS] test_donationsIncludedAndRedemptionCannotTriggerBuyback() (gas: 466285)\n[PASS] test_exactInputBuyFee() (gas: 193603)\n[PASS] test_exactInputSellFee() (gas: 186485)\n[PASS] test_exactOutputBuyFee() (gas: 184250)\n[PASS] test_exactOutputSellFee() (gas: 190775)\n[PASS] test_exactThresholdTriggers() (gas: 421306)\n[PASS] test_factoryZeroLiquidityFeeCollection() (gas: 286399)\n[PASS] test_failedBurnRollsBackGameAndSettlement() (gas: 800369)\n[PASS] test_failureKeepsJarAndNextTraderRetries() (gas: 685858)\n[PASS] test_feeBaseIsGrossETHInEveryMode() (gas: 350604)\n[PASS] test_freshTokenOnlyPoolDefersFeeWithoutReverting() (gas: 34054484)\n[PASS] test_gameOutOfGasDoesNotRevertTrade() (gas: 2183964)\n[PASS] test_hookHasNoOwnerOrAdmin() (gas: 70416)\n[PASS] test_laterSwapAutomaticallyRedeemsDeferredFee() (gas: 34153955)\n[PASS] test_missingSherwoodPoolIsRetriable() (gas: 34224474)\n[PASS] test_partialClaimRedemptionAndNoKeeperPlayer() (gas: 34092072)\n[PASS] test_permissionsAndInitialization() (gas: 14054)\n[PASS] test_prepayingRouterSellSucceedsAndDefersBuyback() (gas: 1225399)\n[PASS] test_prepayingRouterSellWithEmptyJarIsUnaffected() (gas: 790684)\n[PASS] test_reentrantRedemptionIsIsolated() (gas: 750120)\n[PASS] test_specifiedNativePartialFillRevertsAtomically() (gas: 137980)\n[PASS] test_thresholdCrossingBuysAllAndBurns() (gas: 644167)\n[PASS] test_unauthorizedCallbacksAndHelpers() (gas: 133533)\n[PASS] test_unspecifiedNativePartialSellUsesActualETH() (gas: 157030)\n[PASS] test_wrongPoolRejected() (gas: 76038)\n[PASS] test_zeroFeeDustDoesNotCreateCredit() (gas: 181539)\nSuite result: ok. 27 passed; 0 failed; 0 skipped; finished in 115.46ms (176.43ms CPU time)\n\nRan 19 tests for test/SherwoodAdversarial.t.sol:SherwoodAdversarialTest\n[PASS] testFuzz_buyThenSellCannotCreateETH(uint96,bool) (runs: 1000, μ: 511190, ~: 436301)\n[PASS] testFuzz_everyPoolKeyFieldIsAuthenticated(uint8) (runs: 1000, μ: 96299, ~: 96889)\n[PASS] testFuzz_executedSwapAndWalletBalancesExplainEveryFee(bool,bool,uint96) (runs: 1000, μ: 246443, ~: 246647)\n[PASS] test_buybackRejectsFullETHConsumptionWithoutAnyIMD() (gas: 884512)\n[PASS] test_constructorRejectsInvalidInputsAndIncorrectAddressFlags() (gas: 546168)\n[PASS] test_falseReturnFromBurnTransferRollsBackTheAMMAndGame() (gas: 901229)\n[PASS] test_gameCannotRecursivelyTradeTheProjectPool() (gas: 1011608)\n[PASS] test_lateAndMalformedGameFailuresRollBackThenRetry() (gas: 3605608)\n[PASS] test_nativeFeeRejectsSignedExtremesWithoutPanicking() (gas: 87147)\n[PASS] test_oneWeiBelowThresholdAndOneWeiFeeBoundary() (gas: 576528)\n[PASS] test_oneWeiTradeRetriesAFundedJarWithoutChargingDust() (gas: 391301)\n[PASS] test_partialBuybackIsAtomicAndRecoversWhenLiquidityReturns() (gas: 1536193)\n[PASS] test_poolParametersAndAllPermissionBitsArePinned() (gas: 47927)\n[PASS] test_preSyncBelowThresholdEmitsNothing() (gas: 1382185)\n[PASS] test_repeatedBuybacksWithinOneUnlockSettle() (gas: 1727553)\n[PASS] test_routerThatSyncsBeforeEverySwapStillSettlesAndTheJarWaits() (gas: 1924893)\n[PASS] test_successEventMatchesTheFullJarAndActualBurn() (gas: 458100)\n[PASS] test_unlockCallbackRequiresAnActiveRedemption() (gas: 36862)\n[PASS] test_zeroSwapDoesNotRedeemOrSpendAnExistingJar() (gas: 59377)\nSuite result: ok. 19 passed; 0 failed; 0 skipped; finished in 115.52ms (295.57ms CPU time)\n\nRan 1 test for test/Invariants.t.sol:SherwoodInvariants\n[PASS]\nSherwoodInvariants invariants:\n[PASS] invariant_ETHConservedAcrossAllPoolsJarAndGame\n[PASS] invariant_allFlashAccountingSettled\n[PASS] invariant_tokensConservedAndNoIMDRetained\n SherwoodInvariants invariants (runs: 64, calls: 4096, reverts: 0)\n\n╭-------------+----------+-------+---------+----------╮\n| Contract    | Selector | Calls | Reverts | Discards |\n+=====================================================+\n| SwapHandler | trade    | 4096  | 0       | 0        |\n╰-------------+----------+-------+---------+----------╯\n\nSuite result: ok. 1 passed; 0 failed; 0 skipped; finished in 1.20s (1.19s CPU time)\n\nRan 2 tests for test/QuiverStateful.t.sol:QuiverStatefulTest\n[PASS]\nQuiverStatefulTest invariants:\n[PASS] invariant_allowancesFollowApprovalsSpendsAndRevocations\n[PASS] invariant_fixedSupplyMatchesEveryTrackedBalance\n QuiverStatefulTest invariants (runs: 256, calls: 16384, reverts: 0)\n\n╭---------------+-----------------+-------+---------+----------╮\n| Contract      | Selector        | Calls | Reverts | Discards |\n+==============================================================+\n| QuiverHandler | approve         | 3265  | 0       | 0        |\n|---------------+-----------------+-------+---------+----------|\n| QuiverHandler | exceedAllowance | 3336  | 0       | 0        |\n|---------------+-----------------+-------+---------+----------|\n| QuiverHandler | overspend       | 3294  | 0       | 0        |\n|---------------+-----------------+-------+---------+----------|\n| QuiverHandler | spend           | 3275  | 0       | 0        |\n|---------------+-----------------+-------+---------+----------|\n| QuiverHandler | transfer        | 3214  | 0       | 0        |\n╰---------------+-----------------+-------+---------+----------╯\n\n[PASS] test_handlerTransfersAndSpendsTheFullSupply() (gas: 780767)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 2.72s (2.72s CPU time)\n\nRan 2 tests for test/JarAccounting.invariant.t.sol:JarAccountingInvariantTest\n[PASS]\nJarAccountingInvariantTest invariants:\n[PASS] invariant_everySuccessfulBuyBurnsItsOutputAndNoOtherCallDoes\n[PASS] invariant_everyWeiIsStillInTheJarAClaimOrASuccessfulBuy\n[PASS] invariant_projectSupplyAndBalancesAreConserved\n JarAccountingInvariantTest invariants (runs: 256, calls: 24576, reverts: 0)\n\n╭------------+-----------------+-------+---------+----------╮\n| Contract   | Selector        | Calls | Reverts | Discards |\n+===========================================================+\n| JarHandler | collectLPFees   | 4045  | 0       | 0        |\n|------------+-----------------+-------+---------+----------|\n| JarHandler | donateClaims    | 4106  | 0       | 0        |\n|------------+-----------------+-------+---------+----------|\n| JarHandler | donateETH       | 4126  | 0       | 0        |\n|------------+-----------------+-------+---------+----------|\n| JarHandler | redeem          | 4184  | 0       | 0        |\n|------------+-----------------+-------+---------+----------|\n| JarHandler | trade           | 4043  | 0       | 0        |\n|------------+-----------------+-------+---------+----------|\n| JarHandler | unauthorizedBuy | 4072  | 0       | 0        |\n╰------------+-----------------+-------+---------+----------╯\n\n[PASS] test_handlerExercisesFailuresClaimsRedemptionsAndRecovery() (gas: 1809677)\nSuite result: ok. 2 passed; 0 failed; 0 skipped; finished in 4.28s (4.27s CPU time)\n\nRan 7 test suites in 4.28s (8.66s CPU time): 63 tests passed, 0 failed, 0 skipped (63 total tests)\n","passed":true},{"durationMs":57,"exitCode":0,"name":"source-index","output":"{\"v\":1,\"entryPoints\":[\"Quiver.approve(address,uint256)\",\"Quiver.transfer(address,uint256)\",\"Quiver.transferFrom(address,address,uint256)\",\"SherwoodPlayerHook.afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)\",\"SherwoodPlayerHook.beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)\",\"SherwoodPlayerHook.executeBuyback(uint256)\",\"SherwoodPlayerHook.receive()\",\"SherwoodPlayerHook.redeemFees()\",\"SherwoodPlayerHook.unlockCallback(bytes)\"],\"files\":{\".gitignore\":3,\"DEPENDENCIES.md\":12,\"README.md\":87,\"SECURITY.md\":24,\"foundry.toml\":17,\"remappings.txt\":4,\"src/Quiver.sol\":11,\"src/SherwoodPlayerHook.sol\":226,\"test/Invariants.t.sol\":79,\"test/JarAccounting.invariant.t.sol\":100,\"test/Quiver.t.sol\":108,\"test/QuiverStateful.t.sol\":134,\"test/README.md\":57,\"test/SherwoodAdversarial.t.sol\":427,\"test/SherwoodDeferred.t.sol\":112,\"test/SherwoodPlayerHook.t.sol\":336,\"test/helpers/HookFixture.sol\":79,\"test/helpers/JarHandler.sol\":263,\"test/helpers/MockSherwood.sol\":90,\"test/helpers/PoolDriver.sol\":59,\"test/helpers/PrepayRouter.sol\":38,\"test/helpers/SettlementProbe.sol\":82},\"excluded\":[\"lib/\",\"node_modules/\"],\"truncated\":false}","passed":true}],"detail":"all checks passed","evaluation":"checks","profile":"foundry","status":"accepted","submissionHash":"f1d1007ecb2f9db118079340391403ee0d7f6c2a6cd7c6f9f31b1c3269a4c74b","verifiedTreeHash":"abdbeb7f9fce140a0ed34248ba8fe7ff54b5c096","verifierVersion":"0.1.0+6a374fcd"}]}