Arena: epoch-anchored TWAP charm price +10%/day growth; re-anchor on Last Buy reset #305

Closed
opened 2026-06-10 09:09:13 +00:00 by PlasticDigits · 19 comments
PlasticDigits commented 2026-06-10 09:09:13 +00:00 (Migrated from gitlab.com)

Summary

Replace the flat TimeArena.charmPriceWad (governance/TWAP-at-deploy only) with Last Buy epoch–scoped DOUB pricing:

  1. Anchor charmPriceWad from Kumbaya 15m TWAP at each Last Buy epoch start (hard reset → LastBuyEpochStarted).
  2. Grow the anchored price +10% per day continuously (per-second exponential), independent of spot/TWAP until the next Last Buy epoch reset.
  3. Re-anchor only on Last Buy hard reset: sample fresh TWAP, lock new epoch anchor (e.g. market moved from 30k → 10k DOUB/CHARM — epoch N+1 starts at 10k and grows from there).
  4. CRED buys unchanged: CRED_PER_CHARM_WAD = 100e18 forever — only DOUB buy / router path uses the growing epoch price.
  5. Audit / protocol page: per–Last Buy epoch history (anchor TWAP, epoch start time, growth curve params, effective price at epoch end).

Supersedes the “flat admin rate” semantics documented in #303 for live pricing (TWAP remains the epoch anchor source, not a continuous updater). CHARM envelope 0.99–10 stays fixed (#246).


Motivation / product examples

Moment Market TWAP ($1 ≈ DOUB) Effective DOUB/CHARM
Epoch 0 launch 30k DOUB 30,000 (anchor)
+1 day (no reset) 25k or 40k (irrelevant) 33,000 (+10%)
+2 days anything 36,300 (+10% compounded daily)
Last Buy hard reset 10k DOUB 10,000 (new anchor)
+1 day in epoch 1 anything 11,000

buyWithCred always burns 100 CRED × charmWad / 1e18 — no epoch anchor, no growth.


Acceptance criteria

Onchain (TimeArena)

  • AC-1: effectiveCharmPriceWad() (or equivalent view) returns the current DOUB wei per 1e18 CHARM for DOUB buys, incorporating epoch anchor + elapsed growth.
  • AC-2: At arena start / epoch 0, anchor = ArenaCharmPriceTwap.compute(...).charmPriceWad (production) or Anvil spot parity (#303); growth elapsed = block.timestamp - epochAnchorTimestamp.
  • AC-3: On Last Buy hard reset, re-sample TWAP (or Anvil spot), set new anchor for the incoming epoch, reset epochAnchorTimestamp = block.timestamp, increment lastBuyEpoch, emit auditable event (e.g. LastBuyEpochCharmAnchored(epoch, anchorWad, doubUsdWad, twapBlock)). The triggering buy is priced at the post-reset anchor (see Design notes).
  • AC-4: Growth rate = 10%/day continuous: anchor × exp(ln(1.1) × elapsed / 86400) (WAD math; reuse/extend TimeMath + prb-math like retired v1 currentMinBuy).
  • AC-5: Between epoch resets, TWAP/spot moves do not change effectiveCharmPriceWad except via elapsed time.
  • AC-6: buy / buyFor / router DOUB pulls use effectiveCharmPriceWad(), not a flat storage slot.
  • AC-7: buyWithCred still uses CRED_PER_CHARM_WAD only; no DOUB price reads; behavior unchanged (#268).
  • AC-8: Fixed CHARM band 0.99–10 unchanged; min/max DOUB spend = band × effectiveCharmPriceWad().
  • AC-9: Deprecate or restrict setCharmPriceWad: either remove, gate to epoch-0 bootstrap only, or document as emergency break-glass with epoch re-anchor semantics — no silent flat override during an epoch.
  • AC-10: Fail-closed TWAP on production re-anchor (cardinality, spot/TWAP deviation, USD band) — same guards as ArenaCharmPriceTwap (#303).
  • AC-10b: Buy that triggers hard reset pulls DOUB at new epoch anchor, not pre-reset grown price (forge test with mocked TWAP step-change).

Indexer

  • AC-11: Persist per-epoch anchor metadata (anchor wad, optional doub_usd_wad, block/time, tx) — extend idx_arena_last_buy_epoch_started or sibling table keyed by epoch.
  • AC-12: GET /v1/arena/timers (or new /v1/arena/epoch-charm-pricing) exposes effective charm_price_wad at head + current epoch anchor + epoch_started_sec.
  • AC-13: Audit API: list all Last Buy epochs with anchor, start time, deadline, and computed end-of-epoch price (or last buy price before next reset).
  • AC-14: Indexed buys store last_buy_epoch (already) and indexer can attribute buys to epoch pricing for audit charts.

Frontend (protocol / audit page)

  • AC-15: ArenaProtocolPage shows current effective DOUB/CHARM, epoch anchor, epoch age, and projected +10%/day (indexer-first when VITE_INDEXER_URL set — #301).
  • AC-16: Per-epoch table on audit page: epoch #, start time, TWAP anchor (DOUB/CHARM), end effective price (if epoch closed), reset tx link.
  • AC-17: Buy UX (useArenaSaleSession, submit sizing, envelope displays) uses effective price, not flat charmPriceWad storage read.
  • AC-18: CRED pay mode UI still shows 100 CRED / CHARM — no epoch pricing copy.

Docs / invariants


Verification checklist

Layer Command / action Expected
Contracts cd contracts && forge test --match-contract TimeArena Epoch anchor, growth, buy pull parity, CRED unchanged
Contracts forge test --match-contract ArenaCharmPriceTwap TWAP anchor math unchanged
Contracts New fuzz: effective price monotonic in time within epoch; step-down/up only at reset PASS
Anvil bash scripts/verify-anvil-kumbaya-charm-price.sh Epoch-0 anchor in expected band
Anvil Warp +1 days, cast call effectiveCharmPriceWad ≈ anchor × 1.1 (WAD tolerance)
Anvil Trigger Last Buy hard reset, warp pools / mock TWAP New anchor from fresh sample; reset tx DOUB pull = new anchor × charmWad
Indexer cd indexer && cargo clippy --all-targets -- -D warnings && cargo test Migration + API + ingest
Indexer bash scripts/verify-podium-live-anvil.sh (or wallet-profile) charm_price_wad in timers = effective
Frontend cd frontend && npm run typecheck && npm run lint && npm test Bridge + protocol page + sizing
E2E bash scripts/e2e-anvil.sh (arena buy paths) DOUB + CRED buys still pass
Manual /arena/protocol audit epoch table Matches onchain after reset

Files likely to change

Contracts

Indexer

Frontend

Scripts / docs


Phase 1 — Onchain core (single PR)

  1. Storage model (minimal):

    uint256 public epochCharmAnchorWad;      // DOUB per 1 CHARM at epoch start
    uint256 public epochAnchorTimestamp;     // unix sec when current epoch anchor was set
    uint256 public constant CHARM_GROWTH_RATE_WAD = 95_310_179_804_352_786; // ln(1.1) in WAD (~10%/day)
    

    Keep lastBuyEpoch as today (#278).

  2. effectiveCharmPriceWad() — pure/view:
    return TimeMath.growWad(epochCharmAnchorWad, CHARM_GROWTH_RATE_WAD, block.timestamp - epochAnchorTimestamp);

  3. Initialize epoch 0 in initialize / deploy: TWAP → epochCharmAnchorWad, epochAnchorTimestamp = block.timestamp (or arenaStart).

  4. Hard reset + pricing order — refactor buy path so reset-triggering buys use post-reset anchor:

    • Today _buyDoub prices then calls _finishBuy (timer). Invert or split: detect imminent hard reset (remaining < threshold), run TWAP re-anchor + lastBuyEpoch += 1 before doubOwed = charmWad × effectiveCharmPriceWad().
    • On hard reset: twap = ArenaCharmPriceTwap.compute(...) (or Anvil spot adapter) → epochCharmAnchorWad = twap.charmPriceWad → epochAnchorTimestamp = block.timestamp → lastBuyEpoch += 1 → emit LastBuyEpochCharmAnchored → price pull at ~zero elapsed (new anchor).
    • Non-reset buys: effectiveCharmPriceWad() from current anchor + elapsed as usual.
  5. Replace flat charmPriceWad reads in DOUB buy paths with effectiveCharmPriceWad(). Leave charmPriceWad as deprecated alias or remove and migrate ABI.

  6. TWAP on-chain at reset: Production must call pools via ArenaCharmPriceTwap (gas acceptable on reset-only). Anvil: AnvilKumbayaPools.charmPriceWadFromSpot behind chainid == 31337 or fixture router address check.

  7. Tests: unit growth math; integration buy at T0 and T0+1day; reset re-anchors; CRED buy ignores growth; fuzz charm band × effective price.

Phase 2 — Indexer + API

  1. Decode LastBuyEpochCharmAnchored (or extend LastBuyEpochStarted with indexed anchor fields if gas-sensitive).
  2. Migration: anchor_charm_price_wad, doub_usd_wad, anchor_block_number on epoch table.
  3. Head poller: eth_call effectiveCharmPriceWad() at read_block_number → charm_price_wad in /v1/arena/timers.
  4. GET /v1/arena/last-buy-epoch-pricing for audit table.

Phase 3 — Frontend audit + buy UX

  1. Protocol page epoch table consuming new API.
  2. Sale session + submit sizing: read effective price (indexer-first, wallet RPC fallback for submit only).
  3. Update envelope / live-buy displays to label “epoch price (grows 10%/day)”.

Design notes / edge cases

  • Ordering (canonical): The buy that triggers Last Buy hard reset is priced at the post-reset TWAP anchor (fresh sample, ~zero growth elapsed). Re-anchor + epoch increment happen before doubOwed is computed on that tx. All later buys in the epoch continue growing from that anchor.
  • Governance: Prefer removing public setCharmPriceWad in favor of epoch anchors; if kept, only owner + timelock + event.
  • Indexer reindex: Schema bump + redeploy from deployBlock for epoch anchor history.
  • Bots: bots/timearena sale snapshot should read effectiveCharmPriceWad.

  • #303 — TWAP anchor source (closed; this issue makes TWAP epoch-bound, not one-shot deploy)
  • #246 — CHARM band / DOUB pull parity
  • #278 — idx_arena_last_buy_epoch_started
  • #268 — CRED per CHARM flat
  • #301 — indexer-first display

Labels

contracts, frontend, indexer, time-arena, operations

## Summary Replace the **flat** `TimeArena.charmPriceWad` (governance/TWAP-at-deploy only) with **Last Buy epoch–scoped DOUB pricing**: 1. **Anchor** `charmPriceWad` from **Kumbaya 15m TWAP** at each **Last Buy epoch** start (hard reset → `LastBuyEpochStarted`). 2. **Grow** the anchored price **+10% per day** continuously (**per-second** exponential), **independent of spot/TWAP** until the next Last Buy epoch reset. 3. **Re-anchor** only on Last Buy hard reset: sample fresh TWAP, lock new epoch anchor (e.g. market moved from 30k → 10k DOUB/CHARM — epoch N+1 starts at 10k and grows from there). 4. **CRED buys unchanged:** `CRED_PER_CHARM_WAD = 100e18` forever — only DOUB `buy` / router path uses the growing epoch price. 5. **Audit / protocol page:** per–Last Buy epoch history (anchor TWAP, epoch start time, growth curve params, effective price at epoch end). Supersedes the “flat admin rate” semantics documented in [#303](https://gitlab.com/PlasticDigits/yieldomega/-/issues/303) for **live** pricing (TWAP remains the **epoch anchor** source, not a continuous updater). CHARM envelope **0.99–10** stays fixed ([#246](https://gitlab.com/PlasticDigits/yieldomega/-/issues/246)). --- ## Motivation / product examples | Moment | Market TWAP ($1 ≈ DOUB) | Effective DOUB/CHARM | |--------|-------------------------|----------------------| | Epoch 0 launch | 30k DOUB | **30,000** (anchor) | | +1 day (no reset) | 25k or 40k (irrelevant) | **33,000** (+10%) | | +2 days | anything | **36,300** (+10% compounded daily) | | Last Buy hard reset | 10k DOUB | **10,000** (new anchor) | | +1 day in epoch 1 | anything | **11,000** | `buyWithCred` always burns **100 CRED × charmWad / 1e18** — no epoch anchor, no growth. --- ## Acceptance criteria ### Onchain (`TimeArena`) - [ ] **AC-1:** `effectiveCharmPriceWad()` (or equivalent view) returns the **current** DOUB wei per 1e18 CHARM for DOUB buys, incorporating epoch anchor + elapsed growth. - [ ] **AC-2:** At **arena start / epoch 0**, anchor = `ArenaCharmPriceTwap.compute(...).charmPriceWad` (production) or Anvil spot parity ([#303](https://gitlab.com/PlasticDigits/yieldomega/-/issues/303)); growth elapsed = `block.timestamp - epochAnchorTimestamp`. - [ ] **AC-3:** On **Last Buy hard reset**, re-sample TWAP (or Anvil spot), set **new anchor** for the **incoming** epoch, reset `epochAnchorTimestamp = block.timestamp`, increment `lastBuyEpoch`, emit auditable event (e.g. `LastBuyEpochCharmAnchored(epoch, anchorWad, doubUsdWad, twapBlock)`). The **triggering buy** is priced at the **post-reset** anchor (see Design notes). - [ ] **AC-4:** Growth rate = **10%/day** continuous: `anchor × exp(ln(1.1) × elapsed / 86400)` (WAD math; reuse/extend `TimeMath` + prb-math like retired v1 `currentMinBuy`). - [ ] **AC-5:** Between epoch resets, **TWAP/spot moves do not change** `effectiveCharmPriceWad` except via elapsed time. - [ ] **AC-6:** `buy` / `buyFor` / router DOUB pulls use **`effectiveCharmPriceWad()`**, not a flat storage slot. - [ ] **AC-7:** `buyWithCred` still uses **`CRED_PER_CHARM_WAD`** only; **no** DOUB price reads; behavior unchanged ([#268](https://gitlab.com/PlasticDigits/yieldomega/-/issues/268)). - [ ] **AC-8:** Fixed CHARM band **0.99–10** unchanged; min/max DOUB spend = band × `effectiveCharmPriceWad()`. - [ ] **AC-9:** Deprecate or restrict **`setCharmPriceWad`**: either remove, gate to epoch-0 bootstrap only, or document as emergency break-glass with epoch re-anchor semantics — **no silent flat override** during an epoch. - [ ] **AC-10:** Fail-closed TWAP on production re-anchor (cardinality, spot/TWAP deviation, USD band) — same guards as `ArenaCharmPriceTwap` ([#303](https://gitlab.com/PlasticDigits/yieldomega/-/issues/303)). - [ ] **AC-10b:** Buy that triggers hard reset pulls DOUB at **new epoch anchor**, not pre-reset grown price (forge test with mocked TWAP step-change). ### Indexer - [ ] **AC-11:** Persist per-epoch anchor metadata (anchor wad, optional `doub_usd_wad`, block/time, tx) — extend `idx_arena_last_buy_epoch_started` or sibling table keyed by `epoch`. - [ ] **AC-12:** `GET /v1/arena/timers` (or new `/v1/arena/epoch-charm-pricing`) exposes **effective** `charm_price_wad` at head + **current epoch anchor** + `epoch_started_sec`. - [ ] **AC-13:** Audit API: list all Last Buy epochs with anchor, start time, deadline, and computed end-of-epoch price (or last buy price before next reset). - [ ] **AC-14:** Indexed buys store `last_buy_epoch` (already) and indexer can attribute buys to epoch pricing for audit charts. ### Frontend (protocol / audit page) - [ ] **AC-15:** [`ArenaProtocolPage`](../../frontend/src/pages/arena/ArenaProtocolPage.tsx) shows **current effective DOUB/CHARM**, **epoch anchor**, **epoch age**, and **projected +10%/day** (indexer-first when `VITE_INDEXER_URL` set — [#301](https://gitlab.com/PlasticDigits/yieldomega/-/issues/301)). - [ ] **AC-16:** Per-epoch table on audit page: epoch #, start time, TWAP anchor (DOUB/CHARM), end effective price (if epoch closed), reset tx link. - [ ] **AC-17:** Buy UX (`useArenaSaleSession`, submit sizing, envelope displays) uses **effective** price, not flat `charmPriceWad` storage read. - [ ] **AC-18:** CRED pay mode UI still shows **100 CRED / CHARM** — no epoch pricing copy. ### Docs / invariants - [ ] **AC-19:** Update [`docs/product/time-arena.md`](../../docs/product/time-arena.md), [`arena-v2.md`](../../docs/product/arena-v2.md), [`PARAMETERS.md`](../../contracts/PARAMETERS.md), [`invariants-and-business-logic.md`](../../docs/testing/invariants-and-business-logic.md) — replace flat-price invariant with epoch-anchor + 10%/day growth. - [ ] **AC-20:** New invariant IDs e.g. `INV-TIME-ARENA-EPOCH-CHARM-GROWTH`, `INV-TIME-ARENA-CRED-FLAT`. --- ## Verification checklist | Layer | Command / action | Expected | |-------|------------------|----------| | Contracts | `cd contracts && forge test --match-contract TimeArena` | Epoch anchor, growth, buy pull parity, CRED unchanged | | Contracts | `forge test --match-contract ArenaCharmPriceTwap` | TWAP anchor math unchanged | | Contracts | New fuzz: effective price monotonic in time within epoch; step-down/up only at reset | PASS | | Anvil | `bash scripts/verify-anvil-kumbaya-charm-price.sh` | Epoch-0 anchor in expected band | | Anvil | Warp +1 days, `cast call effectiveCharmPriceWad` | ≈ anchor × 1.1 (WAD tolerance) | | Anvil | Trigger Last Buy hard reset, warp pools / mock TWAP | New anchor from fresh sample; **reset tx** DOUB pull = new anchor × charmWad | | Indexer | `cd indexer && cargo clippy --all-targets -- -D warnings && cargo test` | Migration + API + ingest | | Indexer | `bash scripts/verify-podium-live-anvil.sh` (or wallet-profile) | `charm_price_wad` in timers = effective | | Frontend | `cd frontend && npm run typecheck && npm run lint && npm test` | Bridge + protocol page + sizing | | E2E | `bash scripts/e2e-anvil.sh` (arena buy paths) | DOUB + CRED buys still pass | | Manual | `/arena/protocol` audit epoch table | Matches onchain after reset | --- ## Files likely to change ### Contracts - [`contracts/src/arena/TimeArena.sol`](../../contracts/src/arena/TimeArena.sol) — storage, `_finishBuy` hard-reset hook, `effectiveCharmPriceWad`, buy path - [`contracts/src/libraries/TimeMath.sol`](../../contracts/src/libraries/TimeMath.sol) — `GROWTH_RATE_10PCT_WAD`, `growWad(anchor, elapsed)` helper - [`contracts/src/oracle/ArenaCharmPriceTwap.sol`](../../contracts/src/oracle/ArenaCharmPriceTwap.sol) — callable anchor read (may stay library-only) - [`contracts/script/DeployProduction.s.sol`](../../contracts/script/DeployProduction.s.sol) — init epoch 0 anchor - [`contracts/script/DeployKumbayaAnvilFixtures.s.sol`](../../contracts/script/DeployKumbayaAnvilFixtures.s.sol) — Anvil anchor + drop flat `setCharmPriceWad` if obsolete - [`contracts/test/TimeArena.t.sol`](../../contracts/test/TimeArena.t.sol), new `TimeArenaEpochCharmPrice.t.sol` - [`contracts/PARAMETERS.md`](../../contracts/PARAMETERS.md) ### Indexer - `indexer/migrations/*_arena_epoch_charm_price.up.sql` — anchor columns / table - [`indexer/src/decoder.rs`](../../indexer/src/decoder.rs), [`persist.rs`](../../indexer/src/persist.rs) — new event - [`indexer/src/chain_timer.rs`](../../indexer/src/chain_timer.rs) — effective price at head block - [`indexer/src/api_arena.rs`](../../indexer/src/api_arena.rs) — timers + epoch audit route - [`indexer/tests/integration_stage2.rs`](../../indexer/tests/integration_stage2.rs) ### Frontend - [`frontend/src/pages/arena/ArenaProtocolPage.tsx`](../../frontend/src/pages/arena/ArenaProtocolPage.tsx) — epoch pricing audit section - [`frontend/src/pages/arena/useArenaSaleSession.ts`](../../frontend/src/pages/arena/useArenaSaleSession.ts) - [`frontend/src/pages/arena/arenaV2SaleSessionBridge.ts`](../../frontend/src/pages/arena/arenaV2SaleSessionBridge.ts) - [`frontend/src/lib/timeArenaBuySubmitSizing.ts`](../../frontend/src/lib/timeArenaBuySubmitSizing.ts) - [`frontend/src/lib/indexerApi.ts`](../../frontend/src/lib/indexerApi.ts) - [`frontend/src/lib/abis.ts`](../../frontend/src/lib/abis.ts) / regenerated `TimeArena.json` ### Scripts / docs - [`scripts/verify-anvil-kumbaya-charm-price.sh`](../../scripts/verify-anvil-kumbaya-charm-price.sh) - [`docs/product/time-arena.md`](../../docs/product/time-arena.md), [`docs/product/arena-v2.md`](../../docs/product/arena-v2.md) - [`docs/testing/invariants-and-business-logic.md`](../../docs/testing/invariants-and-business-logic.md) --- ## Recommended implementation strategy ### Phase 1 — Onchain core (single PR) 1. **Storage model** (minimal): ```solidity uint256 public epochCharmAnchorWad; // DOUB per 1 CHARM at epoch start uint256 public epochAnchorTimestamp; // unix sec when current epoch anchor was set uint256 public constant CHARM_GROWTH_RATE_WAD = 95_310_179_804_352_786; // ln(1.1) in WAD (~10%/day) ``` Keep `lastBuyEpoch` as today ([#278](https://gitlab.com/PlasticDigits/yieldomega/-/issues/278)). 2. **`effectiveCharmPriceWad()`** — pure/view: `return TimeMath.growWad(epochCharmAnchorWad, CHARM_GROWTH_RATE_WAD, block.timestamp - epochAnchorTimestamp);` 3. **Initialize** epoch 0 in `initialize` / deploy: TWAP → `epochCharmAnchorWad`, `epochAnchorTimestamp = block.timestamp` (or `arenaStart`). 4. **Hard reset + pricing order** — refactor buy path so reset-triggering buys use **post-reset** anchor: - Today `_buyDoub` prices **then** calls `_finishBuy` (timer). Invert or split: detect imminent hard reset (remaining &lt; threshold), run TWAP re-anchor + `lastBuyEpoch += 1` **before** `doubOwed = charmWad × effectiveCharmPriceWad()`. - On hard reset: `twap = ArenaCharmPriceTwap.compute(...)` (or Anvil spot adapter) → `epochCharmAnchorWad = twap.charmPriceWad` → `epochAnchorTimestamp = block.timestamp` → `lastBuyEpoch += 1` → emit `LastBuyEpochCharmAnchored` → price pull at ~zero elapsed (new anchor). - Non-reset buys: `effectiveCharmPriceWad()` from current anchor + elapsed as usual. 5. **Replace** flat `charmPriceWad` reads in DOUB buy paths with `effectiveCharmPriceWad()`. Leave `charmPriceWad` as deprecated alias **or** remove and migrate ABI. 6. **TWAP on-chain at reset:** Production must call pools via `ArenaCharmPriceTwap` (gas acceptable on reset-only). Anvil: `AnvilKumbayaPools.charmPriceWadFromSpot` behind `chainid == 31337` or fixture router address check. 7. **Tests:** unit growth math; integration buy at T0 and T0+1day; reset re-anchors; CRED buy ignores growth; fuzz charm band × effective price. ### Phase 2 — Indexer + API 1. Decode `LastBuyEpochCharmAnchored` (or extend `LastBuyEpochStarted` with indexed anchor fields if gas-sensitive). 2. Migration: `anchor_charm_price_wad`, `doub_usd_wad`, `anchor_block_number` on epoch table. 3. Head poller: eth_call `effectiveCharmPriceWad()` at `read_block_number` → `charm_price_wad` in `/v1/arena/timers`. 4. `GET /v1/arena/last-buy-epoch-pricing` for audit table. ### Phase 3 — Frontend audit + buy UX 1. Protocol page epoch table consuming new API. 2. Sale session + submit sizing: read effective price (indexer-first, wallet RPC fallback for submit only). 3. Update envelope / live-buy displays to label “epoch price (grows 10%/day)”. ### Design notes / edge cases - **Ordering (canonical):** The buy that **triggers** Last Buy hard reset is priced at the **post-reset** TWAP anchor (fresh sample, ~zero growth elapsed). Re-anchor + epoch increment happen **before** `doubOwed` is computed on that tx. All later buys in the epoch continue growing from that anchor. - **Governance:** Prefer removing public `setCharmPriceWad` in favor of epoch anchors; if kept, only owner + timelock + event. - **Indexer reindex:** Schema bump + redeploy from `deployBlock` for epoch anchor history. - **Bots:** `bots/timearena` sale snapshot should read `effectiveCharmPriceWad`. --- ## Related issues - [#303](https://gitlab.com/PlasticDigits/yieldomega/-/issues/303) — TWAP anchor source (closed; this issue makes TWAP **epoch-bound**, not one-shot deploy) - [#246](https://gitlab.com/PlasticDigits/yieldomega/-/issues/246) — CHARM band / DOUB pull parity - [#278](https://gitlab.com/PlasticDigits/yieldomega/-/issues/278) — `idx_arena_last_buy_epoch_started` - [#268](https://gitlab.com/PlasticDigits/yieldomega/-/issues/268) — CRED per CHARM flat - [#301](https://gitlab.com/PlasticDigits/yieldomega/-/issues/301) — indexer-first display --- ## Labels `contracts`, `frontend`, `indexer`, `time-arena`, `operations`
PlasticDigits commented 2026-06-10 09:09:15 +00:00 (Migrated from gitlab.com)

marked as related to #303

marked as related to #303
PlasticDigits commented 2026-06-10 09:09:16 +00:00 (Migrated from gitlab.com)

marked as related to #246

marked as related to #246
PlasticDigits commented 2026-06-10 09:09:16 +00:00 (Migrated from gitlab.com)

marked as related to #278

marked as related to #278
PlasticDigits commented 2026-06-10 09:09:17 +00:00 (Migrated from gitlab.com)

marked as related to #268

marked as related to #268
PlasticDigits commented 2026-06-10 09:12:28 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
PlasticDigits commented 2026-06-10 09:36:23 +00:00 (Migrated from gitlab.com)

mentioned in commit fc4fc99173

mentioned in commit fc4fc99173ecf8ab9de6f3f37f25a081449541fa
PlasticDigits commented 2026-06-10 09:36:33 +00:00 (Migrated from gitlab.com)

mentioned in merge request !453

mentioned in merge request !453
PlasticDigits commented 2026-06-10 09:36:34 +00:00 (Migrated from gitlab.com)

Implementation summary (MR !453)

Epoch-anchored DOUB/CHARM pricing with +10%/day growth until Last Buy hard reset.

Onchain

  • effectiveCharmPriceWad(), epochCharmAnchorWad, epochAnchorTimestamp
  • Hard-reset buy: TWAP/Anvil spot re-anchor + LastBuyEpochCharmAnchored before DOUB pull
  • TimeArenaBuyRouter uses effective price; buyWithCred unchanged
  • Tests: TimeArenaEpochCharmPrice.t.sol

Indexer

  • Head poller: effectiveCharmPriceWad() + anchor fields in /v1/arena/timers
  • Decode/persist LastBuyEpochCharmAnchored; migration 20260610120000_idx_arena_epoch_charm_price_gl305
  • New GET /v1/arena/last-buy-epoch-pricing

Frontend

  • RPC fallback + submit sizing + Kumbaya quoter → effectiveCharmPriceWad
  • Protocol page copy updated; ABI regenerated

Docs / invariants: INV-TIME-ARENA-EPOCH-CHARM-GROWTH, INV-TIME-ARENA-CRED-FLAT, time-arena.md, skills/play-time-arena-doub/SKILL.md

MR: https://gitlab.com/PlasticDigits/yieldomega/-/merge_requests/83

Verification checklist

  • cd contracts && forge test --match-contract TimeArenaEpochCharmPrice -vv
  • cd contracts && forge test --match-contract TimeArenaTest
  • cd indexer && cargo clippy --all-targets -- -D warnings && cargo test
  • cd frontend && npm run typecheck && npm test
  • Anvil: bash scripts/verify-anvil-kumbaya-charm-price.sh (epoch-0 anchor)
  • Warp +1 day → cast call TimeArena effectiveCharmPriceWad() ≈ anchor × 1.1
  • Trigger Last Buy hard reset → reset tx DOUB pull uses new anchor (not pre-reset grown price)
  • GET /v1/arena/timers — charm_price_wad matches onchain effective; anchor fields present
  • /arena/protocol — effective DOUB/CHARM label/copy
  • CRED buy still 100 CRED/CHARM (no epoch pricing in UI)

Follow-up: Full per-epoch audit table UI on /arena/protocol consuming GET /v1/arena/last-buy-epoch-pricing (API is in this MR; rich table can land as a small follow-up if desired).

## Implementation summary (MR !453) Epoch-anchored DOUB/CHARM pricing with +10%/day growth until Last Buy hard reset. **Onchain** - `effectiveCharmPriceWad()`, `epochCharmAnchorWad`, `epochAnchorTimestamp` - Hard-reset buy: TWAP/Anvil spot re-anchor + `LastBuyEpochCharmAnchored` **before** DOUB pull - `TimeArenaBuyRouter` uses effective price; `buyWithCred` unchanged - Tests: `TimeArenaEpochCharmPrice.t.sol` **Indexer** - Head poller: `effectiveCharmPriceWad()` + anchor fields in `/v1/arena/timers` - Decode/persist `LastBuyEpochCharmAnchored`; migration `20260610120000_idx_arena_epoch_charm_price_gl305` - New `GET /v1/arena/last-buy-epoch-pricing` **Frontend** - RPC fallback + submit sizing + Kumbaya quoter → `effectiveCharmPriceWad` - Protocol page copy updated; ABI regenerated **Docs / invariants:** `INV-TIME-ARENA-EPOCH-CHARM-GROWTH`, `INV-TIME-ARENA-CRED-FLAT`, `time-arena.md`, `skills/play-time-arena-doub/SKILL.md` MR: https://gitlab.com/PlasticDigits/yieldomega/-/merge_requests/83 ## Verification checklist - [ ] `cd contracts && forge test --match-contract TimeArenaEpochCharmPrice -vv` - [ ] `cd contracts && forge test --match-contract TimeArenaTest` - [ ] `cd indexer && cargo clippy --all-targets -- -D warnings && cargo test` - [ ] `cd frontend && npm run typecheck && npm test` - [ ] Anvil: `bash scripts/verify-anvil-kumbaya-charm-price.sh` (epoch-0 anchor) - [ ] Warp +1 day → `cast call TimeArena effectiveCharmPriceWad()` ≈ anchor × 1.1 - [ ] Trigger Last Buy hard reset → reset tx DOUB pull uses new anchor (not pre-reset grown price) - [ ] `GET /v1/arena/timers` — `charm_price_wad` matches onchain effective; anchor fields present - [ ] `/arena/protocol` — effective DOUB/CHARM label/copy - [ ] CRED buy still 100 CRED/CHARM (no epoch pricing in UI) **Follow-up:** Full per-epoch audit table UI on `/arena/protocol` consuming `GET /v1/arena/last-buy-epoch-pricing` (API is in this MR; rich table can land as a small follow-up if desired).
project_80561744_bot_5118c68a88bff95eacb6ab1c47c19cae commented 2026-06-10 09:42:08 +00:00 (Migrated from gitlab.com)

mentioned in commit 8118e98dda

mentioned in commit 8118e98dda5d2163be197c4fbf9eec5a8d2e0b65
PlasticDigits commented 2026-06-10 11:00:12 +00:00 (Migrated from gitlab.com)

mentioned in commit 168d8de150

mentioned in commit 168d8de150d3053bcf8145384e8b4125ac8de260
PlasticDigits commented 2026-06-13 06:45:35 +00:00 (Migrated from gitlab.com)

Verification — GitLab #305 (epoch-anchored TWAP charm price +10%/day growth)

Verified MR !453 on main. No repo changes required from this verify pass.

Onchain (AC-1–AC-10)

Item Result How
effectiveCharmPriceWad() + epoch anchor/growth PASS cd contracts && forge test --match-contract TimeArenaEpochCharmPrice -vv — 7/7 (growth, monotonic, buy pull, CRED unchanged, hard-reset re-anchor)
Full TimeArena regression PASS forge test --match-contract TimeArenaTest — 76/76
TWAP anchor library PASS forge test --match-contract ArenaCharmPriceTwap — 5/5
Anvil epoch-0 anchor (1000e18 DeployDev) PASS cast call epochCharmAnchorWad after DeployDev on Anvil :8548
Warp +1 day ≈ anchor × 1.1 PASS anvil_setNextBlockTimestamp(anchorTs+86400) → effectiveCharmPriceWad ≈ 1100e18 (WAD tolerance)
Hard-reset buy at post-reset anchor PASS TimeArenaEpochCharmPrice.t.sol::test_hard_reset_reanchors_and_prices_at_new_anchor

Indexer (AC-11–AC-14)

Item Result How
Clippy + unit tests PASS cd indexer && cargo clippy --all-targets -- -D warnings && cargo test
/v1/arena/timers effective + anchor fields PASS Head poller: charm_price_wad, epoch_charm_anchor_wad, epoch_anchor_timestamp_sec match onchain after +1d warp
/v1/arena/last-buy-epoch-pricing PASS (API) Route returns epochs array; empty until a hard-reset event is ingested — epoch-0 anchor is on /v1/arena/timers (initialize, no event)
verify-podium-live-anvil.sh FAIL (unrelated) integration_stage2::arena_wallet_stats_two_epochs_and_bonus_fields — warbow_battle_points 1000 vs expected 750; not #305-scoped

Frontend (AC-15–AC-18)

Item Result How
typecheck + lint + unit tests PASS cd frontend && npm run typecheck && npm run lint && npm test — 632/632
Buy UX uses effectiveCharmPriceWad PASS arenaV2SaleSessionBridge.ts, timeArenaBuySubmitSizing.ts, kumbayaQuoter.ts
Protocol page effective DOUB/CHARM copy PASS ArenaProtocolPage.tsx — "DOUB / CHARM (effective)" + #305 growth copy; E2E navigates /arena/protocol
CRED pay 100 CRED/CHARM PASS anvil-arena-04-cred-buy.spec.ts; walletCharmCredCopy + protocol page CRED note
Per-epoch audit table UI (AC-16) SKIP API landed; rich table deferred per MR !453 comment

Docs / invariants (AC-19–AC-20)

Item Result How
Product + PARAMETERS + invariants PASS INV-TIME-ARENA-EPOCH-CHARM-GROWTH, INV-TIME-ARENA-CRED-FLAT in invariants-and-business-logic.md; time-arena.md, PARAMETERS.md, skills/play-time-arena-doub/SKILL.md

E2E / scripts

Item Result How
bash scripts/e2e-anvil.sh PASS 6/6 Playwright (DOUB sizing, Kumbaya router buy, CRED buy)
bash scripts/verify-anvil-kumbaya-charm-price.sh SKIP Stack not running post-e2e; Kumbaya TWAP band covered by ArenaCharmPriceTwap forge tests

Follow-ups (non-blocking)

  • Rich per-epoch audit table on /arena/protocol consuming GET /v1/arena/last-buy-epoch-pricing (already noted in MR !453).
  • Seed epoch-0 row in last-buy-epoch-pricing from initialize/head poller if full audit history should include pre-reset epoch 0 without a hard reset.
  • Fix integration_stage2::arena_wallet_stats_two_epochs_and_bonus_fields (warbow_battle_points expectation) — blocks verify-podium-live-anvil.sh on fresh Postgres.

Verifier verdict: #305 acceptance criteria PASS (with AC-16 UI and verify-script caveats above).

## Verification — GitLab #305 (epoch-anchored TWAP charm price +10%/day growth) Verified MR !453 on `main`. No repo changes required from this verify pass. ### Onchain (AC-1–AC-10) | Item | Result | How | |------|--------|-----| | `effectiveCharmPriceWad()` + epoch anchor/growth | **PASS** | `cd contracts && forge test --match-contract TimeArenaEpochCharmPrice -vv` — 7/7 (growth, monotonic, buy pull, CRED unchanged, hard-reset re-anchor) | | Full TimeArena regression | **PASS** | `forge test --match-contract TimeArenaTest` — 76/76 | | TWAP anchor library | **PASS** | `forge test --match-contract ArenaCharmPriceTwap` — 5/5 | | Anvil epoch-0 anchor (`1000e18` DeployDev) | **PASS** | `cast call epochCharmAnchorWad` after DeployDev on Anvil `:8548` | | Warp +1 day ≈ anchor × 1.1 | **PASS** | `anvil_setNextBlockTimestamp(anchorTs+86400)` → `effectiveCharmPriceWad` ≈ `1100e18` (WAD tolerance) | | Hard-reset buy at post-reset anchor | **PASS** | `TimeArenaEpochCharmPrice.t.sol::test_hard_reset_reanchors_and_prices_at_new_anchor` | ### Indexer (AC-11–AC-14) | Item | Result | How | |------|--------|-----| | Clippy + unit tests | **PASS** | `cd indexer && cargo clippy --all-targets -- -D warnings && cargo test` | | `/v1/arena/timers` effective + anchor fields | **PASS** | Head poller: `charm_price_wad`, `epoch_charm_anchor_wad`, `epoch_anchor_timestamp_sec` match onchain after +1d warp | | `/v1/arena/last-buy-epoch-pricing` | **PASS** (API) | Route returns `epochs` array; empty until a hard-reset event is ingested — epoch-0 anchor is on `/v1/arena/timers` (initialize, no event) | | `verify-podium-live-anvil.sh` | **FAIL** (unrelated) | `integration_stage2::arena_wallet_stats_two_epochs_and_bonus_fields` — `warbow_battle_points` 1000 vs expected 750; not #305-scoped | ### Frontend (AC-15–AC-18) | Item | Result | How | |------|--------|-----| | typecheck + lint + unit tests | **PASS** | `cd frontend && npm run typecheck && npm run lint && npm test` — 632/632 | | Buy UX uses `effectiveCharmPriceWad` | **PASS** | `arenaV2SaleSessionBridge.ts`, `timeArenaBuySubmitSizing.ts`, `kumbayaQuoter.ts` | | Protocol page effective DOUB/CHARM copy | **PASS** | `ArenaProtocolPage.tsx` — "DOUB / CHARM (effective)" + #305 growth copy; E2E navigates `/arena/protocol` | | CRED pay 100 CRED/CHARM | **PASS** | `anvil-arena-04-cred-buy.spec.ts`; `walletCharmCredCopy` + protocol page CRED note | | Per-epoch audit table UI (AC-16) | **SKIP** | API landed; rich table deferred per MR !453 comment | ### Docs / invariants (AC-19–AC-20) | Item | Result | How | |------|--------|-----| | Product + PARAMETERS + invariants | **PASS** | `INV-TIME-ARENA-EPOCH-CHARM-GROWTH`, `INV-TIME-ARENA-CRED-FLAT` in `invariants-and-business-logic.md`; `time-arena.md`, `PARAMETERS.md`, `skills/play-time-arena-doub/SKILL.md` | ### E2E / scripts | Item | Result | How | |------|--------|-----| | `bash scripts/e2e-anvil.sh` | **PASS** | 6/6 Playwright (DOUB sizing, Kumbaya router buy, CRED buy) | | `bash scripts/verify-anvil-kumbaya-charm-price.sh` | **SKIP** | Stack not running post-e2e; Kumbaya TWAP band covered by `ArenaCharmPriceTwap` forge tests | ### Follow-ups (non-blocking) - Rich per-epoch audit table on `/arena/protocol` consuming `GET /v1/arena/last-buy-epoch-pricing` (already noted in MR !453). - Seed epoch-0 row in `last-buy-epoch-pricing` from initialize/head poller if full audit history should include pre-reset epoch 0 without a hard reset. - Fix `integration_stage2::arena_wallet_stats_two_epochs_and_bonus_fields` (`warbow_battle_points` expectation) — blocks `verify-podium-live-anvil.sh` on fresh Postgres. **Verifier verdict:** #305 acceptance criteria **PASS** (with AC-16 UI and verify-script caveats above).
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-13 06:45:37 +00:00
PlasticDigits commented 2026-06-13 07:02:54 +00:00 (Migrated from gitlab.com)

mentioned in issue #309

mentioned in issue #309
PlasticDigits commented 2026-06-13 07:34:27 +00:00 (Migrated from gitlab.com)

mentioned in issue #320

mentioned in issue #320
PlasticDigits commented 2026-06-14 02:45:24 +00:00 (Migrated from gitlab.com)

mentioned in merge request !476

mentioned in merge request !476
PlasticDigits commented 2026-06-25 22:38:18 +00:00 (Migrated from gitlab.com)

mentioned in merge request !523

mentioned in merge request !523
PlasticDigits commented 2026-07-02 13:02:49 +00:00 (Migrated from gitlab.com)

mentioned in commit aca60bbf21

mentioned in commit aca60bbf212fe9d40527c1bd317b972bf6d81cac
PlasticDigits commented 2026-07-02 13:40:00 +00:00 (Migrated from gitlab.com)

mentioned in commit a460abfb23

mentioned in commit a460abfb23bd632e29359813f8b8a35dd74d8af0
PlasticDigits commented 2026-07-02 13:50:18 +00:00 (Migrated from gitlab.com)

mentioned in commit 63373db936

mentioned in commit 63373db93695e7ff54e30022dbb59423b39f35fd
PlasticDigits commented 2026-07-07 12:54:36 +00:00 (Migrated from gitlab.com)

mentioned in issue #367

mentioned in issue #367
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#305
No description provided.