Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7a67167
Fix intrinsic/minimal gas calculation for L1 transactions in `process…
antoniolocascio Feb 19, 2026
ac600e5
Clear returndata when reverting
antoniolocascio Feb 19, 2026
a1138d7
Move assertion about blob base fee outside of if block
antoniolocascio Feb 19, 2026
9d54029
Fix L1 simulation native per gas
antoniolocascio Feb 19, 2026
fedc8ff
Update comment
antoniolocascio Feb 19, 2026
f36e4f1
Simplify UsizeDeserializable for B160
antoniolocascio Feb 19, 2026
a9e2b6c
Rename `is_l1_tx` to `is_priority_tx` in the transaction processing c…
antoniolocascio Feb 19, 2026
e7f8f26
Do not charge Ergs in hooks
antoniolocascio Feb 19, 2026
381bb8e
Fix u256_to_usize_saturated
antoniolocascio Feb 19, 2026
48972ef
Add log when gas_limit < intrinsic in L1 tx
antoniolocascio Feb 19, 2026
ae77acd
Reuse allocated records in transient storage
antoniolocascio Feb 19, 2026
46001b2
Check that value is 0 in mint base token hook
antoniolocascio Feb 19, 2026
18f984e
Fix references to EIP-170
antoniolocascio Feb 19, 2026
fc18d3f
Fix debug logs
antoniolocascio Feb 19, 2026
56549ce
Implement missing io methods
antoniolocascio Feb 19, 2026
3c8f35a
Remove UB in aligned_vector
antoniolocascio Feb 19, 2026
4ef2583
Update comment
antoniolocascio Feb 24, 2026
e647c66
Remove dead code
antoniolocascio Feb 24, 2026
3daac53
Rename reverted()
antoniolocascio Feb 24, 2026
c9ef994
Add missing log on saturating arith
antoniolocascio Feb 24, 2026
c2c60b1
Rename clear history map fun and add tests
antoniolocascio Feb 24, 2026
2630281
Add more regression tests
antoniolocascio Feb 24, 2026
0db2015
Merge branch 'dev' into alocascio-audittens-fixes
0xVolosnikov Feb 25, 2026
724f6f2
Update tests using new approach
0xVolosnikov Feb 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ pub fn read_multichain_root<
&MESSAGE_ROOT_ADDRESS,
&root_slot,
)
.expect("must read MessageRoot shared tree height")
.expect("must read MessageRoot multichain root")
}

///
Expand Down
2 changes: 1 addition & 1 deletion basic_bootloader/src/bootloader/block_flow/zk/tx_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ where
block_data
.transaction_hashes_accumulator
.add_tx_hash(&tx_processing_result.tx_hash);
if tx_processing_result.is_l1_tx {
if tx_processing_result.is_priority_tx {
block_data
.enforced_transaction_hashes_accumulator
.add_tx_hash(&tx_processing_result.tx_hash);
Expand Down
8 changes: 4 additions & 4 deletions basic_bootloader/src/bootloader/transaction_flow/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,14 @@ pub enum ExecutionResult<'a, IOTypes: SystemIOTypesConfig> {
}

impl<'a, IOTypes: SystemIOTypesConfig> ExecutionResult<'a, IOTypes> {
pub fn reverted(self) -> Self {
pub fn to_reverted(self) -> Self {
match self {
Self::Success {
output: ExecutionOutput::Call(r),
output: ExecutionOutput::Call(_),
}
| Self::Success {
output: ExecutionOutput::Create(r, _),
} => Self::Revert { output: r },
output: ExecutionOutput::Create(_, _),
} => Self::Revert { output: &[] },
a => a,
}
}
Expand Down
18 changes: 9 additions & 9 deletions basic_bootloader/src/bootloader/transaction_flow/zk/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ pub struct ZkTransactionFlowOnlyEOA<S: EthereumLikeTypes> {
pub struct ZkTxResult<'a> {
pub result: ExecutionResult<'a, EthereumIOTypesConfig>,
pub tx_hash: Bytes32,
pub is_l1_tx: bool,
pub is_priority_tx: bool,
pub is_upgrade_tx: bool,
pub is_service_tx: bool,
pub gas_refunded: u64,
Expand Down Expand Up @@ -136,7 +136,7 @@ impl<S: EthereumLikeTypes> core::fmt::Debug for TxContextForPreAndPostProcessing
.field("native_per_gas", &self.native_per_gas)
.field("tx_gas_limit", &self.tx_gas_limit)
.field("gas_used", &self.gas_used)
.field("gas_refunded", &self.gas_used)
.field("gas_refunded", &self.gas_refunded)
.field("validation_pubdata", &self.validation_pubdata)
.field("total_pubdata", &self.total_pubdata)
.field("native_used", &self.native_used)
Expand Down Expand Up @@ -478,10 +478,10 @@ where
// go to the operator. Base fees are effectively "burned" (not transferred anywhere).
let gas_price_for_operator = if cfg!(feature = "burn_base_fee") {
let base_fee = system.get_eip1559_basefee();
context
.gas_price
.checked_sub(base_fee)
.ok_or(internal_error!("Gas_price - base_fee underflow"))?
// We use saturating arithmetic to allow the caller of this method to
// allow gas_price < base_fee. This can be used, for example, for
// transaction simulation
context.gas_price.saturating_sub(base_fee)
} else {
context.gas_price
};
Expand Down Expand Up @@ -564,7 +564,7 @@ where
ZkTxResult {
result,
tx_hash: context.tx_hash,
is_l1_tx: false,
is_priority_tx: false,
is_upgrade_tx: false,
is_service_tx: transaction.is_service(),
gas_used: context.gas_used,
Expand Down Expand Up @@ -862,10 +862,10 @@ where
Some(context.validation_pubdata),
)?;
if !has_enough {
execution_result = execution_result.reverted();
execution_result = execution_result.to_reverted();
system_log!(system, "Not enough gas for pubdata after execution\n");
Ok((
execution_result.reverted(),
execution_result.to_reverted(),
CachedPubdataInfo {
pubdata_used,
to_charge_for_pubdata,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::bootloader::config::BasicBootloaderExecutionConfig;
use crate::bootloader::constants::{
FREE_L1_TX_NATIVE_PER_GAS, L1_TX_INTRINSIC_NATIVE_COST, L1_TX_INTRINSIC_PUBDATA,
L1_TX_NATIVE_PRICE,
FREE_L1_TX_NATIVE_PER_GAS, L1_TX_INTRINSIC_L2_GAS, L1_TX_INTRINSIC_NATIVE_COST,
L1_TX_INTRINSIC_PUBDATA, L1_TX_NATIVE_PRICE,
};
use crate::bootloader::errors::BootloaderInterfaceError;
use crate::bootloader::errors::TxError;
Expand Down Expand Up @@ -90,7 +90,7 @@ where
native_per_gas,
native_per_pubdata,
minimal_gas_used,
} = prepare_and_check_resources::<S>(
} = prepare_and_check_resources::<S, Config>(
system,
transaction,
is_priority_op,
Expand Down Expand Up @@ -333,7 +333,7 @@ where
Ok(ZkTxResult {
result,
tx_hash,
is_l1_tx: is_priority_op,
is_priority_tx: is_priority_op,
is_upgrade_tx: !is_priority_op,
is_service_tx: false,
gas_used,
Expand Down Expand Up @@ -365,7 +365,11 @@ struct ResourceAndFeeInfo<S: EthereumLikeTypes> {
/// The approach is to use saturating arithmetic and emit a system
/// log if this situation ever happens.
///
fn prepare_and_check_resources<'a, S: EthereumLikeTypes + 'a>(
fn prepare_and_check_resources<
'a,
S: EthereumLikeTypes + 'a,
Config: BasicBootloaderExecutionConfig,
>(
system: &mut System<S>,
transaction: &AbiEncodedTransaction<S::Allocator>,
is_priority_op: bool,
Expand All @@ -382,15 +386,24 @@ where
let native_price = L1_TX_NATIVE_PRICE;
let native_per_gas = if is_priority_op {
if gas_price.is_zero() {
FREE_L1_TX_NATIVE_PER_GAS
} else {
u256_try_to_u64(&gas_price.div_ceil(native_price))
.unwrap_or_else(|| {
system_log!(
system,
"Native per gas calculation for L1 tx overflows, using saturated arithmetic instead");
if Config::SIMULATION {
u256_try_to_u64(&system.get_eip1559_basefee().div_ceil(native_price))
.unwrap_or_else(|| {
system_log!(
system,
"Native per gas calculation for L1 tx overflows, using saturated arithmetic instead");
u64::MAX
})
})
} else {
FREE_L1_TX_NATIVE_PER_GAS
}
} else {
u256_try_to_u64(&gas_price.div_ceil(native_price)).unwrap_or_else(|| {
system_log!(
system,
"Native per gas calculation for L1 tx overflows, using saturated arithmetic instead");
u64::MAX
})
}
} else {
// Upgrade txs are paid by the protocol, so we use a fixed native per gas
Expand All @@ -406,9 +419,15 @@ where
u64::MAX
});

let native_prepaid_from_gas = native_per_gas.saturating_mul(gas_limit);
let native_prepaid_from_gas = native_per_gas.checked_mul(gas_limit)
.unwrap_or_else(|| {
system_log!(
system,
"Native prepaid from gas calculation for L1 tx overflows, using saturated arithmetic instead");
u64::MAX
});

let (calldata_tokens, intrinsic_cost) =
let (calldata_tokens, minimal_gas_used) =
compute_calldata_tokens(system, transaction.calldata(), true);

// With L1ResourcesPolicy, this returns Result<ResourcesForTx<S>, BootloaderSubsystemError>
Expand All @@ -422,14 +441,21 @@ where
false, // is_deployment
transaction.calldata().len() as u64,
calldata_tokens,
intrinsic_cost,
L1_TX_INTRINSIC_L2_GAS,
L1_TX_INTRINSIC_PUBDATA,
L1_TX_INTRINSIC_NATIVE_COST,
)?;

// L1 transactions might have a gas limit < intrinsic cost,
// so we pick the min as minimal_gas_used.
let minimal_gas_used = intrinsic_cost.min(gas_limit);
// L1 transactions might have a gas limit < minimal_gas_used. This should be
// prevented by L1 validation, but we log and saturate if it happens.
if gas_limit < minimal_gas_used {
system_log!(
system,
"L1 tx gas limit below intrinsic cost, using saturated arithmetic instead"
);
}
// Pick the min to keep processing L1 txs even if the L1 validation is wrong.
let minimal_gas_used = minimal_gas_used.min(gas_limit);

Ok(ResourceAndFeeInfo {
resources,
Expand Down Expand Up @@ -578,7 +604,7 @@ where
check_enough_resources_for_pubdata(system, native_per_pubdata, resources, None)?;
let execution_result = if !enough {
system_log!(system, "Not enough gas for pubdata after execution\n");
execution_result.reverted()
execution_result.to_reverted()
} else {
execution_result
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,18 +322,19 @@ where
// Parse blobs, if any
// No need to feature gate this part, as blobs() should return an empty list
// for non-EIP4844 transactions.
let block_base_fee_per_blob_gas = system.get_blob_base_fee_per_gas();

#[cfg(not(feature = "eip-4844"))]
crate::require_internal!(
block_base_fee_per_blob_gas == U256::ONE,
"Blob base fee should be set to 1 if EIP 4844 is disabled",
system
)?;

let blobs = if let Some(blobs_list) = transaction.blobs() {
let tx_max_fee_per_blob_gas = transaction.max_fee_per_blob_gas().ok_or(internal_error!(
"Tx with blobs must define max_fee_per_blob_gas"
))?;
let block_base_fee_per_blob_gas = system.get_blob_base_fee_per_gas();

#[cfg(not(feature = "eip-4844"))]
crate::require_internal!(
block_base_fee_per_blob_gas == U256::ONE,
"Blob base fee should be set to 1 if EIP 4844 is disabled",
system
)?;

if &block_base_fee_per_blob_gas > tx_max_fee_per_blob_gas && !Config::SIMULATION {
return Err(TxError::Validation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ use crate::system_implementation::ethereum_storage_model::caches::full_storage_c
use crate::system_implementation::ethereum_storage_model::caches::preimage::BytecodeKeccakPreimagesStorage;
use crate::system_implementation::ethereum_storage_model::persist_changes::EthereumStoragePersister;
use core::alloc::Allocator;
use ruint::aliases::B160;
use storage_models::common_structs::snapshottable_io::SnapshottableIo;
use storage_models::common_structs::StorageCacheModel;
use storage_models::common_structs::StorageModel;
Expand Down Expand Up @@ -374,34 +373,6 @@ impl<
);
}

type AccountAddress<'a>
= &'a B160
where
Self: 'a;
type AccountDiff<'a>
= BasicAccountDiff<Self::IOTypes>
where
Self: 'a;

fn get_account_diff<'a>(
&'a self,
_address: Self::AccountAddress<'a>,
) -> Option<Self::AccountDiff<'a>> {
None
}
fn accounts_diffs_iterator<'a>(
&'a self,
) -> impl ExactSizeIterator<Item = (Self::AccountAddress<'a>, Self::AccountDiff<'a>)> + Clone
{
self.account_cache.cache.iter().map(|v| {
let current = v.current().value();
(
v.key().as_ref(),
(current.nonce, current.balance, current.bytecode_hash),
)
})
}

type StorageKey<'a>
= &'a WarmStorageKey
where
Expand Down
23 changes: 0 additions & 23 deletions basic_system/src/system_implementation/flat_storage_model/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ use storage_models::common_structs::StorageCacheModel;
use storage_models::common_structs::StorageModel;
use zk_ee::system::errors::internal::InternalError;
use zk_ee::system::BalanceSubsystemError;
use zk_ee::system::BasicAccountDiff;
use zk_ee::system::DeconstructionSubsystemError;
use zk_ee::system::NonceSubsystemError;
use zk_ee::system::Resources;
Expand Down Expand Up @@ -439,28 +438,6 @@ impl<
.expect("must report preimages");
}

type AccountAddress<'a>
= &'a B160
where
Self: 'a;
type AccountDiff<'a>
= BasicAccountDiff<Self::IOTypes>
where
Self: 'a;

fn get_account_diff<'a>(
&'a self,
_address: Self::AccountAddress<'a>,
) -> Option<Self::AccountDiff<'a>> {
None
}
fn accounts_diffs_iterator<'a>(
&'a self,
) -> impl ExactSizeIterator<Item = (Self::AccountAddress<'a>, Self::AccountDiff<'a>)> + Clone
{
[].into_iter()
}

type StorageKey<'a>
= &'a WarmStorageKey
where
Expand Down
22 changes: 0 additions & 22 deletions basic_system/src/system_implementation/system/io_subsystem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -769,28 +769,6 @@ impl<
self.storage.report_new_preimages(result_keeper);
}

type AccountAddress<'a>
= M::AccountAddress<'a>
where
Self: 'a;
type AccountDiff<'a>
= M::AccountDiff<'a>
where
Self: 'a;

fn get_account_diff<'a>(
&'a self,
address: Self::AccountAddress<'a>,
) -> Option<Self::AccountDiff<'a>> {
self.storage.get_account_diff(address)
}
fn accounts_diffs_iterator<'a>(
&'a self,
) -> impl ExactSizeIterator<Item = (Self::AccountAddress<'a>, Self::AccountDiff<'a>)> + Clone
{
self.storage.accounts_diffs_iterator()
}

type StorageKey<'a>
= M::StorageKey<'a>
where
Expand Down
2 changes: 1 addition & 1 deletion docs/system_hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ The hook accepts the following ABI-encoded parameters:
- `bytes32` - observable bytecode hash

Key features:
- Enforces EIP-158 by rejecting bytecode longer than 24576 bytes
- Enforces EIP-170 by rejecting bytecode longer than 24576 bytes
- Used exclusively for protocol upgrades approved by governance
- Does not publish full bytecode in pubdata to fit within gas/calldata limits
- Bytecodes are published separately via Ethereum calldata
Expand Down
2 changes: 1 addition & 1 deletion evm_interpreter/src/interpreter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,7 @@ impl<'ee, S: EthereumLikeTypes> Interpreter<'ee, S> {
let deployed_code = return_values.returndata;
let mut error_after_constructor = None;
if deployed_code.len() > MAX_CODE_SIZE {
// EIP-158: reject code of length > 24576.
// EIP-170: reject code of length > 24576.
error_after_constructor = Some(EvmError::CreateContractSizeLimit)
} else if !deployed_code.is_empty() && deployed_code[0] == 0xEF {
// EIP-3541: reject code starting with 0xEF.
Expand Down
2 changes: 1 addition & 1 deletion forward_system/src/run/query_processors/tx_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub struct TxDataResponder<TS: TxSource> {
/// Note: we use different fields for next_tx and next_tx_format
/// so that they don't have to be consumed at the same time.
pub next_tx_format: Option<TxEncodingFormat>,
/// Cached next transaction format, populated after size query
/// Cached next transaction from, populated after size query
/// (if present)
pub next_tx_from: Option<B160>,
}
Expand Down
2 changes: 1 addition & 1 deletion forward_system/src/system/system_types/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ impl<O: IOOracle> SystemTypes for ForwardSystemTypes<O> {
Self::Resources,
EthereumLikeStorageAccessCostModel,
VecStackFactory,
0, // Stack limit (0 = unlimited)
0, // VecStackFactory ignores N (node size), so 0 is fine here
O, // Oracle implementation
FlatTreeWithAccountsUnderHashesStorageModel<
Self::Allocator,
Expand Down
Loading
Loading