# Audit report

> Audit every payout and the token economics of moneycanbuy.fun. Repository at commit 5f6af8f1052f555553aad41a0e12fc1d29fa90b6. Run forge at the repo root, import as "src/...". Modify no existing file; new files only under contracts/test/audit/.
>
> SOURCE OF TRUTH. The RULES below are the money rules; docs/MECHANICS.md, YOLO.md and TOKENOMICS.md restate them. Code or docs off the RULES = a finding. Do not re-report what contracts/test/audit/ proves fixed.
>
> SCOPE. Amounts: who gets how much in every outcome, and who can game it. Code security and role scenarios are other orders; one info line each.
>
> RULES. R = the reward in its currency (ETH, USDC, IMD or YOLO). A = the author's own share (own top-ups in; others' top-ups and YOLO add-ons out). Payouts only from money the paying contract holds; token funds pay nothing. A user's tokens leave their wallet only in their own call; anyone else spending a user's allowance = critical.
> - Fee 10% of R, only on acceptance. Of it 5% to the right NFT votes if the NFT decided right, else "unearned NFT 5%"; the other 5% = stakers' part.
> - No stake: doer 90%, treasury 10% (5% if the NFT 5% was earned).
> - Staked doer: b = 4% x min(1, power / A in YOLO at the price parameter), floored to whole bps, no threshold. Doer 90% + b; (5% - b) half burn, half treasury; unearned NFT 5% burn. $100, no dispute, doer/burn/treasury: full power 94/5.50/0.50; half 92/6.50/1.50. b is paid on all R and the add-on.
> - Power = amount x weeks / 52 (1-52 weeks), fixed at the lock, never falling; add or extend: max(old + added x weeks left / 52, total x weeks left / 52). Read before the submission block; the lock must still run at payout or b = 0.
> - Burn: YOLO to the BuybackBurner, then burnYolo to 0x...dEaD; ETH, USDC, IMD to the buyback wallet at payout.
> - Doer bond 0.5 IMD + 3% of A in R's currency; dispute bond 0.5 IMD + 1% of A. Never doubled; each currency splits alone.
> - Loser's bond: NFT decided and the outcome matches: 50% winning side (pro rata to bonds), 50% right NFT votes; NFT wrong or undecided: 100% winning side. The winner's own bond comes back.
> - NFT undecided and no swarm call: accepted, the disputer's bond 100% to the doer.
> - Rejected: R stays, open for the next submission.
> - Appeal: a dispute party pays the 0.5 IMD fee from its bond (again: from its wallet); an outsider posts a dispute bond + fee and, winning, first gets 0.5 IMD off the loser's bond. Max 3 swarm calls per submission; a silent call's fee is gone; a failed ask or an appeal not asked within 6 h counts, bond and fee back; after the third, outcome as without appeal.
> - Top-ups and YOLO add-ons are locked to the deadline; nobody accepted: each funder takes back their own. A top-up during review joins R. Submitted before the deadline, accepted after = done.
> - Cancel only by the dev wallet, any open phase: all back to who put it in, predictions 100%; swarm fees paid stay gone.
> - YoloPredict, YOLO only, irrevocable, until the deadline, paused during review. Entry 100: 5 burn, 5 deferred, 90 pool. Points = 90 x ((deadline - now)/(deadline - creation))^2. Winners: their 90 back + the losing pool by points. Deferred: done -> 2.5% author, 2.5% doer; else burn. Done with nobody on WILL DO: the losing pool half author, half doer; not done with nobody on WON'T DO: all to author. Cancel = 100% back, no burn.
> - Trophy: 7,777 YOLO, burned.
> - Token: 1,000,000,000 YOLO per docs/TOKENOMICS.md. Buy tax 30% -> 0 in minutes 30-60 after openAt; minutes 0-30 only snapshot wallets buy, 500,000 YOLO per NFT, untaxed. Sell tax 30% in 0-30, 30% -> 0 in 30-60, on top, to the treasury wallet.
>
> Prices (no network): IMD $5.42, ETH $2,434, YOLO $0.001 (parameter), swarm call 0.5 IMD = $2.71, gas 0.045 gwei; in code every price is a dev-wallet parameter, USDC = $1.
>
> ALREADY FIXED IN THIS COMMIT (passing PoC in contracts/test/audit/): maxImdIn caps the IMD bond part too (MaxImdInCapTest); a late askAppealSwarm reverts (LateAppealAskTest); a 0 bps stake keeps the staked split (StakeFloorZeroSplitTest); stepDeadline while Appealed (StepDeadlineAppealedTest).
>
> ACCEPTED BY DESIGN, documented in docs/MECHANICS.md Known limitations. Not findings unless the code differs from the documented rule: 20+ NFTs disputing from a second wallet; undisputed in 24 h = accepted, WON'T DO predictors dispute; an appeal not asked in 6 h void, full refund, new 24 h window; the queue judged in turn; one dispute per submission; cancelTask in any open phase, spent swarm fees kept; a started YoloLockHub week counted whole.
>
> REPORT FORMAT, MANDATORY. One entry per item, same order, even "no issue"; a missing item = INCOMPLETE. Entry 0: VERDICT, max 8 lines: payouts vs RULES, every money blocker. Items 1-15: what you checked, file:line, severity (critical = theft, permanent loss or freeze; high = a RULES amount broken; medium = bounded loss or profitable gaming; low = dust, edge case; info = drift), loss in tokens and $ at a stated R, smallest fix, a Foundry PoC (full source, contracts/test/audit/, exact amounts, fails now, passes once fixed, no fork, no ffi) if real, else one line why not.
>
> 1. ACCEPTED, NO DISPUTE. DisputeDesk._payReward: the $100 examples in USDC, ETH, IMD, YOLO; b = 0.
> 2. DISPUTE, NFT RIGHT. Accepted and rejected: loser's bond 50/50, winner's bond back, voter shares.
> 3. NFT WRONG OR UNDECIDED. Swarm overturns or decides; undecided, no call: unearned NFT 5%, 100% of the loser's bond.
> 4. REJECTED. R after a fake, the next submission's bond and payout, claimVoidSubmission.
> 5. APPEAL MONEY. _penaltyAndWinnerBonds, _splitCurrency, _sideSpent, _charge: party vs outsider, second call, three silences, failed ask. Each party's net in IMD and R per case.
> 6. BONDS. Base A, rounding at the minimum rewards, who keeps dust.
> 7. STAKE BONUS. CaseDesk._stakeBps, YoloLockHub, _lockRuns: base A not R, floor to bps, add/extend after submission, lock ended before payout.
> 8. YOLO ADD-ON. YoloSweetener: same split as R, voters only after a right NFT decision, refunds.
> 9. EXPIRY AND CANCEL. withdrawContribution, cancelTask: each funder their part.
> 10. YOLOPREDICT. settle, claim, _split, _points: claims + burned + deferred = paid in, empty sides, a winning side with zero points, void.
> 11. BURNS. Every burn path to its end; monthly burn vs unlock at 200 tasks/month, R $100.
> 12. TOKENOMICS VS CODE. token/, LaunchPlan: split = 1e27, dust, unlock table, both taxes at minutes 0, 15, 30, 45, 60, tax recipient.
> 13. INCENTIVES. Net $ per action at the minimum rewards and R $100: disputing a fake, an NFT vote, an outsider appeal vs gas; stake recycling via own top-ups, griefing cost.
> 14. DOCS VS CODE. Every amount in the three docs, one line each.
> 15. COVERAGE. Files read in full, what was not reached, forge build and test result.
>
> Not findings: test/mocks; placeholder wallets; legal; brand/, landing/; Intake, Uniswap, IMD factory; the name and size of the "Stabilizer / MM fund". By design unless worse than stated: self-dispute from a second wallet; one late position taking the other side; many wallets in predictions; an author profiting from a task nobody does; one lock for all submissions; a stale price moving only the stake bonus; no task limits; ERC-6909 buys before openAt; untaxed v2/v3 pools; fees of silent swarm calls kept.

| | |
|---|---|
| Repository | https://github.com/sergey-mavr-agent/moneycanbuy.fun.git |
| Commit | `5f6af8f1052f555553aad41a0e12fc1d29fa90b6` |
| Job | `532b2ea7-88fc-4027-8e59-f7d22aa83d47` |
| Judged | 2026-10-19 15:14 UTC |
| Findings | 3 low · 3 info |

Four agents audited the code as it is at `5f6af8f`, each in one area (math, permissions, economics, control flow),
and a judge reproduced, merged and ranked what they found, then read the code once more itself. Nothing in the repository was changed or deployed.

## Findings

### 1. Low: YoloPredict: a winner whose points floor to 0 counts as 'nobody on the winning side'; the losing pool goes to the author and the doer instead of the winner

`contracts/src/YoloPredict.sol:209`

```
        if (res == Resolution.WillDoWon && m.willDoPointsTotal == 0 && m.wontDoPool != 0) {
```

settle() decides that nobody was on the winning side by willDoPointsTotal == 0 (line 209) / wontDoPointsTotal == 0 (line 216), not by that side's pool. _points (line 285) is floor(floor(eff*rem/dur)*rem/dur), so a position placed shortly before a far deadline has 0 points while its 90% sits in the pool: with duration above ~1e9 s (a deadline 30+ years out, which TaskVault.createTask allows, it only requires deadline > now) a 1 YOLO position 1 s before the deadline gets 0 points. The market then pays the whole losing pool to the author and the doer (NoWinner) and claim() pays that winner only their effPosition (points == 0 skips the split). RULES: 'Winners: their 90 back + the losing pool by points' and 'Done with nobody on WILL DO' only when nobody is on it; docs/YOLO.md line 229: 'a lone late winner takes the whole losing pool'. Loss at the reproduction's amounts: the winner loses 90 YOLO ($0.09 at $0.001; it scales with the losing pool). Bounded and needs a decades-long deadline, so low. Smallest fix: test willDoPool == 0 / wontDoPool == 0 in the no-winner branches, and in claim() share the pool pro rata to the winning amounts when the winning side's points are 0 (or floor points at 1 wei in _points). Merged from the audit_permissions specialist; reproduced here with their proof.

**Reproduction**

Base fixture. Task deadline = now + 40 years (USDC 100e6). bob predicts WON'T DO 100 YOLO at creation (pool 90e18). At deadline-1 alice predicts WILL DO 1 YOLO: positionsOf(alice).willDoPoints == 0 while markets.willDoPool == 0.9e18. doer submits in the same second, 24 h later poke accepts: task Completed. alice claim(): expected alice +90.9 YOLO, author +2.525 (deferred 2.5%), doer +2.525. Actual (forge test --match-path contracts/test/scratch/Proof_2420c475f563.t.sol): author +47.525 (45 of the losing pool + 2.525), doer +47.525, alice +0.9 only. Test fails 'author: only the deferred 2.5%: 47525000000000000000 != 2525000000000000000'.

**Proof**: a Foundry test that fails on this code and passes once it is fixed.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {Base} from "../Base.t.sol";
import {YoloPredict} from "../../src/YoloPredict.sol";
import "../../src/VaultTypes.sol";

/// @notice YoloPredict.settle decides "nobody on the winning side" by `willDoPointsTotal == 0`, not by the pool.
contract PredictZeroPointsWinnerTest is Base {
    YoloPredict internal predict;

    function setUp() public override {
        super.setUp();
        predict = new YoloPredict(address(vault), 1e18);
        vm.prank(alice);
        yolo.approve(address(predict), type(uint256).max);
        vm.prank(bob);
        yolo.approve(address(predict), type(uint256).max);
    }

    function test_ZeroPointWinner_LosesWholePoolToAuthorAndDoer() public {
        uint64 deadline = uint64(_now() + 40 * 365 days);
        uint256 t = _createWithDeadline(author, address(usdc), 100e6, deadline);

        vm.prank(bob);
        predict.predict(t, false, 100e18);
        uint256 losingPool = 90e18;

        vm.warp(deadline - 1);
        vm.prank(alice);
        predict.predict(t, true, 1e18);
        (, uint256 willDoPoints,,) = predict.positionsOf(t, alice);
        assertEq(willDoPoints, 0, "points floor to 0");
        (,, uint256 willDoPool,,,,) = predict.markets(t);
        assertEq(willDoPool, 0.9e18, "but alice IS on WILL DO");

        uint256 s = _submit(doer, t);
        _warp(24 hours);
        vault.poke(t);
        assertEq(uint8(vault.getTask(t).status), uint8(TaskStatus.Completed));
        assertEq(uint8(_sub(s).outcome), uint8(Outcome.Accepted));

        uint256 authorBefore = yolo.balanceOf(author);
        uint256 doerBefore = yolo.balanceOf(doer);
        uint256 aliceBefore = yolo.balanceOf(alice);
        vm.prank(alice);
        predict.claim(t);

        uint256 deferred = (100e18 + 1e18) * 500 / 10_000;
        assertEq(yolo.balanceOf(author) - authorBefore, deferred / 2, "author: only the deferred 2.5%");
        assertEq(yolo.balanceOf(doer) - doerBefore, deferred - deferred / 2, "doer: only the deferred 2.5%");
        assertEq(yolo.balanceOf(alice) - aliceBefore, 0.9e18 + losingPool, "winner: 90% back + the losing pool");
    }
}
```

### 2. Low: Stake bonus paid although the lock behind it ended before the payout: re-locking in the same epoch revives stakePower

`contracts/src/YoloLockHub.sol:83`

```
        locks[msg.sender] = Lock(uint96(amount), uint64(end), l.epoch);
```

RULES: the lock behind b must still run at payout or b = 0. stakePower(who, since) (line 108-113) only checks p.epoch == l.epoch && l.end > now && p.end > since, and epoch grows only in withdraw(). A lock that ended (end <= now) can be set again with lock(0, weeks) without a withdrawal; the comment on lines 65-66 says such a lock 'starts afresh' (old power 0), but line 83 keeps l.epoch, so at the payout stakePower returns the power of the point before the submission and DisputeDesk._lockRuns (line 743) pays the staked split with the old bps. The lock behind the submission had expired; a new, possibly 1-week, lock stands in for it. Loss on R = $100 USDC with stakeBps 7: doer +$0.07 (94.07 instead of 94.00), treasury 2.465 instead of 10, buyback wallet 7.465 instead of 0; bounded by 4% of R to the doer and 10% of R shifted from the treasury to the burn. Low: the doer could have extended the lock one second before its end at the same cost and kept b legitimately, so the gaming value is small; it is a drift from the stated rule. Smallest fix: in lock(), when the previous lock is not running (l.end <= block.timestamp) start a new epoch (uint32 ep = l.end > block.timestamp ? l.epoch : l.epoch + 1) and store/checkpoint ep, so a re-lock after the end is another lock, as withdraw() already does. Merged from the audit_flow specialist; reproduced here.

**Reproduction**

Base fixture (YOLO $0.0001: $100 = 1,000,000 YOLO). doer: hub.lock(1_000_000e18, 1) (power 19,230 YOLO); warp 1 s; author creates a 100e6 USDC task; doer submits -> Submission.stakeBps == 7, staked == true; dave disputes. warp 8 days (lock ended): hub.stakePower(doer, submittedAt) == 0. doer: hub.lock(0, 1) -> hub.stakePower(doer, submittedAt) == 19,230e18 again. vault.poke: vote undecided -> NoDecision -> nobody called -> Accepted. Expected (lock ended before payout, b = 0, no-stake split): getSettlement(s).stakeBps == 0, treasuryPot(usdc) == 10e6, buyback wallet 0, doer credit 94e6. Actual (forge test --match-path contracts/test/scratch/Relock.t.sol): getSettlement(s).stakeBps == 7 (assertion '7 != 0'), treasuryPot 2.465e6, buybackWallet 7.465e6, doer credit 94.07e6.

**Proof**: a Foundry test that fails on this code and passes once it is fixed.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {Base} from "../Base.t.sol";
import "../../src/VaultTypes.sol";

/// @notice A lock that ended before the payout is set again (lock(0, 1)) without a withdrawal: same epoch, so
///         YoloLockHub.stakePower returns the pre-submission power again and the staked split is paid.
contract RelockAfterEndTest is Base {
    function test_RelockAfterEnd_RevivesStakeBonus() public {
        // YOLO $0.0001 in Base: $100 = 1,000,000 YOLO. 1,000,000 YOLO for 1 week: power 19,230 -> 7 bps.
        vm.prank(doer);
        hub.lock(1_000_000e18, 1);
        _warp(1);
        uint256 t = _create(author, address(usdc), 100e6);
        uint256 s = _submit(doer, t);
        assertEq(_sub(s).stakeBps, 7, "b = 7 bps");
        assertTrue(_sub(s).staked);
        uint64 since = _sub(s).submittedAt;
        _dispute(dave, s);

        _warp(8 days); // the lock ended
        assertEq(hub.stakePower(doer, since), 0, "lock ended: no stake power");
        vm.prank(doer);
        hub.lock(0, 1); // re-lock, no withdrawal in between
        assertEq(hub.stakePower(doer, since), uint256(1_000_000e18) / 52, "revived without a withdrawal");

        vault.poke(t); // vote ended undecided -> NoDecision -> nobody called: accepted, disputer's bond to the doer
        assertEq(uint8(_sub(s).outcome), uint8(Outcome.Accepted));

        // RULES: the lock must still run at payout or b = 0 -> no-stake split: treasury 10, burn 0
        assertEq(vault.getSettlement(s).stakeBps, 0, "b = 0: the lock behind the submission ended");
        assertEq(vault.treasuryPot(address(usdc)), 10e6, "treasury 10%");
        assertEq(usdc.balanceOf(buybackWallet), 0, "no burn");
        assertEq(vault.credits(address(usdc), doer), 90e6 + 3e6 + 1e6, "90 + own bond + disputer's bond");
    }
}
```

### 3. Low: claimVoterRewards skips the sweetener voter share when the $YOLO add-on arrived after the submission

`contracts/src/desks/DisputeDesk.sol:446`

```
        if (s.addon != 0 && s.outcome == Outcome.Accepted) bonusTask = s.taskId;
```

Submission.addon is fixed at submission (CaseDesk.submit line 112: s.addon = t.addon). YoloSweetener.sweeten (line 97) allows a bonus while the task is Open and before the deadline, so an add-on can arrive during the review. YoloSweetener.payout then pays it by the reward's rule and books a 5% voter pool when nftDecision == Accept (lines 140-144), but _claimVoter decides whether to call YoloSweetener.claimVoter from the stale s.addon == 0, so the documented behaviour of claimVoterRewards (lines 382-383: 'An accepted submission with a $YOLO add-on also claims payTo's share of its paid-out bonus') does not hold for that add-on. No money is lost: the voter can still call YoloSweetener.claimVoter(taskId, payTo) directly, so low. Smallest fix: drop the s.addon != 0 guard and rely on the existing try/catch (claimVoter reverts NothingToPay when there is no pool), or read the live bonus from the sweetener. Merged from the audit_math specialist; reproduced here.

**Reproduction**

Base fixture. USDC task R = 100e6 by author; doer submits (Submission.addon == 0); alice sweetens 10,000e18 YOLO during the review; dave disputes; voter1 votes 20 NFTs accept (instant decision); +24 h poke -> Completed. sweetener.payout(t) books voterPool(t) == 500e18. vault.claimVoterRewards([s], voter1): expected voter1 +500e18 YOLO in the same batch and voterPool(t) == 0; actual (forge test --match-path contracts/test/scratch/AddonAfterSub.t.sol): voter1's YOLO balance unchanged ('0 != 500000000000000000000'), voterPool(t) still 500e18. Control in the same file: the same add-on placed before the submission is paid by the batch claim.

**Proof**: a Foundry test that fails on this code and passes once it is fixed.

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.28;

import {Base} from "../Base.t.sol";
import {VoteLib} from "../../src/libraries/VoteLib.sol";
import "../../src/VaultTypes.sol";

/// @notice An add-on sweetened during the review: claimVoterRewards skips the sweetener voter share (s.addon == 0).
contract AddonAfterSubmissionTest is Base {
    function test_AddonAfterSubmission_BatchClaimSkipsSweetener() public {
        uint256 t = _create(author, address(usdc), 100e6);
        uint256 s = _submit(doer, t);
        assertEq(_sub(s).addon, 0);
        vm.prank(alice);
        sweetener.sweeten(t, 10_000e18);
        _dispute(dave, s);
        _vote(s, 1, VoteLib.SIDE_ACCEPT, 120, 20); // instant decision: accept
        assertEq(uint8(_phase(s)), uint8(Phase.Decided));
        _warp(24 hours);
        vault.poke(t);
        assertEq(uint8(vault.getTask(t).status), uint8(TaskStatus.Completed));
        sweetener.payout(t);
        assertEq(sweetener.voterPool(t), 500e18, "5% of the add-on to the right votes");

        uint256[] memory ids = new uint256[](1);
        ids[0] = s;
        uint256 before = yolo.balanceOf(voters[1]);
        vault.claimVoterRewards(ids, voters[1]);
        assertEq(yolo.balanceOf(voters[1]) - before, 500e18, "the batch claim pays the add-on share too");
        assertEq(sweetener.voterPool(t), 0);
    }

    /// @dev Control: the add-on before the submission is claimed in the batch.
    function test_AddonBeforeSubmission_BatchClaimPaysSweetener() public {
        uint256 t = _create(author, address(usdc), 100e6);
        vm.prank(alice);
        sweetener.sweeten(t, 10_000e18);
        uint256 s = _submit(doer, t);
        assertEq(_sub(s).addon, 10_000e18);
        _dispute(dave, s);
        _vote(s, 1, VoteLib.SIDE_ACCEPT, 120, 20);
        _warp(24 hours);
        vault.poke(t);
        sweetener.payout(t);
        uint256[] memory ids = new uint256[](1);
        ids[0] = s;
        uint256 before = yolo.balanceOf(voters[1]);
        vault.claimVoterRewards(ids, voters[1]);
        assertEq(yolo.balanceOf(voters[1]) - before, 500e18);
    }
}
```

### 4. Info: An outsider swarm caller who wins gets no 0.5 IMD when the losing party already spent its IMD bond on its own silent call; docs state the refund unconditionally

`contracts/src/desks/DisputeDesk.sol:641`

```
        if (appellantIsStranger && callerWins && penaltyImd != 0) {
```

RULES: an outsider who posts bond + fee and wins 'first gets 0.5 IMD off the loser's bond'. penaltyImd is reduced by the loser's own _sideSpent before this line (line 637), so when the losing side (the disputer or the doer) paid its first swarm call out of its 0.5 IMD bond and that call stayed silent, the loser's IMD penalty is 0 and the outsider's fee refund is 0: the outsider nets -0.5 IMD (-$2.71) on a won call instead of 0. docs/MECHANICS.md line 150 ('gets 0.5 IMD from the loser's IMD bond before the split') and line 194 do not mention this case. Consistent with 'off the loser's bond' when nothing is left, so info: either document 'if the loser's IMD bond still holds it' or, if the refund is meant to be guaranteed, take it from the loser's reward-currency bond part next (a design decision). Merged from the audit_flow specialist; reproduced here.

**Reproduction**

Base fixture, 100e6 USDC task, doer submits, dave disputes, no votes -> NoDecision after 24 h. dave calls the swarm against the doer (party: 0.5 IMD fee taken from his bond, his IMD balance unchanged); the Intake completes with no answer (silent); poke -> NoDecision again. erin (outsider) calls the swarm siding with the doer: pays 0.5 IMD bond + 1e6 USDC + 0.5 IMD fee. The swarm answers yes -> Accepted. Expected per the RULES wording: erin credited 1.0 IMD (bond + 0.5 fee refund). Actual (forge test --match-path contracts/test/scratch/StrangerFee.t.sol -vv): erin credited 0.5 IMD (bond only) + 1.25e6 USDC; doer 0.5 IMD + 93.75e6 USDC. Net money: nothing is lost by the protocol; the fee went to the swarm.

### 5. Info: A failed ask in callSwarm reverts instead of counting toward the 3 swarm calls with bond and fee back

`contracts/src/desks/DisputeDesk.sol:270`

```
        if (!ok) revert SwarmUnavailable();
```

RULES: 'a failed ask or an appeal not asked within 6 h counts, bond and fee back'. The appeal path (CaseDesk._askAppeal lines 403-422) does this: dropCaller refunds, then ++s.asks. The NoDecision escalation path (callSwarm -> askEscalation) instead reverts the whole transaction when the Intake request fails. The caller loses nothing (the revert undoes the bond and fee collection), but the failed ask is not counted in Submission.asks and the submission stays in NoDecision. Nothing is mis-paid: the window still ends at callFrom + 24 h and _endUncalled settles it as accepted. Drift between the rule text and the code, no money loss. Merged from the audit_economics specialist; confirmed by reading the code paths.

**Reproduction**

Submission in Phase.NoDecision, an Intake whose request() reverts while priceOf() returns 0.5e18. A stranger calls vault.callSwarm(subId, true, 2e18, payIn, rt). Expected per RULES: bond and fee credited back and s.asks == 1. Actual: the call reverts SwarmUnavailable (DisputeDesk.sol line 270), s.asks stays 0, no credit is booked, and the IMD stays in the wallet. Net money is the same in both cases (0 loss).

### 6. Info: docs/TOKENOMICS.md dev share percentage (7.777777%) does not equal the coded 77,777,777 YOLO

`docs/TOKENOMICS.md:16`

```
| Dev | 7.777777% | 77,777,777 | linear over 12 months |
```

7.777777% of 1,000,000,000 is 77,777,770 YOLO; YoloSplitter.DEV (contracts/src/token/YoloSplitter.sol line 33) is 77_777_777e18 (7.7777777%). The amount column and the code agree; the percentage is truncated by one digit. Same truncation in docs/YOLO.md line 89, contracts/src/token/Vestings.sol line 20 and contracts/script/LaunchPlan.sol line 85 ('dev 7.777777%'). Documentation drift only: 7 YOLO ($0.007). Fix: write 7.7777777%. Merged from the audit_math specialist; confirmed against the files.

**Reproduction**

YoloSplitter.DEV = 77_777_777e18; 1e27 * 7.777777 / 100 = 77_777_770e18; difference 7e18 YOLO. Expected the doc percentage to match the amount (7.7777777%).

---

Judge's submission `fac8a29213827dfd683d693c4466b2289a82cd73f9c9131a5ad780f97ce84e99`, accepted on the IdentityMD network. Acceptance means the report met the job's checks;
it is not a guarantee that the code has no other defects.
