Skip to content

fix: separate cyToken share decimals from underlying decimals (A17-5)#372

Open
thedavidmeister wants to merge 1 commit into
mainfrom
2026-05-04-cytoken-underlying-decimals
Open

fix: separate cyToken share decimals from underlying decimals (A17-5)#372
thedavidmeister wants to merge 1 commit into
mainfrom
2026-05-04-cytoken-underlying-decimals

Conversation

@thedavidmeister

Copy link
Copy Markdown
Contributor

Summary

CyToken had a single decimals field used for both the vault share token's decimals AND the underlying token's decimals — same name, two different quantities. The type permitted the mismatch and Lock.svelte rendered underlying balances and parsed user input with the share decimals.

When a future cyToken's underlying has different decimals than the share (e.g. a 6-decimal USDC underlying paired with an 18-decimal share), the bug fires: a balance of 1234567n formats as 0.000000000001234567 instead of 1.234567, and user input "1" parses to 10**18 instead of 10**6 — a 10**12× over-deposit. Today every entry in stores.ts happens to have matching decimals, so the bug is dormant; the type system was the only line of defence and it didn't enforce the distinction.

Changes:

  • CyToken.underlyingDecimals: number (required) — see src/lib/types.ts
  • All 15 token entries in src/lib/stores.ts now set underlyingDecimals (matching their existing decimals since current data is consistent)
  • Lock.svelte: 4 underlying-context uses switched to underlyingDecimals (parseUnits user input, balance display, MAX-balance set, underlying-balance label); 2 share-output uses stay on decimals
  • ~30 inline CyToken test fixtures across 19 test files updated
  • mockSelectedCyToken's writable now defaults to a cysFLR-shaped token; exported DEFAULT_SELECTED_CYTOKEN
  • vitest-setup.ts wires selectedCyToken: mockSelectedCyToken into the $lib/stores mock (no-op for existing tests)

Closes #371. Companion follow-up filed at #370 (foundry fork-test that verifies every entry's on-chain claim).

Test plan

  • npm run check — 0 svelte-check errors
  • npm run lint-check — clean
  • npx vitest run — 280/280 pass
  • CI green

A runtime test that renders Lock with a mismatched-decimals fixture is not in this PR. The mock-instance topology (vitest-setup-file mocks vs per-file imports of mockSelectedCyToken) produces two separate writables, so set() from the test doesn't propagate to the rendered component. The structural fix relies on the type system enforcing underlyingDecimals is supplied at every CyToken construction site, and #370 tracks the on-chain verification that those claims match deployed decimals().

🤖 Generated with Claude Code

…371)

Add a required `underlyingDecimals: number` to `CyToken`. Pre-fix the
single `decimals` field was used for both the vault share token's
decimals and the underlying token's decimals — same name, two different
quantities. The type permitted the mismatch and Lock.svelte rendered
underlying balances and parsed user input with the share decimals.

When a future cyToken's underlying has different decimals than the
share (e.g. a 6-decimal underlying paired with an 18-decimal share),
the bug fires:
  - balance display: 1234567n formatted with 18 decimals reads as
    0.000000000001234567 instead of 1.234567
  - user input "1" parsed with 18 decimals produces 10**18, a 10**12×
    over-deposit on a 6-decimal underlying
  - share output formatted with underlying decimals understates by
    10**12

Today every entry in `stores.ts` happens to have matching decimals,
so the bug is dormant. The type system was the only line of defence
and it didn't enforce the distinction.

Changes:
  - `CyToken.underlyingDecimals: number` required
  - 15 token entries in `stores.ts` updated (each set to match its
    `decimals` since current data is consistent)
  - Lock.svelte: 4 underlying-context uses switched to
    `underlyingDecimals`; 2 share-output uses stay on `decimals`
  - ~30 inline CyToken fixtures across 19 test files updated
  - `mockSelectedCyToken`'s writable now defaults to a cysFLR-shaped
    CyToken so existing tests still see a populated default; exported
    `DEFAULT_SELECTED_CYTOKEN` for tests that want to reset
  - vitest-setup.ts wires `selectedCyToken: mockSelectedCyToken` into
    the `$lib/stores` mock (no-op for existing tests; a consistent
    injection point for future tests)

A runtime test that renders Lock with a mismatched-decimals fixture
and asserts the formatter uses underlyingDecimals didn't make this
PR. The mock-instance topology between vitest-setup.ts and the test
file produces two separate writables for mockSelectedCyToken, so a
set() from the test doesn't reach the rendered component. The
structural fix relies on the type system enforcing underlyingDecimals.
#370 (filed alongside) tracks a foundry fork-test that verifies every
entry's on-chain claim against the deployed `decimals()` so the
values in stores.ts can't drift from reality.

Closes #371.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[A17-5] [HIGH] Lock.svelte conflates underlying and cyToken decimals

1 participant