Contracts: Manual DOUB podium pool top-up (no platform take)
Context
Arena v2 buy routing (GitLab #249) sends 70% of gross DOUB to prizes (10% → each active podium pool, 7.5% → each next-round seed pool) and 30% to AdminSellVault. Operators and participants also need a permissionless manual top-up path that boosts prize liquidity without the platform take.
Parent epic: #238 (closed). Complements #249 (closed) (shared PodiumVaults / routing helpers).
Relevant files
contracts/src/TimeArena.sol— new external entrypointcontracts/src/PodiumVaults.sol(or prize-routing module from #249 (closed))contracts/src/Doubloon.sol—transferFrom/ allowancedocs/product/time-arena.md(spec #240 (closed)) — document donor economicsindexer/src/decoder.rs— ingest new event(s)- Optional later: protocol / arena UI donate CTA (not blocking for this issue)
Behavior
Add a public function (e.g. topUpPodiumPools(uint256 amountDoubWad)) that:
require(amountDoubWad > 0)- Pulls DOUB from
msg.senderviaIERC20.transferFrom(msg.sender, …, amountDoubWad)(balance-delta parity per #123 (closed)) - Allocates 100% of
amountDoubWadacross the eight prize vaults using the same per-category ratios as buys, but noAdminSellVaultslice:
| Destination (per podium category) | Share of top-up amount |
|---|---|
| Active podium pool | 10 / 70 (≈ 14.29%) |
| Next-round seed pool | 7.5 / 70 (≈ 10.71%) |
Applied independently for Last Buy, Streak, Time Booster, and WarBow (four categories × two vaults). Integer rounding: document remainder policy (recommend: last vault or largest active pool — match #249 (closed)).
Equivalence: A manual top-up of 700 DOUB must land the same per-vault increments as the prize portion of a 1000 DOUB buy (100 active + 75 seed per category).
- Emits indexer-friendly events (e.g.
PodiumPoolsToppedUp(donor, amountDoubWad)plus per-vaultPodiumFunded/SeedFundedif reusing #249 (closed) events) - No minting of CHARM, CRED, or XP; no timer extension; no
totalRaisedincrement unless product explicitly wants gross accounting (default: do not count toward buy stats)
Acceptance criteria
-
topUpPodiumPools(amount)pulls DOUB only frommsg.sender(transferFrom); reverts without allowance/balance - Zero DOUB from this path goes to
AdminSellVault(no 30% platform take) - Per-category split matches buy prize ratios (10% : 7.5% active:seed), normalized over 100% of donated amount
- Reuses shared internal routing with buy path where possible (single source of truth for bps)
- Distinct event(s) emitted for indexer / wallet stats (donor address + amount)
-
nonReentrantif sharing vault code withbuy - Spec note in
docs/product/time-arena.md: manual top-up is voluntary prize sponsorship only
Verification checklist
- Forge: approve +
topUpPodiumPools(700e18)→ each active pool +100e18, each seed +75e18 (four categories) - Forge:
topUpPodiumPools(1000e18)→AdminSellVaultbalance unchanged - Forge: fuzz amounts; sum of vault deltas == donated amount (no dust loss beyond documented remainder rule)
- Forge:
transferFromfailure reverts with no vault mutation - Indexer: migration + decode row for new event (can be follow-up linked issue if preferred)