Contracts: Manual DOUB podium pool top-up (no platform take)
## Context Arena v2 buy routing ([GitLab #249](https://gitlab.com/PlasticDigits/yieldomega/-/work_items/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](https://gitlab.com/PlasticDigits/yieldomega/-/work_items/238). Complements [#249](https://gitlab.com/PlasticDigits/yieldomega/-/work_items/249) (shared `PodiumVaults` / routing helpers). ## Relevant files - `contracts/src/TimeArena.sol` — new external entrypoint - `contracts/src/PodiumVaults.sol` (or prize-routing module from #249) - `contracts/src/Doubloon.sol` — `transferFrom` / allowance - `docs/product/time-arena.md` (spec [#240](https://gitlab.com/PlasticDigits/yieldomega/-/work_items/240)) — document donor economics - `indexer/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: 1. **`require(amountDoubWad > 0)`** 2. Pulls DOUB from **`msg.sender`** via **`IERC20.transferFrom(msg.sender, …, amountDoubWad)`** (balance-delta parity per [#123](https://gitlab.com/PlasticDigits/yieldomega/-/work_items/123)) 3. Allocates **100%** of `amountDoubWad` across the **eight** prize vaults using the **same per-category ratios as buys**, but **no** `AdminSellVault` slice: | 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). **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). 4. Emits indexer-friendly events (e.g. `PodiumPoolsToppedUp(donor, amountDoubWad)` plus per-vault `PodiumFunded` / `SeedFunded` if reusing #249 events) 5. **No** minting of CHARM, CRED, or XP; **no** timer extension; **no** `totalRaised` increment unless product explicitly wants gross accounting (default: **do not** count toward buy stats) ## Acceptance criteria - [ ] `topUpPodiumPools(amount)` pulls DOUB only from `msg.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) - [ ] `nonReentrant` if sharing vault code with `buy` - [ ] 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)` → `AdminSellVault` balance unchanged - [ ] Forge: fuzz amounts; sum of vault deltas == donated amount (no dust loss beyond documented remainder rule) - [ ] Forge: `transferFrom` failure reverts with no vault mutation - [ ] Indexer: migration + decode row for new event (can be follow-up linked issue if preferred)
issue