diff --git a/src/standard/gasometer/mod.rs b/src/standard/gasometer/mod.rs index e4df8e760..200727324 100644 --- a/src/standard/gasometer/mod.rs +++ b/src/standard/gasometer/mod.rs @@ -680,3 +680,117 @@ impl MemoryCost { Ok(Some(costs::memory_gas(new)?)) } } + +/// Transaction cost. +#[derive(Debug, Clone, Copy)] +pub enum TransactionCost { + /// Call transaction cost. + Call { + /// Length of zeros in transaction data. + zero_data_len: usize, + /// Length of non-zeros in transaction data. + non_zero_data_len: usize, + /// Number of addresses in transaction access list (see EIP-2930) + access_list_address_len: usize, + /// Total number of storage keys in transaction access list (see EIP-2930) + access_list_storage_len: usize, + }, + /// Create transaction cost. + Create { + /// Length of zeros in transaction data. + zero_data_len: usize, + /// Length of non-zeros in transaction data. + non_zero_data_len: usize, + /// Number of addresses in transaction access list (see EIP-2930) + access_list_address_len: usize, + /// Total number of storage keys in transaction access list (see EIP-2930) + access_list_storage_len: usize, + /// Cost of initcode = 2 * ceil(len(initcode) / 32) (see EIP-3860) + initcode_cost: u64, + }, +} + +impl TransactionCost { + pub fn call(data: &[u8], access_list: &[(H160, Vec)]) -> TransactionCost { + let zero_data_len = data.iter().filter(|v| **v == 0).count(); + let non_zero_data_len = data.len() - zero_data_len; + let (access_list_address_len, access_list_storage_len) = count_access_list(access_list); + + TransactionCost::Call { + zero_data_len, + non_zero_data_len, + access_list_address_len, + access_list_storage_len, + } + } + + pub fn create(data: &[u8], access_list: &[(H160, Vec)]) -> TransactionCost { + let zero_data_len = data.iter().filter(|v| **v == 0).count(); + let non_zero_data_len = data.len() - zero_data_len; + let (access_list_address_len, access_list_storage_len) = count_access_list(access_list); + let initcode_cost = init_code_cost(data); + + TransactionCost::Create { + zero_data_len, + non_zero_data_len, + access_list_address_len, + access_list_storage_len, + initcode_cost, + } + } + + pub fn cost(&self, config: &Config) -> u64 { + let gas_cost = match self { + TransactionCost::Call { + zero_data_len, + non_zero_data_len, + access_list_address_len, + access_list_storage_len, + } => { + #[deny(clippy::let_and_return)] + let cost = config.gas_transaction_call + + *zero_data_len as u64 * config.gas_transaction_zero_data + + *non_zero_data_len as u64 * config.gas_transaction_non_zero_data + + *access_list_address_len as u64 * config.gas_access_list_address + + *access_list_storage_len as u64 * config.gas_access_list_storage_key; + + cost + } + TransactionCost::Create { + zero_data_len, + non_zero_data_len, + access_list_address_len, + access_list_storage_len, + initcode_cost, + } => { + let mut cost = config.gas_transaction_create + + *zero_data_len as u64 * config.gas_transaction_zero_data + + *non_zero_data_len as u64 * config.gas_transaction_non_zero_data + + *access_list_address_len as u64 * config.gas_access_list_address + + *access_list_storage_len as u64 * config.gas_access_list_storage_key; + if config.max_initcode_size.is_some() { + cost += initcode_cost; + } + + cost + } + }; + + gas_cost + } +} + +/// Counts the number of addresses and storage keys in the access list +fn count_access_list(access_list: &[(H160, Vec)]) -> (usize, usize) { + let access_list_address_len = access_list.len(); + let access_list_storage_len = access_list.iter().map(|(_, keys)| keys.len()).sum(); + + (access_list_address_len, access_list_storage_len) +} + +pub fn init_code_cost(data: &[u8]) -> u64 { + // As per EIP-3860: + // > We define initcode_cost(initcode) to equal INITCODE_WORD_COST * ceil(len(initcode) / 32). + // where INITCODE_WORD_COST is 2. + 2 * ((data.len() as u64 + 31) / 32) +} diff --git a/src/standard/invoker.rs b/src/standard/invoker.rs index ba74a6566..1dac81b7d 100644 --- a/src/standard/invoker.rs +++ b/src/standard/invoker.rs @@ -1,4 +1,4 @@ -use super::{Config, GasedMachine, Gasometer, Machine}; +use super::{gasometer::TransactionCost, Config, Etable, GasedMachine, Gasometer, Machine}; use crate::call_create::{CallCreateTrapData, CallTrapData, CreateTrapData}; use crate::{ Capture, Context, ExitError, ExitException, ExitResult, Gasometer as GasometerT, @@ -8,7 +8,7 @@ use crate::{ use alloc::rc::Rc; use core::cmp::min; use core::convert::Infallible; -use primitive_types::{H160, U256}; +use primitive_types::{H160, H256, U256}; pub enum CallCreateTrapPrepareData { Call { @@ -28,10 +28,103 @@ pub enum CallCreateTrapEnterData { Create { trap: CreateTrapData, address: H160 }, } +const DEFAULT_HEAP_DEPTH: Option = Some(4); + pub struct Invoker<'config> { config: &'config Config, } +impl<'config> Invoker<'config> { + pub fn transact_call( + &self, + caller: H160, + address: H160, + value: U256, + data: Vec, + gas_limit: U256, + access_list: Vec<(H160, Vec)>, + handler: &mut H, + etable: &Etable, + ) -> ExitResult + where + H: RuntimeFullBackend + TransactionalBackend, + { + let gas_limit = if gas_limit > U256::from(u64::MAX) { + return Err(ExitException::OutOfGas.into()); + } else { + gas_limit.as_u64() + }; + + let context = Context { + caller, + address, + apparent_value: value, + }; + let code = handler.code(address); + + let transaction_cost = TransactionCost::call(&data, &access_list).cost(self.config); + + let machine = Machine::new( + Rc::new(code), + Rc::new(data), + self.config.stack_limit, + self.config.memory_limit, + RuntimeState { + context, + retbuf: Vec::new(), + gas: U256::zero(), + }, + ); + let mut gasometer = Gasometer::new(gas_limit, &machine, self.config); + + gasometer.record_cost(transaction_cost)?; + + handler.push_substate(); + + let work = || -> ExitResult { + if self.config.increase_state_access_gas { + if self.config.warm_coinbase_address { + let coinbase = handler.block_coinbase(); + handler.mark_hot(coinbase, None)?; + } + handler.mark_hot(caller, None)?; + handler.mark_hot(address, None)?; + } + + handler.inc_nonce(caller)?; + + let transfer = Transfer { + source: caller, + target: address, + value, + }; + handler.transfer(transfer)?; + + let machine = GasedMachine { + machine, + gasometer, + is_static: false, + }; + + let (_machine, result) = + crate::execute(machine, handler, 0, DEFAULT_HEAP_DEPTH, self, etable); + + result + }; + + match work() { + Ok(exit) => { + handler.pop_substate(TransactionalMergeStrategy::Commit); + Ok(exit) + } + Err(err) => { + handler.pop_substate(TransactionalMergeStrategy::Discard); + Err(err) + } + } + } +} + impl<'config, H> InvokerT, H, Opcode> for Invoker<'config> where H: RuntimeFullBackend + TransactionalBackend,