Spec vs implementation: min buy should be price per 1e18 CHARM (coupling); on-chain is 1:1 spend weight #14

Closed
opened 2026-03-31 12:27:18 +00:00 by PlasticDigits · 9 comments
PlasticDigits commented 2026-03-31 12:27:18 +00:00 (Migrated from gitlab.com)

Summary

Product intent: The time-growing minimum buy should double as the price of one CHARM, with CHARM expressed as an 18-decimal unit (1e18 base units = “one charm” in user-facing terms). We want that coupling: the floor is not an independent dust limit — it is the per-charm price in the accepted asset, so economics stay aligned as the curve moves.

Current behavior: The contract does not implement that model. charmWeight increases by the raw amount of accepted asset (same units as minBuy), i.e. 1:1 spend-to-weight with no fixed 18-decimal “charms” notion. Minimum buy only gates amount >= minBuy; it does not define charmsMinted = amount * 1e18 / price (or equivalent) where price is the growing floor.

So today, raising currentMinBuyAmount changes the minimum transaction size and the per-tx cap but does not correctly encode “price per 1e18 CHARM” as the spec intends.

On-chain (authoritative today)

In TimeCurve._buy: require(amount >= minBuy), charmWeight[msg.sender] += amount, totalCharmWeight += amount. Weight is in asset raw units, not normalized CHARM with 18 decimals from a price.

Cross-repo impact

  • Docs / UX should state clearly: min buy = price per 1e18 CHARM (or per 1.0 CHARM in fixed-point), and redemption math must use the same definition.
  • Implementation needs the pricing/charm-mint formula, rounding rules, and totalCharmWeight / redeemCharms denominator alignment.
  • Tests, simulations, indexer, frontend must match the chosen formula (WAD/fixed-point, floor vs ceil).

Acceptance criteria (suggested)

  • Written spec: min buy ≡ price per 1e18 CHARM in accepted asset; document rounding.
  • On-chain: charm accrual and redemption use that definition consistently.
  • Docs, tests, sims, indexer, and UI updated so “minimum buy” and “charm price” are not described as accidentally decoupled — they are the same parameter by design, once correctly implemented.
## Summary **Product intent:** The time-growing **minimum buy** should double as the **price of one CHARM**, with CHARM expressed as an **18-decimal** unit (1e18 base units = “one charm” in user-facing terms). We **want** that coupling: the floor is not an independent dust limit — it is the **per-charm price** in the accepted asset, so economics stay aligned as the curve moves. **Current behavior:** The contract does **not** implement that model. `charmWeight` increases by the raw `amount` of accepted asset (same units as `minBuy`), i.e. **1:1 spend-to-weight** with no fixed 18-decimal “charms” notion. Minimum buy only gates `amount >= minBuy`; it does **not** define `charmsMinted = amount * 1e18 / price` (or equivalent) where `price` is the growing floor. So today, raising `currentMinBuyAmount` changes the **minimum transaction size** and the **per-tx cap** but does **not** correctly encode “price per 1e18 CHARM” as the spec intends. ## On-chain (authoritative today) In `TimeCurve._buy`: `require(amount >= minBuy)`, `charmWeight[msg.sender] += amount`, `totalCharmWeight += amount`. Weight is in **asset raw units**, not normalized CHARM with 18 decimals from a price. ## Cross-repo impact - **Docs / UX** should state clearly: min buy = **price per 1e18 CHARM** (or per 1.0 CHARM in fixed-point), and redemption math must use the same definition. - **Implementation** needs the pricing/charm-mint formula, rounding rules, and `totalCharmWeight` / `redeemCharms` denominator alignment. - **Tests, simulations, indexer, frontend** must match the chosen formula (WAD/fixed-point, floor vs ceil). ## Acceptance criteria (suggested) - Written spec: min buy ≡ **price per 1e18 CHARM** in accepted asset; document rounding. - On-chain: charm accrual and redemption use that definition consistently. - Docs, tests, sims, indexer, and UI updated so “minimum buy” and “charm price” are not described as accidentally decoupled — they are **the same parameter by design**, once correctly implemented.
PlasticDigits commented 2026-03-31 12:28:05 +00:00 (Migrated from gitlab.com)

changed title from Design flaw: minimum buy doubles as per-charm price; charm units should be 18-decimal to Spec vs implementation: min buy should be price per 1e18 CHARM (coupling); on-chain is 1:1 spend weight

<p>changed title from <code class="idiff"><span class="idiff left deletion">Design flaw</span>: min<span class="idiff deletion">imum</span> buy <span class="idiff right deletion">doubles as per-charm price; charm units should be 18-decimal</span></code> to <code class="idiff"><span class="idiff left addition">Spec vs implementation</span>: min buy <span class="idiff right addition">should be price per 1e18 CHARM (coupling); on-chain is 1:1 spend weight</span></code></p>
PlasticDigits commented 2026-03-31 12:28:05 +00:00 (Migrated from gitlab.com)

changed the description

changed the description
PlasticDigits commented 2026-04-01 01:44:42 +00:00 (Migrated from gitlab.com)

CORRECTION.
Min and max buy should be in charms, 1 to 10. TO prevent reverts, in smart contract it should be 0.99 to 10, but in frontend show 1 to 10.
Price should be decoupled from min/max buy.
Price logic should be in a seperate smart contract that can be swapped out with different algorithms
Primary price logic algorithm should be linear, following price = m * time_in_seconds_since_srart + b. For DOUB we want price to increase by $0.1 per day and start at $1.0

CORRECTION. Min and max buy should be in charms, 1 to 10. TO prevent reverts, in smart contract it should be 0.99 to 10, but in frontend show 1 to 10. Price should be decoupled from min/max buy. Price logic should be in a seperate smart contract that can be swapped out with different algorithms Primary price logic algorithm should be linear, following price = m * time_in_seconds_since_srart + b. For DOUB we want price to increase by $0.1 per day and start at $1.0
Brouie commented 2026-04-01 06:17:37 +00:00 (Migrated from gitlab.com)

partial verify on d9987ca. onchain values match spec: initialMinBuy=1, purchaseCapMultiple=10, currentMinBuyAmount grew to ~1.97, max buy ~19.73 (10x min). can't test actual buy flow because sale is ended on local anvil stack. 'Sale is not active' correctly displayed. endSale/redeemCharms/distributePrizes buttons present. need a fresh deploy with active sale to fully verify min/max enforcement and price decoupling.

partial verify on d9987ca. onchain values match spec: initialMinBuy=1, purchaseCapMultiple=10, currentMinBuyAmount grew to ~1.97, max buy ~19.73 (10x min). can't test actual buy flow because sale is ended on local anvil stack. 'Sale is not active' correctly displayed. endSale/redeemCharms/distributePrizes buttons present. need a fresh deploy with active sale to fully verify min/max enforcement and price decoupling.
PlasticDigits commented 2026-04-01 12:50:06 +00:00 (Migrated from gitlab.com)

Update pushed (main @ 44138d7)

What landed

  • Onchain: TimeCurve now buys by CHARM quantity (charmWad) with bounds derived from the exponential envelope (TimeMath.currentMinBuy × 0.99 / 10). Spend is charm × price via pluggable ICharmPrice; default LinearCharmPrice is linear in elapsed time (base + per-day increment), decoupled from min/max envelope scaling.
  • Indexer: migration + decode/persist/API for charm_wad / price_per_charm_wad; integration test fixtures updated.
  • Frontend: ABIs, timeCurveMath, TimeCurve page slider 1–10 CHARM with envelope-derived bounds and linear price readout.
  • Docs: docs/product/primitives.md and docs/testing/invariants-and-business-logic.md updated with the split (envelope vs linear price), invariant table, and cross-links to LinearCharmPrice.t.sol, TimeCurve.t.sol, TimeCurveInvariant.t.sol.

Verification

  • FOUNDRY_PROFILE=ci FOUNDRY_OUT=/tmp/yieldomega-forge-out forge test: 126 tests passed (includes fuzz on ratio, scaling, buy in-bounds, and LinearCharmPrice).
  • cd indexer && cargo test: ok.

Issue left open for any follow-up (e.g. alternate pricing modules, UI copy, or deployment checklist).

## Update pushed (main @ 44138d7) **What landed** - **Onchain:** `TimeCurve` now buys by **CHARM quantity** (`charmWad`) with bounds derived from the **exponential envelope** (`TimeMath.currentMinBuy` × 0.99 / 10). **Spend** is `charm × price` via pluggable `ICharmPrice`; default `LinearCharmPrice` is **linear in elapsed time** (base + per-day increment), **decoupled** from min/max envelope scaling. - **Indexer:** migration + decode/persist/API for `charm_wad` / `price_per_charm_wad`; integration test fixtures updated. - **Frontend:** ABIs, `timeCurveMath`, TimeCurve page slider **1–10 CHARM** with envelope-derived bounds and linear price readout. - **Docs:** `docs/product/primitives.md` and `docs/testing/invariants-and-business-logic.md` updated with the split (envelope vs linear price), invariant table, and **cross-links** to `LinearCharmPrice.t.sol`, `TimeCurve.t.sol`, `TimeCurveInvariant.t.sol`. **Verification** - `FOUNDRY_PROFILE=ci FOUNDRY_OUT=/tmp/yieldomega-forge-out forge test`: **126** tests passed (includes fuzz on ratio, scaling, `buy` in-bounds, and `LinearCharmPrice`). - `cd indexer && cargo test`: **ok**. Issue left **open** for any follow-up (e.g. alternate pricing modules, UI copy, or deployment checklist).
PlasticDigits (Migrated from gitlab.com) closed this issue 2026-04-21 03:39:53 +00:00
Brouie commented 2026-04-29 06:08:51 +00:00 (Migrated from gitlab.com)

mentioned in issue #39

mentioned in issue #39
PlasticDigits commented 2026-06-25 20:07:35 +00:00 (Migrated from gitlab.com)

mentioned in issue #354

mentioned in issue #354
PlasticDigits commented 2026-06-25 20:07:51 +00:00 (Migrated from gitlab.com)

mentioned in issue #342

mentioned in issue #342
PlasticDigits commented 2026-06-25 22:29:38 +00:00 (Migrated from gitlab.com)

mentioned in merge request !522

mentioned in merge request !522
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#14
No description provided.