Skip to content

refactor(levm): decluttering vm.rs #2733

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 14 commits into from
May 13, 2025
7 changes: 2 additions & 5 deletions cmd/ef_tests/state/runner/levm_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,13 @@ use ethrex_common::{
use ethrex_levm::{
db::gen_db::GeneralizedDatabase,
errors::{ExecutionReport, TxValidationError, VMError},
vm::{EVMConfig, VM},
Environment,
vm::VM,
EVMConfig, Environment,
};
use ethrex_rlp::encode::RLPEncode;
use ethrex_storage::AccountUpdate;
use ethrex_vm::backends;
use keccak_hash::keccak;
use std::collections::HashMap;

pub async fn run_ef_test(test: &EFTest) -> Result<EFTestReport, EFTestRunnerError> {
// There are some tests that don't have a hash, unwrap will panic
Expand Down Expand Up @@ -168,7 +167,6 @@ pub fn prepare_vm_for_tx<'a>(
VM::new(
Environment {
origin: test_tx.sender,
refunded_gas: 0,
gas_limit: test_tx.gas_limit,
config,
block_number: test.env.current_number,
Expand All @@ -187,7 +185,6 @@ pub fn prepare_vm_for_tx<'a>(
tx_max_fee_per_blob_gas: test_tx.max_fee_per_blob_gas,
tx_nonce: test_tx.nonce,
block_gas_limit: test.env.current_gas_limit,
transient_storage: HashMap::new(),
is_privileged: false,
},
db,
Expand Down
8 changes: 2 additions & 6 deletions crates/vm/backends/levm/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,10 @@ use ethrex_common::{
};
use ethrex_levm::db::gen_db::GeneralizedDatabase;
use ethrex_levm::errors::TxValidationError;
use ethrex_levm::EVMConfig;
use ethrex_levm::{
errors::{ExecutionReport, TxResult, VMError},
vm::{EVMConfig, Substate, VM},
vm::{Substate, VM},
Environment,
};
use ethrex_storage::error::StoreError;
Expand Down Expand Up @@ -115,7 +116,6 @@ impl LEVM {
let config = EVMConfig::new_from_chain_config(&chain_config, block_header);
let env = Environment {
origin: tx_sender,
refunded_gas: 0,
gas_limit: tx.gas_limit(),
config,
block_number: block_header.number.into(),
Expand All @@ -133,7 +133,6 @@ impl LEVM {
tx_max_fee_per_blob_gas: tx.max_fee_per_blob_gas().map(U256::from),
tx_nonce: tx.nonce(),
block_gas_limit: block_header.gas_limit,
transient_storage: HashMap::new(),
difficulty: block_header.difficulty,
is_privileged: matches!(tx, Transaction::PrivilegedL2Transaction(_)),
};
Expand Down Expand Up @@ -596,7 +595,6 @@ pub fn generic_system_contract_levm(
block_excess_blob_gas: block_header.excess_blob_gas.map(U256::from),
block_blob_gas_used: block_header.blob_gas_used.map(U256::from),
block_gas_limit: 30_000_000,
transient_storage: HashMap::new(),
config,
..Default::default()
};
Expand Down Expand Up @@ -711,7 +709,6 @@ fn env_from_generic(
let config = EVMConfig::new_from_chain_config(&chain_config, header);
Ok(Environment {
origin: tx.from.0.into(),
refunded_gas: 0,
gas_limit: tx.gas.unwrap_or(header.gas_limit), // Ensure tx doesn't fail due to gas limit
config,
block_number: header.number.into(),
Expand All @@ -729,7 +726,6 @@ fn env_from_generic(
tx_max_fee_per_blob_gas: tx.max_fee_per_blob_gas,
tx_nonce: tx.nonce.unwrap_or_default(),
block_gas_limit: header.gas_limit,
transient_storage: HashMap::new(),
difficulty: header.difficulty,
is_privileged: false,
})
Expand Down
96 changes: 92 additions & 4 deletions crates/vm/levm/src/environment.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
use ethrex_common::{Address, H256, U256};
use ethrex_common::{
types::{BlockHeader, ChainConfig, Fork, ForkBlobSchedule},
Address, H256, U256,
};

use crate::vm::EVMConfig;
use crate::constants::{
BLOB_BASE_FEE_UPDATE_FRACTION, BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE, MAX_BLOB_COUNT,
MAX_BLOB_COUNT_ELECTRA, TARGET_BLOB_GAS_PER_BLOCK, TARGET_BLOB_GAS_PER_BLOCK_PECTRA,
};

use std::collections::HashMap;
/// [EIP-1153]: https://eips.ethereum.org/EIPS/eip-1153#reference-implementation
Expand All @@ -11,7 +17,6 @@ pub type TransientStorage = HashMap<(Address, U256), U256>;
pub struct Environment {
/// The sender address of the external transaction.
pub origin: Address,
pub refunded_gas: u64,
/// Gas limit of the Transaction
pub gas_limit: u64,
pub config: EVMConfig,
Expand All @@ -32,6 +37,89 @@ pub struct Environment {
pub tx_max_fee_per_blob_gas: Option<U256>,
pub tx_nonce: u64,
pub block_gas_limit: u64,
pub transient_storage: TransientStorage,
pub is_privileged: bool,
}

#[derive(Debug, Clone, Copy)]
/// This struct holds special configuration variables specific to the
/// EVM. In most cases, at least at the time of writing (February
/// 2025), you want to use the default blob_schedule values for the
/// specified Fork. The "intended" way to do this is by using the `EVMConfig::canonical_values(fork: Fork)` function.
///
/// However, that function should NOT be used IF you want to use a
/// custom `ForkBlobSchedule`, like it's described in [EIP-7840](https://eips.ethereum.org/EIPS/eip-7840)
/// Values are determined by [EIP-7691](https://eips.ethereum.org/EIPS/eip-7691#specification)
pub struct EVMConfig {
pub fork: Fork,
pub blob_schedule: ForkBlobSchedule,
}

impl EVMConfig {
pub fn new(fork: Fork, blob_schedule: ForkBlobSchedule) -> EVMConfig {
EVMConfig {
fork,
blob_schedule,
}
}

pub fn new_from_chain_config(chain_config: &ChainConfig, block_header: &BlockHeader) -> Self {
let fork = chain_config.fork(block_header.timestamp);

let blob_schedule = chain_config
.get_fork_blob_schedule(block_header.timestamp)
.unwrap_or_else(|| EVMConfig::canonical_values(fork));

EVMConfig::new(fork, blob_schedule)
}

/// This function is used for running the EF tests. If you don't
/// have acces to a EVMConfig (mainly in the form of a
/// genesis.json file) you can use this function to get the
/// "Default" ForkBlobSchedule for that specific Fork.
/// NOTE: This function could potentially be expanded to include
/// other types of "default"s.
pub fn canonical_values(fork: Fork) -> ForkBlobSchedule {
let max_blobs_per_block: u64 = Self::max_blobs_per_block(fork);
let target: u64 = Self::get_target_blob_gas_per_block_(fork);
let base_fee_update_fraction: u64 = Self::get_blob_base_fee_update_fraction_value(fork);

ForkBlobSchedule {
target,
max: max_blobs_per_block,
base_fee_update_fraction,
}
}

const fn max_blobs_per_block(fork: Fork) -> u64 {
match fork {
Fork::Prague => MAX_BLOB_COUNT_ELECTRA,
Fork::Osaka => MAX_BLOB_COUNT_ELECTRA,
_ => MAX_BLOB_COUNT,
}
}

const fn get_blob_base_fee_update_fraction_value(fork: Fork) -> u64 {
match fork {
Fork::Prague | Fork::Osaka => BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE,
_ => BLOB_BASE_FEE_UPDATE_FRACTION,
}
}

const fn get_target_blob_gas_per_block_(fork: Fork) -> u64 {
match fork {
Fork::Prague | Fork::Osaka => TARGET_BLOB_GAS_PER_BLOCK_PECTRA,
_ => TARGET_BLOB_GAS_PER_BLOCK,
}
}
}

impl Default for EVMConfig {
/// The default EVMConfig depends on the default Fork.
fn default() -> Self {
let fork = core::default::Default::default();
EVMConfig {
fork,
blob_schedule: Self::canonical_values(fork),
}
}
}
16 changes: 8 additions & 8 deletions crates/vm/levm/src/execution_handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use crate::{
gas_cost::CODE_DEPOSIT_COST,
opcodes::Opcode,
utils::*,
vm::{StateBackup, VM},
vm::{Substate, VM},
};

use bytes::Bytes;
Expand All @@ -14,14 +14,14 @@ impl<'a> VM<'a> {
pub fn handle_precompile_result(
&mut self,
precompile_result: Result<Bytes, VMError>,
backup: StateBackup,
backup: Substate,
current_call_frame: &mut CallFrame,
) -> Result<ExecutionReport, VMError> {
match precompile_result {
Ok(output) => Ok(ExecutionReport {
result: TxResult::Success,
gas_used: current_call_frame.gas_used,
gas_refunded: self.env.refunded_gas,
gas_refunded: self.accrued_substate.refunded_gas,
output,
logs: std::mem::take(&mut current_call_frame.logs),
}),
Expand All @@ -35,7 +35,7 @@ impl<'a> VM<'a> {
Ok(ExecutionReport {
result: TxResult::Revert(error),
gas_used: current_call_frame.gas_limit,
gas_refunded: self.env.refunded_gas,
gas_refunded: self.accrued_substate.refunded_gas,
output: Bytes::new(),
logs: vec![],
})
Expand Down Expand Up @@ -153,7 +153,7 @@ impl<'a> VM<'a> {
executed_call_frame: &mut CallFrame,
) -> Result<ExecutionReport, VMError> {
let backup = self
.backups
.substate_backups
.pop()
.ok_or(VMError::Internal(InternalError::CouldNotPopCallframe))?;
// On successful create check output validity
Expand Down Expand Up @@ -207,7 +207,7 @@ impl<'a> VM<'a> {
return Ok(ExecutionReport {
result: TxResult::Revert(error),
gas_used: executed_call_frame.gas_used,
gas_refunded: self.env.refunded_gas,
gas_refunded: self.accrued_substate.refunded_gas,
output: std::mem::take(&mut executed_call_frame.output),
logs: vec![],
});
Expand All @@ -218,7 +218,7 @@ impl<'a> VM<'a> {
Ok(ExecutionReport {
result: TxResult::Success,
gas_used: executed_call_frame.gas_used,
gas_refunded: self.env.refunded_gas,
gas_refunded: self.accrued_substate.refunded_gas,
output: std::mem::take(&mut executed_call_frame.output),
logs: std::mem::take(&mut executed_call_frame.logs),
})
Expand All @@ -230,7 +230,7 @@ impl<'a> VM<'a> {
executed_call_frame: &mut CallFrame,
) -> Result<ExecutionReport, VMError> {
let backup = self
.backups
.substate_backups
.pop()
.ok_or(VMError::Internal(InternalError::CouldNotPopCallframe))?;
if error.should_propagate() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl<'a> VM<'a> {
let key = self.current_call_frame_mut()?.stack.pop()?;
let to = self.current_call_frame()?.to;
let value = self
.env
.accrued_substate
.transient_storage
.get(&(to, key))
.cloned()
Expand Down Expand Up @@ -63,7 +63,9 @@ impl<'a> VM<'a> {
let value = current_call_frame.stack.pop()?;
(key, value, current_call_frame.to)
};
self.env.transient_storage.insert((to, key), value);
self.accrued_substate
.transient_storage
.insert((to, key), value);

Ok(OpcodeResult::Continue { pc_increment: 1 })
}
Expand Down Expand Up @@ -186,7 +188,7 @@ impl<'a> VM<'a> {

// Gas Refunds
// Sync gas refund with global env, ensuring consistency accross contexts.
let mut gas_refunds = self.env.refunded_gas;
let mut gas_refunds = self.accrued_substate.refunded_gas;

// https://eips.ethereum.org/EIPS/eip-2929
let (remove_slot_cost, restore_empty_slot_cost, restore_slot_cost) = (4800, 19900, 2800);
Expand Down Expand Up @@ -224,7 +226,7 @@ impl<'a> VM<'a> {
}
}

self.env.refunded_gas = gas_refunds;
self.accrued_substate.refunded_gas = gas_refunds;

self.current_call_frame_mut()?
.increase_consumed_gas(gas_cost::sstore(
Expand Down
23 changes: 8 additions & 15 deletions crates/vm/levm/src/opcode_handlers/system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::{
memory::{self, calculate_memory_size},
precompiles::is_precompile,
utils::{address_to_word, word_to_address, *},
vm::{StateBackup, VM},
vm::VM,
};
use bytes::Bytes;
use ethrex_common::{
Expand Down Expand Up @@ -725,13 +725,10 @@ impl<'a> VM<'a> {

self.accrued_substate.created_accounts.insert(new_address); // Mostly for SELFDESTRUCT during initcode.

// Backup of Database, Substate, Gas Refunds and Transient Storage if sub-context is reverted
let backup = StateBackup::new(
self.accrued_substate.clone(),
self.env.refunded_gas,
self.env.transient_storage.clone(),
);
self.backups.push(backup);
// Backup of Substate, a copy of the substate to restore if sub-context is reverted
let backup = self.accrued_substate.clone();
self.substate_backups.push(backup);

Ok(OpcodeResult::Continue { pc_increment: 0 })
}

Expand Down Expand Up @@ -831,13 +828,9 @@ impl<'a> VM<'a> {
ret_size,
);
self.call_frames.push(new_call_frame);
// Backup of Database, Substate, Gas Refunds and Transient Storage if sub-context is reverted
let backup = StateBackup::new(
self.accrued_substate.clone(),
self.env.refunded_gas,
self.env.transient_storage.clone(),
);
self.backups.push(backup);
// Backup of Substate, a copy of the substate to restore if sub-context is reverted
let backup = self.accrued_substate.clone();
self.substate_backups.push(backup);

if is_precompile(&code_address, self.env.config.fork) {
let _report = self.run_execution()?;
Expand Down
5 changes: 3 additions & 2 deletions crates/vm/levm/src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ use crate::{
TOTAL_COST_FLOOR_PER_TOKEN, WARM_ADDRESS_ACCESS_COST,
},
opcodes::Opcode,
vm::{EVMConfig, Substate, VM},
vm::{Substate, VM},
EVMConfig,
};
use bytes::Bytes;
use ethrex_common::types::Account;
Expand Down Expand Up @@ -462,7 +463,7 @@ impl<'a> VM<'a> {
self.current_call_frame_mut()?.valid_jump_destinations =
get_valid_jump_destinations(&self.current_call_frame()?.bytecode).unwrap_or_default();

self.env.refunded_gas = refunded_gas;
self.accrued_substate.refunded_gas = refunded_gas;

Ok(())
}
Expand Down
Loading
Loading