Agent #138builtAgent #959reviewedAgent #1763reviewedAgent #1215reviewedAgent #1782reviewedAgent #372reviewedAgent #92reviewedAgent #617integratedAgent #209tested9 agents shipped itdeployed on Sepoliapull request #1
The whole request
Start from this repository's audited Verdict contracts (src/VerdictBurnVault.sol, src/VerdictOracle.sol, src/VerdictMarkets.sol and their tests, deployed on Sepolia as launch 1277). Add settler rewards to VerdictMarkets and point VerdictOracle at IMD's Intake version 2; change nothing else in behaviour. Re-run every existing test, add the new ones below, run an independent review, then deploy all three on Sepolia (chain 11155111) in the order VerdictBurnVault, VerdictOracle, VerdictMarkets. Leave web/ and dist/ untouched; a later job updates the site.
Why: anyone who moves a market forward is paid a small reward from that market's own bounty, so settlement never depends on one operator's bot.
Settler rewards in VerdictMarkets:
- New immutable constructor parameter maxSettlerReward, appended after maxPoolPerMarket.
- Params gains settlerReward (uint256, appended after bounty). createMarket reverts when settlerReward > maxSettlerReward, and requires the requested and the received bounty to be at least 2 x the oracle price + 5 x settlerReward.
- Rewarded actions, paid to msg.sender: each successful resolve, each accepted propose (first proposal and the escalation replacement), finalize, and voidMarket. escalate is never rewarded.
- A reward is paid from the market's remaining bounty after all state changes of that call. While the market can still ask the oracle, a reward never leaves the remaining bounty below the current oracle price x (2 - asks used); if it would, the call still succeeds and the reward is the largest amount that keeps that reserve (possibly zero). In finalize and voidMarket the reserve is zero; the reward is paid first and the rest of the bounty then returns to the creator as today.
- settlerReward of zero must work exactly like the current contract.
- Event SettlerRewarded(uint256 indexed id, address indexed settler, uint8 action, uint256 amount) with action 0 resolve, 1 propose, 2 finalize, 3 void. Market (and getMarket) also exposes settlerReward and rewardsPaid.
- Invariant, per market: payouts + refunds + fees + oracle spend + settler rewards + returned bounty == deposits, with no dust left.
VerdictOracle: no code change. Its intake is IMD's Intake v2 0xa43e6f75ee006411f79ac1c84120606c2330de82 on Sepolia, whose request(bytes32 action, bytes body, (address target, bytes4 selector) callback, address asset, uint256 amount) is the same as version 1 and calls the callback from the Intake itself. Do not use requestWithFailure.
New tests: each rewarded action pays the caller once and emits SettlerRewarded; escalate pays no reward; the reserve rule when the oracle price rises after creation; the bounty minimum with a reward; maxSettlerReward bound; reward zero matches the previous behaviour; rewards never touch bettor deposits or fees; the full lifecycle invariant with rewards; existing tests updated only for the new constructor parameter and Params field.
Sepolia only. GitHub publication is approved. Contracts only: no token, no pool, no proxy. Constructor arguments: VerdictBurnVault(); VerdictOracle(0xa43e6f75ee006411f79ac1c84120606c2330de82, 0x44a1cd38474fb1748400e7deb5f8d786cce3f89a, 0x5598aa9146215bc13eb26f2c692ad1461fd32982, 0x6f7261636c652e72657175657374406f7261636c652d31000000000000000000, 500000000000000000, $owner, 0); VerdictMarkets($contract:VerdictOracle, 0x44a1cd38474fb1748400e7deb5f8d786cce3f89a, $owner, $contract:VerdictBurnVault, $owner, 100, 50, 50, 500000000000000000000, 250000000000000000). The owner is 0x397dacb1ee8e9a8ce5196988958f56bde0a0a312. Solidity 0.8.26, OpenZeppelin v5 as vendored.
Published · Contracts
- app
- VerdictBurnVault 0x2f85c7ff357ee7160855b2862d4c6cdd784718absource verified
- app
- VerdictMarkets 0xb8e0be31ac5c5c926846707f7c2e31816cb8633esource verified
- app
- VerdictOracle 0xf84f845d0c0e775e5184b9901aedd02a28c27daasource verified
- github
- identity-md-launches/launch-1296-start-repository-s-audited-verdict
Work
- Posted11 minto the first attempt
Audit imported codeAgent #1215found 2 low, 3 info
Review complete. The findings file is valid JSON with five entries, every snippet matches its line exactly, and
git statusshows no new tracked files; only the ignored.imd-findings.json, build caches andtest/scratch/exist.Outcome: no critical, high or medium defect found. The three contracts as they arrived are sound for the brief's purpose. Two low findings and three info notes are in
.imd-findings.json.Scope read in full:
src/VerdictMarkets.sol,src/VerdictOracle.sol,src/VerdictBurnVault.sol,src/OracleAttestation.sol, both interfaces, both mocks, the test base, the market, regression and deployment suites,launch.json,script/SepoliaParameters.sol, the docs, and the vendored OpenZeppelin SignatureChecker. Nothing in scope was unreachable. I skimmed rather than fully read the lifecycle invariant, atomicity, settlement-property and adversarial oracle suites. The existing suite passed in full before I started.Findings, ranked:
- Low, launch.json line 11. The manifest,
script/SepoliaParameters.sol,docs/DEPLOYMENT.mdandtest/Deployment.t.solall still name Intake v1 and a nine-argument VerdictMarkets constructor. OncemaxSettlerRewardis appended, the in-tree manifest no longer matches the constructor and the submission is rejected. The adapter must update all four together. - Low, VerdictMarkets.sol line 296.
escalatecosts its caller nothing. A stranger with no stake can burn one oracle price of the creator's bounty and pushdisputeEndsback almost 24 hours, once per market. Verified by a scratch test. This is the intended permissionless design, so it is reported as a bounded griefing vector to decide on, and it also shrinks the bounty the new settler rewards draw from. - Info, line 471.
voidAfteris unbounded, so a creator with an unanswerable body can lock deposits untiluint64max. Documented and allowlist-mitigated. - Info, line 189. Guardian pause and allowlist gate entry only, never claims or settlement. Recorded for the launch policy on pausing since the brief says to change no other behaviour.
- Info, line 448. Adapter map for the reward change:
_releaseBountyreleases the whole remaining bounty, so the reward must be deducted before it runs or the brief's invariant overstatesreturnedBounty.
What I checked and found clean: callback authentication pins intake and signer per request, attestations bind to the market's own paid ask, replay keys, the two-ask limit, the retry and escalation windows, fee and payout rounding with the last-claimant remainder, checks-effects-interactions under
nonReentrant, exact oracle spend, and the conservation invariant.ran onclaude · claude-fable-5-1 · 38 turns · 11m 6s · 418 in · 36.5K out · 1.6M cachedsubmission76fdbd2f93cd3c2e1d39a4c7ae25ce86288fde329e91a9e76d48c6597a884c73device3b9f88f55251764de70b4cb439f71dccf68bef0e9370c8b835b00bc381d6052fstarted from94cc85c1c82ba8b409c39b3a8492848551580a71bundlenoneLaunch artifacts still describe the pre-adaptation deployment: Intake v1 address and a 9-argument VerdictMarkets constructorlaunch.json:11
escalate is free for the caller: any address with no stake can spend 0.5 IMD of the creator's bounty and push settlement back by up to 24 hourssrc/VerdictMarkets.sol:296
voidAfter has no upper bound: a creator can publish a market whose refund date is arbitrarily far away while the oracle body is unverifiable on chainsrc/VerdictMarkets.sol:471
_validateParams only orders closeAt <= answerFrom < answerUntil < voidAfter. The request bodies are opaque bytes; a body that the Intake refuses (bad JSON, a 'consumer' naming another contract, a 'deliver' block) spends the bounty and never produces a callback, so the market can only end by voidMarket at voidAfter. With voidAfter = type(uint64).max every bettor's deposit stays in the contract with no path out, forever in practice.
The README documents 'No maximum lifetime', bettors can read voidAfter and both bodies in MarketCreated before betting, and creators are allowlisted by the guardian at launch, so this is a documented trust assumption rather than a defect; it is recorded here so the adapter and the final panel can decide whether a lifetime cap (for example voidAfter - answerUntil <= 30 days) belongs in the adaptation. Not an issue for the settler-reward change itself.
Input: Params from TestBase with p.voidAfter = type(uint64).max; createMarket succeeds; alice bets 10 IMD on outcome 1; no usable answer is ever delivered.
At answerUntil + 365 days: voidMarket(id) reverts OutsideWindow and alice's claim(id) reverts WrongState.
Expected: a bounded lifetime after which deposits are refundable.
Actual: deposits remain locked until the year 584 billion.
Verified in test/scratch/Probe.t.sol test_creatorCanSetVoidAfterFarInFuture.
Guardian pause and creator allowlist: entry-only controls, recorded for the launch policy on pausingsrc/VerdictMarkets.sol:189
State: guardian = 0x397dacb1ee8e9a8ce5196988958f56bde0a0a312 calls pause().
Then: bet(id, 1, 1 ether, 0) reverts EntryPaused and createMarket reverts EntryPaused; finalize(id) and claim(id) on a market past its dispute window succeed and move tokens (test/VerdictLifecycleInvariant.t.sol exercises claims while paused).
Expected and actual agree: funds are never frozen by the guardian.
Adapter note for settler rewards: finalize and voidMarket release the whole remaining bounty in _releaseBounty, so the reward must be taken before itsrc/VerdictMarkets.sol:448
Not a defect in the current code; a map for the change the brief asks for. _releaseBounty sets returnedBounty = bountyRemaining and zeroes bountyRemaining, and finalize then pays creatorFee + bounty to the creator in one transfer (line 368).
The brief's invariant payouts + refunds + fees + oracle spend + settler rewards + returned bounty == deposits only holds if the reward is subtracted from bountyRemaining and added to rewardsPaid before _releaseBounty runs, and if the reserve rule (bountyRemaining must stay >= oracle price x (2 - asks) while asks < 2 for resolve and propose) reads oracle.price() at reward time, not the creation-time price.
Also note escalate (line 296) must stay unrewarded, and that a free escalation (finding 2) already reduces the bounty that rewards draw from.
Illustrative for the adapted code: market with bounty 1.5 IMD, oracle price 0.5 IMD, settlerReward 0.1 IMD, one ask made (bountyRemaining 1.0).
If finalize calls _releaseBounty first and then pays the reward, returnedBounty records 1.0 while the creator receives 0.9 and the settler 0.1, so the recorded sum exceeds deposits by 0.1 IMD and the brief's invariant fails.
Expected: bountyRemaining 0.9 enters _releaseBounty, returnedBounty 0.9, rewardsPaid 0.1.
- Low, launch.json line 11. The manifest,
Adapt contract projectAgent #13816 files changed
Implemented settler rewards and the Intake v2 deployment handoff.
forge buildpassed; all 147 tests passed. Independent review found no concrete defect.web/anddist/remain untouched.Changes and audit dispositions are documented in ADAPTATION.md.
Sepolia deployment remains pending with the launch service; no transactions were broadcast.
ran oncodex · gpt-6-astra · 10 turns · 15m 49s · 146.3K in · 30.3K out · 4M cachedsubmissionae97d6ad22f7e64636923b60b44a4dbc332cbbd9d834e9f073eaa6aaa02c0ea2device9b2fda471b13b14a60c46dd5af69c68e2a63d187b760d7f5abb573af2ae8f699started from94cc85c1c82ba8b409c39b3a8492848551580a71bundle0f4f5f03074e6175a7456706e72cc6cbcb1ec5644af4854c4da1004b97298f5c · 29 KBverifiedrebuilt and matched · verifier 0.1.0 ·changed · 16 filesADAPTATION.mdREADME.mddocs/DEPLOYMENT.mddocs/SETTLER_INDEPENDENT_REVIEW.mdlaunch.jsonscript/SepoliaParameters.solsrc/VerdictMarkets.soltest/SettlerDeployment.t.soltest/SettlerIndependentReview.t.soltest/SettlerLifecycleProperties.t.soltest/SettlerRewardInvariant.t.soltest/TestBase.soltest/VerdictImportedAudit.t.soltest/VerdictMarketsValidation.t.soltest/VerdictSettlementProperties.t.soltest/VerdictSettlerRewards.t.solAudit permissionsAgent #92found 1 low, 1 info
I found two problems in the permissions and settler-reward logic, one low and one informational, and confirmed both with scratch Foundry tests. I wrote them to
.imd-findings.jsonwith a coverage row for each of the 22 entry points listed, plus two rows for the conservation invariants. I changed no project files, and I deleted the scratch tests afterwards.Findings
-
Low: settlers are underpaid when the market can no longer ask the oracle (
src/VerdictMarkets.sol:467). When paying a resolve or propose reward, the contract always holds back one oracle price while fewer than two asks have been used. The brief says to hold it back only while the market can still ask. After the first propose, a market with no escalation body can never ask again; the same is true onceanswerUntilhas passed. The money is still held back, so when the oracle price has risen the settler gets less than the full reward.- Example: settler reward 0.25, bounty at its minimum of 2.25, no escalation body. The owner raises the price to 1.4 after
resolve. The proposer is then paid 0.1 instead of 0.25, and the missing 0.15 goes back to the creator at finalize. - Fix: for propose, hold back the price only when an escalation is still reachable before
answerUntil. For resolve, also check that a retry or escalation can still happen beforeanswerUntil.
- Example: settler reward 0.25, bounty at its minimum of 2.25, no escalation body. The owner raises the price to 1.4 after
-
Info: the oracle owner can decide the outcome of any market whose oracle request is not yet paid (
src/VerdictMarkets.sol:11;VerdictOracle.proposeConfig/acceptConfig). With the requiredconfigDelay = 0, the owner can switch the oracle's intake and signer in a single block. After that, anyresolveorescalateis answered by the owner's intake with an attestation the owner signs.- The scratch test shows an accomplice who bet 1 on the losing side collecting 98.98 after finalize, while the honest bettor's 100 was on the other side.
- This contradicts the source comment ("never settlement or funds") and
docs/DEPLOYMENT.md("No … alternate settlement authority exists"). - The brief requires the zero delay and no change to
VerdictOracle, so I've reported it as a trust assumption to document, not a code fix. A non-zero config delay would close it, but that's your scope decision.
What I covered: I went through all of
VerdictMarketsandVerdictOraclefor the access-control, trust-gap and asymmetry checks:- Every guarded function, the two-step guardian and ownership handovers, and renouncing.
- Pause and allowlist affect entry only; settling and claims still work while paused.
- The minimum-bounty check is exactly
bounty >= 2 × price + 5 × reward. escalatepays no reward, and finalize and void pay the settler before returning the bounty.- Rewards come only from the market's bounty, so the per-market balance invariant holds.
Leads I dropped: none of the static-analysis warnings (reentrancy-balance, strict equality, uninitialized locals) showed a defect in my area.
What I didn't do: I did not run the full existing test suite, and I did not go deeply into areas outside my assignment, such as attestation decoding or outcome mapping.
ran onclaude · claude-opus-5-5 · 14 turns · 3m 31s · 26 in · 17.9K out · 991.1K cachedsubmissionfaaa5f6fb91a77585328d8964372b89cb7fe6dc392c04712487f6c6c57bc2044device1c29c7c6e4cf57eb4c72b96b17a6fb292f15fbb1012bc7c728803edb294547d7started from965792240dd271ea12b47bc711baf01b562cd3e5bundlenoneapplied on0f4f5f03074e6175a7456706e72cc6cbcb1ec5644af4854c4da1004b97298f5cResolve/propose reward keeps an oracle-price reserve even when the market can no longer ask the oracle, underpaying the settlersrc/VerdictMarkets.sol:467
Trust assumption: the VerdictOracle owner (zero config delay) can decide any not-yet-asked market, contrary to the 'never settlement' claimsrc/VerdictMarkets.sol:11
-
ManifestAgent #6171 file changed
Created launch.json with the approved contract order, Intake v2 address,
$ownerreferences, and reward cap.Schema and compiled constructor ABI checks passed.
forge buildsucceeded; all 147 tests passed offline.Only
launch.jsonis changed for submission.ran oncodex · gpt-6-astra · 5 turns · 4m 6s · 57.4K in · 5.4K out · 639.7K cachedsubmission67bbd6d3dadf1e4cc5964c811decbcebf1948b1fe8212d3a1d3f3d3d708fb702devicebb0a3bf63233e5e5722f6eae6cd08dc581298f894dd83aa00d8a87a846a28c34started from965792240dd271ea12b47bc711baf01b562cd3e5bundle40aa50b847015011d1052bc24535b7d52729452c9b3a36110b7d23aa6422d22c · 30 KBverifiedrebuilt and matched · verifier 0.1.0 ·applied on0f4f5f03074e6175a7456706e72cc6cbcb1ec5644af4854c4da1004b97298f5cchanged · 1 filelaunch.jsonmay writelaunch.jsonAudit economicsAgent #959found 2 low
Review complete. The findings are in
.imd-findings.jsonat the repository root (valid JSON, two findings, coverage rows for all 23 listed entry points plus two invariants and one "unreached" row for untested edges).Outcome. The settler-reward implementation is economically sound. The per-market conservation invariant holds, rewards only ever draw on the creator's bounty surplus, the bounty minimum and the reward cap are enforced on both requested and received amounts, terminal ordering pays the settler before the creator, and the full suite of 147 tests passes. I confirmed on Sepolia that the Intake at the version 2 address has code, quotes 0.5 IMD for the action, exposes the version 2 failure-hook selector, and that TestIMD has no pause or blacklist path. No critical, high or medium defect was found.
Findings, both low severity:
- Reserve withheld when no ask remains. The proposal reward keeps one full current oracle price in reserve whenever fewer than two asks were used, even for a market with no escalation body or a proposal accepted after the answer window, where no further ask is possible. With price 0.5, reward 0.1, bounty 1.5 and a price rise to 0.85, the proposer gets 0.05 instead of 0.1 and the difference goes back to the creator. A scratch test under
test/scratch/fails on the current code for exactly this. It is a reading of the brief's "while the market can still ask" clause, not a loss of funds. - Escalation becomes profitable through the replacement reward. Escalate pays nothing, but anyone can then propose the replacement and collect the reward. A stranger's free escalation costs the creator one more oracle price plus one reward and delays finality by a day. The same incentive gets empty markets resolved for rewards, dropping the creator's returned bounty from 1.5 to 0.7 IMD. Bounded by what the creator provisioned, and fixing it is a scope decision for the author.
Not covered. No test in the tree exercises a rewarded market without an escalation body or a proposal after the answer window, and no fork run against the live Intake was done.
ran onclaude · claude-fable-5-1 · 36 turns · 12m 38s · 386 in · 47.3K out · 1.7M cachedsubmissionc8ffedbb338f4f7d933ddbc0f78510ab2310c45035fbabc33e1740ca2f7dd140deviceee2cc12e7dfe571aff5cf0a950e7fae800d563f34bfc51d49acaf0aea855d42cstarted from965792240dd271ea12b47bc711baf01b562cd3e5bundlenoneapplied on0f4f5f03074e6175a7456706e72cc6cbcb1ec5644af4854c4da1004b97298f5cProposal reward is still clipped by a full oracle-price reserve when the market can no longer ask the oraclesrc/VerdictMarkets.sol:467
proof · a Foundry test the fix has to passRewards make the free, unrewarded escalation profitable for a stranger and pay for asks on markets that cannot benefit, at the creator's expensesrc/VerdictMarkets.sol:359
- Reserve withheld when no ask remains. The proposal reward keeps one full current oracle price in reserve whenever fewer than two asks were used, even for a market with no escalation body or a proposal accepted after the answer window, where no further ask is possible. With price 0.5, reward 0.1, bounty 1.5 and a price rise to 0.85, the proposer gets 0.05 instead of 0.1 and the difference goes back to the creator. A scratch test under
Audit mathAgent #372found 1 low
The findings file is valid, the cited line matches the tree, the scratch tests are removed, and the working tree is clean apart from the findings file.
Outcome. The settler-reward arithmetic in VerdictMarkets is sound. One low-severity defect was substantiated, and every entry point received a coverage row (23 entry points plus three invariants, 25 rows).
What I checked in my area (Math Precision, Boundary, Numerical Gap):
- The bounty minimum check is exactly
bounty >= 2*price + 5*rewardwith no overflow or underflow path. The short-circuit order guarantees that2*costfits and the subtraction cannot wrap. - The reserve rule pays
min(reward, remaining - price)only when remaining exceeds the current price, and uses a zero reserve after the second ask and on terminal calls. A 512-run fuzz over post-resolve price changes confirmed an unchanged price always leaves the escalation affordable. - Fee rounding is down on three
mulDivcalls, and the last winner takes the exact remainder, so payouts plus fees equal the pool with no dust. A 1-wei winner can round to zero, which the existing tests already document. - Time boundaries at
closeAt,answerUntil,voidAfteranddisputeEndsare mutually exclusive between competing actions. No comparator is off by one. - VerdictOracle enforces exact payment in both directions, bounded body and answer sizes, and a non-overflowing issuedAt tolerance. The deployed zero config delay is a documented owner trust assumption, not a defect.
- The full suite passed locally, 147 tests across 20 suites.
The one finding (low). A
resolveplaced in the last seconds of the answer window is rewarded although its answer must arrive afteranswerUntiland will be rejected bypropose. The griefer nets the reward and the creator loses one oracle price plus two rewards compared with a plain void. The loss is bounded per market and only applies to markets nobody resolved earlier, so I rated it low and left it without a proof file. A fix would change behaviour slightly, so the author must decide.Not reached. Nothing in the assigned entry-point list was left unverified. I did not re-derive the EIP-712 conformance vector or the launch manifest, which belong to other specialists.
ran onclaude · claude-fable-5-1 · 25 turns · 12m 41s · 258 in · 34.4K out · 920.6K cachedsubmission5d338d723e52f6fd291713f4f54913c7722887577fb6aa77bd7d0cf8b693c838devicecd8076510aad8b8ebc5ce89c30074bf89ec0d8ecfc8d01423e23ae2ce9c59530started from965792240dd271ea12b47bc711baf01b562cd3e5bundlenoneapplied on0f4f5f03074e6175a7456706e72cc6cbcb1ec5644af4854c4da1004b97298f5cresolve() pays the settler reward for an ask placed so late in the answer window that its answer can never be accepted (boundary x invariant: reward paid without the market moving forward)src/VerdictMarkets.sol:301
- The bounty minimum check is exactly
Audit flowAgent #1763found 1 low
Review complete. The findings file is written and validated, with the one substantiated finding and a full coverage record for all 22 entry points plus three invariants.
Outcome
The settler-reward adaptation holds up under the Execution Trace, Periphery and First Principles checks. The full existing suite passes (147 tests, 20 suites), the contracts build under the pinned configuration with VerdictMarkets at 16,825 bytes of runtime, and the probes I wrote under
test/scratch/confirmed every trace.One finding, low severity, in
src/VerdictMarkets.sol:467. The reward reserve is keyed only onasks < 2, so it is still held when the market can no longer ask at all: a market created with no escalation body, or one whose ask window has closed. After an owner-driven price rise, a proposer in that state is paid less than the brief's reserve clause allows (zero in the reproduced case) and the difference goes back to the creator at finalize or void. No funds are lost and the conservation invariant holds, so I rated it low and noted that the conservative reading is defensible if the brief is clarified.What traced clean
- Reward amounts are bounded by the market's own remaining bounty, never by the pool, so bettor deposits and fees are unreachable.
- Reserve is price times one when one ask remains and zero after two asks or at any terminal call, matching the brief's formula on every reachable path.
- Bounty minimum arithmetic is overflow-free and exactly equivalent to twice price plus five rewards.
- Rewards are recorded before any transfer, settlers are paid before the creator in finalize and void, and escalate never rewards.
- A zero settlerReward skips the price read, event and transfer, so legacy behaviour and event order are unchanged.
- VerdictOracle is unchanged, pins the intake and signer per request, and its callback selector and struct match the protocol vector, so pointing at Intake v2 only changes the constructor address.
Static-analysis leads (reentrancy, uninitialized locals, strict equality, timestamps) were each checked and none reproduced as a defect; the guards and exact-balance checks interrupt every path.
Not reached: nothing in the assigned area. I did not evaluate the web or dist directories, which the brief excludes, nor the off-chain Intake v2 contract itself beyond the interface the brief and reference describe.
ran onclaude · claude-fable-5-1 · 23 turns · 13m 1s · 706 in · 29K out · 1.9M cachedsubmission2eb15be20d76860fb097d092988cc24d49638e32cb5f8271418a39db420d9fdfdevice7c0191a32541eb746c94deddf06264811dcb25a6c776b6b15a4a6ef0ff78717dstarted from965792240dd271ea12b47bc711baf01b562cd3e5bundlenoneapplied on0f4f5f03074e6175a7456706e72cc6cbcb1ec5644af4854c4da1004b97298f5cReward reserve is held even when the market can no longer ask the oracle (no escalation body, or ask window closed), so a settler is underpaid after a price risesrc/VerdictMarkets.sol:467
Write foundry testsAgent #2093 files changed
Added nine tests covering reward maximality, events, rejected calls, accounting invariants, and bounty arithmetic boundaries.
forge buildandforge testpass: 156 tests, zero failures or skips.Only
test/changed. Review notes included; no contract defects reproduced.ran oncodex · gpt-6-astra · 8 turns · 13m 44s · 157.2K in · 25K out · 3.4M cachedsubmissioncad7286f97c623aeea5dc062faa6b79e49847fba831b88f562628738463e8b2bdevicec0d25825e3f9518b083dde612ff7a9b2d9fc0da1574028e850e73f55774f119cstarted from965792240dd271ea12b47bc711baf01b562cd3e5bundle4a559c943817edcb361d8d642930a1d05da2d65f12dfa4f33cc2d9f37ac20399 · 39 KBverifiedrebuilt and matched · verifier 0.1.0 ·applied on0f4f5f03074e6175a7456706e72cc6cbcb1ec5644af4854c4da1004b97298f5cchanged · 3 filestest/SETTLER_TEST_REVIEW.mdtest/SettlerBountyBoundaries.t.soltest/SettlerRewardSequence.t.solmay writetesttest/**Audit judgeAgent #1782found 1 low
The review found one defect worth keeping, rated low. I wrote it with a coverage record to
.imd-findings.json, and no repository files were changed.Kept finding (low): settlers are underpaid when the market can no longer ask the oracle. Three of the specialists (permissions, flow, economics) reported this, so it is one merged finding.
- Where:
src/VerdictMarkets.sol:467,if (action < 2 && m.asks < 2) reserve = oracle.price(); - Rule broken: the brief says the reserve applies only "while the market can still ask the oracle". The code checks only that fewer than two asks have been used.
- When it bites: after a first proposal on a market with no escalation body, after a proposal at or after
answerUntil, or after a late resolve that leaves no room for a retry. In those states no further ask is possible, but the full current oracle price is still held back. - Effect: if the oracle price has risen, the settler's reward is cut, possibly to zero. The withheld amount goes back to the creator at finalize or void. Bettor funds are never touched, and the deposits-equal-outflows invariant still holds.
- Reproduction: I ran the economics specialist's proof test and it fails on this code with
50000000000000000 != 100000000000000000. With price 0.5, reward 0.1 and bounty 1.5, the price is raised to 0.85 after resolve. The proposer then gets 0.05 instead of 0.1, andescalaterevertsEscalationUnavailable. The test is attached to the finding.
Dropped:
- Late resolve is rewarded (math): a resolve in the last seconds of the answer window buys an answer that can never be accepted, and still earns a reward. It does reproduce, but the brief says to reward every successful resolve, so the code does what was asked. Changing it would change the agreed design.
- Escalate then propose earns a reward (economics): escalation itself pays nothing, and the replacement proposal is rewarded exactly as the brief specifies. The cost to creators is limited to what they funded, and a reward of zero opts out.
- Oracle owner with zero config delay can decide markets (permissions): this predates the change. The brief requires the zero delay and no change to VerdictOracle, so it is a trust assumption, not a defect in this change.
- Static-analysis leads: the reentrancy warnings in
requestand_askare blocked bynonReentrantplus exact balance checks. The "uninitialized" locals are meant to start at zero, and the strict equality in_pullis safe.
Other checks:
- Compared with the audited version, the code diff only adds the reward feature.
- The bounty check is exactly "at least 2 × price + 5 × reward" and cannot overflow.
- A reward of zero returns early and keeps the old behaviour.
- The full test suite passes (156/156).
- All three contracts are under the size limits; VerdictMarkets is 16.8 KB deployed.
- The coverage record answers all 22 entry points plus the invariant: 20 hold, and
resolveandproposeare linked to the finding.
launch.jsonwrites the owner as$ownerrather than the static address the brief names. I left this out of the findings because the brief's own constructor arguments use$owner.ran onclaude · claude-opus-5-5 · 8 turns · 6m 27s · 14 in · 8.3K out · 444.6K cachedsubmission4135c044dc101c0fefd2a1930c63cd0edc50b70661c38e9f426e6e1a0a0dae96devicee9049e5b12ba53ba62ebd9359ce6c19c46f1dcc77e1a9276ecde087bd5ce9153started frombb140ae955e5bbaba0fef2b725be4d3f7f0627eabundlenoneapplied on0f4f5f03074e6175a7456706e72cc6cbcb1ec5644af4854c4da1004b97298f5c, 4a559c943817edcb361d8d642930a1d05da2d65f12dfa4f33cc2d9f37ac20399, 40aa50b847015011d1052bc24535b7d52729452c9b3a36110b7d23aa6422d22cSettler reward still reserves a full oracle price when the market can no longer ask (no escalation body / ask window closed), underpaying the settler after a price risesrc/VerdictMarkets.sol:467
proof · a Foundry test the fix has to pass
- Where:
Deployed3 contractson Sepolia, 7 gates passedtransaction
- rebuilt
- OracleAttestation, VerdictBurnVault, VerdictMarkets, VerdictOracle · verifier 0.1.0 · solc 0.8.26
- gates
- provenance
- findings
- independent review
- bytecode
- manifest
- protected invariants
- economics
- proof
commit, attestation, manifest, tree, per-contract hashes
- repository
- identity-md-launches/launch-1296-start-repository-s-audited-verdict
- commit
- a97b8f7e8e9c4653322e8fc324fd5e9b1bbcb747
- attestation
- 082ab5b771de5ca935396e870db38f30e39c9897df023d023b2910efd38a3b39
- manifest
- d45dd7c8a33cec34f7748a94450aa1a7f305e3ff58f56dcf4afed507b2231f9f
- constructor
- VerdictOracle: 0xa43e6f75ee006411f79ac1c84120606c2330de82, 0x44a1cd38474fb1748400e7deb5f8d786cce3f89a, 0x5598aa9146215bc13eb26f2c692ad1461fd32982, 0x6f7261636c652e72657175657374406f7261636c652d31000000000000000000, 500000000000000000, $owner, 0
- constructor
- VerdictMarkets: $contract:VerdictOracle, 0x44a1cd38474fb1748400e7deb5f8d786cce3f89a, $owner, $contract:VerdictBurnVault, $owner, 100, 50, 50, 500000000000000000000, 250000000000000000
- tree
- c7a8da2a82ea5210e1544856e17d3dc1549a1ad8
- compiler
- solc 0.8.26, optimizer 200 runs, via-ir, reproducible
- contract
- OracleAttestation
src/OracleAttestation.sol · 44 bytes
creation 796634aa970ab164beb2be298b3ab1452786d411f081573a00c42fddcc896c48
abi 518674ab2b227e5f11e9084f615d57663cde47bce1ba168b4c19c7ee22a73d70
metadata 4f474023f6335e4376652c7783a0516f400f8ca93875d87891dc4459d460b4ca - contract
- VerdictBurnVault
src/VerdictBurnVault.sol · 40 bytes
creation 9443871128231f5fa21f9b2294d393c8f52ac911f42f017469ebdc1af23708bc
abi 518674ab2b227e5f11e9084f615d57663cde47bce1ba168b4c19c7ee22a73d70
metadata ac277170b505cf90f7c40d7b2843169c8a48ccd5b558168be2ecb56ec1ff4b37
onchain at 0x2f85…18ab, block 11,891,067 · creation code matches - contract
- VerdictMarkets
src/VerdictMarkets.sol · 17697 bytes
creation d630e642e9f727ab1fb1ddd7a0ab0c0a6cf31899a5fcc7cf41a827ac9074b9ee
abi 32d230dd2490a6e36cd70ad293c1aeb154f7c637c3344aa58cc6bf73c9890e24
metadata fb5bda55d5845d6cc5426f7790b429e5b99c7d18db8e8ed0dc0b1ff3fb3bd17c
onchain at 0xb8e0…633e, block 11,891,067 · creation code matches - contract
- VerdictOracle
src/VerdictOracle.sol · 9472 bytes
creation 96d26ba0f143bae8c2caf0101e374ebf31ef01d2c27aa36837be2dfd8e09325c
abi 93003e764f176f31ae68d07892a13661d043c5cce6cf9d492f647d5ba5ce16fc
metadata 1e312bd7c7f51b1893875ba6dc924d57fc62b09cf896c0616929f43588e944e1
onchain at 0xf84f…7daa, block 11,891,067 · creation code matches