TimeArena: CRED buy at 100 CRED/CHARM + first-buy 150 CRED bonus (next epoch) #268

Closed
opened 2026-05-29 08:14:56 +00:00 by PlasticDigits · 15 comments
PlasticDigits commented 2026-05-29 08:14:56 +00:00 (Migrated from gitlab.com)

Summary

Update TimeArena CRED-buy economics and add a one-time first-buy CRED bonus. Today buyWithCred burns a flat 70 CRED per buy regardless of CHARM size; product requires 100 CRED per 1e18 CHARM (WAD). Separately, each wallet’s first arena buy ever (not reset by timer hard-resets or epoch rolls) should credit 150 CRED toward claimable balance for the next Last Buy epoch.

Parent context: Arena v2 epic #238, Play CRED #248.


Current codebase

Area Behavior
TimeArena.buyWithCred Burns fixed CRED_BUY_BURN = 70e18; accrues CHARM/XP/timers via _accrueCharmOnly; no DOUB prize routing, no epochCredPool accrual, no referral CRED mint.
TimeArena.buy (DOUB) Pulls DOUB, routes 40/30/30, adds 35 CRED to epochCredPool[lastBuyEpoch], referral mints 5%+5% of that tranche.
PlayCred Non-transferable ERC20; only TimeArena (MINTER) mints/burns.
pendingCred(user, epoch) Pro-rata only: epochCredPool[epoch] * epochCharmWad / epochCharmTotal (zero if no CHARM weight).
claimCred(epoch) Mints pro-rata share; zeros epochCharmWad[epoch][user].
buyCount[user] Increments every buy; used for Last Buy podium; not reset on timer hard-reset.
Tests / docs INV-TIME-ARENA-CRED-BURN-BUY expects 70 CRED; docs/product/arena-v2.md documents 70 CRED burn.
Indexer Decodes Buy with paidWithCred; no dedicated first-buy bonus event today.

Relevant files

  • contracts/src/arena/TimeArena.sol
  • contracts/src/PlayCred.sol
  • contracts/src/interfaces/IPlayCred.sol
  • contracts/test/TimeArena.t.sol
  • docs/product/arena-v2.md
  • docs/testing/invariants-and-business-logic.md
  • indexer/src/decoder.rs (if new events)
  • contracts/script/DeployDev.s.sol, DeployProduction.s.sol (no deploy change unless new immutables)

Why this is needed

  1. 100 CRED / CHARM — Flat 70 CRED mis-prices large vs small CHARM buys inside the 0.99–10 band; burn should scale with charmWad so CRED and DOUB buys align on CHARM intent.
  2. First-buy 150 CRED (next epoch) — Onboarding incentive: reward first participation without diluting the current epoch pro-rata pool; “next epoch” keeps accounting clean when the first buy happens mid-epoch.

Constraints and guardrails

  • Onchain authority — All economics enforced in TimeArena; indexer/frontend derived only (guardrails).
  • AGPL-3.0 — Contract changes under repo license.
  • UUPS upgrade — Prefer storage-append + initializer-safe changes; document migration if upgrading live proxy.
  • PlayCred non-transferable — Bonus and claims must use mint/burn via TimeArena only; no peer transfers.
  • CRED buys unchanged side effects — Still no DOUB routing / totalDoubRaised / epochCredPool accrual on CRED path unless explicitly scoped (default: unchanged).
  • First-buy flag — Must be permanent per wallet (buyCount == 0 before increment, or dedicated bool set once); must not reset on lastBuyEpoch hard-reset or rollPodiumEpoch.
  • First-buy eligibility — Clarify in implementation: applies to first DOUB or CRED buy (recommend any _finishBuy); document if router buyFor counts for the buyer address only.
  • Rounding — Use Math.mulDiv(charmWad, 100e18, WAD) (or equivalent) for burn; revert on zero burn.
  • Reentrancy — Keep nonReentrant on external entrypoints; no external calls before state updates in bonus path.

1) Variable CRED burn (100 per CHARM)

  • Replace constant-only burn with e.g. credBurn = Math.mulDiv(charmWad, 100e18, WAD) in _buyCred.
  • Remove or repurpose CRED_BUY_BURN constant; add CRED_PER_CHARM_WAD = 100e18 (name TBD).
  • Revert if credBurn == 0 or balanceOf(buyer) < credBurn before burn.

2) First-buy 150 CRED → next epoch pending

  • On first buy (buyCount[buyer] == 0 before _finishBuy increment):
    • Target epoch: lastBuyEpoch + 1 (if hard-reset increments epoch in same tx, use post-reset lastBuyEpoch + 1 — document chosen rule in NatSpec).
    • Credit 150e18 via dedicated storage, e.g. mapping(uint256 => mapping(address => uint256)) epochFixedCredBonus.
  • Extend pendingCred(user, epoch) to return proRata + epochFixedCredBonus[epoch][user] (when user has bonus and/or weight).
  • Extend claimCred(epoch) to mint pro-rata + bonus, clear bonus and epoch CHARM weight as today.
  • Emit event e.g. FirstBuyCredScheduled(address indexed buyer, uint256 indexed targetEpoch, uint256 amount) for indexer/audit.

3) Docs and invariants

  • Update arena-v2.md, invariant table INV-TIME-ARENA-CRED-BURN-BUY, add INV-TIME-ARENA-FIRST-BUY-CRED-BONUS.

Acceptance criteria

  • buyWithCred(cw) burns exactly cw * 100e18 / 1e18 (18-decimal CRED).
  • Burn scales across CHARM band (0.99e18 … 10e18); reverts below min/max CHARM as today.
  • Wallet’s first _finishBuy schedules 150e18 CRED for next epoch per spec; second buy from same wallet does not schedule again.
  • Timer hard-reset / new lastBuyEpoch does not reset first-buy consumption flag.
  • pendingCred and claimCred include fixed bonus; claim after epoch < lastBuyEpoch works with zero pro-rata but non-zero bonus.
  • DOUB buy path unchanged except first-buy bonus hook (35 CRED pool accrual still per DOUB buy).
  • PlayCred remains non-transferable; no new transfer paths.
  • Forge tests and invariant doc updated; forge test green.

Test plan — functional paths

# Scenario Expected
1 buyWithCred(1e18) Burns 100e18 CRED
2 buyWithCred(10e18) Burns 1000e18 CRED
3 buyWithCred(99e16) Burns 99e16 CRED (or documented rounding)
4 Insufficient CRED balance Revert before state change
5 First wallet buy (DOUB) Bonus scheduled; buyCount becomes 1
6 First wallet buy (CRED) Same bonus once
7 Second buy same wallet No second bonus
8 Claim epoch with only bonus (no CHARM) Mints 150e18
9 Claim epoch with pro-rata + bonus Mints sum
10 Claim clears bonus + charm weight pendingCred → 0
11 Epoch not ended claimCred reverts
12 paidWithCred on Buy event Still true for CRED path

Test plan — attack vectors / abuse

Vector Test / mitigation
CHARM rounding dust Min CHARM buy: burn > 0; no free buys
Reentrancy via PlayCred PlayCred has no hooks; keep nonReentrant on arena
Double first-buy bonus Two buys same block from fresh wallet: only one bonus (use flag before increment)
Contract wallet / buyFor Bonus attributed to buyer, not router; test buyFor does not grant router
Epoch boundary gaming Buy triggering hard-reset: bonus targets documented epoch; fuzz/advance block
Claim without participation Bonus claimable without CHARM in target epoch if spec requires — test isolation from pro-rata
Sybil farming Product accepts new-wallet bonus; document no onchain Sybil resistance (ops/monitoring)
Overflow mulDiv on max CHARM (10e18) * 100e18 within uint256
Paused arena Bonus not scheduled when paused / not live
Upgrade storage clash If UUPS: append-only slots; test initializer on fork

Verification criteria

  • forge test --match-contract TimeArena passes including updated CRED tests.
  • Manual: deploy Dev stack, first buy → pendingCred for lastBuyEpoch+1 shows 150e18 (or post-claim behavior documented).
  • Manual: buyWithCred with known balance decreases by 100 * charm.
  • NatSpec on new public mappings/views.
  • Indexer decodes new event if added (optional follow-up issue if out of scope).

  • Frontend CRED pay selector — separate issue (depends on this for burn formula).
  • Changing 35 CRED DOUB accrual or referral 5%+5% split.

Depends / blocks

  • Blocks frontend issue for accurate onchain burn checks.
## Summary Update **TimeArena** CRED-buy economics and add a one-time **first-buy** CRED bonus. Today `buyWithCred` burns a flat **70 CRED** per buy regardless of CHARM size; product requires **100 CRED per 1e18 CHARM (WAD)**. Separately, each wallet’s **first arena buy ever** (not reset by timer hard-resets or epoch rolls) should credit **150 CRED** toward claimable balance for the **next** Last Buy epoch. Parent context: Arena v2 epic [#238](https://gitlab.com/PlasticDigits/yieldomega/-/issues/238), Play CRED [#248](https://gitlab.com/PlasticDigits/yieldomega/-/issues/248). --- ## Current codebase | Area | Behavior | |------|----------| | **`TimeArena.buyWithCred`** | Burns fixed `CRED_BUY_BURN = 70e18`; accrues CHARM/XP/timers via `_accrueCharmOnly`; **no** DOUB prize routing, **no** `epochCredPool` accrual, **no** referral CRED mint. | | **`TimeArena.buy` (DOUB)** | Pulls DOUB, routes 40/30/30, adds **35 CRED** to `epochCredPool[lastBuyEpoch]`, referral mints 5%+5% of that tranche. | | **`PlayCred`** | Non-transferable ERC20; only `TimeArena` (MINTER) mints/burns. | | **`pendingCred(user, epoch)`** | Pro-rata only: `epochCredPool[epoch] * epochCharmWad / epochCharmTotal` (zero if no CHARM weight). | | **`claimCred(epoch)`** | Mints pro-rata share; zeros `epochCharmWad[epoch][user]`. | | **`buyCount[user]`** | Increments every buy; used for Last Buy podium; **not** reset on timer hard-reset. | | **Tests / docs** | `INV-TIME-ARENA-CRED-BURN-BUY` expects 70 CRED; `docs/product/arena-v2.md` documents 70 CRED burn. | | **Indexer** | Decodes `Buy` with `paidWithCred`; no dedicated first-buy bonus event today. | **Relevant files** - `contracts/src/arena/TimeArena.sol` - `contracts/src/PlayCred.sol` - `contracts/src/interfaces/IPlayCred.sol` - `contracts/test/TimeArena.t.sol` - `docs/product/arena-v2.md` - `docs/testing/invariants-and-business-logic.md` - `indexer/src/decoder.rs` (if new events) - `contracts/script/DeployDev.s.sol`, `DeployProduction.s.sol` (no deploy change unless new immutables) --- ## Why this is needed 1. **100 CRED / CHARM** — Flat 70 CRED mis-prices large vs small CHARM buys inside the 0.99–10 band; burn should scale with `charmWad` so CRED and DOUB buys align on CHARM intent. 2. **First-buy 150 CRED (next epoch)** — Onboarding incentive: reward first participation without diluting the current epoch pro-rata pool; “next epoch” keeps accounting clean when the first buy happens mid-epoch. --- ## Constraints and guardrails - **Onchain authority** — All economics enforced in `TimeArena`; indexer/frontend derived only ([guardrails](docs/agents/metadata-and-skills.md)). - **AGPL-3.0** — Contract changes under repo license. - **UUPS upgrade** — Prefer storage-append + initializer-safe changes; document migration if upgrading live proxy. - **PlayCred non-transferable** — Bonus and claims must use mint/burn via `TimeArena` only; no peer transfers. - **CRED buys unchanged side effects** — Still no DOUB routing / `totalDoubRaised` / `epochCredPool` accrual on CRED path unless explicitly scoped (default: unchanged). - **First-buy flag** — Must be **permanent per wallet** (`buyCount == 0` before increment, or dedicated `bool` set once); **must not** reset on `lastBuyEpoch` hard-reset or `rollPodiumEpoch`. - **First-buy eligibility** — Clarify in implementation: applies to **first DOUB or CRED buy** (recommend **any** `_finishBuy`); document if router `buyFor` counts for the **buyer** address only. - **Rounding** — Use `Math.mulDiv(charmWad, 100e18, WAD)` (or equivalent) for burn; revert on zero burn. - **Reentrancy** — Keep `nonReentrant` on external entrypoints; no external calls before state updates in bonus path. --- ## Recommended direction ### 1) Variable CRED burn (100 per CHARM) - Replace constant-only burn with e.g. `credBurn = Math.mulDiv(charmWad, 100e18, WAD)` in `_buyCred`. - Remove or repurpose `CRED_BUY_BURN` constant; add `CRED_PER_CHARM_WAD = 100e18` (name TBD). - Revert if `credBurn == 0` or `balanceOf(buyer) < credBurn` before burn. ### 2) First-buy 150 CRED → next epoch pending - On first buy (`buyCount[buyer] == 0` before `_finishBuy` increment): - Target epoch: `lastBuyEpoch + 1` (if hard-reset increments epoch in same tx, use post-reset `lastBuyEpoch + 1` — document chosen rule in NatSpec). - Credit **150e18** via dedicated storage, e.g. `mapping(uint256 => mapping(address => uint256)) epochFixedCredBonus`. - Extend **`pendingCred(user, epoch)`** to return `proRata + epochFixedCredBonus[epoch][user]` (when user has bonus and/or weight). - Extend **`claimCred(epoch)`** to mint **pro-rata + bonus**, clear bonus and epoch CHARM weight as today. - Emit event e.g. `FirstBuyCredScheduled(address indexed buyer, uint256 indexed targetEpoch, uint256 amount)` for indexer/audit. ### 3) Docs and invariants - Update `arena-v2.md`, invariant table `INV-TIME-ARENA-CRED-BURN-BUY`, add `INV-TIME-ARENA-FIRST-BUY-CRED-BONUS`. --- ## Acceptance criteria - [ ] `buyWithCred(cw)` burns exactly `cw * 100e18 / 1e18` (18-decimal CRED). - [ ] Burn scales across CHARM band (0.99e18 … 10e18); reverts below min/max CHARM as today. - [ ] Wallet’s **first** `_finishBuy` schedules **150e18** CRED for **next** epoch per spec; **second** buy from same wallet does not schedule again. - [ ] Timer hard-reset / new `lastBuyEpoch` does **not** reset first-buy consumption flag. - [ ] `pendingCred` and `claimCred` include fixed bonus; claim after `epoch < lastBuyEpoch` works with zero pro-rata but non-zero bonus. - [ ] DOUB buy path unchanged except first-buy bonus hook (35 CRED pool accrual still per DOUB buy). - [ ] `PlayCred` remains non-transferable; no new transfer paths. - [ ] Forge tests and invariant doc updated; `forge test` green. --- ## Test plan — functional paths | # | Scenario | Expected | |---|----------|----------| | 1 | `buyWithCred(1e18)` | Burns 100e18 CRED | | 2 | `buyWithCred(10e18)` | Burns 1000e18 CRED | | 3 | `buyWithCred(99e16)` | Burns 99e16 CRED (or documented rounding) | | 4 | Insufficient CRED balance | Revert before state change | | 5 | First wallet buy (DOUB) | Bonus scheduled; `buyCount` becomes 1 | | 6 | First wallet buy (CRED) | Same bonus once | | 7 | Second buy same wallet | No second bonus | | 8 | Claim epoch with only bonus (no CHARM) | Mints 150e18 | | 9 | Claim epoch with pro-rata + bonus | Mints sum | | 10 | Claim clears bonus + charm weight | `pendingCred` → 0 | | 11 | Epoch not ended | `claimCred` reverts | | 12 | `paidWithCred` on `Buy` event | Still true for CRED path | --- ## Test plan — attack vectors / abuse | Vector | Test / mitigation | |--------|-------------------| | **CHARM rounding dust** | Min CHARM buy: burn > 0; no free buys | | **Reentrancy via PlayCred** | `PlayCred` has no hooks; keep `nonReentrant` on arena | | **Double first-buy bonus** | Two buys same block from fresh wallet: only one bonus (use flag before increment) | | **Contract wallet / `buyFor`** | Bonus attributed to `buyer`, not router; test `buyFor` does not grant router | | **Epoch boundary gaming** | Buy triggering hard-reset: bonus targets documented epoch; fuzz/advance block | | **Claim without participation** | Bonus claimable without CHARM in target epoch if spec requires — test isolation from pro-rata | | **Sybil farming** | Product accepts new-wallet bonus; document no onchain Sybil resistance (ops/monitoring) | | **Overflow** | `mulDiv` on max CHARM (10e18) * 100e18 within uint256 | | **Paused arena** | Bonus not scheduled when paused / not live | | **Upgrade storage clash** | If UUPS: append-only slots; test initializer on fork | --- ## Verification criteria - [ ] `forge test --match-contract TimeArena` passes including updated CRED tests. - [ ] Manual: deploy Dev stack, first buy → `pendingCred` for `lastBuyEpoch+1` shows 150e18 (or post-claim behavior documented). - [ ] Manual: `buyWithCred` with known balance decreases by `100 * charm`. - [ ] NatSpec on new public mappings/views. - [ ] Indexer decodes new event if added (optional follow-up issue if out of scope). --- ## Out of scope (link separately) - Frontend CRED pay selector — separate issue (depends on this for burn formula). - Changing **35 CRED** DOUB accrual or referral **5%+5%** split. ## Depends / blocks - **Blocks** frontend issue for accurate onchain burn checks.
PlasticDigits commented 2026-05-29 08:15:04 +00:00 (Migrated from gitlab.com)

mentioned in issue #269

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

marked as related to #269

marked as related to #269
PlasticDigits commented 2026-05-29 08:33:13 +00:00 (Migrated from gitlab.com)

mentioned in commit 9342e71046

mentioned in commit 9342e710460bea896e1e731844622e82cf8d2aa6
PlasticDigits commented 2026-05-29 08:33:14 +00:00 (Migrated from gitlab.com)

mentioned in commit 41a911bd91

mentioned in commit 41a911bd91efe101ca2c3b5050d0d3092ca0b841
PlasticDigits commented 2026-05-29 08:33:23 +00:00 (Migrated from gitlab.com)

Implementation complete (merged to main)

@brouie — please verify when you have a moment. Issue left open until sign-off.

Summary

  • buyWithCred: burns charmWad × 100e18 / 1e18 CRED (CRED_PER_CHARM_WAD); reverts on zero burn / insufficient balance.
  • First buy bonus: wallet’s first _finishBuy (DOUB or CRED) schedules 150e18 CRED in epochFixedCredBonus[lastBuyEpoch + 1] after any same-tx hard-reset; emits FirstBuyCredScheduled.
  • pendingCred / claimCred: return/mint pro-rata pool share plus fixed bonus; bonus-only claim allowed (no CHARM weight required).
  • Storage: append-only epochFixedCredBonus mapping (UUPS-safe).
  • Docs / invariants: arena-v2.md, invariants-and-business-logic.md (INV-TIME-ARENA-CRED-BURN-BUY, INV-TIME-ARENA-FIRST-BUY-CRED-BONUS), guardrails + skills/play-time-arena-doub/SKILL.md.

Merge: 41a911b on main.

Verification checklist

  • cd contracts && forge test --match-contract TimeArena — 28/28 green
  • buyWithCred(1e18) burns exactly 100e18 CRED
  • buyWithCred(10e18) burns 1000e18 CRED
  • buyWithCred(99e16) burns 99e18 CRED (min CHARM band)
  • Insufficient CRED balance reverts before state change
  • First DOUB buy: epochFixedCredBonus[lastBuyEpoch+1][wallet] == 150e18
  • First CRED buy: same bonus once; second buy does not add another 150
  • First buy triggering timer hard-reset: bonus targets post-reset lastBuyEpoch + 1
  • pendingCred / claimCred include bonus; claim clears bonus + epoch CHARM weight
  • Claim prior epoch with bonus only (no CHARM in that epoch) mints 150e18
  • claimCred reverts while epoch >= lastBuyEpoch
  • DOUB path unchanged except first-buy hook (35 CRED pool accrual still per DOUB buy)
  • buyCount / first-buy flag not reset on epoch roll (see test_first_buy_flag_survives_epoch_roll)

Out of scope (unchanged)

  • Indexer decode of FirstBuyCredScheduled (optional follow-up)
  • Frontend CRED burn display (separate issue; blocked on this contract work)
## Implementation complete (merged to `main`) @brouie — please verify when you have a moment. Issue left open until sign-off. ### Summary - **`buyWithCred`**: burns `charmWad × 100e18 / 1e18` CRED (`CRED_PER_CHARM_WAD`); reverts on zero burn / insufficient balance. - **First buy bonus**: wallet’s first `_finishBuy` (DOUB or CRED) schedules **150e18** CRED in `epochFixedCredBonus[lastBuyEpoch + 1]` after any same-tx hard-reset; emits `FirstBuyCredScheduled`. - **`pendingCred` / `claimCred`**: return/mint pro-rata pool share **plus** fixed bonus; bonus-only claim allowed (no CHARM weight required). - **Storage**: append-only `epochFixedCredBonus` mapping (UUPS-safe). - **Docs / invariants**: `arena-v2.md`, `invariants-and-business-logic.md` (`INV-TIME-ARENA-CRED-BURN-BUY`, `INV-TIME-ARENA-FIRST-BUY-CRED-BONUS`), guardrails + `skills/play-time-arena-doub/SKILL.md`. Merge: `41a911b` on `main`. ### Verification checklist - [ ] `cd contracts && forge test --match-contract TimeArena` — 28/28 green - [ ] `buyWithCred(1e18)` burns exactly **100e18** CRED - [ ] `buyWithCred(10e18)` burns **1000e18** CRED - [ ] `buyWithCred(99e16)` burns **99e18** CRED (min CHARM band) - [ ] Insufficient CRED balance reverts before state change - [ ] First DOUB buy: `epochFixedCredBonus[lastBuyEpoch+1][wallet] == 150e18` - [ ] First CRED buy: same bonus once; second buy does not add another 150 - [ ] First buy triggering timer hard-reset: bonus targets **post-reset** `lastBuyEpoch + 1` - [ ] `pendingCred` / `claimCred` include bonus; claim clears bonus + epoch CHARM weight - [ ] Claim prior epoch with bonus only (no CHARM in that epoch) mints 150e18 - [ ] `claimCred` reverts while `epoch >= lastBuyEpoch` - [ ] DOUB path unchanged except first-buy hook (35 CRED pool accrual still per DOUB buy) - [ ] `buyCount` / first-buy flag not reset on epoch roll (see `test_first_buy_flag_survives_epoch_roll`) ### Out of scope (unchanged) - Indexer decode of `FirstBuyCredScheduled` (optional follow-up) - Frontend CRED burn display (separate issue; blocked on this contract work)
PlasticDigits commented 2026-05-29 09:06:34 +00:00 (Migrated from gitlab.com)

mentioned in commit 84ba60048a

mentioned in commit 84ba60048a6603a3f7f63d57bbb93f5d79d1cfbf
PlasticDigits commented 2026-05-30 14:04:05 +00:00 (Migrated from gitlab.com)

mentioned in issue #248

mentioned in issue #248
PlasticDigits commented 2026-06-01 11:28:46 +00:00 (Migrated from gitlab.com)

mentioned in commit 096c6f953b

mentioned in commit 096c6f953bb1778fb872d10df817c2b54580731a
PlasticDigits commented 2026-06-01 11:28:53 +00:00 (Migrated from gitlab.com)

QA verification complete (GitLab #268)

Verified on main @ 096c6f9 in worktree issue/268-verify.

What was verified

Onchain (already merged in 41a911b; re-verified on current main):

  • buyWithCred(cw) burns cw × 100e18 / 1e18 CRED (CRED_PER_CHARM_WAD)
  • First _finishBuy per wallet schedules 150e18 in epochFixedCredBonus[lastBuyEpoch + 1] (post same-tx hard-reset)
  • pendingCred / claimCred include pro-rata + fixed bonus; bonus-only claim works
  • DOUB path unchanged except first-buy hook (35 CRED pool accrual per DOUB buy)
  • FirstBuyCredScheduled emitted; buyCount / first-buy flag survives epoch roll

This QA pass added:

  • bash scripts/verify-cred-buy-anvil.sh — fresh Anvil DeployDev smoke (burn scaling + first-buy bonus via cast)
  • Invariant detail section §268
  • Manual QA checklist §268
  • Crosslinks in guardrails + skills/play-time-arena-doub/SKILL.md

Verification checklist (all passed)

  • cd contracts && forge test --match-contract TimeArena — 64/64 green
  • bash scripts/verify-cred-buy-anvil.sh — Anvil burn + first-buy bonus smoke OK
  • buyWithCred(1e18) burns 100e18 CRED
  • buyWithCred(10e18) burns 1000e18 CRED
  • buyWithCred(99e16) burns 99e18 CRED (min CHARM band)
  • Insufficient CRED reverts (test_buyWithCred_reverts_insufficient_cred)
  • First DOUB/CRED buy: epochFixedCredBonus[lastBuyEpoch+1] == 150e18; second buy does not add another 150
  • Hard-reset first buy targets post-reset epoch (test_first_buy_hard_reset_targets_post_epoch)
  • pendingCred / claimCred include bonus; claim clears bonus + epoch CHARM weight
  • Bonus-only claim mints 150e18 (test_claim_cred_bonus_only_no_charm)
  • claimCred reverts while epoch >= lastBuyEpoch
  • npm test -- --run src/lib/arenaCredBurn.test.ts — 5/5 green
  • NatSpec on CRED_PER_CHARM_WAD, FIRST_BUY_CRED_BONUS, epochFixedCredBonus, FirstBuyCredScheduled

Out of scope (unchanged)

  • Indexer decode of FirstBuyCredScheduled — optional follow-up
  • Frontend CRED pay UI — #269 (depends on this contract work; burn helper already mirrors onchain)

Closing — contract acceptance criteria and verification checklist satisfied.

## QA verification complete (GitLab #268) Verified on `main` @ `096c6f9` in worktree `issue/268-verify`. ### What was verified **Onchain (already merged in `41a911b`; re-verified on current `main`):** - `buyWithCred(cw)` burns `cw × 100e18 / 1e18` CRED (`CRED_PER_CHARM_WAD`) - First `_finishBuy` per wallet schedules **150e18** in `epochFixedCredBonus[lastBuyEpoch + 1]` (post same-tx hard-reset) - `pendingCred` / `claimCred` include pro-rata + fixed bonus; bonus-only claim works - DOUB path unchanged except first-buy hook (35 CRED pool accrual per DOUB buy) - `FirstBuyCredScheduled` emitted; `buyCount` / first-buy flag survives epoch roll **This QA pass added:** - `bash scripts/verify-cred-buy-anvil.sh` — fresh Anvil DeployDev smoke (burn scaling + first-buy bonus via `cast`) - Invariant detail section [`§268`](docs/testing/invariants-and-business-logic.md#timearena-cred-buy-gitlab-268) - Manual QA checklist [`§268`](docs/testing/manual-qa-checklists.md#manual-qa-issue-268) - Crosslinks in guardrails + [`skills/play-time-arena-doub/SKILL.md`](skills/play-time-arena-doub/SKILL.md) ### Verification checklist (all passed) - [x] `cd contracts && forge test --match-contract TimeArena` — **64/64** green - [x] `bash scripts/verify-cred-buy-anvil.sh` — Anvil burn + first-buy bonus smoke OK - [x] `buyWithCred(1e18)` burns **100e18** CRED - [x] `buyWithCred(10e18)` burns **1000e18** CRED - [x] `buyWithCred(99e16)` burns **99e18** CRED (min CHARM band) - [x] Insufficient CRED reverts (`test_buyWithCred_reverts_insufficient_cred`) - [x] First DOUB/CRED buy: `epochFixedCredBonus[lastBuyEpoch+1] == 150e18`; second buy does not add another 150 - [x] Hard-reset first buy targets post-reset epoch (`test_first_buy_hard_reset_targets_post_epoch`) - [x] `pendingCred` / `claimCred` include bonus; claim clears bonus + epoch CHARM weight - [x] Bonus-only claim mints 150e18 (`test_claim_cred_bonus_only_no_charm`) - [x] `claimCred` reverts while `epoch >= lastBuyEpoch` - [x] `npm test -- --run src/lib/arenaCredBurn.test.ts` — 5/5 green - [x] NatSpec on `CRED_PER_CHARM_WAD`, `FIRST_BUY_CRED_BONUS`, `epochFixedCredBonus`, `FirstBuyCredScheduled` ### Out of scope (unchanged) - Indexer decode of `FirstBuyCredScheduled` — optional follow-up - Frontend CRED pay UI — [#269](https://gitlab.com/PlasticDigits/yieldomega/-/issues/269) (depends on this contract work; burn helper already mirrors onchain) Closing — contract acceptance criteria and verification checklist satisfied.
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-06-01 11:29:02 +00:00
PlasticDigits commented 2026-06-02 13:29:26 +00:00 (Migrated from gitlab.com)

mentioned in issue #257

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

mentioned in issue #299

mentioned in issue #299
PlasticDigits commented 2026-06-07 07:50:10 +00:00 (Migrated from gitlab.com)

mentioned in issue #300

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

mentioned in issue #305

mentioned in issue #305
PlasticDigits commented 2026-06-10 09:09:17 +00:00 (Migrated from gitlab.com)

marked as related to #305

marked as related to #305
PlasticDigits commented 2026-06-13 07:02:54 +00:00 (Migrated from gitlab.com)

mentioned in issue #309

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