{"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":"853ff4df-cb75-4375-8c73-475ff7c5c998","kind":"audit","nodes":[{"acceptedSubmissionHash":"9b9684dc2a0645f11dd4774c287c9259637efa418487ade164a00003483457de","dependsOn":[],"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":"cf161486c444539d034d025373b5017c4977e34235a56a6c8946ae2ab732fb6f","dependsOn":[],"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":"def755ccbd1c5ff30893d22f6d03a549a2cd331b3d9af38117d72a66d332608b","dependsOn":["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":"80a5040a56b9aadafccd0dca7619c9d3a495cd90cfeeec503760b91ab81430ac","dependsOn":[],"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":"22e2055f3456aa8f79cb9c27b887f8af59943968c7820c477407537f4d7b0bcc","dependsOn":[],"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"}],"objective":"Audit BOSS before its launch: BossHook (Uniswap v4 hook, 2.5% fee in IMD on top of the pool LP fee of 1.25%: 1.5% to the pot, 1% to the treasury; every buy hits the boss, every sell heals it; on a kill 10% of the pot seeds the next boss and the rest is split 50/50 between the killing blow and, by damage, the wallets by the damage each still holds; credit follows the BOSS: BossToken reports every transfer to the hook, a buy is credited to the wallet that receives the BOSS from the pool, BOSS handed back to the pool forfeits in proportion, BOSS sent to another wallet carries its credit, the killing blow travels with its BOSS and the prize stays with its largest holder; within one transaction a sell takes back the pending credit of the BOSS it hands in, the net credit created never exceeds the HP removed, and pending credit is scoped to the swap's sender), AbilityBoard and OracleGate (abilities written by the swarm, accepted only with an IMD oracle attestation, 21 seats, quorum 15). This commit fixes the findings of job be67d693; the README lists the known limits. Focus: can anyone drain or lock the pot, steal another wallet's prize or credit, get credit without net damage, block trades or transfers, replay an attestation, or make the treasury pay more than intended? Every finding with a location, a reproduction and a Foundry proof where possible.","parentJobId":null,"planHash":"9b5efdf1367bc1e13feca54d5b457ecf5a0c02b2be37f9a28f4897e6e007d192","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"853ff4df-cb75-4375-8c73-475ff7c5c998","publication":{"commit":null,"deliveredAt":null,"repoUrl":null},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"50966","feedbackHash":"ac444ea0f93f13bad5715fb198604d8404ae7032898a3693fb9d5dc36a572dc9","nodeKey":"audit_economics","submissionHash":"9b9684dc2a0645f11dd4774c287c9259637efa418487ade164a00003483457de","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51023","feedbackHash":"311a33c6f777507e2cf5665c31943f7305025fd141b263299f15e1a61ec0acc6","nodeKey":"audit_flow","submissionHash":"cf161486c444539d034d025373b5017c4977e34235a56a6c8946ae2ab732fb6f","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52017","feedbackHash":"e547190839d9124f756f9cdc61e784902ab5c1609b93503e54cbce351b024a18","nodeKey":"audit_judge","submissionHash":"def755ccbd1c5ff30893d22f6d03a549a2cd331b3d9af38117d72a66d332608b","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51026","feedbackHash":"2f6e7660fae32875d2e8dc9b82db3faf4e4163c55742a78339f3e3ffe24e6b69","nodeKey":"audit_math","submissionHash":"80a5040a56b9aadafccd0dca7619c9d3a495cd90cfeeec503760b91ab81430ac","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"50981","feedbackHash":"235d216ba2a4b16b4b21e34fe5d60371c38db2625d37305e2a387176c4da04b5","nodeKey":"audit_permissions","submissionHash":"22e2055f3456aa8f79cb9c27b887f8af59943968c7820c477407537f4d7b0bcc","tag1":"review:submission","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"ab5d1b3bbed7ffb244f30cb83a25fa5950bcd204dbe1035755a25d6b36f8dc2e","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"5739ce0d803a43cd","findings":[{"citation":"resolved","description":"_trackKill (and _releaseClosed, which it follows) cannot tell blow BOSS from older BOSS the same wallet already held, so a transfer out of the killer's wallet in the kill transaction is counted blow-first: the full transfer amount is deducted from the killer's held-blow counter (capped at what it holds) and the same proportional slice of the closed round's damage credit is moved to the recipient. A killer who batches the kill with an ordinary BOSS payment of older holdings in the same transaction (a smart-contract wallet or ERC-4337 account executing kill + transfer in one call, or any router script that forwards some older BOSS after the swap) therefore hands the entire killer prize and the entire killing-blow damage credit to the payee while still holding every wei of the blow BOSS. The README states the opposite for this case ('other BOSS of that round moving in the same transaction carries its credit but never the prize'). The recipient gains a windfall it did nothing for; the killer is only harmed by an action it chose, so no third party can trigger it, but the loss is total and the trigger is an ordinary batched payment. Fix: count only the part of the transfer that exceeds the sender's non-blow balance as blow BOSS, i.e. blowOut = value > (balanceBefore - held) ? value - (balanceBefore - held) : 0, using the sender's balance (BossToken can pass balanceOf(from) after the transfer, or the hook can STATICCALL the trusted token); apply the same non-blow-first rule in _releaseClosed for the signer's closed-round credit, and correct the README sentence.","line":774,"path":"src/BossHook.sol","reproduction":"Fresh pool (INITIAL_HP = 1 IMD). Victim V holds 1,000,000 BOSS from an earlier round or the deployer (no credit). In ONE transaction with tx.origin = V, through a router that custodies nothing: (1) exact-input buy of 2 IMD on the BOSS pool (kills round 1; about 765,095 BOSS of blow are owed to the router); (2) poolManager.take(BOSS, V, all) so the blow arrives at V: results(1).killer == V, unclaimedKill[V] == killerPrize (13,500,000,000,000,000 wei), damage[1][V] == 1e18; (3) V transfers 1,000,000 older BOSS to merchant M (boss.transferFrom(V, M, 1_000_000 ether) by the router). Expected: V still holds all 765,095 blow BOSS, so results(1).killer stays V and damage[1][V] stays 1e18. Actual: _trackKill sets held[V] to 0 and held[M] to 765,095 BOSS, _recount moves the prize: results(1).killer == M, unclaimedKill[V] == 0, unclaimedKill[M] == 13,500,000,000,000,000; _releaseClosed moves all of the credit: damage[1][V] == 0, damage[1][M] == 1e18. The proof test asserts the killer stays V and fails on the current code.","severity":"low","snippet":"            if (amount > hf) amount = hf;","title":"Kill prize and killing credit move with any BOSS the killer sends in the kill transaction, even when the killer still holds the whole blow"}],"hash":"22e2055f3456aa8f79cb9c27b887f8af59943968c7820c477407537f4d7b0bcc","nodeId":"b3d0506a-be6a-4fa9-bfb3-cddec119d60c","outcome":"completed","summary":"Review complete. The findings file is in place and validated.\n\n**Outcome:** one substantiated finding, rated low, with a self-contained Foundry proof that fails on the current code. No critical, high or medium defect was found in the assigned area.\n\n**The finding** (`src/BossHook.sol:774`, in the kill-prize tracker): in the kill transaction, any BOSS the killer sends is counted as killing-blow BOSS first, because the hook cannot tell blow BOSS from older holdings in the same wallet. A killer who batches the kill with an ordinary BOSS payment of older holdings, for instance through a smart-contract or ERC-4337 wallet, hands the whole killer prize and the whole killing-blow damage credit to the payee while still holding every wei of the blow. The README states the opposite for this case. Only the killer's own action triggers it, hence low. The reproduction and proof are in `.imd-findings.json`; the proof file is `test/scratch/ProofPrizeFollowsOldBoss.t.sol`. The suggested fix counts only the part of a transfer that exceeds the sender's non-blow balance as blow BOSS, applying the same rule to the closed-round credit move.\n\n**What was checked and held up:**\n- Access control on every entry point: hook callbacks are pool-manager-only, `onBossTransfer` is token-only, `bindHook` is once-only and deployer-signed, `propose` is keeper-only, claims are pull-only to fixed destinations, `unlockCallback` rejects any unlock the hook did not start.\n- Pot and treasury accounting: fees minted as claims equal pot plus treasury plus reserved; payouts never exceed what is reserved; the treasury share is exactly 1% and cannot be redirected.\n- Attestation replay: chain id, verifying contract, expiry, low-s and v checks are present; an id is never accepted twice; a stale attestation is refused once the keeper re-proposes.\n- The per-transaction credit cap, locker scoping, flash-sell unpend, sink forfeit and prize re-decision were traced through every path including forfeits before a kill, heal-day multipliers, two kills in one transaction and claims between unlocks. No way was found to create net credit above HP removed, to underflow the bookkeeping, or to revert a transfer.\n- On-chain inputs: IMD at the pinned address is a non-proxy 18-decimal token, and the pinned AbilityBoard is live with the expected keeper.\n\n**Two observations outside the findings:** the full suite of 409 tests passes, but `forge fmt --check` already fails on six pre-existing test files in the tree, which the requester's own rules say must stay clean. Everything else the focus list names is either guarded in code or already listed in the README's known limits.","treeHash":null,"usage":{"cachedInputTokens":2595509,"inputTokens":578,"model":"claude-fable-5-1","outputTokens":75306,"runtime":"claude","turns":27,"wallClockMs":1141636}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"56b13350bba65fd7","findings":[{"citation":"resolved","description":"Exact-output buy and sell fee calculations round down, causing trades under 40 wei of paired currency to pay 0 fee. Treasury split also rounds down, so fees of 1-2 wei go entirely to pot.","line":423,"path":"src/BossHook.sol","reproduction":"Trade with poolIn = 38 wei: fee = 38 * 25000 / 975000 = 0. Treasury from 1-2 wei fee = 0, entire fee to pot.","severity":"info","snippet":"fee = poolIn * FEE_PIPS / (PIPS - FEE_PIPS);","title":"Fee rounding favors protocol on dust amounts"}],"hash":"80a5040a56b9aadafccd0dca7619c9d3a495cd90cfeeec503760b91ab81430ac","nodeId":"85a7e26f-6f74-4681-93d0-2eff7525bb07","outcome":"completed","summary":"I've completed a comprehensive math precision, boundary, and numerical gap audit of the BOSS system. Here's what I found:\n\n## Summary\n\nThe BOSS system's arithmetic is **sound**. All multiplications are safe from overflow with realistic values, divisions have proper zero-guards, rounding consistently favors the protocol on dust amounts (by design), and critical invariants hold across 4,096+ fuzz runs. No pot draining, credit inflation, treasury overpayment, or trade blocking vulnerabilities were found.\n\n## Key Findings\n\n**Precision & Rounding (Info-level only)**\n- Fee calculations round down (Solidity default), so trades under 40 wei of paired currency pay 0 fee\n- Treasury split from 1-2 wei fees rounds to 0, sending entire fee to pot\n- All proportional calculations (`_slice`, `share`) round down consistently\n- This is acceptable: dust-level precision loss that favors traders on tiny amounts\n\n**Overflow Safety**\n- All multiplication-before-division operations verified safe up to 10^30 wei (10^12 ether)\n- Maximum intermediates well below uint256 max (2^256)\n- uint64 types safe for rounds (584 billion years at 1 kill/second) and timestamps (year 584 billion)\n\n**Division Guards**\n- `BossMath.share`: returns 0 when `total == 0`\n- `_release` / `_releaseClosed`: return (0,0) when `owned == 0`  \n- `_accrue` / `_play`: early return when amount is 0\n- No division-by-zero risks\n\n**Credit Cap Mechanism**\n- `_cappedPending` correctly limits credit to HP removed: `pd = min(pending, h0 - h)`\n- Prevents inflation from flash sells, heal-heavy abilities, and price moves\n- Verified in audit test suite (job be67d693 fixes)\n\n**Pot Conservation**\n- `BossMath.splitPot` conserves exactly: `seed + killerPrize + damagePool == pot`\n- Fuzz-verified across all values including edge cases\n\n**Attestation Replay Prevention**\n- Monotonic check: `id <= acceptedId` rejects replays\n- Combined with expiry checks (`issuedAt`, `expiresAt`) and requestId uniqueness\n- Each ability ID accepted once only\n\n**Ability Multiplier Bounds**\n- Clamped to [0.25x, 4x] via `MIN_MULT_BPS` (2,500) and `MAX_MULT_BPS` (40,000)\n- Applied in `_abilityMults` after ability call\n- Prevents value extraction through extreme multipliers\n\n**Treasury Overpayment**\n- Cannot overpay: `treasuryOwed` tracks exact 40% of fees\n- `claimTreasury` zeroes `treasuryOwed` before payout\n- Burns exact amount from hook's ERC-6909 claims\n- Protected by `nonReentrant` guard\n\n## Invariants Verified\n\nThe invariant test suite (64 runs, 4,096 calls, 0 reverts) confirms:\n- Money accounting closes: `totalFees == paidOut + treasuryPaid + hookClaims`\n- HP always within `[maxHp/4, maxHp*16]`\n- Closed rounds never overpay damage pools\n- Credit never exceeds BOSS held\n- Open round eligible damage equals sum of held damage\n- No credit accumulates on the hook (sink property)\n\n## Result\n\n**No critical, high, or medium findings.** One info-level observation about dust-amount rounding documented in `.imd-findings.json`. The math is production-ready.","treeHash":null,"usage":{"cachedInputTokens":2388434,"inputTokens":44,"model":"claude-fable-5-1","outputTokens":16893,"runtime":"claude","turns":54,"wallClockMs":386325}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"759c614fdc84ff66","findings":[{"citation":"resolved","description":"In `_play`, a sell always takes back this transaction's pending credit for the BOSS it hands in (`_unpend`), even when that BOSS really comes from the wallet. The netted router shape moves no token for the sell, so that take-back is the only charge, and it is correct. A router can also settle the same unlock gross: it pays the sell's BOSS into the PoolManager with sync/transfer/settle, then takes all of the buy's BOSS (Universal Router SETTLE with an explicit amount followed by TAKE does this). That real transfer into the pool also reaches `onBossTransfer` -> `_release`, which forfeits the wallet's existing round credit for the same BOSS. The second charge is not offset: `_unpend` already removed that BOSS's share of the pending credit, and the take then credits the remaining pending to only `pb - s` bought BOSS while the wallet receives all of the buy's BOSS. The result is two economically identical trades (same balances, same HP, same fees) with different credit, depending only on how the router settles. Honest players lose credit: s/b0 of their earlier damage is forfeited a second time. Nobody gains, because the forfeited damage leaves eligibleDamage, so this is a correctness and fairness defect, not a theft. It is not among the README's known limits; review 4's F1 (cap room) was a different double charge.\n\nFix: when BOSS from a wallet enters the pool in the same transaction and round as a sell that already took back pending credit, net that BOSS against the BOSS `_unpend` handed back before forfeiting. For example, keep a transient `T_UNPENDED_BOSS` raised in `_unpend` and consumed (not forfeited) first in the sink branch of `onBossTransfer`, and credit the matching `bought` on arrival. Alternatively, defer the sell's take-back until settlement shows whether the BOSS came from the pending buy or from a wallet.","line":515,"path":"src/BossHook.sol","reproduction":"Pool BOSS/IMD, initial HP 1 IMD, no ability. Tx 1: wallet W (its own router) buys 0.2 IMD exact-in and takes the BOSS, giving damage[1][W] = d0 = 0.2e18 and bought b0. Tx 2, one unlock, two shapes of the same trade: buy 0.2 IMD exact-in, then sell s = b0/2 BOSS exact-in. (a) Net: take the owed BOSS (B - s). (b) Gross: sync/transfer s BOSS from W to the PoolManager and settle, then take all B BOSS. Both shapes leave W with the same BOSS balance and the boss with the same HP. Expected: equal credit. Actual: (a) damage[1][W] = 299982983419970910, (b) damage[1][W] = 199982983419970910. The gross shape loses 0.1 IMD = d0*s/b0 of credit. Run: forge test --match-path test/scratch/GrossSettle.t.sol","severity":"low","snippet":"            _unpend(r, bossAmount);","title":"A buy and a sell in one unlock, settled gross rather than net, charge the sold BOSS twice (pending take-back plus forfeit)"},{"citation":"resolved","description":"Once a round closes, its credit is meant to be frozen. In any later transaction `onBossTransfer` only touches the open round, and the README says a closed round is never forfeited. The exception, so that the killing blow can travel with its BOSS, is keyed only on `T_KILL_ROUND != 0`. `_releaseClosed(kr, from, value)` then moves closed-round damage and bought for every `from` holding round-kr BOSS, not just for the blow's custodians (the signer's exclusion of kd/kb is the only special case). In a multi-party transaction (an ERC-4337 bundle, a relayer multicall, a solver batch), a player who only sends BOSS after someone else's kill hands over a share of their round-kr claim. The share is the transfer as a fraction of their round-kr bought, and it goes to the recipient. The same transfer one transaction later moves nothing. So the payout of an uninvolved player depends on which unrelated operations the bundler packed before theirs. The damage pool total is unchanged (credit is conserved), but it is redistributed from the player to whoever they paid. The README documents that the killing blow travels and that bundle placement affects the credit cap. It does not document that other holders' closed-round credit becomes mobile.\n\nFix: restrict closed-round travel to the killing blow's credit. For example, track per wallet in transient storage the closed-round damage and BOSS that arrived through `_arrive`'s kill path or a hop from a holder of it (the same holders `_trackKill` already follows), and let `_releaseClosed` release only that tracked amount instead of the wallet's whole round-kr balance.","line":714,"path":"src/BossHook.sol","reproduction":"Pool BOSS/IMD, initial HP 1 IMD. Tx 1: carol (smart wallet) buys 0.3 IMD and takes the BOSS, so damage[1][carol] = 0.3e18. Separate transactions: Tx 2, killer buys 2 IMD (kills round 1). Tx 3, carol transfers half her BOSS to dave. Result: damage[1][carol] = 0.3e18 and damage[1][dave] = 0. One bundle (tx.origin = bundler) with the same two operations, the kill and then carol's transfer of half her BOSS to dave. Expected: carol keeps 0.3e18. Actual: damage[1][carol] = 0.15e18 and dave receives 0.15e18, so carol's claimDamage(1) halves. Run: forge test --match-path test/scratch/ClosedRoundBystander.t.sol","severity":"low","snippet":"        if (!sink && !damageClaimed[kr][from]) {","title":"In a transaction that closed a round, any bystander's closed-round credit moves with their BOSS transfers (frozen in every other transaction)"}],"hash":"9b9684dc2a0645f11dd4774c287c9259637efa418487ade164a00003483457de","nodeId":"5ca90914-8632-4e9b-9aeb-a7bb47b2ced9","outcome":"completed","summary":"I found no critical, high or medium issues: nobody can drain or lock the pot, steal another wallet's prize, or replay an attestation. Two low-severity defects are confirmed with Foundry tests that fail on the current code. Both are in `.imd-findings.json`, each with its test source.\n\n## Findings\n\n**1. Low: the same trade can be charged twice, depending on how the router settles** (`src/BossHook.sol:515`, `_unpend`)\n- **Setup:** a wallet earned 0.2 IMD of credit (`d0`) in an earlier transaction. Later, in one unlock, it buys another 0.2 IMD of BOSS and sells half the BOSS it bought before.\n- **Settled net** (the pool nets the BOSS of the buy and the sell): it ends with 0.29998 IMD of credit.\n- **Settled gross** (the router pays the sold BOSS into the pool, then takes all the bought BOSS): it ends with 0.19998 IMD.\n- **Why:** the sell takes back pending credit for that BOSS, and the real transfer into the pool also forfeits the wallet's older credit for the same BOSS.\n- **Effect:** both shapes leave the same balances and the same HP, but honest players lose `d0 × sold / bought` of credit. Nobody gains, because the forfeited damage leaves the pool's total.\n- **Fix:** net BOSS paid into the pool against BOSS the sell already took back in the same transaction before forfeiting anything.\n\n**2. Low: during a transaction with a kill, anyone's closed-round credit moves with their BOSS** (`src/BossHook.sol:714`, `_releaseClosed`)\n- **Setup:** carol holds 0.3 IMD of round-1 credit. She sends half her BOSS to dave.\n- **In a separate transaction after the kill:** she keeps 0.3, as it should be once the round is closed.\n- **In the same bundle, after someone else's kill:** she drops to 0.15 and dave gets 0.15.\n- **Why:** the rule that lets the killing blow's credit travel applies to every holder of that round's BOSS, not only to the blow's own BOSS.\n- **Effect:** in a bundle or relayer multicall, an uninvolved player's payout depends on what was placed before them. The total pool is unchanged; credit just moves to whoever they paid.\n- **Fix:** let only the blow's credit travel, tracking the wallets that received it the same way the prize is already tracked.\n\n## What else I checked\nThese all held up when I traced them by hand:\n- **Money:** pot, treasury and reserved funds always match the hook's claims. A closed round's damage never adds up to more than its snapshot, so claims can't overpay, and a wallet that already claimed can't move its credit to a fresh wallet. Fees are right on all four swap shapes.\n- **Credit:** the net-credit cap holds across sells, forfeits, router changes, kills and two kills in one transaction. Credit given never exceeds the damage actually dealt.\n- **Prize:** it moves only within the kill transaction, only through BOSS that left the pool with the blow, and can't be paid twice or taken from a wallet's older prizes.\n- **Blocking:** I found no way to make a transfer revert. Payouts can't be called while the PoolManager is unlocked, so they can't be re-entered mid-swap.\n- **Oracle:** each approval is tied to one proposal id, to this hook and to this chain, with an expiry and a check against malleable signatures. An id is accepted once, and a proposal's ability can't change without a new id.\n\n**Known limits:** I didn't re-report anything the README already lists, notably selling older BOSS from another wallet to heal the boss without forfeiting credit, flash kills, and the per-router limits.\n\n**Coverage:** I ran only my two tests, not the project's full suite, and no Slither or invariant runs. The test files under `test/scratch/` were deleted afterwards; their full source is in the findings file.","treeHash":null,"usage":{"cachedInputTokens":2951534,"inputTokens":44,"model":"claude-opus-5-5","outputTokens":80581,"runtime":"claude","turns":26,"wallClockMs":911238}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"ca080fd306399669","findings":[{"citation":"resolved","description":"The README rule is that a closed round is never forfeited: BOSS of a closed round handed back to the pool leaves its credit where it was. That holds in a transaction of its own, because T_KILL_ROUND is zero and onBossTransfer returns before this block. Inside the transaction that closed the round (kr != 0), every later hop of any wallet's round-kr BOSS runs _releaseClosed, which moves the proportional credit to the hop's recipient. A sell through a custodying router (Universal Router: Permit2 pulls the BOSS to the router, the router settles it to the PoolManager) is two hops: wallet -> router moves the credit onto the router, router -> PoolManager is a sink and leaves it there. The seller did nothing different from a sell in their own transaction, yet loses the credit; it is now claimable for the router (claimDamage(kr, router) is permissionless), and on the Universal Router anyone can SWEEP the IMD out afterwards, so a bundler that orders a victim's sell op after a kill op in its bundle ends up with the victim's damage share. The kill-credit hop is only needed for BOSS that left the pool with the killing blow in this transaction; applying it to every wallet's closed-round BOSS is the defect. Fix: in the hop path, move closed-round credit only for BOSS the hook has tracked as blow BOSS held by `from` (the T_KILL_HELD count), or skip the hop when `from` did not receive BOSS from the pool in this transaction; the open-round path already handles the current round.","line":714,"path":"src/BossHook.sol","reproduction":"Fixture: BossFixture (INITIAL_HP 1 ether). Tx 1: bob buyExactIn 0.3 ether -> damage(1,bob) = 0.3e18. Tx 2, one transaction from a bundler EOA through ScriptRouter.runMany with two unlocks: unlock A = [buy(2 ether) (alice's killing blow, round 1 closes), takeBoss(alice, max)]; unlock B = [pullFrom(bob, router, bobBoss/2), sellOwn(bobBoss/2)] (bob's sell, wallet -> router -> pool). Expected (same as when unlock B runs in its own transaction, which test_separateTx shows): damage(1,bob) stays 0.3e18, damage(1,router) = 0. Actual: damage(1,bob) = 0.15e18, damage(1,router) = 0.15e18, claimableDamage(1,router) = 2328750000000000 wei, i.e. half of bob's share of the round-1 damage pool is now payable to the router (test/scratch/Probe1.t.sol::test_bundled_closedRoundSellStrandsCreditOnRouter fails with 150000000000000000 != 300000000000000000).","severity":"medium","snippet":"        if (!sink && !damageClaimed[kr][from]) {\n            (uint256 kd, uint256 kb) = _releaseClosed(kr, from, value);\n            if (kb != 0) {\n                damage[kr][to] += kd;\n                bought[kr][to] += kb;\n                emit DamageMoved(kr, from, to, kd, kb);\n            }\n        }","title":"Closed-round credit of a bystander is stripped and stranded on a custodying router when their sell is bundled after a kill"},{"citation":"resolved","description":"The only on-chain check on a proposed ability is that the address has code. Since Pectra, an EOA that signed a 7702 authorization has 23 bytes of code (0xef0100 || target), so it passes. Such an address is not a contract with a fixed, verifiable body: its owner can re-delegate it to any other code at any time with a new authorization, and the hook keeps calling the same `activeAbility` address. The 12-hour public delay, the 24-hour interval and the oracle review (conditions a to e are checked against the source at that address at review time) are then bypassed for every later change of behaviour. The effect stays bounded by the hook's clamp [0.25x, 4x] and the STATICCALL (no money moves), which is why this is low, but it is a concrete way the ability in force can change daily without the oracle, which the trust model says cannot happen. Preconditions: the keeper proposes such an address and the panel approves it once (an explorer shows the delegation target's verified source, which can satisfy a reviewer). Fix: in propose (or in acceptAbility), reject addresses whose code starts with 0xef0100 (and, more generally, require extcodesize > 23 or compare extcodehash to the proposal), so an ability is always an immutable contract.","line":33,"path":"src/AbilityBoard.sol","reproduction":"vm.etch(eoa, abi.encodePacked(hex\"ef0100\", target)) so eoa.code.length == 23 (the exact shape an EOA has after a 7702 authorization); vm.prank(keeper); board.propose(eoa, \"swarm job 123\"). Expected: revert (not an immutable contract). Actual: succeeds, pending() returns (1, eoa) (test/scratch/Probe3.t.sol::test_proposeAcceptsDelegationDesignator passes, showing the acceptance). On mainnet the EOA's owner can later sign a new authorization pointing to different code and the hook's next swap runs it under the same activeAbility address.","severity":"low","snippet":"        if (ability_.code.length == 0) revert NoCode();","title":"AbilityBoard.propose accepts an EIP-7702 delegated EOA as an ability, whose code can be re-pointed after approval without a new proposal or delay"},{"citation":"resolved","description":"The hook derives the expected launch tick from the ordering it sees (-129000 when BOSS is currency0, +129000 when it is currency1) and accepts a start within 300 ticks of it. launch.json carries a single fixed pool.initialPrice = 125262255113908064987203232, which is getSqrtPriceAtTick(-129000) and is right only when the deployed BOSS address is numerically below IMD (0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7). The token address is assigned by the factory at deploy time ($token), so the ordering is not known when the manifest is written. The README notes the mirrored value, but the manifest cannot express both. This is a safe failure (the hook refuses, nothing launches unscored), not a loss, but it is a concrete way the launch transaction reverts. Fix: have the launch compute the start price from the ordering (hook.launchSqrtPriceX96(bossIsCurrency0) exists for this), or mine the token address so BOSS sorts below IMD, or document that the manifest value must be swapped for 50111677533496076234078224273595 when BOSS sorts above IMD.","line":337,"path":"src/BossHook.sol","reproduction":"Deploy BossToken at an address above IMD (CREATE2 salt loop), deploy the hook with launchTickAbs 129000 and pairedCurrency = IMD, then call manager.initialize(key(IMD, BOSS, 12500, 60, hook), 125262255113908064987203232): reverts (WrongStartingPrice(sqrtPrice, -129000, 129000), wrapped by the PoolManager). The same key with getSqrtPriceAtTick(129000) = 50111677533496076234078224273595 initializes (test/scratch/Probe2.t.sol::test_manifestPrice_bossAboveIMD_reverts). test_manifestPriceValue checks that 125262255113908064987203232 == getSqrtPriceAtTick(-129000).","severity":"info","snippet":"        if (tick < expectedTick - LAUNCH_TICK_TOLERANCE || tick > expectedTick + LAUNCH_TICK_TOLERANCE) {\n            revert WrongStartingPrice(sqrtPriceX96, tick, expectedTick);","title":"launch.json pins the start price for one currency ordering only; if the factory's BOSS address sorts above IMD, initialize reverts WrongStartingPrice and the launch fails"}],"hash":"cf161486c444539d034d025373b5017c4977e34235a56a6c8946ae2ab732fb6f","nodeId":"6fa8455b-3533-401f-b755-43e0e9e70e8f","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds three validated findings. No source files were changed; only the findings file and probes under `test/scratch/` were written.\n\n**What I covered**\n\nI read all of `BossHook`, `BossToken`, `OracleGate`, `AbilityBoard`, `BossMath`, the interfaces, `launch.json`, the README's known limits and the prior review notes, then traced the focus items: pot drain or lock, prize and credit theft, credit without net damage, trade or transfer blocking, attestation replay, and treasury overpayment. I checked every arithmetic path in `onBossTransfer` for a revert that could block transfers and found none. The fee machinery, pot split, claim accounting, locker scoping, HP-removed cap and the single-attestation-per-id rule all held up against the counterexamples I tried.\n\n**Findings**\n\n- **Medium, `src/BossHook.sol:714`.** In the transaction that closes a round, every later hop of any wallet's closed-round BOSS moves its credit to the hop's recipient. A bystander's sell through a custodying router (wallet to router to pool) therefore strips half their round credit onto the router, where `claimDamage` is permissionless and, on the Universal Router, sweepable by anyone. The same sell in its own transaction leaves the credit untouched. Reproduced in `test/scratch/Probe1.t.sol`: bob's credit drops from 0.3e18 to 0.15e18 and the router becomes claimable for 2328750000000000 wei.\n- **Low, `src/AbilityBoard.sol:33`.** The board accepts any address with code, including an EIP-7702 delegated EOA (23 bytes). Its owner can re-point the code after oracle approval, bypassing the delay and review. Effect stays within the clamp. Reproduced in `test/scratch/Probe3.t.sol`.\n- **Info, `src/BossHook.sol:337`.** `launch.json` pins one start price. If the factory's BOSS address sorts above IMD, `initialize` reverts and the launch fails safely. Reproduced in `test/scratch/Probe2.t.sol`, which also confirms the manifest value equals the sqrt price at tick -129000.\n\n**Limits of this review**\n\nNo critical or high finding emerged, so no self-contained Foundry proof file was attached. The documented two-wallet heal trick and router-scoped bundling limits were left as agreed rules. The mainnet board, keeper and oracle signer addresses in the manifest could not be verified offline.","treeHash":null,"usage":{"cachedInputTokens":3439797,"inputTokens":738,"model":"claude-fable-5-1","outputTokens":82484,"runtime":"claude","turns":24,"wallClockMs":1340181}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"9df7d5d52e83c572","findings":[{"citation":"resolved","description":"Once a round closes its credit is meant to be frozen: in any later transaction `onBossTransfer` returns before the closed-round block (T_KILL_ROUND is zero) and the README says a closed round is never forfeited. The exception that lets the killing blow travel with its BOSS is keyed only on `T_KILL_ROUND != 0`, so inside the transaction that closed round kr every later hop of ANY wallet's round-kr BOSS runs `_releaseClosed(kr, from, value)` and moves the proportional share of that wallet's closed-round damage to the hop's recipient; only the signer's undelivered blow is excluded. In a multi-party transaction (an ERC-4337 bundle, a relayer multicall, a solver batch) a player whose operation runs after someone else's kill therefore loses a share of their round-kr claim: (1) a sell through a custodying router (Universal Router + Permit2: wallet -> router is a hop that moves the credit onto the router, router -> PoolManager is a sink that leaves it there) strands the credit on the router, where `claimDamage(kr, router)` is permissionless and pays IMD to the router, which anyone can SWEEP out of the Universal Router; (2) a plain transfer to another wallet hands the payee the share. The same operation one transaction later moves nothing, so a player's payout depends on what the bundler packed before it, and a bundler that lands its own kill op in front of a victim's queued sell op collects the victim's damage share. The damage pool total is conserved (eligibleDamage unchanged), so this is redistribution from the bystander to the router or payee, not inflation. Not among the README's known limits (it documents only that the killing blow travels). Two specialist reports (economics: transfer to another wallet; flow: sell through a custodying router) describe this one mechanism and are merged here. Fix: restrict closed-round travel to the killing blow's credit, e.g. on a hop move closed-round damage only for BOSS the hook tracks as blow BOSS held by `from` (the T_KILL_HELD count that `_trackKill` already keeps, slicing the kill credit that arrived through `_arrive` proportionally), and leave every other wallet's round-kr credit where it is.","line":714,"path":"src/BossHook.sol","reproduction":"Fresh pool (INITIAL_HP = 1 IMD). Tx 1: bob buys 0.3 IMD exact-in via PoolSwapTest -> damage[1][bob] = 0.3e18. One transaction from a bundler EOA (tx.origin = bundler) that calls a router twice: (a) buyAndTake(2 ether, alice): exact-in buy that kills round 1, all BOSS taken to alice; (b) sellViaCustody(bob, half of bob's BOSS): transferFrom(bob -> router) then an exact-in sell settled from the router. Expected (identical to running (a) and (b) as two transactions, which the test checks first): damage[1][bob] = 0.3e18, damage[1][router] = 0. Actual: damage[1][bob] = 0.15e18, damage[1][router] = 0.15e18 and claimableDamage(1, router) > 0. Variant: replace (b) by transferBoss(bob -> dave, half): expected dave 0, actual bob 0.15e18 / dave 0.15e18. Run: forge test --match-path test/scratch/ClosedRoundBystander.t.sol (both tests fail with 150000000000000000 != 300000000000000000).","severity":"medium","snippet":"        if (!sink && !damageClaimed[kr][from]) {","title":"Closed-round credit of any bystander moves with their BOSS inside the transaction that closed the round (stranded on a custodying router, or handed to a payee)"},{"citation":"resolved","description":"`_trackKill` (and `_releaseClosed`, which runs before it on the same hop) cannot tell blow BOSS from older BOSS the same wallet already held, so a transfer out of a blow holder is counted blow-first: the whole transfer amount, capped at the holder's held-blow count, is deducted from `from` and added to `to`, `_recount` then hands the prize to the largest holder, and `_releaseClosed` moves the same proportional slice of the closed round's damage credit. A killer who batches the kill with an ordinary BOSS payment of older holdings in the same transaction (a smart-contract or ERC-4337 account executing kill + transfer in one call, or a router script that forwards some older BOSS after the swap) hands the entire killer prize and the entire killing-blow damage credit to the payee while still holding every wei of the blow. The README states the opposite for this case: 'other BOSS of that round moving in the same transaction carries its credit but never the prize'. Only the killer's own action triggers it (no third party can), but the loss is total and the trigger is an ordinary batched payment. Fix: count only the part of the transfer that exceeds the sender's non-blow balance as blow BOSS: after the transfer, blowOut = held > balanceOf(from) ? held - balanceOf(from) : 0 (one STATICCALL to the trusted BossToken, or have the token pass the sender's remaining balance), apply the same non-blow-first rule in `_releaseClosed` for the signer's closed-round credit, and keep the README sentence as the rule.","line":774,"path":"src/BossHook.sol","reproduction":"Fresh pool (INITIAL_HP = 1 IMD). Killer V holds 1,000,000 BOSS received from the deployer (no credit). ONE transaction with tx.origin = V through V's own router that custodies nothing: (1) exact-input buy of 2 IMD kills round 1; (2) poolManager.take(BOSS, V, all) delivers the blow (about 765,000 BOSS) to V: results(1).killer == V, unclaimedKill[V] == killerPrize (13,500,000,000,000,000 wei), damage[1][V] == 1e18; (3) boss.transferFrom(V, M, 1_000_000 ether) by the router: only older BOSS leaves, V keeps every wei of the blow. Expected: results(1).killer stays V, unclaimedKill[V] == killerPrize, unclaimedKill[M] == 0 (and damage[1][V] stays 1e18). Actual: held[V] is set to 0 and held[M] to the whole blow, _recount moves the prize: results(1).killer == M, unclaimedKill[V] == 0, unclaimedKill[M] == killerPrize; _releaseClosed moves all of the credit: damage[1][V] == 0, damage[1][M] == 1e18. Run: forge test --match-path test/scratch/BlowFirstPrize.t.sol (fails: killer is M, not V).","severity":"low","snippet":"            if (amount > hf) amount = hf;","title":"Kill prize and killing credit move with any BOSS the killer sends in the kill transaction, even older BOSS, while the killer still holds the whole blow"},{"citation":"resolved","description":"In `_play` a sell always takes back this transaction's pending credit for the BOSS it hands in (`_unpend`), even when that BOSS really comes from the wallet. For the netted router shape (no token moves for the sell, one take of the difference) that take-back is the only charge and it is correct. A router can also settle the same unlock gross: it pays the sell's BOSS into the PoolManager with sync/transfer/settle from the wallet and takes all of the buy's BOSS (the Universal Router's SETTLE with an explicit amount followed by TAKE does this). That real transfer into the pool also reaches `onBossTransfer` -> `_release`, which forfeits the wallet's existing round credit for the same BOSS. The second charge is not offset: `_unpend` already removed that BOSS's share of the pending credit, and the take then credits the remaining pending to only `pb - s` bought BOSS while the wallet receives all of the buy's BOSS. Two economically identical trades (same balances, same HP, same fees) end with different credit, depending only on how the router settles. Honest players lose credit (s/b0 of their earlier damage is forfeited a second time); nobody gains directly, since the forfeited damage leaves eligibleDamage, so this is a correctness and fairness defect, not a theft. Not among the README's known limits. Fix: when BOSS from a wallet enters the pool in the same transaction and round as a sell that already took back pending credit, net that BOSS against the BOSS `_unpend` handed back before forfeiting (for example keep a transient counter raised in `_unpend` and consumed, not forfeited, first in the sink branch of `onBossTransfer`, crediting the matching bought on arrival), or defer the sell's take-back until settlement shows whether the BOSS came from the pending buy or from a wallet.","line":515,"path":"src/BossHook.sol","reproduction":"Pool BOSS/IMD, INITIAL_HP 1 IMD, no ability. Tx 1: wallet W (its own router) buys 0.2 IMD exact-in and takes the BOSS: damage[1][W] = 0.2e18, bought b0. Tx 2, one unlock, two shapes of the same trade: buy 0.2 IMD exact-in, then sell s = b0/2 BOSS exact-in. (a) Net: take the BOSS owed (B - s) to W. (b) Gross: sync/transferFrom s BOSS from W to the PoolManager and settle, then take all B BOSS to W. Both shapes leave W the same BOSS balance and the boss the same HP (the test asserts both). Expected: equal credit. Actual: (a) damage[1][W] = 299982983419970910, (b) damage[1][W] = 199982983419970911; the gross shape loses 0.1 IMD = d0 * s / b0 of credit. Run: forge test --match-path test/scratch/GrossSettle.t.sol (fails with 199982983419970911 != 299982983419970910).","severity":"low","snippet":"            _unpend(r, bossAmount);","title":"A buy and a sell in one unlock, settled gross rather than net, charge the sold BOSS twice (pending take-back plus forfeit)"},{"citation":"resolved","description":"The only on-chain check on a proposed ability is that the address has code. Since Pectra an EOA that signed an EIP-7702 authorization carries 23 bytes of code (0xef0100 || target), so it passes, and EXTCODESIZE reports 23 for it. Such an address is not a contract with a fixed, verifiable body: its owner can re-delegate it to any other code at any time with a new authorization, and the hook keeps STATICCALLing the same `activeAbility` address. The 12-hour public delay, the 24-hour interval and the oracle review (conditions a to e are checked against the source at that address at review time) are then bypassed for every later change of behaviour, contradicting the trust model ('The keeper cannot activate an ability without the oracle'). The effect stays bounded by the hook's clamp [0.25x, 4x] and the STATICCALL (no money moves), which is why this is low. Preconditions: the keeper proposes such an address and the panel approves it once (an explorer shows the delegation target's verified source, which can satisfy a reviewer). Fix: in `propose` (or in `acceptAbility`) reject addresses whose code starts with 0xef0100 (equivalently require code.length > 23, or pin extcodehash in the proposal and check it at accept and at activation), so an ability is always an immutable contract.","line":33,"path":"src/AbilityBoard.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity 0.8.26;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {PoolManager} from \"v4-core/src/PoolManager.sol\";\nimport {IPoolManager} from \"v4-core/src/interfaces/IPoolManager.sol\";\n\nimport {HookFlags} from \"src/HookFlags.sol\";\nimport {BossToken} from \"src/BossToken.sol\";\nimport {BossHook} from \"src/BossHook.sol\";\nimport {OracleGate} from \"src/OracleGate.sol\";\nimport {AbilityBoard} from \"src/AbilityBoard.sol\";\nimport {IAbilityBoard} from \"src/interfaces/IAbility.sol\";\n\n/// @dev Stands in for IMD; only `decimals()` matters here (no pool is initialized).\ncontract MockIMD {\n    uint8 public constant decimals = 18;\n}\n\n/// @dev An ability body with verified-looking source: the delegation target of the EOA.\ncontract SomeAbility {\n    function modifiers(bytes calldata) external pure returns (uint256, uint256) {\n        return (10_000, 10_000);\n    }\n\n    function name() external pure returns (string memory) {\n        return \"some ability\";\n    }\n}\n\n/// @title AbilityBoard.propose accepts an EIP-7702 delegated EOA as an ability\n/// @notice The only on-chain check on a proposed ability is `code.length != 0`. Since Pectra an\n/// EOA that signed a 7702 authorization carries 23 bytes of code (0xef0100 || target), so it\n/// passes, yet its owner can re-point that delegation to any other code at any time with a new\n/// authorization, without a new proposal, oracle review or 12-hour delay. The hook keeps calling\n/// the same `activeAbility` address, so the ability in force can change at will after one approval.\n/// The test passes once either the board's `propose` or the hook's `acceptAbility` refuses it.\ncontract Delegated7702Test is Test {\n    uint160 internal constant FLAGS = HookFlags.BEFORE_INITIALIZE | HookFlags.BEFORE_SWAP | HookFlags.AFTER_SWAP\n        | HookFlags.BEFORE_SWAP_RETURN_DELTA | HookFlags.AFTER_SWAP_RETURN_DELTA;\n    bytes32 internal constant QUESTION = keccak256(\"boss-ability-question-v1\");\n    uint256 internal constant ORACLE_KEY = 0x0DAC1E;\n\n    PoolManager internal manager;\n    MockIMD internal imd;\n    BossToken internal boss;\n    AbilityBoard internal board;\n    BossHook internal hook;\n    address internal keeper = makeAddr(\"keeper\");\n    address internal oracleSigner;\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        oracleSigner = vm.addr(ORACLE_KEY);\n        manager = new PoolManager(address(this));\n        imd = new MockIMD();\n        boss = new BossToken();\n        board = new AbilityBoard(keeper);\n        bytes memory args = abi.encode(\n            IPoolManager(address(manager)),\n            address(boss),\n            keeper,\n            IAbilityBoard(address(board)),\n            oracleSigner,\n            QUESTION,\n            uint256(1 ether),\n            uint24(129_000),\n            address(imd)\n        );\n        bytes memory initCode = abi.encodePacked(type(BossHook).creationCode, args);\n        (bytes32 salt, address predicted) = HookFlags.mineSalt(address(this), initCode, FLAGS, 300_000);\n        address addr;\n        assembly (\"memory-safe\") {\n            addr := create2(0, add(initCode, 32), mload(initCode), salt)\n        }\n        require(addr == predicted && addr != address(0), \"hook deploy failed\");\n        hook = BossHook(addr);\n    }\n\n    /// @dev Expected: a delegated EOA is not an immutable contract and is refused, by the board or\n    /// by the hook. Actual: the board accepts it and the hook queues it on a valid attestation.\n    function test_delegatedEoaIsNotAcceptedAsAnAbility() public {\n        address target = address(new SomeAbility());\n        address eoa = makeAddr(\"delegated-eoa\");\n        vm.etch(eoa, abi.encodePacked(hex\"ef0100\", target)); // the exact code an EOA has after a 7702 authorization\n        assertEq(eoa.code.length, 23, \"delegation designator\");\n\n        vm.prank(keeper);\n        (bool proposed,) = address(board).call(abi.encodeCall(AbilityBoard.propose, (eoa, \"swarm job 123\")));\n        if (!proposed) return; // refused at the board: fixed\n\n        (uint256 id, address pendingAbility) = board.pending();\n        assertEq(pendingAbility, eoa, \"the board holds the delegated EOA as pending\");\n        vm.warp(block.timestamp + 1);\n        OracleGate.Attestation memory a = _attestation(id * 2 + 1);\n        bytes memory sig = _sign(a);\n        (bool accepted,) = address(hook).call(abi.encodeCall(BossHook.acceptAbility, (a, sig)));\n        assertFalse(accepted, \"a delegated EOA must not be queued as the ability\");\n        assertEq(hook.queuedAbility(), address(0), \"nothing queued\");\n    }\n\n    function _attestation(uint256 answer) internal view returns (OracleGate.Attestation memory a) {\n        a.requestId = keccak256(abi.encode(\"req\", answer, block.timestamp));\n        a.chainId = block.chainid;\n        a.questionHash = QUESTION;\n        a.answerType = 3;\n        a.answer = abi.encode(answer);\n        a.panelJobId = keccak256(\"panel\");\n        a.panelSize = 21;\n        a.quorum = 15;\n        a.agreed = 18;\n        a.issuedAt = uint64(block.timestamp);\n        a.expiresAt = uint64(block.timestamp + 1 hours);\n    }\n\n    function _sign(OracleGate.Attestation memory a) internal view returns (bytes memory) {\n        (uint8 v, bytes32 r, bytes32 s) = vm.sign(ORACLE_KEY, hook.attestationDigest(a));\n        return abi.encodePacked(r, s, v);\n    }\n}","reproduction":"vm.etch(eoa, abi.encodePacked(hex\"ef0100\", target)) so eoa.code.length == 23, the exact code shape an EOA has after a 7702 authorization; vm.prank(keeper); board.propose(eoa, \"swarm job 123\"). Expected: revert (not an immutable contract). Actual: succeeds and pending() returns (1, eoa); then, with a valid oracle attestation for answer id*2+1 (issuedAt > proposedAt), hook.acceptAbility(a, sig) queues the delegated EOA (queuedAbility == eoa) instead of refusing it. On mainnet the EOA's owner can later sign a new authorization pointing to different code and the hook's next swap runs it under the same activeAbility address. Run: forge test --match-path test/scratch/Delegated7702.t.sol (fails: 'a delegated EOA must not be queued as the ability'; it passes once either propose or acceptAbility refuses the address).","severity":"low","snippet":"        if (ability_.code.length == 0) revert NoCode();","title":"AbilityBoard.propose accepts an EIP-7702 delegated EOA as an ability, whose code can be re-pointed after approval without a new proposal, oracle review or delay"},{"citation":"resolved","description":"The hook derives the expected launch tick from the ordering it sees (-129000 when BOSS is currency0, +129000 when it is currency1) and accepts a start within 300 ticks of it. The machine-readable manifest field pool.initialPrice carries the single value 125262255113908064987203232 = getSqrtPriceAtTick(-129000), which is right only when the deployed BOSS address is numerically below IMD (0xd34a99bc0f67ae1bbd63c660e6d0b0dd03e263b7). The token address is assigned by the factory at deploy time ($token), so the ordering is not known when the manifest is written; the manifest's free-text notes state the mirrored value (getSqrtPriceAtTick(129000) = 50111677533496076234078224273595) but the field cannot express both and nothing in the tree selects between them. This is a safe failure (the hook refuses, nothing launches unscored), not a loss, but it is a concrete way the launch transaction reverts about half of the time. Fix: have the launch compute the start price from the ordering (hook.launchSqrtPriceX96(bossIsCurrency0) exists for this), or mine the token address so BOSS sorts below IMD, or make the launch tooling swap the value when BOSS sorts above IMD.","line":34,"path":"launch.json","reproduction":"Deploy BossToken at an address above the paired currency (CREATE2 salt loop), deploy the hook with launchTickAbs 129000 and pairedCurrency = IMD, then call manager.initialize(key(IMD, BOSS, 12500, 60, hook), 125262255113908064987203232): reverts (WrongStartingPrice(sqrtPrice, -129000, 129000), wrapped by the PoolManager). The same key with getSqrtPriceAtTick(129000) = 50111677533496076234078224273595 initializes, and with BOSS below IMD the manifest value initializes. Verified by test/scratch/LaunchPrice.t.sol (test_bossAboveImd_manifestPriceReverts, test_bossBelowImd_manifestPriceInitializes, test_manifestPriceIsTickMinus129000 all pass).","severity":"info","snippet":"    \"initialPrice\": \"125262255113908064987203232\"","title":"launch.json pins the start price for one currency ordering only; if the factory's BOSS address sorts above IMD, initialize reverts WrongStartingPrice and the launch fails"}],"hash":"def755ccbd1c5ff30893d22f6d03a549a2cd331b3d9af38117d72a66d332608b","nodeId":"0ad02249-3f46-430b-bbed-e9694c603ce3","outcome":"completed","summary":"The review is complete. The findings file at the repository root holds five substantiated findings, four of them with self-contained Foundry proofs that fail on the current code for the stated reason, and nothing outside that file and `test/scratch/` was changed.\n\n**What I kept, after reproducing each against the code**\n\n- **Medium, closed-round bystander credit.** The two specialist reports (economics: a transfer to another wallet; flow: a sell through a custodying router) are one mechanism: inside the transaction that closed a round, the closed-round hop path in `onBossTransfer` runs for every wallet's BOSS of that round, not just the killing blow's. Reproduced both shapes in one bundle: bob's round-1 credit drops from 0.3 to 0.15, with the other half stranded on the router or handed to the payee, while the same operations in separate transactions move nothing. Merged into one finding with the proof `test/scratch/ClosedRoundBystander.t.sol`.\n- **Low, blow-first prize tracking.** A killer who pays a merchant older BOSS in the kill transaction hands over the whole prize while still holding every wei of the blow. Reproduced: the killer of record becomes the merchant. Proof `test/scratch/BlowFirstPrize.t.sol`.\n- **Low, gross settlement double charge.** The same buy-then-sell unlock yields 0.29998 credit when settled net and 0.19998 when the wallet pays the sold BOSS in gross. Proof `test/scratch/GrossSettle.t.sol`.\n- **Low, EIP-7702 delegated EOA as an ability.** I extended the specialist's board-only check through the hook: a valid attestation queues the delegated EOA. The proof passes once either `propose` or `acceptAbility` refuses it. Proof `test/scratch/Delegated7702.t.sol`.\n- **Info, single-ordering launch price.** Verified that the manifest value initializes when BOSS sorts below IMD and reverts when it sorts above. Anchored to the manifest field, no proof.\n\n**What I dropped**\n\n- The fee-dust rounding note. The README already lists trades under 40 wei paying no hook fee as a known limit.\n\n**Checks and limits**\n\n- The project's own unit suites pass at baseline here (399 tests), so the proof failures are specific to the defects.\n- The specialists' proof files were not in this tree, so every proof is my own, each self-contained and run with `forge test --match-path`.\n- Not run: the invariant suite and the mainnet-fork Universal Router test, which needs an RPC. The claim that anyone can sweep IMD out of the Universal Router rests on its permissionless SWEEP command, not on a fork run.\n- Beyond the specialist leads I traced the pot and claim accounting, the attestation checks, the credit cap, and the transfer handler's revert paths, and found no further reproducible defect within the documented limits.","treeHash":null,"usage":{"cachedInputTokens":2620104,"inputTokens":546,"model":"claude-fable-5-1","outputTokens":86648,"runtime":"claude","turns":36,"wallClockMs":1206512}}],"verification":[]}