Skip to content

fix(b20-stablecoin): hard-code decimals to 6 to eliminate zero-return window#3385

Merged
refcell merged 1 commit into
mainfrom
ericliu/bop-349-psrc-27-fix
Jun 10, 2026
Merged

fix(b20-stablecoin): hard-code decimals to 6 to eliminate zero-return window#3385
refcell merged 1 commit into
mainfrom
ericliu/bop-349-psrc-27-fix

Conversation

@eric-ships

@eric-ships eric-ships commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

BOP-342 BOP-349

Motivation

B20StablecoinToken::decimals() previously delegated to the storage adapter via TokenAccounting::decimals(), which returns 0 for an uninitialized slot. During the factory bootstrap window -- after set_code installs the marker bytecode but before the initCalls chain writes the token fields -- any external call to decimals() would return 0 instead of the correct stablecoin precision of 6. This is incorrect: stablecoin precision is a protocol-level constant, not a per-token configuration value.

What changed

  • In B20StablecoinToken's dispatch, the decimals selector now returns B20Variant::Stablecoin.decimals() (always 6) instead of reading from the TokenAccounting storage adapter.
  • A unit test is added verifying that decimals() returns 6 regardless of the value held in the backing accounting (including 0, which simulates the uninitialized-slot case).

What was tried / considered

The recommendation in the audit finding suggested adding a method override directly on B20StablecoinToken. Because the dispatch layer already handles per-variant logic and B20Variant::Stablecoin.decimals() already encodes this constant (used by the factory storage when emitting B20Created), routing through that existing constant keeps the source of truth in one place and avoids adding a new method to the trait hierarchy.

… window (BOP-349/PSRC-27)

`B20StablecoinToken::decimals()` previously delegated to the storage
adapter via `TokenAccounting::decimals()`, which returns 0 for an
uninitialized slot. During the factory bootstrap window -- after
`set_code` installs the marker bytecode but before `initCalls` writes
the token fields -- any call to `decimals()` would return 0 instead of
the correct stablecoin precision of 6.

Replace the storage read with a compile-time constant sourced from
`B20Variant::Stablecoin::decimals()`, which already encodes the fixed
6-decimal precision used by all stablecoin tokens. This matches the
protocol spec, removes a storage read from a hot path, and closes the
zero-return window entirely.

Add a unit test that asserts `decimals()` returns 6 regardless of the
value stored in the backing accounting (0 or 18), covering both the
uninitialized and post-init cases.
@cb-heimdall

cb-heimdall commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

✅ Heimdall Review Status

Requirement Status More Info
Reviews 1/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

@github-actions

Copy link
Copy Markdown
Contributor

Review Summary

No issues found. The change is correct and well-scoped.

What was reviewed:

  • crates/common/precompiles/src/b20_stablecoin/dispatch.rs — dispatch now returns B20Variant::Stablecoin.decimals() (constant 6) instead of reading from the storage adapter.

Assessment:

  • The .expect() on line 133 is effectively infallible — B20Variant::Stablecoin.decimals() is a const fn that always returns Some(6). The expect message is descriptive. No runtime risk here.
  • The fix correctly reuses the existing B20Variant::Stablecoin.decimals() as the single source of truth rather than introducing a new constant or method override, consistent with how the factory already uses this value.
  • The test covers both the uninitialized-slot case (decimals = 0) and a wrong-value case (decimals = 18), confirming the dispatch always returns 6 regardless of storage state.
  • The asset variant (b20_asset/dispatch.rs) correctly continues to read decimals from storage, since asset decimals are per-token — no consistency issue.

@github-actions

Copy link
Copy Markdown
Contributor

✅ base-std fork tests: all 610 passed

base/base is fully in sync with the base-std spec.

@refcell refcell left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@refcell refcell added this pull request to the merge queue Jun 10, 2026
Merged via the queue into main with commit 7b84d3e Jun 10, 2026
26 checks passed
@refcell refcell deleted the ericliu/bop-349-psrc-27-fix branch June 10, 2026 09:46
@eric-ships eric-ships changed the title fix(b20-stablecoin): hard-code decimals to 6 to eliminate zero-return window (BOP-349/PSRC-27) fix(b20-stablecoin): hard-code decimals to 6 to eliminate zero-return window Jun 10, 2026
refcell added a commit that referenced this pull request Jun 10, 2026
Backports PRs #3310, #3363, #3383, #3385, #3408, and #3406 onto releases/v1.1.0.
refcell added a commit that referenced this pull request Jun 10, 2026
Backports PRs #3310, #3363, #3383, #3385, and #3406 onto releases/v1.1.0.
rayyan224 added a commit that referenced this pull request Jun 11, 2026
…oin dispatch

Remove stray <<<<<<< HEAD marker left by cherry-pick conflict resolution and
restore make_token() helper and dispatch_rejects_call_with_nonzero_value test
that were accidentally dropped when taking the incoming side of the #3385
conflict.

Generated with Claude Code

Co-Authored-By: Claude <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.

3 participants