Skip to content

Feat/agent duels - #3

Open
dantol29 wants to merge 18 commits into
mainfrom
feat/agent-duels
Open

Feat/agent duels#3
dantol29 wants to merge 18 commits into
mainfrom
feat/agent-duels

Conversation

@dantol29

Copy link
Copy Markdown
Collaborator

No description provided.

dantol29 and others added 18 commits July 6, 2026 12:27
Note MockUSDC permit support: MockUSDC already extends ERC20Permit and exposes mint(address,uint256); reused as-is.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add bet(), betWithPermit(), and _bet() to DuelMarket with full parimutuel
pool/stake accounting. betWithPermit uses best-effort try/catch permit to
prevent front-run DoS. 5 new tests (TDD); full suite 12/12 passing.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds resolve(uint256) — permissionless, nonReentrant. Snapshots navEnd,
computes signed 1e18 PnL%, selects winner (0/1/2), transfers feeBps of
the losing pool to feeRecipient when decisive (no fee on tie). Status
set to Resolved, emits DuelResolved.

5 new tests cover A-wins, B-wins, tie-no-fee, TooEarly, WrongStatus.
Full suite: 24/24 passing. No new compiler warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Replace lint-suppressed int256 casts in resolve() with OZ SafeCast.toInt256
(reverts on genuine overflow instead of silently wrapping; no suppression
comment needed). Strengthen test_resolve_tie_noFee to assert Resolved status,
navEndA/B, and the DuelResolved(id, 2, ...) event.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add claim(uint256) — winner payout (stake + share of loserPool-fee),
tie/void full refund, NothingToClaim for losers, AlreadyClaimed guard.
7 tests: winner-A, winner-B, loser, double-claim, void, tie, WrongStatus.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add test_claim_multipleWinnersProRata (exact floor-division payouts
alice=165_333333, carol=82_666666; conservation dust=1 wei) and
test_claim_voidNonParticipantReverts (non-staker reverts NothingToClaim).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Four fuzz scenarios (A wins / B wins / tie / multiple-winners) assert:
- post-resolve market balance == totalPot - fee
- all winner claims succeed without revert (solvency)
- dust bounded by winner count (0-2 wei)
- tie: zero fee, full refund, payouts sum to totalPot

Reentrancy: MockReentrantUSDC fires onReceive() during safeTransfer;
ReentrantClaimReceiver re-enters claim() and records the revert selector.
Test asserts guard fires (ReentrancyGuardReentrantCall or AlreadyClaimed).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- multipleWinners: add explicit per-winner proportionality assertions
  (assertEq alice/carol payout == stake + stake*distributable/winnerPool)
  so the fuzz discriminates a wrong pro-rata formula.
- tie: fuzz the shared end-nav in [1, 2*NAV_START] so gain/flat/loss are
  all exercised; both treasuries set to the same value keeps winner==2.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
F1: Add Duel.feeBpsSnapshot field set in resolve(); claim() now reads
the snapshot instead of the live feeBps, ensuring the fee deducted
from payouts always matches the fee already transferred out.

F2: Wrap both IAgentTreasury.nav() calls in resolve() in try/catch;
if either reverts, set status=Voided and emit DuelVoided so bettors
can reclaim stakes via the existing void path in claim().

Add MockRevertingTreasury mock and two TDD tests (RED→GREEN).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add DeployDuelMarket.s.sol (mirrors DeployTreasuryFactory style, reads
env USDC_ADDRESS/DUEL_FEE_RECIPIENT/DUEL_FEE_BPS/DUEL_OWNER) and extend
dump-artifacts.mjs to include DuelMarket → web/src/lib/contracts/DuelMarket.ts.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…state)

Simplify per product decision: FEE_BPS is a compile-time constant (100 = 1%)
and feeRecipient is immutable. Removes feeBps state, setFeeConfig, feeBpsSnapshot,
MAX_FEE_BPS, FeeTooHigh, FeeConfigChanged. This ELIMINATES audit finding F1
(mutable-fee resolve/claim desync) by construction — no mutable fee state exists.
Tests updated for 1% (40 pass incl. fuzz); deploy script drops DUEL_FEE_BPS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…surface)

Two pure-removal simplifications:
- Drop betWithPermit: it only saved a bettor's first approve tx (every
  subsequent bet on an existing allowance was already one tx), which didn't
  justify the EIP-2612 signature verification + try/catch fallback surface.
  Betting is now approve + bet only.
- Drop Ownable/paused/setFeeConfig: the contract is now fully immutable and
  permissionless post-deploy — no owner, no pause, no admin function.
  feeRecipient is set once in the constructor and is immutable.

Constructor is now DuelMarket(usdc, feeRecipient) — no feeBps_/owner_ params.
258->225 lines, ABI 50->39 entries. 36/36 tests pass (4 permit/pause tests
removed, rest unchanged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant