{"assessments":[],"deployments":[],"fuzz":[],"identity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"interpretation":"Records acceptance and evidence. Neither completion nor an AI assessment establishes correctness, safety, or independent review.","jobId":"20f559d5-78bb-4ec6-91f9-54b724d1ecc5","kind":"audit","nodes":[{"acceptedSubmissionHash":"2fdbc9313d9e0010c6bcda86d27f6f2ca2fc293e227f6cfd8590f3316a650444","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_economics","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"44c2fbe180e9c78e5d8a50491325611e0d7809f9512c5812c290d09793c0c463","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_flow","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"1b3fc3c792cf93a9018bc575868f1bce8f8b33b388da9b7d437dc812820e96cd","dependsOn":["audit_math","audit_permissions","audit_economics","audit_flow"],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","tools":[]},"key":"audit_judge","kind":"code","role":"review","skillHash":"af3aa01159bbf354f621cafa5c0006f6169e0938a8b015be508b7377dbf165bc","skillId":"audit-judge","state":"accepted"},{"acceptedSubmissionHash":"43fd8463b5f431faefa97ac0b57355da9b3ca31c85cc0321cceb38dd714b905a","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_math","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"},{"acceptedSubmissionHash":"958d013453d7bb95ee28af8ed74aa802c96310cd483078ce87eb00e1ca28b81d","dependsOn":[],"execution":{"network":false,"profile":"foundry","requires":[],"skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","tools":[]},"key":"audit_permissions","kind":"code","role":"review","skillHash":"e5ac2cb1fd91a56aa40b16487fc230c48de0d317c8266140331dd3219bb40a85","skillId":"audit-specialist","state":"accepted"}],"objective":"Audit every payout and the token economics of moneycanbuy.fun 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/.\n\nSOURCE 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.\n\nSCOPE. 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.\n\nRULES. 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.\n- 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.\n- No stake: doer 90%, treasury 10% (5% if the NFT 5% was earned).\n- 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.\n- 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.\n- Burn: YOLO to the BuybackBurner, then burnYolo to 0x...dEaD; ETH, USDC, IMD to the buyback wallet at payout.\n- 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.\n- 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.\n- NFT undecided and no swarm call: accepted, the disputer's bond 100% to the doer.\n- Rejected: R stays, open for the next submission.\n- 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 if IMD is left in it. 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.\n- 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.\n- Cancel only by the dev wallet, any open phase: all back to who put it in, predictions 100%; swarm fees paid stay gone.\n- 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.\n- Trophy: 7,777 YOLO, burned.\n- 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.\n\nPrices (no network): IMD $4.50, ETH $2,398, YOLO $0.001 (parameter), swarm call 0.5 IMD = $2.25, gas 0.12 gwei; in code every price is a dev-wallet parameter, USDC = $1.\n\nKNOWN 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.\n\nGOAL. 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.\n\nREPORT 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.\n\n1. ACCEPTED, NO DISPUTE. DisputeDesk._payReward: the $100 examples in USDC, ETH, IMD, YOLO; b = 0.\n2. DISPUTE, NFT RIGHT. Accepted and rejected: loser's bond 50/50, winner's bond back, voter shares.\n3. NFT WRONG OR UNDECIDED. Swarm overturns or decides; undecided, no call: unearned NFT 5%, 100% of the loser's bond.\n4. REJECTED. R after a fake, the next submission's bond and payout, claimVoidSubmission.\n5. 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.\n6. BONDS. Base A, rounding at the minimum rewards, who keeps dust.\n7. STAKE BONUS. CaseDesk._stakeBps, YoloLockHub, _lockRuns: base A not R, floor to bps, add/extend after submission, lock ended before payout.\n8. YOLO ADD-ON. YoloSweetener: same split as R, voters only after a right NFT decision, refunds.\n9. EXPIRY AND CANCEL. withdrawContribution, cancelTask: each funder their part.\n10. YOLOPREDICT. settle, claim, _split, _points: claims + burned + deferred = paid in, empty sides, a winning side with zero points, void.\n11. BURNS. Every burn path to its end; monthly burn vs unlock at 200 tasks/month, R $100.\n12. TOKENOMICS VS CODE. token/, LaunchPlan: split = 1e27, dust, unlock table, both taxes at minutes 0, 15, 30, 45, 60, tax recipient.\n13. 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.\n14. DOCS VS CODE. Every amount in the three docs, one line each.\n15. COVERAGE. Files read in full, what was not reached, forge build and test result.\n\nNot 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.\n","parentJobId":null,"planHash":"3d1938d7417cbf4379cbcc85c5689c803676d3c123a58a68fa6aee082943f995","previousHash":"0000000000000000000000000000000000000000000000000000000000000000","projectId":"20f559d5-78bb-4ec6-91f9-54b724d1ecc5","publication":{"commit":null,"deliveredAt":null,"repoUrl":null},"receiptIdentity":{"adapter":"0xde152afb7db5373f34876e1499fbd893a82dd336","chainId":1,"collection":"0x0000ec93127baa929e58e97dd0095a2bfb38ec1d","registry":"0x8004a169fb4a3325136eb29fa0ceb6d2e539a432"},"registry":"0xb6d0a187b050fa5bb0b87033a203f37becf4a775","research":[],"schema":"identitymd-work-v1","signals":[{"agentId":"52257","feedbackHash":"3c8b0e918325149355ac087bb746332ee103ca3b21743cf19612beaf6563b82e","nodeKey":"audit_economics","submissionHash":"2fdbc9313d9e0010c6bcda86d27f6f2ca2fc293e227f6cfd8590f3316a650444","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"52258","feedbackHash":"ec14e3b0e214f6f2c7a9f2c67680b32f9745482df3b2daa0262632cedfedb75b","nodeKey":"audit_flow","submissionHash":"44c2fbe180e9c78e5d8a50491325611e0d7809f9512c5812c290d09793c0c463","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"54231","feedbackHash":"6ec2d75566e6cf439f1bcd03248e19a9b89321fc029f6fb2f7d7f1b2e62281a1","nodeKey":"audit_judge","submissionHash":"1b3fc3c792cf93a9018bc575868f1bce8f8b33b388da9b7d437dc812820e96cd","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51018","feedbackHash":"fda5cb1af7c0cbf31583bcddc82b13052472931d9f348eca11c172bd38931343","nodeKey":"audit_math","submissionHash":"43fd8463b5f431faefa97ac0b57355da9b3ca31c85cc0321cceb38dd714b905a","tag1":"review:submission","tag2":"acceptance-v2","value":1},{"agentId":"51203","feedbackHash":"35357027b7e6d7243eedd419c46f135a401314ec8617498034df3bf947122e0a","nodeKey":"audit_permissions","submissionHash":"958d013453d7bb95ee28af8ed74aa802c96310cd483078ce87eb00e1ca28b81d","tag1":"review:submission","tag2":"acceptance-v2","value":1}],"site":null,"snapshotHash":"6179866715ceae20391fea3b1e6337338f0c45f6f851371f95f8585d13df1582","state":"completed","submissions":[{"artifacts":[],"attempt":1,"bundleHash":null,"device":"a0f92fec784312b7","findings":[{"citation":"resolved","description":"Merged from audit_math (medium) and audit_economics (medium). vote() checks only the phase, the side, payTo and NFT ownership: nothing excludes the submission's disputer (or its payTo) from voting, and nothing stops it voting ACCEPT against its own dispute. dispute() excludes only the doer. With the launch parameters (VaultParams.sol LaunchParams.initial: instantDelta 20, endDelta 10, minWinnerWallets 1) one wallet holding 10 identity.md NFTs disputes an honest submission (bond 0.5 IMD + 1% of A), votes ACCEPT with its 10 ids, and is the only 'right' voter at the vote end (CaseDesk._tryDecide -> _decide with endDelta). settle() then sets nftRight, _payReward books nftFeeBps = 5% of R into voterFee, and _settleDecided books 50% of the loser's bond (the wallet's own) into voterBond; _claimVoter pays all of it to the same wallet (100% of the accept votes). Net per task in R's currency: +5% R - 1% A + 0.5% A = +4.5% of R when R = A, against -0.25 IMD ($1.13 at $4.50) and cents of gas at 0.12 gwei. At R = 10,000 USDC: +450 USDC net; at R = $100: +$3.38. Loser: the treasury (500 USDC instead of 1,000 on the $10k task: the whole NFT 5% leaves it). The doer gains 0.5% A + 0.25 IMD and a 48 h delay. docs/MECHANICS.md 'Known limitations' lists this with '20+ NFTs', 'from a second wallet' and 'about $3 on a $100 task'; the code needs 10 NFTs, no second wallet, and the gain is unbounded and linear in R, repeatable on every large task by any 10-NFT holder, so the documented rule is economically abusable. Smallest fix: in vote(), revert when the sender or payTo is the submission's disputer (d.disputer); or in settle(), when the disputer lost, route recs[disputer]'s votes out of voterVotesLeft / voterFee / voterBond to the treasury. Then also fix the doc line (10 NFTs, gain linear in R).","line":136,"path":"contracts/src/desks/DisputeDesk.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.28;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\nimport {ERC721} from \"@openzeppelin/contracts/token/ERC721/ERC721.sol\";\n\nimport {TaskVault} from \"src/TaskVault.sol\";\nimport {ITaskVault} from \"src/interfaces/ITaskVault.sol\";\nimport {ParamsRegistry} from \"src/ParamsRegistry.sol\";\nimport {CaseDesk} from \"src/desks/CaseDesk.sol\";\nimport {DisputeDesk} from \"src/desks/DisputeDesk.sol\";\nimport {TextRules} from \"src/helpers/TextRules.sol\";\nimport {SwarmQuestionBuilder} from \"src/helpers/SwarmQuestion.sol\";\nimport {AttestationChecker} from \"src/helpers/AttestationChecker.sol\";\nimport {VotePower} from \"src/VotePower.sol\";\nimport {YoloLockHub} from \"src/YoloLockHub.sol\";\nimport {BuybackBurner} from \"src/BuybackBurner.sol\";\nimport {DevPrices} from \"src/DevPrices.sol\";\nimport {VoteLib} from \"src/libraries/VoteLib.sol\";\nimport \"src/VaultTypes.sol\";\n\ncontract Coin is ERC20 {\n    uint8 private immutable _d;\n\n    constructor(string memory n, uint8 d) ERC20(n, n) {\n        _d = d;\n    }\n\n    function decimals() public view override returns (uint8) {\n        return _d;\n    }\n\n    function mint(address to, uint256 a) external {\n        _mint(to, a);\n    }\n}\n\ncontract Nft is ERC721 {\n    constructor() ERC721(\"identity.md\", \"IMDNFT\") {}\n\n    function mint(address to, uint256 id) external {\n        _mint(to, id);\n    }\n}\n\n/// @dev The core launch in one frame: the desks accept `wire` only inside the transaction that deployed them.\ncontract Core {\n    YoloLockHub public hub;\n    address public vault;\n\n    constructor(address registry, address imd, address usdc, address yolo, address prices, address buyback, address nft) {\n        TextRules text = new TextRules();\n        hub = new YoloLockHub(yolo);\n        BuybackBurner burner = new BuybackBurner(yolo, imd, usdc, prices, buyback);\n        CaseDesk caseDesk =\n            new CaseDesk(registry, imd, usdc, address(text), address(new SwarmQuestionBuilder()), yolo, address(hub));\n        VotePower vp = new VotePower(nft, address(0));\n        DisputeDesk disputeDesk = new DisputeDesk(registry, imd, usdc, address(text), address(vp), yolo, address(hub));\n        vault = address(\n            new TaskVault(\n                registry,\n                imd,\n                usdc,\n                yolo,\n                address(new AttestationChecker(registry)),\n                address(text),\n                address(caseDesk),\n                address(disputeDesk),\n                address(burner)\n            )\n        );\n    }\n}\n\n/// @notice A holder of endDelta (10) NFTs disputes an honest submission itself (DisputeDesk.dispute only excludes the\n///         doer), votes ACCEPT against its own dispute with its 10 NFTs (DisputeDesk.vote does not exclude the\n///         disputer), and is the only \"right\" voter at the vote end. settle() then books the NFT 5% of R\n///         (voterFee) and 50% of the loser's bond (its own bond) to the accept votes, all of which the same wallet\n///         claims. On a 10,000 USDC task: +500 USDC fee + 50 USDC of its own bond back - 100 USDC bond = +450 USDC\n///         net (and -0.25 IMD), paid by the treasury, which gets 500 instead of 1,000 USDC.\n///         Fails now (the disputer leaves with more USDC than it came with); passes once the disputer's own votes\n///         cannot earn the voter share (revert in vote(), or route their share to the treasury).\ncontract SelfDisputeVoterFeeTest is Test {\n    Coin imd;\n    Coin usdc;\n    Coin yolo;\n    Nft nft;\n    ITaskVault vault;\n    DevPrices prices;\n\n    address author = makeAddr(\"author\");\n    address doer = makeAddr(\"doer\");\n    address whale = makeAddr(\"whale\");\n    address treasury = makeAddr(\"treasury\");\n    address devWallet = makeAddr(\"devWallet\");\n    address buyback = makeAddr(\"buyback\");\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        imd = new Coin(\"IMD\", 18);\n        usdc = new Coin(\"USDC\", 6);\n        yolo = new Coin(\"YOLO\", 18);\n        nft = new Nft();\n        prices = new DevPrices(devWallet, address(usdc), address(imd), address(yolo), 0.001e18, 2398e18, 4.5e18);\n        address intake = makeAddr(\"intake\");\n        ParamsRegistry registry = new ParamsRegistry(\n            makeAddr(\"multisig\"), intake, address(prices), address(0), address(nft), address(0), treasury, makeAddr(\"attester\")\n        );\n        Core core = new Core(address(registry), address(imd), address(usdc), address(yolo), address(prices), buyback, address(nft));\n        vault = ITaskVault(core.vault());\n        address[3] memory people = [author, doer, whale];\n        for (uint256 i; i < 3; ++i) {\n            imd.mint(people[i], 100e18);\n            usdc.mint(people[i], 10_000e6);\n            vm.startPrank(people[i]);\n            imd.approve(address(vault), type(uint256).max);\n            usdc.approve(address(vault), type(uint256).max);\n            vm.stopPrank();\n        }\n        // the launch rule: endDelta = 10 NFTs decide at the vote end, one wallet is enough (minWinnerWallets = 1)\n        for (uint256 id; id < 10; ++id) {\n            nft.mint(whale, id);\n        }\n    }\n\n    function test_DisputerVotesForTheDoerAndTakesTheNftFee() public {\n        ImdPay memory noSwap;\n        vm.prank(author);\n        uint256 t = vault.createTask(\n            address(usdc),\n            10_000e6,\n            uint64(vm.getBlockTimestamp() + 30 days),\n            \"Title\",\n            TaskRules(\"do the thing\", \"PROOF: one https link\", \"NO if: unclear\"),\n            0,\n            PROOF_LINK,\n            \"\",\n            noSwap\n        );\n        vm.prank(doer);\n        uint256 s = vault.submit(t, \"https://x.com/doer/status/1\", Evidence(\"\", \"\"), 0, type(uint256).max, noSwap);\n\n        uint256 whaleUsdcBefore = usdc.balanceOf(whale);\n        uint256 whaleImdBefore = imd.balanceOf(whale);\n\n        // the NFT holder disputes the honest submission itself: bond 0.5 IMD + 1% of A = 100 USDC\n        vm.prank(whale);\n        vault.dispute(s, \"ipfs://reason\", \"\", noSwap);\n\n        // ... and votes ACCEPT against its own dispute with its 10 NFTs (10 < instantDelta 20: no instant decision)\n        uint256[] memory ids = new uint256[](10);\n        for (uint256 i; i < 10; ++i) {\n            ids[i] = i;\n        }\n        vm.prank(whale);\n        try vault.vote(s, VoteLib.SIDE_ACCEPT, whale, ids) {} catch {}\n\n        // vote end: 10 >= endDelta -> Decided(Accept); appeal window: nobody appeals -> Final, Accepted, NFT right\n        vm.warp(vm.getBlockTimestamp() + 24 hours + 1);\n        vault.poke(t);\n        vm.warp(vm.getBlockTimestamp() + 24 hours + 1);\n        vault.poke(t);\n        assertEq(uint256(vault.getSubmission(s).outcome), uint256(Outcome.Accepted), \"accepted\");\n\n        // the whale takes everything the \"right\" NFT votes earned: the fee and half of its own bond\n        try vault.claimVoterReward(s, whale) {} catch {}\n        try vault.claimDisputer(s) {} catch {}\n        try vault.withdraw(address(usdc)) {} catch {}\n        vm.startPrank(whale);\n        try vault.withdraw(address(usdc)) {} catch {}\n        try vault.withdraw(address(imd)) {} catch {}\n        vm.stopPrank();\n\n        uint256 whaleUsdcAfter = usdc.balanceOf(whale);\n        // RULES: a losing disputer forfeits its bond; the NFT 5% is for voters who decided right, not for the\n        // party that lost the dispute. Now: whale +450 USDC (500 fee + 50 of its own bond - 100 bond).\n        assertLe(whaleUsdcAfter, whaleUsdcBefore, \"the losing disputer must not leave with more USDC than it came with\");\n        // the treasury keeps the 10% nobody earned (no stake): 1,000 USDC, not 500\n        assertGe(vault.treasuryPot(address(usdc)), 1_000e6, \"treasury keeps the unearned NFT 5%\");\n        assertLe(imd.balanceOf(whale), whaleImdBefore, \"no IMD gain either\");\n    }\n}","reproduction":"Launch prices, 10 NFTs (ids 0..9) in one wallet 'whale'. author createTask(USDC, 10,000e6). doer submit -> s. whale dispute(s, 'ipfs://reason', '') paying 0.5e18 IMD + 100e6 USDC. whale vote(s, SIDE_ACCEPT, whale, [0..9]): phase stays Voting (10 < instantDelta 20). warp +24h+1, poke -> Decided(Accept) (10 >= endDelta, 1 >= minWinnerWallets). warp +24h+1, poke -> Final, Accepted, nftRight. claimVoterReward(s, whale) + withdraw(USDC): whale USDC 10,000e6 -> 10,450e6 (500e6 voterFee + 50e6 voterBond.rew - 100e6 bond), IMD -0.25e18; treasuryPot(USDC) = 500e6 instead of 1,000e6. Expected (RULES: a losing disputer forfeits its bond; the NFT 5% is for voters who decided right, not the party that lost): whale USDC after <= before. Ran contracts/test/scratch/SelfDisputeVoterFee.t.sol: FAIL 'the losing disputer must not leave with more USDC than it came with: 10450000000 > 10000000000'.","severity":"medium","snippet":"    function vote(uint256 subId, uint8 side, address payTo, uint256[] calldata tokenIds) external routed {","title":"Self-dispute by a 10-NFT holder takes the NFT 5% of R and half of its own bond: +4.5% of R per task from the treasury, linear in R"},{"citation":"resolved","description":"Merged from audit_permissions (medium, with proof) and audit_economics (medium). CaseDesk._stakeBps compares the doer's lock power with DevPrices.yoloValue(t.currency, t.base), where t.base is only the author's own contribution (TaskVault.submitTerms), and CaseDesk.submit sizes the doer bond with _doerBond(t.base, p) (line 113) on the same base. DisputeDesk._payReward then pays bonus = f.reward * bps / BPS on the whole pot (others' top-ups included, also those made during the review), and YoloSweetener.payout pays the same bps on the whole YOLO add-on. The code matches the RULES (b on A, paid on R and the add-on; bonds 3% / 1% of A), so this is the rule itself being abusable: on every crowd-funded task the author keeps its own share at the per-currency minimum (5 USDC / 0.002 ETH / 1 IMD / 1,000 YOLO) and the rest comes from a second wallet or Sweeten; a doer with a lock worth only A then takes the full 4% of R and of the add-on, and a fake submission on such a pot risks only 0.5 IMD + 3% of A. Who profits: the doer (or the author-doer pair) by 4% of R; who loses: the treasury, whose stakers' half falls from 5% of R to 0.5% of R (on a YOLO reward also the burn). At R = 1,000 USDC with A = 5 USDC: lock 5,000 YOLO ($5 at the $0.001 parameter, about $0.39 at the pool's opening price) for 52 weeks -> doer 940 USDC instead of 900, treasury 5 USDC instead of 50, burn 55 USDC; an honest measure against the 1,000 USDC the bonus is paid on gives 2 bps = 0.20 USDC. With an add-on: a 1,000 YOLO ($1) task sweetened with 10,000,000 YOLO ($10,000) pays a doer with 1,000 YOLO locked 400,000 YOLO ($400) of the add-on's fee. A fake on the 1,005 USDC pot risks $2.40 (0.15 USDC + 0.5 IMD) for a 904.5 USDC payout if nobody disputes in 24 h, and a disputer's upside is at most that $2.40. Cost of the attack: the lock (returned after its term) and one top-up transaction; the doer need not collude with the author. Smallest fix: measure the power (and size the bonds) against the pot plus the add-on at submission (t.pot + t.addon converted) or cap the bonus at 4% x min(1, power / (R + add-on) in YOLO) at payout, and update MECHANICS 'Doer stake condition', TOKENOMICS and YOLO.md accordingly; this is a change to a RULES amount, so it needs the requester's decision.","line":527,"path":"contracts/src/desks/CaseDesk.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.28;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\nimport {TaskVault} from \"src/TaskVault.sol\";\nimport {ITaskVault} from \"src/interfaces/ITaskVault.sol\";\nimport {ParamsRegistry} from \"src/ParamsRegistry.sol\";\nimport {CaseDesk} from \"src/desks/CaseDesk.sol\";\nimport {DisputeDesk} from \"src/desks/DisputeDesk.sol\";\nimport {TextRules} from \"src/helpers/TextRules.sol\";\nimport {SwarmQuestionBuilder} from \"src/helpers/SwarmQuestion.sol\";\nimport {AttestationChecker} from \"src/helpers/AttestationChecker.sol\";\nimport {VotePower} from \"src/VotePower.sol\";\nimport {YoloLockHub} from \"src/YoloLockHub.sol\";\nimport {BuybackBurner} from \"src/BuybackBurner.sol\";\nimport {DevPrices} from \"src/DevPrices.sol\";\nimport \"src/VaultTypes.sol\";\n\ncontract Coin is ERC20 {\n    uint8 private immutable _d;\n\n    constructor(string memory n, uint8 d) ERC20(n, n) {\n        _d = d;\n    }\n\n    function decimals() public view override returns (uint8) {\n        return _d;\n    }\n\n    function mint(address to, uint256 a) external {\n        _mint(to, a);\n    }\n}\n\n\n/// @dev The core launch in one frame: the desks accept `wire` only inside the transaction that deployed them.\ncontract Core {\n    YoloLockHub public hub;\n    address public vault;\n\n    constructor(address registry, address imd, address usdc, address yolo, address prices, address buyback) {\n        TextRules text = new TextRules();\n        hub = new YoloLockHub(yolo);\n        BuybackBurner burner = new BuybackBurner(yolo, imd, usdc, prices, buyback);\n        CaseDesk caseDesk =\n            new CaseDesk(registry, imd, usdc, address(text), address(new SwarmQuestionBuilder()), yolo, address(hub));\n        VotePower vp = new VotePower(address(0xabcd), address(0));\n        DisputeDesk disputeDesk = new DisputeDesk(registry, imd, usdc, address(text), address(vp), yolo, address(hub));\n        vault = address(\n            new TaskVault(\n                registry,\n                imd,\n                usdc,\n                yolo,\n                address(new AttestationChecker(registry)),\n                address(text),\n                address(caseDesk),\n                address(disputeDesk),\n                address(burner)\n            )\n        );\n    }\n}\n\n/// @notice Stake bonus leverage: the power is measured against the author's own share A only (CaseDesk._stakeBps),\n///         but the bonus b is paid on the whole pot R (DisputeDesk._payReward). A task funded 5 USDC by the author\n///         and 995 USDC by a second wallet lets a doer with 5,000 YOLO ($5 at the $0.001 parameter) locked take\n///         4% of 1,000 USDC = 40 USDC out of the stakers' 5% (the treasury drops from 50 to 5 USDC).\n///         Expected (power against the reward the bonus is paid on): b = 4% x 5,000 / 1,000,000 = 0.02% -> 2 bps,\n///         0.20 USDC. Fails now (doer bonus 40 USDC), passes once the share is measured against R.\ncontract StakeBonusLeverageTest is Test {\n    Coin imd;\n    Coin usdc;\n    Coin yolo;\n    ITaskVault vault;\n    YoloLockHub hub;\n    DevPrices prices;\n\n    address author = makeAddr(\"author\");\n    address topper = makeAddr(\"topper\");\n    address doer = makeAddr(\"doer\");\n    address treasury = makeAddr(\"treasury\");\n    address devWallet = makeAddr(\"devWallet\");\n    address buyback = makeAddr(\"buyback\");\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        imd = new Coin(\"IMD\", 18);\n        usdc = new Coin(\"USDC\", 6);\n        yolo = new Coin(\"YOLO\", 18);\n        // launch values: YOLO $0.001, ETH $2,398, IMD $4.50\n        prices = new DevPrices(devWallet, address(usdc), address(imd), address(yolo), 0.001e18, 2398e18, 4.5e18);\n        address nft = makeAddr(\"nft\");\n        address intake = makeAddr(\"intake\");\n        ParamsRegistry registry = new ParamsRegistry(\n            makeAddr(\"multisig\"), intake, address(prices), address(0), nft, address(0), treasury, makeAddr(\"attester\")\n        );\n        Core core = new Core(address(registry), address(imd), address(usdc), address(yolo), address(prices), buyback);\n        hub = core.hub();\n        vault = ITaskVault(core.vault());\n        address[3] memory people = [author, topper, doer];\n        for (uint256 i; i < 3; ++i) {\n            imd.mint(people[i], 100e18);\n            usdc.mint(people[i], 10_000e6);\n            yolo.mint(people[i], 1_000_000e18);\n            vm.startPrank(people[i]);\n            imd.approve(address(vault), type(uint256).max);\n            usdc.approve(address(vault), type(uint256).max);\n            yolo.approve(address(hub), type(uint256).max);\n            vm.stopPrank();\n        }\n    }\n\n    function test_StakeBonusPaidOnOthersTopUps_WithPowerWorthOnlyTheAuthorsShare() public {\n        // the doer locks 5,000 YOLO ($5) for 52 weeks: power = 5,000 YOLO\n        vm.prank(doer);\n        hub.lock(5_000e18, 52);\n        vm.warp(vm.getBlockTimestamp() + 1);\n\n        // the author funds the minimum 5 USDC (A = 5 USDC = 5,000 YOLO at $0.001); a second wallet tops up 995 USDC\n        ImdPay memory noSwap;\n        vm.prank(author);\n        uint256 t = vault.createTask(\n            address(usdc),\n            5e6,\n            uint64(vm.getBlockTimestamp() + 30 days),\n            \"Title\",\n            TaskRules(\"do the thing\", \"PROOF: one https link\", \"NO if: unclear\"),\n            0,\n            PROOF_LINK,\n            \"\",\n            noSwap\n        );\n        vm.prank(topper);\n        vault.topUp(t, 995e6, noSwap);\n        assertEq(vault.getTask(t).pot, 1_000e6, \"R = 1,000 USDC\");\n\n        vm.prank(doer);\n        uint256 s = vault.submit(t, \"https://x.com/doer/status/1\", Evidence(\"\", \"\"), 0, type(uint256).max, noSwap);\n        assertEq(vault.getSubmission(s).stakeBps, 400, \"full 4% share from a $5 lock on a $1,000 reward\");\n\n        // nobody disputes: accepted after 24 h\n        vm.warp(vm.getBlockTimestamp() + 24 hours);\n        vault.poke(t);\n\n        uint256 doerBondRew = 5e6 * 300 / 10_000; // 3% of A = 0.15 USDC, back to the doer\n        uint256 doerGot = vault.credits(address(usdc), doer) - doerBondRew;\n        uint256 bonus = doerGot - 900e6;\n        // the power is worth 5,000 YOLO; the reward the bonus is paid on is 1,000 USDC = 1,000,000 YOLO:\n        // 4% x 5,000 / 1,000,000 = 2 bps of R = 0.20 USDC. Now: 40 USDC (treasury 5 USDC instead of 50).\n        assertLe(bonus, 0.2e6, \"bonus must be proportional to power / the reward it is paid on\");\n        assertGe(vault.treasuryPot(address(usdc)), 5e6 + 24.9e6, \"treasury keeps its half of (5% - b)\");\n    }\n}","reproduction":"Launch prices (YOLO $0.001). doer: hub.lock(5_000e18, 52); warp +1 s. author: createTask(USDC, 5e6, ...). topper: topUp(t, 995e6) -> pot 1,000e6. doer: submit -> getSubmission(s).stakeBps == 400. warp +24h, poke: credits(USDC, doer) = 900e6 + 40e6 bonus + 0.15e6 bond; treasuryPot(USDC) = 5e6; buyback wallet 55e6. Expected with the power measured against the 1,000 USDC the bonus is paid on: stakeBps 2, bonus 0.20 USDC, treasury 29.9 USDC. Ran the specialist's proof contracts/test/scratch/Proof_93b5a2f4713c.t.sol: FAIL 'bonus must be proportional to power / the reward it is paid on: 40000000 > 200000'. quoteSubmission(t).rew == 150000 (0.15 USDC) on the 1,000 USDC pot follows from _doerBond(t.base, p) with t.base = 5e6.","severity":"medium","snippet":"        return (uint16(power >= r ? MAX_STAKE_BONUS_BPS : MAX_STAKE_BONUS_BPS * power / r), true);","title":"Stake bonus and bonds are sized on the author's share A while the bonus is paid on the whole pot R and the add-on: a $5 lock takes 4% of any crowd-funded reward"},{"citation":"resolved","description":"From audit_math (low). LaunchParams.initial (contracts/src/VaultParams.sol:131-133) sets instantDelta = 20, endDelta = 10 and minWinnerWallets = 1: a wallet voting alone with 10 NFTs decides at the vote end (CaseDesk._tryDecide). The doc states 20+ NFTs, a second wallet and 'about $3 on a $100 task'; the code needs 10 NFTs, no second wallet (DisputeDesk.dispute excludes only the doer, vote excludes nobody) and the yield is 5% of R - 0.5% of A - 0.25 IMD on every task ($450 on a $10,000 task). Fix the text or the rule (see the medium finding on DisputeDesk.vote).","line":341,"path":"docs/MECHANICS.md","reproduction":"Same flow as the self-dispute proof: after dispute + vote(ACCEPT, 10 ids) the phase is Voting; after warp 24h+1 and poke it is Decided(Accept) with 10 votes from the disputer's own wallet. Expected per the doc: 10 NFTs cannot decide alone and a second wallet is needed. Actual: they decide and the disputer votes itself (contracts/test/scratch/SelfDisputeVoterFee.t.sol reaches Outcome.Accepted with nftRight).","severity":"low","snippet":"- A holder of 20+ NFTs can dispute an honest submission from a second wallet and vote alone to take the NFT 5% of the","title":"Docs vs code: the known limitation says 20+ NFTs from a second wallet decide a self-dispute; the vote-end rule needs 10 (endDelta), the disputer itself may vote, and the gain is linear in R"},{"citation":"resolved","description":"From audit_flow (low). RULES: a winning outsider first gets 0.5 IMD off the loser's bond 'if IMD is left in it'. _settleDecided computes penaltyImd from the fixed IMD parts only (f.bond.imd / d.bondTotal.imd / a.bond.imd, lines 640-644) and the stranger fee from that (line 649). On a task whose reward currency is IMD the loser's bond also holds an IMD 'rew' part (1% or 3% of A, the same token, collected in the same collectImd call) which sits in penaltyRew and is never considered for the refund. When the loser spent its fixed 0.5 IMD on its own silent swarm call (sideSpent), penaltyImd is 0, the branch is skipped and the outsider's 0.5 IMD fee is not refunded although IMD is still left in the loser's bond; that IMD goes 50/50 (or 100%) to the winning side instead. Bounded: 0.5 IMD (about $2.25) per case, needs an IMD-reward task plus a silent swarm. Smallest fix: when f.currency == address(imd), take what is left of the stranger fee (bondFixedImd - strangerFee) from penaltyRew before _splitCurrency.","line":648,"path":"contracts/src/desks/DisputeDesk.sol","reproduction":"Task reward 100 IMD (author alone, A = 100 IMD); doer submits (bond 0.5 IMD fixed + 3 IMD rew); disputer D disputes (0.5 IMD + 1 IMD rew); NFT vote ends undecided (NoDecision). D calls the swarm against the doer: _fundCall takes the 0.5 IMD fee from D's fixed bond (fromBond = 0.5e18), _charge sets sideSpent.disputer = 0.5e18. Swarm silent (7 h) -> NoDecision again. Outsider O calls the swarm siding with the doer (bond 0.5 IMD + 1 IMD rew, fee 0.5 IMD from its wallet); swarm answers accept -> settle(Accepted). Line 644: penaltyImd = 0.5e18 - 0.5e18 = 0, so line 648 is skipped: O gets no 0.5 IMD refund although 1 IMD (D's rew part) is in penaltyRew and is split pro rata (O 0.25 IMD, doer 0.75 IMD). Expected (RULES): O first gets 0.5 IMD off that 1 IMD, then 0.5 IMD is split (O 0.125, doer 0.375). Traced in code; no Foundry test written (low).","severity":"low","snippet":"        if (appellantIsStranger && callerWins && penaltyImd != 0) {","title":"Winning outsider's 0.5 IMD refund ignores the IMD 'rew' part of the loser's bond on IMD-reward tasks"},{"citation":"resolved","description":"From audit_economics (low). The stake bonus compares the lock power with A converted at DevPrices.yoloPriceUsd. LaunchPlan opens the YOLO/IMD pool at YOLO_OPENING_MCAP_IMD = 15,000 IMD for 1e27 YOLO (line 89), i.e. 1.5e-5 IMD per YOLO, about $0.0000787 at IMD_USD_START = 5.2477 (line 79), while YOLO_PRICE_USD starts at $0.001 (line 77). A $100 USDC task needs 100,000 YOLO locked 52 weeks for the full 4%: $7.87 of YOLO at the opening price for +$4 per task, instead of the $100 the parameter implies. The dev wallet can only correct it in x10 steps (DevPrices.MAX_STEP). A stale price moving only the stake bonus is accepted by the brief; the inconsistent starting value is a launch-plan defect. Fix: set YOLO_PRICE_USD to the opening price (about 0.0000787e18) or raise the opening market cap to about 190,000 IMD.","line":77,"path":"contracts/script/LaunchPlan.sol","reproduction":"contracts/script/LaunchPlan.sol constants: YOLO_PRICE_USD = 0.001e18 (line 77), IMD_USD_START = 5.247665725934671441e18 (line 79), YOLO_OPENING_MCAP_IMD = 15_000e18 (line 89), YOLO_SUPPLY = 1e27. 15,000 x 5.2477 / 1e9 = $0.0000787 per YOLO at the pool opening vs the $0.001 parameter: 12.7x. Arithmetic on the constants; no test needed.","severity":"low","snippet":"    uint256 internal constant YOLO_PRICE_USD = 0.001e18;","title":"Launch YOLO price parameter ($0.001) is 12.7x the pool's opening price, so a full 4% stake bonus needs a lock worth about $7.87 of YOLO on a $100 task"},{"citation":"resolved","description":"From audit_permissions (info). RULES: the lock must still run at payout or b = 0. The vault checks the hub at its own payout (DisputeDesk._lockRuns, line 735) and stores stakeBps; the sweetener's payout is a separate permissionless call that re-reads only the stored bps, never the hub. A doer whose lock ends the day after the acceptance (or who withdraws it in between) still gets up to 4% of the add-on. Bounded by 4% of the add-on and documented in YOLO.md ('if the vault paid the doer the stake bonus on this submission'): drift between the RULES wording and the code.","line":146,"path":"contracts/src/YoloSweetener.sol","reproduction":"Lock ends at T; the acceptance is poked at T-1 (st.stakeBps = 400 stored); the doer withdraws the lock at T; sweetener.payout called at T+1: extra = bonus * 400 / 10000 still paid to the doer (lines 146-152). Traced in code.","severity":"info","snippet":"        uint256 bps = vault.getSettlement(subId).stakeBps;","title":"YoloSweetener pays the stake share from Settlement.stakeBps even if the lock ended between the vault payout and sweetener.payout"},{"citation":"resolved","description":"From audit_economics (info). RULES: 'the lock must still run at payout or b = 0'. Read literally, a stake with b = 0 keeps the staked split (unearned NFT 5% burned, stakers' 5% half burn half treasury); the code takes the no-stake branch (all 10% to the treasury, burn 0). MECHANICS 'Known limitations' documents it ('the fee goes as without a stake'): accepted drift, no user loses.","line":735,"path":"contracts/src/desks/DisputeDesk.sol","reproduction":"Doer locks 1 week, submits, lock ends, poke after 24 h on a 100 USDC task: treasuryPot += 10 USDC, burn 0; under the RULES wording burn 7.5 USDC and treasury 2.5 USDC. Traced in code.","severity":"info","snippet":"        if (f.staked && _lockRuns(f)) {","title":"Lock ended before payout falls back to the no-stake split (treasury 10%) instead of the staked split with b = 0"},{"citation":"resolved","description":"From audit_permissions (info). With the launch minimums (5 USDC, 0.002 ETH, 1 IMD, 1,000 YOLO) every 1% / 3% bond part is non-zero, so the branch never fires; it would only on a registry version with a minimum below 100 base units (ParamsBounds allows minRewardUsdc down to 1e6, still 10,000 units). Edge case, no fix needed.","line":778,"path":"contracts/src/desks/DisputeDesk.sol","reproduction":"base < 100 units -> _pctBond returns 0 -> winnerBonds == 0 -> the loser's rew penalty goes to the treasury, not the winner. Not reachable with any launch-era minimum.","severity":"info","snippet":"        if (winnerBonds == 0) return (toVoters, winnerShare, 0, 0);","title":"_splitCurrency sends the winner's share to the treasury when the winning side has no bond in that currency (unreachable at the launch minimums)"},{"citation":"resolved","description":"Merged from audit_permissions (info) and audit_economics (info). Unlock per month in months 1-12 after the split: incentives 187.5M/24 = 7.81M, treasury (222,222,223 - 74,074,074)/12 = 12.35M, dev 77,777,777/12 = 6.48M, team 50M/12 = 4.17M, about 30.8M YOLO ($30.8k at $0.001). Burn per accepted $100 task: 0 without a stake (the treasury takes 10%), $5.50 at full power, up to $7.50 near zero power, $10 on a YOLO reward without a stake (addTreasury burns YOLO). 200 tasks/month all YOLO-denominated and unstaked: 2.0M YOLO (6.5% of the unlock); 200 staked USDC tasks: $1,100 to the buyback wallet, 1.1M YOLO only if it buys and burns (a manual promise, no on-chain buyback). Drift, not a defect.","line":15,"path":"contracts/src/token/Vestings.sol","reproduction":"Arithmetic from Vestings.sol / YoloSplitter.sol constants and DisputeDesk._payReward (burn = half + nftPart - toVoters; TaskVault.addTreasury burns YOLO).","severity":"info","snippet":"    uint256 public constant AT_ONCE = 62_500_000e18;","title":"Monthly YOLO burn is a few percent of the monthly unlock at 200 tasks/month and R $100"},{"citation":"resolved","description":"From audit_permissions (low, kept as info: no RULES amount is broken). A third-party appellant posts 0.5 IMD + 1% of A plus the 0.5 IMD fee ($2.25) and, winning, gets the fee back only from what is left of the loser's IMD bond, then a pro-rata share of the loser's rew bond. On a 5 USDC task the loser's doer bond rew part is 0.15 USDC: the appellant recovers 0.5 IMD and 0.075-0.15 USDC, below the gas of appeal + askAppealSwarm (about 350k gas, $0.10 at 0.12 gwei and ETH $2,398), and loses the whole fee if the loser already spent its bond IMD on a silent call. On a $100 task: +$1.50 (NFT wrong) or +$0.75 (NFT right) net of gas. Disputing a fake on $100: +$2.63 net (3% of A = $3 minus gas) with the NFT undecided. An NFT vote: 5% of R + half the loser's bond shared among the right votes. Worth a note in the docs: outsider appeals pay only above a few hundred dollars of reward.","line":649,"path":"contracts/src/desks/DisputeDesk.sol","reproduction":"R = 5 USDC, NFT decides Accept wrongly, outsider appeals (0.5 IMD + 0.05 USDC bond + 0.5 IMD fee), swarm says Reject: _settleDecided credits the stranger 0.5 IMD (strangerFee, line 649) then splits 0.15 USDC between disputer (bond 0.05) and appellant (bond 0.05): appellant +0.075 USDC, gas about $0.10. Arithmetic on the code paths.","severity":"info","snippet":"            uint256 strangerFee = p.bondFixedImd > penaltyImd ? penaltyImd : p.bondFixedImd;","title":"Outsider appeal and swarm call are net negative at the minimum rewards; disputing a fake pays about $2.60 on a $100 task"}],"hash":"1b3fc3c792cf93a9018bc575868f1bce8f8b33b388da9b7d437dc812820e96cd","nodeId":"25ea0881-b10e-44a5-9490-745b2d09882f","outcome":"completed","summary":"The findings file is written and verified to parse. Recap:\n\n**Outcome.** `.imd-findings.json` holds 10 findings: 2 medium with Foundry proofs, 3 low, 5 info. Of the 13 specialist findings, 4 pairs were duplicates and were merged; nothing was dropped as false, but one low was downgraded to info because no RULES amount is broken.\n\n**Build note.** The full via-IR build is killed by the sandbox's 1.5 GB memory cap. I built sources plus the scratch tests only (test dir overridden by environment variable, no config file touched), and ran both proofs that way.\n\n**Mediums, both reproduced with a failing test:**\n- **Self-dispute by a 10-NFT holder** (`contracts/src/desks/DisputeDesk.sol:136`). The disputer may vote ACCEPT against its own dispute, decide alone at the vote end, and claim the NFT 5% of R plus half its own bond. My test shows the disputer leaving a 10,000 USDC task with +450 USDC and the treasury getting 500 instead of 1,000. Worse than the documented limitation: 10 NFTs not 20, no second wallet, gain linear in R. Merged the math and economics findings.\n- **Stake bonus and bonds sized on A, paid on R and the add-on** (`contracts/src/desks/CaseDesk.sol:527`). The specialist's proof fails as stated: a $5 lock takes 40 USDC of bonus on a 1,000 USDC crowd-funded pot, treasury 5 instead of 50. Code matches the RULES, so flagged as the rule itself being abusable, with the fix needing the requester's decision. Merged permissions and economics findings.\n\n**Lows:** docs say 20+ NFTs where the code needs 10; the outsider's 0.5 IMD refund ignores the IMD rew part of the loser's bond on IMD tasks; the launch YOLO price parameter is 12.7x the pool's opening price.\n\n**Infos:** sweetener pays stakeBps after the lock ended; lock-ended fallback to the no-stake split; the unreachable dust branch; monthly burn versus unlock (two specialist infos merged); outsider appeal incentives at minimum rewards.","treeHash":null,"usage":{"cachedInputTokens":1138916,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":26699,"runtime":"claude","turns":23,"wallClockMs":1268986}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"22c9b760d0a15149","findings":[{"citation":"resolved","description":"The NFT 5% of the fee and 50% of the loser's bond go to the correct NFT votes whenever the NFT decided right. A holder of endDelta (10) NFTs disputes an honest submission from a second wallet, votes Accept alone, and is the only 'right' voter. The dispute bond is 0.5 IMD + 1% of A; half of it comes back to the same person as a voter. Net gain = 5% of R - 0.5% of A - 0.25 IMD, i.e. about 4.5% of R minus $1.13, paid by the treasury (which loses the whole NFT 5%). MECHANICS 'Known limitations' lists this at $100 ('about $3') as not paying off, but the gain is linear in R while the cost is 10 reusable NFTs and gas (~$0.05 at 0.12 gwei). The honest doer is unaffected. Smallest fix: when the dispute is lost by the disputer, pay the NFT 5% only if at least minWinnerWallets > 1 distinct wallets (or a quorum of votes) voted on the winning side, otherwise route it to the treasury; or cap the voter fee share at the disputer's forfeited bond.","line":728,"path":"contracts/src/desks/DisputeDesk.sol","reproduction":"Base harness (contracts/test/Base.t.sol, endDelta 10): _holder('h',10); fund wallet w2; t=_create(author, usdc, 10_000e6); s=_submit(doer,t); _dispute(w2,s); _voteAs(s,h,SIDE_ACCEPT); _endVote(s); warp 24h+1; poke; claimVoterReward(s,h). Actual (verified with forge): h gains 550 USDC (500 fee + 50 bond share) and 0.25 IMD; w2 loses 100 USDC and 0.5 IMD; treasury gets 500 USDC instead of 1,000. Net to the attacker +450 USDC - 0.25 IMD = +$448.9 per $10,000 task. Expected by the doc: the rule 'does not pay off as an abuse'.","severity":"medium","snippet":"        uint256 toVoters = nftRight ? nftPart : 0;","title":"NFT holder self-dispute takes the 5% NFT fee from the treasury at a profit that scales with R"},{"citation":"resolved","description":"RULES size the doer bond (3%), the dispute bond (1%) and the stake power comparison on A, the author's own share, while the payout and the stake bonus b are paid on the whole pot R (others' top-ups included, also those made during the review). With author 5 USDC (the minimum) and a 1,000 USDC top-up by anyone else, R = 1,005 USDC: the doer bond is 0.15 USDC + 0.5 IMD ($2.40), the dispute bond 0.05 USDC + 0.5 IMD, and a lock worth $5 of YOLO at the price parameter (5,000 YOLO at $0.001; 50,000 at the test mock's $0.0001; about $0.39 at the pool opening price of 15,000 IMD per 1B) earns the full 4% of 1,005 = 40.2 USDC on every such task. A fake submission on such a task risks $2.40 for a 904.5 USDC payout if nobody disputes within 24 h, and a disputer's upside is at most 50% of $2.40. The author can shrink A to the minimum by topping up from a second wallet. Code matches RULES; the documented rule itself is the weakness. Smallest fix: base the bonds and the power comparison on max(A, pot at submission) or on the pot, keep the payout rule.","line":113,"path":"contracts/src/desks/CaseDesk.sol","reproduction":"Base harness: t=_create(author, usdc, 5e6); _topUp(alice,t,1_000e6); quoteSubmission(t).rew == 150000 (0.15 USDC); hub.lock(oracle.yoloValue(usdc,5e6)=50,000e18, 52); warp 1; s=_submit(doer,t); stakeBps == 400; quoteDisputeBond(s).rew == 50000; warp 24h; poke; doer credit == 944.85 USDC (904.5 + 40.2 bonus + 0.15 bond). Verified with forge; expected under a pot-based rule: bond 30.15 USDC, full bonus needs YOLO worth 1,005 USDC.","severity":"medium","snippet":"        Bond memory b = _doerBond(t.base, p);","title":"Doer bond and stake-bonus base follow the author's own share A, so a crowd-funded task is cheap to fake and cheap to earn 4% on"},{"citation":"resolved","description":"The stake bonus compares lock power with A converted at DevPrices.yoloPriceUsd. LaunchPlan opens the pool at YOLO_OPENING_MCAP_IMD = 15,000 IMD for 1e27 YOLO, i.e. 1.5e-5 IMD per YOLO, about $0.0000787 at IMD $5.25 (the plan's IMD_USD_START), while YOLO_PRICE_USD starts at $0.001. A $100 USDC task needs 100,000 YOLO locked for 52 weeks for the full 4%: that is $7.87 of YOLO at the opening price for +$4 per task, forever, instead of the $100 the parameter implies. The dev can correct it later only in x10 steps (DevPrices.MAX_STEP). A stale price moving the bonus is accepted; the inconsistent starting value is a launch-plan defect.","line":77,"path":"contracts/script/LaunchPlan.sol","reproduction":"Read LaunchPlan.sol: YOLO_PRICE_USD = 0.001e18 (line 77), YOLO_OPENING_MCAP_IMD = 15_000e18 (line 89), IMD_USD_START = 5.2477e18 (line 79). 15_000e18 * 5.2477 / 1e9 = $0.0000787 per YOLO vs the $0.001 parameter. Expected: the parameter equals the opening price (0.0000787e18) or the mcap is 190,000 IMD.","severity":"low","snippet":"    uint256 internal constant YOLO_PRICE_USD = 0.001e18;","title":"Launch YOLO price parameter ($0.001) is 12.7x the pool's opening price, under-sizing the stake lock"},{"citation":"resolved","description":"Unlock per month after the split: incentives 187.5M/24 = 7.81M, treasury 148.15M/12 = 12.35M, dev 6.48M, team 4.17M, total about 30.8M YOLO. Burn at 200 accepted $100 tasks a month, all rewarded in YOLO and no stake: 10% of R = 10,000 YOLO each at $0.001 = 2.0M YOLO (25.4M at the pool opening price); with USDC/ETH rewards the burn in YOLO is zero (the fee goes to the treasury or the buyback wallet, no on-chain buyback). Drift only, no RULES amount broken.","line":15,"path":"contracts/src/token/Vestings.sol","reproduction":"Arithmetic from Vestings.sol and YoloSplitter.sol constants and the RULES fee split.","severity":"info","snippet":"    uint256 public constant AT_ONCE = 62_500_000e18;","title":"Monthly YOLO burn is an order of magnitude below the monthly unlock at 200 tasks/month"},{"citation":"resolved","description":"RULES: 'the lock must still run at payout or b = 0'. With b = 0 and a stake, the staked split would burn the unearned NFT 5% and split the stakers' 5% half burn, half treasury. Code falls back to the no-stake split (all 10% to the treasury). MECHANICS Known limitations documents this ('the fee goes as without a stake'), so it is accepted drift between RULES and doc, not a loss to any user.","line":735,"path":"contracts/src/desks/DisputeDesk.sol","reproduction":"Doer locks 1 week, submits, lock ends, poke after 24h: treasury gets 10% of R, burn 0; under the RULES wording burn would be 7.5% and treasury 2.5%.","severity":"info","snippet":"        if (f.staked && _lockRuns(f)) {","title":"Lock ended before payout uses the no-stake split (treasury 10%) rather than the staked split with b = 0"}],"hash":"2fdbc9313d9e0010c6bcda86d27f6f2ca2fc293e227f6cfd8590f3316a650444","nodeId":"5cc75d4d-5e4f-416a-8158-ff4ac9e6ca9f","outcome":"completed","summary":"Findings file is written and validated. Here is the report.\n\n**0. VERDICT**\nPayouts match the RULES in every outcome I traced: accepted (no stake, stake, NFT right/wrong/undecided), rejected, appeal money, cancel, expiry, YoloPredict, Sweetener, trophy, token split, taxes. No critical or high. No money blocker. Two mediums are economic gaming of rules as written: (1) an NFT holder self-disputes and takes the NFT 5% from the treasury, gain about 4.5% of R, +$448.9 on a $10,000 task; (2) bonds and the stake base follow A, so a $5 author share plus $1,000 of top-ups makes a fake submission cost $2.40 for a 904.5 USDC payout and a $5 lock earn 40.2 USDC. One low: the launch YOLO price parameter is 12.7x the pool opening price. Build and 682 tests pass.\n\n**1. ACCEPTED, NO DISPUTE** (DisputeDesk.sol:724-746). $100: fee 10, nftPart 5. No stake: doer 90, treasury 10 (USDC 90e6, ETH 0.0375 of 0.0417, IMD 20 of 22.2, YOLO 90,000 of 100,000, treasury YOLO goes to the burner). Staked full: doer 94, burn 5.5, treasury 0.5; half: 92 / 6.5 / 1.5. Matches RULES. No issue.\n\n**2. DISPUTE, NFT RIGHT** (DisputeDesk.sol:622-713, 759-790). Accepted: loser bond per currency, 50% to the doer (`winnerBondBps`), 50% to accept voters, voters also get the 5% fee, doer's bond back minus own swarm spend. Rejected: 50% to the disputer bucket pro rata to bonds, 50% to reject voters, disputer's bond back. Verified at $10,000: voters 500 fee + 50 USDC + 0.25 IMD. No issue. Medium gaming reported in finding 1 (see JSON): a 10-NFT holder is the only \"right\" voter against their own second-wallet dispute. Smallest fix: require more than one distinct winning wallet or cap the voter share at the forfeited bond. PoC: Base-harness steps in the JSON, verified with forge; not self-contained so not attached as proof.\n\n**3. NFT WRONG OR UNDECIDED** (DisputeDesk.sol:511-514, 728, 773). Swarm overturn: `nftRight` false, voters nothing, 100% of the loser's bond to the winner, unearned NFT 5% to treasury (no stake) or burn (stake). Undecided and no call: `_endUncalled` accepts, disputer's bond 100% to the doer. No issue.\n\n**4. REJECTED** (TaskVault.sol:431-449, CaseDesk.sol:140-155). Pot stays, next queued submission activated with its own 3% of A bond fixed at its submission; `claimVoidSubmission` returns a queued bond on Completed/Unwound. No issue.\n\n**5. APPEAL MONEY** (DisputeDesk.sol:282-326, 595-612, 644, 703). Party: first call 0.5 IMD from own bond (`_sideSpent`), later calls from wallet, no second bond. Outsider: dispute bond + fee, winning gets up to 0.5 IMD off the loser's remaining IMD bond then a pro-rata winner share. Silence: bond and unspent funds back, fee gone. Void or failed ask: full refund, counts as a call. Three silences: outcome as uncalled. Nets at R $100: doer loses appeal to swarm: -0.5 IMD -3 USDC; outsider wins: +0.5 IMD +50% of 3.5 split. No issue.\n\n**6. BONDS** (Desk.sol:118-137). 0.5 IMD + 3%/1% of A, never doubled, IMD and rew split alone. Minimums: 5 USDC gives 0.15/0.05 USDC; 0.002 ETH gives 6e13/2e13; dust where the winning side has no rew bond goes to the treasury (`_splitCurrency` line 778), unreachable above the minimums. Medium reported in finding 2: A can be the $5 minimum on a $1,005 pot.\n\n**7. STAKE BONUS** (CaseDesk.sol:511-528, YoloLockHub.sol:62-114, DisputeDesk.sol:750). b = 400 x power / yoloValue(A), floored, power read strictly before the block, add/extend after submission changes nothing, withdrawn or ended lock pays 0 and uses the no-stake split (info, documented). Lock power formula matches RULES. No issue beyond findings 2 and 3 (price parameter).\n\n**8. YOLO ADD-ON** (YoloSweetener.sol:125-172). Same 10% split, voters only on `nftDecision == Accept`, stake share from `Settlement.stakeBps`, burn to the burner, refunds on Expired/Unwound per funder. No issue.\n\n**9. EXPIRY AND CANCEL** (TaskVault.sol:335-376, DisputeDesk.sol:573-589). Each funder takes back `active`; cancel returns bonds minus own swarm spend, appellant bond ","treeHash":null,"usage":{"cachedInputTokens":2212602,"inputTokens":486,"model":"claude-fable-5-1","outputTokens":40257,"runtime":"claude","turns":31,"wallClockMs":1039801}},{"artifacts":[],"attempt":2,"bundleHash":null,"device":"30a6c1a419ef4f9c","findings":[{"citation":"resolved","description":"vote() only checks phase, side, payTo and NFT ownership; nothing excludes the submission's disputer (or its payTo) from voting, nor from voting ACCEPT against its own dispute. With endDelta = 10 and minWinnerWallets = 1 (VaultParams.sol LaunchParams.initial) one wallet holding 10 identity.md NFTs can: dispute an honest submission (bond 0.5 IMD + 1% of A), vote ACCEPT with its 10 NFTs, wait for the vote end (10 < instantDelta 20, so no instant decision) and the appeal window. The NFT 'decided right', so settle() books nftFeeBps (5% of R) into voterFee and 50% of the loser's bond (the wallet's own bond) into voterBond; claimVoterReward pays all of it to the same wallet because it holds 100% of the accept votes. Net per task in the reward currency: +5% R - 1% A + 0.5% A = +4.5% of A when R = A, against -0.25 IMD ($1.125 at $4.50). At R = A = 10,000 USDC: +450 USDC net, zero marginal cost (the NFTs are already held, gas at 0.12 gwei is cents). At R = $100: +$3.375 net. The treasury loses the whole 5% (it would have received it without a dispute); the doer gains 0.5% A + 0.25 IMD. docs/MECHANICS.md 'Known limitations' lists this with '20+ NFTs' and 'about $3 on a $100 task'; the code needs only 10 NFTs at the vote end and the gain is linear in the reward with no cap, so the documented rule is economically abusable on every large task by any 10-NFT holder, repeatably. Smallest fix: when the final outcome is on the side opposite to the disputer, route the disputer's own votes (recs[disputer] and votes whose payTo is the disputer) out of voterVotesLeft / voterFee (to the treasury), or forbid the disputer and its payTo from voting on its own dispute (revert in vote()).","line":136,"path":"contracts/src/desks/DisputeDesk.sol","reproduction":"USDC task of 10,000e6 by author (A = R). doer submits. whale (10 NFTs, ids 0..9) calls dispute(subId, 'ipfs://reason','ipfs://reason-proof') paying 0.5e18 IMD + 100e6 USDC, then vote(subId, SIDE_ACCEPT, whale, [0..9]). Phase stays Voting (10 < instantDelta 20). warp +24h, poke -> Decided (Accept). warp +24h, poke -> Final, Outcome.Accepted, nftRight. claimVoterReward(subId, whale) pays 500e6 USDC (voterFee = 5% of R) + 50e6 USDC + 0.25e18 IMD (50% of its own bond). Expected (RULES: a losing disputer forfeits its bond; the NFT 5% is for voters who decided right, not for the party that lost): whale's USDC balance after <= before. Actual: whale's USDC is +450e6 (and IMD -0.25e18): assertLe(usdcAfter, usdcBefore) fails. Test: contracts/test/scratch/DisputerVoterTakesNftFee.t.sol, test_DisputerVotesForTheDoerAndProfits.","severity":"medium","snippet":"    function vote(uint256 subId, uint8 side, address payTo, uint256[] calldata tokenIds) external routed {","title":"Disputer may vote on its own dispute for the doer and take the NFT 5% of R plus half of its own bond: profitable at 4.5% of A per task with 10 NFTs"},{"citation":"resolved","description":"LaunchParams.initial sets instantDelta = 20 but endDelta = 10 and minWinnerWallets = 1 (contracts/src/VaultParams.sol:131-133). A wallet with 10 NFTs that votes alone decides at the vote end (CaseDesk._tryDecide -> _decide with endDelta). The doc states the attack needs 20+ NFTs and yields 'about $3 on a $100 task'; it does not state that the yield is 5% of R - 0.5% of A on every task (e.g. $450 on a $10,000 task) and that the same wallet may be the disputer itself (no second wallet needed: dispute() only excludes the doer). Fix the text (10 NFTs; gain linear in R) or the rule (see the medium finding).","line":341,"path":"docs/MECHANICS.md","reproduction":"Same flow as the medium finding with 10 NFTs in one wallet: after dispute + vote(ACCEPT, 10 ids) the phase is Voting; after warp 24h + poke it is Decided(Accept) with 10 votes, contradicting '20+ NFTs'. Expected per doc: 10 NFTs cannot decide alone. Actual: they do at the vote end.","severity":"low","snippet":"- A holder of 20+ NFTs can dispute an honest submission from a second wallet and vote alone to take the NFT 5% of the","title":"Docs vs code: the known limitation says 20+ NFTs decide a self-dispute; the vote-end rule needs 10 (endDelta), and the gain scales with the reward"}],"hash":"43fd8463b5f431faefa97ac0b57355da9b3ca31c85cc0321cceb38dd714b905a","nodeId":"e7611976-d472-4148-bf58-a4a1ce7959b5","outcome":"completed","summary":"The PoC now fails for exactly the stated reason: the disputer-voter nets +450 USDC and −0.25 IMD on a 10,000 USDC task. The findings file holds this proof. Time is up, so here is the report.\n\n**VERDICT.** The payout code in DisputeDesk, CaseDesk, YoloPredict, YoloSweetener, YoloLockHub, BuybackBurner and the token/vesting contracts matches the RULES in every path I traced: the $100 examples (94/5.50/0.50 and 92/6.50/1.50), bonds, the 50/50 loser split, the appeal funding from a party's bond, the predict split, the burns and the tokenomics numbers all reconcile. I found no critical or high money defect. One medium: the documented \"self-dispute\" limitation is worse than stated and scales with the reward.\n\n**Findings** (both in `.imd-findings.json`):\n\n1. **Medium, with PoC.** `DisputeDesk.vote` (contracts/src/desks/DisputeDesk.sol:136) lets the disputer vote ACCEPT on its own dispute. With 10 NFTs (endDelta is 10, not the documented 20) one wallet disputes an honest submission, decides at the vote end, and claims the NFT 5% of R plus half of its own bond. Net at R = A = 10,000 USDC: +450 USDC, −0.25 IMD, zero marginal cost. The treasury loses the 5%. Proof: `contracts/test/scratch/DisputerVoterTakesNftFee.t.sol`, fails now with the whale +450 USDC. Fix: exclude the disputer and its payTo from the voter pool of its own dispute, or revert their vote.\n2. **Low.** docs/MECHANICS.md:341 says \"20+ NFTs\" and \"$3 on a $100 task\"; code needs 10 NFTs and the gain is linear in the reward.\n\n**Items 1–15, one line each.** 1 accepted no dispute: correct in all four currencies, b = 0 → 90/10. 2 NFT right: 50/50 split and winner bond back correct. 3 NFT wrong/undecided: 100% to the winner, unearned 5% burned when staked, correct. 4 rejected: R stays, next bond identical, claimVoidSubmission refunds, correct. 5 appeal money: party pays from bond once then from wallet, outsider bond + fee, stranger 0.5 IMD only from leftover IMD, three silences end as uncalled, correct. 6 bonds: 0.5 IMD + 3%/1% of A, dust to the treasury when a side has no bond, correct. 7 stake bonus: measured on A, floored bps, lock read strictly before the submission block, relock is a new epoch, lock ended → 0, correct. 8 sweetener: same split, voters only on a right Accept, withdraw after expiry, correct. 9 expiry and cancel: each funder their own part, predictions 100%, correct. 10 predict: claims + burn + deferred = paid in, empty sides and zero-point winners handled, correct. 11 burns: YOLO to burner then dEaD, others to the buyback wallet with fallback, correct; monthly burn vs unlock not modelled for lack of time. 12 tokenomics: split sums to 1e27, unlock table (536.6M/721.4M/906.3M) matches the vestings, taxes at 0/15/30/45/60 min match, recipient is the treasury wallet. 13 incentives: the only positive-EV action for an outsider is finding 1. 14 docs: finding 2 is the only drift found. 15 coverage: all files above read in full; HiddenList, TrophyCatalog, ProtocolDirectory, Intake adapters not read. Build succeeded (171 artifacts); I ran only my PoC, not the full suite, because the via-IR compile took most of the budget.","treeHash":null,"usage":{"cachedInputTokens":3206339,"inputTokens":612,"model":"claude-fable-5-1","outputTokens":43062,"runtime":"claude","turns":43,"wallClockMs":2047936}},{"artifacts":[],"attempt":2,"bundleHash":null,"device":"76f57db4177175be","findings":[{"citation":"resolved","description":"RULES / Known limitations: a winning outsider first gets 0.5 IMD off the loser's bond 'if IMD is left in it'. The code only looks at the fixed IMD part (penaltyImd, the 0.5 IMD bondFixedImd bucket). On a task whose reward currency is IMD the loser's bond also has an IMD 'rew' part (1% of A, held in the same token) which is split in the penaltyRew branch and never considered for the refund. When the loser spent its fixed 0.5 IMD on its own silent swarm call (sideSpent), penaltyImd is 0, so the outsider gets no refund although IMD (the 1% rew part) is still left in the loser's bond and goes 50/50 to the winning side and the right NFT votes instead. Bounded loss: 0.5 IMD (about $2.25 at IMD $4.50) per such case, to the outsider who paid the swarm fee; nobody can extract more than that and it needs an IMD-reward task plus a silent swarm. Smallest fix: when penaltyImd < bondFixedImd and f.currency == address(imd), take the remainder of the stranger fee from penaltyRew before _splitCurrency (or compute the stranger fee on penaltyImd + (currency == imd ? penaltyRew : 0)).","line":648,"path":"contracts/src/desks/DisputeDesk.sol","reproduction":"State: task with reward 100 IMD (author alone, A = 100 IMD); doer submits (bond 0.5 IMD fixed + 3 IMD rew); disputer D disputes (bond 0.5 IMD fixed + 1 IMD rew); NFT vote ends with no decision (NoDecision). D calls the swarm for its own side (callSwarm sidesDoer=false): the 0.5 IMD fee is taken from D's fixed bond part (_fundCall fromBond = 0.5e18; _charge sets sideSpent.disputer = 0.5e18). Swarm stays silent (7 h), phase back to NoDecision. Outsider O calls the swarm siding with the doer (new bond 0.5 IMD + 1 IMD, fee 0.5 IMD from wallet); swarm answers accept. Expected (RULES): O gets 0.5 IMD off what is left of D's bond (1 IMD rew part is left), then the rest is split. Actual: penaltyImd = 0.5e18 - sideSpent.disputer = 0, so the strangerFee branch is skipped; O is credited only its own bonds (0.5 + 1 IMD) and a pro-rata winner share of the 1 IMD rew penalty (1e18 * 1.0e18 / (3e18 + 1e18) = 0.25 IMD, all to the winning side since the NFT did not decide); O's 0.5 IMD swarm fee is not refunded; the doer gets 0.75 IMD of D's rew part instead of (1 - 0.5) * 3/4 = 0.375 IMD.","severity":"low","snippet":"        if (appellantIsStranger && callerWins && penaltyImd != 0) {\n            uint256 strangerFee = p.bondFixedImd > penaltyImd ? penaltyImd : p.bondFixedImd;\n            penaltyImd -= strangerFee;\n            bank.credit(address(imd), a.appellant, strangerFee);\n        }","title":"Winning outsider's 0.5 IMD refund ignores the IMD 'rew' part of the loser's bond on IMD-reward tasks"}],"hash":"44c2fbe180e9c78e5d8a50491325611e0d7809f9512c5812c290d09793c0c463","nodeId":"78fcf7af-8c45-4c3d-84e4-872d5cde6305","outcome":"completed","summary":"The findings file is valid and the snippet matches the tree. Here is the report.\n\n**0. VERDICT.** Payouts match the RULES in every outcome traced: 90/10 fee, NFT 5% only when the NFT decided right, staked split (b floored, half burn / half treasury, unearned 5% burned), 50/50 loser's bond when the NFT was right, 100% to the winner otherwise, bonds never doubled, each currency split alone, cancel returns everything but swarm fees, YoloPredict claims + burned + deferred = paid in, trophy 7,777 YOLO burned, token split 1e27 with the documented vesting and tax curves. No money blocker found. Tokens leave a wallet only in the owner's own call (vault routes read the appended sender; sweetener, predict, lock hub and trophy use msg.sender). One low: the winning outsider's 0.5 IMD refund looks only at the fixed IMD bucket, so on an IMD-reward task after the loser's own silent call the refund is skipped even though IMD remains in the loser's bond. Build and the full non-fork suite pass (682 tests).\n\n**1. Accepted, no dispute.** `contracts/src/desks/DisputeDesk.sol:724-746`. $100 USDC: fee 10e6, doer 90e6, treasury 10e6. ETH 0.0417e18 and IMD 22.22e18 give the same 90/10 by integer bps. YOLO 100,000e18: treasury part goes to `addTreasury` which burns YOLO (`TaskVault.sol:577-580`). No issue. No PoC: matches rules.\n\n**2. Dispute, NFT right.** `DisputeDesk.sol:622-713`, `_splitCurrency` 759-790. Accepted: disputer's bond 50% voters (rounded-up half), 50% doer; doer's bond credited back at 703; voters get 5% of R via `voterFee`. Rejected: doer's bond 50% voters, 50% into `disputerReward`, pulled pro rata in `claimDisputer`; disputer's bond back. No issue.\n\n**3. NFT wrong or undecided.** Swarm overturn: `nftRight` false, unearned 5% to treasury (or burned when staked), losing side plus losing caller 100% to the winner. Undecided and no call: `_endUncalled` accepts; penalty is the whole disputer bond minus its own spent fee, all to the doer. No issue.\n\n**4. Rejected.** `TaskVault.closeSubmission:439-447` keeps the pot, activates the next queued one; `_doerBond` is the same 0.5 IMD + 3% of A; `claimVoidSubmission` pays the bond back on Completed or Unwound only. No issue.\n\n**5. Appeal money.** `_fundCall:282-313`, `_charge:316-326`, `_penaltyAndWinnerBonds:595-612`. Party: first call 0.5 IMD from its bond, later calls from wallet, no second bond, spent part deducted from refund and from penalty. Outsider: bond + fee; win returns bond and 0.5 IMD off the loser's fixed IMD bucket. Three silences / void asks end as without appeal, bond and unspent fee credited. Net per case in IMD matches the RULES. Low (JSON): on an IMD-reward task the refund ignores the IMD rew part of the loser's bond. Loss 0.5 IMD ($2.25) to the outsider; fix: compute the stranger fee on `penaltyImd + (currency == imd ? penaltyRew : 0)`. Low, no PoC.\n\n**6. Bonds.** `Desk.sol:118-137`. At minimums: USDC 150,000 / 50,000 units; ETH 6e13 / 2e13; IMD 3e16 / 1e16; YOLO 30e18 / 10e18, all exact, no dust. Base A = author's active contribution. No issue.\n\n**7. Stake bonus.** `CaseDesk._stakeBps:511-528`, `YoloLockHub.sol:62-114`, `_lockRuns:750-754`. Power read strictly before the submission block against A at the dev price, floored to bps; add or extend keeps max(old + added × left/52, total × left/52); withdraw or relock bumps the epoch so an ended lock gives b = 0 at payout. No issue.\n\n**8. YOLO add-on.** `YoloSweetener.payout:125-156`. Same 10% / 5% / b split, voters only when `nftDecision == Accept` on an accepted submission, burn for the rest, refunds on Expired or Unwound. No issue.\n\n**9. Expiry and cancel.** `TaskVault.withdrawContribution:335-347`, `cancelTask:365-376`, `_settleNeutral:573-582`. Each funder's own active share, bonds and unspent prepayments back, spent fees gone. No issue.\n\n**10. YoloPredict.** `YoloPredict.sol:134-292`. Claims + entry burn + deferred = paid in; empty winning side pays author/doer as ruled; zero-point winners split by position; Unwound return","treeHash":null,"usage":{"cachedInputTokens":1397920,"inputTokens":386,"model":"claude-fable-5-1","outputTokens":35978,"runtime":"claude","turns":26,"wallClockMs":1048745}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"99e4819277ea59fc","findings":[],"hash":"4a4fa4d20fb156f60ca25c26c76a118a83322620e2bc541c3debc425cef19f58","nodeId":"78fcf7af-8c45-4c3d-84e4-872d5cde6305","outcome":"failed","summary":"submodules did not fetch: Submodule 'contracts/lib/forge-std' (https://github.com/foundry-rs/forge-std) registered for path 'contracts/lib/forge-std'\nSubmodule 'contracts/lib/openzeppelin-contracts' (https://github.com/OpenZeppelin/openzeppelin-contracts) registered for path 'contracts/lib/openzeppelin-contracts'\nSubmodule 'contracts/lib/v4-core' (https://github.com/Uniswap/v4-core) registered for path 'contracts/lib/v4-co","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":null,"outputTokens":0,"runtime":"claude","turns":0,"wallClockMs":13682}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"60ffae3cc413a0c6","findings":[],"hash":"7c5407f86880b5529ad384c14114b356ff436be66529e6fbe9e58ae9a67aedad","nodeId":"e7611976-d472-4148-bf58-a4a1ce7959b5","outcome":"failed","summary":"submodules did not fetch: Submodule 'contracts/lib/forge-std' (https://github.com/foundry-rs/forge-std) registered for path 'contracts/lib/forge-std'\nSubmodule 'contracts/lib/openzeppelin-contracts' (https://github.com/OpenZeppelin/openzeppelin-contracts) registered for path 'contracts/lib/openzeppelin-contracts'\nSubmodule 'contracts/lib/v4-core' (https://github.com/Uniswap/v4-core) registered for path 'contracts/lib/v4-co","treeHash":null,"usage":{"cachedInputTokens":0,"inputTokens":0,"model":null,"outputTokens":0,"runtime":"claude","turns":0,"wallClockMs":38569}},{"artifacts":[],"attempt":1,"bundleHash":null,"device":"896d1238054266ca","findings":[{"citation":"resolved","description":"CaseDesk._stakeBps compares the doer's lock power with DevPrices.yoloValue(t.currency, t.base), where t.base is only the author's own contribution (TaskVault.submitTerms: _contributions[taskId][t.author].active). DisputeDesk._payReward then pays bonus = f.reward * bps / BPS on the whole pot (others' top-ups included) and YoloSweetener.payout pays the same bps on the whole YOLO add-on. The documented rule ('b is paid on all R and the add-on', power against A) is therefore economically abusable: anyone who keeps the author wallet's share at the per-currency minimum (5 USDC / 0.002 ETH / 1 IMD / 1,000 YOLO) and funds the rest from a second wallet or through Sweeten lets a doer with a lock worth only A take the full 4% of R and of the add-on. Who profits: the doer (or author-doer pair) by 4% of R; who loses: the treasury, whose stakers' half falls from 5% of R to 0.5% of R (and on a YOLO reward the burn). At R = 1,000 USDC: A = 5 USDC, lock 5,000 YOLO = $5 for 52 weeks (power 5,000 YOLO = A at the $0.001 parameter): doer 940 USDC instead of 900, treasury 5 USDC instead of 50, burn 55 USDC. An honest measure (power against the reward the bonus is paid on, 1,000,000 YOLO) would give 2 bps = 0.20 USDC. With a YOLO add-on: a 1,000 YOLO ($1) task sweetened with 10,000,000 YOLO ($10,000) pays a doer with 1,000 YOLO locked 400,000 YOLO ($400) of the add-on's fee for $1 locked. Cost of the attack: the lock (returned after its term) plus one top-up transaction; the doer need not collude with the author at all, it works on every crowd-funded task. Smallest fix: measure the power against the pot and the add-on at submission (t.pot + t.addon converted) or cap the bonus at 4% x min(1, power / (R + add-on) in YOLO) at payout; the docs (MECHANICS 'Doer stake condition', TOKENOMICS, YOLO.md use 7) then change accordingly.","line":527,"path":"contracts/src/desks/CaseDesk.sol","proof":"// SPDX-License-Identifier: MIT\npragma solidity ^0.8.28;\n\nimport {Test} from \"forge-std/Test.sol\";\nimport {ERC20} from \"@openzeppelin/contracts/token/ERC20/ERC20.sol\";\n\nimport {TaskVault} from \"src/TaskVault.sol\";\nimport {ITaskVault} from \"src/interfaces/ITaskVault.sol\";\nimport {ParamsRegistry} from \"src/ParamsRegistry.sol\";\nimport {CaseDesk} from \"src/desks/CaseDesk.sol\";\nimport {DisputeDesk} from \"src/desks/DisputeDesk.sol\";\nimport {TextRules} from \"src/helpers/TextRules.sol\";\nimport {SwarmQuestionBuilder} from \"src/helpers/SwarmQuestion.sol\";\nimport {AttestationChecker} from \"src/helpers/AttestationChecker.sol\";\nimport {VotePower} from \"src/VotePower.sol\";\nimport {YoloLockHub} from \"src/YoloLockHub.sol\";\nimport {BuybackBurner} from \"src/BuybackBurner.sol\";\nimport {DevPrices} from \"src/DevPrices.sol\";\nimport \"src/VaultTypes.sol\";\n\ncontract Coin is ERC20 {\n    uint8 private immutable _d;\n\n    constructor(string memory n, uint8 d) ERC20(n, n) {\n        _d = d;\n    }\n\n    function decimals() public view override returns (uint8) {\n        return _d;\n    }\n\n    function mint(address to, uint256 a) external {\n        _mint(to, a);\n    }\n}\n\n\n/// @dev The core launch in one frame: the desks accept `wire` only inside the transaction that deployed them.\ncontract Core {\n    YoloLockHub public hub;\n    address public vault;\n\n    constructor(address registry, address imd, address usdc, address yolo, address prices, address buyback) {\n        TextRules text = new TextRules();\n        hub = new YoloLockHub(yolo);\n        BuybackBurner burner = new BuybackBurner(yolo, imd, usdc, prices, buyback);\n        CaseDesk caseDesk =\n            new CaseDesk(registry, imd, usdc, address(text), address(new SwarmQuestionBuilder()), yolo, address(hub));\n        VotePower vp = new VotePower(address(0xabcd), address(0));\n        DisputeDesk disputeDesk = new DisputeDesk(registry, imd, usdc, address(text), address(vp), yolo, address(hub));\n        vault = address(\n            new TaskVault(\n                registry,\n                imd,\n                usdc,\n                yolo,\n                address(new AttestationChecker(registry)),\n                address(text),\n                address(caseDesk),\n                address(disputeDesk),\n                address(burner)\n            )\n        );\n    }\n}\n\n/// @notice Stake bonus leverage: the power is measured against the author's own share A only (CaseDesk._stakeBps),\n///         but the bonus b is paid on the whole pot R (DisputeDesk._payReward). A task funded 5 USDC by the author\n///         and 995 USDC by a second wallet lets a doer with 5,000 YOLO ($5 at the $0.001 parameter) locked take\n///         4% of 1,000 USDC = 40 USDC out of the stakers' 5% (the treasury drops from 50 to 5 USDC).\n///         Expected (power against the reward the bonus is paid on): b = 4% x 5,000 / 1,000,000 = 0.02% -> 2 bps,\n///         0.20 USDC. Fails now (doer bonus 40 USDC), passes once the share is measured against R.\ncontract StakeBonusLeverageTest is Test {\n    Coin imd;\n    Coin usdc;\n    Coin yolo;\n    ITaskVault vault;\n    YoloLockHub hub;\n    DevPrices prices;\n\n    address author = makeAddr(\"author\");\n    address topper = makeAddr(\"topper\");\n    address doer = makeAddr(\"doer\");\n    address treasury = makeAddr(\"treasury\");\n    address devWallet = makeAddr(\"devWallet\");\n    address buyback = makeAddr(\"buyback\");\n\n    function setUp() public {\n        vm.warp(1_800_000_000);\n        imd = new Coin(\"IMD\", 18);\n        usdc = new Coin(\"USDC\", 6);\n        yolo = new Coin(\"YOLO\", 18);\n        // launch values: YOLO $0.001, ETH $2,398, IMD $4.50\n        prices = new DevPrices(devWallet, address(usdc), address(imd), address(yolo), 0.001e18, 2398e18, 4.5e18);\n        address nft = makeAddr(\"nft\");\n        address intake = makeAddr(\"intake\");\n        ParamsRegistry registry = new ParamsRegistry(\n            makeAddr(\"multisig\"), intake, address(prices), address(0), nft, address(0), treasury, makeAddr(\"attester\")\n        );\n        Core core = new Core(address(registry), address(imd), address(usdc), address(yolo), address(prices), buyback);\n        hub = core.hub();\n        vault = ITaskVault(core.vault());\n        address[3] memory people = [author, topper, doer];\n        for (uint256 i; i < 3; ++i) {\n            imd.mint(people[i], 100e18);\n            usdc.mint(people[i], 10_000e6);\n            yolo.mint(people[i], 1_000_000e18);\n            vm.startPrank(people[i]);\n            imd.approve(address(vault), type(uint256).max);\n            usdc.approve(address(vault), type(uint256).max);\n            yolo.approve(address(hub), type(uint256).max);\n            vm.stopPrank();\n        }\n    }\n\n    function test_StakeBonusPaidOnOthersTopUps_WithPowerWorthOnlyTheAuthorsShare() public {\n        // the doer locks 5,000 YOLO ($5) for 52 weeks: power = 5,000 YOLO\n        vm.prank(doer);\n        hub.lock(5_000e18, 52);\n        vm.warp(vm.getBlockTimestamp() + 1);\n\n        // the author funds the minimum 5 USDC (A = 5 USDC = 5,000 YOLO at $0.001); a second wallet tops up 995 USDC\n        ImdPay memory noSwap;\n        vm.prank(author);\n        uint256 t = vault.createTask(\n            address(usdc),\n            5e6,\n            uint64(vm.getBlockTimestamp() + 30 days),\n            \"Title\",\n            TaskRules(\"do the thing\", \"PROOF: one https link\", \"NO if: unclear\"),\n            0,\n            PROOF_LINK,\n            \"\",\n            noSwap\n        );\n        vm.prank(topper);\n        vault.topUp(t, 995e6, noSwap);\n        assertEq(vault.getTask(t).pot, 1_000e6, \"R = 1,000 USDC\");\n\n        vm.prank(doer);\n        uint256 s = vault.submit(t, \"https://x.com/doer/status/1\", Evidence(\"\", \"\"), 0, type(uint256).max, noSwap);\n        assertEq(vault.getSubmission(s).stakeBps, 400, \"full 4% share from a $5 lock on a $1,000 reward\");\n\n        // nobody disputes: accepted after 24 h\n        vm.warp(vm.getBlockTimestamp() + 24 hours);\n        vault.poke(t);\n\n        uint256 doerBondRew = 5e6 * 300 / 10_000; // 3% of A = 0.15 USDC, back to the doer\n        uint256 doerGot = vault.credits(address(usdc), doer) - doerBondRew;\n        uint256 bonus = doerGot - 900e6;\n        // the power is worth 5,000 YOLO; the reward the bonus is paid on is 1,000 USDC = 1,000,000 YOLO:\n        // 4% x 5,000 / 1,000,000 = 2 bps of R = 0.20 USDC. Now: 40 USDC (treasury 5 USDC instead of 50).\n        assertLe(bonus, 0.2e6, \"bonus must be proportional to power / the reward it is paid on\");\n        assertGe(vault.treasuryPot(address(usdc)), 5e6 + 24.9e6, \"treasury keeps its half of (5% - b)\");\n    }\n}","reproduction":"Launch prices (YOLO $0.001). Doer: hub.lock(5_000e18, 52); next second. Author: createTask(USDC, 5e6, ...). Second wallet: topUp(995e6). Doer: submit -> getSubmission(s).stakeBps == 400. 24 h later poke: doer credit = 900e6 + 40e6 + 0.15e6 bond; treasuryPot(USDC) = 5e6; buyback wallet 55e6. Expected with power measured against the 1,000 USDC the bonus is paid on: stakeBps 2, bonus 0.20 USDC, treasury 29.9 USDC.","severity":"medium","snippet":"        return (uint16(power >= r ? MAX_STAKE_BONUS_BPS : MAX_STAKE_BONUS_BPS * power / r), true);","title":"Stake bonus power is measured against the author's share A but paid on the whole pot R and the add-on: a $5 lock takes 4% of any crowd-funded reward"},{"citation":"resolved","description":"A third-party appellant posts 0.5 IMD + 1% of A plus the 0.5 IMD fee ($2.25 at $4.50/IMD) and, winning, gets the fee back only from what is left of the loser's IMD bond, then a pro-rata share of the loser's reward-currency bond. On a 5 USDC task the loser's doer bond is 0.5 IMD + 0.15 USDC: the appellant recovers 0.5 IMD and at most 0.075-0.15 USDC, less than the gas of appeal + askAppealSwarm (about 350k gas, $0.10 at 0.12 gwei and ETH $2,398), and loses the whole 0.5 IMD fee if the loser already spent its bond IMD on its own silent call. On a $100 task the win is $1.50 (NFT wrong) or $0.75 (NFT right) net of gas. Disputing a fake ($100 task): +$2.63 net; an NFT vote: 5% of R + half the loser bond shared, positive from 20 NFTs. Fix: none required by the RULES; note in the docs that outsider appeals pay only above a few hundred dollars of reward.","line":649,"path":"contracts/src/desks/DisputeDesk.sol","reproduction":"R = 5 USDC, NFT decides Accept wrongly, outsider appeals (0.5 IMD + 0.05 USDC bond + 0.5 IMD fee), swarm says Reject: _settleDecided credits the stranger 0.5 IMD (strangerFee) then splits 0.15 USDC between disputer (bond 0.05) and appellant (bond 0.05): appellant +0.075 USDC, gas ~$0.10.","severity":"low","snippet":"            uint256 strangerFee = p.bondFixedImd > penaltyImd ? penaltyImd : p.bondFixedImd;","title":"Outsider appeal and swarm call are net negative at the minimum rewards: nobody but a party has an incentive to correct a wrong NFT decision on a small task"},{"citation":"resolved","description":"Burn per accepted $100 task: 0 with no stake (treasury 10%), $5.50 at full power, up to $7.50 at a near-zero power; $10 on a YOLO reward without a stake. 200 tasks/month, all staked at full power: $1,100/month, 1.1M YOLO at $0.001 (and only if the buyback wallet actually buys YOLO with the USDC/ETH/IMD it receives, which is a manual promise). Monthly unlock during months 1-12: incentives 7.8M + treasury 12.3M + dev 6.5M + team 4.2M = 30.8M YOLO ($30.8k). Burn/unlock about 3.6%; even 200 YOLO-denominated tasks with no stake burn 2M YOLO/month (6.5%). Drift, not a defect.","line":57,"path":"docs/TOKENOMICS.md","reproduction":"Arithmetic from TOKENOMICS.md split and Vestings.sol durations; DisputeDesk._payReward burn = half + nftPart - toVoters.","severity":"info","snippet":"- **Staked doers' fee share.** Half of (5% − b), plus the NFT 5% when nobody earned it: from 5.5% (full power, no","title":"Monthly burn is a few percent of the monthly unlock at 200 tasks/month and R $100"},{"citation":"resolved","description":"With the launch minimums (5 USDC, 0.002 ETH, 1 IMD, 1,000 YOLO) every 1% / 3% bond part is non-zero, so the dust branch never fires. It would only on a registry version with a minimum below 100 base units. Edge case, no fix needed.","line":778,"path":"contracts/src/desks/DisputeDesk.sol","reproduction":"base < 100 units -> _pctBond returns 0 -> winnerBonds == 0 -> penalty of the loser's rew part goes to the treasury, not the winner.","severity":"info","snippet":"        if (winnerBonds == 0) return (toVoters, winnerShare, 0, 0);","title":"_splitCurrency sends the winner's share to the treasury when the winning side has no bond in that currency (unreachable at the launch minimums)"},{"citation":"resolved","description":"The RULES say the lock must still run at payout or b = 0. The vault checks the hub at its own payout (DisputeDesk._lockRuns) and stores stakeBps; the sweetener's payout is a separate permissionless call that re-reads only the stored bps, not the hub. A doer whose lock ends the day after the acceptance still gets up to 4% of the add-on. Bounded by the add-on's 4% and consistent with 'one check, one share'; documented in YOLO.md ('if the vault paid the doer the stake bonus on this submission'). Drift between RULES wording and code.","line":146,"path":"contracts/src/YoloSweetener.sol","reproduction":"Lock ends at T; acceptance poked at T-1 (stakeBps = 400 stored); sweetener.payout called at T+1 -> extra = bonus * 400 / 10000 still paid.","severity":"info","snippet":"        uint256 bps = vault.getSettlement(subId).stakeBps;","title":"YoloSweetener pays the stake share from Settlement.stakeBps even if the lock ended between the vault payout and sweetener.payout"}],"hash":"958d013453d7bb95ee28af8ed74aa802c96310cd483078ce87eb00e1ca28b81d","nodeId":"615850fb-2e37-4991-aadb-bfd85e44e4bf","outcome":"completed","summary":"Final report for the moneycanbuy.fun payout and token-economics audit at commit 84b446c. Findings are in `.imd-findings.json` (1 medium with a Foundry PoC, 1 low, 3 info).\n\n**0. VERDICT.** The payout code matches the RULES in every outcome I traced: fee 10% only on acceptance, NFT 5% only when the NFT decided right, stakers' 5% split (b to the doer, the rest half burn, half treasury, unearned NFT 5% burned with a stake, treasury without), loser bonds 50/50 or 100% per currency, winner bonds back, swarm fees never refunded except the outsider's 0.5 IMD off the loser's IMD, cancel returns everything but spent fees, YoloPredict 5/5/90 with 100% back on void, trophy burned to dEaD, taxes 30% flat then linear to 0. No critical: every `transferFrom` pulls from the caller of the vault, the hub, the sweetener, Predict or the trophy. One money blocker before mainnet, a documented rule that is economically abusable: the stake bonus is sized against the author's own share A but paid on the whole pot and the YOLO add-on, so a lock worth the per-currency minimum takes the full 4% of any crowd-funded reward (medium, PoC fails on this code). Everything else is low or info.\n\n**1. Accepted, no dispute** (`contracts/src/desks/DisputeDesk.sol:724-746`). $100 USDC: doer 90 + 3 bond back, treasury 10. ETH 0.0417, IMD 22.22, YOLO 100,000: same split, treasury's YOLO part goes to the burner. b = 0 keeps the no-stake split only when `staked` is false; a floored 0 bps still uses the staked split (fixed, `StakeFloorZeroSplit.t.sol`). No issue.\n\n**2. Dispute, NFT right** (`_settleDecided` 622-713, `_splitCurrency` 759-790). Accepted: disputer's 0.5 IMD + 1 USDC split 50% doer, 50% accept votes, doer's bond back, voters also get 5 USDC of the fee. Rejected: doer's 0.5 IMD + 3 USDC split 50% disputer (via `claimDisputer`), 50% reject votes, no fee. Voter shares pro rata to counted NFTs, last claimant takes dust. No issue.\n\n**3. NFT wrong or undecided** (`CaseDesk._endUncalled` 363-366, `settle` 500-516). Overturned: 100% of the loser's bond to the winning side, fee's NFT 5% to treasury (no stake) or burn (stake). Undecided, no call: Accepted, disputer's bond 100% to the doer. No issue.\n\n**4. Rejected** (`TaskVault.closeSubmission` 431-449, `claimVoidSubmission` 140-155). R stays in the pot, the next queued submission activates with the same 0.5 IMD + 3% of A bond; a void queued bond is paid back in full. No issue.\n\n**5. Appeal money** (`_fundCall` 282-313, `_charge` 316-326, `_penaltyAndWinnerBonds` 595-612). Party: first call from its bond IMD, later calls from its wallet, no second bond; loser's IMD penalty is reduced by what it spent. Outsider: bond + fee, gets 0.5 IMD first from the loser's IMD if any is left. Three silences or three void asks end as without an appeal, bonds and unspent fee back, fees gone. Low: at the minimum rewards an outsider appeal nets less than its gas (`.imd-findings.json` item 2, line 649).\n\n**6. Bonds** (`contracts/src/desks/Desk.sol:118-137`). 5 USDC task: doer 0.5 IMD + 0.15 USDC, disputer 0.5 IMD + 0.05 USDC; 0.002 ETH: 6e13 and 2e13 wei; 1 IMD: 0.03 and 0.01; 1,000 YOLO: 30 and 10. No rounding to zero at any launch minimum; dust in the pro-rata splits stays with the last claimant; a zero winning bond would send the penalty to the treasury (info, line 778, unreachable at launch values).\n\n**7. Stake bonus** (`contracts/src/desks/CaseDesk.sol:511-528`, `YoloLockHub.sol:62-114`). Floors to whole bps, reads the lock strictly before the submission block, add/extend only raises power, a relock after the end is a new epoch, lock ended at payout gives b = 0 and the no-stake split. **Medium**: power is measured against A only, b paid on R and the add-on. $1,000 USDC task, author 5 USDC, second wallet 995 USDC, doer locks 5,000 YOLO ($5) for 52 weeks: doer 940 USDC instead of 900, treasury 5 instead of 50, burn 55; a power-proportional b would be 0.20 USDC. Add-on: 1,000 YOLO task with a 10M YOLO add-on pays 400,000 YOLO ($400) for","treeHash":null,"usage":{"cachedInputTokens":2972083,"inputTokens":516,"model":"claude-fable-5-1","outputTokens":52019,"runtime":"claude","turns":43,"wallClockMs":1475429}}],"verification":[]}