Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 

Repository files navigation

🔍 Base Audit — Security Analysis of Base L2 Precompiles

A deep-dive security audit of Base's B-20 token precompile implementations (base/base repository), focusing on the privileged flag semantics and pause/policy guard consistency.

Executive Summary

During analysis of Base's B-20 token operations, two critical inconsistencies were identified in how the privileged flag (used during factory bootstrap) interacts with pause and policy guards across different token operations.

Findings

🔴 Finding 1: burnBlocked Bypasses Pause Check via Privileged Burn (NEW)

Severity: Medium-High
File: crates/common/precompiles/src/common/ops/burnable.rs
Function: burn_blocked()

Description:

The burn_blocked function calls self.burn(caller, from, amount, true) with privileged = true. Since burn() skips the pause check when privileged, this means burn_blocked can execute even when the BURN feature is paused.

This contradicts the explicit comment in the code:

// Intentional asymmetry: BURN_BLOCKED_ROLE replaces BURN_ROLE, but emergency burn pauses
// still halt every burn path, including burnBlocked.

Code flow:

burn_blocked(caller, from, amount, privileged=true)
  → B20Guards::ensure_blocked(self, from)?;  // ✅ checked
  → self.burn(caller, from, amount, true)     // ⚠️ privileged=true
      → // Role check SKIPPED (privileged=true)
      → // Pause check SKIPPED (privileged=true)  ← BUG
      → // Policy check: none for burn
      → // Balance & supply accounting

Impact:

  • A paused BURN feature is intended to halt ALL burn paths (per the comment)
  • But burn_blocked bypasses this pause when called with privileged=true
  • During factory bootstrap (initCalls), a privileged burnBlocked call would succeed even if BURN was paused via an earlier initCall
  • This breaks the invariant that "emergency burn pauses halt every burn path"

Proof: The test burn_reverts_when_burn_feature_paused in the same file confirms that a direct privileged burn() call reverts when BURN is paused. But burn_blockedburn(privileged=true) would NOT revert, because the inner burn call uses privileged=true unconditionally.

Recommended Fix: Either:

  1. Pass privileged (not true) to the inner burn call: self.burn(caller, from, amount, privileged)?;
  2. Or add an explicit pause check in burn_blocked before calling burn:
    B20Guards::ensure_not_paused::<Self>(self, IB20::PausableFeature::BURN)?;

🟡 Finding 2: privileged Flag Inconsistency Between Operations (Already Reported)

Reference: Issue #2914
Severity: Medium

The privileged flag has different semantics across operations:

Operation Role Check Pause Check Policy Check
transfer N/A ❌ Skipped ❌ Skipped
mint ❌ Skipped ✅ Enforced ✅ Enforced
burn ❌ Skipped ✅ Enforced N/A
pause ❌ Skipped N/A N/A
burn_blocked ❌ Skipped ❌ Skipped (via burn) ✅ Enforced (ensure_blocked)

This means:

  • Factory bootstrap can transfer tokens freely (bypassing pause/policy)
  • Factory bootstrap cannot mint if MINT is paused (even with privileged)
  • Factory bootstrap cannot burn if BURN is paused (even with privileged)
  • Factory bootstrap CAN burnBlocked even if BURN is paused (Finding 1)

🟢 Finding 3: burn Missing from == Address::ZERO Check (Low)

File: crates/common/precompiles/src/common/ops/burnable.rs

The burn function does not validate that from != Address::ZERO. While balance_of(Address::ZERO) would typically return 0 (making the balance check fail), explicitly rejecting zero-address burns would be consistent with transfer (which checks from == Address::ZERO) and provide clearer error messages.


Repository Structure

base-audit/
├── README.md                           # This file
├── FINDINGS.md                         # Detailed technical findings
├── PRIVILEGED_FLAG_ANALYSIS.md         # Deep-dive on privileged semantics
└── PROOF_OF_CONCEPT.md                 # Step-by-step PoC for Finding 1

Timeline

  • 2026-05-28: Initial audit of B-20 precompile operations
  • 2026-05-28: Finding 1 (burnBlocked pause bypass) identified
  • 2026-05-28: Finding 2 confirmed as already reported (#2914)
  • 2026-05-28: Finding 3 (missing zero-address check) noted

Disclosure

This audit was conducted as a code review exercise. Findings are reported responsibly through GitHub Issues on the base/base repository.

License

MIT © 2026 Sourav Joy

About

Security audit of Base L2 B-20 precompile operations — burn_blocked pause bypass & privileged flag analysis

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors