For the complete documentation index, see llms.txt. This page is also available as Markdown.

75317 bc medium fpvm recomputes blob base fee from jovian da footprint

Submitted on Apr 28th 2026 at 13:39:30 UTC by @iam0x04 for Audit Comp | Base Azul

  • Report ID: #75317

  • Report Type: Blockchain/DLT

  • Report severity: Medium

  • Target: https://github.com/base/base/tree/v0.8.0-rc.28

  • Impacts:

    • A bug in the respective layer 0/1/2 network code that results in unintended smart contract behavior with no concrete funds at direct risk

Description

Vulnerability Details

The stateless fault-proof executor builds its BlockEnv by deriving the next block's excess blob gas from the parent header:

crates/proof/executor/src/builder/env.rs:

let (params, fraction) = if spec_id.is_enabled_in(OpSpecId::ISTHMUS) {
    (Some(BlobParams::prague()), BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE)
} else if spec_id.is_enabled_in(OpSpecId::ECOTONE) {
    (Some(BlobParams::cancun()), BLOB_BASE_FEE_UPDATE_FRACTION_CANCUN)
} else {
    (None, 0)
};

let blob_excess_gas_and_price = parent_header
    .maybe_next_block_excess_blob_gas(params)
    .or_else(|| spec_id.is_enabled_in(OpSpecId::ECOTONE).then_some(0))
    .map(|excess| BlobExcessGasAndPrice::new(excess, fraction));

That is no longer valid after Jovian. Base repurposes header.blob_gas_used to store the DA footprint, while consensus keeps excess_blob_gas fixed at zero. The production execution node reflects this by hard-coding every post-Cancun BlockEnv to:

in both block execution and Engine API payload validation:

  • crates/execution/evm/src/lib.rs:81-84

  • crates/execution/evm/src/lib.rs:112-115

  • crates/execution/evm/src/lib.rs:292-295

https://github.com/base/base/blob/4b389f03b4963ec7bbb4d024445af6281a90d9a8/crates/execution/evm/src/lib.rs

The proof executor instead feeds parent.blob_gas_used into Ethereum's maybe_next_block_excess_blob_gas calculation. For a Jovian parent with a high DA footprint, the FPVM executes the next block with a non-zero excess blob gas and a BLOBBASEFEE greater than 1, while real Base nodes execute the same block with BLOBBASEFEE == 1.

Impact Details

This is a proof soundness break. A proposer can construct a valid L2 parent with high Jovian DA footprint, then include a child transaction or contract path that reads the BLOBBASEFEE opcode. The production node computes state using BLOBBASEFEE == 1, while the fault-proof program computes a different state root/output root from the same L1 data.

An invalid output root matching the buggy FPVM execution can survive challenge because challengers and on-chain fault proofs use the same divergent execution logic. Conversely, valid canonical outputs for affected blocks become unprovable by this FPVM.

References

https://github.com/base/base/blob/4b389f03b4963ec7bbb4d024445af6281a90d9a8/crates/proof/executor/src/builder/env.rs#L95-L116

Proof of Concept

Put this under crates/proof/executor/src/builder/env.rs. and uses the same RollupConfig import as production.

Expected behavior: After Jovian, Base execution treats header.blob_gas_used as DA footprint and keeps the EVM blob gas price fixed at 1. The FPVM should therefore build the child BlockEnv with BLOBBASEFEE = 1.

Actual behavior: The FPVM passes the Jovian parent header into maybe_next_block_excess_blob_gas(). Because parent.blob_gas_used now stores DA footprint, a high-DA parent makes the FPVM compute BLOBBASEFEE > 1.

Impact: Any child transaction that reads BLOBBASEFEE can execute differently in the FPVM than on production Base nodes, producing different state/output roots from the same L1 data.

Was this helpful?