Skip to content

feat(arena): G full-backing withdraw + wei units + opt-in auto-requeue (contracts)#69

Merged
colinisme merged 4 commits into
mainfrom
feat/arena-g-withdraw-requeue
Jun 5, 2026
Merged

feat(arena): G full-backing withdraw + wei units + opt-in auto-requeue (contracts)#69
colinisme merged 4 commits into
mainfrom
feat/arena-g-withdraw-requeue

Conversation

@colinisme

Copy link
Copy Markdown
Contributor

What

Contract-layer implementation of two Arena features. This PR is contracts + tests only — MCP, frontend, and deploy-script follow-ups are listed below and tracked in docs/g-withdraw-plan.md.

1. G full-backing withdraw (GTreasury)

Turns Arena G into a real, withdrawable token on mainnet while staying a free, non-withdrawable faucet point on testnet — the two modes are mutually exclusive and owner-toggled.

  • totalOutstandingG accumulator; invariant balance ≥ totalOutstandingG (every gBalance unit backed 1:1 by native G).
  • withdraw(agentId, amount) — only the agent owner, only to their own wallet (symmetric with depositG), nonReentrant, checks-effects-interactions.
  • withdrawSurplus(to, amount) — owner-only rescue and revenue (buy/roll rake). Can never touch user backing (surplus floor); withdraw-mode only.
  • faucetEnabled / withdrawEnabled — mutually exclusive (an unbacked faucet mint can never coexist with withdraw).
  • spendG floors totalOutstandingG at 0 instead of underflow-reverting → an in-place upgrade of the existing testnet proxy (stale totalOutstandingG = 0 while agents hold gBalance) doesn't brick every spend.

2. Wei unit migration + opt-in auto-requeue (ArenaEngine)

  • Wei units: WEI_PER_G = 1e18; buy/roll spend and bootstrapMarket scale to wei at the spend boundary (UnitCatalog costs stay readable whole-G ints). Tier thresholds scaled to wei (100/1000 G) — else any real deposit lands everyone in Gold. bootstrapMarket faucet-gated.
  • Auto-requeue (opt-in): submit(uint256, bool requeueOnSettle) overload + autoRequeue mapping. settleMatch re-queues opted-in ghosts (tier re-snapshotted from current G) instead of clearing, so the pool doesn't drain between rounds. Default submit() stays one-shot. Pool-full / empty-bench fall back to clear so settle never reverts.

Tests

forge test against this branch's base (main): 113 passed, 2 failed. The 2 failures (test_InciteCooldown, test_InciteReducesHappiness) are pre-existing and unrelated (randomness-dependent GameEngine tests that fail on main unchanged; this PR touches no GameEngine code).

New: GTreasuryWithdraw.t.sol (16 tests — reentrancy, mode interlock, surplus-cannot-touch-backing, post-upgrade underflow, 256-run backing-invariant fuzz) + 5 auto-requeue tests in ArenaTier.t.sol. Existing arena suites migrated to wei.

Deploy / upgrade notes (for whoever ships this)

  • Mainnet: fresh deploy, then setFaucetEnabled(false)setWithdrawEnabled(true); do not run bootstrapMarket.
  • Testnet (in-place upgrade): initialize() does not re-run, so the new faucetEnabled reads its false default — owner must call setFaucetEnabled(true) once post-upgrade to restore the faucet.

Follow-ups (NOT in this PR — from a full multi-agent audit, see docs/g-withdraw-plan.md)

Must-do to ship end-to-end:

  • MCP: scale arenaDepositG / fund_agent_g by 1e18; read gBalance as BigNumber/string (Number() loses precision at wei scale); add arena_withdraw / arena_withdraw_surplus / surplus views; expose submit(uint256,bool) requeue flag in arena_submit.
  • Deploy scripts: SeedArena.s.sol / ArenaTierDemo.s.sol fundAgentG amounts × WEI_PER_G; mainnet deploy path (mode flags, skip bootstrap).

Should / follow-up: frontend gBalance type → string + formatUnits display, withdraw/requeue UI, doc/skill staleness ("G not withdrawable" / "1 wei = 1 G").

colinisme added 2 commits June 5, 2026 16:18
…lock

GTreasury becomes a full-backing ledger so Arena G can be a real,
withdrawable token (mainnet) while staying a free testnet faucet point.

- totalOutstandingG accumulator; invariant balance >= totalOutstandingG
- withdraw(agentId, amount): agent owner -> own wallet only (symmetric
  with depositG), nonReentrant, checks-effects-interactions
- withdrawSurplus(to, amount): owner-only rescue/revenue that can never
  touch user backing (surplus floor); withdraw-mode only
- faucetEnabled / withdrawEnabled: mutually-exclusive modes (unbacked
  faucet mints can never coexist with withdraw)
- spendG floors totalOutstandingG at 0 instead of underflow-reverting, so
  an in-place upgrade of a pre-existing proxy (stale totalOutstandingG=0)
  doesn't brick every spend

Tests: GTreasuryWithdraw.t.sol — 16 tests incl. reentrancy, mode
interlock, surplus-cannot-touch-backing, post-upgrade underflow, and a
256-run fuzz of the backing invariant.

Design + MCP/frontend/deploy follow-ups: docs/g-withdraw-plan.md
Wei unit migration (gBalance becomes a real 18-decimal token):
- WEI_PER_G = 1e18; buy/roll spend and bootstrapMarket scale to wei at the
  spend boundary (UnitCatalog costs stay readable whole-G ints)
- tier thresholds scaled to wei (100/1000 G) — else any real deposit lands
  everyone in Gold
- bootstrapMarket faucet-gated (unbacked mint can't run in withdraw mode)

Opt-in auto-requeue (fixes "pool drains because you must re-submit after
every match"):
- submit(uint256, bool requeueOnSettle) overload + autoRequeue mapping
- settleMatch re-queues opted-in ghosts (tier re-snapshotted from current
  G) instead of clearing; one-shot submit() unchanged
- flag reset on withdraw/clear; pool-full / empty-bench fall back to clear
  so settle never reverts

Tests: arena suites migrated to wei; 5 new auto-requeue tests.
@vercel

vercel Bot commented Jun 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gravity-town Ready Ready Preview, Comment Jun 5, 2026 8:51am

Request Review

…B fit)

The wei migration + auto-requeue additions pushed ArenaEngine to 26,494 B,
over the 24,576 B contract-size limit (main was already at 24,504, +72 B).
optimizer_runs has no effect under via_ir (saves 7 B), so the combat
machinery is moved into a separate linked library.

- new ArenaCombat library (public simulate / simulateWithTrace / initialStats
  / eloUpdate): the combat loop + AbilityLib event-queue + UnitCatalog inline
  here instead of ArenaEngine. Deployed once, reached via DELEGATECALL (one
  call per settle / view — not per turn), so gas overhead is negligible.
- ArenaEngine keeps storage + matchmaking + settlement orchestration; its
  combat fns become thin wrappers that snapshot the Match into ArenaCombat.Side.
- External ABI unchanged (simulateMatch/getInitialStats/settleMatch/
  previewEloUpdate same signatures) -> no MCP/frontend changes.

Result: ArenaEngine 26,494 -> 21,163 B (+3,413 headroom); ArenaCombat 9,331 B.
Behavior identical -- 113 tests pass (determinism/trace tests pin it); the 2
pre-existing incite failures are unrelated.

Deploy note: Foundry auto-deploys + links the ArenaCombat library on
`new ArenaEngine()`, so Deploy.s.sol / Upgrade.s.sol need no change.
@colinisme

Copy link
Copy Markdown
Contributor Author

Added a 3rd commit (886f683): ArenaCombat library extraction to fix an EIP-170 size blocker this PR introduced.

The wei migration + auto-requeue pushed ArenaEngine to 26,494 B, over the 24,576 B limit (main was already at 24,504, only +72 B free — likely why #61 was reverted). optimizer_runs has no effect under via_ir (saves 7 B), so the combat machinery moved into a separately-deployed linked library.

Contract Size Margin
ArenaEngine 26,494 → 21,163 B +3,413
ArenaCombat (new) 9,331 B +15,245 ✅

External ABI unchanged (simulateMatch/getInitialStats/settleMatch/previewEloUpdate same signatures) → no MCP/frontend changes. Foundry auto-deploys + links the library on new ArenaEngine(), so deploy scripts need no change. 113 tests pass (determinism/trace tests pin behavior; the 2 incite failures are pre-existing & unrelated).

The old NatSpec led with "Permissionless" but mentioned a quoted "owner-only"
label, which reads as if the function is owner-gated. Rewrite to state plainly:
contract-level permissionless + rate-limited, no onlyOwner; the OWNER_KEYS check
is an off-chain MCP convention, not a contract restriction. No code change.
@colinisme colinisme merged commit d4f22f4 into main Jun 5, 2026
6 checks passed
@colinisme colinisme deleted the feat/arena-g-withdraw-requeue branch June 5, 2026 09:12
colinisme added a commit that referenced this pull request Jun 5, 2026
feat(mcp): wire G wei-units + withdraw + opt-in auto-requeue tools

Follows the contract layer (#69). G is now wei-denominated on-chain
(1 G = 1e18 wei); the MCP layer speaks whole G and converts at the boundary.

Units:
- gToWei/weiToG helpers; scale deposit/withdraw/fund/market-listing inputs
  x1e18, display every gBalance/surplus/price /1e18 (no more Number(wei)
  precision loss >2^53). UnitCatalog shop costs stay whole-G (contract scales
  at the spend boundary).

New capabilities (mirror the new contract API):
- arena_withdraw_g — player withdraws own backed G to their wallet (withdraw mode)
- arena_withdraw_surplus — [ADMIN] pull protocol surplus (never user backing)
- arena_get_treasury — view surplus / total backed / mode
- arena_set_mode — [ADMIN] switch faucet (testnet) <-> withdraw (mainnet),
  disabling the exclusive partner first
- arena_submit gains an auto_requeue flag (submit(uint256,bool) overload)
- fund_agent_g now calls the faucet-gated fundAgentG (can't mint unbacked G on
  a backed mainnet) instead of the ungated creditG

ABI + signatures verified against #69's GTreasury/ArenaEngine; tsc clean.
Reviewed via an adversarial multi-agent pass (scaling/precision/ABI/auth/regression).

Deploy note: ship together with #69 (the contract wei migration) — running this
MCP against the pre-migration contract would mis-scale units.
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