diff --git a/crates/vm/levm/bench/revm_comparison/src/lib.rs b/crates/vm/levm/bench/revm_comparison/src/lib.rs index f8a13e563d..74b35d597e 100644 --- a/crates/vm/levm/bench/revm_comparison/src/lib.rs +++ b/crates/vm/levm/bench/revm_comparison/src/lib.rs @@ -1,5 +1,5 @@ use bytes::Bytes; -use ethrex_levm::{call_frame::CallFrame, errors::TxResult, utils::new_vm_with_bytecode}; +use ethrex_levm::{call_frame::CallFrame, errors::TxResult, testing::new_vm_with_bytecode}; use revm::{ db::BenchmarkDB, primitives::{address, Bytecode, TransactTo}, diff --git a/crates/vm/levm/src/call_frame.rs b/crates/vm/levm/src/call_frame.rs index 1adaa113d9..9fec39ec79 100644 --- a/crates/vm/levm/src/call_frame.rs +++ b/crates/vm/levm/src/call_frame.rs @@ -3,7 +3,7 @@ use crate::{ errors::{InternalError, VMError}, memory::Memory, opcodes::Opcode, - vm::get_valid_jump_destinations, + utils::get_valid_jump_destinations, }; use bytes::Bytes; use ethrex_core::{types::Log, Address, U256}; diff --git a/crates/vm/levm/src/lib.rs b/crates/vm/levm/src/lib.rs index 1ee2da0e0b..eae8bc2d45 100644 --- a/crates/vm/levm/src/lib.rs +++ b/crates/vm/levm/src/lib.rs @@ -10,6 +10,7 @@ pub mod opcode_handlers; pub mod opcodes; pub mod operations; pub mod precompiles; +pub mod testing; pub mod utils; pub mod vm; pub use account::*; diff --git a/crates/vm/levm/src/opcode_handlers/block.rs b/crates/vm/levm/src/opcode_handlers/block.rs index 3b6e05bcb9..c0f52a02a1 100644 --- a/crates/vm/levm/src/opcode_handlers/block.rs +++ b/crates/vm/levm/src/opcode_handlers/block.rs @@ -3,7 +3,8 @@ use crate::{ constants::LAST_AVAILABLE_BLOCK_LIMIT, errors::{InternalError, OpcodeSuccess, VMError}, gas_cost, - vm::{address_to_word, VM}, + utils::*, + vm::VM, }; use ethrex_core::{ types::{BLOB_BASE_FEE_UPDATE_FRACTION, MIN_BASE_FEE_PER_BLOB_GAS}, @@ -137,7 +138,9 @@ impl VM { ) -> Result { self.increase_consumed_gas(current_call_frame, gas_cost::SELFBALANCE)?; - let balance = self.get_account(current_call_frame.to).info.balance; + let balance = get_account(&mut self.cache, &self.db, current_call_frame.to) + .info + .balance; current_call_frame.stack.push(balance)?; Ok(OpcodeSuccess::Continue) diff --git a/crates/vm/levm/src/opcode_handlers/environment.rs b/crates/vm/levm/src/opcode_handlers/environment.rs index fd855db926..ae1c4c8ebe 100644 --- a/crates/vm/levm/src/opcode_handlers/environment.rs +++ b/crates/vm/levm/src/opcode_handlers/environment.rs @@ -4,7 +4,8 @@ use crate::{ errors::{InternalError, OpcodeSuccess, VMError}, gas_cost::{self}, memory::{self, calculate_memory_size}, - vm::{has_delegation, word_to_address, VM}, + utils::{has_delegation, word_to_address}, + vm::VM, }; use ethrex_core::U256; use keccak_hash::keccak; diff --git a/crates/vm/levm/src/opcode_handlers/system.rs b/crates/vm/levm/src/opcode_handlers/system.rs index 7997b9f2c4..3af13ae59d 100644 --- a/crates/vm/levm/src/opcode_handlers/system.rs +++ b/crates/vm/levm/src/opcode_handlers/system.rs @@ -5,7 +5,9 @@ use crate::{ errors::{InternalError, OpcodeSuccess, OutOfGasError, ResultReason, TxResult, VMError}, gas_cost::{self, max_message_call_gas}, memory::{self, calculate_memory_size}, - vm::{address_to_word, word_to_address, VM}, + utils::*, + utils::{address_to_word, word_to_address}, + vm::VM, Account, }; use bytes::Bytes; @@ -51,8 +53,12 @@ impl VM { let (account_info, address_was_cold) = self.access_account(callee); - let (is_delegation, eip7702_gas_consumed, code_address, bytecode) = - self.eip7702_get_code(callee)?; + let (is_delegation, eip7702_gas_consumed, code_address, bytecode) = eip7702_get_code( + &mut self.cache, + &self.db, + &mut self.accrued_substate, + callee, + )?; let gas_left = current_call_frame .gas_limit @@ -130,8 +136,12 @@ impl VM { let (_account_info, address_was_cold) = self.access_account(code_address); - let (is_delegation, eip7702_gas_consumed, code_address, bytecode) = - self.eip7702_get_code(code_address)?; + let (is_delegation, eip7702_gas_consumed, code_address, bytecode) = eip7702_get_code( + &mut self.cache, + &self.db, + &mut self.accrued_substate, + code_address, + )?; let gas_left = current_call_frame .gas_limit @@ -237,8 +247,12 @@ impl VM { calculate_memory_size(return_data_start_offset, return_data_size)?; let new_memory_size = new_memory_size_for_args.max(new_memory_size_for_return_data); - let (is_delegation, eip7702_gas_consumed, code_address, bytecode) = - self.eip7702_get_code(code_address)?; + let (is_delegation, eip7702_gas_consumed, code_address, bytecode) = eip7702_get_code( + &mut self.cache, + &self.db, + &mut self.accrued_substate, + code_address, + )?; let gas_left = current_call_frame .gas_limit @@ -312,8 +326,12 @@ impl VM { calculate_memory_size(return_data_start_offset, return_data_size)?; let new_memory_size = new_memory_size_for_args.max(new_memory_size_for_return_data); - let (is_delegation, eip7702_gas_consumed, _, bytecode) = - self.eip7702_get_code(code_address)?; + let (is_delegation, eip7702_gas_consumed, _, bytecode) = eip7702_get_code( + &mut self.cache, + &self.db, + &mut self.accrued_substate, + code_address, + )?; let gas_left = current_call_frame .gas_limit @@ -503,8 +521,18 @@ impl VM { // [EIP-6780] - SELFDESTRUCT only in same transaction from CANCUN if self.env.spec_id >= SpecId::CANCUN { - self.increase_account_balance(target_address, balance_to_transfer)?; - self.decrease_account_balance(current_call_frame.to, balance_to_transfer)?; + increase_account_balance( + &mut self.cache, + &mut self.db, + target_address, + balance_to_transfer, + )?; + decrease_account_balance( + &mut self.cache, + &mut self.db, + current_call_frame.to, + balance_to_transfer, + )?; // Selfdestruct is executed in the same transaction as the contract was created if self @@ -513,15 +541,24 @@ impl VM { .contains(¤t_call_frame.to) { // If target is the same as the contract calling, Ether will be burnt. - self.get_account_mut(current_call_frame.to)?.info.balance = U256::zero(); + get_account_mut_vm(&mut self.cache, &self.db, current_call_frame.to)? + .info + .balance = U256::zero(); self.accrued_substate .selfdestruct_set .insert(current_call_frame.to); } } else { - self.increase_account_balance(target_address, balance_to_transfer)?; - self.get_account_mut(current_call_frame.to)?.info.balance = U256::zero(); + increase_account_balance( + &mut self.cache, + &mut self.db, + target_address, + balance_to_transfer, + )?; + get_account_mut_vm(&mut self.cache, &self.db, current_call_frame.to)? + .info + .balance = U256::zero(); self.accrued_substate .selfdestruct_set @@ -571,8 +608,8 @@ impl VM { ); let new_address = match salt { - Some(salt) => Self::calculate_create2_address(deployer_address, &code, salt)?, - None => Self::calculate_create_address(deployer_address, deployer_account_info.nonce)?, + Some(salt) => calculate_create2_address(deployer_address, &code, salt)?, + None => calculate_create_address(deployer_address, deployer_account_info.nonce)?, }; // touch account @@ -601,9 +638,9 @@ impl VM { } // THIRD: Validations that push 0 to the stack without returning reserved gas but incrementing deployer's nonce - let new_account = self.get_account(new_address); + let new_account = get_account(&mut self.cache, &self.db, new_address); if new_account.has_code_or_nonce() { - self.increment_account_nonce(deployer_address)?; + increment_account_nonce(&mut self.cache, &self.db, deployer_address)?; current_call_frame.stack.push(CREATE_DEPLOYMENT_FAIL)?; return Ok(OpcodeSuccess::Continue); } @@ -620,10 +657,15 @@ impl VM { cache::insert_account(&mut self.cache, new_address, new_account); // 2. Increment sender's nonce. - self.increment_account_nonce(deployer_address)?; + increment_account_nonce(&mut self.cache, &self.db, deployer_address)?; // 3. Decrease sender's balance. - self.decrease_account_balance(deployer_address, value_in_wei_to_send)?; + decrease_account_balance( + &mut self.cache, + &mut self.db, + deployer_address, + value_in_wei_to_send, + )?; let mut new_call_frame = CallFrame::new( deployer_address, @@ -662,7 +704,12 @@ impl VM { } TxResult::Revert(err) => { // Return value to sender - self.increase_account_balance(deployer_address, value_in_wei_to_send)?; + increase_account_balance( + &mut self.cache, + &mut self.db, + deployer_address, + value_in_wei_to_send, + )?; // Deployment failed so account shouldn't exist cache::remove_account(&mut self.cache, &new_address); @@ -757,8 +804,8 @@ impl VM { // Transfer value from caller to callee. if should_transfer_value { - self.decrease_account_balance(msg_sender, value)?; - self.increase_account_balance(to, value)?; + decrease_account_balance(&mut self.cache, &mut self.db, msg_sender, value)?; + increase_account_balance(&mut self.cache, &mut self.db, to, value)?; } let tx_report = self.execute(&mut new_call_frame)?; @@ -791,8 +838,8 @@ impl VM { TxResult::Revert(_) => { // Revert value transfer if should_transfer_value { - self.decrease_account_balance(to, value)?; - self.increase_account_balance(msg_sender, value)?; + decrease_account_balance(&mut self.cache, &mut self.db, to, value)?; + increase_account_balance(&mut self.cache, &mut self.db, msg_sender, value)?; } // Push 0 to stack current_call_frame.stack.push(REVERT_FOR_CALL)?; diff --git a/crates/vm/levm/src/testing.rs b/crates/vm/levm/src/testing.rs new file mode 100644 index 0000000000..ffebb9e396 --- /dev/null +++ b/crates/vm/levm/src/testing.rs @@ -0,0 +1,109 @@ +use crate::{ + account::{Account, AccountInfo}, + db::{cache, CacheDB, Db}, + environment::Environment, + errors::{InternalError, VMError}, + operations::Operation, + vm::VM, +}; +use bytes::Bytes; +use ethrex_core::{types::TxKind, Address, U256}; +use std::{collections::HashMap, sync::Arc}; + +pub fn ops_to_bytecode(operations: &[Operation]) -> Result { + let mut bytecode = Vec::new(); + for op in operations { + bytecode.extend_from_slice( + &op.to_bytecode() + .map_err(|_| VMError::Internal(InternalError::UtilsError))?, + ); // for now it is just a utils error... + } + Ok(bytecode.into()) +} + +pub fn new_vm_with_bytecode(bytecode: Bytes) -> Result { + new_vm_with_ops_addr_bal_db( + bytecode, + Address::from_low_u64_be(100), + U256::MAX, + Db::new(), + CacheDB::default(), + ) +} + +pub fn new_vm_with_ops(operations: &[Operation]) -> Result { + let bytecode = ops_to_bytecode(operations)?; + new_vm_with_ops_addr_bal_db( + bytecode, + Address::from_low_u64_be(100), + U256::MAX, + Db::new(), + CacheDB::default(), + ) +} + +pub fn new_vm_with_ops_db(operations: &[Operation], db: Db) -> Result { + let bytecode = ops_to_bytecode(operations)?; + new_vm_with_ops_addr_bal_db( + bytecode, + Address::from_low_u64_be(100), + U256::MAX, + db, + CacheDB::default(), + ) +} + +/// This function is for testing purposes only. +pub fn new_vm_with_ops_addr_bal_db( + contract_bytecode: Bytes, + sender_address: Address, + sender_balance: U256, + mut db: Db, + mut cache: CacheDB, +) -> Result { + let accounts = [ + // This is the contract account that is going to be executed + ( + Address::from_low_u64_be(42), + Account { + info: AccountInfo { + nonce: 0, + balance: U256::MAX, + bytecode: contract_bytecode, + }, + storage: HashMap::new(), + }, + ), + ( + // This is the sender account + sender_address, + Account { + info: AccountInfo { + nonce: 0, + balance: sender_balance, + bytecode: Bytes::default(), + }, + storage: HashMap::new(), + }, + ), + ]; + + db.add_accounts(accounts.to_vec()); + + // add to cache accounts from list accounts + cache::insert_account(&mut cache, accounts[0].0, accounts[0].1.clone()); + cache::insert_account(&mut cache, accounts[1].0, accounts[1].1.clone()); + + let env = Environment::default_from_address(sender_address); + + VM::new( + TxKind::Call(Address::from_low_u64_be(42)), + env, + Default::default(), + Default::default(), + Arc::new(db), + cache, + Vec::new(), + None, + ) +} diff --git a/crates/vm/levm/src/utils.rs b/crates/vm/levm/src/utils.rs index ffebb9e396..beae2e8e98 100644 --- a/crates/vm/levm/src/utils.rs +++ b/crates/vm/levm/src/utils.rs @@ -1,109 +1,593 @@ use crate::{ - account::{Account, AccountInfo}, - db::{cache, CacheDB, Db}, - environment::Environment, - errors::{InternalError, VMError}, - operations::Operation, - vm::VM, + account::Account, + call_frame::CallFrame, + constants::*, + db::{ + cache::{self}, + CacheDB, Database, + }, + errors::{InternalError, OutOfGasError, VMError}, + gas_cost::{ + self, fake_exponential, ACCESS_LIST_ADDRESS_COST, ACCESS_LIST_STORAGE_KEY_COST, + BLOB_GAS_PER_BLOB, COLD_ADDRESS_ACCESS_COST, CREATE_BASE_COST, WARM_ADDRESS_ACCESS_COST, + }, + opcodes::Opcode, + vm::{AccessList, AuthorizationList, AuthorizationTuple, Substate}, + AccountInfo, }; use bytes::Bytes; -use ethrex_core::{types::TxKind, Address, U256}; -use std::{collections::HashMap, sync::Arc}; - -pub fn ops_to_bytecode(operations: &[Operation]) -> Result { - let mut bytecode = Vec::new(); - for op in operations { - bytecode.extend_from_slice( - &op.to_bytecode() - .map_err(|_| VMError::Internal(InternalError::UtilsError))?, - ); // for now it is just a utils error... +use ethrex_core::{Address, H256, U256}; +use ethrex_rlp; +use ethrex_rlp::encode::RLPEncode; +use keccak_hash::keccak; +use libsecp256k1::{Message, RecoveryId, Signature}; +use revm_primitives::SpecId; +use sha3::{Digest, Keccak256}; +use std::{ + collections::{HashMap, HashSet}, + sync::Arc, +}; +pub type Storage = HashMap; + +// ================== Address related functions ====================== +pub fn address_to_word(address: Address) -> U256 { + // This unwrap can't panic, as Address are 20 bytes long and U256 use 32 bytes + let mut word = [0u8; 32]; + + for (word_byte, address_byte) in word.iter_mut().skip(12).zip(address.as_bytes().iter()) { + *word_byte = *address_byte; } - Ok(bytecode.into()) + + U256::from_big_endian(&word) } -pub fn new_vm_with_bytecode(bytecode: Bytes) -> Result { - new_vm_with_ops_addr_bal_db( - bytecode, - Address::from_low_u64_be(100), - U256::MAX, - Db::new(), - CacheDB::default(), - ) +/// Calculates the address of a new conctract using the CREATE +/// opcode as follows: +/// +/// address = keccak256(rlp([sender_address,sender_nonce]))[12:] +pub fn calculate_create_address( + sender_address: Address, + sender_nonce: u64, +) -> Result { + let mut encoded = Vec::new(); + (sender_address, sender_nonce).encode(&mut encoded); + let mut hasher = Keccak256::new(); + hasher.update(encoded); + Ok(Address::from_slice(hasher.finalize().get(12..).ok_or( + VMError::Internal(InternalError::CouldNotComputeCreateAddress), + )?)) } -pub fn new_vm_with_ops(operations: &[Operation]) -> Result { - let bytecode = ops_to_bytecode(operations)?; - new_vm_with_ops_addr_bal_db( - bytecode, - Address::from_low_u64_be(100), - U256::MAX, - Db::new(), - CacheDB::default(), - ) +/// Calculates the address of a new contract using the CREATE2 opcode as follow +/// +/// initialization_code = memory[offset:offset+size] +/// +/// address = keccak256(0xff + sender_address + salt + keccak256(initialization_code))[12:] +/// +pub fn calculate_create2_address( + sender_address: Address, + initialization_code: &Bytes, + salt: U256, +) -> Result { + let init_code_hash = keccak(initialization_code); + + let generated_address = Address::from_slice( + keccak( + [ + &[0xff], + sender_address.as_bytes(), + &salt.to_big_endian(), + init_code_hash.as_bytes(), + ] + .concat(), + ) + .as_bytes() + .get(12..) + .ok_or(VMError::Internal( + InternalError::CouldNotComputeCreate2Address, + ))?, + ); + Ok(generated_address) } -pub fn new_vm_with_ops_db(operations: &[Operation], db: Db) -> Result { - let bytecode = ops_to_bytecode(operations)?; - new_vm_with_ops_addr_bal_db( - bytecode, - Address::from_low_u64_be(100), - U256::MAX, - db, - CacheDB::default(), - ) +pub fn get_valid_jump_destinations(code: &Bytes) -> Result, VMError> { + let mut valid_jump_destinations = HashSet::new(); + let mut pc = 0; + + while let Some(&opcode_number) = code.get(pc) { + let current_opcode = Opcode::from(opcode_number); + + if current_opcode == Opcode::JUMPDEST { + // If current opcode is jumpdest, add it to valid destinations set + valid_jump_destinations.insert(pc); + } else if (Opcode::PUSH1..=Opcode::PUSH32).contains(¤t_opcode) { + // If current opcode is push, skip as many positions as the size of the push + let size_to_push = + opcode_number + .checked_sub(u8::from(Opcode::PUSH1)) + .ok_or(VMError::Internal( + InternalError::ArithmeticOperationUnderflow, + ))?; + let skip_length = usize::from(size_to_push.checked_add(1).ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, + ))?); + pc = pc.checked_add(skip_length).ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, // to fail, pc should be at least usize max - 31 + ))?; + } + + pc = pc.checked_add(1).ok_or(VMError::Internal( + InternalError::ArithmeticOperationOverflow, // to fail, code len should be more than usize max + ))?; + } + + Ok(valid_jump_destinations) } -/// This function is for testing purposes only. -pub fn new_vm_with_ops_addr_bal_db( - contract_bytecode: Bytes, - sender_address: Address, - sender_balance: U256, - mut db: Db, - mut cache: CacheDB, -) -> Result { - let accounts = [ - // This is the contract account that is going to be executed - ( - Address::from_low_u64_be(42), - Account { - info: AccountInfo { - nonce: 0, - balance: U256::MAX, - bytecode: contract_bytecode, - }, +// ================== Account related functions ===================== +/// Gets account, first checking the cache and then the database (caching in the second case) +pub fn get_account(cache: &mut CacheDB, db: &Arc, address: Address) -> Account { + match cache::get_account(cache, &address) { + Some(acc) => acc.clone(), + None => { + let account_info = db.get_account_info(address); + let account = Account { + info: account_info, storage: HashMap::new(), - }, - ), - ( - // This is the sender account - sender_address, + }; + cache::insert_account(cache, address, account.clone()); + account + } + } +} + +pub fn get_account_no_push_cache( + cache: &CacheDB, + db: &Arc, + address: Address, +) -> Account { + match cache::get_account(cache, &address) { + Some(acc) => acc.clone(), + None => { + let account_info = db.get_account_info(address); Account { - info: AccountInfo { - nonce: 0, - balance: sender_balance, - bytecode: Bytes::default(), - }, + info: account_info, storage: HashMap::new(), - }, - ), - ]; - - db.add_accounts(accounts.to_vec()); - - // add to cache accounts from list accounts - cache::insert_account(&mut cache, accounts[0].0, accounts[0].1.clone()); - cache::insert_account(&mut cache, accounts[1].0, accounts[1].1.clone()); - - let env = Environment::default_from_address(sender_address); - - VM::new( - TxKind::Call(Address::from_low_u64_be(42)), - env, - Default::default(), - Default::default(), - Arc::new(db), - cache, - Vec::new(), - None, + } + } + } +} + +pub fn get_account_mut_vm<'vm>( + cache: &'vm mut CacheDB, + db: &'vm Arc, + address: Address, +) -> Result<&'vm mut Account, VMError> { + if !cache::is_account_cached(cache, &address) { + let account_info = db.get_account_info(address); + let account = Account { + info: account_info, + storage: HashMap::new(), + }; + cache::insert_account(cache, address, account.clone()); + } + cache::get_account_mut(cache, &address).ok_or(VMError::Internal(InternalError::AccountNotFound)) +} + +pub fn increase_account_balance( + cache: &mut CacheDB, + db: &mut Arc, + address: Address, + increase: U256, +) -> Result<(), VMError> { + let account = get_account_mut_vm(cache, db, address)?; + account.info.balance = account + .info + .balance + .checked_add(increase) + .ok_or(VMError::BalanceOverflow)?; + Ok(()) +} + +pub fn decrease_account_balance( + cache: &mut CacheDB, + db: &mut Arc, + address: Address, + decrease: U256, +) -> Result<(), VMError> { + let account = get_account_mut_vm(cache, db, address)?; + account.info.balance = account + .info + .balance + .checked_sub(decrease) + .ok_or(VMError::BalanceUnderflow)?; + Ok(()) +} + +// ================== Bytecode related functions ===================== +pub fn update_account_bytecode( + cache: &mut CacheDB, + db: &Arc, + address: Address, + new_bytecode: Bytes, +) -> Result<(), VMError> { + let account = get_account_mut_vm(cache, db, address)?; + account.info.bytecode = new_bytecode; + Ok(()) +} + +// ==================== Gas related functions ======================= +pub fn get_intrinsic_gas( + is_create: bool, + spec_id: SpecId, + access_list: &AccessList, + authorization_list: &Option, + initial_call_frame: &CallFrame, +) -> Result { + // Intrinsic Gas = Calldata cost + Create cost + Base cost + Access list cost + let mut intrinsic_gas: u64 = 0; + + // Calldata Cost + // 4 gas for each zero byte in the transaction data 16 gas for each non-zero byte in the transaction. + let calldata_cost = + gas_cost::tx_calldata(&initial_call_frame.calldata, spec_id).map_err(VMError::OutOfGas)?; + + intrinsic_gas = intrinsic_gas + .checked_add(calldata_cost) + .ok_or(OutOfGasError::ConsumedGasOverflow)?; + + // Base Cost + intrinsic_gas = intrinsic_gas + .checked_add(TX_BASE_COST) + .ok_or(OutOfGasError::ConsumedGasOverflow)?; + + // Create Cost + if is_create { + intrinsic_gas = intrinsic_gas + .checked_add(CREATE_BASE_COST) + .ok_or(OutOfGasError::ConsumedGasOverflow)?; + + let number_of_words = initial_call_frame.calldata.len().div_ceil(WORD_SIZE); + let double_number_of_words: u64 = number_of_words + .checked_mul(2) + .ok_or(OutOfGasError::ConsumedGasOverflow)? + .try_into() + .map_err(|_| VMError::Internal(InternalError::ConversionError))?; + + intrinsic_gas = intrinsic_gas + .checked_add(double_number_of_words) + .ok_or(OutOfGasError::ConsumedGasOverflow)?; + } + + // Access List Cost + let mut access_lists_cost: u64 = 0; + for (_, keys) in access_list { + access_lists_cost = access_lists_cost + .checked_add(ACCESS_LIST_ADDRESS_COST) + .ok_or(OutOfGasError::ConsumedGasOverflow)?; + for _ in keys { + access_lists_cost = access_lists_cost + .checked_add(ACCESS_LIST_STORAGE_KEY_COST) + .ok_or(OutOfGasError::ConsumedGasOverflow)?; + } + } + + intrinsic_gas = intrinsic_gas + .checked_add(access_lists_cost) + .ok_or(OutOfGasError::ConsumedGasOverflow)?; + + // Authorization List Cost + // `unwrap_or_default` will return an empty vec when the `authorization_list` field is None. + // If the vec is empty, the len will be 0, thus the authorization_list_cost is 0. + let amount_of_auth_tuples: u64 = authorization_list + .clone() + .unwrap_or_default() + .len() + .try_into() + .map_err(|_| VMError::Internal(InternalError::ConversionError))?; + let authorization_list_cost = PER_EMPTY_ACCOUNT_COST + .checked_mul(amount_of_auth_tuples) + .ok_or(VMError::Internal(InternalError::GasOverflow))?; + + intrinsic_gas = intrinsic_gas + .checked_add(authorization_list_cost) + .ok_or(OutOfGasError::ConsumedGasOverflow)?; + + Ok(intrinsic_gas) +} + +// ================= Blob hash related functions ===================== +/// After EIP-7691 the maximum number of blob hashes changes. For more +/// information see +/// [EIP-7691](https://eips.ethereum.org/EIPS/eip-7691#specification). +pub const fn max_blobs_per_block(specid: SpecId) -> usize { + match specid { + SpecId::PRAGUE => MAX_BLOB_COUNT_ELECTRA, + SpecId::PRAGUE_EOF => MAX_BLOB_COUNT_ELECTRA, + _ => MAX_BLOB_COUNT, + } +} + +/// According to EIP-7691 +/// (https://eips.ethereum.org/EIPS/eip-7691#specification): +/// +/// "These changes imply that get_base_fee_per_blob_gas and +/// calc_excess_blob_gas functions defined in EIP-4844 use the new +/// values for the first block of the fork (and for all subsequent +/// blocks)." +pub const fn get_blob_base_fee_update_fraction_value(specid: SpecId) -> U256 { + match specid { + SpecId::PRAGUE => BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE, + SpecId::PRAGUE_EOF => BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE, + _ => BLOB_BASE_FEE_UPDATE_FRACTION, + } +} + +pub fn get_base_fee_per_blob_gas( + block_excess_blob_gas: Option, + spec_id: SpecId, +) -> Result { + fake_exponential( + MIN_BASE_FEE_PER_BLOB_GAS, + block_excess_blob_gas.unwrap_or_default(), + get_blob_base_fee_update_fraction_value(spec_id), ) } + +/// Gets the max blob gas cost for a transaction that a user is +/// willing to pay. +pub fn get_max_blob_gas_price( + tx_blob_hashes: Vec, + tx_max_fee_per_blob_gas: Option, +) -> Result { + let blobhash_amount: u64 = tx_blob_hashes + .len() + .try_into() + .map_err(|_| VMError::Internal(InternalError::ConversionError))?; + + let blob_gas_used: u64 = blobhash_amount + .checked_mul(BLOB_GAS_PER_BLOB) + .unwrap_or_default(); + + let max_blob_gas_cost = tx_max_fee_per_blob_gas + .unwrap_or_default() + .checked_mul(blob_gas_used.into()) + .ok_or(InternalError::UndefinedState(1))?; + + Ok(max_blob_gas_cost) +} +/// Gets the actual blob gas cost. +pub fn get_blob_gas_price( + tx_blob_hashes: Vec, + block_excess_blob_gas: Option, + spec_id: SpecId, +) -> Result { + let blobhash_amount: u64 = tx_blob_hashes + .len() + .try_into() + .map_err(|_| VMError::Internal(InternalError::ConversionError))?; + + let blob_gas_price: u64 = blobhash_amount + .checked_mul(BLOB_GAS_PER_BLOB) + .unwrap_or_default(); + + let base_fee_per_blob_gas = get_base_fee_per_blob_gas(block_excess_blob_gas, spec_id)?; + + let blob_gas_price: U256 = blob_gas_price.into(); + let blob_fee: U256 = blob_gas_price + .checked_mul(base_fee_per_blob_gas) + .ok_or(VMError::Internal(InternalError::UndefinedState(1)))?; + + Ok(blob_fee) +} + +// =================== Opcode related functions ====================== + +pub fn get_n_value(op: Opcode, base_opcode: Opcode) -> Result { + let offset = (usize::from(op)) + .checked_sub(usize::from(base_opcode)) + .ok_or(VMError::InvalidOpcode)? + .checked_add(1) + .ok_or(VMError::InvalidOpcode)?; + + Ok(offset) +} + +pub fn get_number_of_topics(op: Opcode) -> Result { + let number_of_topics = (u8::from(op)) + .checked_sub(u8::from(Opcode::LOG0)) + .ok_or(VMError::InvalidOpcode)?; + + Ok(number_of_topics) +} + +// =================== Nonce related functions ====================== +pub fn increment_account_nonce( + cache: &mut CacheDB, + db: &Arc, + address: Address, +) -> Result { + let account = get_account_mut_vm(cache, db, address)?; + account.info.nonce = account + .info + .nonce + .checked_add(1) + .ok_or(VMError::NonceOverflow)?; + Ok(account.info.nonce) +} + +pub fn decrement_account_nonce( + cache: &mut CacheDB, + db: &Arc, + address: Address, +) -> Result<(), VMError> { + let account = get_account_mut_vm(cache, db, address)?; + account.info.nonce = account + .info + .nonce + .checked_sub(1) + .ok_or(VMError::NonceUnderflow)?; + Ok(()) +} + +// ==================== Word related functions ======================= +pub fn word_to_address(word: U256) -> Address { + Address::from_slice(&word.to_big_endian()[12..]) +} + +// ================== EIP-7702 related functions ===================== + +/// Checks if account.info.bytecode has been delegated as the EIP7702 +/// determines. +pub fn has_delegation(account_info: &AccountInfo) -> Result { + let mut has_delegation = false; + if account_info.has_code() && account_info.bytecode.len() == EIP7702_DELEGATED_CODE_LEN { + let first_3_bytes = account_info + .bytecode + .get(..3) + .ok_or(VMError::Internal(InternalError::SlicingError))?; + + if first_3_bytes == SET_CODE_DELEGATION_BYTES { + has_delegation = true; + } + } + Ok(has_delegation) +} + +/// Gets the address inside the account.info.bytecode if it has been +/// delegated as the EIP7702 determines. +pub fn get_authorized_address(account_info: &AccountInfo) -> Result { + if has_delegation(account_info)? { + let address_bytes = account_info + .bytecode + .get(SET_CODE_DELEGATION_BYTES.len()..) + .ok_or(VMError::Internal(InternalError::SlicingError))?; + // It shouldn't panic when doing Address::from_slice() + // because the length is checked inside the has_delegation() function + let address = Address::from_slice(address_bytes); + Ok(address) + } else { + // if we end up here, it means that the address wasn't previously delegated. + Err(VMError::Internal(InternalError::AccountNotDelegated)) + } +} + +pub fn eip7702_recover_address( + auth_tuple: &AuthorizationTuple, +) -> Result, VMError> { + if auth_tuple.s_signature > *SECP256K1_ORDER_OVER2 || U256::zero() >= auth_tuple.s_signature { + return Ok(None); + } + if auth_tuple.r_signature > *SECP256K1_ORDER || U256::zero() >= auth_tuple.r_signature { + return Ok(None); + } + if auth_tuple.v != U256::one() && auth_tuple.v != U256::zero() { + return Ok(None); + } + + let rlp_buf = (auth_tuple.chain_id, auth_tuple.address, auth_tuple.nonce).encode_to_vec(); + + let mut hasher = Keccak256::new(); + hasher.update([MAGIC]); + hasher.update(rlp_buf); + let bytes = &mut hasher.finalize(); + + let Ok(message) = Message::parse_slice(bytes) else { + return Ok(None); + }; + + let bytes = [ + auth_tuple.r_signature.to_big_endian(), + auth_tuple.s_signature.to_big_endian(), + ] + .concat(); + + let Ok(signature) = Signature::parse_standard_slice(&bytes) else { + return Ok(None); + }; + + let Ok(recovery_id) = RecoveryId::parse( + auth_tuple + .v + .as_u32() + .try_into() + .map_err(|_| VMError::Internal(InternalError::ConversionError))?, + ) else { + return Ok(None); + }; + + let Ok(authority) = libsecp256k1::recover(&message, &signature, &recovery_id) else { + return Ok(None); + }; + + let public_key = authority.serialize(); + let mut hasher = Keccak256::new(); + hasher.update( + public_key + .get(1..) + .ok_or(VMError::Internal(InternalError::SlicingError))?, + ); + let address_hash = hasher.finalize(); + + // Get the last 20 bytes of the hash -> Address + let authority_address_bytes: [u8; 20] = address_hash + .get(12..32) + .ok_or(VMError::Internal(InternalError::SlicingError))? + .try_into() + .map_err(|_| VMError::Internal(InternalError::ConversionError))?; + Ok(Some(Address::from_slice(&authority_address_bytes))) +} + +/// Used for the opcodes +/// The following reading instructions are impacted: +/// EXTCODESIZE, EXTCODECOPY, EXTCODEHASH +/// and the following executing instructions are impacted: +/// CALL, CALLCODE, STATICCALL, DELEGATECALL +/// In case a delegation designator points to another designator, +/// creating a potential chain or loop of designators, clients must +/// retrieve only the first code and then stop following the +/// designator chain. +/// +/// For example, +/// EXTCODESIZE would return 2 (the size of 0xef01) instead of 23 +/// which would represent the delegation designation, EXTCODEHASH +/// would return +/// 0xeadcdba66a79ab5dce91622d1d75c8cff5cff0b96944c3bf1072cd08ce018329 +/// (keccak256(0xef01)), and CALL would load the code from address and +/// execute it in the context of authority. +/// +/// The idea of this function comes from ethereum/execution-specs: +/// https://github.com/ethereum/execution-specs/blob/951fc43a709b493f27418a8e57d2d6f3608cef84/src/ethereum/prague/vm/eoa_delegation.py#L115 +pub fn eip7702_get_code( + cache: &mut CacheDB, + db: &Arc, + accrued_substate: &mut Substate, + address: Address, +) -> Result<(bool, u64, Address, Bytes), VMError> { + // Address is the delgated address + let account = get_account(cache, db, address); + let bytecode = account.info.bytecode.clone(); + + // If the Address doesn't have a delegation code + // return false meaning that is not a delegation + // return the same address given + // return the bytecode of the given address + if !has_delegation(&account.info)? { + return Ok((false, 0, address, bytecode)); + } + + // Here the address has a delegation code + // The delegation code has the authorized address + let auth_address = get_authorized_address(&account.info)?; + + let access_cost = if accrued_substate.touched_accounts.contains(&auth_address) { + WARM_ADDRESS_ACCESS_COST + } else { + accrued_substate.touched_accounts.insert(auth_address); + COLD_ADDRESS_ACCESS_COST + }; + + let authorized_bytecode = get_account(cache, db, auth_address).info.bytecode; + + Ok((true, access_cost, auth_address, authorized_bytecode)) +} diff --git a/crates/vm/levm/src/vm.rs b/crates/vm/levm/src/vm.rs index ebf8ca675b..4621b05be6 100644 --- a/crates/vm/levm/src/vm.rs +++ b/crates/vm/levm/src/vm.rs @@ -11,26 +11,18 @@ use crate::{ InternalError, OpcodeSuccess, OutOfGasError, ResultReason, TransactionReport, TxResult, TxValidationError, VMError, }, - gas_cost::{ - self, fake_exponential, ACCESS_LIST_ADDRESS_COST, ACCESS_LIST_STORAGE_KEY_COST, - BLOB_GAS_PER_BLOB, CODE_DEPOSIT_COST, COLD_ADDRESS_ACCESS_COST, CREATE_BASE_COST, - STANDARD_TOKEN_COST, TOTAL_COST_FLOOR_PER_TOKEN, WARM_ADDRESS_ACCESS_COST, - }, + gas_cost::{self, CODE_DEPOSIT_COST, STANDARD_TOKEN_COST, TOTAL_COST_FLOOR_PER_TOKEN}, opcodes::Opcode, precompiles::{ execute_precompile, is_precompile, SIZE_PRECOMPILES_CANCUN, SIZE_PRECOMPILES_PRAGUE, SIZE_PRECOMPILES_PRE_CANCUN, }, + utils::*, AccountInfo, TransientStorage, }; use bytes::Bytes; use ethrex_core::{types::TxKind, Address, H256, U256}; -use ethrex_rlp; -use ethrex_rlp::encode::RLPEncode; -use keccak_hash::keccak; -use libsecp256k1::{Message, RecoveryId, Signature}; use revm_primitives::SpecId; -use sha3::{Digest, Keccak256}; use std::{ cmp::max, collections::{HashMap, HashSet}, @@ -66,31 +58,9 @@ pub struct VM { pub authorization_list: Option, } -pub fn address_to_word(address: Address) -> U256 { - // This unwrap can't panic, as Address are 20 bytes long and U256 use 32 bytes - let mut word = [0u8; 32]; - - for (word_byte, address_byte) in word.iter_mut().skip(12).zip(address.as_bytes().iter()) { - *word_byte = *address_byte; - } - - U256::from_big_endian(&word) -} - -pub fn word_to_address(word: U256) -> Address { - Address::from_slice(&word.to_big_endian()[12..]) -} - -// Taken from cmd/ef_tests/ethrex/types.rs, didn't want to fight dependencies yet -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct AccessListItem { - pub address: Address, - pub storage_keys: Vec, -} - -type AccessList = Vec<(Address, Vec)>; +pub type AccessList = Vec<(Address, Vec)>; -type AuthorizationList = Vec; +pub type AuthorizationList = Vec; // TODO: We have to implement this in ethrex_core #[derive(Debug, Clone, Default, Copy)] pub struct AuthorizationTuple { @@ -102,40 +72,6 @@ pub struct AuthorizationTuple { pub s_signature: U256, } -pub fn get_valid_jump_destinations(code: &Bytes) -> Result, VMError> { - let mut valid_jump_destinations = HashSet::new(); - let mut pc = 0; - - while let Some(&opcode_number) = code.get(pc) { - let current_opcode = Opcode::from(opcode_number); - - if current_opcode == Opcode::JUMPDEST { - // If current opcode is jumpdest, add it to valid destinations set - valid_jump_destinations.insert(pc); - } else if (Opcode::PUSH1..=Opcode::PUSH32).contains(¤t_opcode) { - // If current opcode is push, skip as many positions as the size of the push - let size_to_push = - opcode_number - .checked_sub(u8::from(Opcode::PUSH1)) - .ok_or(VMError::Internal( - InternalError::ArithmeticOperationUnderflow, - ))?; - let skip_length = usize::from(size_to_push.checked_add(1).ok_or(VMError::Internal( - InternalError::ArithmeticOperationOverflow, - ))?); - pc = pc.checked_add(skip_length).ok_or(VMError::Internal( - InternalError::ArithmeticOperationOverflow, // to fail, pc should be at least usize max - 31 - ))?; - } - - pc = pc.checked_add(1).ok_or(VMError::Internal( - InternalError::ArithmeticOperationOverflow, // to fail, code len should be more than usize max - ))?; - } - - Ok(valid_jump_destinations) -} - impl VM { // TODO: Refactor this. #[allow(clippy::too_many_arguments)] @@ -228,10 +164,8 @@ impl VM { // CREATE tx let sender_nonce = get_account(&mut cache, &db, env.origin).info.nonce; - let new_contract_address = VM::calculate_create_address(env.origin, sender_nonce) - .map_err(|_| { - VMError::Internal(InternalError::CouldNotComputeCreateAddress) - })?; + let new_contract_address = calculate_create_address(env.origin, sender_nonce) + .map_err(|_| VMError::Internal(InternalError::CouldNotComputeCreateAddress))?; default_touched_accounts.insert(new_contract_address); @@ -479,7 +413,12 @@ impl VM { match validate_create { Ok(new_address) => { // Set bytecode to new account if success - self.update_account_bytecode(new_address, contract_code)?; + update_account_bytecode( + &mut self.cache, + &self.db, + new_address, + contract_code, + )?; } Err(error) => { // Revert if error @@ -568,84 +507,16 @@ impl VM { matches!(self.tx_kind, TxKind::Create) } - fn get_intrinsic_gas(&self, initial_call_frame: &CallFrame) -> Result { - // Intrinsic Gas = Calldata cost + Create cost + Base cost + Access list cost - let mut intrinsic_gas: u64 = 0; - - // Calldata Cost - // 4 gas for each zero byte in the transaction data 16 gas for each non-zero byte in the transaction. - let calldata_cost = gas_cost::tx_calldata(&initial_call_frame.calldata, self.env.spec_id) - .map_err(VMError::OutOfGas)?; - - intrinsic_gas = intrinsic_gas - .checked_add(calldata_cost) - .ok_or(OutOfGasError::ConsumedGasOverflow)?; - - // Base Cost - intrinsic_gas = intrinsic_gas - .checked_add(TX_BASE_COST) - .ok_or(OutOfGasError::ConsumedGasOverflow)?; - - // Create Cost - if self.is_create() { - intrinsic_gas = intrinsic_gas - .checked_add(CREATE_BASE_COST) - .ok_or(OutOfGasError::ConsumedGasOverflow)?; - - let number_of_words = initial_call_frame.calldata.len().div_ceil(WORD_SIZE); - let double_number_of_words: u64 = number_of_words - .checked_mul(2) - .ok_or(OutOfGasError::ConsumedGasOverflow)? - .try_into() - .map_err(|_| VMError::Internal(InternalError::ConversionError))?; - - intrinsic_gas = intrinsic_gas - .checked_add(double_number_of_words) - .ok_or(OutOfGasError::ConsumedGasOverflow)?; - } - - // Access List Cost - let mut access_lists_cost: u64 = 0; - for (_, keys) in self.access_list.clone() { - access_lists_cost = access_lists_cost - .checked_add(ACCESS_LIST_ADDRESS_COST) - .ok_or(OutOfGasError::ConsumedGasOverflow)?; - for _ in keys { - access_lists_cost = access_lists_cost - .checked_add(ACCESS_LIST_STORAGE_KEY_COST) - .ok_or(OutOfGasError::ConsumedGasOverflow)?; - } - } - - intrinsic_gas = intrinsic_gas - .checked_add(access_lists_cost) - .ok_or(OutOfGasError::ConsumedGasOverflow)?; - - // Authorization List Cost - // `unwrap_or_default` will return an empty vec when the `authorization_list` field is None. - // If the vec is empty, the len will be 0, thus the authorization_list_cost is 0. - let amount_of_auth_tuples: u64 = self - .authorization_list - .clone() - .unwrap_or_default() - .len() - .try_into() - .map_err(|_| VMError::Internal(InternalError::ConversionError))?; - let authorization_list_cost = PER_EMPTY_ACCOUNT_COST - .checked_mul(amount_of_auth_tuples) - .ok_or(VMError::Internal(InternalError::GasOverflow))?; - - intrinsic_gas = intrinsic_gas - .checked_add(authorization_list_cost) - .ok_or(OutOfGasError::ConsumedGasOverflow)?; - - Ok(intrinsic_gas) - } - fn add_intrinsic_gas(&mut self, initial_call_frame: &mut CallFrame) -> Result<(), VMError> { // Intrinsic gas is the gas consumed by the transaction before the execution of the opcodes. Section 6.2 in the Yellow Paper. - let intrinsic_gas = self.get_intrinsic_gas(initial_call_frame)?; + let intrinsic_gas = get_intrinsic_gas( + self.is_create(), + self.env.spec_id, + &self.access_list, + &self.authorization_list, + initial_call_frame, + )?; self.increase_consumed_gas(initial_call_frame, intrinsic_gas) .map_err(|_| TxValidationError::IntrinsicGasTooLow)?; @@ -691,60 +562,6 @@ impl VM { } } - /// Gets the max blob gas cost for a transaction that a user is willing to pay. - fn get_max_blob_gas_price(&self) -> Result { - let blobhash_amount: u64 = self - .env - .tx_blob_hashes - .len() - .try_into() - .map_err(|_| VMError::Internal(InternalError::ConversionError))?; - - let blob_gas_used: u64 = blobhash_amount - .checked_mul(BLOB_GAS_PER_BLOB) - .unwrap_or_default(); - - let max_blob_gas_cost = self - .env - .tx_max_fee_per_blob_gas - .unwrap_or_default() - .checked_mul(blob_gas_used.into()) - .ok_or(InternalError::UndefinedState(1))?; - - Ok(max_blob_gas_cost) - } - - /// Gets the actual blob gas cost. - fn get_blob_gas_price(&self) -> Result { - let blobhash_amount: u64 = self - .env - .tx_blob_hashes - .len() - .try_into() - .map_err(|_| VMError::Internal(InternalError::ConversionError))?; - - let blob_gas_price: u64 = blobhash_amount - .checked_mul(BLOB_GAS_PER_BLOB) - .unwrap_or_default(); - - let base_fee_per_blob_gas = self.get_base_fee_per_blob_gas()?; - - let blob_gas_price: U256 = blob_gas_price.into(); - let blob_fee: U256 = blob_gas_price - .checked_mul(base_fee_per_blob_gas) - .ok_or(VMError::Internal(InternalError::UndefinedState(1)))?; - - Ok(blob_fee) - } - - pub fn get_base_fee_per_blob_gas(&self) -> Result { - fake_exponential( - MIN_BASE_FEE_PER_BLOB_GAS, - self.env.block_excess_blob_gas.unwrap_or_default(), - get_blob_base_fee_update_fraction_value(self.env.spec_id), - ) - } - /// ## Description /// This method performs validations and returns an error if any of the validations fail. /// It also makes pre-execution changes: @@ -755,11 +572,17 @@ impl VM { /// See 'docs' for more information about validations. fn prepare_execution(&mut self, initial_call_frame: &mut CallFrame) -> Result<(), VMError> { let sender_address = self.env.origin; - let sender_account = self.get_account(sender_address); + let sender_account = get_account(&mut self.cache, &self.db, sender_address); if self.env.spec_id >= SpecId::PRAGUE { // check for gas limit is grater or equal than the minimum required - let intrinsic_gas: u64 = self.get_intrinsic_gas(initial_call_frame)?; + let intrinsic_gas: u64 = get_intrinsic_gas( + self.is_create(), + self.env.spec_id, + &self.access_list, + &self.authorization_list, + initial_call_frame, + )?; // calldata_cost = tokens_in_calldata * 4 let calldata_cost: u64 = @@ -799,7 +622,10 @@ impl VM { // blob gas cost = max fee per blob gas * blob gas used // https://eips.ethereum.org/EIPS/eip-4844 - let max_blob_gas_cost = self.get_max_blob_gas_price()?; + let max_blob_gas_cost = get_max_blob_gas_price( + self.env.tx_blob_hashes.clone(), + self.env.tx_max_fee_per_blob_gas, + )?; // For the transaction to be valid the sender account has to have a balance >= gas_price * gas_limit + value if tx is type 0 and 1 // balance >= max_fee_per_gas * gas_limit + value + blob_gas_cost if tx is type 2 or 3 @@ -827,11 +653,17 @@ impl VM { )); } - let blob_gas_cost = self.get_blob_gas_price()?; + let blob_gas_cost = get_blob_gas_price( + self.env.tx_blob_hashes.clone(), + self.env.block_excess_blob_gas, + self.env.spec_id, + )?; // (2) INSUFFICIENT_MAX_FEE_PER_BLOB_GAS if let Some(tx_max_fee_per_blob_gas) = self.env.tx_max_fee_per_blob_gas { - if tx_max_fee_per_blob_gas < self.get_base_fee_per_blob_gas()? { + if tx_max_fee_per_blob_gas + < get_base_fee_per_blob_gas(self.env.block_excess_blob_gas, self.env.spec_id)? + { return Err(VMError::TxValidation( TxValidationError::InsufficientMaxFeePerBlobGas, )); @@ -854,7 +686,7 @@ impl VM { // technically, the sender will not be able to pay it. // (3) INSUFFICIENT_ACCOUNT_FUNDS - self.decrease_account_balance(sender_address, up_front_cost) + decrease_account_balance(&mut self.cache, &mut self.db, sender_address, up_front_cost) .map_err(|_| TxValidationError::InsufficientAccountFunds)?; // (4) INSUFFICIENT_MAX_FEE_PER_GAS @@ -880,7 +712,7 @@ impl VM { self.add_intrinsic_gas(initial_call_frame)?; // (7) NONCE_IS_MAX - self.increment_account_nonce(sender_address) + increment_account_nonce(&mut self.cache, &self.db, sender_address) .map_err(|_| VMError::TxValidation(TxValidationError::NonceIsMax))?; // (8) PRIORITY_GREATER_THAN_MAX_FEE_PER_GAS @@ -983,7 +815,12 @@ impl VM { } else { // Transfer value to receiver // It's here to avoid storing the "to" address in the cache before eip7702_set_access_code() step 7). - self.increase_account_balance(initial_call_frame.to, initial_call_frame.msg_value)?; + increase_account_balance( + &mut self.cache, + &mut self.db, + initial_call_frame.to, + initial_call_frame.msg_value, + )?; } Ok(()) } @@ -1016,13 +853,23 @@ impl VM { // If transaction execution results in failure (any // exceptional condition or code reverting), setting // delegation designations is not rolled back. - self.decrease_account_balance(receiver_address, initial_call_frame.msg_value)?; + decrease_account_balance( + &mut self.cache, + &mut self.db, + receiver_address, + initial_call_frame.msg_value, + )?; } else { // We remove the receiver account from the cache, like nothing changed in it's state. remove_account(&mut self.cache, &receiver_address); } - self.increase_account_balance(sender_address, initial_call_frame.msg_value)?; + increase_account_balance( + &mut self.cache, + &mut self.db, + sender_address, + initial_call_frame.msg_value, + )?; } // 2. Return unused gas + gas refunds to the sender. @@ -1047,7 +894,12 @@ impl VM { .checked_mul(U256::from(gas_to_return)) .ok_or(VMError::Internal(InternalError::UndefinedState(1)))?; - self.increase_account_balance(sender_address, wei_return_amount)?; + increase_account_balance( + &mut self.cache, + &mut self.db, + sender_address, + wei_return_amount, + )?; // 3. Pay coinbase fee let coinbase_address = self.env.coinbase; @@ -1066,14 +918,19 @@ impl VM { .ok_or(VMError::BalanceOverflow)?; if coinbase_fee != U256::zero() { - self.increase_account_balance(coinbase_address, coinbase_fee)?; + increase_account_balance( + &mut self.cache, + &mut self.db, + coinbase_address, + coinbase_fee, + )?; }; // 4. Destruct addresses in selfdestruct set. // In Cancun the only addresses destroyed are contracts created in this transaction let selfdestruct_set = self.accrued_substate.selfdestruct_set.clone(); for address in selfdestruct_set { - let account_to_remove = self.get_account_mut(address)?; + let account_to_remove = get_account_mut_vm(&mut self.cache, &self.db, address)?; *account_to_remove = Account::default(); } @@ -1092,7 +949,7 @@ impl VM { // Add created contract to cache, reverting transaction if the address is already occupied if self.is_create() { let new_contract_address = initial_call_frame.to; - let new_account = self.get_account(new_contract_address); + let new_account = get_account(&mut self.cache, &self.db, new_contract_address); let value = initial_call_frame.msg_value; let balance = new_account @@ -1127,54 +984,6 @@ impl VM { )) } - /// Calculates the address of a new conctract using the CREATE opcode as follow - /// - /// address = keccak256(rlp([sender_address,sender_nonce]))[12:] - pub fn calculate_create_address( - sender_address: Address, - sender_nonce: u64, - ) -> Result { - let mut encoded = Vec::new(); - (sender_address, sender_nonce).encode(&mut encoded); - let mut hasher = Keccak256::new(); - hasher.update(encoded); - Ok(Address::from_slice(hasher.finalize().get(12..).ok_or( - VMError::Internal(InternalError::CouldNotComputeCreateAddress), - )?)) - } - - /// Calculates the address of a new contract using the CREATE2 opcode as follow - /// - /// initialization_code = memory[offset:offset+size] - /// - /// address = keccak256(0xff + sender_address + salt + keccak256(initialization_code))[12:] - /// - pub fn calculate_create2_address( - sender_address: Address, - initialization_code: &Bytes, - salt: U256, - ) -> Result { - let init_code_hash = keccak(initialization_code); - - let generated_address = Address::from_slice( - keccak( - [ - &[0xff], - sender_address.as_bytes(), - &salt.to_big_endian(), - init_code_hash.as_bytes(), - ] - .concat(), - ) - .as_bytes() - .get(12..) - .ok_or(VMError::Internal( - InternalError::CouldNotComputeCreate2Address, - ))?, - ); - Ok(generated_address) - } - /// Increases gas consumption of CallFrame and Environment, returning an error if the callframe gas limit is reached. pub fn increase_consumed_gas( &mut self, @@ -1249,77 +1058,19 @@ impl VM { // When updating account storage of an account that's not yet cached we need to store the StorageSlot in the account // Note: We end up caching the account because it is the most straightforward way of doing it. - let account = self.get_account_mut(address)?; + let account = get_account_mut_vm(&mut self.cache, &self.db, address)?; account.storage.insert(key, storage_slot.clone()); Ok((storage_slot, storage_slot_was_cold)) } - pub fn increase_account_balance( - &mut self, - address: Address, - increase: U256, - ) -> Result<(), VMError> { - let account = self.get_account_mut(address)?; - account.info.balance = account - .info - .balance - .checked_add(increase) - .ok_or(VMError::BalanceOverflow)?; - Ok(()) - } - - pub fn decrease_account_balance( - &mut self, - address: Address, - decrease: U256, - ) -> Result<(), VMError> { - let account = self.get_account_mut(address)?; - account.info.balance = account - .info - .balance - .checked_sub(decrease) - .ok_or(VMError::BalanceUnderflow)?; - Ok(()) - } - - pub fn increment_account_nonce(&mut self, address: Address) -> Result { - let account = self.get_account_mut(address)?; - account.info.nonce = account - .info - .nonce - .checked_add(1) - .ok_or(VMError::NonceOverflow)?; - Ok(account.info.nonce) - } - - pub fn decrement_account_nonce(&mut self, address: Address) -> Result<(), VMError> { - let account = self.get_account_mut(address)?; - account.info.nonce = account - .info - .nonce - .checked_sub(1) - .ok_or(VMError::NonceUnderflow)?; - Ok(()) - } - - pub fn update_account_bytecode( - &mut self, - address: Address, - new_bytecode: Bytes, - ) -> Result<(), VMError> { - let account = self.get_account_mut(address)?; - account.info.bytecode = new_bytecode; - Ok(()) - } - pub fn update_account_storage( &mut self, address: Address, key: H256, new_value: U256, ) -> Result<(), VMError> { - let account = self.get_account_mut(address)?; + let account = get_account_mut_vm(&mut self.cache, &self.db, address)?; let account_original_storage_slot_value = account .storage .get(&key) @@ -1332,28 +1083,6 @@ impl VM { Ok(()) } - pub fn get_account_mut(&mut self, address: Address) -> Result<&mut Account, VMError> { - if !cache::is_account_cached(&self.cache, &address) { - let account_info = self.db.get_account_info(address); - let account = Account { - info: account_info, - storage: HashMap::new(), - }; - cache::insert_account(&mut self.cache, address, account.clone()); - } - cache::get_account_mut(&mut self.cache, &address) - .ok_or(VMError::Internal(InternalError::AccountNotFound)) - } - - /// Gets account, first checking the cache and then the database (caching in the second case) - pub fn get_account(&mut self, address: Address) -> Account { - get_account(&mut self.cache, &self.db, address) - } - - pub fn get_account_no_push_cache(&self, address: Address) -> Account { - get_account_no_push_cache(&self.cache, &self.db, address) - } - fn handle_create_non_empty_account( &mut self, initial_call_frame: &CallFrame, @@ -1409,7 +1138,8 @@ impl VM { self.accrued_substate .touched_accounts .insert(authority_address); - let authority_account_info = self.get_account_no_push_cache(authority_address).info; + let authority_account_info = + get_account_no_push_cache(&self.cache, &self.db, authority_address).info; // 5. Verify the code of authority is either empty or already delegated. let empty_or_delegated = authority_account_info.bytecode.is_empty() @@ -1450,8 +1180,8 @@ impl VM { None => { // This is to add the account to the cache // NOTE: Refactor in the future - self.get_account(authority_address); - self.get_account_mut(authority_address)? + get_account(&mut self.cache, &self.db, authority_address); + get_account_mut_vm(&mut self.cache, &self.db, authority_address)? } }; @@ -1462,7 +1192,7 @@ impl VM { }; // 9. Increase the nonce of authority by one. - self.increment_account_nonce(authority_address) + increment_account_nonce(&mut self.cache, &self.db, authority_address) .map_err(|_| VMError::TxValidation(TxValidationError::NonceIsMax))?; } @@ -1481,232 +1211,4 @@ impl VM { get_valid_jump_destinations(&initial_call_frame.bytecode).unwrap_or_default(); Ok(refunded_gas) } - - /// Used for the opcodes - /// The following reading instructions are impacted: - /// EXTCODESIZE, EXTCODECOPY, EXTCODEHASH - /// and the following executing instructions are impacted: - /// CALL, CALLCODE, STATICCALL, DELEGATECALL - /// In case a delegation designator points to another designator, - /// creating a potential chain or loop of designators, - /// clients must retrieve only the first code and then stop following the designator chain. - /// - /// For example, - /// EXTCODESIZE would return 2 (the size of 0xef01) instead of 23 which would represent the delegation designation, - /// EXTCODEHASH would return 0xeadcdba66a79ab5dce91622d1d75c8cff5cff0b96944c3bf1072cd08ce018329 (keccak256(0xef01)), and - /// CALL would load the code from address and execute it in the context of authority. - /// - /// The idea of this function comes from ethereum/execution-specs: - /// https://github.com/ethereum/execution-specs/blob/951fc43a709b493f27418a8e57d2d6f3608cef84/src/ethereum/prague/vm/eoa_delegation.py#L115 - pub fn eip7702_get_code( - &mut self, - address: Address, - ) -> Result<(bool, u64, Address, Bytes), VMError> { - // Address is the delgated address - let account = get_account(&mut self.cache, &self.db, address); - let bytecode = account.info.bytecode.clone(); - - // If the Address doesn't have a delegation code - // return false meaning that is not a delegation - // return the same address given - // return the bytecode of the given address - if !has_delegation(&account.info)? { - return Ok((false, 0, address, bytecode)); - } - - // Here the address has a delegation code - // The delegation code has the authorized address - let auth_address = get_authorized_address(&account.info)?; - - let access_cost = if self - .accrued_substate - .touched_accounts - .contains(&auth_address) - { - WARM_ADDRESS_ACCESS_COST - } else { - self.accrued_substate.touched_accounts.insert(auth_address); - COLD_ADDRESS_ACCESS_COST - }; - - let authorized_bytecode = get_account(&mut self.cache, &self.db, auth_address) - .info - .bytecode; - - Ok((true, access_cost, auth_address, authorized_bytecode)) - } -} - -fn get_n_value(op: Opcode, base_opcode: Opcode) -> Result { - let offset = (usize::from(op)) - .checked_sub(usize::from(base_opcode)) - .ok_or(VMError::InvalidOpcode)? - .checked_add(1) - .ok_or(VMError::InvalidOpcode)?; - - Ok(offset) -} - -fn get_number_of_topics(op: Opcode) -> Result { - let number_of_topics = (u8::from(op)) - .checked_sub(u8::from(Opcode::LOG0)) - .ok_or(VMError::InvalidOpcode)?; - - Ok(number_of_topics) -} - -/// Gets account, first checking the cache and then the database (caching in the second case) -pub fn get_account(cache: &mut CacheDB, db: &Arc, address: Address) -> Account { - match cache::get_account(cache, &address) { - Some(acc) => acc.clone(), - None => { - let account_info = db.get_account_info(address); - let account = Account { - info: account_info, - storage: HashMap::new(), - }; - cache::insert_account(cache, address, account.clone()); - account - } - } -} - -pub fn get_account_no_push_cache( - cache: &CacheDB, - db: &Arc, - address: Address, -) -> Account { - match cache::get_account(cache, &address) { - Some(acc) => acc.clone(), - None => { - let account_info = db.get_account_info(address); - Account { - info: account_info, - storage: HashMap::new(), - } - } - } -} - -/// Checks if account.info.bytecode has been delegated as the EIP7702 determines. -pub fn has_delegation(account_info: &AccountInfo) -> Result { - let mut has_delegation = false; - if account_info.has_code() && account_info.bytecode.len() == EIP7702_DELEGATED_CODE_LEN { - let first_3_bytes = account_info - .bytecode - .get(..3) - .ok_or(VMError::Internal(InternalError::SlicingError))?; - - if first_3_bytes == SET_CODE_DELEGATION_BYTES { - has_delegation = true; - } - } - Ok(has_delegation) -} - -/// Gets the address inside the account.info.bytecode if it has been delegated as the EIP7702 determines. -pub fn get_authorized_address(account_info: &AccountInfo) -> Result { - if has_delegation(account_info)? { - let address_bytes = account_info - .bytecode - .get(SET_CODE_DELEGATION_BYTES.len()..) - .ok_or(VMError::Internal(InternalError::SlicingError))?; - // It shouldn't panic when doing Address::from_slice() - // because the length is checked inside the has_delegation() function - let address = Address::from_slice(address_bytes); - Ok(address) - } else { - // if we end up here, it means that the address wasn't previously delegated. - Err(VMError::Internal(InternalError::AccountNotDelegated)) - } -} - -fn eip7702_recover_address(auth_tuple: &AuthorizationTuple) -> Result, VMError> { - if auth_tuple.s_signature > *SECP256K1_ORDER_OVER2 || U256::zero() >= auth_tuple.s_signature { - return Ok(None); - } - if auth_tuple.r_signature > *SECP256K1_ORDER || U256::zero() >= auth_tuple.r_signature { - return Ok(None); - } - if auth_tuple.v != U256::one() && auth_tuple.v != U256::zero() { - return Ok(None); - } - - let rlp_buf = (auth_tuple.chain_id, auth_tuple.address, auth_tuple.nonce).encode_to_vec(); - - let mut hasher = Keccak256::new(); - hasher.update([MAGIC]); - hasher.update(rlp_buf); - let bytes = &mut hasher.finalize(); - - let Ok(message) = Message::parse_slice(bytes) else { - return Ok(None); - }; - - let bytes = [ - auth_tuple.r_signature.to_big_endian(), - auth_tuple.s_signature.to_big_endian(), - ] - .concat(); - - let Ok(signature) = Signature::parse_standard_slice(&bytes) else { - return Ok(None); - }; - - let Ok(recovery_id) = RecoveryId::parse( - auth_tuple - .v - .as_u32() - .try_into() - .map_err(|_| VMError::Internal(InternalError::ConversionError))?, - ) else { - return Ok(None); - }; - - let Ok(authority) = libsecp256k1::recover(&message, &signature, &recovery_id) else { - return Ok(None); - }; - - let public_key = authority.serialize(); - let mut hasher = Keccak256::new(); - hasher.update( - public_key - .get(1..) - .ok_or(VMError::Internal(InternalError::SlicingError))?, - ); - let address_hash = hasher.finalize(); - - // Get the last 20 bytes of the hash -> Address - let authority_address_bytes: [u8; 20] = address_hash - .get(12..32) - .ok_or(VMError::Internal(InternalError::SlicingError))? - .try_into() - .map_err(|_| VMError::Internal(InternalError::ConversionError))?; - Ok(Some(Address::from_slice(&authority_address_bytes))) -} - -/// After EIP-7691 the maximum number of blob hashes changes. For more -/// information see -/// [EIP-7691](https://eips.ethereum.org/EIPS/eip-7691#specification). -pub const fn max_blobs_per_block(specid: SpecId) -> usize { - match specid { - SpecId::PRAGUE => MAX_BLOB_COUNT_ELECTRA, - SpecId::PRAGUE_EOF => MAX_BLOB_COUNT_ELECTRA, - _ => MAX_BLOB_COUNT, - } -} - -/// According to EIP-7691 -/// (https://eips.ethereum.org/EIPS/eip-7691#specification): -/// -/// "These changes imply that get_base_fee_per_blob_gas and -/// calc_excess_blob_gas functions defined in EIP-4844 use the new -/// values for the first block of the fork (and for all subsequent -/// blocks)." -pub const fn get_blob_base_fee_update_fraction_value(specid: SpecId) -> U256 { - match specid { - SpecId::PRAGUE => BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE, - SpecId::PRAGUE_EOF => BLOB_BASE_FEE_UPDATE_FRACTION_PRAGUE, - _ => BLOB_BASE_FEE_UPDATE_FRACTION, - } } diff --git a/crates/vm/levm/tests/edge_case_tests.rs b/crates/vm/levm/tests/edge_case_tests.rs index 80ece79c7b..4b8a5eb05b 100644 --- a/crates/vm/levm/tests/edge_case_tests.rs +++ b/crates/vm/levm/tests/edge_case_tests.rs @@ -7,7 +7,7 @@ use ethrex_core::U256; use ethrex_levm::{ errors::{TxResult, VMError}, operations::Operation, - utils::{new_vm_with_bytecode, new_vm_with_ops}, + testing::{new_vm_with_bytecode, new_vm_with_ops}, }; #[test] diff --git a/crates/vm/levm/tests/tests.rs b/crates/vm/levm/tests/tests.rs index bae5a8dd98..d366710156 100644 --- a/crates/vm/levm/tests/tests.rs +++ b/crates/vm/levm/tests/tests.rs @@ -20,8 +20,9 @@ use ethrex_levm::{ blake2f, bls12_g1msm, ecadd, ecmul, ecpairing, ecrecover, identity, modexp, ripemd_160, sha2_256, }, - utils::{new_vm_with_ops, new_vm_with_ops_addr_bal_db, new_vm_with_ops_db, ops_to_bytecode}, - vm::{word_to_address, Storage, VM}, + testing::{new_vm_with_ops, new_vm_with_ops_addr_bal_db, new_vm_with_ops_db, ops_to_bytecode}, + utils::{calculate_create_address, word_to_address}, + vm::{Storage, VM}, Environment, }; use std::{borrow::BorrowMut, collections::HashMap, sync::Arc}; @@ -3832,7 +3833,7 @@ fn create_happy_path() { let call_frame = vm.current_call_frame_mut().unwrap(); let returned_address = call_frame.stack.pop().unwrap(); - let expected_address = VM::calculate_create_address( + let expected_address = calculate_create_address( executing_contract_address, executing_contract_before.info.nonce, )