feat(arena): G full-backing withdraw + wei units + opt-in auto-requeue (contracts)#69
Conversation
…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.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…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.
|
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).
External ABI unchanged (simulateMatch/getInitialStats/settleMatch/previewEloUpdate same signatures) → no MCP/frontend changes. Foundry auto-deploys + links the library on |
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.
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.
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
Ginto 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.totalOutstandingGaccumulator; invariantbalance ≥ totalOutstandingG(every gBalance unit backed 1:1 by native G).withdraw(agentId, amount)— only the agent owner, only to their own wallet (symmetric withdepositG),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).spendGfloorstotalOutstandingGat 0 instead of underflow-reverting → an in-place upgrade of the existing testnet proxy (staletotalOutstandingG = 0while agents holdgBalance) doesn't brick every spend.2. Wei unit migration + opt-in auto-requeue (
ArenaEngine)WEI_PER_G = 1e18; buy/roll spend andbootstrapMarketscale 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.bootstrapMarketfaucet-gated.submit(uint256, bool requeueOnSettle)overload +autoRequeuemapping.settleMatchre-queues opted-in ghosts (tier re-snapshotted from current G) instead of clearing, so the pool doesn't drain between rounds. Defaultsubmit()stays one-shot. Pool-full / empty-bench fall back to clear so settle never reverts.Tests
forge testagainst 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 onmainunchanged; 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 inArenaTier.t.sol. Existing arena suites migrated to wei.Deploy / upgrade notes (for whoever ships this)
setFaucetEnabled(false)→setWithdrawEnabled(true); do not runbootstrapMarket.initialize()does not re-run, so the newfaucetEnabledreads itsfalsedefault — owner must callsetFaucetEnabled(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:
arenaDepositG/fund_agent_gby1e18; read gBalance as BigNumber/string (Number() loses precision at wei scale); addarena_withdraw/arena_withdraw_surplus/ surplus views; exposesubmit(uint256,bool)requeue flag inarena_submit.SeedArena.s.sol/ArenaTierDemo.s.solfundAgentGamounts ×WEI_PER_G; mainnet deploy path (mode flags, skip bootstrap).Should / follow-up: frontend gBalance type → string +
formatUnitsdisplay, withdraw/requeue UI, doc/skill staleness ("G not withdrawable" / "1 wei = 1 G").