# Audit report

> Audit the protocol logic and role scenarios of moneycanbuy.fun for abuse before mainnet launch. Repository at commit 84b446c0ff513a39c60486edada40a4a1d781b6b. Run forge at the repo root, import as "src/...". Modify no existing file; new files only under contracts/test/audit/. Do not re-report what a passing test there proves fixed.
>
> SCOPE. Walk every role through a task from creation to the last payout and look for logic abuse: holding someone else's money, skipping or capturing the dispute, voting twice, collusion against third parties, what a user cannot learn from the chain alone. Code security and payout amounts are other orders; a wrong percentage or a bug with no role scenario is one info line here.
>
> SOURCE OF TRUTH. The rules below win over docs and comments. Code: contracts/src/ (TaskVault, desks/, VotePower, YoloPredict, YoloSweetener, YoloLockHub, HiddenList).
>
> RULES.
> R1 Task: reward in ETH, USDC, IMD or YOLO (min 0.002 ETH, 5 USDC, 1 IMD, 1,000 YOLO), fixed deadline. Anyone tops up while Open before the deadline; a top-up during a check joins the reward at once. Nobody withdraws while Open; Expired: each contributor takes back his own; a Completed task takes no new money.
> R2 Submit: anyone, the author too; bond 0.5 IMD + 3% of the author's own share; one active, the rest queued and judged even after the deadline; on acceptance or cancel queued ones void, bond back. The doer writes why it is honest at submission. The proof label moneycanbuy.fun is optional unless the condition asks for it. A doer whose YOLO lock (YoloLockHub) runs from submission to payout gets up to 4% of the reward extra; any power counts, no threshold.
> R3 Dispute: 24 h from activation, anyone but the doer, one disputer per submission, bond 0.5 IMD + 1% of the author's own share; reason required, proof link optional.
> R4 Vote: 24 h; DisputeDesk.vote(subId, side, payTo, tokenIds); NFT ids owned now or delegated (delegate registry v2); each id once per dispute; side fixed; own transaction and gas. Instant decision at a 20-vote lead; at the end a 10-vote lead and >= 1 wallet decide; else no decision.
> R5 Swarm: after a decision anyone appeals within 24 h (the doer only a rejection); a dispute side pays the fee from its bond, an outsider posts bond + fee; anyone asks the question in a later call; an appeal not asked within 6 h voids with a full refund. No decision: anyone calls the swarm within 24 h; nobody calls = accepted, disputer bond to the doer. Answer within 6 h (+1 h delivery grace) is final; silence restores the prior state; max 3 calls per submission, then as if never called.
> R6 Predict: YOLO, irrevocable, before the deadline, paused while a submission is active; Completed = WILL DO, Expired = WON'T DO, cancelled = full refund. Anyone predicts, author and doer too.
> R7 The dev wallet may cancel an open task in any phase: everyone takes back what he put in except swarm fees already paid, the dev wallet nothing. The dev wallet also sets the YOLO, ETH and IMD prices by hand (USDC = $1) and the buyback wallet. The params owner may pause new tasks. HiddenList hides on the site only.
> R8 Frontends read the chain via public RPCs; no project server, keeper or relayer.
>
> KNOWN LIMITATIONS. Every line under Known limitations in docs/MECHANICS.md is an accepted rule, not a finding. Report one only if the documented rule itself is economically abusable: who profits, how much, at what cost, in tokens and $ at a stated reward. Also accepted: the disputer may call the swarm for the doer as an outsider; a winning outsider's 0.5 IMD comes back only from IMD left in the loser's bond; nothing checks that a proof is newer than the task: the burden of proof is the doer's, an old or foreign proof is for the dispute and the swarm.
>
> GOAL. Every high and medium finding with a Foundry PoC; low and info in a separate list at the end, one line each, no PoC needed.
>
> REPORT FORMAT, MANDATORY. One entry per item, same order, even "no issue"; a missing item = INCOMPLETE. Entry 0: VERDICT, max 8 lines, every blocker. Items 1-11: severity, scenario as numbered steps (who calls what, when, at what cost), file:line, loss or delay in tokens at a 100 USDC reward and a 10,000 YOLO prediction pool, smallest fix, and a Foundry PoC (full source, contracts/test/audit/, fails now and passes once fixed, no fork, no ffi) if real, else one line why not. Severity: critical = someone takes or permanently freezes another's money; high = realistic loss, or a rule above broken; medium = bounded loss, a lock over 7 days, or cheap griefing; low = edge case; info = text drift, "no issue".
>
> 1. ROLE WALK. For author, contributor, doer, queued doer, disputer, voter, delegate, appellant, outside caller, predictor, staked doer, dev wallet, params owner: each call in each phase. Flag a call allowed outside its rule, or a rule with no path.
> 2. HOSTAGE. Who keeps another's money locked, how long, at what cost, beyond the documented queue and unasked-appeal paths: a phase with no exit, a lock with no cost. Give the longest path from the deadline to the author's refund.
> 3. SKIPPING THE DISPUTE. Getting a fake accepted with no real check beyond the documented one-dispute slot: the window opening at a quiet hour through queue order, an instant decision by own NFTs, a cancel or the deadline in between.
> 4. DOUBLE VOTE. One NFT counted twice in a dispute: owner and delegate, transfer after a vote, delegation changed mid-vote, several transactions, payTo tricks; same for HiddenList votes. Can one payTo be paid on both sides?
> 5. COLLUSION VS PREDICTORS. Author + doer against outside predictors: a task meant to fail, a submission timed to freeze predictions, the deferred 2.5% + 2.5%, empty sides. Give the cheapest profitable plan an honest predictor cannot stop by disputing.
> 6. COLLUSION VS AUTHOR AND CONTRIBUTORS. Doer + disputer, doer + voters, doer + appellant; top-ups during a check; the author submitting to his own task.
> 7. THE DEV WALLET AS A PLAYER. Beyond the documented cancel: anything the dev wallet or a friend of it gains, in money or outcome.
> 8. ORDER AND RACES. Same-block front-running of dispute, vote, appeal, callSwarm, topUp, predict, cancel; lazy timers (poke) played late changing an outcome or a winner; deadline vs an active submission.
> 9. KNOWING BEFORE PAYING. From views alone, can a user learn before each call its full cost (bond, fee, max IMD in), the end of the current step, what he can claim and when? A view that lies or reverts in some phase is a finding.
> 10. NO SERVER. Can a third-party frontend with only an RPC rebuild every task, text, reason, vote and swarm answer (texts kept as hashes plus events)? Does any rule need someone of the project to send a transaction (keeper, relayer, poke)?
> 11. COVERAGE. Files read in full, what was not reached, forge build and test result (passed/failed).
>
> Not findings (by design, unless worse than stated): self-dispute from a second wallet; a single possible doer predicting WON'T DO and not doing; an author profiting from a task nobody can do; one late position on an empty side taking the other pool; NFT holders not voting on small tasks; one stake lock for all submissions; many wallets in predictions; fees of silent swarm calls kept; test/mocks, brand/, landing/.

| | |
|---|---|
| Repository | https://github.com/sergey-mavr-agent/moneycanbuy.fun.git |
| Commit | `84b446c0ff513a39c60486edada40a4a1d781b6b` |
| Job | `738e2c7a-df3f-4f83-8578-1a7df00e898b` |
| Judged | 2026-10-19 21:42 UTC |
| Findings | 2 medium · 5 low · 6 info |

Four agents audited the code as it is at `84b446c`, 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: Bonds scale with the author's own share only: an author (or a doer with a minimum-share author) attacks a crowd-funded pot and the WON'T DO pool for cents

`contracts/src/TaskVault.sol:410`

```
r.base = _contributions[taskId][t.author].active;
```

Merged from audit_permissions #6, audit_flow #1 and audit_math #5. The doer bond and the dispute bond are 0.5 IMD + 3% / 1% of the AUTHOR'S OWN share (TaskVault.submitTerms r.base; CaseDesk.submit _doerBond(t.base)), while an accepted submission pays the WHOLE pot (CaseDesk._finalize reads potOf) and flips YoloPredict to WILL DO. The author may submit to their own task (R2). Role scenario: 1. author creates a 5 USDC task; 2. alice tops up 95 USDC (pot 100); 3. outsiders predict WON'T DO 10,000 YOLO; 4. the author submits a junk link to their own task: bond 0.5 IMD + 0.15 USDC (3% of 5 USDC); 5. nobody disputes within 24 h (the only check, the documented one-dispute slot) -> Completed: the author is credited 90 USDC + the bond back (85 USDC of alice's money net) and, as the only party on the WILL DO side, takes the whole 9,000 YOLO WON'T DO pool plus the 500 YOLO deferred part at settle; alice's withdrawContribution reverts NothingToClaim. If disputed, each junk try costs the author 0.5 IMD + 0.15 USDC (about $4.15 at IMD $8) and can be queued again (R2 queue), each try holding the pot ~2-6 days and needing a dispute plus a paid swarm call when no NFT votes. R2/R3 state the base as written and MECHANICS 'Bond' documents it, so this is a documented rule reported because it is economically abusable at the stated numbers: profit up to the contributors' top-ups plus the WON'T DO pool, cost 0.5 IMD + 3% of a 5 USDC share. Smallest fix: base the percent part of the doer bond on the pot at submission (or max(author share, pot/10)), at least when doer == author; a design decision for the requester (R2 would change).

**Reproduction**

contracts/test/scratch/Judge.t.sol test_A_authorSelfSubmitBondBase (Base harness, forge 1.8.3): createTask(USDC, 5e6) by author, topUp(95e6) by alice, dave and erin predict WON'T DO 5,000e18 each; quoteSubmission(t).rew == 150000 (0.15 USDC) and .imd == 0.5e18; author submits; warp 24 h + 1 s; poke: task Completed, credits(usdc, author) == 90150000, alice.withdrawContribution reverts NothingToClaim, YoloPredict.settle pays the author exactly 9,500e18 YOLO. Expected under a pot-based bond: a bond of at least 3 USDC for a 100 USDC pot. The repo's own test BondBase.t.sol test_TopUps_OnlyTheAuthorsCount asserts the current rule, so it is intended; the report asks for a decision, not a bug fix.

### 2. Medium: Dev-wallet cancelTask in Review/Voting/Asked voids a losing YOLO Predict position or a losing bond for a related party at zero cost, and takes the earned reward from an honest doer

`contracts/src/TaskVault.sol:365`

```
function cancelTask(uint256 taskId) external nonReentrant {
```

Merged from audit_permissions #7(a,b), audit_flow #2 and audit_math #2. cancelTask only requires the task to be Open after its timers are played; it is allowed while an honest submission sits in Review, Voting, NoDecision, Decided or Asked. Unwound -> YoloPredict._resolve returns Void and claim pays paidIn in full (burn and deferred parts included), DisputeDesk._settleNeutral returns every bond, the doer gets no reward. Scenario at 100 USDC and a 10,000 YOLO pool: 1. a friend F of the dev wallet predicts WON'T DO 10,000 YOLO; alice predicts WILL DO 1,000; 2. an honest doer submits a real proof, nobody disputes; 3. one second before windowEnd the dev wallet calls cancelTask; 4. F claims 10,000 YOLO back (would have lost all of it), the doer is credited only the 3 USDC bond, alice gets only her stake back. Same for a friend doer about to lose a dispute: a cancel before the answer is recorded returns the bond and the honest disputer's win. Who profits: F, 10,000 YOLO; cost: gas; the dev wallet itself gets nothing (documented). Limits verified: once a swarm answer is recorded or a timer has ended the submission, cancelTask's _advance finalizes first (a delivered Reject stands and the task is then cancelled as an open task; after an acceptance cancel reverts TaskNotOpen). The cancel is a documented power (R7, MECHANICS known limitations) and a trust assumption; it is reported because the documented rule is economically abusable by a related party at the stated numbers. Smallest fix: forbid cancelTask while a submission is being judged (Review or later) unless the Intake is unavailable, or settle YoloPredict on an Unwound task with the 5% burn kept so a cancel cannot rescue a position; or document that predictors bear this operator risk.

**Reproduction**

contracts/test/scratch/Judge.t.sol test_B_devCancelVoidsLosingPrediction: task 100 USDC, dave predicts WON'T DO 10,000e18, alice WILL DO 1,000e18, doer submits at day 1, warp 24 h - 1 s, vm.prank(devWallet) vault.cancelTask(t) succeeds: status Unwound, dave's YoloPredict.claim returns exactly 10,000e18, credits(usdc, doer) == 3,000,000 (bond only), submission outcome Unwound. test_B2_devCancelAfterSwarmAnswerBeforePoke: after _answer(s, false) is recorded, cancelTask plays the Reject first (outcome 2) and then unwinds the open task (status 4): the dev cannot override a delivered answer, but can still void every prediction afterwards.

### 3. Low: Intake not selling the action (priceOf 0 or revert) makes a NoDecision window end as Accepted with the disputer's bond to the doer, and an NFT instant decision unappealable

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

```
if (price == 0) revert ActionNotSold();
```

Merged from audit_permissions #8, audit_flow #3 and audit_math #1. callSwarm (line 264) and appeal (line 216) revert ActionNotSold while the Intake's price is 0 and SwarmUnavailable when the ask fails. Unlike an asked appeal (CaseDesk._askAppeal voids and reopens the window), the NoDecision and Decided windows have no retry: after callFrom + 24 h CaseDesk.step runs _endUncalled (CaseDesk.sol:365), nftDecision None -> Accepted and the disputer's bond is credited to the doer; a 20-NFT instant Accept of a fake has no counter at all. Only the Intake operator (de-listing or repricing the action, or an outage lasting 24 h) can trigger it; a user cannot. Loss at 100 USDC: the pot to a fake doer plus the disputer's 0.5 IMD + 1 USDC. The repo's Disputes.t.sol test_CallSwarm_ActionNotSold_DefaultStands and test_Appeal_ActionNotSold assert this default, so it is intended. Smallest fix: when quoteSwarmFee is 0 during a NoDecision/Decided window, extend the window or end a disputed submission neutrally (bonds back) instead of Accepted.

**Reproduction**

contracts/test/scratch/Judge.t.sol test_C_noDecisionActionNotSold: 100 USDC task, submit, dispute by dave, no votes, poke at vote end -> Phase.NoDecision; intake.setPrice(0); dave's callSwarm reverts ActionNotSold; poke 24 h later -> Outcome.Accepted; credits(usdc, doer) == 94e6 (90 reward + 3 own bond + 1 disputer bond) and credits(imd, doer) == 1e18 (own 0.5 + dave's 0.5).

### 4. Low: Appeal squatting: an appeal filed at the last second and never asked costs its filer nothing and holds a decided submission up to 90 h; longest hostage path about 141 h per queued submission

`contracts/src/desks/CaseDesk.sol:206`

```
if (block.timestamp < uint256(s.appealedAt) + p.swarmTimeout) return false;
```

Merged from audit_permissions #2/#3, audit_flow #6 and audit_math #3/#7. Every phase has a timer exit (Review 24 h, Voting 24 h, NoDecision/Decided 24 h, Appealed 6 h, Asked 6 h + 1 h grace, 3 swarm calls max), so no money is frozen for good. But an appeal() only files and funds; if nobody calls askAppealSwarm within 6 h, step() voids it with a FULL refund of bond and fee (dropCaller) and reopens a 24 h window. A squatter (any wallet, e.g. the favoured side's second wallet) files at window end - 1 s three times and never asks: the decision is held about 90 h beyond the NFT decision at a cost of gas only, and it owns the single appeal slot (an honest appellant's appeal in the same block reverts WrongPhase). Counter available to anyone: askAppealSwarm on the squatted appeal, which spends the squatter's prepaid fee. Longest path per submission from activation: 24 + 24 + 3 x (24 + 6 or 7) = 138-141 h; N junk submissions queued before the deadline multiply it. Documented in MECHANICS known limitations (unasked appeal, queue); reported for the zero cost and the slot race. Smallest fix: keep a small part of a voided appeal's fee, or let the honest side's own appeal replace an unasked one.

**Reproduction**

contracts/test/scratch/Judge.t.sol test_F_appealSquatFree: 100 USDC task, submit, dispute, voter1's 20 NFTs vote accept (instant Decided); erin appeals at +24 h - 1 s, nobody asks, poke at +6 h voids it; repeated 3 times: the submission ends Final/Accepted 89 h after the decision and erin's withdraw() returns exactly her starting IMD and USDC.

### 5. Low: YoloPredict.predict and resolution read the vault unpoked: predictions stay blocked (SubmissionActive) after a submission was already ended by its timer, and status views lie until a poke

`contracts/src/YoloPredict.sol:139`

```
if (task.activeSub != 0) revert SubmissionActive();
```

Merged from audit_permissions #9(2), audit_economics #1 and audit_flow #5. settle() calls vault.poke first, predict() and resolution() do not. A submission rejected by the NFT whose 24 h appeal window has passed unpoked still shows as activeSub, so predict reverts SubmissionActive although R6 reopens predictions, and resolution/getTask().status read Pending/Open after a deadline until anyone pokes. No money is at risk (anyone may poke), but a frontend that trusts the views shows a wrong phase and a user's allowed call reverts. Smallest fix: vault.poke(taskId) at the start of predict (as settle does) and apply timers in the views.

**Reproduction**

contracts/test/scratch/Judge.t.sol test_D_predictStaleAfterTimer: submission disputed, 20 NFTs vote reject (instant Decided), warp 24 h + 1 s without poking: alice's predict(t, true, 1e18) reverts SubmissionActive; after vault.poke(t) the task is Open with no active submission and the same predict succeeds.

### 6. Low: A queued submission's 24 h dispute window starts at whatever call plays the timer, so its doer can pick the hour it opens

`contracts/src/desks/CaseDesk.sol:357`

```
s.windowEnd = uint64(block.timestamp) + _params(_terms[subId].version).disputeWindow;
```

audit_economics #2, reproduced. _activate anchors windowEnd to block.timestamp of the poke that finalized the previous submission, not to the computed end of its last step. The queued doer (or a friend) can wait and poke at a quiet hour; the window is still 24 h and anyone else may poke earlier, so the effect is bounded. Smallest fix: activate at the previous step's computed end time (callFrom + appealWindow, windowEnd or voteEnd) when the poke is late.

**Reproduction**

contracts/test/scratch/Judge.t.sol test_E_queuedWindowStartsAtPoke: sub A active, sub B queued; A rejected by 20 NFTs at T; doer2 pokes at T + 29 h: B is in Review with windowEnd == poke time + 24 h, i.e. later than T + 48 h.

### 7. Low: HiddenList's 3-distinct-owner gate is met by one holder moving NFTs between own wallets mid-round (ownerOf at vote time, no snapshot)

`contracts/src/HiddenList.sol:281`

```
try IERC721(nft).ownerOf(id) returns (address o) {
```

audit_economics #3, verified by code: _mayVote resolves the owner at vote time and hideOwners counts distinct owners per round; nothing remembers an id's owner across transfers. One holder of 10 NFTs votes 8 hide from wallet X, transfers one each to Y and Z and votes them: hideNfts 10 >= QUORUM_NFTS, hideOwners 3 >= MIN_WALLETS. The list hides on the site only (R7), so the impact is cosmetic; the per-round id bitmap already stops the same id voting twice. Smallest fix: none cheap on chain; accept as documented or require the owners' ids to have been held since the round opened.

**Reproduction**

Code reading: HiddenList.sol:256-297 (_countTokens, _mayVote) and _won at line 250. Steps: wallet X owns ids 1..10; X votes hide [1..8]; transfer 9 to Y, Y votes hide [9]; transfer 10 to Z, Z votes hide [10]; after ROUND (24 h) status() reports hidden by vote with a single real owner.

### 8. Info: The dev-set YOLO price alone sets the 4% doer stake bonus: MAX_STEP bounds one call, not a sequence, so a dust lock can earn the full bonus out of the fee

`contracts/src/DevPrices.sol:89`

```
if (next > current * MAX_STEP || next * MAX_STEP < current) revert PriceSanity();
```

Merged from audit_permissions #7(c), audit_economics #4 and audit_math #8. CaseDesk._stakeBps (CaseDesk.sol:527) compares the doer's lock power with yoloValue(currency, base); several setYoloPriceUsd calls in one block move the price anywhere. The bonus is paid out of the protocol fee (treasury/burn share), never out of users' money, so this is a trust assumption of the dev wallet under R7 with no user loss.

**Reproduction**

Code reading: DevPrices.sol:86-89 bounds a single call to 10x; CaseDesk.sol:527 gives 400 bps whenever power >= r. Steps: dev wallet calls setYoloPriceUsd(current * 10) five times in one block; a friend with a 1 YOLO one-week lock submits to a 100 USDC task: Submission.stakeBps == 400; on acceptance 4 USDC of the 10 USDC fee goes to the doer.

### 9. Info: A winning stranger appellant gets bondFixedImd back, not the fee actually spent (Appeal.spent is recorded but never read)

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

```
uint256 strangerFee = p.bondFixedImd > penaltyImd ? penaltyImd : p.bondFixedImd;
```

audit_flow #4, verified against the code and the repo's own test. The refund is min(bondFixedImd, penaltyImd) regardless of a.spent; with an Intake price below 0.5 IMD the stranger gets more than the fee, above it less. Text drift from 'their swarm-call fee back'; the money comes from the loser's bond, so no third party loses. Fix: min(a.spent, bondFixedImd, penaltyImd).

**Reproduction**

contracts/test/Disputes.t.sol test_CallSwarm_LowerPrice_NoFeeRefund already shows it: intake price 0.2 IMD, erin (stranger) calls the swarm and wins, credits(imd, erin) == 0.5e18 carve-out + 0.5e18 own bond although she spent 0.2 IMD.

### 10. Info: A payTo address can hold votes on both sides and hasVoted/sideOf answer false/0 for it although it is a reward recipient

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

```
VoterRec storage pr = book.recs[payTo];
```

audit_math #4, reproduced. No double count exists: the per-dispute id bitmap stops owner+delegate, transfer-after-vote and multi-transaction reuse, a voter wallet keeps one side, and _claimVoter pays a payTo only for the final side. But the payTo record is written without a side, so two voters on opposite sides naming the same payTo give it accept and reject votes at once, and hasVoted(subId, payTo) is false. Frontends that use hasVoted to list voters miss reward recipients. Fix: report payTo rows separately or forbid a payTo already recorded on the other side.

**Reproduction**

contracts/test/scratch/Judge.t.sol test_G_payToBothSides: voter2 (10 ids) votes accept with payTo P, voter3 (5 ids) votes reject with payTo P: votesOf(s, P) == (10, 5) and hasVoted(s, P) == false.

### 11. Info: Only the swarm's yes/no reaches the chain: the attestation's panel size, agreed count and expiry are not logged by the vault

`contracts/src/TaskVault.sol:823`

```
emit SwarmAnswered(requestId, r.subId, answer, t, true);
```

Merged from audit_permissions #10 and audit_math #6. Every user text is reconstructible from events (TaskCreated, Submitted, EvidenceAttached, Disputed, VoteCounted, SwarmAsked/Answered/Silent, AppealVoided) and every transition is permissionless and lazy (poke, askAppealSwarm, settle, payout), so no project keeper, relayer or signer is needed (R8 holds). A third-party frontend reading only vault logs cannot show why the swarm answered as it did: the signed Attestation is in the Intake's calldata, not in a vault event.

**Reproduction**

Filter SwarmAnswered logs: fields are (requestId, subId, answer, answerTime, viaCallback) only; TaskVault.sol:800-825 records the bool and times and emits nothing of the attestation body.

### 12. Info: R5 text drift: a dispute side's bond pays only its first swarm call and only up to the bond's 0.5 IMD; the rest and every later call come from the wallet

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

```
if ((sidesDoer ? sp.doer : sp.disputer) == 0) {
```

audit_flow #7, verified in _fundCall (DisputeDesk.sol:296-312): fromBond = min(fee, bondImd) only while the side's sideSpent is 0; a fee above 0.5 IMD and any repeat call after silence are wallet-paid, capped by maxImdIn. MECHANICS documents it; R5's 'pays the fee from its bond' is a simplification. No view returns a side's exact maxImdIn (quoteSwarmFee + sideSpent + getSubmission().bond.imd must be combined); a wrong guess reverts CostAboveMax at no cost.

**Reproduction**

Code reading of DisputeDesk._fundCall and _charge; existing Base harness: with PRICE 1e18 a disputer's callSwarm takes 0.5e18 from the bond and 0.5e18 from the wallet, a second call after silence takes 1e18 from the wallet.

### 13. Info: Verdict and coverage: no critical or high finding; role walk and double-vote checks clean; full test tree not compiled on this machine

`contracts/src/TaskVault.sol:67`

```
contract TaskVault {
```

VERDICT: no blocker for launch found in the protocol logic. Decisions for the requester before mainnet: the author-share bond base (medium) and the dev cancel during a check (medium). Role walk: every entry point of author, contributor, doer, queued doer, disputer, voter/delegate, appellant, outside caller, predictor, staked doer, dev wallet and params owner was traced to its phase guard; no call outside its rule and no rule without a path. Double vote: per-dispute id bitmap before the ownership check, side fixed per wallet, payTo paid on one side only (DisputeDesk.vote 136-191). Collusion vs predictors: the cheapest plan is author = doer on a task only the author can do (documented not-a-finding; a fake variant is stoppable by one dispute or one askAppealSwarm). Races: same-block dispute/vote/appeal/callSwarm resolve by order with the loser reverting and no money moved; a late poke never changes a recorded answer. Read in full: TaskVault, desks/CaseDesk, desks/DisputeDesk, desks/Desk, YoloPredict, VotePower; in part: HiddenList, DevPrices, VaultTypes, Base.t.sol, docs/MECHANICS known limitations. Not read: BuybackBurner, DareTrophy, TrophyCatalog, ProtocolDirectory, adapters, token, script. Build: sources plus the Base harness compiled (solc 0.8.28, via-ir, warnings only). Tests: the Base harness reverts WiringClosed in setUp under forge 1.8.3 because the desks' transient wiring flag does not survive separate top-level calls in this forge; a scratch Base variant that deploys desks and vault in one call (Launcher) was used and 8 probe tests passed (Judge.t.sol). The repository's own suites were not run here for that reason; the specialists report 52 audit tests and 120 root-suite tests passing on their machines.

**Reproduction**

Not applicable: coverage statement. Probe suite: contracts/test/scratch/Judge.t.sol with contracts/test/scratch/Base2.t.sol, `FOUNDRY_TEST=contracts/test/scratch forge test --match-path contracts/test/scratch/Judge.t.sol`: 8 passed, 0 failed.

---

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