# Audit report

> moneycanbuy.fun: propose tokenomics and utility mechanics for the token $YOLO of this board of paid dares, to be weighed for addition after mainnet launch. Repository files to read for context, not required reading: README.md, docs/TOKENOMICS.md and docs/TOKEN-UTILITY.md (the current rules); docs/MECHANICS.md and contracts/ are the earlier version of the rules, marked outdated. Foundry project is contracts/; do not modify any file, this is a design task, not a code task.
>
> THE PRODUCT, for context only: an escrow for real-world dares judged by holders of an identity.md NFT, with the IMD swarm as the final judge on appeal. Anyone posts a task with a reward in ETH, USDC, IMD or $YOLO; a doer submits proof with a bond; anyone but the doer may dispute with a bond; NFT holders vote; the swarm settles an appeal; the contract pays. $YOLO already has, in this code or its docs: a 10% fee on accepted tasks split between NFT voters, the treasury and a doer who locked YOLO for 1-52 weeks; a prediction market betting WILL DO / WON'T DO per task; a buyback-and-burn of other currencies' fee shares into YOLO; a trophy NFT whose mint burns YOLO; a 150,000,000 YOLO "Stabilizer / MM fund" wallet with fixed public selling rules for the launch hour (keep this fund and its name exactly as is; do not propose removing or renaming it). Do not re-propose any of the five mechanics just listed; propose what is not already there.
>
> RULE THAT EVERY IDEA MUST RESPECT: no mechanic may pay a user from the incentives, treasury, dev, team or liquidity allocation. Every payout must come from money a paying contract already holds because of an action inside that same mechanic (a fee, a bond, a burn, a lock) — never "a wallet sends tokens to reward X". An idea that needs a wallet to hand out tokens is invalid; say instead where the money for it already sits.
>
> HOW MANY: at least 10 distinct ideas, ranked best first by your own judgement of (real $YOLO demand created) vs (abuse risk) vs (contract complexity). You may add up to 5 more past the 10 if they are strong; do not pad weak ideas just to reach a count.
>
> REPORT FORMAT, MANDATORY. Entry 0: the ranked list, one line each, name only, best first. Then one full entry per idea, in the same rank order, every field below, even one line each when a field is simple. A missing field or a missing idea below your stated count makes the report INCOMPLETE.
> - NAME: short.
> - WHAT: the mechanic, one paragraph, specific enough to estimate a Solidity contract from.
> - WHY: what $YOLO demand, holding reason, or board quality this creates, that does not already exist in the five mechanics listed above.
> - WHO PAYS: the exact source of the money or token that moves (a fee percentage, a bond, a new burn, a lock) — not a fund wallet.
> - ABUSE RISK: the cheapest attack you can see (Sybil, collusion, self-dealing, price manipulation, griefing), and roughly what it costs an attacker versus what it extracts.
> - CONTRACT COMPLEXITY: low, medium or high, with one reason (new storage, new external call, new oracle-like dependency, interaction with an existing desk or vault).
> - CONFLICTS: does it need a $YOLO price, a new timer, a new role, or touch an existing fee split (name which one)?
>
> Last entry, after the ranked ideas: COVERAGE. What in docs/TOKENOMICS.md or docs/TOKEN-UTILITY.md you read in full, and whether you treated anything there as already existing and therefore out of scope for a new idea.
>
> Not in scope: changes to the core dispute flow (CaseDesk, DisputeDesk), the NFT vote itself, the launch's first-hour rules, or the Stabilizer wallet's own rules — propose additions around them, not changes to them. No idea requiring a centralized price feed the contract does not already have, unless the idea itself names which existing on-chain price source it reuses.

| | |
|---|---|
| Repository | https://github.com/sergey-mavr-agent/moneycanbuy.fun.git |
| Commit | `8603831d172a3573a31035399482f49a5048bbc3` |
| Job | `36b1ee2b-4a5f-4987-8276-0f3de731ff0d` |
| Judged | 2026-10-16 20:29 UTC |
| Findings | 7 medium · 9 low · 3 info |

Four agents audited the code as it is at `8603831`, 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. Medium: YoloSweetener acts on the vault's un-advanced task status: YOLO is accepted into a task whose active submission is already final, and is then paid to that earlier doer

`contracts/src/YoloSweetener.sol:112`

```
        if (vault.submitTerms(taskId, address(0)).status != TaskStatus.Open) revert TaskNotOpen();
```

TaskVault plays its timers lazily: every vault entry point that touches a task (topUp, announceWithdraw, withdrawContribution, poke, submit) runs _advance first (TaskVault.sol:606), so the vault never books money against a stale status. YoloSweetener reads vault.submitTerms, a plain view that returns the stored status and busy flag without advancing (TaskVault.sol:395-407), in sweeten (line 112), announceWithdraw (line 126) and withdraw (line 150). After the active submission's dispute window has ended, or after the deadline, and before anyone pokes, the stored status is still Open and busy is still true. In that gap sweeten accepts YOLO, announceWithdraw reverts SubmissionActive, withdraw reverts BonusLocked, and once anyone pokes the task is Completed and payout hands the late bonus to a doer who was accepted before the bonus existed. The sweetener's header comment says a doer 'sees the bonus leaving before he submits' and TOKEN-UTILITY.md section 2 says the add-on 'follows exactly the same lock-until-deadline rule as the reward itself'; the vault's own topUp in the same state reverts TaskNotOpen. Fix: call TaskVault.poke(taskId) (public, no money moves) before reading submitTerms in sweeten, announceWithdraw and withdraw, or expose an advancing variant of submitTerms; additionally refuse sweeten when t.deadline != 0 && block.timestamp >= t.deadline. Merged from audit_flow (same mechanism).

**Reproduction**

Ran contracts/test/scratch/JudgeSweetener.t.sol (Base fixture, forge test --match-path 'test/scratch/*'). Case A: author creates an ETH task of 0.01 ETH with no deadline; doer submits; warp 24 h + 1 s with nobody poking. vault.topUp by the author reverts TaskNotOpen (the vault advanced and completed the task). sweetener.sweeten(t, 1_000_000e18) by alice succeeds, bonusOf(t) == 1_000_000e18; alice's announceWithdraw reverts SubmissionActive; after vault.poke(t) the task status is Completed; sweetener.payout(t, s) pays the doer 900_000e18 and the treasury 100_000e18 of alice's YOLO. Expected: the sweetener refuses YOLO on a task whose submission is already final (as topUp does), or lets alice take it back; actual: alice's YOLO goes to the doer with no recovery path. Case B: task with deadline now + 1 day; warp 1 day + 1 s; vault.topUp reverts TaskNotOpen while sweetener.sweeten succeeds and books 1_000e18 into a task that poke then marks Expired.

### 2. Medium: The $YOLO add-on fee (and the YOLO fee of YOLO rewards) goes to the treasury wallet, read from the current parameter version, against the documented burn / voter / doer-bonus split

`contracts/src/YoloSweetener.sol:211`

```
        if (fee != 0) yolo.safeTransfer(treasury, fee);
```

TOKEN-UTILITY.md line 34-35 says a $YOLO add-on's fee is 'split exactly the way a $YOLO reward's fee would be', and TOKENOMICS.md lines 88-90 state as a contract rule that 'the fee YOLO of YOLO-denominated rewards and bonuses never reaches the treasury wallet at all (the contract burns it directly)'. In the code at this commit: YoloSweetener.payout (lines 208-211) sends the whole 10% fee to a treasury address read from registry.core(registry.currentVersion()) (line 209) rather than from the task's own version t.version; DisputeDesk._payReward (line 563, `bank.addTreasury(f.currency, fee - toVoters);`) books the fee of a YOLO-denominated reward to the treasury pot that sweepTreasury credits to the treasury wallet. No contract in src/ burns YOLO except DareTrophy. Three effects: the bonus fee is never burned; correct NFT voters get no share of it; and a treasury change through proposeParams/activateParams redirects the fees of tasks that completed before the change but were not yet paid out. The brief marks contracts/ as the earlier rules, but these are the only shipped payout paths for YOLO fees and the supply-side accounting in TOKENOMICS (the treasury never accumulates YOLO income) rests on this sentence. Fix: either mark the TOKENOMICS sentence as a target rule, or route the add-on fee through the same split as the reward fee (burn to 0x...dEaD for the treasury share, NFT-voter share, doer-stake bonus) and read the destination from registry.core(t.version). Merged from audit_permissions (sweetener) and audit_math (DisputeDesk, same drift).

**Reproduction**

Ran contracts/test/scratch/JudgeSweetener.t.sol test_feeGoesToCurrentVersionTreasury: ETH task (version v0) created, alice sweetens 1_000_000e18 YOLO, doer submits, warp 24 h + 1, poke -> Completed. Owner proposes defaultParams with treasury = 0xBEEF, warp TIMELOCK + 1, activateParams -> currentVersion == v0 + 1. sweetener.payout(t, s): yolo.balanceOf(0xBEEF) == 100_000e18, the version-v0 treasury receives 0, yolo.balanceOf(0xdEaD) == 0. Expected per the docs: 100,000 YOLO split burn / NFT voters / doer bonus, nothing to any treasury wallet, and in any case the task's own version's treasury; actual: all 100,000 YOLO to the wallet set after the task completed, nothing burned.

### 3. Medium: YOLO Predict has no resolution rule for tasks without a deadline or tasks that end Unwound: the weight formula has no denominator and irrevocable positions lock forever

`docs/TOKEN-UTILITY.md:161`

```
Any task gets an automatic prediction market, open from its creation to its deadline, paused while a submission
```

Section 7 resolves the market only two ways: completion makes WILL DO the winner, 'the deadline passing with nothing accepted' makes WON'T DO the winner; a position 'can never be withdrawn or cancelled'; weight is (time left until the deadline / the task's whole length)^2 measured from creation. The protocol allows tasks with no deadline: TaskVault.createTask line 224 `if (deadline != 0 && deadline <= block.timestamp) revert BadDeadline();`, VaultTypes.sol line 116 `uint64 deadline; // 0 = no deadline`, MECHANICS.md 'deadline 0 = none'. A task can also end Unwound through emergencyExit (TaskVault.sol:362), which is neither 'completed' nor 'deadline passed'. For a deadline-less task the weight's 'whole length' is undefined (0 - created, or infinite), WON'T DO can never win, and the market is open 'to its deadline', i.e. forever. For an Unwound task neither resolution applies. In both cases the doc gives no winner, no void and no refund, so after the 5% burn and 5% deferral the 90% pools and the deferred part sit in the Predict contract indefinitely, contradicting line 130 ('nothing is created or destroyed by a payout, only redirected'). Fix: state that deadline-less tasks have no Predict market (or define a finite market length and WON'T DO on Expired only), and define Unwound as a void that returns both 90% pools pro rata with the deferred 5% burned. Merged from audit_flow and audit_math (same gap).

**Reproduction**

Input: createTask(currency = USDC, amount = 100e6, deadline = 0, ...), which TaskVault.sol:224 accepts (the Base fixture's _create helper uses deadline 0 for every task). Alice places 10,000 YOLO WILL DO at creation + 1 day: 500 burned, 500 deferred, 9,000 in the WILL DO pool. Nobody ever submits. Expected per section 7: weight in [0, 1] and a resolution at the deadline. Actual per the doc's own formula: whole length = deadline - creation has no finite value, the WON'T DO side can never win because no deadline ever passes, alice cannot cancel ('no undo'), and her 9,000 YOLO plus the 500 deferred stay in the Predict contract forever. Second input: task with a deadline, a submission whose step stalls, emergencyExit after emergencyDelay (30 d) -> status Unwound before the deadline; neither 'completed on time' nor 'deadline passing with nothing accepted' applies, so no winner, no refund.

### 4. Medium: YOLO Predict: a single late position on an otherwise empty side takes the whole opposite pool regardless of its weight (sole-side sniping, including front-running a doer's submit)

`docs/TOKEN-UTILITY.md:176`

```
  winner. Winners split the losing side's 90% pool by points (position × weight), on top of getting their own
```

The weight ((time left / whole length)^2) only divides the losing pool among positions on the same side. When one side holds exactly one position, it owns 100% of that side's points, so its weight is irrelevant and it collects the entire opposite 90% pool. The 'nobody predicted the winning side' rules (lines 180-183) make this worse: a single minimum position placed at the last moment moves the whole losing pool away from the author (task not done) or from author and doer (task done) to whoever placed it. Placing a position is open to anyone with no exclusion and no cutoff before the deadline (line 161-164, 185-186), so the cheapest actor is an outsider who bets last, or a mempool watcher who front-runs the doer's submit with a minimum WILL DO position (the market pauses at submission, so nobody can answer it). Fix (keeps the 'no void' rule): pay winners by points against a floor, for example points / max(winning-side points, k x losing-side points) with the unclaimed rest to the existing burn; or close a side to new positions in the final N% of the task's length, or when the side is empty and the weight is below a threshold.

**Reproduction**

Task length 10 days. The WILL DO pool holds 90,000 YOLO net from early bettors; the WON'T DO side is empty; no submission pending. At deadline - 12 s the attacker places 1,000 YOLO WON'T DO: 50 burned, 50 deferred, 900 to the pool, weight (12 / 864,000)^2 = 1.9e-10. The deadline passes with nothing accepted. Expected under the weight rule and line 182: a position placed 12 s before the deadline earns ~0 and the 90,000 goes to the author. Actual under line 176: the attacker is the only WON'T DO position, holds 100% of the side's points, and takes 90,000 + 900 back for 100 YOLO of fees; the author gets 0. Mirror case: watcher sees the doer's submit transaction in the mempool, front-runs it with a 1,000 YOLO WILL DO position; on acceptance the watcher takes the whole WON'T DO pool that lines 180-181 would otherwise split between author and doer.

### 5. Medium: The 20-vote early decision reintroduces the partial-count relay exploit that internal audit finding C-02 removed from the code

`docs/TOKEN-UTILITY.md:69`

```
- **Early decision.** The moment one side is ahead by **20 votes or more** (and has at least one voting wallet),
```

Section 4 says the vote closes the moment one side is ahead by 20 votes with at least one voting wallet. Votes are EIP-712 ballots relayed in batches by anyone (DisputeDesk.sol:88 submitVotes). docs/AUDIT-INTERNAL.md lines 145-151 (C-02, Medium) showed that an early decision on a partial count is decided by relay order in a public mempool: a front-runner copies one side's ballots out of a pending batch and relays them alone, the count crosses the margin, the honest batch reverts WrongPhase and the other side's ballots are never counted. The code was fixed to decide only at the end of the vote window: VaultParams.sol:45 `uint32 instantDelta; // 100: UNUSED (audit C-02: no instant decision); kept for the ABI and launch`, and CaseDesk only decides at voteEnd. The current rules document re-specifies the removed behaviour with a lower margin (20 against the retired 100) and treats it as a live per-task parameter (line 76, line 251). Fix: drop the early decision and decide only at the 24-hour mark as the code does since C-02, or require the margin against every ballot signed before the close rather than the count at the moment a batch lands.

**Reproduction**

State (C-02's own numbers): a disputed submission with 120 accept and 115 reject ballots signed and sitting in one relayer's pending batch. Expected under the end-of-window rule: diff 5 < 10, no decision, the case goes to the no-decision path. Actual under line 69: anyone (no NFT needed) relays 20 of the accept ballots alone first; the moment they are counted accept leads 20-0 with >= 1 wallet, the vote closes as Accept, the other 215 ballots are never counted. The same actor can do the reverse with 20 reject ballots. Verified against the tree: instantDelta is marked UNUSED at VaultParams.sol:45 and AUDIT-INTERNAL.md:145-151 documents the removed exploit.

### 6. Medium: Predict pool size is decoupled from the dispute incentive: an undisputed junk proof on a small task takes the whole WON'T DO pool for a 0.5 IMD + 3% bond

`docs/TOKEN-UTILITY.md:185`

```
**Known, accepted risk: no cap, no exclusion, no undo.** There is no limit on the total size of either side of
```

The documented risks (lines 185-194) cover the doer or author betting against a task. They do not cover a WILL DO bettor who needs no role at all: any wallet can submit a proof (section 3); a submission nobody disputes within 24 hours is accepted and paid (section 4), which also resolves Predict as WILL DO (line 174). Below the swarm pre-check threshold the only defence against a junk proof is a dispute, and the disputer's whole upside is 50% of the doer's bond (0.25 IMD + 1.5% of the reward, line 127), which scales with the reward, not with the Predict pool, while the pool has no cap (line 185). So the cost of defending a task does not grow with what is at stake on it. Fix at docs level, without touching the core flow: pay the successful disputer of a submission a share (for example 5%) of the WILL DO side's pool out of the Predict contract (money that contract already holds because of positions placed in the same mechanic), or cap each side's pool at a multiple of the reward, and state the chosen rule next to the existing accepted risk.

**Reproduction**

State: task T with reward 5 USDC (the minimum, line 18), deadline in 7 days, below the $300 swarm pre-check. Attacker A bets 1,000 YOLO WILL DO at creation (weight 1). Others bet 10,000,000 YOLO WON'T DO (9,000,000 net in the pool). Day 6: A submits a proof link to an unrelated page with bond 0.5 IMD + 0.15 USDC (line 42). Nobody disputes within 24 h (the disputer's expected gain is 0.25 IMD + 0.075 USDC): the bond returns, A is paid 4.5 USDC, Predict resolves WILL DO, A as the sole WILL DO position takes 9,000,000 YOLO plus 900 of his own back. Cost to A on failure: 0.5 IMD + 0.15 USDC. Expected per lines 161-176: the pool goes to whoever predicted the real outcome; actual: it goes to whoever posts a 0.5 IMD bond on a task nobody watches.

### 7. Medium: The three price parameters have no named setter, no bounds and no lower bound: an instant FDV change re-routes the doer bonus, and FDV = 0 or price = 0 breaks every non-YOLO submission

`docs/TOKENOMICS.md:48`

```
Three numbers are plain parameters, each set in a single transaction that emits an event the site reads:
```

TOKENOMICS.md never says who may set the FDV, ETH and IMD price parameters; every other parameter is ParamsRegistry owner + 48 h timelock + hard bounds, these three have neither (line 151-155) and the doer bonus snapshots them at submission (TOKEN-UTILITY.md 141-145). Two concrete consequences the docs do not cover. (1) Whoever holds the setter can, in one block, move up to 4% of every subsequent fee from burn + treasury to the doer; and with no timelock it is a free option: a doer who sees a pending FDV decrease in the mempool submits first and locks in the higher bonus. (2) There is no lower bound: with FDV = 0 the conversion reward-in-YOLO = reward_usd x 1e9 / FDV divides by zero on every ETH/USDC/IMD submission (a revert blocks all non-YOLO submissions until the parameter is changed, since the bonus is fixed at submission); with ETH price = 0 reward-in-YOLO is 0, so power / 0 is undefined and a zero guard that returns min(1, ...) = 1 grants the full 4% to any 1-week lock of 1 YOLO on every ETH task. TOKENOMICS line 65 says 'Nothing breaks when that happens'. The FDV's decimals are also unspecified (whole dollars vs 18 decimals), which decides whether a sub-$1,000 FDV truncates the YOLO price to 0 in integer math. Fix: name the role (the ParamsRegistry owner), give the three parameters hard bounds with a non-zero minimum and a per-update maximum step or a minimum delay, define their decimals, and specify 'no bonus' when the conversion yields 0; keep the submission-time snapshot. Merged from audit_permissions (setter / mempool option) and audit_math (zero bound).

**Reproduction**

Case 1: honest FDV = $10M (YOLO = $0.01). A doer locks 52,000 YOLO for 1 week: power = 52,000 x 1/52 = 1,000. On a 10,000 USDC task reward-in-YOLO = 1,000,000, power / reward = 0.001 < 0.1, bonus = $0. The setter sets FDV = $10B (YOLO = $10) in block N; the doer submits in block N+1: reward-in-YOLO = 1,000, power / reward = 1, bonus fixed at 4% = $400; the setter restores FDV in block N+2. Actual: $400 of the fee that should go 50/50 to burn and treasury goes to the doer, and line 143 says a later change does not reach back. Case 2: FDV parameter set to 0 (nothing in the docs forbids it). Doer submits on a 100 USDC task with 50,000 YOLO locked 52 weeks. Expected per TOKEN-UTILITY line 141: bonus = 4% x min(1, 50,000 / reward-in-YOLO). Actual: reward-in-YOLO = 100 x 1e9 / 0, division by zero; the submission reverts (DoS of every non-YOLO submission) or, with a zero guard, grants an unbounded bonus.

### 8. Low: Doer-stake bonus is fixed at submission but forfeited if the lock ends before payout, and a 1% dispute by any third party can push payout past the lock's end

`docs/TOKEN-UTILITY.md:148`

```
  the same lock is still running. A lock that ended early forfeits the bonus (not the locked YOLO itself — only
```

Lines 147-148 fix power and bonus at submission but re-check at payout that 'the same lock is still running' and forfeit the bonus otherwise. Lines 155-157 say a lock has no early exit under any circumstance, so the only way a lock can have 'ended' at payout is by running its full term honestly. Payout is at least 24 h after submission (dispute window) and up to roughly 54 h with a vote and a swarm call, and payout timing is not in the doer's control: any non-doer can dispute for 0.5 IMD + 1% of the reward and anyone can call the swarm. Every submission made in the last ~54 h of a lock therefore loses its bonus although the doer did what the rule asked, and a rival can strip a 4% bonus at a 1% cost. Where the forfeited bonus goes is not stated. Fix: judge the bonus on the lock running at submission only (power is already frozen then), or require at submission that lockEnd >= now + dispute window + vote + swarm timeout, and say where a forfeited bonus goes. Merged from audit_economics and audit_permissions (same mechanism).

**Reproduction**

State: doer D locks 1,000,000 YOLO for 1 week at T0. At T0 + 6 d 20 h D submits proof on a YOLO task with reward 10,000 YOLO; bonus fixed at 4% = 400 YOLO. Nobody disputes; the task settles at T0 + 7 d 20 h; the lock ended at T0 + 7 d. Expected (line 147, 'fixed at the moment of submission'): D gets 9,400 YOLO. Actual (line 148): D gets 9,000 YOLO and the 400 go to an undefined destination. Variant with a hostile third party: D submits a 100,000 USDC task 30 h before the lock ends (bonus $4,000); rival R disputes at hour 23 for 0.5 IMD + 1,000 USDC; the vote runs 24 h and accepts; payout at about hour 47, after the lock ended at hour 30; R loses the bond (500 USDC to D, 500 to voters) and D still loses the $4,000 bonus, so R's attack costs 1,000 USDC to destroy 4,000 USDC of D's income.

### 9. Low: Doer-stake power is amount x weeks with no minimum term tied to the task and is never consumed per submission: a 1-week lock of 52x the reward earns the full bonus, and one lock covers any number of c

`docs/TOKEN-UTILITY.md:138`

```
- **Power** = amount locked × (weeks locked ÷ 52). Fixed the moment the lock is set or extended — it does not
```

Section 6's stated purpose is a bigger share for taking YOLO 'out of circulation ... for the whole term'. Power = amount x (weeks / 52) with no cap on amount; the bonus is fixed at submission; the only later check is that the lock is still running (line 147). An undisputed task settles within 24 h, so a 1-week lock always outlives it: a doer reaches full power with a 1-week lock of 52 x reward instead of a 52-week lock of 1 x reward, pays the same 4% bonus and is free after 7 days. Power is also compared with each reward on its own and never reserved: line 102 defines full power as 'locked YOLO worth at least as much as the reward', but one lock gives full power on every open submission at once, so the YOLO demand the stake creates grows with the doer's largest single reward, not with the volume taken. Fix: require the lock to run at least N weeks past the submission (or past the task deadline), or weight power by remaining term at payout; and reserve power per active submission (sum of reserved <= power, released at payout or rejection) or divide power by the doer's open submission count. Merged from audit_flow (term) and audit_permissions (concurrency); same mechanic, two gaps.

**Reproduction**

Input 1: task reward 1,000 YOLO; doer locks 52,000 YOLO for 1 week on day 0, submits on day 0, no dispute, accepted on day 1. Power = 52,000 x 1/52 = 1,000 = reward, so b = 4% x min(1, 1) = 4%: doer gets 940 YOLO instead of 900; the lock ends on day 7 and the 52,000 YOLO are free again. Input 2: a doer locks 1,000,000 YOLO for 52 weeks (power 1,000,000) and has 50 submissions running at once, each on a 1,000,000 YOLO reward. Expected per line 102: full power backs about 1,000,000 YOLO of rewards. Actual: the full 4% on all 50, i.e. 2,000,000 YOLO of bonus backed by a lock that covers 1/50 of the rewards.

### 10. Low: Dispute bond 'in the reward's currency' contradicts 'the whole flow moves no ETH through the contract at all' for ETH-denominated tasks

`docs/TOKEN-UTILITY.md:92`

```
and is never reimbursed for it from the protocol's money. The whole flow — dispute, vote, appeal — moves no ETH
```

Section 4 line 59 defines the dispute bond as '0.5 IMD + 1% of the reward, in the reward's currency' and the appeal as paying 'the dispute bond again'. For a task whose reward is ETH that bond is ETH, yet lines 91-93 state that the dispute, vote and appeal flow 'moves no ETH through the contract at all'. The two sentences cannot both be implemented; the implementer must pick one (bonds always in IMD, as the outdated code does with Desk._bondFor, or ETH bonds and the 'no ETH' sentence dropped). The money-flow table (line 225, 'Dispute / appeal bond ... returned to the winner') names no currency either.

**Reproduction**

Input: a task with currency = ETH and reward 0.04 ETH (the table at line 47 uses this reward). Section 4 rule: dispute bond = 0.5 IMD + 1% x 0.04 ETH = 0.5 IMD + 0.0004 ETH, so the disputer sends 0.0004 ETH to the contract. Line 92-93 rule: no ETH moves through the contract for a dispute. Expected: one consistent rule; actual: two different transactions for the same dispute.

### 11. Low: NFT voters' fee share is restricted to '$YOLO-denominated tasks' in section 9 but is 5% of every accepted reward in TOKENOMICS and in section 5's $100 USDC example

`docs/TOKEN-UTILITY.md:210`

```
  $YOLO-denominated tasks. A correct vote earns a share of the loser's bond and, on a $YOLO-denominated task, a
```

TOKENOMICS.md lines 80-83 ('5%, the NFT part. Goes to the identity.md NFT holders who voted correctly in a dispute') and TOKEN-UTILITY.md section 5 ('Disputed, NFT voted correctly, submission accepted' on a $100 reward: NFT voters $5) make the fee share currency-independent. Section 9 lines 210-212 restrict it to 'a $YOLO-denominated task, a share of that task's fee — paid in whatever currency the task's reward was in', which is self-contradictory (a $YOLO-denominated task's reward currency is always YOLO) and contradicts the $100 example. A reader of section 9 would conclude a correct vote on a USDC task earns only the bond share. Fix: reword section 9 to 'on any task, a share of that task's fee, paid in the reward's currency'.

**Reproduction**

Input: USDC task, reward 100 USDC, disputed, NFT vote decides 'accept' correctly, swarm not called. TOKENOMICS and section 5 expected: correct voters share 5 USDC of fee plus 50% of the loser's bond. Section 9 reading: the task is not $YOLO-denominated, so voters get only the bond share and 0 USDC of fee. Two payouts for the same input.

### 12. Low: A staked doer whose power is below a tenth of the reward: TOKENOMICS burns the NFT part ('unless the doer was staked'), TOKEN-UTILITY sends it to the treasury ('same as no stake')

`docs/TOKENOMICS.md:81`

```
  or a dispute the NFT vote got wrong or never decided, these 5% go to the treasury — unless the doer was staked,
```

TOKENOMICS.md lines 80-83: with no dispute the 5% NFT part goes to the treasury 'unless the doer was staked, in which case they are burned instead'. TOKEN-UTILITY.md lines 113-114 and 146: below a tenth of the reward's worth 'the outcome is the same as no stake' / 'same payout as having no stake at all'. A doer with a running lock whose power is 5% of the reward 'was staked' (TOKENOMICS: burn) but has the 'same payout as no stake' (TOKEN-UTILITY: treasury). The money-flow table (line 227) does not settle it. Fix: state whether the burn of the NFT part keys off 'has any running lock' or off 'earns a non-zero bonus'.

**Reproduction**

Input: $100 USDC reward, no dispute, doer holds a 52-week lock whose power converts to $5 of YOLO (5% of the reward, below the tenth). TOKENOMICS rule: doer $90, NFT part $5 burned, staker part with no bonus split evenly: burn $2.50, treasury $2.50 (burn $7.50 total). TOKEN-UTILITY rule ('same as no stake', section 5 row 'No stake'): doer $90, burn $0, treasury $10. Same input, two different burn amounts.

### 13. Low: Predict payout by points has no stated fixed-point scale: late winners truncate to zero points, and a winning side with zero total points leaves the losing pool with no destination

`docs/TOKEN-UTILITY.md:171`

```
- **Weight**: a position's voting power is (time left until the deadline ÷ the task's whole length)², fixed the
```

Weight is a squared ratio below 1 with no stated scale, and line 176 divides the losing pool by total winner points. (1) With any finite scale a late position's weight truncates to 0 (at 1e4 any position with under 1% of the task's length left; at 1e6 under 0.1%), so a correct late predictor gets only their own 90% back and none of the losing pool although the doc calls them a winner. (2) When every winning position has zero points (all late, or one placed in the deadline block where time left = 0 at any scale), total winner points = 0, the split is a division by zero and the losing side's 90% pool has no destination; the 'nobody predicted the winning side' rule (line 178) does not apply because someone did. Line 130's invariant ('nothing is created or destroyed by a payout, only redirected') is violated. Fix: state the scale (for example 1e18 fixed point with a floor of 1 wei of weight) and add a rule for total winner points = 0 (split pro rata to position size, or treat as 'nobody predicted the winning side').

**Reproduction**

Input: task length 30 days; one WILL DO position of 1,000 YOLO placed 4 hours before the deadline (time left / length = 1/180, squared = 3.1e-5); WON'T DO pool 10,000 YOLO; task completed on time. With weight in basis points: weight = floor(0.31) = 0, points = 0, total winner points = 0. Expected: the WILL DO predictor receives the 9,000 YOLO losing pool (90% of 10,000) plus their 900. Actual: payout = 9,000 x 0 / 0, undefined; no rule names where the 9,000 YOLO go.

### 14. Low: Outside-caller refund is a fixed 0.5 IMD while the swarm fee is live-priced, and the remaining split 'in proportion to their bonds' compares bonds of two currencies with no price source

`docs/TOKEN-UTILITY.md:81`

```
nor the original disputer) wins, the loser's bond pays them first: 0.5 IMD (refunding the swarm fee they fronted),
```

Section 4 has the appellant pay 'the dispute bond again plus the live swarm fee' but refunds a fixed 0.5 IMD 'refunding the swarm fee they fronted'. The swarm fee is whatever the Intake sells the action for at that moment (Desk.sol:108 _swarmPrice; the code reimburses the actual amount spent, DisputeDesk.sol:512 `feeBack = a.spent < penalty ? a.spent : penalty;`). If the fee is 2 IMD the caller is under-refunded by 1.5 IMD; if it is 0.1 IMD they are over-refunded by 0.4 IMD out of the loser's bond. Separately, the loser's bond is '0.5 IMD + 1% (or 3%) of the reward in the reward's currency'; after the IMD leg is used for the refund the remainder is in the reward currency, and 'split between the winning side and the outside caller in proportion to their bonds' compares bonds of two currencies, while TOKENOMICS says the protocol has no price source beyond three manual parameters that serve the doer-stake bonus only. Fix: refund min(fee actually paid, loser's IMD leg) and state the proportion per currency leg.

**Reproduction**

State: ETH task, reward 1 ETH; doer bond 0.5 IMD + 0.03 ETH; outside caller's appeal bond 0.5 IMD + 0.01 ETH, live swarm fee 2 IMD paid by the caller; swarm sides with the caller; loser = doer. Expected: the caller is made whole for the fee fronted (2 IMD), as the code does at DisputeDesk.sol:512. Actual per the doc: the caller gets 0.5 IMD (the whole IMD leg of the doer's bond) and is 1.5 IMD short; the remaining 0.03 ETH is to be split 'in proportion to their bonds' between the disputer (0.5 IMD + 0.01 ETH) and the caller (0.5 IMD + 0.01 ETH), which has no single answer across currencies.

### 15. Low: _imdToUsd truncates to whole micro-dollars before scaling: the per-YOLO price read by DareTrophy.price and YoloSweetener._cap is floored, 0.8% low at the launch floor price

`contracts/src/oracles/ChainlinkUniV3PriceOracle.sol:111`

```
        return (amount * 1e6 / imdWeiPerUsdc()) * _chainlink(usdcUsd) / 1e6;
```

_imdToUsd divides amount x 1e6 by imdWeiPerUsdc() to whole USDC units (1e-6 USD) and only then multiplies by the USDC/USD price. Both YOLO consumers that need a per-token price call usdValue(yolo, 1e18) (DareTrophy.sol:115, YoloSweetener.sol:223), which for the IMD-quoted YOLO sample resolves to _imdToUsd of the IMD value of one token, so the result is a whole number of micro-dollars. At the launch floor (yoloFloorImd = 1.5e13 IMD wei per YOLO) and IMD about $7.6 the exact value is $0.00011390 per YOLO and the function returns $0.000113, 0.79% low; the error is 1 / (micro-dollars per YOLO) and grows as IMD/USD falls. DareTrophy.price = ceilDiv(1.5e36, usdPerYolo) therefore charges about 104 YOLO more per mint, and YoloSweetener's cap = 300e18 x 1e18 / usdPerYolo is about 0.8% higher, paying an unconfirmed doer more and returning less to sweeteners. Multiplying before dividing (amount x _chainlink(usdcUsd) / imdWeiPerUsdc()) removes it with no overflow risk at realistic amounts. Bounded, non-compounding; low.

**Reproduction**

Ran contracts/test/scratch/JudgeOracle.t.sol (harness exposing _imdToUsd; mock USDC/USD feed at $1.00, mock v3 pool at constant tick 256,050, the oracle's launch arguments with yoloFloorImd = 1.5e13): imdToUsd(1.5e13) = 113000000000000 ($0.000113) while imdToUsd(1.5e13 x 1e6) / 1e6 = 113903486000000 ($0.0001139); difference 903,486,000,000 wei of USD = 79 bps. Expected: the two agree to within 1 wei. Actual: every per-token read is floored to a micro-dollar.

### 16. Low: The trophy burn is priced off the FDV parameter, contradicting TOKENOMICS' 'exactly one job' and leaving a second value flow under the unbounded, instant setter

`docs/TOKEN-UTILITY.md:199`

```
burns $1.50 worth of $YOLO, priced off the FDV parameter (see [TOKENOMICS.md](TOKENOMICS.md)) at mint
```

TOKENOMICS.md lines 57-62 say the price parameters 'have exactly one job left in the whole protocol' (the doer-stake threshold) and lines 151-158 that 'no money is at risk' from them. TOKEN-UTILITY.md section 8 also sizes the trophy burn off the FDV parameter. The two documents disagree, and the risk section omits a second value flow the unbounded setter controls. Fix: give the trophy a fixed YOLO price (as DareTrophy.fallbackYolo already does in the outdated code), or list the trophy in TOKENOMICS as a second use and bound the parameter.

**Reproduction**

FDV parameter = $10M: a mint burns 1.50 / 0.01 = 150 YOLO. The setter changes FDV to $10B: a mint burns 1.50 / 10 = 0.15 YOLO, 1/1000 of the intended burn, for every trophy minted while it stands. Expected per TOKENOMICS line 61: the FDV parameter affects only the doer-stake threshold. Actual per TOKEN-UTILITY line 199: it also sets the trophy burn.

### 17. Info: The 'buyback burner' is referenced in the risks section but defined nowhere in the two current token docs, which route non-YOLO fee income to the treasury

`docs/TOKEN-UTILITY.md:246`

```
- **No project server runs any of this.** There is no backend, bot or keeper: whoever wants the buyback burner
```

Line 246-248 names a 'buyback burner' that users must trigger. No section of TOKEN-UTILITY.md or TOKENOMICS.md defines what it buys back, from which balance, through which pool, or with what slippage rule. TOKENOMICS line 92-93 says the treasury accumulates YOLO 'from its share of ETH, USDC and IMD fees', and the money-flow table (line 227) routes the fee's treasury share to the treasury with no buyback step. A reader cannot tell whether ETH/USDC/IMD fee income is kept by the treasury wallet, swapped to YOLO and burned by a contract, or both. The assignment lists the buyback-and-burn as an existing mechanic, so the docs should carry one definition and the money-flow table a row for it.

**Reproduction**

Input: grep 'buyback' across docs/ and README.md: the only hit is TOKEN-UTILITY.md:246. Read section 10 (lines 218-234) looking for the flow 'treasury share of ETH/USDC/IMD fee -> buyback -> burn'. Expected: a row naming the payer, the receiver (burn) and the trigger (anyone). Actual: no such row; line 227 ends at 'the treasury'.

### 18. Info: Doc constants drift from the launch parameters: NFT fee share 5% in the docs vs nftFeeBps 600 in the code, and the '6% burn' sentence holds only for undisputed YOLO tasks

`contracts/src/VaultParams.sol:161`

```
        p.nftFeeBps = 600;
```

TOKENOMICS.md splits the 10% fee 5% / 5% and TOKEN-UTILITY.md section 5 tabulates '$5 NFT voters / $5 treasury' for a disputed accepted $100 task; the launch parameters give correct voters 6% (VaultParams.sol:161, 600 of feeBps 1000), so the deployed code pays $6 / $4. The docs are the stated current rules and the contracts the earlier ones, so this is a drift to reconcile. Also TOKEN-UTILITY.md:152-153 says a full-power staked doer on a YOLO reward 'burns 6% total instead of 10%'; by the doc's own split that holds only when undisputed (NFT part 5% burned + 0.5% + 0.5%); when a dispute is decided right by the NFT vote the 5% goes to voters and the burn is 1%. Arithmetic that does check out: the seven bucket amounts sum to exactly 1,000,000,000; the unlock table matches Vestings.sol; every $100 row in section 5 sums to $100.

**Reproduction**

Input: 100 USDC task, disputed, NFT votes correctly, accepted, doer unstaked. Expected per TOKEN-UTILITY.md section 5: doer 90, voters 5, treasury 5. Actual per DisputeDesk._payReward with the launch parameters (feeBps 1000, nftFeeBps 600): doer 90, voters 6, treasury 4. Fix: change one side to match (nftFeeBps = 500, or the docs to 6%).

### 19. Info: Doer-stake power and threshold leave integer order and the comparator unspecified: 'weeks ÷ 52' first truncates to 0, and 'below a tenth' does not say whether exactly a tenth qualifies

`docs/TOKEN-UTILITY.md:146`

```
- Power below a tenth of the reward earns nothing — same payout as having no stake at all.
```

Section 6 writes power as amount x (weeks / 52). Taken literally in integer arithmetic the parenthesis is 0 for every lock shorter than 52 weeks, so only a full-year lock has any power; the intended amount x weeks / 52 must be stated. The threshold 'power below a tenth of the reward earns nothing' does not say whether exactly one tenth qualifies; at exactly 10% the bonus jumps from 0 to 0.4%, so the comparator decides a payout. Neither loses funds, but both are needed to write the contract the doc is meant to be estimated from. Fix: write power = amount x weeks / 52 and 'power x 10 < reward earns nothing'.

**Reproduction**

Input: 100,000 YOLO locked for 26 weeks on a 100,000 YOLO reward. Expected (intended): power = 50,000, bonus = 4% x 0.5 = 2%. Actual if implemented as written in integers: 100,000 x (26 / 52) = 100,000 x 0 = 0, no bonus. Input 2: power exactly 10,000 on a 100,000 reward: the doc gives both 'earns nothing' and 0.4% depending on the comparator.

---

Judge's submission `a1a5607080865e69c0736ffe71951b8f1895d3a4bfc6bf2db930251833e75db8`, 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.
