Skip to content

test(b20): add Rust-parity gap coverage + guard mock-only privileged tests#146

Draft
amiecorso wants to merge 3 commits into
mainfrom
amiecorso/b20-rust-test-parity-gaps
Draft

test(b20): add Rust-parity gap coverage + guard mock-only privileged tests#146
amiecorso wants to merge 3 commits into
mainfrom
amiecorso/b20-rust-test-parity-gaps

Conversation

@amiecorso
Copy link
Copy Markdown
Collaborator

What

Adds base-std unit tests for externally-observable B20 behaviors that the
Rust precompile suite covers but base-std did not exercise, surfaced by a
Rust → base-std test-parity audit of the B20 precompiles.

New coverage

  • supply: zero-amount mint / burn / burnBlocked no-ops, exact
    supply-cap boundary (totalSupply + amount == cap), multi-call mint
    accumulation
  • erc20: self-transfer no-inflation, privileged sender/receiver policy
    bypass, external-allowlist sender/receiver allow + sender deny (custom
    policy id, exercising the registry path beyond the ALWAYS_ALLOW /
    ALWAYS_BLOCK sentinels), privileged transferFrom executor-policy bypass
  • B20Asset: toScaledBalance arithmetic-overflow revert
  • B20Factory: createB20 invalid-params (ABI decode) revert

Mock-only test guards

Guards the pre-existing privileged transferFrom / transferFromWithMemo
tests under LIVE_PRECOMPILES. They reopen the factory bootstrap window via
vm.store on the mock-only initialized slot, which the live precompile
omits from its namespaced layout (it derives init-state from code presence).
This matches the convention the other mock-only tests already use
(MockB20SlotHelpers, createPolicy_revertOrder, etc.). The 3 new
privileged tests carry the same guard.

Why

These are the "mutation-blindspot" gaps: behaviors the Rust impl asserts but
base-std never exercised, so fork testing couldn't catch a divergence in them.
The canonical example (0-amount) turned out to already agree on both sides
(InvalidAmount is defined but unused) — the tests pin that agreement so a
future re-divergence is caught.

Validation

  • Mock mode: all new tests pass (256 fuzz runs).
  • Fork mode (live Rust precompile, base/base main via base-anvil): the
    11 non-privileged new tests pass; the 3 privileged tests skip by
    construction. Full suite: 609 passed / 0 failed / 12 skipped.

Notes

  • Net +301 lines, test-only; no src / mock / interface changes.
  • No ticket — happy to attach a BOP number if one should track this.

…tests

Adds base-std unit tests for externally-observable behaviors the Rust
precompile suite covers but base-std did not exercise (surfaced by a
Rust -> base-std test-parity audit of the B20 precompiles):

- supply: zero-amount mint / burn / burnBlocked no-ops, exact supply-cap
  boundary, multi-call mint accumulation
- erc20: self-transfer no-inflation, privileged sender/receiver policy
  bypass, external-allowlist sender/receiver allow + sender deny,
  privileged transferFrom executor-policy bypass
- B20Asset: toScaledBalance arithmetic-overflow revert
- B20Factory: createB20 invalid-params decode revert

Also guards the pre-existing privileged transferFrom / transferFromWithMemo
tests under LIVE_PRECOMPILES. Those tests reopen the factory bootstrap
window via vm.store on the mock-only `initialized` slot, which the live
precompile omits from its namespaced layout; the guard matches the
convention already used by the other mock-only tests.

Validated in both modes: mock (256 fuzz runs) and fork against the live
Rust precompile at base/base main (non-privileged tests pass; privileged
tests skip by construction).
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 7, 2026

Interface Coverage

✅ All interface functions have test coverage.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 7, 2026

📊 Forge Coverage (src/lib/)

🟢 ≥99% across all metrics.

File Lines Stmts Branches Funcs
🟢 B20FactoryLib.sol 100.00% 100.00% 100.00% 100.00%
🟢 MockActivationRegistry.sol 100.00% 100.00% 100.00% 100.00%
🟢 MockActivationRegistryStorage.sol 100.00% 100.00% 100.00% 100.00%
🟢 MockB20.sol 100.00% 100.00% 100.00% 100.00%
🟢 MockB20Asset.sol 100.00% 100.00% 100.00% 100.00%
🟡 MockB20Factory.sol 98.95% 99.08% 100.00% 100.00%
🟢 MockB20Stablecoin.sol 100.00% 100.00% 100.00% 100.00%
🟢 MockB20Storage.sol 100.00% 100.00% 100.00% 100.00%
🟢 MockPolicyRegistry.sol 100.00% 100.00% 100.00% 100.00%
🟢 MockPolicyRegistryStorage.sol 100.00% 100.00% 100.00% 100.00%
Total 99.86% 99.88% 100.00% 100.00%

Full report: download artifact. To browse locally: make coverage (runs forge coverage + genhtml + opens the HTML report).

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 7, 2026

✅ Fork tests: all 608 passed

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

- drop redundant zero-amount mint/burn/burnBlocked tests (already exercised by the unbounded-fuzz happy-path tests, whose domain includes 0)
- simplify the self-transfer no-inflation test to a single transfer
- rewrite the privileged transfer sender/receiver-policy bypass tests to reach privilege via a genuine factory bootstrap initCall chain (mint -> updatePolicy -> transfer) instead of a vm.store window reopen, so they run against the live precompile under LIVE_PRECOMPILES
- correct the skip rationale on the privileged transferFrom/memo allowance tests: a pre-existing third-party allowance cannot coexist with an open bootstrap window, so there is no fork-reachable construction (the prior slot-layout explanation was misleading)

Validation: mock 271 pass / 0 fail / 0 skip; fork 266 pass / 0 fail / 5 skip (the 5 privileged allowance tests) via run-fork-tests.sh against base-anvil.
@amiecorso amiecorso marked this pull request as draft June 7, 2026 17:23
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.

1 participant