{"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":"965a5fce-0269-4f65-8079-bf6793d5774c","kind":"audit","nodes":[{"acceptedSubmissionHash":"47eb5599365c2f65c7a03ca2651b401743ebef3e1991d01053f45f5a5a28066c","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":"416a0393778220d793d30b4c02e01171e8e357be4c47e3cbe4cf5b3a373678b2","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":"74b5a94c059ad8cb2ecb56ed909381d86a52ee41e2d5c8c52b2e556dab22c3f3","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":"10a692afa2d7c4b3665ac8ab2b19b81af827a3cd94f9184f54da59228b2133f8","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":"e71966e80b300261b934e2f15b016df82e73170b6377cd3d1f734e9013468d88","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 open-round credit; once a round closes its credit is frozen except the killing blow's, which travels with its BOSS and keeps the prize with its largest holder (the router that executed the kill pays it out in proportion to its balance); 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 must be contracts written by the swarm, accepted only with an IMD oracle attestation, 21 seats, quorum 15). This commit fixes the findings of jobs be67d693, 853ff4df and 94b6c9a5; 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":"f55b32488ab20ba2bb95eb0427555971b6986ec80869b40da9241324cdf2f9fc","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"965a5fce-0269-4f65-8079-bf6793d5774c","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":"51538","feedbackHash":"a01bb9aadf811a5a1ebf703e3d5b538055bf598e1bbf9642a124891ba99f1a0d","nodeKey":"audit_economics","submissionHash":"47eb5599365c2f65c7a03ca2651b401743ebef3e1991d01053f45f5a5a28066c","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51041","feedbackHash":"200fec29070ebc135a66181ecb2ef5fed280130702ce10cca27eb38eceafb2f2","nodeKey":"audit_flow","submissionHash":"416a0393778220d793d30b4c02e01171e8e357be4c47e3cbe4cf5b3a373678b2","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51166","feedbackHash":"b54743f181be2e6509ce12b40a61bce6284a5c4fca8a7261a80f06085ed541e4","nodeKey":"audit_judge","submissionHash":"74b5a94c059ad8cb2ecb56ed909381d86a52ee41e2d5c8c52b2e556dab22c3f3","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52552","feedbackHash":"2d72a94b43fbf8963faf5e18113f291f3ee285d3d9c568323be14301d40798e1","nodeKey":"audit_math","submissionHash":"10a692afa2d7c4b3665ac8ab2b19b81af827a3cd94f9184f54da59228b2133f8","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52128","feedbackHash":"a35fa0b616776c118145397012d4ce7e8768c9ec7ad394e6c783ce7e4d65e93c","nodeKey":"audit_permissions","submissionHash":"e71966e80b300261b934e2f15b016df82e73170b6377cd3d1f734e9013468d88","tag1":"review:submission","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"7d7640e3ff9fcfe34743805e40e0b73cc4b3b7ad3ee58129fa8bdb24ead4f697","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"809ae05b6282abad","findings":[{"citation":"resolved","description":"In `_play`, a buy computes `dmg = BossMath.scale(gross, buyMult)` (line 522) and returns before `_pend`/`_kill` when the truncation is zero. With an ability at the legal clamp floor buyMult = 2500 (BossMath.MIN_MULT_BPS; an oracle-approved ability may set it), every buy with gross <= 3 wei is a complete no-op for the game: hp is unchanged, no pending damage is parked, and no `bought[round][buyer]` entry is made, so the BOSS that later leaves the pool for that buy carries no credit either. README.md line 195 claims 'A trade under 40 wei of IMD pays no hook fee (rounding) but still hits or heals'. The same truncation drops the heal side: `heal = BossMath.scale(gross, sellMult)` (line 532) is zero whenever the pool payout is below 10000/sellMult wei (e.g. a 1 wei BOSS sell, where the pool pays ~0 wei), so that sell heals nothing. Internal bookkeeping is self-consistent (no credit is created without the pend), and the amounts involved are ~10^15 times below gas cost, so this is a documentation-accuracy defect, not an exploit: no path lets a third party gain from the dropped trade. No other numeric lens issue was substantiated against this tree.","line":523,"path":"src/BossHook.sol","reproduction":"Foundry, on this tree (verified locally with a passing scratch test, test/scratch/Probes.t.sol::test_probe_minMult_3weiNoHit): activate a FixedAbility(2500, 2500) by writing hook.activeAbility via stdstore (the same setup as test/invariant/BossInvariant.t.sol lines 19-20); call the fixture helper buyExactIn(alice, 3) (3 wei of IMD, fee rounds to 0). Expected per README.md:195: the trade still hits the boss. Actual: hook.hp() stays 1 ether (1e18), no Hit event, and hook.eligibleDamage(1) stays 0. A control buyExactIn(alice, 4) does hit for exactly 1 wei (hp becomes 1e18 - 1), pinning the cutoff at gross*buyMult < 10000. On the sell side, a 1 wei exact-input sell moves the BOSS but the pool payout truncates to 0 wei, so scale(0, sellMult) = 0 and hp is unchanged.","severity":"info","snippet":"if (dmg == 0) return;","title":"A trade whose scaled damage truncates to zero is dropped before any game bookkeeping, so the README's 'a trade under 40 wei ... still hits or heals' does not hold below the multiplier-scaled cutoff"}],"hash":"10a692afa2d7c4b3665ac8ab2b19b81af827a3cd94f9184f54da59228b2133f8","nodeId":"87686161-aadc-4e25-a3e8-e4de5e56133c","outcome":"completed","summary":"The working tree shows no tracked modifications, `test/scratch/Probes.t.sol` exists as expected, and the findings file is validated in place. The review is complete.\n\n**Outcome:** I audited the math in `BossHook.sol`, `BossMath.sol`, `BossToken.sol`, `AbilityBoard.sol`, and `OracleGate.sol` against the Math Precision, Boundary, and Numerical Gap guides. No critical, high, or medium defect was substantiated. One info-level finding is filed in `.imd-findings.json`.\n\nWhat I verified, with concrete numbers and runs:\n\n- **Fee math**: all four swap shapes' fee/gross reconciliation (`src/BossHook.sol:415-447`) matches the tests' pinned rates; `_accrue` conserves exactly (`treasury = floor(fee·0.4)`, pot gets the ceil remainder); zero-fee trades below 40 wei revert no hidden state.\n- **Pot and claims**: `splitPot` conserves to the wei (10% seed, then 50/50 of the rest, damage half always claimable, `eligibleDamage ≥ 1` at every kill so the no-eligibility branch is unreachable); claim shares floor to `damagePool`, so closed rounds can never overpay.\n- **Credit cap algebra**: delivery is capped by `HP0 + forfeited − delivered − hp`, which is exactly the README's \"credited minus forfeited never exceeds HP removed\"; no sequence of buys, sells, heals, or forfeits I could construct (or fuzz) breaks it.\n- **Boundaries probed empirically**: a zero-fee 39-wei kill (fee 0, damage 39, split exact), 1-wei transfers and sells after a kill, odd-pot dust through all claims, and the 0.25x multiplier cutoff — accounting identity `claims == pot + treasuryOwed + totalReserved` held at every step. The full shipped suite also passes: 550 tests, 0 failures.\n\nThe one filed finding: with an ability at the legal 0.25x floor, buys of ≤ 3 wei truncate to zero damage and are dropped before any bookkeeping, and a sell whose pool payout is 0 wei heals nothing, so README line 195's \"under 40 wei … still hits or heals\" is inexact (`src/BossHook.sol:523`). Impact is dust-level; it is filed as documentation accuracy, not an exploit, and reproducibility details are in the finding.\n\nLimits of this review: the mainnet-fork tests are skipped without an RPC, so no live-chain behavior was exercised; and a clean result is evidence of depth, not proof of absence.","treeHash":null,"usage":{"cachedInputTokens":3627648,"inputTokens":251149,"model":"claude-fable-5-1","outputTokens":147796,"runtime":"claude","turns":47,"wallClockMs":1059093}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"3bed38612db34f32","findings":[{"citation":"resolved","description":"This is the agreed design (README: 'The wallet that lands the killing blow gets the killer share'), not a coding defect, and is reported so the launch documentation can state it. The killer prize is 45% of the pot (50% after the 10% seed) and goes to whoever makes hp reach 0, however small that hit is; damage already done only earns a share of the other 45%. The remaining HP is public state and every pending buy is visible in the mempool, so a searcher can front-run any buy that would kill with an exact-input buy of hpLeft (plus dust), take the killer prize, and the victim's buy lands on the fresh boss (1.5x HP). Nothing in the code limits this (no commit-reveal, no minimum share of the round's damage for the killer, no randomised close), and the README's Known limits do not mention it. Suggested handling, in order of least design change: document it and recommend a private relay (Flashbots Protect / MEV Blocker) for finishing buys in the README and app; or, if the economics are to change, weight the killer prize by the killing transaction's share of the round's damage. No fix is required if the race is accepted as part of the game.","line":529,"path":"src/BossHook.sol","reproduction":"BossFixture (test/utils/BossFixture.sol), INITIAL_HP = 1 ether. (1) alice buyExactIn 0.99 ether: hp left ~0.01 ether, alice holds ~99% of round-1 damage. (2) bob buyExactIn(hpLeft + 1e15) in the next transaction (the one a searcher places in front of alice's finishing buy). Actual: results(1).killer == bob, unclaimedKill[bob] == killerPrize == 6756750000000000; claimableDamage(1, alice) == 6689182500000000, claimableDamage(1, bob) == 67567500000000. bob, with ~1% of the damage, collects 6824317500000000 against alice's 6689182500000000. Verified with test/scratch/Probe.t.sol::test_probe_tinyLastHitTakesKillerPrize on this commit. Expected (if the design is meant to reward damage): the killing transaction's prize proportional to its damage, or documented guidance to finish kills through a private relay.","severity":"info","snippet":"            if (current == dmg) _kill(signer);","title":"Killer prize goes to the last hit regardless of contribution: the kill is a public-mempool race that a searcher wins by front-running the finishing buy"}],"hash":"416a0393778220d793d30b4c02e01171e8e357be4c47e3cbe4cf5b3a373678b2","nodeId":"d2a0fb95-65c2-4428-a1ca-ddc2b29713fa","outcome":"completed","summary":"The review is complete and the findings file is written and validated.\n\n**Outcome.** I found no exploitable defect in BossHook, BossToken, AbilityBoard or OracleGate at this commit. The one item in `.imd-findings.json` is an info-level design note, confirmed with a scratch test: the killer prize (45% of the pot) goes to whoever lands the last hit, so a mempool searcher can front-run a finishing buy with an exact buy of the remaining HP. In the probe, a wallet with about 1% of the round's damage collected more than the wallet that did 99%. The README's known limits do not mention this race, so the requester may want to document private-relay guidance or accept it as part of the game.\n\n**What I traced and why it held.**\n- Pot and reserves: fees are minted as claims and only leave through the three pull claims inside the hook's own unlock. The sum of each round's per-wallet damage equals the snapshotted eligible damage through every credit, forfeit, move and blow hop, so damage claims never exceed the pool. Prize moves are guarded by the current killer and the unclaimed balance.\n- Transfer blocking: every subtraction in `onBossTransfer` and its helpers is bounded by construction, including the rounded-up forfeit, the blow-credit slots and the cap anchor. I found no revert path beyond the caller check, so the token cannot be frozen by hook state.\n- Credit without net damage: the per-transaction cap only ever lowers credit, forfeits raise the anchor by real credit that left the game, and a locker switch only trims. The residual excess from heals that forfeit less than they heal is the limit the README already lists.\n- Prize and credit theft: the locker-proportional, contract-first and wallet-older-first hop rules, plus the paid flag and recount, behaved as documented in every path I walked, including fee-then-sweep, planted BOSS, 4337 bundler as signer and 7702 wallets. Each case I could construct is in the README's known limits.\n- Attestation replay: replay is blocked by the monotonic accepted id, the current pending id in the answer, the chain id and verifying-contract domain, low-s and v checks, and the stale-issuance check. An attestation cannot serve a different proposal.\n- Treasury: fixed 40% of the 2.5% fee, pull-only to the fixed keeper.\n\n**Checks run.** `forge build` and the full suite without the fork test: 550 tests passed. My two probes under `test/scratch/` passed as written. I did not run Slither or long fuzz campaigns, and I did not review the mainnet fork test since it needs an RPC.","treeHash":null,"usage":{"cachedInputTokens":936493,"inputTokens":258,"model":"claude-fable-5-1","outputTokens":56676,"runtime":"claude","turns":18,"wallClockMs":910183}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"b57ae3a96321a0e1","findings":[{"citation":"resolved","description":"`_cappedPending()` cuts T_PEND_DAMAGE to the HP the transaction removed, but T_PEND_BOSS is left untouched, so `_arrive` calls `_credit(r, to, 0, boss2)` and `bought[r][to]` grows by BOSS that carries no damage. The forfeit rule in `_release` is proportional to `bought`, so this damage-free BOSS pads the denominator: after a real buy of X BOSS with damage D, handing back exactly those X BOSS forfeits only D*X/(padded+X) instead of D. The README's stated rule ('selling all of it loses all of it') is therefore not what the code enforces once a wallet has taken a capped buy. Precondition: the wallet (or any wallet whose BOSS the same locker sells earlier in the transaction) holds BOSS that carries no credit this round, so the heal exceeds the buy's damage and the cap reaches zero; that is the same precondition as the documented two-wallet limit (audit be67d693 finding 7), and the economic effect is at most what that limit already allows, which is why this is low. It is still a divergence between the documented proportional-forfeit rule and the code, and it lets a single wallet do what the README says needs two. Fix: in `_arrive` (and `_kill`) scale T_PEND_BOSS down with the damage when the cap bites (keep `boss2 = pb * pd_capped / pd_raw`), or do not add BOSS to `bought` when `dmg2 == 0`, so `bought` only counts BOSS that actually carries credit.","line":830,"path":"src/BossHook.sol","reproduction":"Fixture: test/utils/BossFixture.sol (INITIAL_HP = 1 ether, mock IMD) plus test/utils/ScriptRouter.sol as the locker, funded with 100,000 IMD; alice and bob approve the router for BOSS. 1) carol buyExactIn(0.6 ether): hp = 0.4 ether. 2) bob holds 400,000 BOSS from the deployer (damage-free, as BOSS from a prior round or bought elsewhere would be). 3) One transaction signed by alice, two unlocks of the same router (runMany): unlock A = pullFrom(bob, router, 400_000 ether), sellOwn(400_000 ether) (heals to 0.6777 ether, forfeits nothing: bought[r][bob] == 0); unlock B = buy(0.3 ether), takeBoss(alice, max). Observed after this transaction: damage[1][alice] == 0 (cap bit, correct), bought[1][alice] == 114,847,257,889,700,052,885,796 (padded, damage-free). 4) Next transaction: alice buyExactIn(0.2 ether): damage 0.2 ether, bought grows by 76,548,556,160,233,219,136,303 BOSS. 5) alice sellExactIn of exactly those 76,548.56 BOSS. Expected (README rule, 'selling all of it loses all of it'): damage[1][alice] == 0 after the sell. Actual: damage[1][alice] == 120,010,208,645,145,751 (60% of the 0.2 ether credit kept, kept share 6000 bps) while every BOSS of that buy has been handed back to the pool and the boss was healed by it. The probe is test/scratch/Dilute.t.sol in this review (logs: 'alice damage after capped buy 0', 'kept share bps 6000').","severity":"low","snippet":"        _credit(uint64(_tload(T_PEND_ROUND)), to, dmg2, boss2);","title":"Credit-capped buy still records its BOSS in `bought`, padding the forfeit denominator so a later sell keeps credit it should forfeit"}],"hash":"47eb5599365c2f65c7a03ca2651b401743ebef3e1991d01053f45f5a5a28066c","nodeId":"25f40bb3-ca49-4a58-8688-cd525c8860c1","outcome":"completed","summary":"The findings file is written and validated. Final state:\n\n**Outcome:** one substantiated low finding, no critical, high or medium. The file `.imd-findings.json` holds it.\n\n**What I covered in depth**\n- BossHook swap and fee paths: all four swap shapes, fee rounding, partial-fill checks, treasury split, pot accounting and the pull-only claims. Pot can only leave through a kill split, and claims are bounded by `totalReserved`. No path lets anyone drain or lock the pot or make the treasury pay more than 1%.\n- The credit machinery: pending credit, the HP cap, locker scoping, forfeits, the killing blow's hops and the prize recount. I traced every subtraction in `onBossTransfer` for underflow and found each guarded, so the token handler cannot revert and block transfers. The single-transaction invariant \"net credit never exceeds HP removed\" holds by construction; the leaks that remain are cross-transaction and already in the README.\n- OracleGate and AbilityBoard: replay is blocked by the monotonic id, the id-bound answer, the proposal timestamp, chain id and verifying contract. The EIP-7702 check is sound post-Cancun.\n\n**The finding (low):** when the credit cap cuts a buy's damage to zero, `_arrive` still adds its BOSS to `bought`. That damage-free BOSS pads the proportional forfeit, so a wallet can later sell every BOSS of a real buy and keep most of its credit. The probe showed a wallet keeping 60% of 0.2 IMD credit after handing back all the BOSS that earned it. The precondition is the one the documented two-wallet limit already needs, so the economic effect is not new, but the stated rule \"selling all of it loses all of it\" does not hold. Suggested fix: scale the pending BOSS down with the capped damage, or skip the `bought` increment when the delivered damage is zero.\n\n**Not done:** no fork tests ran, and I wrote no self-contained Foundry proof since nothing reached high severity. The probe test lives in `test/scratch/Dilute.t.sol` and depends on the project's test fixtures.","treeHash":null,"usage":{"cachedInputTokens":1005479,"inputTokens":354,"model":"claude-fable-5-1","outputTokens":51687,"runtime":"claude","turns":12,"wallClockMs":802690}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"45aa937328087de3","findings":[{"citation":"resolved","description":"`_hopBlow` pays the killing blow out of the killing locker pro rata to the locker's whole BOSS balance, and `_weight` (line 866) drops the locker to weight 0 (T_KILL_PAID) as soon as it pays any wallet. Both rules assume the player ends up with the largest share of the blow. A custodial router that takes an interface fee as a share of its balance (the Universal Router's PAY_PORTION shape) and then transfers exactly the swap output minus the fee to the player (no SWEEP, the shape most aggregators use) breaks that assumption as soon as anyone has parked BOSS on it in an earlier transaction: the fee transfer carries `bips` of the blow, but the exact delivery carries only output/(plant+output) of the rest. With a plant of a few hundred times the output the player's share drops below the fee wallet's, `_trackKill` -> `_recount` sees the locker at weight 0 and the fee wallet as the largest holder, and `_moveKill` sends the whole 45% killer prize to the interface fee wallet. The rest of the blow's damage credit (~98.7% in the reproduction) stays on the router; if the router has a public sweep (the Universal Router does) anyone can `claimDamage(kr, router)` and sweep it. The README's Known limits state that an exact-amount delivery keeps the prize with the player and that planted BOSS 'never' keeps the prize away from the player; the fee-then-exact shape is not covered and gives the prize to a third party. Cost to the griefer: ~2.3M BOSS (about 6 IMD at the launch price), recoverable only if the router is sweepable; the player loses the prize, the fee wallet gains it. Suggested minimal fix that keeps the design: in `_trackKill`/`_recount` only move the prize away from the credited killer to a wallet whose held blow BOSS exceeds half of T_KILL_BOSS (a strict majority), otherwise leave it with `results[kr].killer`; alternatively weigh the locker's payouts by raw BOSS only when its balance before the killing swap was zero.","line":748,"path":"src/BossHook.sol","reproduction":"test/scratch/Judge.t.sol::test_feeThenExact_plant_movesPrizeToFeeWallet (BossFixture, INITIAL_HP = 1 IMD, mock IMD). (1) alice buyExactIn(0.99 ether): hp = 0.01 IMD. (2) A FeeExactRouter R (R7Router + buyFeeThenExact: buys to itself, transfers balance*bips/10000 to feeTo, then transfers exactly output*(1-bips/10000) to the user) funded with 1 IMD. (3) eve transfers 300 * 7650.95 BOSS = 2,295,285 BOSS to R in an earlier transaction. (4) carol (tx.origin) calls R.buyFeeThenExact(0.02 ether, carol, feeCollector, 100). Expected (README Known limits): results(1).killer == carol, carol's round-1 damage ~0.0099 IMD, feeCollector 1%. Actual: results(1).killer == feeCollector, unclaimedKill[feeCollector] == 6817500000000000 (the whole prize), unclaimedKill[carol] == 0; damage[1][carol] == 32890365356168 (0.33% of the blow), damage[1][feeCollector] == 99999999999999 (1%), damage[1][R] == 9867109634643833 (98.7%, stranded on the router). Control without the plant (test_feeThenExact_noPlant_control): killer == carol, damage[1][carol] == 9900000000000001, feeCollector 1%.","severity":"medium","snippet":"            out = hf * value / (IBossToken(bossToken).balanceOf(from) + value);","title":"Planted BOSS on a fee-then-exact-delivery killing router hands the killer prize to the interface fee wallet and strands ~99% of the blow's credit on the router"},{"citation":"resolved","description":"`bindHook` has no check on msg.sender beyond `tx.origin == _launchOrigin`. If the token and the hook are not created in the same transaction, every call the deploying EOA makes to any contract in between (a claim, an approval through a dapp, a relayed launch step) lets that contract call `bindHook()` and become the permanent hook. The binding is one-shot, so the real BossHook constructor then reverts with HookAlreadyBound, and because `_transfer` calls `onBossTransfer` on whatever is bound, a hook that reverts freezes all BOSS transfers with no recovery (no owner, no rebind). The README relies on the swarm factory deploying token and hook atomically; the contract does not enforce it. Document as a launch trust assumption or harden: bind to a precomputed CREATE2 hook address in the token constructor, or require the binder to be the expected hook / a contract deployed by the token's deployer.","line":54,"path":"src/BossToken.sol","reproduction":"test/scratch/Judge.t.sol::test_bindHook_phish. deployer (EOA, vm.prank(deployer, deployer)) deploys BossToken in tx 1. In tx 2 the deployer calls Phish.claimAirdrop(), where Phish calls boss.bindHook() and Phish.onBossTransfer reverts('blocked'). Expected: only the BossHook can be bound. Actual: boss.hook() == address(Phish); boss.transfer(address(1), 1) from the deployer reverts with 'blocked'; a later BossHook constructor would revert with HookAlreadyBound. Control: the same call from a stranger (eve) reverts NotLaunchOrigin.","severity":"low","snippet":"        if (tx.origin != _launchOrigin) revert NotLaunchOrigin();","title":"bindHook authorizes by tx.origin only: any contract the deployer EOA calls between the token and hook deployments can bind itself as the hook and freeze every BOSS transfer"},{"citation":"resolved","description":"`_cappedPending()` cuts T_PEND_DAMAGE to the HP this transaction removed, but T_PEND_BOSS is left untouched, so `_arrive` calls `_credit(r, to, 0, boss2)` and `bought[r][to]` grows by BOSS that carries no damage. `_release` forfeits proportionally to `bought`, so this damage-free BOSS pads the denominator: after a later real buy of X BOSS with damage D, handing back exactly those X BOSS forfeits only D*X/(padded+X) instead of D. The README rule 'selling all of it loses all of it' (proportional forfeit) is not what the code enforces once a wallet has taken a capped buy. Precondition: a heal earlier in the same transaction that exceeds the buy's damage, i.e. the same precondition as the documented two-wallet limit (audit be67d693 finding 7); the economic effect is bounded by what that limit already allows, so low. Fix: scale T_PEND_BOSS down with the damage when the cap bites (boss2 = pb * pd_capped / pd_raw) in `_arrive` and `_kill`, or do not add BOSS to `bought` when dmg2 == 0.","line":830,"path":"src/BossHook.sol","reproduction":"test/scratch/Judge.t.sol::test_cappedBuyPadsBought (BossFixture + test/utils/ScriptRouter.sol funded with 100,000 IMD). (1) carol buyExactIn(0.6 ether): hp = 0.4 ether. (2) bob holds 400,000 BOSS from the deployer (credit-free). (3) One transaction signed by alice, two unlocks of the router (runMany): unlock A = pullFrom(bob, router, 400_000 ether), sellOwn(400_000 ether) (heals to 0.6777 ether, forfeits nothing); unlock B = buy(0.3 ether), takeBoss(alice, max). Observed: damage[1][alice] == 0 (cap bit), bought[1][alice] == 114847257889700052885796 (padded, damage-free). (4) alice buyExactIn(0.2 ether): damage 200000000000000000, bought grows by 76548556160233219136303. (5) alice sellExactIn of exactly 76548556160233219136303 BOSS. Expected: damage[1][alice] == 0. Actual: damage[1][alice] == 120010208645145751 (60% kept) while all BOSS of that buy was handed back and the boss healed by it.","severity":"low","snippet":"        _credit(uint64(_tload(T_PEND_ROUND)), to, dmg2, boss2);","title":"A credit-capped buy still adds its BOSS to `bought`, padding the forfeit denominator so a later sell of a full buy keeps credit the README says it forfeits"},{"citation":"resolved","description":"In `_play`, a buy computes dmg = BossMath.scale(gross, buyMult) and returns before `_pend`/`_kill` when it truncates to zero. With an ability at the clamp floor buyMult = 2500, every buy with gross <= 3 wei is a no-op: hp unchanged, no pending damage, no `bought` entry, so the BOSS that leaves the pool for it carries no credit. The heal side truncates the same way (heal = scale(gross, sellMult), line 532; a 1 wei BOSS sell pays ~0 wei and heals nothing). README.md:195 claims such a trade 'still hits or heals'. Bookkeeping stays self-consistent and the amounts are far below gas cost: documentation accuracy only, no exploit.","line":523,"path":"src/BossHook.sol","reproduction":"test/scratch/Judge.t.sol::test_minMult_3wei_noHit. Activate FixedAbility(2500, 2500) by writing hook.activeAbility via stdstore (as test/invariant/BossInvariant.t.sol does); buyExactIn(alice, 3). Expected per README: the boss is hit. Actual: hook.hp() stays 1000000000000000000 and no Hit event. Control buyExactIn(alice, 4): hp becomes 999999999999999999 (1 wei hit), pinning the cutoff at gross*buyMult < 10000.","severity":"info","snippet":"            if (dmg == 0) return;","title":"A trade whose scaled damage or heal truncates to zero is dropped before any game bookkeeping, so the README's 'a trade under 40 wei still hits or heals' does not hold under a sub-1x ability"},{"citation":"resolved","description":"Agreed design (README: the wallet that lands the killing blow gets the killer share), reported so the launch documentation can state it. The killer prize is 45% of the pot and goes to whoever makes hp reach 0, however small that hit is; damage already done only earns a share of the other 45%. Remaining HP is public and every pending buy is visible in the mempool, so a searcher can front-run any buy that would kill with an exact-input buy of hpLeft plus dust, take the prize, and the victim's buy lands on the fresh boss at 1.5x HP. Nothing in the code limits this and the README's Known limits do not mention it. Least-change handling: document it and recommend a private relay for finishing buys; if the economics are to change, weight the killer prize by the killing transaction's share of the round's damage.","line":529,"path":"src/BossHook.sol","reproduction":"test/scratch/Judge.t.sol::test_tinyLastHit (BossFixture, INITIAL_HP = 1 ether). (1) alice buyExactIn(0.99 ether): hp left ~0.01 ether, alice holds ~99% of round-1 damage. (2) bob buyExactIn(hpLeft + 1e15) in the next transaction. Actual: results(1).killer == bob, unclaimedKill[bob] == killerPrize == 6756750000000000; claimableDamage(1, alice) == 6689182500000000, claimableDamage(1, bob) == 67567500000000. bob, with ~1% of the damage, collects 6824317500000000 against alice's 6689182500000000.","severity":"info","snippet":"            if (current == dmg) _kill(signer);","title":"The killer prize goes to whoever lands the last hit regardless of contribution, which makes the kill a public-mempool race a searcher wins by front-running the finishing buy"}],"hash":"74b5a94c059ad8cb2ecb56ed909381d86a52ee41e2d5c8c52b2e556dab22c3f3","nodeId":"c4cfbdb0-75b0-4128-92ad-dc2f06d02eb3","outcome":"completed","summary":"The review is complete and `.imd-findings.json` holds five verified findings. Nothing in the repository was changed; the scratch test under `test/scratch/` is my working material only.\n\n**What I did.** I rebuilt every specialist probe in one Foundry file and ran each against the pinned commit. All five reproduced with the exact numbers the specialists reported, so none was dropped. There were no duplicates across specialists: each finding sits at a different line and mechanism. The project's own suite also passed (549 tests, fork and invariant suites excluded for time).\n\n**Findings kept, by severity:**\n\n- **Medium, BossHook.sol line 748.** BOSS planted on a killing router that takes a fee as a share of its balance and then delivers an exact amount sends the whole killer prize to the interface fee wallet and strands most of the blow's credit on the router. Reproduced: with a 300x plant, the fee wallet became the killer and received the full prize; the player received nothing. The control run without the plant kept the prize with the player.\n- **Low, BossToken.sol line 54.** The hook binding trusts only the transaction origin. Any contract the deployer calls between the token and hook deployments can bind itself and freeze all transfers. Reproduced with a phishing contract; a stranger's attempt correctly reverted.\n- **Low, BossHook.sol line 830.** A credit-capped buy still adds its BOSS to the forfeit denominator, so a later full sell keeps credit the README says is lost. Reproduced: alice kept 60% of a buy's credit after handing back every BOSS of that buy.\n- **Info, BossHook.sol line 523.** Under a 0.25x ability, buys of 3 wei or less are dropped entirely, contradicting the README's claim that tiny trades still hit or heal. Reproduced: hp unchanged at 3 wei, 1 wei hit at 4 wei.\n- **Info, BossHook.sol line 529.** The killer prize goes to the last hit regardless of contribution, which makes every finishing buy a front-runnable race. Agreed design, recorded so the launch notes can state it.\n\n**No proofs attached.** The rules reserve self-contained proof files for critical or high findings, and nothing here rises above medium.\n\n**My own pass** over the focus questions (draining or locking the pot, stealing prizes or credit, credit without net damage, blocking transfers, attestation replay, treasury overpayment) found no additional reproducible defect in the time available. The accounting around `_arrive`, `_hopBlow` and `_release` held up on the underflow and double-credit paths I traced, and the attestation flow is bound to chain, contract, question hash and a strictly increasing board id.","treeHash":null,"usage":{"cachedInputTokens":1249745,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":27679,"runtime":"claude","turns":15,"wallClockMs":465953}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"9c6767b941fcfedc","findings":[{"citation":"resolved","description":"`_hopBlow` pays the killing blow out of the killing locker in proportion to the locker's whole BOSS balance, and `T_KILL_PAID` removes the locker from the prize count as soon as it pays any wallet. Both rules assume the player eventually receives the largest share. A custodial router that (1) pays an interface fee as a share of its balance (PAY_PORTION) and then (2) transfers exactly the swap's output to the player (no SWEEP) breaks that assumption when anyone has parked BOSS on it in an earlier transaction: the fee transfer carries `bips` of the blow (correct, as the README promises), but the exact delivery carries only `output / (plant + output)` of the remainder. With a plant of a few hundred times the output the player's share drops below the fee share, `_trackKill` -> `_recount` sees the locker at weight 0 (PAID) and the fee wallet as the largest holder, and `_moveKill` hands the 45% killer prize to the interface fee wallet. The rest of the blow's damage credit (~98.7% in the reproduction) stays on the router; if the router has a public sweep (the Universal Router does) anyone can trigger `claimDamage(kr, router)` and sweep it. The README documents the credit share and the quoted-delivery/surplus shape, but states the player keeps the prize whenever the router 'sends an exact amount' and that planted BOSS 'never' keeps the prize away from the player; the fee-then-exact shape (how most aggregators pay a fee: fee taken off the output, the remainder transferred exactly) is not covered and gives the prize to a third party. The planter gains nothing directly (griefing; the planted BOSS stays on the router, recoverable only if it is sweepable), the player loses the prize, the fee wallet receives it. Suggested fix: a minority holder should never take the prize from the signer: in `_trackKill`/`_recount` only move the prize to a wallet whose held blow BOSS exceeds half of the blow (T_KILL_BOSS at the kill), otherwise leave it with `results[kr].killer` as credited at the kill; alternatively weigh the locker's payouts by raw BOSS value only when the locker's balance before the kill was zero.","line":748,"path":"src/BossHook.sol","reproduction":"State: BossFixture (BOSS/IMD pool, initialHp 1 IMD). alice buys 0.99 IMD exact-in so hp = 0.01 IMD. A locker contract R (test/review7/R7Router shape) is funded with 0.02 IMD. In an earlier transaction eve transfers 300x the blow's BOSS (300 * 7650.95 BOSS = 2.295M BOSS) to R. carol (tx.origin) calls R.buyFeeThenExact(0.02 IMD, carol, feeCollector, 100): R buys 0.02 IMD exact-in (kills, blow BOSS 7650.95, blow credit 0.01 IMD), takes the BOSS to itself, transfers 1% of its balance (23,026 BOSS) to feeCollector, then transfers exactly output*(1-1%) = 7574.44 BOSS to carol. Expected (README Known limits: fee carries only its share; exact-amount delivery keeps the prize with the player): results[1].killer == carol, carol's round-1 damage ~0.0099 IMD. Actual: results[1].killer == feeCollector, unclaimedKill[feeCollector] = 0.0068175 IMD (the whole killer prize), unclaimedKill[carol] = 0; damage[1][carol] = 32,890,365,448,504 wei (0.33% of the blow), damage[1][feeCollector] = 99,999,999,999,999 (1%), damage[1][R] = 9,867,109,634,551,497 (98.7%, claimable by anyone via claimDamage(1, R) into R). Control without the plant: carol is the killer with 0.0099 IMD credit, feeCollector 1%. Probe: test/scratch/Probe.t.sol (FeeExactRouter inherits test/review7/R7Router).","severity":"medium","snippet":"            out = hf * value / (IBossToken(bossToken).balanceOf(from) + value);\n            if (to != address(0) && from != tx.origin) _tstore(T_KILL_PAID, 1);","title":"Planted BOSS on a fee-taking exact-delivery router moves the killer prize to the interface fee wallet and strands ~99% of the blow's credit on the router"},{"citation":"resolved","description":"`bindHook` authorizes by `tx.origin == _launchOrigin` and has no other check on the caller. If the token and the hook are not created in the same transaction, every call the deploying EOA makes to any contract in between (a claim, an approval through a dapp, a relayed launch step) lets that contract call `bindHook()` and become the permanent hook. The binding is one-shot, so the real BossHook constructor then reverts (`HookAlreadyBound`), and because `_transfer` calls `onBossTransfer` on whatever is bound, a rogue hook that reverts freezes all BOSS transfers with no recovery path (no owner, no rebind). The README and launch notes rely on the swarm factory deploying token and hook atomically; the contract itself does not enforce that. Trust assumption to document, or harden: bind in the token constructor to a precomputed CREATE2 hook address, or require `msg.sender.code.length != 0 && msg.sender == expectedHook`, or let the deployer (msg.sender of the constructor) bind explicitly rather than any contract reached via tx.origin.","line":54,"path":"src/BossToken.sol","reproduction":"deployer (EOA) deploys BossToken in tx 1. In tx 2 deployer calls Phish.claimAirdrop() where Phish.claimAirdrop() calls boss.bindHook() and Phish.onBossTransfer reverts. Expected: only BossHook can be bound. Actual: boss.hook() == Phish; boss.transfer(address(1), 1) from the deployer reverts with 'blocked'; the real BossHook constructor would revert with HookAlreadyBound. Probe: test/scratch/Bind.t.sol, test_deployerSignedCallBindsRogueHook_blocksAllTransfers passes (demonstrates the binding and the frozen token).","severity":"low","snippet":"        if (tx.origin != _launchOrigin) revert NotLaunchOrigin();","title":"bindHook trusts tx.origin: any transaction the deployer EOA signs before the hook deploys can bind a rogue hook that blocks every BOSS transfer forever"}],"hash":"e71966e80b300261b934e2f15b016df82e73170b6377cd3d1f734e9013468d88","nodeId":"8d48c37c-c9e3-43b5-b147-8cb23c45f9f0","outcome":"completed","summary":"The findings file is valid and both snippets match the tree. The review is complete; here is the recap.\n\n**Outcome.** Two substantiated findings written to `.imd-findings.json`, both verified with Foundry probes under `test/scratch/`. No critical or high defects found; the pot accounting, pull-only claims, attestation binding, and transfer handler held up against every path I traced.\n\n**Finding 1, medium, `src/BossHook.sol:748`.** The killing locker pays the blow out pro rata to its balance and stops counting for the prize once it pays any wallet. A custodial router that takes a fee as a share of its balance and then delivers the exact swap output breaks the \"player keeps the prize\" promise when someone has parked BOSS on it earlier. In the probe, a 300x plant made the fee wallet the largest blow holder. The 45% killer prize moved to the fee wallet, the player got 0.33% of the blow's credit, and 98.7% stayed on the router where anyone can trigger its claim and sweep it. The README covers the quoted-delivery shape and the credit share, but says exact-amount delivery keeps the prize with the player. Suggested fix: never move the prize to a wallet holding less than half the blow's BOSS.\n\n**Finding 2, low, `src/BossToken.sol:54`.** The hook binding is authorized by `tx.origin` alone. If token and hook are not deployed in one transaction, any contract the deployer EOA calls in between can bind itself, and a reverting rogue hook freezes every BOSS transfer permanently. The launch notes assume atomic deployment; the contract does not enforce it. A probe demonstrates the rogue binding and the frozen token.\n\n**Checked and found sound.** Attestation replay is prevented by the monotonic accepted id plus the answer bound to the proposal id, with the stale check and EIP-712 domain pinned to this hook. Pot, treasury, and reserved claims conserve against ERC-6909 holdings. The transfer handler has no arithmetic revert path I could reach. Credit-without-damage paths within one transaction are stopped by the HP anchor cap. Cross-transaction heal-from-another-wallet and the planter credit share on sweepable routers are already listed as accepted limits, so I did not re-report them.\n\n**Not covered.** No fork run against the real IMD token or Universal Router, so IMD transfer behaviour on payout is unverified. Proof files for a critical or high were not needed.","treeHash":null,"usage":{"cachedInputTokens":1881822,"inputTokens":450,"model":"claude-fable-5-1","outputTokens":68419,"runtime":"claude","turns":22,"wallClockMs":1204660}}],"verification":[]}