diff --git a/interpreter/src/call_create.rs b/interpreter/src/call_create.rs index 7fc853ae0..3c6825bc1 100644 --- a/interpreter/src/call_create.rs +++ b/interpreter/src/call_create.rs @@ -1,3 +1,5 @@ +//! Call and create trap handler. + use crate::utils::{h256_to_u256, u256_to_usize}; use crate::{ Context, ExitError, ExitException, ExitResult, Machine, Memory, Opcode, RuntimeBackend, @@ -72,8 +74,11 @@ pub enum CallScheme { StaticCall, } +/// Combined call create trap data. pub enum CallCreateTrapData { + /// A call trap data. Call(CallTrapData), + /// A create trap data. Create(CreateTrapData), } diff --git a/interpreter/src/error.rs b/interpreter/src/error.rs index cf4db0152..bd5d6342f 100644 --- a/interpreter/src/error.rs +++ b/interpreter/src/error.rs @@ -27,6 +27,7 @@ impl Capture { } } +/// Exit result. pub type ExitResult = Result; /// Exit reason. diff --git a/interpreter/src/lib.rs b/interpreter/src/lib.rs index 965b3d771..09cd00dc5 100644 --- a/interpreter/src/lib.rs +++ b/interpreter/src/lib.rs @@ -58,6 +58,7 @@ impl Machine { self.position } + /// Machine code. pub fn code(&self) -> &[u8] { &self.code } @@ -84,6 +85,8 @@ impl Machine { } } + /// Perform any operation. If the operation fails, then set the machine + /// status to already exited. pub fn perform Result>( &mut self, f: F, @@ -198,10 +201,12 @@ impl Machine { self.code.get(self.position).map(|opcode| Opcode(*opcode)) } + /// Whether the machine has empty code. pub fn is_empty(&self) -> bool { self.code.is_empty() } + /// Advance the PC to the next opcode. pub fn advance(&mut self) { if self.position == self.code.len() { return; diff --git a/interpreter/src/stack.rs b/interpreter/src/stack.rs index 426f03c01..672cc42a2 100644 --- a/interpreter/src/stack.rs +++ b/interpreter/src/stack.rs @@ -18,6 +18,10 @@ macro_rules! impl_perform_popn_pushn { ($($peek_push:expr),*), $pop_pushn_f:ident ) => { + /// Pop $pop_len values from the stack, and then push $push_len values + /// into the stack. + /// + /// If `f` returns error, then the stack will not be changed. #[allow(unused_parens)] pub fn $name(&mut self, f: F) -> Result where F: FnOnce( @@ -98,6 +102,7 @@ impl Stack { Ok(()) } + /// Check whether it's possible to pop and push enough items in the stack. pub fn check_pop_push(&self, pop: usize, push: usize) -> Result<(), ExitException> { if self.data.len() < pop { return Err(ExitException::StackUnderflow); diff --git a/interpreter/src/utils.rs b/interpreter/src/utils.rs index d3d4ce695..7b2320c9e 100644 --- a/interpreter/src/utils.rs +++ b/interpreter/src/utils.rs @@ -1,18 +1,23 @@ +//! Small utilities. + use crate::{ExitError, ExitFatal}; use core::cmp::Ordering; use core::ops::{Div, Rem}; use primitive_types::{H256, U256}; +/// Convert [U256] into [H256]. pub fn u256_to_h256(v: U256) -> H256 { let mut r = H256::default(); v.to_big_endian(&mut r[..]); r } +/// Convert [H256] to [U256]. pub fn h256_to_u256(v: H256) -> U256 { U256::from_big_endian(&v[..]) } +/// Convert [U256] to [usize]. pub fn u256_to_usize(v: U256) -> Result { if v > U256::from(usize::MAX) { return Err(ExitFatal::NotSupported.into()); @@ -20,6 +25,7 @@ pub fn u256_to_usize(v: U256) -> Result { Ok(v.as_usize()) } +/// Sign of [I256]. #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum Sign { Plus, @@ -34,6 +40,7 @@ const SIGN_BIT_MASK: U256 = U256([ 0x7fffffffffffffff, ]); +/// Signed 256-bit integer. #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub struct I256(pub Sign, pub U256); diff --git a/jsontests/src/hash.rs b/jsontests/src/hash.rs index 43ca35b21..e803ab842 100644 --- a/jsontests/src/hash.rs +++ b/jsontests/src/hash.rs @@ -1,4 +1,4 @@ -use evm::backend::in_memory::InMemoryBackend; +use crate::in_memory::InMemoryBackend; use evm::utils::h256_to_u256; use primitive_types::{H256, U256}; use sha3::{Digest, Keccak256}; diff --git a/src/backend/in_memory.rs b/jsontests/src/in_memory.rs similarity index 98% rename from src/backend/in_memory.rs rename to jsontests/src/in_memory.rs index fe696dd55..1a94b08b5 100644 --- a/src/backend/in_memory.rs +++ b/jsontests/src/in_memory.rs @@ -1,10 +1,12 @@ -use crate::{ +use evm::{ ExitError, ExitException, Log, MergeStrategy, RuntimeBackend, RuntimeBaseBackend, RuntimeEnvironment, TransactionalBackend, }; -use alloc::collections::{BTreeMap, BTreeSet}; use primitive_types::{H160, H256, U256}; -use std::mem; +use std::{ + collections::{BTreeMap, BTreeSet}, + mem, +}; #[derive(Clone, Debug)] pub struct InMemoryEnvironment { diff --git a/jsontests/src/main.rs b/jsontests/src/main.rs index c40418762..88cd5228a 100644 --- a/jsontests/src/main.rs +++ b/jsontests/src/main.rs @@ -1,5 +1,6 @@ mod error; mod hash; +mod in_memory; mod run; mod types; diff --git a/jsontests/src/run.rs b/jsontests/src/run.rs index de781bde8..60bf4ced7 100644 --- a/jsontests/src/run.rs +++ b/jsontests/src/run.rs @@ -1,8 +1,6 @@ use crate::error::{Error, TestError}; +use crate::in_memory::{InMemoryAccount, InMemoryBackend, InMemoryEnvironment, InMemoryLayer}; use crate::types::*; -use evm::backend::in_memory::{ - InMemoryAccount, InMemoryBackend, InMemoryEnvironment, InMemoryLayer, -}; use evm::standard::{Config, Etable, EtableResolver, Gasometer, Invoker, TransactArgs}; use evm::utils::u256_to_h256; use evm::Capture; diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 900fa7b5f..fe8ca30f3 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -1,6 +1,26 @@ -pub mod in_memory; +//! # Backend-related traits and implementations +//! +//! A backend exposes external information that is available to an EVM +//! interpreter. This includes block information such as the current coinbase, +//! block gas limit, etc, as well as the state such as account balance, storage +//! and code. +//! +//! Backends have layers, representing information that may be committed or +//! discard after the current call stack finishes. Due to the vast differences of +//! how different backends behave (for example, in some backends like wasm, +//! pushing/poping layers are dealt by extern functions), layers are handled +//! internally inside a backend. +pub use evm_interpreter::{RuntimeBackend, RuntimeBaseBackend, RuntimeEnvironment}; + +/// Backend with layers that can transactionally be committed or discarded. pub trait TransactionalBackend { + /// Push a new substate layer into the backend. fn push_substate(&mut self); + /// Pop the last substate layer from the backend, either committing or + /// discarding it. + /// + /// The caller is expected to maintain balance of push/pop, and the backend + /// are free to panic if it does not. fn pop_substate(&mut self, strategy: crate::MergeStrategy); } diff --git a/src/call_stack.rs b/src/call_stack.rs index 1dd622e46..00a1c8d73 100644 --- a/src/call_stack.rs +++ b/src/call_stack.rs @@ -293,6 +293,9 @@ enum HeapTransactState<'backend, 'invoker, H, Tr, I: Invoker> { }, } +/// Heap-based call stack for a transaction. This is suitable for single +/// stepping or debugging. The hybrid version [transact] uses a heap-based call +/// stack internally after certain depth. pub struct HeapTransact<'backend, 'invoker, H, Tr, I: Invoker>( Option>, ); @@ -301,6 +304,7 @@ impl<'backend, 'invoker, H, Tr, I> HeapTransact<'backend, 'invoker, H, Tr, I> where I: Invoker, { + /// Create a new heap-based call stack. pub fn new( args: I::TransactArgs, invoker: &'invoker I, @@ -385,18 +389,21 @@ where ret } + /// Step the call stack, but run the interpreter inside. pub fn step_run( &mut self, ) -> Result<(), Capture, I::Interrupt>> { self.step_with(|call_stack| call_stack.step_run()) } + /// Step the call stack, and step the interpreter inside. pub fn step( &mut self, ) -> Result<(), Capture, I::Interrupt>> { self.step_with(|call_stack| call_stack.step()) } + /// Run the call stack until it exits or receives interrupts. pub fn run(&mut self) -> Capture, I::Interrupt> { loop { let step_ret = self.step_run(); @@ -407,6 +414,8 @@ where } } + /// The machine of the last item on the call stack. This will be `None` if + /// the heap stack is just created. pub fn last_machine(&self) -> Option<&I::Machine> { match &self.0 { Some(HeapTransactState::Running { call_stack, .. }) => match &call_stack.last { @@ -467,6 +476,18 @@ where } } +/// Initiate a transaction, using a hybrid call stack. +/// +/// Up until `heap_depth`, a stack-based call stack is used first. A stack-based +/// call stack is faster, but for really deep calls, it can reach the default +/// stack size limit of the platform and thus overflow. +/// +/// After `heap_depth`, a heap-based call stack is then used. +/// +/// If `heap_depth` is `None`, then always use a stack-based call stack. +/// +/// Because a stack-based call stack cannot handle interrupts, the [Invoker] +/// type must have its `Interrupt` type set to [Infallible]. pub fn transact( args: I::TransactArgs, heap_depth: Option, diff --git a/src/color.rs b/src/color.rs index 64206d599..9721ef492 100644 --- a/src/color.rs +++ b/src/color.rs @@ -2,10 +2,31 @@ use crate::{ Capture, Control, Etable, ExitResult, Gasometer, InvokerMachine, Machine, Opcode, RuntimeState, }; +/// # Colored machine. +/// +/// A colored machine combines the machine interpreter, the gasometer, as well +/// as a [Color]. It's the machine type that is pushed into a standard +/// [crate::standard::Invoker] call stack. +/// +/// ## About the color field +/// +/// A color is anything that implements the [Color] trait, defining how the +/// combined machine should be stepped or ran. +/// +/// The standard color for a machine is an [Etable] (resolved by the standard +/// [crate::standard::EtableResolver]). The machine will use the opcode handler +/// defined in the etable for the machine invocation. +/// +/// A customized color can allow you to implement account versioning or a +/// complex precompile that invoke subcalls. pub struct ColoredMachine { + /// The interpreter machine. pub machine: Machine, + /// The gasometer. pub gasometer: G, + /// Whether the current call stack is static. pub is_static: bool, + /// The color of the machine. pub color: C, } @@ -38,7 +59,9 @@ where } } +/// A color of an machine. pub trait Color { + /// Step the machine. fn step( &self, machine: &mut Machine, @@ -47,6 +70,7 @@ pub trait Color { handler: &mut H, ) -> Result<(), Capture>; + /// Run the machine. fn run( &self, machine: &mut Machine, diff --git a/src/gasometer.rs b/src/gasometer.rs index 95f7fe6f7..c3bef47f9 100644 --- a/src/gasometer.rs +++ b/src/gasometer.rs @@ -1,35 +1,27 @@ //! EVM gasometer. use crate::{ExitError, Machine}; -use core::ops::{Add, AddAssign, Sub, SubAssign}; use primitive_types::U256; -pub trait Gas: - Copy - + Into - + Add - + AddAssign - + Sub - + SubAssign -{ -} - -impl Gas for u64 {} -impl Gas for U256 {} - +/// A static gasometer, exposing functions for precompile cost recording or for +/// transactions. pub trait StaticGasometer: Sized { fn record_cost(&mut self, cost: U256) -> Result<(), ExitError>; fn record_codedeposit(&mut self, len: usize) -> Result<(), ExitError>; fn gas(&self) -> U256; } +/// A gasometer that is suitable for an interpreter machine. pub trait Gasometer: StaticGasometer { + /// Record gas cost for a single opcode step. fn record_step( &mut self, machine: &Machine, is_static: bool, backend: &H, ) -> Result<(), ExitError>; + /// Record gas cost, advancing as much as possible (possibly into the next + /// branch). Returns the number of advances. fn record_stepn( &mut self, machine: &Machine, diff --git a/src/invoker.rs b/src/invoker.rs index e2bde3a03..d3d5d7587 100644 --- a/src/invoker.rs +++ b/src/invoker.rs @@ -1,27 +1,50 @@ use crate::{Capture, ExitError, ExitResult}; +/// Control for an invoker. pub enum InvokerControl { + /// Pushing the call stack. Enter(VE), + /// Directly exit, not pushing the call stack. DirectExit(VD), } +/// A machine that is put onto the call stack. pub trait InvokerMachine { + /// Deconstruct value of the machine. + /// + /// This type is needed bacause an invoker may not push a value onto the + /// call stack, but directly exit. In the latter case, it should return the + /// deconstruct value. When popping from the call stack, we also deconstruct + /// the machine to the deconstruct value, thus unifying the types. type Deconstruct; + /// Step the machine using a handler. fn step(&mut self, handler: &mut H) -> Result<(), Capture>; + /// Run the machine until it returns. fn run(&mut self, handler: &mut H) -> Capture; + /// Deconstruct the machine to its deconstruct value. fn deconstruct(self) -> Self::Deconstruct; } +/// An invoker, responsible for pushing/poping values in the call stack. pub trait Invoker { + /// Machine type on the call stack. type Machine: InvokerMachine; + /// Possible interrupt type that may be returned by the call stack. type Interrupt; + /// Type for transaction arguments. type TransactArgs; + /// The invoke of a top-layer transaction call stack. When finalizing a + /// transaction, this invoke is used to figure out the finalization routine. type TransactInvoke; + /// The returned value of the transaction. type TransactValue; + /// The invoke of a sub-layer call stack. When exiting a call stack, this + /// invoke is used to figure out the exit routine. type SubstackInvoke; + /// Create a new transaction with the given transaction arguments. fn new_transact( &self, args: Self::TransactArgs, @@ -39,6 +62,8 @@ pub trait Invoker { ), ExitError, >; + + /// Finalize a transaction. fn finalize_transact( &self, invoke: &Self::TransactInvoke, @@ -47,6 +72,7 @@ pub trait Invoker { handler: &mut H, ) -> Result; + /// Enter a sub-layer call stack. fn enter_substack( &self, trap: Tr, @@ -69,6 +95,8 @@ pub trait Invoker { >, Self::Interrupt, >; + + /// Exit a sub-layer call stack. fn exit_substack( &self, result: ExitResult, diff --git a/src/lib.rs b/src/lib.rs index 4b3a371dc..9018da0ce 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,7 +29,7 @@ //! ## Debugging //! //! Rust EVM supports two different methods for debugging. You can either single -//! stepping the execution, or you can trace the opcodes. +//! step the execution, or you can trace the opcodes. //! //! ### Single stepping //! @@ -46,6 +46,18 @@ //! //! If you also want to trace inside gasometers, simply create a wrapper struct //! of the gasometer you use, and pass that into the invoker. +//! +//! ## Customization +//! +//! All aspects of the interpreter can be customized individually. +//! +//! * New opcodes can be added or customized through [Etable]. +//! * Gas metering behavior can be customized by wrapping [standard::Gasometer] or creating new +//! ones. +//! * Code resolution and precompiles can be customized by [standard::Resolver]. +//! * Call invocation and transaction behavior can be customized via [standard::Invoker]. +//! * Finally, each machine on the call stack has the concept of [Color], which allows you to +//! implement account versioning, or specialized precompiles that invoke subcalls. #![deny(warnings)] #![forbid(unsafe_code, unused_variables)] @@ -66,12 +78,18 @@ pub use evm_interpreter::*; pub use crate::backend::TransactionalBackend; pub use crate::call_stack::{transact, HeapTransact}; pub use crate::color::{Color, ColoredMachine}; -pub use crate::gasometer::{Gas, Gasometer, StaticGasometer}; +pub use crate::gasometer::{Gasometer, StaticGasometer}; pub use crate::invoker::{Invoker, InvokerControl, InvokerMachine}; +/// Merge strategy of a backend substate layer or a call stack gasometer layer. #[derive(Clone, Debug, Copy)] pub enum MergeStrategy { + /// Fully commit the sub-layer into the parent. This happens if the sub-machine executes + /// successfully. Commit, + /// Revert the state, but keep remaining gases. This happens with the `REVERT` opcode. Revert, + /// Discard the state and gases. This happens in all situations where the machine encounters an + /// error. Discard, } diff --git a/src/standard/gasometer/mod.rs b/src/standard/gasometer/mod.rs index 05da39ef4..47c3d9512 100644 --- a/src/standard/gasometer/mod.rs +++ b/src/standard/gasometer/mod.rs @@ -10,7 +10,9 @@ use crate::{ use core::cmp::{max, min}; use primitive_types::{H160, H256, U256}; +/// A gasometer that handles transaction metering. pub trait TransactGasometer<'config, S: AsRef>: Sized { + /// Create a new top-layer gasometer from a call transaction. fn new_transact_call( gas_limit: U256, data: &[u8], @@ -18,6 +20,7 @@ pub trait TransactGasometer<'config, S: AsRef>: Sized { config: &'config Config, ) -> Result; + /// Create a new top-layer gasometer from a create transaction. fn new_transact_create( gas_limit: U256, code: &[u8], @@ -25,16 +28,21 @@ pub trait TransactGasometer<'config, S: AsRef>: Sized { config: &'config Config, ) -> Result; + /// Effective gas, assuming the current gasometer is a top-layer gasometer, + /// reducing the amount of refunded gas. fn effective_gas(&self) -> U256; + /// Derive a sub-gasometer from the current one, pushing the call stack. fn submeter(&mut self, gas_limit: U256, call_has_value: bool) -> Result; + /// Merge a sub-gasometer using the given strategy, poping the call stack. fn merge(&mut self, other: Self, strategy: MergeStrategy); /// Analyse the code so that the gasometer can apply further optimizations. fn analyse_code(&mut self, _code: &[u8]) {} } +/// Standard gasometer implementation. pub struct Gasometer<'config> { gas_limit: u64, memory_gas: u64, @@ -44,6 +52,8 @@ pub struct Gasometer<'config> { } impl<'config> Gasometer<'config> { + /// Perform any operation on the gasometer. Set the gasometer to `OutOfGas` + /// if the operation fails. #[inline] pub fn perform Result>( &mut self, @@ -58,6 +68,7 @@ impl<'config> Gasometer<'config> { } } + /// Set the current gasometer to `OutOfGas`. pub fn oog(&mut self) { self.memory_gas = 0; self.refunded_gas = 0; @@ -69,6 +80,7 @@ impl<'config> Gasometer<'config> { self.used_gas + self.memory_gas } + /// Left gas that is supposed to be available to the current interpreter. pub fn gas(&self) -> u64 { self.gas_limit - self.memory_gas - self.used_gas } @@ -88,6 +100,7 @@ impl<'config> Gasometer<'config> { } } + /// Set memory gas usage. pub fn set_memory_gas(&mut self, memory_cost: u64) -> Result<(), ExitError> { let all_gas_cost = self.used_gas.checked_add(memory_cost); if let Some(all_gas_cost) = all_gas_cost { @@ -102,6 +115,7 @@ impl<'config> Gasometer<'config> { } } + /// Create a new gasometer with the given gas limit and chain config. pub fn new(gas_limit: u64, config: &'config Config) -> Self { Self { gas_limit, @@ -504,7 +518,7 @@ fn dynamic_opcode_cost( /// Gas cost. #[derive(Debug, Clone, Copy)] -pub enum GasCost { +enum GasCost { /// Zero gas cost. Zero, /// Base gas cost. @@ -754,7 +768,7 @@ impl GasCost { /// Memory cost. #[derive(Debug, Clone, Copy)] -pub struct MemoryCost { +struct MemoryCost { /// Affected memory offset. pub offset: U256, /// Affected length. @@ -807,7 +821,7 @@ impl MemoryCost { /// Transaction cost. #[derive(Debug, Clone, Copy)] -pub enum TransactionCost { +enum TransactionCost { /// Call transaction cost. Call { /// Length of zeros in transaction data. @@ -912,7 +926,7 @@ fn count_access_list(access_list: &[(H160, Vec)]) -> (usize, usize) { (access_list_address_len, access_list_storage_len) } -pub fn init_code_cost(data: &[u8]) -> u64 { +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. diff --git a/src/standard/invoker/mod.rs b/src/standard/invoker/mod.rs index 7afceca52..b0e8d5637 100644 --- a/src/standard/invoker/mod.rs +++ b/src/standard/invoker/mod.rs @@ -18,9 +18,13 @@ use core::marker::PhantomData; use primitive_types::{H160, H256, U256}; use sha3::{Digest, Keccak256}; +/// A trap that can be turned into either a call/create trap (where we push new +/// call stack), or an interrupt (an external signal). pub trait IntoCallCreateTrap { + /// An external signal. type Interrupt; + /// Turn the current trap into either a call/create trap or an interrupt. fn into_call_create_trap(self) -> Result; } @@ -32,11 +36,13 @@ impl IntoCallCreateTrap for Opcode { } } +/// The invoke used in a substack. pub enum SubstackInvoke { Call { trap: CallTrapData }, Create { trap: CreateTrapData, address: H160 }, } +/// The invoke used in a top-layer transaction stack. pub struct TransactInvoke { pub create_address: Option, pub gas_fee: U256, @@ -44,29 +50,47 @@ pub struct TransactInvoke { pub caller: H160, } +/// Transaction arguments. #[derive(Clone, Debug)] pub enum TransactArgs { + /// A call transaction. Call { + /// Transaction sender. caller: H160, + /// Transaction target. address: H160, + /// Transaction value. value: U256, + /// Transaction call data. data: Vec, + /// Transaction gas limit. gas_limit: U256, + /// Transaction gas price. gas_price: U256, + /// Access list information, in the format of (address, storage keys). access_list: Vec<(H160, Vec)>, }, + /// A create transaction. Create { + /// Transaction sender. caller: H160, + /// Transaction value. value: U256, + /// Init code. init_code: Vec, - salt: Option, // Some for CREATE2 + /// Salt of `CREATE2`. `None` for a normal create transaction. + salt: Option, + /// Transaction gas limit. gas_limit: U256, + /// Transaction gas price. gas_price: U256, + /// Access list information, in the format of (address, storage keys). access_list: Vec<(H160, Vec)>, }, } impl TransactArgs { + /// Transaction gas limit. pub fn gas_limit(&self) -> U256 { match self { Self::Call { gas_limit, .. } => *gas_limit, @@ -74,6 +98,7 @@ impl TransactArgs { } } + /// Transaction gas price. pub fn gas_price(&self) -> U256 { match self { Self::Call { gas_price, .. } => *gas_price, @@ -81,6 +106,7 @@ impl TransactArgs { } } + /// Access list information. pub fn access_list(&self) -> &Vec<(H160, Vec)> { match self { Self::Call { access_list, .. } => access_list, @@ -88,6 +114,7 @@ impl TransactArgs { } } + /// Transaction sender. pub fn caller(&self) -> H160 { match self { Self::Call { caller, .. } => *caller, @@ -95,6 +122,7 @@ impl TransactArgs { } } + /// Transaction value. pub fn value(&self) -> U256 { match self { Self::Call { value, .. } => *value, @@ -103,6 +131,16 @@ impl TransactArgs { } } +/// Standard invoker. +/// +/// The generic parameters are as follows: +/// * `S`: The runtime state, usually [RuntimeState] but can be customized. +/// * `G`: Gasometer type, usually [crate::standard::Gasometer] but can be +/// customized. +/// * `H`: Backend type. +/// * `R`: Code resolver type, also handle precompiles. Usually +/// [EtableResolver] but can be customized. +/// * `Tr`: Trap type, usually [crate::Opcode] but can be customized. pub struct Invoker<'config, 'resolver, S, G, H, R, Tr> { config: &'config Config, resolver: &'resolver R, @@ -110,6 +148,7 @@ pub struct Invoker<'config, 'resolver, S, G, H, R, Tr> { } impl<'config, 'resolver, S, G, H, R, Tr> Invoker<'config, 'resolver, S, G, H, R, Tr> { + /// Create a new standard invoker with the given config and resolver. pub fn new(config: &'config Config, resolver: &'resolver R) -> Self { Self { config, diff --git a/src/standard/invoker/resolver.rs b/src/standard/invoker/resolver.rs index 88c4aa24c..ec738faab 100644 --- a/src/standard/invoker/resolver.rs +++ b/src/standard/invoker/resolver.rs @@ -7,9 +7,17 @@ use crate::{ use alloc::rc::Rc; use primitive_types::H160; +/// A code resolver. +/// +/// The resolver handles how a call (with the target code address) or create +/// (with the init code) is turned into a colored machine. The resolver can +/// construct a machine, pushing the call stack, or directly exit, handling a +/// precompile. pub trait Resolver { + /// Color of the machine. See [ColoredMachine] for more information. type Color: Color; + /// Resolve a call (with the target code address). fn resolve_call( &self, code_address: H160, @@ -23,6 +31,7 @@ pub trait Resolver { ExitError, >; + /// Resolve a create (with the init code). fn resolve_create( &self, init_code: Vec, @@ -36,7 +45,10 @@ pub trait Resolver { >; } +/// A set of precompiles. pub trait PrecompileSet { + /// Attempt to execute the precompile at the given `code_address`. Returns + /// `None` if it's not a precompile. fn execute( &self, code_address: H160, @@ -62,6 +74,8 @@ impl PrecompileSet for () { } } +/// The standard code resolver where the color is an [Etable]. This is usually +/// what you need. pub struct EtableResolver<'config, 'precompile, 'etable, S, H, Pre, Tr, F> { config: &'config Config, etable: &'etable Etable, @@ -71,6 +85,7 @@ pub struct EtableResolver<'config, 'precompile, 'etable, S, H, Pre, Tr, F> { impl<'config, 'precompile, 'etable, S, H, Pre, Tr, F> EtableResolver<'config, 'precompile, 'etable, S, H, Pre, Tr, F> { + /// Create a new [Etable] code resolver. pub fn new( config: &'config Config, precompiles: &'precompile Pre, diff --git a/src/standard/mod.rs b/src/standard/mod.rs index b210120e9..bf712929a 100644 --- a/src/standard/mod.rs +++ b/src/standard/mod.rs @@ -1,3 +1,9 @@ +//! # Standard machines and gasometers +//! +//! This module implements the standard configurations of the interpreter, like how it works on +//! Ethereum mainnet. Most of them can still be customized to add additional functionality, by +//! wrapping them or replacing the generic parameters. + mod config; mod gasometer; mod invoker; @@ -6,20 +12,37 @@ pub use self::config::Config; pub use self::gasometer::{Gasometer, TransactGasometer}; pub use self::invoker::{EtableResolver, Invoker, PrecompileSet, Resolver, TransactArgs}; +/// Standard EVM machine, where the runtime state is [crate::RuntimeState]. pub type Machine = crate::Machine; + +/// Standard Etable opcode handle function. pub type Efn = crate::Efn; + +/// Standard Etable. pub type Etable> = crate::Etable; + +/// Standard colored machine, combining an interpreter machine, a gasometer, and the standard +/// "color" -- an etable. pub type ColoredMachine<'etable, G, H, F = Efn> = crate::ColoredMachine>; + +/// Simply [Invoker] with common generics fixed, using standard [Gasometer] and standard trap +/// [crate::Opcode]. pub type SimpleInvoker<'config, 'resolver, H, R> = Invoker<'config, 'resolver, crate::RuntimeState, Gasometer<'config>, H, R, crate::Opcode>; +/// A runtime state that can be merged across call stack substate layers. pub trait MergeableRuntimeState: AsRef + AsMut { + /// Derive a new substate from the substate runtime. fn substate(&self, runtime: crate::RuntimeState, parent: &M) -> Self; + /// Merge a substate into the current runtime state, using the given + /// strategy. fn merge(&mut self, substate: Self, strategy: crate::MergeStrategy); + /// Create a new top-layer runtime state with a call transaction. fn new_transact_call(runtime: crate::RuntimeState) -> Self; + /// Create a new top-layer runtime state with a create transaction. fn new_transact_create(runtime: crate::RuntimeState) -> Self; }