diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index a3afec529..5256c1077 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@v2 - name: Build run: cargo build --verbose - - name: Build for feature + - name: Build for feature (tracing) run: cargo build --features tracing --verbose - name: Run tests run: cargo test --verbose diff --git a/core/src/external.rs b/core/src/external.rs new file mode 100644 index 000000000..85a98a6c5 --- /dev/null +++ b/core/src/external.rs @@ -0,0 +1,13 @@ +use primitive_types::H160; + +/// Operations for recording external costs +pub enum ExternalOperation { + /// Reading basic account from storage. Fixed size. + AccountBasicRead, + /// Reading address code from storage. Dynamic size. + AddressCodeRead(H160), + /// Basic check for account emptiness. Fixed size. + IsEmpty, + /// Writing to storage. Fixed size. + Write, +} diff --git a/core/src/lib.rs b/core/src/lib.rs index 0dd187c54..20e8fc053 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -8,6 +8,7 @@ extern crate alloc; mod error; mod eval; +mod external; mod memory; mod opcode; mod stack; @@ -15,6 +16,7 @@ mod utils; mod valids; pub use crate::error::{Capture, ExitError, ExitFatal, ExitReason, ExitRevert, ExitSucceed, Trap}; +pub use crate::external::ExternalOperation; pub use crate::memory::Memory; pub use crate::opcode::Opcode; pub use crate::stack::Stack; diff --git a/gasometer/src/lib.rs b/gasometer/src/lib.rs index ce60a3907..fe57722ad 100644 --- a/gasometer/src/lib.rs +++ b/gasometer/src/lib.rs @@ -510,7 +510,10 @@ pub fn dynamic_opcode_cost( value: U256::from_big_endian(&stack.peek(2)?[..]), gas: U256::from_big_endian(&stack.peek(0)?[..]), target_is_cold: handler.is_cold(target, None)?, - target_exists: handler.exists(target), + target_exists: { + handler.record_external_operation(evm_core::ExternalOperation::IsEmpty)?; + handler.exists(target) + }, } } Opcode::STATICCALL => { @@ -519,7 +522,10 @@ pub fn dynamic_opcode_cost( GasCost::StaticCall { gas: U256::from_big_endian(&stack.peek(0)?[..]), target_is_cold: handler.is_cold(target, None)?, - target_exists: handler.exists(target), + target_exists: { + handler.record_external_operation(evm_core::ExternalOperation::IsEmpty)?; + handler.exists(target) + }, } } Opcode::SHA3 => GasCost::Sha3 { @@ -553,7 +559,10 @@ pub fn dynamic_opcode_cost( GasCost::DelegateCall { gas: U256::from_big_endian(&stack.peek(0)?[..]), target_is_cold: handler.is_cold(target, None)?, - target_exists: handler.exists(target), + target_exists: { + handler.record_external_operation(evm_core::ExternalOperation::IsEmpty)?; + handler.exists(target) + }, } } Opcode::DELEGATECALL => GasCost::Invalid(opcode), @@ -606,7 +615,10 @@ pub fn dynamic_opcode_cost( GasCost::Suicide { value: handler.balance(address), target_is_cold: handler.is_cold(target, None)?, - target_exists: handler.exists(target), + target_exists: { + handler.record_external_operation(evm_core::ExternalOperation::IsEmpty)?; + handler.exists(target) + }, already_removed: handler.deleted(address), } } @@ -620,7 +632,10 @@ pub fn dynamic_opcode_cost( value: U256::from_big_endian(&stack.peek(2)?[..]), gas: U256::from_big_endian(&stack.peek(0)?[..]), target_is_cold: handler.is_cold(target, None)?, - target_exists: handler.exists(target), + target_exists: { + handler.record_external_operation(evm_core::ExternalOperation::IsEmpty)?; + handler.exists(target) + }, } } diff --git a/runtime/src/eval/system.rs b/runtime/src/eval/system.rs index e7ef50365..adf6bffd1 100644 --- a/runtime/src/eval/system.rs +++ b/runtime/src/eval/system.rs @@ -90,21 +90,33 @@ pub fn base_fee(runtime: &mut Runtime, handler: &H) -> Control { Control::Continue } -pub fn extcodesize(runtime: &mut Runtime, handler: &H) -> Control { +pub fn extcodesize(runtime: &mut Runtime, handler: &mut H) -> Control { pop!(runtime, address); - push_u256!(runtime, handler.code_size(address.into())); + if let Err(e) = + handler.record_external_operation(crate::ExternalOperation::AddressCodeRead(address.into())) + { + return Control::Exit(e.into()); + } + let code_size = handler.code_size(address.into()); + push_u256!(runtime, code_size); Control::Continue } -pub fn extcodehash(runtime: &mut Runtime, handler: &H) -> Control { +pub fn extcodehash(runtime: &mut Runtime, handler: &mut H) -> Control { pop!(runtime, address); - push!(runtime, handler.code_hash(address.into())); + if let Err(e) = + handler.record_external_operation(crate::ExternalOperation::AddressCodeRead(address.into())) + { + return Control::Exit(e.into()); + } + let code_hash = handler.code_hash(address.into()); + push!(runtime, code_hash); Control::Continue } -pub fn extcodecopy(runtime: &mut Runtime, handler: &H) -> Control { +pub fn extcodecopy(runtime: &mut Runtime, handler: &mut H) -> Control { pop!(runtime, address); pop_u256!(runtime, memory_offset, code_offset, len); @@ -112,12 +124,18 @@ pub fn extcodecopy(runtime: &mut Runtime, handler: &H) -> Control .machine .memory_mut() .resize_offset(memory_offset, len)); - match runtime.machine.memory_mut().copy_large( - memory_offset, - code_offset, - len, - &handler.code(address.into()), - ) { + + if let Err(e) = + handler.record_external_operation(crate::ExternalOperation::AddressCodeRead(address.into())) + { + return Control::Exit(e.into()); + } + let code = handler.code(address.into()); + match runtime + .machine + .memory_mut() + .copy_large(memory_offset, code_offset, len, &code) + { Ok(()) => (), Err(e) => return Control::Exit(e.into()), }; diff --git a/runtime/src/handler.rs b/runtime/src/handler.rs index 31e894fbe..10eb976c7 100644 --- a/runtime/src/handler.rs +++ b/runtime/src/handler.rs @@ -120,4 +120,7 @@ pub trait Handler { fn other(&mut self, opcode: Opcode, _stack: &mut Machine) -> Result<(), ExitError> { Err(ExitError::InvalidCode(opcode)) } + + /// Records some associated `ExternalOperation`. + fn record_external_operation(&mut self, op: crate::ExternalOperation) -> Result<(), ExitError>; } diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 7cda7fd1d..2309c85e6 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -5,10 +5,9 @@ mod memory; pub use self::memory::{MemoryAccount, MemoryBackend, MemoryVicinity}; - +use crate::ExitError; use alloc::vec::Vec; use primitive_types::{H160, H256, U256}; - /// Basic account information. #[derive(Clone, Eq, PartialEq, Debug, Default)] #[cfg_attr( @@ -50,7 +49,7 @@ pub enum Apply { } /// EVM backend. -#[auto_impl::auto_impl(&, Arc, Box)] +//#[auto_impl::auto_impl(&, Arc, Box)] pub trait Backend { /// Gas price. Unused for London. fn gas_price(&self) -> U256; @@ -85,6 +84,13 @@ pub trait Backend { fn storage(&self, address: H160, index: H256) -> H256; /// Get original storage value of address at index, if available. fn original_storage(&self, address: H160, index: H256) -> Option; + + fn record_external_operation( + &mut self, + _op: crate::ExternalOperation, + ) -> Result<(), ExitError> { + Ok(()) + } } /// EVM backend that can apply changes. diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index 4d30981d5..856e6fe2b 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -229,6 +229,25 @@ pub trait StackState<'config>: Backend { fn code_hash(&self, address: H160) -> H256 { H256::from_slice(Keccak256::digest(self.code(address)).as_slice()) } + + fn record_external_dynamic_opcode_cost( + &mut self, + _opcode: Opcode, + _gas_cost: crate::gasometer::GasCost, + _target: StorageTarget, + ) -> Result<(), ExitError> { + Ok(()) + } + + fn record_external_cost( + &mut self, + _ref_time: Option, + _proof_size: Option, + ) -> Result<(), ExitError> { + Ok(()) + } + + fn refund_external_cost(&mut self, _ref_time: Option, _proof_size: Option) {} } /// Stack-based executor. @@ -435,7 +454,6 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> if let Some(limit) = self.config.max_initcode_size { if init_code.len() > limit { self.state.metadata_mut().gasometer.fail(); - let _ = self.exit_substate(StackExitKind::Failed); return emit_exit!(ExitError::CreateContractLimit.into(), Vec::new()); } } @@ -476,7 +494,6 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> if let Some(limit) = self.config.max_initcode_size { if init_code.len() > limit { self.state.metadata_mut().gasometer.fail(); - let _ = self.exit_substate(StackExitKind::Failed); return emit_exit!(ExitError::CreateContractLimit.into(), Vec::new()); } } @@ -567,7 +584,9 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> self.initialize_with_access_list(access_list); } - + if let Err(e) = self.record_external_operation(crate::ExternalOperation::AccountBasicRead) { + return (e.into(), Vec::new()); + } if let Err(e) = self.state.inc_nonce(caller) { return (e.into(), Vec::new()); } @@ -704,6 +723,9 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> return Capture::Exit((ExitError::OutOfFund.into(), None, Vec::new())); } + if let Err(e) = self.record_external_operation(crate::ExternalOperation::AccountBasicRead) { + return Capture::Exit((ExitReason::Error(e), None, Vec::new())); + } if let Err(e) = self.state.inc_nonce(caller) { return Capture::Exit((e.into(), None, Vec::new())); } @@ -729,7 +751,14 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> self.enter_substate(gas_limit, false); { - if self.code_size(address) != U256::zero() { + if let Err(e) = + self.record_external_operation(crate::ExternalOperation::AddressCodeRead(address)) + { + let _ = self.exit_substate(StackExitKind::Failed); + return Capture::Exit((ExitReason::Error(e), None, Vec::new())); + } + let code_size = self.code_size(address); + if code_size != U256::zero() { let _ = self.exit_substate(StackExitKind::Failed); return Capture::Exit((ExitError::CreateCollision.into(), None, Vec::new())); } @@ -761,6 +790,12 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> } if self.config.create_increase_nonce { + if let Err(e) = + self.record_external_operation(crate::ExternalOperation::AccountBasicRead) + { + let _ = self.exit_substate(StackExitKind::Failed); + return Capture::Exit((ExitReason::Error(e), None, Vec::new())); + } if let Err(e) = self.state.inc_nonce(address) { return Capture::Exit((e.into(), None, Vec::new())); } @@ -838,11 +873,16 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> } } - let code = self.code(code_address); - self.enter_substate(gas_limit, is_static); self.state.touch(context.address); + if let Err(e) = + self.record_external_operation(crate::ExternalOperation::AddressCodeRead(code_address)) + { + let _ = self.exit_substate(StackExitKind::Failed); + return Capture::Exit((ExitReason::Error(e), Vec::new())); + } + let code = self.code(code_address); if let Some(depth) = self.state.metadata().depth { if depth > self.config.call_stack_limit { let _ = self.exit_substate(StackExitKind::Reverted); @@ -851,6 +891,12 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> } if let Some(transfer) = transfer { + if let Err(e) = + self.record_external_operation(crate::ExternalOperation::AccountBasicRead) + { + let _ = self.exit_substate(StackExitKind::Failed); + return Capture::Exit((ExitReason::Error(e), Vec::new())); + } match self.state.transfer(transfer) { Ok(()) => (), Err(e) => { @@ -956,6 +1002,12 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> { Ok(()) => { let exit_result = self.exit_substate(StackExitKind::Succeeded); + + if let Err(e) = + self.record_external_operation(crate::ExternalOperation::Write) + { + return (e.into(), None, Vec::new()); + } self.state.set_code(address, out); if let Err(e) = exit_result { return (e.into(), None, Vec::new()); @@ -1276,8 +1328,11 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Handler )?; let gasometer = &mut self.state.metadata_mut().gasometer; - gasometer.record_dynamic_cost(gas_cost, memory_cost)?; + + self.state + .record_external_dynamic_opcode_cost(opcode, gas_cost, target)?; + match target { StorageTarget::Address(address) => { self.state.metadata_mut().access_address(address) @@ -1291,6 +1346,10 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Handler Ok(()) } + + fn record_external_operation(&mut self, op: crate::ExternalOperation) -> Result<(), ExitError> { + self.state.record_external_operation(op) + } } struct StackExecutorHandle<'inner, 'config, 'precompiles, S, P> { @@ -1325,11 +1384,13 @@ impl<'inner, 'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Pr Err(err) => return (ExitReason::Error(err), Vec::new()), }; + let target_exists = self.executor.exists(code_address); + let gas_cost = crate::gasometer::GasCost::Call { value: transfer.clone().map(|x| x.value).unwrap_or_else(U256::zero), gas: U256::from(gas_limit.unwrap_or(u64::MAX)), target_is_cold, - target_exists: self.executor.exists(code_address), + target_exists, }; // We record the length of the input. @@ -1392,6 +1453,24 @@ impl<'inner, 'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Pr .record_cost(cost) } + /// Record Substrate specific cost. + fn record_external_cost( + &mut self, + ref_time: Option, + proof_size: Option, + ) -> Result<(), ExitError> { + self.executor + .state + .record_external_cost(ref_time, proof_size) + } + + /// Refund Substrate specific cost. + fn refund_external_cost(&mut self, ref_time: Option, proof_size: Option) { + self.executor + .state + .refund_external_cost(ref_time, proof_size); + } + /// Retreive the remaining gas. fn remaining_gas(&self) -> u64 { self.executor.state.metadata().gasometer.gas() diff --git a/src/executor/stack/memory.rs b/src/executor/stack/memory.rs index 957b2b3d5..d9658f033 100644 --- a/src/executor/stack/memory.rs +++ b/src/executor/stack/memory.rs @@ -398,9 +398,9 @@ impl<'config> MemoryStackSubstate<'config> { } } -#[derive(Clone, Debug)] +#[derive(Debug)] pub struct MemoryStackState<'backend, 'config, B> { - backend: &'backend B, + backend: &'backend mut B, substate: MemoryStackSubstate<'config>, } @@ -451,9 +451,10 @@ impl<'backend, 'config, B: Backend> Backend for MemoryStackState<'backend, 'conf } fn code(&self, address: H160) -> Vec { - self.substate - .known_code(address) - .unwrap_or_else(|| self.backend.code(address)) + if let Some(code) = self.substate.known_code(address) { + return code; + } + self.backend.code(address) } fn storage(&self, address: H160, key: H256) -> H256 { @@ -539,7 +540,7 @@ impl<'backend, 'config, B: Backend> StackState<'config> for MemoryStackState<'ba } fn set_code(&mut self, address: H160, code: Vec) { - self.substate.set_code(address, code, self.backend) + self.substate.set_code(address, code, self.backend); } fn transfer(&mut self, transfer: Transfer) -> Result<(), ExitError> { @@ -556,7 +557,7 @@ impl<'backend, 'config, B: Backend> StackState<'config> for MemoryStackState<'ba } impl<'backend, 'config, B: Backend> MemoryStackState<'backend, 'config, B> { - pub fn new(metadata: StackSubstateMetadata<'config>, backend: &'backend B) -> Self { + pub fn new(metadata: StackSubstateMetadata<'config>, backend: &'backend mut B) -> Self { Self { backend, substate: MemoryStackSubstate::new(metadata), diff --git a/src/executor/stack/precompile.rs b/src/executor/stack/precompile.rs index 716eed631..556df901a 100644 --- a/src/executor/stack/precompile.rs +++ b/src/executor/stack/precompile.rs @@ -50,6 +50,16 @@ pub trait PrecompileHandle { /// Record cost to the Runtime gasometer. fn record_cost(&mut self, cost: u64) -> Result<(), ExitError>; + /// Record Substrate specific cost. + fn record_external_cost( + &mut self, + ref_time: Option, + proof_size: Option, + ) -> Result<(), ExitError>; + + /// Refund Substrate specific cost. + fn refund_external_cost(&mut self, ref_time: Option, proof_size: Option); + /// Retreive the remaining gas. fn remaining_gas(&self) -> u64;