Contracts: Manual DOUB podium pool top-up (no platform take) #261

Closed
opened 2026-05-27 13:59:15 +00:00 by PlasticDigits · 15 comments
PlasticDigits commented 2026-05-27 13:59:15 +00:00 (Migrated from gitlab.com)

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. Complements #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) — 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)
  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).

  1. Emits indexer-friendly events (e.g. PodiumPoolsToppedUp(donor, amountDoubWad) plus per-vault PodiumFunded / SeedFunded if reusing #249 events)
  2. 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)
## 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)
PlasticDigits commented 2026-05-27 13:59:16 +00:00 (Migrated from gitlab.com)

marked as related to #238

marked as related to #238
PlasticDigits commented 2026-05-27 13:59:17 +00:00 (Migrated from gitlab.com)

marked as related to #249

marked as related to #249
PlasticDigits commented 2026-05-27 14:33:22 +00:00 (Migrated from gitlab.com)

mentioned in issue #262

mentioned in issue #262
PlasticDigits commented 2026-05-27 14:33:23 +00:00 (Migrated from gitlab.com)

marked as related to #262

marked as related to #262
PlasticDigits commented 2026-05-29 08:10:14 +00:00 (Migrated from gitlab.com)

mentioned in issue #267

mentioned in issue #267
PlasticDigits commented 2026-05-29 08:10:15 +00:00 (Migrated from gitlab.com)

marked as related to #267

marked as related to #267
PlasticDigits commented 2026-05-29 08:47:09 +00:00 (Migrated from gitlab.com)

mentioned in commit 0c9a320299

mentioned in commit 0c9a32029928e602348b1705cefdb0a40a63a64d
PlasticDigits commented 2026-05-29 08:47:23 +00:00 (Migrated from gitlab.com)

Summary

GitLab #261 (topUpPodiumPools) was already implemented on main (shipped with #262 indexer/AUDIT work). This pass verified acceptance, tightened Forge coverage, and cross-linked docs/skills for third-party agents.

Onchain (unchanged behavior):

  • TimeArena.topUpPodiumPools(amountDoubWad) — transferFrom caller, 100% to eight prize vaults via ArenaBuyRouting.splitPrizeTopUpAmount (10:7.5 active:seed per category), no AdminSellVault, no totalDoubRaised / CRED / XP / timer side effects
  • Emits PodiumPoolsToppedUp + PodiumFunded / SeedFunded

Merged to main: 0c9a320 — extra tests + documentation cross-links.

Verification checklist

  • cd contracts && forge test --match-test topUp — all pass (equivalence 700 top-up vs 1000 DOUB buy prize slice, admin vault unchanged on 1000 top-up, allowance revert, event emit)
  • cd contracts && FOUNDRY_PROFILE=ci forge test --match-contract 'ArenaPrizeRouting|TimeArena' — green
  • On deployed dev: approve(TimeArena, amount) then topUpPodiumPools(700e18) — eight pools gain 100e18 active + 75e18 seed per category; admin vault balance unchanged
  • Indexer (post-#262): after top-up tx, GET /v1/arena/podium-pool-donations shows donor + amount; no rows in idx_arena_vault_funding for that tx (buy-only table)
  • Docs: invariants §261, fee-routing § top-up, arena-v2, play-time-arena-doub

@brouie — please verify on your target network when convenient. Leaving issue open until sign-off.

## Summary GitLab **#261** (`topUpPodiumPools`) was already implemented on `main` (shipped with [#262](https://gitlab.com/PlasticDigits/yieldomega/-/issues/262) indexer/AUDIT work). This pass **verified acceptance**, tightened **Forge** coverage, and **cross-linked** docs/skills for third-party agents. **Onchain (unchanged behavior):** - `TimeArena.topUpPodiumPools(amountDoubWad)` — `transferFrom` caller, **100%** to eight prize vaults via `ArenaBuyRouting.splitPrizeTopUpAmount` (10:7.5 active:seed per category), **no** `AdminSellVault`, **no** `totalDoubRaised` / CRED / XP / timer side effects - Emits `PodiumPoolsToppedUp` + `PodiumFunded` / `SeedFunded` **Merged to `main`:** `0c9a320` — extra tests + documentation cross-links. ## Verification checklist - [ ] `cd contracts && forge test --match-test topUp` — all pass (equivalence 700 top-up vs 1000 DOUB buy prize slice, admin vault unchanged on 1000 top-up, allowance revert, event emit) - [ ] `cd contracts && FOUNDRY_PROFILE=ci forge test --match-contract 'ArenaPrizeRouting|TimeArena'` — green - [ ] On deployed dev: `approve(TimeArena, amount)` then `topUpPodiumPools(700e18)` — eight pools gain **100e18 active + 75e18 seed** per category; admin vault balance unchanged - [ ] Indexer (post-#262): after top-up tx, `GET /v1/arena/podium-pool-donations` shows donor + amount; **no** rows in `idx_arena_vault_funding` for that tx (buy-only table) - [ ] Docs: [invariants §261](docs/testing/invariants-and-business-logic.md#arena-podium-pool-topup-gitlab-261), [fee-routing § top-up](docs/onchain/fee-routing-and-governance.md#manual-podium-pool-top-up-gitlab-261), [arena-v2](docs/product/arena-v2.md#manual-podium-pool-top-up-gitlab-261), [play-time-arena-doub](skills/play-time-arena-doub/SKILL.md) @brouie — please verify on your target network when convenient. Leaving issue **open** until sign-off.
PlasticDigits commented 2026-06-01 09:51:19 +00:00 (Migrated from gitlab.com)

mentioned in commit ce5a80fef3

mentioned in commit ce5a80fef34aa0cc1b1bfc29efa0d3fe1243dcd3
PlasticDigits commented 2026-06-01 09:51:40 +00:00 (Migrated from gitlab.com)

Verification pass (issue/261-verify worktree)

Re-verified #261 (TimeArena.topUpPodiumPools) on current main after merging strengthened Forge coverage (ce5a80f).

What was verified (automated)

  • Onchain behavior (unchanged): transferFrom caller only; 100% of DOUB to eight prize vaults via ArenaBuyRouting.splitPrizeTopUpAmount (10:7.5 active:seed per category); zero AdminSellVault; no totalDoubRaised / CRED / XP / timer side effects; emits PodiumPoolsToppedUp + PodiumFunded / SeedFunded.
  • Forge (cd contracts && forge test --match-test topUp): 9/9 pass, including equivalence 700 top-up vs buy prize slice, admin vault unchanged on 1000 top-up, four PodiumFunded/SeedFunded events, failed transferFrom leaves vaults unchanged, and fuzz no-dust.
  • Docs / invariants / play skills: INV-TIME-ARENA-PODIUM-TOPUP, invariants §261, fee-routing § top-up, arena-v2, play-time-arena-doub; bash scripts/check-doc-anchors.sh OK.

Not fully exercised in this agent session

  • scripts/verify-donate-pools-anvil.sh (#262 ingest for PodiumPoolsToppedUp): blocked here by flaky Anvil lifecycle on port 8545. Please run on a clean host.
  • Postgres integration_stage2: requires YIELDOMEGA_PG_TEST_URL (CI).

QA checklist (please confirm on your stack)

  • cd contracts && forge test --match-test topUp — all green
  • bash scripts/verify-donate-pools-anvil.sh — prints verify-donate-pools-anvil: OK
  • On dev Anvil: approve + topUpPodiumPools(700e18) — vault +700e18; per-category 100e18 active + 75e18 seed; admin vault unchanged
  • After top-up tx: GET /v1/arena/podium-pool-donations shows donor + amount; no idx_arena_vault_funding rows for that tx

Status: Leaving open per prior note awaiting operator sign-off on deployed dev + indexer E2E. Requesting verification by the other QA agent team when convenient.

## Verification pass (issue/261-verify worktree) Re-verified **#261** (`TimeArena.topUpPodiumPools`) on current `main` after merging strengthened Forge coverage (**`ce5a80f`**). ### What was verified (automated) - **Onchain behavior** (unchanged): `transferFrom` caller only; **100%** of DOUB to eight prize vaults via `ArenaBuyRouting.splitPrizeTopUpAmount` (10:7.5 active:seed per category); **zero** `AdminSellVault`; no `totalDoubRaised` / CRED / XP / timer side effects; emits `PodiumPoolsToppedUp` + `PodiumFunded` / `SeedFunded`. - **Forge** (`cd contracts && forge test --match-test topUp`): **9/9 pass**, including equivalence 700 top-up vs buy prize slice, admin vault unchanged on 1000 top-up, four PodiumFunded/SeedFunded events, failed transferFrom leaves vaults unchanged, and fuzz no-dust. - **Docs / invariants / play skills:** `INV-TIME-ARENA-PODIUM-TOPUP`, invariants §261, fee-routing § top-up, arena-v2, play-time-arena-doub; `bash scripts/check-doc-anchors.sh` OK. ### Not fully exercised in this agent session - `scripts/verify-donate-pools-anvil.sh` (#262 ingest for PodiumPoolsToppedUp): blocked here by flaky Anvil lifecycle on port 8545. Please run on a clean host. - Postgres `integration_stage2`: requires `YIELDOMEGA_PG_TEST_URL` (CI). ### QA checklist (please confirm on your stack) - [ ] `cd contracts && forge test --match-test topUp` — all green - [ ] `bash scripts/verify-donate-pools-anvil.sh` — prints `verify-donate-pools-anvil: OK` - [ ] On dev Anvil: approve + topUpPodiumPools(700e18) — vault +700e18; per-category 100e18 active + 75e18 seed; admin vault unchanged - [ ] After top-up tx: GET /v1/arena/podium-pool-donations shows donor + amount; no idx_arena_vault_funding rows for that tx **Status:** Leaving **open** per prior note awaiting operator sign-off on deployed dev + indexer E2E. Requesting verification by the other QA agent team when convenient.
PlasticDigits commented 2026-06-01 10:51:06 +00:00 (Migrated from gitlab.com)

mentioned in commit ef5ed6cf4c

mentioned in commit ef5ed6cf4c7a6026c6b18422cbc43487a14ba505
PlasticDigits commented 2026-06-01 10:51:34 +00:00 (Migrated from gitlab.com)

mentioned in issue #260

mentioned in issue #260
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-01 11:01:47 +00:00
PlasticDigits commented 2026-06-07 07:50:09 +00:00 (Migrated from gitlab.com)

mentioned in issue #300

mentioned in issue #300
PlasticDigits commented 2026-06-07 09:56:02 +00:00 (Migrated from gitlab.com)

mentioned in merge request !440

mentioned in merge request !440
PlasticDigits commented 2026-06-25 14:28:39 +00:00 (Migrated from gitlab.com)

mentioned in issue #342

mentioned in issue #342
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
code/yieldomega#261
No description provided.