From 699fd9a7d644cc94b116cf70466fc360a8a19148 Mon Sep 17 00:00:00 2001 From: Qinxuan Chen Date: Tue, 28 May 2024 22:10:44 +0800 Subject: [PATCH 1/7] reorg interpreter and adjust rustfmt (#287) reorg interpreter crate - extract exit module from error module - move trap module into error module - add machine module - move memory and stack modules into machine module add more rustfmt configs ```toml imports_granularity="Crate" group_imports = "StdExternalCrate" ``` --- interpreter/src/{error.rs => error/exit.rs} | 27 +--- interpreter/src/error/mod.rs | 30 ++++ interpreter/src/{ => error}/trap.rs | 153 ++++++++++---------- interpreter/src/etable.rs | 14 +- interpreter/src/eval/arithmetic.rs | 5 +- interpreter/src/eval/bitwise.rs | 3 +- interpreter/src/eval/misc.rs | 11 +- interpreter/src/eval/mod.rs | 13 +- interpreter/src/eval/system.rs | 13 +- interpreter/src/interpreter/etable.rs | 13 +- interpreter/src/interpreter/mod.rs | 10 +- interpreter/src/{ => interpreter}/valids.rs | 30 +--- interpreter/src/lib.rs | 79 +--------- interpreter/src/{ => machine}/memory.rs | 14 +- interpreter/src/machine/mod.rs | 55 +++++++ interpreter/src/{ => machine}/stack.rs | 23 +-- interpreter/src/runtime.rs | 8 +- interpreter/src/utils.rs | 14 +- interpreter/tests/performance.rs | 9 +- interpreter/tests/usability.rs | 16 +- jsontests/src/error.rs | 10 +- jsontests/src/hash.rs | 5 +- jsontests/src/in_memory.rs | 8 +- jsontests/src/main.rs | 4 +- jsontests/src/run.rs | 28 ++-- jsontests/src/types.rs | 4 +- precompile/src/blake2/mod.rs | 6 +- precompile/src/bn128.rs | 9 +- precompile/src/lib.rs | 22 ++- precompile/src/modexp.rs | 9 +- precompile/src/simple.rs | 9 +- rustfmt.toml | 2 + src/backend/mod.rs | 3 +- src/backend/overlayed.rs | 11 +- src/call_stack.rs | 9 +- src/gasometer.rs | 2 +- src/invoker.rs | 6 +- src/lib.rs | 12 +- src/standard/gasometer/consts.rs | 2 +- src/standard/gasometer/costs.rs | 8 +- src/standard/gasometer/mod.rs | 15 +- src/standard/gasometer/utils.rs | 2 +- src/standard/invoker/mod.rs | 37 +++-- src/standard/invoker/resolver.rs | 15 +- src/standard/invoker/routines.rs | 19 ++- src/standard/invoker/state.rs | 8 +- src/standard/mod.rs | 30 ++-- tracer/src/lib.rs | 2 +- tracer/src/standard.rs | 6 +- 49 files changed, 488 insertions(+), 355 deletions(-) rename interpreter/src/{error.rs => error/exit.rs} (89%) create mode 100644 interpreter/src/error/mod.rs rename interpreter/src/{ => error}/trap.rs (96%) rename interpreter/src/{ => interpreter}/valids.rs (66%) rename interpreter/src/{ => machine}/memory.rs (97%) create mode 100644 interpreter/src/machine/mod.rs rename interpreter/src/{ => machine}/stack.rs (93%) diff --git a/interpreter/src/error.rs b/interpreter/src/error/exit.rs similarity index 89% rename from interpreter/src/error.rs rename to interpreter/src/error/exit.rs index 92ead5d0b..dcf797168 100644 --- a/interpreter/src/error.rs +++ b/interpreter/src/error/exit.rs @@ -1,32 +1,7 @@ -use crate::Opcode; use alloc::borrow::Cow; use core::fmt; -/// Capture represents the result of execution. -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub enum Capture { - /// The machine has exited. It cannot be executed again. - Exit(E), - /// The machine has trapped. It is waiting for external information, and can - /// be executed again. - Trap(T), -} - -impl Capture { - pub fn exit(self) -> Option { - match self { - Self::Exit(e) => Some(e), - Self::Trap(_) => None, - } - } - - pub fn trap(self) -> Option { - match self { - Self::Exit(_) => None, - Self::Trap(t) => Some(t), - } - } -} +use crate::opcode::Opcode; /// Exit result. pub type ExitResult = Result; diff --git a/interpreter/src/error/mod.rs b/interpreter/src/error/mod.rs new file mode 100644 index 000000000..2473c07a0 --- /dev/null +++ b/interpreter/src/error/mod.rs @@ -0,0 +1,30 @@ +mod exit; +mod trap; + +pub use self::{exit::*, trap::*}; + +/// Capture represents the result of execution. +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum Capture { + /// The machine has exited. It cannot be executed again. + Exit(E), + /// The machine has trapped. It is waiting for external information, and can + /// be executed again. + Trap(T), +} + +impl Capture { + pub fn exit(self) -> Option { + match self { + Self::Exit(e) => Some(e), + Self::Trap(_) => None, + } + } + + pub fn trap(self) -> Option { + match self { + Self::Exit(_) => None, + Self::Trap(t) => Some(t), + } + } +} diff --git a/interpreter/src/trap.rs b/interpreter/src/error/trap.rs similarity index 96% rename from interpreter/src/trap.rs rename to interpreter/src/error/trap.rs index 8f2e43a65..66764b3fb 100644 --- a/interpreter/src/trap.rs +++ b/interpreter/src/error/trap.rs @@ -1,16 +1,22 @@ //! Call and create trap handler. -use crate::utils::{h256_to_u256, u256_to_usize}; -use crate::{ - interpreter::Interpreter, Context, ExitError, ExitException, ExitResult, Machine, Memory, - RuntimeBackend, RuntimeState, Transfer, -}; use alloc::vec::Vec; -use core::cmp::{max, min}; -use core::convert::Infallible; +use core::{ + cmp::{max, min}, + convert::Infallible, +}; + use primitive_types::{H160, H256, U256}; use sha3::{Digest, Keccak256}; +use crate::{ + error::{ExitError, ExitException, ExitResult}, + interpreter::Interpreter, + machine::{Machine, Memory}, + runtime::{Context, RuntimeBackend, RuntimeState, Transfer}, + utils::{h256_to_u256, u256_to_usize}, +}; + pub trait TrapConstruct { fn construct(v: T) -> Self; } @@ -21,72 +27,6 @@ pub trait TrapConsume { fn consume(self) -> Result; } -/// Create scheme. -#[derive(Clone, Copy, Eq, PartialEq, Debug)] -pub enum CreateScheme { - /// Legacy create scheme of `CREATE`. - Legacy { - /// Caller of the create. - caller: H160, - }, - /// Create scheme of `CREATE2`. - Create2 { - /// Caller of the create. - caller: H160, - /// Code hash. - code_hash: H256, - /// Salt. - salt: H256, - }, -} - -impl CreateScheme { - pub fn address(&self, handler: &H) -> H160 { - match self { - Self::Create2 { - caller, - code_hash, - salt, - } => { - let mut hasher = Keccak256::new(); - hasher.update([0xff]); - hasher.update(&caller[..]); - hasher.update(&salt[..]); - hasher.update(&code_hash[..]); - H256::from_slice(hasher.finalize().as_slice()).into() - } - Self::Legacy { caller } => { - let nonce = handler.nonce(*caller); - let mut stream = rlp::RlpStream::new_list(2); - stream.append(caller); - stream.append(&nonce); - H256::from_slice(Keccak256::digest(&stream.out()).as_slice()).into() - } - } - } - - #[must_use] - pub const fn caller(&self) -> H160 { - match self { - Self::Create2 { caller, .. } => *caller, - Self::Legacy { caller } => *caller, - } - } -} - -/// Call scheme. -#[derive(Clone, Copy, Eq, PartialEq, Debug)] -pub enum CallScheme { - /// `CALL` - Call, - /// `CALLCODE` - CallCode, - /// `DELEGATECALL` - DelegateCall, - /// `STATICCALL` - StaticCall, -} - pub enum CallCreateTrap { Create, Create2, @@ -161,6 +101,20 @@ impl CallCreateTrapData { } } +/// Call scheme. +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum CallScheme { + /// `CALL` + Call, + /// `CALLCODE` + CallCode, + /// `DELEGATECALL` + DelegateCall, + /// `STATICCALL` + StaticCall, +} + +#[derive(Clone, Debug)] pub struct CallTrapData { pub target: H160, pub transfer: Option, @@ -376,6 +330,59 @@ impl CallTrapData { } } +/// Create scheme. +#[derive(Clone, Copy, Eq, PartialEq, Debug)] +pub enum CreateScheme { + /// Legacy create scheme of `CREATE`. + Legacy { + /// Caller of the create call. + caller: H160, + }, + /// Create scheme of `CREATE2`. + Create2 { + /// Caller of the create call. + caller: H160, + /// Code hash. + code_hash: H256, + /// Salt. + salt: H256, + }, +} + +impl CreateScheme { + pub fn address(&self, handler: &H) -> H160 { + match self { + Self::Create2 { + caller, + code_hash, + salt, + } => { + let mut hasher = Keccak256::new(); + hasher.update([0xff]); + hasher.update(&caller[..]); + hasher.update(&salt[..]); + hasher.update(&code_hash[..]); + H256::from_slice(hasher.finalize().as_slice()).into() + } + Self::Legacy { caller } => { + let nonce = handler.nonce(*caller); + let mut stream = rlp::RlpStream::new_list(2); + stream.append(caller); + stream.append(&nonce); + H256::from_slice(Keccak256::digest(&stream.out()).as_slice()).into() + } + } + } + + #[must_use] + pub const fn caller(&self) -> H160 { + match self { + Self::Create2 { caller, .. } => *caller, + Self::Legacy { caller } => *caller, + } + } +} + #[derive(Clone, Debug)] pub struct CreateTrapData { pub scheme: CreateScheme, diff --git a/interpreter/src/etable.rs b/interpreter/src/etable.rs index 515897176..a11c2bdea 100644 --- a/interpreter/src/etable.rs +++ b/interpreter/src/etable.rs @@ -1,9 +1,15 @@ +use core::{ + marker::PhantomData, + ops::{Deref, DerefMut}, +}; + use crate::{ - eval::*, trap::CallCreateTrap, ExitResult, GasState, Machine, Opcode, RuntimeBackend, - RuntimeEnvironment, RuntimeState, TrapConstruct, + error::{CallCreateTrap, ExitResult, TrapConstruct}, + eval::*, + machine::Machine, + opcode::Opcode, + runtime::{GasState, RuntimeBackend, RuntimeEnvironment, RuntimeState}, }; -use core::marker::PhantomData; -use core::ops::{Deref, DerefMut}; pub trait EtableSet { type State; diff --git a/interpreter/src/eval/arithmetic.rs b/interpreter/src/eval/arithmetic.rs index ae6e9a15c..07045d003 100644 --- a/interpreter/src/eval/arithmetic.rs +++ b/interpreter/src/eval/arithmetic.rs @@ -1,8 +1,9 @@ -use crate::utils::I256; -use core::convert::TryInto; use core::ops::Rem; + use primitive_types::{U256, U512}; +use crate::utils::I256; + #[inline] pub fn div(op1: U256, op2: U256) -> U256 { if op2 == U256::zero() { diff --git a/interpreter/src/eval/bitwise.rs b/interpreter/src/eval/bitwise.rs index 49883fd1c..23bb5dd3d 100644 --- a/interpreter/src/eval/bitwise.rs +++ b/interpreter/src/eval/bitwise.rs @@ -1,6 +1,7 @@ -use crate::utils::{Sign, I256}; use primitive_types::U256; +use crate::utils::{Sign, I256}; + #[inline] pub fn slt(op1: U256, op2: U256) -> U256 { let op1: I256 = op1.into(); diff --git a/interpreter/src/eval/misc.rs b/interpreter/src/eval/misc.rs index 0a9b9c5a3..657751fa0 100644 --- a/interpreter/src/eval/misc.rs +++ b/interpreter/src/eval/misc.rs @@ -1,9 +1,14 @@ -use super::Control; -use crate::utils::u256_to_h256; -use crate::{ExitError, ExitException, ExitFatal, ExitSucceed, Machine}; use core::cmp::min; + use primitive_types::{H256, U256}; +use crate::{ + error::{ExitError, ExitException, ExitFatal, ExitSucceed}, + etable::Control, + machine::Machine, + utils::u256_to_h256, +}; + #[inline] pub fn codesize(state: &mut Machine) -> Control { let stack = &mut state.stack; diff --git a/interpreter/src/eval/mod.rs b/interpreter/src/eval/mod.rs index ba578aec8..238f572e6 100644 --- a/interpreter/src/eval/mod.rs +++ b/interpreter/src/eval/mod.rs @@ -5,13 +5,18 @@ mod bitwise; mod misc; mod system; -use crate::{ - trap::CallCreateTrap, Control, ExitException, ExitSucceed, GasState, Machine, Opcode, - RuntimeBackend, RuntimeEnvironment, RuntimeState, TrapConstruct, -}; use core::ops::{BitAnd, BitOr, BitXor}; + use primitive_types::{H256, U256}; +use crate::{ + error::{CallCreateTrap, ExitException, ExitSucceed, TrapConstruct}, + etable::Control, + machine::Machine, + opcode::Opcode, + runtime::{GasState, RuntimeBackend, RuntimeEnvironment, RuntimeState}, +}; + pub fn eval_pass( _machine: &mut Machine, _handle: &mut H, diff --git a/interpreter/src/eval/system.rs b/interpreter/src/eval/system.rs index 88256632a..4a30b9075 100644 --- a/interpreter/src/eval/system.rs +++ b/interpreter/src/eval/system.rs @@ -1,12 +1,15 @@ -use super::Control; -use crate::{ - ExitException, ExitFatal, ExitSucceed, GasState, Log, Machine, RuntimeBackend, - RuntimeEnvironment, RuntimeState, Transfer, -}; use alloc::vec::Vec; + use primitive_types::{H256, U256}; use sha3::{Digest, Keccak256}; +use crate::{ + error::{ExitException, ExitFatal, ExitSucceed}, + etable::Control, + machine::Machine, + runtime::{GasState, Log, RuntimeBackend, RuntimeEnvironment, RuntimeState, Transfer}, +}; + pub fn sha3, Tr>(machine: &mut Machine) -> Control { pop_u256!(machine, from, len); diff --git a/interpreter/src/interpreter/etable.rs b/interpreter/src/interpreter/etable.rs index ea0df5833..83ecf1577 100644 --- a/interpreter/src/interpreter/etable.rs +++ b/interpreter/src/interpreter/etable.rs @@ -1,11 +1,14 @@ -use crate::interpreter::{Interpreter, RunInterpreter, StepInterpreter}; -use crate::{ - Capture, Control, EtableSet, ExitError, ExitException, ExitFatal, ExitResult, ExitSucceed, - Machine, Opcode, Stack, Valids, -}; use alloc::vec::Vec; use core::ops::{Deref, DerefMut}; +use crate::{ + error::{Capture, ExitError, ExitException, ExitFatal, ExitResult, ExitSucceed}, + etable::{Control, EtableSet}, + interpreter::{valids::Valids, Interpreter, RunInterpreter, StepInterpreter}, + machine::{Machine, Stack}, + opcode::Opcode, +}; + pub struct EtableInterpreter<'etable, ES: EtableSet> { valids: Valids, position: usize, diff --git a/interpreter/src/interpreter/mod.rs b/interpreter/src/interpreter/mod.rs index 37f66a83a..4d0788869 100644 --- a/interpreter/src/interpreter/mod.rs +++ b/interpreter/src/interpreter/mod.rs @@ -1,10 +1,14 @@ mod etable; +mod valids; -pub use self::etable::EtableInterpreter; - -use crate::{Capture, ExitResult, Machine}; use alloc::vec::Vec; +pub use self::etable::EtableInterpreter; +use crate::{ + error::{Capture, ExitResult}, + machine::Machine, +}; + pub trait Interpreter { type State; diff --git a/interpreter/src/valids.rs b/interpreter/src/interpreter/valids.rs similarity index 66% rename from interpreter/src/valids.rs rename to interpreter/src/interpreter/valids.rs index e9bc3613a..f9bba082d 100644 --- a/interpreter/src/valids.rs +++ b/interpreter/src/interpreter/valids.rs @@ -1,6 +1,7 @@ -use crate::Opcode; use alloc::vec::Vec; +use crate::opcode::Opcode; + /// Mapping of valid jump destination from code. #[derive(Clone, Debug, Eq, PartialEq)] pub struct Valids(Vec); @@ -25,36 +26,17 @@ impl Valids { } } - Valids(valids) - } - - /// Get the length of the valid mapping. This is the same as the - /// code bytes. - #[inline] - #[must_use] - pub fn len(&self) -> usize { - self.0.len() - } - - /// Returns true if the valids list is empty - #[inline] - #[must_use] - pub fn is_empty(&self) -> bool { - self.len() == 0 + Self(valids) } - /// Returns `true` if the position is a valid jump destination. If - /// not, returns `false`. + /// Returns `true` if the position is a valid jump destination. + /// If not, returns `false`. #[must_use] pub fn is_valid(&self, position: usize) -> bool { if position >= self.0.len() { return false; } - if !self.0[position] { - return false; - } - - true + self.0[position] } } diff --git a/interpreter/src/lib.rs b/interpreter/src/lib.rs index 0e6693bef..f29887abb 100644 --- a/interpreter/src/lib.rs +++ b/interpreter/src/lib.rs @@ -6,78 +6,13 @@ extern crate alloc; -mod error; -mod etable; +pub mod error; +pub mod etable; pub mod eval; -pub mod interpreter; -mod memory; -mod opcode; -mod runtime; -mod stack; -pub mod trap; +mod interpreter; +pub mod machine; +pub mod opcode; +pub mod runtime; pub mod utils; -mod valids; -pub use crate::error::{Capture, ExitError, ExitException, ExitFatal, ExitResult, ExitSucceed}; -pub use crate::etable::{Control, Efn, Etable, EtableSet}; -pub use crate::memory::Memory; -pub use crate::opcode::Opcode; -pub use crate::runtime::{ - Context, GasState, Log, RuntimeBackend, RuntimeBaseBackend, RuntimeEnvironment, RuntimeState, - TransactionContext, Transfer, -}; -pub use crate::stack::Stack; -pub use crate::trap::{TrapConstruct, TrapConsume}; -pub use crate::valids::Valids; - -use alloc::rc::Rc; -use alloc::vec::Vec; - -/// Core execution layer for EVM. -pub struct Machine { - /// Program data. - data: Rc>, - /// Program code. - code: Rc>, - /// Return value. Note the difference between `retbuf`. - /// A `retval` holds what's returned by the current machine, with `RETURN` or `REVERT` opcode. - /// A `retbuf` holds the buffer of returned value by sub-calls. - pub retval: Vec, - /// Memory. - pub memory: Memory, - /// Stack. - pub stack: Stack, - /// Extra state, - pub state: S, -} - -impl Machine { - /// Machine code. - pub fn code(&self) -> &[u8] { - &self.code - } - - /// Create a new machine with given code and data. - pub fn new( - code: Rc>, - data: Rc>, - stack_limit: usize, - memory_limit: usize, - state: S, - ) -> Self { - Self { - data, - code, - retval: Vec::new(), - memory: Memory::new(memory_limit), - stack: Stack::new(stack_limit), - state, - } - } - - /// Whether the machine has empty code. - #[must_use] - pub fn is_empty(&self) -> bool { - self.code.is_empty() - } -} +pub use self::interpreter::{EtableInterpreter, Interpreter, RunInterpreter, StepInterpreter}; diff --git a/interpreter/src/memory.rs b/interpreter/src/machine/memory.rs similarity index 97% rename from interpreter/src/memory.rs rename to interpreter/src/machine/memory.rs index 0c6c92c5a..98abcc707 100644 --- a/interpreter/src/memory.rs +++ b/interpreter/src/machine/memory.rs @@ -1,10 +1,14 @@ -use crate::{ExitException, ExitFatal}; -use alloc::vec; -use alloc::vec::Vec; -use core::ops::{BitAnd, Not, Range}; -use core::{cmp::min, mem}; +use alloc::{vec, vec::Vec}; +use core::{ + cmp::min, + mem, + ops::{BitAnd, Not, Range}, +}; + use primitive_types::U256; +use crate::error::{ExitException, ExitFatal}; + /// A sequencial memory. It uses Rust's `Vec` for internal /// representation. #[derive(Clone, Debug)] diff --git a/interpreter/src/machine/mod.rs b/interpreter/src/machine/mod.rs new file mode 100644 index 000000000..94f224f67 --- /dev/null +++ b/interpreter/src/machine/mod.rs @@ -0,0 +1,55 @@ +mod memory; +mod stack; + +use alloc::{rc::Rc, vec::Vec}; + +pub use self::{memory::Memory, stack::Stack}; + +/// Core execution layer for EVM. +pub struct Machine { + /// Program data. + pub(crate) data: Rc>, + /// Program code. + pub(crate) code: Rc>, + /// Return value. Note the difference between `retbuf`. + /// A `retval` holds what's returned by the current machine, with `RETURN` or `REVERT` opcode. + /// A `retbuf` holds the buffer of returned value by sub-calls. + pub retval: Vec, + /// Memory. + pub memory: Memory, + /// Stack. + pub stack: Stack, + /// Extra state, + pub state: S, +} + +impl Machine { + /// Create a new machine with given code and data. + pub fn new( + code: Rc>, + data: Rc>, + stack_limit: usize, + memory_limit: usize, + state: S, + ) -> Self { + Self { + data, + code, + retval: Vec::new(), + memory: Memory::new(memory_limit), + stack: Stack::new(stack_limit), + state, + } + } + + /// Machine code. + pub fn code(&self) -> &[u8] { + &self.code + } + + /// Whether the machine has empty code. + #[must_use] + pub fn is_empty(&self) -> bool { + self.code.is_empty() + } +} diff --git a/interpreter/src/stack.rs b/interpreter/src/machine/stack.rs similarity index 93% rename from interpreter/src/stack.rs rename to interpreter/src/machine/stack.rs index 68b8c3fe7..8798d5897 100644 --- a/interpreter/src/stack.rs +++ b/interpreter/src/machine/stack.rs @@ -1,7 +1,9 @@ -use crate::{ExitError, ExitException}; use alloc::vec::Vec; + use primitive_types::H256; +use crate::error::{ExitError, ExitException}; + /// EVM stack. #[derive(Clone, Debug)] pub struct Stack { @@ -56,15 +58,15 @@ impl Stack { } } - #[inline] /// Stack limit. + #[inline] #[must_use] pub const fn limit(&self) -> usize { self.limit } - #[inline] /// Stack length. + #[inline] #[must_use] pub fn len(&self) -> usize { self.data.len() @@ -77,8 +79,8 @@ impl Stack { self.data.is_empty() } - #[inline] /// Stack data. + #[inline] #[must_use] pub const fn data(&self) -> &Vec { &self.data @@ -89,16 +91,17 @@ impl Stack { self.data.clear(); } + /// Pop a value from the stack. + /// If the stack is already empty, returns the `StackUnderflow` error. #[inline] - /// Pop a value from the stack. If the stack is already empty, returns the - /// `StackUnderflow` error. pub fn pop(&mut self) -> Result { self.data.pop().ok_or(ExitException::StackUnderflow) } + /// Push a new value into the stack. + /// If it exceeds the stack limit, returns `StackOverflow` error and + /// leaves the stack unchanged. #[inline] - /// Push a new value into the stack. If it will exceed the stack limit, - /// returns `StackOverflow` error and leaves the stack unchanged. pub fn push(&mut self, value: H256) -> Result<(), ExitException> { if self.data.len() + 1 > self.limit { return Err(ExitException::StackOverflow); @@ -135,10 +138,10 @@ impl Stack { } } - #[inline] /// Peek a value at given index for the stack, where the top of /// the stack is at index `0`. If the index is too large, /// `StackError::Underflow` is returned. + #[inline] pub fn peek(&self, no_from_top: usize) -> Result { if self.data.len() > no_from_top { Ok(self.data[self.data.len() - no_from_top - 1]) @@ -147,10 +150,10 @@ impl Stack { } } - #[inline] /// Set a value at given index for the stack, where the top of the /// stack is at index `0`. If the index is too large, /// `StackError::Underflow` is returned. + #[inline] pub fn set(&mut self, no_from_top: usize, val: H256) -> Result<(), ExitException> { if self.data.len() > no_from_top { let len = self.data.len(); diff --git a/interpreter/src/runtime.rs b/interpreter/src/runtime.rs index 7dbce8a49..ed9056cbf 100644 --- a/interpreter/src/runtime.rs +++ b/interpreter/src/runtime.rs @@ -1,9 +1,10 @@ -use crate::ExitError; -use alloc::rc::Rc; -use alloc::vec::Vec; +use alloc::{rc::Rc, vec::Vec}; + use primitive_types::{H160, H256, U256}; use sha3::{Digest, Keccak256}; +use crate::error::ExitError; + /// Gas state. pub trait GasState { fn gas(&self) -> U256; @@ -14,6 +15,7 @@ pub trait GasState { pub struct RuntimeState { /// Runtime context. pub context: Context, + /// Transaction context. pub transaction_context: Rc, /// Return data buffer. pub retbuf: Vec, diff --git a/interpreter/src/utils.rs b/interpreter/src/utils.rs index a32cc07eb..9809a2774 100644 --- a/interpreter/src/utils.rs +++ b/interpreter/src/utils.rs @@ -1,10 +1,14 @@ //! Small utilities. -use crate::{ExitError, ExitFatal}; -use core::cmp::Ordering; -use core::ops::{Div, Rem}; +use core::{ + cmp::Ordering, + ops::{Div, Rem}, +}; + use primitive_types::{H256, U256}; +use crate::error::{ExitError, ExitFatal}; + /// Convert [U256] into [H256]. #[must_use] pub fn u256_to_h256(v: U256) -> H256 { @@ -160,10 +164,10 @@ impl Rem for I256 { #[cfg(test)] mod tests { - use crate::utils::{Sign, I256}; - use primitive_types::U256; use std::num::Wrapping; + use super::*; + #[test] fn div_i256() { // Sanity checks based on i8. Notice that we need to use `Wrapping` here because diff --git a/interpreter/tests/performance.rs b/interpreter/tests/performance.rs index 1e9d255f2..65d0ce64b 100644 --- a/interpreter/tests/performance.rs +++ b/interpreter/tests/performance.rs @@ -1,7 +1,12 @@ -use evm_interpreter::interpreter::{EtableInterpreter, RunInterpreter}; -use evm_interpreter::{Capture, Etable, ExitSucceed, Machine}; use std::rc::Rc; +use evm_interpreter::{ + error::{Capture, ExitSucceed}, + etable::Etable, + machine::Machine, + EtableInterpreter, RunInterpreter, +}; + static ETABLE: Etable<(), (), ()> = Etable::core(); macro_rules! ret_test { diff --git a/interpreter/tests/usability.rs b/interpreter/tests/usability.rs index 02421a987..5a8f5e4ad 100644 --- a/interpreter/tests/usability.rs +++ b/interpreter/tests/usability.rs @@ -1,11 +1,17 @@ -use evm_interpreter::interpreter::{EtableInterpreter, RunInterpreter}; +use std::rc::Rc; + use evm_interpreter::{ - trap::CallCreateTrap, Capture, Context, Control, Etable, ExitError, ExitSucceed, Log, Machine, - Opcode, RuntimeBackend, RuntimeBaseBackend, RuntimeEnvironment, RuntimeState, - TransactionContext, + error::{CallCreateTrap, Capture, ExitError, ExitSucceed}, + etable::{Control, Etable}, + machine::Machine, + opcode::Opcode, + runtime::{ + Context, Log, RuntimeBackend, RuntimeBaseBackend, RuntimeEnvironment, RuntimeState, + TransactionContext, + }, + EtableInterpreter, RunInterpreter, }; use primitive_types::{H160, H256, U256}; -use std::rc::Rc; const CODE1: &str = "60e060020a6000350480632839e92814601e57806361047ff414603457005b602a6004356024356047565b8060005260206000f35b603d6004356099565b8060005260206000f35b600082600014605457605e565b8160010190506093565b81600014606957607b565b60756001840360016047565b90506093565b609060018403608c85600186036047565b6047565b90505b92915050565b6000816000148060a95750816001145b60b05760b7565b81905060cf565b60c1600283036099565b60cb600184036099565b0190505b91905056"; const DATA1: &str = "2839e92800000000000000000000000000000000000000000000000000000000000000030000000000000000000000000000000000000000000000000000000000000001"; diff --git a/jsontests/src/error.rs b/jsontests/src/error.rs index b492a1e04..75df2b289 100644 --- a/jsontests/src/error.rs +++ b/jsontests/src/error.rs @@ -1,7 +1,4 @@ -#![allow(clippy::upper_case_acronyms)] -use thiserror::Error; - -#[derive(Error, Debug)] +#[derive(Debug, thiserror::Error)] #[allow(dead_code)] pub enum TestError { #[error("state root is different")] @@ -10,14 +7,15 @@ pub enum TestError { ExpectException, } -#[derive(Error, Debug)] +#[allow(clippy::upper_case_acronyms)] +#[derive(Debug, thiserror::Error)] pub enum Error { #[error("io error")] IO(#[from] std::io::Error), #[error("json error")] JSON(#[from] serde_json::Error), #[error("evm error")] - EVM(#[from] evm::ExitError), + EVM(#[from] evm::interpreter::error::ExitError), #[error("unsupported fork")] UnsupportedFork, #[error("non-utf8 filename")] diff --git a/jsontests/src/hash.rs b/jsontests/src/hash.rs index 7a7e91e52..a804ce9c0 100644 --- a/jsontests/src/hash.rs +++ b/jsontests/src/hash.rs @@ -1,8 +1,9 @@ -use crate::in_memory::InMemoryBackend; -use evm::utils::h256_to_u256; +use evm::interpreter::utils::h256_to_u256; use primitive_types::{H256, U256}; use sha3::{Digest, Keccak256}; +use crate::in_memory::InMemoryBackend; + /// Basic account type. #[derive(Debug, Clone, PartialEq, Eq)] pub struct TrieAccount { diff --git a/jsontests/src/in_memory.rs b/jsontests/src/in_memory.rs index 05fc87e69..f25c60a88 100644 --- a/jsontests/src/in_memory.rs +++ b/jsontests/src/in_memory.rs @@ -1,7 +1,11 @@ -use evm::{backend::OverlayedChangeSet, RuntimeBaseBackend, RuntimeEnvironment}; -use primitive_types::{H160, H256, U256}; use std::collections::BTreeMap; +use evm::{ + backend::OverlayedChangeSet, + interpreter::runtime::{RuntimeBaseBackend, RuntimeEnvironment}, +}; +use primitive_types::{H160, H256, U256}; + #[derive(Clone, Debug)] pub struct InMemoryEnvironment { pub block_hashes: BTreeMap, diff --git a/jsontests/src/main.rs b/jsontests/src/main.rs index 390571365..c21ee0a62 100644 --- a/jsontests/src/main.rs +++ b/jsontests/src/main.rs @@ -4,10 +4,10 @@ mod in_memory; mod run; mod types; -use crate::error::Error; -use crate::types::*; use clap::Parser; +use crate::{error::Error, types::*}; + #[derive(Parser)] #[command(author, version, about, long_about = None)] struct Cli { diff --git a/jsontests/src/run.rs b/jsontests/src/run.rs index 5fa6e8c6a..68080cf03 100644 --- a/jsontests/src/run.rs +++ b/jsontests/src/run.rs @@ -1,16 +1,22 @@ -use crate::error::{Error, TestError}; -use crate::in_memory::{InMemoryAccount, InMemoryBackend, InMemoryEnvironment}; -use crate::types::{Fork, TestCompletionStatus, TestData, TestExpectException, TestMulti}; -use evm::backend::OverlayedBackend; -use evm::standard::{Config, Etable, EtableResolver, Invoker, TransactArgs}; -use evm::utils::u256_to_h256; -use evm::Capture; -use evm::{interpreter::Interpreter, GasState}; +use std::{ + collections::{BTreeMap, BTreeSet}, + fs::{self, File}, + io::BufReader, +}; + +use evm::{ + backend::OverlayedBackend, + interpreter::{error::Capture, runtime::GasState, utils::u256_to_h256, Interpreter}, + standard::{Config, Etable, EtableResolver, Invoker, TransactArgs}, +}; use evm_precompile::StandardPrecompileSet; use primitive_types::U256; -use std::collections::{BTreeMap, BTreeSet}; -use std::fs::{self, File}; -use std::io::BufReader; + +use crate::{ + error::{Error, TestError}, + in_memory::{InMemoryAccount, InMemoryBackend, InMemoryEnvironment}, + types::{Fork, TestCompletionStatus, TestData, TestExpectException, TestMulti}, +}; const BASIC_FILE_PATH_TO_TRIM: [&str; 2] = [ "jsontests/res/ethtests/GeneralStateTests/", diff --git a/jsontests/src/types.rs b/jsontests/src/types.rs index 82b6be9f4..9e93a29a2 100644 --- a/jsontests/src/types.rs +++ b/jsontests/src/types.rs @@ -1,11 +1,11 @@ +use std::{collections::BTreeMap, fmt}; + use hex::FromHex; use primitive_types::{H160, H256, U256}; use serde::{ de::{Error, Visitor}, Deserialize, Deserializer, }; -use std::collections::BTreeMap; -use std::fmt; /// Statistic type to gather tests pass completion status #[derive(Default, Clone, Debug, Eq, PartialEq)] diff --git a/precompile/src/blake2/mod.rs b/precompile/src/blake2/mod.rs index 81144c5f4..f94a64b00 100644 --- a/precompile/src/blake2/mod.rs +++ b/precompile/src/blake2/mod.rs @@ -1,7 +1,11 @@ mod eip152; +use evm::{ + interpreter::error::{ExitException, ExitResult, ExitSucceed}, + GasMutState, +}; + use crate::PurePrecompile; -use evm::{ExitException, ExitResult, ExitSucceed, GasMutState}; pub struct Blake2F; diff --git a/precompile/src/bn128.rs b/precompile/src/bn128.rs index 5b8c5793b..44d90597a 100644 --- a/precompile/src/bn128.rs +++ b/precompile/src/bn128.rs @@ -1,8 +1,13 @@ -use crate::PurePrecompile; use alloc::vec::Vec; -use evm::{ExitError, ExitException, ExitResult, ExitSucceed, GasMutState}; + +use evm::{ + interpreter::error::{ExitError, ExitException, ExitResult, ExitSucceed}, + GasMutState, +}; use primitive_types::U256; +use crate::PurePrecompile; + /// Copy bytes from input to target. fn read_input(source: &[u8], target: &mut [u8], offset: usize) { // Out of bounds, nothing to copy. diff --git a/precompile/src/lib.rs b/precompile/src/lib.rs index 6042bdb52..00cc02829 100644 --- a/precompile/src/lib.rs +++ b/precompile/src/lib.rs @@ -20,17 +20,25 @@ mod bn128; mod modexp; mod simple; -pub use crate::blake2::Blake2F; -pub use crate::bn128::{Bn128Add, Bn128Mul, Bn128Pairing}; -pub use crate::modexp::Modexp; -pub use crate::simple::{ECRecover, Identity, Ripemd160, Sha256}; - use alloc::vec::Vec; -use evm::standard::{Config, PrecompileSet}; -use evm::{ExitError, ExitException, ExitResult, GasMutState, RuntimeState}; +use evm::{ + interpreter::{ + error::{ExitError, ExitException, ExitResult}, + runtime::RuntimeState, + }, + standard::{Config, PrecompileSet}, + GasMutState, +}; use primitive_types::H160; +pub use crate::{ + blake2::Blake2F, + bn128::{Bn128Add, Bn128Mul, Bn128Pairing}, + modexp::Modexp, + simple::{ECRecover, Identity, Ripemd160, Sha256}, +}; + pub trait PurePrecompile { fn execute(&self, input: &[u8], gasometer: &mut G) -> (ExitResult, Vec); } diff --git a/precompile/src/modexp.rs b/precompile/src/modexp.rs index 39d86d5fa..a67676114 100644 --- a/precompile/src/modexp.rs +++ b/precompile/src/modexp.rs @@ -1,9 +1,14 @@ -use crate::PurePrecompile; use alloc::{vec, vec::Vec}; use core::cmp::max; -use evm::{ExitException, ExitResult, ExitSucceed, GasMutState}; + +use evm::{ + interpreter::error::{ExitException, ExitResult, ExitSucceed}, + GasMutState, +}; use num::{BigUint, FromPrimitive, Integer, One, ToPrimitive, Zero}; +use crate::PurePrecompile; + pub struct Modexp; const MIN_GAS_COST: u64 = 200; diff --git a/precompile/src/simple.rs b/precompile/src/simple.rs index 58a2912be..c14043abe 100644 --- a/precompile/src/simple.rs +++ b/precompile/src/simple.rs @@ -1,10 +1,15 @@ -use crate::{linear_cost, PurePrecompile}; use core::cmp::min; -use evm::{ExitException, ExitResult, ExitSucceed, GasMutState}; + +use evm::{ + interpreter::error::{ExitException, ExitResult, ExitSucceed}, + GasMutState, +}; use k256::ecdsa::{RecoveryId, Signature, VerifyingKey}; use primitive_types::{H256, U256}; use sha3::{Digest, Keccak256}; +use crate::{linear_cost, PurePrecompile}; + pub struct ECRecover; impl PurePrecompile for ECRecover { diff --git a/rustfmt.toml b/rustfmt.toml index 218e20321..eb74f05bb 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1 +1,3 @@ hard_tabs = true +imports_granularity="Crate" +group_imports = "StdExternalCrate" diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 96436d450..e68e3b5ff 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -13,8 +13,9 @@ mod overlayed; +pub use evm_interpreter::runtime::{RuntimeBackend, RuntimeBaseBackend, RuntimeEnvironment}; + pub use self::overlayed::{OverlayedBackend, OverlayedChangeSet}; -pub use evm_interpreter::{RuntimeBackend, RuntimeBaseBackend, RuntimeEnvironment}; /// Backend with layers that can transactionally be committed or discarded. pub trait TransactionalBackend { diff --git a/src/backend/overlayed.rs b/src/backend/overlayed.rs index 7ffd43df9..20bf960f6 100644 --- a/src/backend/overlayed.rs +++ b/src/backend/overlayed.rs @@ -1,15 +1,18 @@ -use crate::{ - ExitError, ExitException, Log, MergeStrategy, RuntimeBackend, RuntimeBaseBackend, - RuntimeEnvironment, TransactionalBackend, -}; use alloc::{ boxed::Box, collections::{BTreeMap, BTreeSet}, vec::Vec, }; use core::mem; + +use evm_interpreter::{ + error::{ExitError, ExitException}, + runtime::{Log, RuntimeBackend, RuntimeBaseBackend, RuntimeEnvironment}, +}; use primitive_types::{H160, H256, U256}; +use crate::{backend::TransactionalBackend, MergeStrategy}; + #[derive(Clone, Debug)] pub struct OverlayedChangeSet { pub logs: Vec, diff --git a/src/call_stack.rs b/src/call_stack.rs index a615acb0e..09a28f847 100644 --- a/src/call_stack.rs +++ b/src/call_stack.rs @@ -1,8 +1,13 @@ -use crate::interpreter::{Interpreter, RunInterpreter, StepInterpreter}; -use crate::{Capture, ExitError, ExitFatal, ExitResult, Invoker, InvokerControl}; use alloc::vec::Vec; use core::convert::Infallible; +use evm_interpreter::{ + error::{Capture, ExitError, ExitFatal, ExitResult}, + Interpreter, RunInterpreter, StepInterpreter, +}; + +use crate::invoker::{Invoker, InvokerControl}; + struct Substack { invoke: TrD, machine: M, diff --git a/src/gasometer.rs b/src/gasometer.rs index 7114c4369..ce3b75ae8 100644 --- a/src/gasometer.rs +++ b/src/gasometer.rs @@ -1,6 +1,6 @@ //! EVM gasometer. -use crate::{ExitError, GasState}; +use evm_interpreter::{error::ExitError, runtime::GasState}; use primitive_types::U256; pub trait GasMutState: GasState { diff --git a/src/invoker.rs b/src/invoker.rs index 755834763..9d8b76b61 100644 --- a/src/invoker.rs +++ b/src/invoker.rs @@ -1,6 +1,10 @@ -use crate::{interpreter::Interpreter, Capture, ExitError, ExitResult}; use alloc::vec::Vec; +use evm_interpreter::{ + error::{Capture, ExitError, ExitResult}, + Interpreter, +}; + /// Control for an invoker. pub enum InvokerControl { /// Pushing the call stack. diff --git a/src/lib.rs b/src/lib.rs index 12857e99f..70fd5c25e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -72,12 +72,14 @@ mod call_stack; mod gasometer; mod invoker; -pub use evm_interpreter::*; +pub use evm_interpreter as interpreter; -pub use crate::backend::TransactionalBackend; -pub use crate::call_stack::{transact, HeapTransact}; -pub use crate::gasometer::GasMutState; -pub use crate::invoker::{Invoker, InvokerControl}; +pub use crate::{ + backend::TransactionalBackend, + call_stack::{transact, HeapTransact}, + gasometer::GasMutState, + invoker::{Invoker, InvokerControl}, +}; /// Merge strategy of a backend substate layer or a call stack gasometer layer. #[derive(Clone, Debug, Copy)] diff --git a/src/standard/gasometer/consts.rs b/src/standard/gasometer/consts.rs index f51085577..115fe5c19 100644 --- a/src/standard/gasometer/consts.rs +++ b/src/standard/gasometer/consts.rs @@ -1,4 +1,4 @@ -use evm_interpreter::Opcode; +use evm_interpreter::opcode::Opcode; pub const G_ZERO: u64 = 0; pub const G_BASE: u64 = 2; diff --git a/src/standard/gasometer/costs.rs b/src/standard/gasometer/costs.rs index dda497e66..cbf611ff0 100644 --- a/src/standard/gasometer/costs.rs +++ b/src/standard/gasometer/costs.rs @@ -1,9 +1,9 @@ -use super::consts::*; -use super::utils::log2floor; -use crate::standard::Config; -use evm_interpreter::ExitException; +use evm_interpreter::error::ExitException; use primitive_types::{H256, U256}; +use super::{consts::*, utils::log2floor}; +use crate::standard::Config; + pub fn call_extra_check(gas: U256, after_gas: u64, config: &Config) -> Result<(), ExitException> { if config.err_on_call_with_more_gas && U256::from(after_gas) < gas { Err(ExitException::OutOfGas) diff --git a/src/standard/gasometer/mod.rs b/src/standard/gasometer/mod.rs index a526de53b..2b09bda0f 100644 --- a/src/standard/gasometer/mod.rs +++ b/src/standard/gasometer/mod.rs @@ -2,15 +2,20 @@ mod consts; mod costs; mod utils; -use crate::standard::Config; -use crate::{ - Control, ExitError, ExitException, Machine, MergeStrategy, Opcode, RuntimeBackend, - RuntimeState, Stack, -}; use alloc::vec::Vec; use core::cmp::{max, min}; + +use evm_interpreter::{ + error::{ExitError, ExitException}, + etable::Control, + machine::{Machine, Stack}, + opcode::Opcode, + runtime::{RuntimeBackend, RuntimeState}, +}; use primitive_types::{H160, H256, U256}; +use crate::{standard::Config, MergeStrategy}; + pub struct GasometerState<'config> { gas_limit: u64, memory_gas: u64, diff --git a/src/standard/gasometer/utils.rs b/src/standard/gasometer/utils.rs index 9630b9ab9..b2d7880f1 100644 --- a/src/standard/gasometer/utils.rs +++ b/src/standard/gasometer/utils.rs @@ -1,7 +1,7 @@ use primitive_types::U256; pub fn log2floor(value: U256) -> u64 { - assert!(value != U256::zero()); + assert_ne!(value, U256::zero()); let mut l: u64 = 256; for i in 0..4 { let i = 3 - i; diff --git a/src/standard/invoker/mod.rs b/src/standard/invoker/mod.rs index e7e6576e6..70bf27454 100644 --- a/src/standard/invoker/mod.rs +++ b/src/standard/invoker/mod.rs @@ -2,24 +2,35 @@ mod resolver; pub mod routines; mod state; -pub use self::resolver::{EtableResolver, PrecompileSet, Resolver}; -pub use self::state::InvokerState; +use alloc::{rc::Rc, vec::Vec}; +use core::{cmp::min, convert::Infallible}; -use super::Config; -use crate::trap::{CallCreateTrap, CallCreateTrapData, CallTrapData, CreateScheme, CreateTrapData}; -use crate::{ - interpreter::Interpreter, Capture, Context, ExitError, ExitException, ExitResult, ExitSucceed, - GasState, Invoker as InvokerT, InvokerControl, MergeStrategy, Opcode, RuntimeBackend, - RuntimeEnvironment, RuntimeState, TransactionContext, TransactionalBackend, Transfer, - TrapConsume, +use evm_interpreter::{ + error::{ + CallCreateTrap, CallCreateTrapData, CallTrapData, Capture, CreateScheme, CreateTrapData, + ExitError, ExitException, ExitResult, ExitSucceed, TrapConsume, + }, + opcode::Opcode, + runtime::{ + Context, GasState, RuntimeBackend, RuntimeEnvironment, RuntimeState, TransactionContext, + Transfer, + }, + Interpreter, }; -use alloc::rc::Rc; -use alloc::vec::Vec; -use core::cmp::min; -use core::convert::Infallible; use primitive_types::{H160, H256, U256}; use sha3::{Digest, Keccak256}; +pub use self::{ + resolver::{EtableResolver, PrecompileSet, Resolver}, + state::InvokerState, +}; +use crate::{ + backend::TransactionalBackend, + invoker::{Invoker as InvokerT, InvokerControl}, + standard::Config, + MergeStrategy, +}; + /// 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 { diff --git a/src/standard/invoker/resolver.rs b/src/standard/invoker/resolver.rs index 4c46534c0..348afd958 100644 --- a/src/standard/invoker/resolver.rs +++ b/src/standard/invoker/resolver.rs @@ -1,11 +1,16 @@ -use crate::interpreter::{EtableInterpreter, Interpreter}; -use crate::{ - standard::Config, EtableSet, ExitError, ExitResult, InvokerControl, Machine, RuntimeBackend, - RuntimeState, -}; use alloc::{rc::Rc, vec::Vec}; + +use evm_interpreter::{ + error::{ExitError, ExitResult}, + etable::EtableSet, + machine::Machine, + runtime::{RuntimeBackend, RuntimeState}, + EtableInterpreter, Interpreter, +}; use primitive_types::H160; +use crate::{invoker::InvokerControl, standard::Config}; + /// A code resolver. /// /// The resolver handles how a call (with the target code address) or create diff --git a/src/standard/invoker/routines.rs b/src/standard/invoker/routines.rs index fbc0ff8a2..9bd3d3fce 100644 --- a/src/standard/invoker/routines.rs +++ b/src/standard/invoker/routines.rs @@ -1,12 +1,19 @@ -use super::{CallTrapData, CreateTrapData, InvokerState, Resolver, SubstackInvoke}; -use crate::standard::Config; -use crate::{ - ExitError, ExitException, ExitResult, InvokerControl, MergeStrategy, Opcode, RuntimeBackend, - RuntimeEnvironment, RuntimeState, TransactionalBackend, Transfer, -}; use alloc::vec::Vec; + +use evm_interpreter::{ + error::{CallTrapData, CreateTrapData, ExitError, ExitException, ExitResult}, + opcode::Opcode, + runtime::{RuntimeBackend, RuntimeEnvironment, RuntimeState, Transfer}, +}; use primitive_types::{H160, U256}; +use crate::{ + backend::TransactionalBackend, + invoker::InvokerControl, + standard::{Config, InvokerState, Resolver, SubstackInvoke}, + MergeStrategy, +}; + #[allow(clippy::too_many_arguments, clippy::type_complexity)] pub fn make_enter_call_machine( _config: &Config, diff --git a/src/standard/invoker/state.rs b/src/standard/invoker/state.rs index eef276a28..3957329f2 100644 --- a/src/standard/invoker/state.rs +++ b/src/standard/invoker/state.rs @@ -1,7 +1,13 @@ -use crate::{standard::Config, ExitError, GasState, MergeStrategy, RuntimeState}; use alloc::vec::Vec; + +use evm_interpreter::{ + error::ExitError, + runtime::{GasState, RuntimeState}, +}; use primitive_types::{H160, H256, U256}; +use crate::{standard::Config, MergeStrategy}; + pub trait InvokerState<'config>: GasState + Sized { fn new_transact_call( runtime: RuntimeState, diff --git a/src/standard/mod.rs b/src/standard/mod.rs index e364d723d..7b9b1a9f3 100644 --- a/src/standard/mod.rs +++ b/src/standard/mod.rs @@ -8,26 +8,34 @@ mod config; mod gasometer; mod invoker; -pub use self::config::Config; -pub use self::gasometer::{eval as eval_gasometer, GasometerState}; -pub use self::invoker::{ - routines, EtableResolver, Invoker, InvokerState, PrecompileSet, Resolver, SubstackInvoke, - TransactArgs, TransactInvoke, TransactValue, -}; - -use crate::{ExitError, GasMutState, GasState, MergeStrategy, RuntimeState}; use alloc::vec::Vec; + +use evm_interpreter::{ + error::{CallCreateTrap, ExitError}, + etable, machine, + runtime::{GasState, RuntimeState}, +}; use primitive_types::{H160, H256, U256}; +pub use self::{ + config::Config, + gasometer::{eval as eval_gasometer, GasometerState}, + invoker::{ + routines, EtableResolver, Invoker, InvokerState, PrecompileSet, Resolver, SubstackInvoke, + TransactArgs, TransactInvoke, TransactValue, + }, +}; +use crate::{gasometer::GasMutState, MergeStrategy}; + /// Standard machine. -pub type Machine<'config> = crate::Machine>; +pub type Machine<'config> = machine::Machine>; /// Standard Etable opcode handle function. -pub type Efn<'config, H> = crate::Efn, H, crate::trap::CallCreateTrap>; +pub type Efn<'config, H> = etable::Efn, H, CallCreateTrap>; /// Standard Etable. pub type Etable<'config, H, F = Efn<'config, H>> = - crate::Etable, H, crate::trap::CallCreateTrap, F>; + etable::Etable, H, CallCreateTrap, F>; pub struct State<'config> { pub runtime: RuntimeState, diff --git a/tracer/src/lib.rs b/tracer/src/lib.rs index 7b0b01f81..fb8daf698 100644 --- a/tracer/src/lib.rs +++ b/tracer/src/lib.rs @@ -1,6 +1,6 @@ mod standard; -use evm::{Machine, Opcode}; +use evm::interpreter::{machine::Machine, opcode::Opcode}; pub trait EvalTracer { fn on_eval(&mut self, machine: &Machine, handle: &H, opcode: Opcode, position: usize); diff --git a/tracer/src/standard.rs b/tracer/src/standard.rs index 1177cf0f7..73f9fabef 100644 --- a/tracer/src/standard.rs +++ b/tracer/src/standard.rs @@ -1,5 +1,7 @@ -use evm::standard::{Machine, State}; -use evm::Opcode; +use evm::{ + interpreter::opcode::Opcode, + standard::{Machine, State}, +}; pub trait EvalTracer { fn on_eval(&mut self, machine: &Machine, handle: &H, opcode: Opcode, position: usize); From 63b19f7ee28e58f0e1df7b766e90940c0fcb4898 Mon Sep 17 00:00:00 2001 From: Rodrigo Quelhas <22591718+RomarQ@users.noreply.github.com> Date: Tue, 4 Jun 2024 07:10:14 +0100 Subject: [PATCH 2/7] feat: support transient storage opcodes (EIP-1153) (#278) * feat: support transient storage opcodes (EIP-1153) * test: fix UnimplementedHandler * move eip2929 fix to a different PR * revert mutability in argument * adds flag for enabling eip-1153 * use gas_storage_read_warm for tload and tstore --- interpreter/src/etable.rs | 3 +++ interpreter/src/eval/mod.rs | 18 +++++++++++++++ interpreter/src/eval/system.rs | 22 +++++++++++++++++++ interpreter/src/opcode.rs | 5 +++++ interpreter/src/runtime.rs | 9 ++++++++ interpreter/tests/usability.rs | 11 ++++++++++ jsontests/src/in_memory.rs | 22 +++++++++++++++++++ jsontests/src/run.rs | 1 + src/backend/overlayed.rs | 39 +++++++++++++++++++++++++++++++++ src/standard/config.rs | 11 ++++++++++ src/standard/gasometer/costs.rs | 7 ++++++ src/standard/gasometer/mod.rs | 10 +++++++-- 12 files changed, 156 insertions(+), 2 deletions(-) diff --git a/interpreter/src/etable.rs b/interpreter/src/etable.rs index a11c2bdea..1e7d3d82a 100644 --- a/interpreter/src/etable.rs +++ b/interpreter/src/etable.rs @@ -304,6 +304,9 @@ where table.0[Opcode::GAS.as_usize()] = eval_gas as _; + table.0[Opcode::TLOAD.as_usize()] = eval_tload as _; + table.0[Opcode::TSTORE.as_usize()] = eval_tstore as _; + table.0[Opcode::LOG0.as_usize()] = eval_log0 as _; table.0[Opcode::LOG1.as_usize()] = eval_log1 as _; table.0[Opcode::LOG2.as_usize()] = eval_log2 as _; diff --git a/interpreter/src/eval/mod.rs b/interpreter/src/eval/mod.rs index 238f572e6..c3554053b 100644 --- a/interpreter/src/eval/mod.rs +++ b/interpreter/src/eval/mod.rs @@ -674,6 +674,24 @@ pub fn eval_gas( self::system::gas(machine, handle) } +pub fn eval_tload, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::tload(machine, handle) +} + +pub fn eval_tstore, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::tstore(machine, handle) +} + macro_rules! eval_log { ($($num:expr),*) => { $(paste::paste! { diff --git a/interpreter/src/eval/system.rs b/interpreter/src/eval/system.rs index 4a30b9075..2521fe587 100644 --- a/interpreter/src/eval/system.rs +++ b/interpreter/src/eval/system.rs @@ -295,6 +295,28 @@ pub fn gas( Control::Continue } +pub fn tload, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handler: &mut H, +) -> Control { + pop!(machine, index); + let value = handler.transient_storage(machine.state.as_ref().context.address, index); + push!(machine, value); + + Control::Continue +} + +pub fn tstore, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handler: &mut H, +) -> Control { + pop!(machine, index, value); + match handler.set_transient_storage(machine.state.as_ref().context.address, index, value) { + Ok(()) => Control::Continue, + Err(e) => Control::Exit(e.into()), + } +} + pub fn log, H: RuntimeEnvironment + RuntimeBackend, Tr>( machine: &mut Machine, n: u8, diff --git a/interpreter/src/opcode.rs b/interpreter/src/opcode.rs index b6003ff54..337cec153 100644 --- a/interpreter/src/opcode.rs +++ b/interpreter/src/opcode.rs @@ -236,6 +236,11 @@ impl Opcode { /// `GAS` pub const GAS: Opcode = Opcode(0x5a); + /// `TLOAD` + pub const TLOAD: Opcode = Opcode(0x5c); + /// `TSTORE` + pub const TSTORE: Opcode = Opcode(0x5d); + /// `LOGn` pub const LOG0: Opcode = Opcode(0xa0); pub const LOG1: Opcode = Opcode(0xa1); diff --git a/interpreter/src/runtime.rs b/interpreter/src/runtime.rs index ed9056cbf..992099e04 100644 --- a/interpreter/src/runtime.rs +++ b/interpreter/src/runtime.rs @@ -115,6 +115,8 @@ pub trait RuntimeBaseBackend { fn code(&self, address: H160) -> Vec; /// Get storage value of address at index. fn storage(&self, address: H160, index: H256) -> H256; + /// Get transient storage value of address at index. + fn transient_storage(&self, address: H160, index: H256) -> H256; /// Check whether an address exists. fn exists(&self, address: H160) -> bool; @@ -140,6 +142,13 @@ pub trait RuntimeBackend: RuntimeBaseBackend { fn mark_hot(&mut self, address: H160, index: Option); /// Set storage value of address at index. fn set_storage(&mut self, address: H160, index: H256, value: H256) -> Result<(), ExitError>; + /// Set transient storage value of address at index, transient storage gets discarded after every transaction. (see EIP-1153) + fn set_transient_storage( + &mut self, + address: H160, + index: H256, + value: H256, + ) -> Result<(), ExitError>; /// Create a log owned by address with given topics and data. fn log(&mut self, log: Log) -> Result<(), ExitError>; /// Mark an address to be deleted. diff --git a/interpreter/tests/usability.rs b/interpreter/tests/usability.rs index 5a8f5e4ad..d174e82a1 100644 --- a/interpreter/tests/usability.rs +++ b/interpreter/tests/usability.rs @@ -114,6 +114,9 @@ impl RuntimeBaseBackend for UnimplementedHandler { fn storage(&self, _address: H160, _index: H256) -> H256 { unimplemented!() } + fn transient_storage(&self, _address: H160, _index: H256) -> H256 { + unimplemented!() + } fn exists(&self, _address: H160) -> bool { unimplemented!() @@ -143,6 +146,14 @@ impl RuntimeBackend for UnimplementedHandler { fn set_storage(&mut self, _address: H160, _index: H256, _value: H256) -> Result<(), ExitError> { unimplemented!() } + fn set_transient_storage( + &mut self, + _address: H160, + _index: H256, + _value: H256, + ) -> Result<(), ExitError> { + unimplemented!() + } fn log(&mut self, _log: Log) -> Result<(), ExitError> { unimplemented!() } diff --git a/jsontests/src/in_memory.rs b/jsontests/src/in_memory.rs index f25c60a88..63c84ef87 100644 --- a/jsontests/src/in_memory.rs +++ b/jsontests/src/in_memory.rs @@ -25,6 +25,7 @@ pub struct InMemoryAccount { pub code: Vec, pub nonce: U256, pub storage: BTreeMap, + pub transient_storage: BTreeMap, } #[derive(Clone, Debug)] @@ -66,6 +67,16 @@ impl InMemoryBackend { } } + for ((address, key), value) in changeset.transient_storage.clone() { + let account = self.state.entry(address).or_default(); + + if value == H256::default() { + account.transient_storage.remove(&key); + } else { + account.transient_storage.insert(key, value); + } + } + for address in changeset.deletes.clone() { self.state.remove(&address); } @@ -146,6 +157,17 @@ impl RuntimeBaseBackend for InMemoryBackend { .unwrap_or(H256::default()) } + fn transient_storage(&self, address: H160, index: H256) -> H256 { + self.state + .get(&address) + .cloned() + .unwrap_or(Default::default()) + .transient_storage + .get(&index) + .cloned() + .unwrap_or(H256::default()) + } + fn nonce(&self, address: H160) -> U256 { self.state .get(&address) diff --git a/jsontests/src/run.rs b/jsontests/src/run.rs index 68080cf03..fcc649f8c 100644 --- a/jsontests/src/run.rs +++ b/jsontests/src/run.rs @@ -144,6 +144,7 @@ pub fn run_test( code: account.code.0, nonce: account.nonce, storage, + transient_storage: Default::default(), }, ) }) diff --git a/src/backend/overlayed.rs b/src/backend/overlayed.rs index 20bf960f6..c8fe2296e 100644 --- a/src/backend/overlayed.rs +++ b/src/backend/overlayed.rs @@ -21,6 +21,7 @@ pub struct OverlayedChangeSet { pub nonces: BTreeMap, pub storage_resets: BTreeSet, pub storages: BTreeMap<(H160, H256), H256>, + pub transient_storage: BTreeMap<(H160, H256), H256>, pub deletes: BTreeSet, } @@ -49,6 +50,7 @@ impl OverlayedBackend { nonces: self.substate.nonces, storage_resets: self.substate.storage_resets, storages: self.substate.storages, + transient_storage: self.substate.transient_storage, deletes: self.substate.deletes, }, ) @@ -118,6 +120,14 @@ impl RuntimeBaseBackend for OverlayedBackend { } } + fn transient_storage(&self, address: H160, index: H256) -> H256 { + if let Some(value) = self.substate.known_transient_storage(address, index) { + value + } else { + self.backend.transient_storage(address, index) + } + } + fn exists(&self, address: H160) -> bool { if let Some(exists) = self.substate.known_exists(address) { exists @@ -157,6 +167,18 @@ impl RuntimeBackend for OverlayedBackend { Ok(()) } + fn set_transient_storage( + &mut self, + address: H160, + index: H256, + value: H256, + ) -> Result<(), ExitError> { + self.substate + .transient_storage + .insert((address, index), value); + Ok(()) + } + fn log(&mut self, log: Log) -> Result<(), ExitError> { self.substate.logs.push(log); Ok(()) @@ -243,6 +265,11 @@ impl TransactionalBackend for OverlayedBackend { for ((address, key), value) in child.storages { self.substate.storages.insert((address, key), value); } + for ((address, key), value) in child.transient_storage { + self.substate + .transient_storage + .insert((address, key), value); + } for address in child.deletes { self.substate.deletes.insert(address); } @@ -260,6 +287,7 @@ struct Substate { nonces: BTreeMap, storage_resets: BTreeSet, storages: BTreeMap<(H160, H256), H256>, + transient_storage: BTreeMap<(H160, H256), H256>, deletes: BTreeSet, } @@ -273,6 +301,7 @@ impl Substate { nonces: Default::default(), storage_resets: Default::default(), storages: Default::default(), + transient_storage: Default::default(), deletes: Default::default(), } } @@ -319,6 +348,16 @@ impl Substate { } } + pub fn known_transient_storage(&self, address: H160, key: H256) -> Option { + if let Some(value) = self.transient_storage.get(&(address, key)) { + Some(*value) + } else if let Some(parent) = self.parent.as_ref() { + parent.known_transient_storage(address, key) + } else { + None + } + } + pub fn known_exists(&self, address: H160) -> Option { if self.balances.contains_key(&address) || self.nonces.contains_key(&address) diff --git a/src/standard/config.rs b/src/standard/config.rs index 63300bab4..5cb65b5d8 100644 --- a/src/standard/config.rs +++ b/src/standard/config.rs @@ -97,6 +97,8 @@ pub struct Config { pub has_base_fee: bool, /// Has PUSH0 opcode. See [EIP-3855](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3855.md) pub has_push0: bool, + /// Enables transient storage. See [EIP-1153](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1153.md) + pub eip_1153_enabled: bool, } impl Config { @@ -150,6 +152,7 @@ impl Config { has_ext_code_hash: false, has_base_fee: false, has_push0: false, + eip_1153_enabled: false, } } @@ -203,6 +206,7 @@ impl Config { has_ext_code_hash: true, has_base_fee: false, has_push0: false, + eip_1153_enabled: false, } } @@ -237,6 +241,7 @@ impl Config { disallow_executable_format, warm_coinbase_address, max_initcode_size, + eip_1153_enabled, } = inputs; // See https://eips.ethereum.org/EIPS/eip-2929 @@ -299,6 +304,7 @@ impl Config { has_ext_code_hash: true, has_base_fee, has_push0, + eip_1153_enabled, } } } @@ -315,6 +321,7 @@ struct DerivedConfigInputs { disallow_executable_format: bool, warm_coinbase_address: bool, max_initcode_size: Option, + eip_1153_enabled: bool, } impl DerivedConfigInputs { @@ -329,6 +336,7 @@ impl DerivedConfigInputs { disallow_executable_format: false, warm_coinbase_address: false, max_initcode_size: None, + eip_1153_enabled: false, } } @@ -343,6 +351,7 @@ impl DerivedConfigInputs { disallow_executable_format: true, warm_coinbase_address: false, max_initcode_size: None, + eip_1153_enabled: false, } } @@ -357,6 +366,7 @@ impl DerivedConfigInputs { disallow_executable_format: true, warm_coinbase_address: false, max_initcode_size: None, + eip_1153_enabled: false, } } @@ -372,6 +382,7 @@ impl DerivedConfigInputs { warm_coinbase_address: true, // 2 * 24576 as per EIP-3860 max_initcode_size: Some(0xC000), + eip_1153_enabled: false, } } } diff --git a/src/standard/gasometer/costs.rs b/src/standard/gasometer/costs.rs index cbf611ff0..92586e4df 100644 --- a/src/standard/gasometer/costs.rs +++ b/src/standard/gasometer/costs.rs @@ -241,6 +241,13 @@ pub fn sstore_cost( }, ) } +pub fn tload_cost(config: &Config) -> Result { + Ok(config.gas_storage_read_warm) +} + +pub fn tstore_cost(config: &Config) -> Result { + Ok(config.gas_storage_read_warm) +} pub fn suicide_cost(value: U256, is_cold: bool, target_exists: bool, config: &Config) -> u64 { let eip161 = !config.empty_considered_exists; diff --git a/src/standard/gasometer/mod.rs b/src/standard/gasometer/mod.rs index 2b09bda0f..5d6355f0b 100644 --- a/src/standard/gasometer/mod.rs +++ b/src/standard/gasometer/mod.rs @@ -366,6 +366,7 @@ fn dynamic_opcode_cost( target_is_cold: handler.is_cold(address, Some(index)), } } + Opcode::TLOAD if config.eip_1153_enabled => GasCost::TLoad, Opcode::DELEGATECALL if config.has_delegate_call => { let target = stack.peek(1)?.into(); @@ -386,7 +387,6 @@ fn dynamic_opcode_cost( Opcode::SSTORE if !is_static => { let index = stack.peek(0)?; let value = stack.peek(1)?; - GasCost::SStore { original: handler.original_storage(address, index), current: handler.storage(address, index), @@ -394,6 +394,7 @@ fn dynamic_opcode_cost( target_is_cold: handler.is_cold(address, Some(index)), } } + Opcode::TSTORE if !is_static && config.eip_1153_enabled => GasCost::TStore, Opcode::LOG0 if !is_static => GasCost::Log { n: 0, len: U256::from_big_endian(&stack.peek(1)?[..]), @@ -605,6 +606,10 @@ enum GasCost { /// True if target has not been previously accessed in this transaction target_is_cold: bool, }, + /// Gas cost for `TLOAD`. + TLoad, + /// Gas cost for `TSTORE`. + TStore, /// Gas cost for `SHA3`. Sha3 { /// Length of the data. @@ -701,7 +706,8 @@ impl GasCost { new, target_is_cold, } => costs::sstore_cost(original, current, new, gas, target_is_cold, config)?, - + GasCost::TLoad => costs::tload_cost(config)?, + GasCost::TStore => costs::tstore_cost(config)?, GasCost::Sha3 { len } => costs::sha3_cost(len)?, GasCost::Log { n, len } => costs::log_cost(n, len)?, GasCost::VeryLowCopy { len } => costs::verylowcopy_cost(len)?, From e5802fb861306cb34b410590dee4587fa0d1f51c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Agust=C3=ADn=20Rodriguez?= Date: Tue, 4 Jun 2024 05:47:54 -0300 Subject: [PATCH 3/7] feat: support memory copying instruction (EIP-5656) (#279) * add support for MCOPY instruction * update mcopy gas cost * fix memory cost for mcopy * fmt * add flag for eip-5656 * Update interpreter/src/machine/memory.rs Co-authored-by: Qinxuan Chen --------- Co-authored-by: Rodrigo Quelhas Co-authored-by: Rodrigo Quelhas <22591718+RomarQ@users.noreply.github.com> Co-authored-by: Qinxuan Chen --- interpreter/src/etable.rs | 1 + interpreter/src/eval/misc.rs | 19 +++++++++- interpreter/src/eval/mod.rs | 9 +++++ interpreter/src/machine/memory.rs | 58 +++++++++++++++++++++++++++++-- interpreter/src/opcode.rs | 2 ++ src/standard/config.rs | 11 ++++++ src/standard/gasometer/mod.rs | 13 +++++++ 7 files changed, 110 insertions(+), 3 deletions(-) diff --git a/interpreter/src/etable.rs b/interpreter/src/etable.rs index 1e7d3d82a..ec82b8c27 100644 --- a/interpreter/src/etable.rs +++ b/interpreter/src/etable.rs @@ -186,6 +186,7 @@ impl Etable { table[Opcode::MSIZE.as_usize()] = eval_msize as _; table[Opcode::JUMPDEST.as_usize()] = eval_jumpdest as _; + table[Opcode::MCOPY.as_usize()] = eval_mcopy as _; table[Opcode::PUSH0.as_usize()] = eval_push0 as _; table[Opcode::PUSH1.as_usize()] = eval_push1 as _; diff --git a/interpreter/src/eval/misc.rs b/interpreter/src/eval/misc.rs index 657751fa0..0fd3f4ad3 100644 --- a/interpreter/src/eval/misc.rs +++ b/interpreter/src/eval/misc.rs @@ -1,4 +1,4 @@ -use core::cmp::min; +use core::cmp::{max, min}; use primitive_types::{H256, U256}; @@ -99,6 +99,23 @@ pub fn mload(state: &mut Machine) -> Control { Control::Continue } +/// Support for EIP-5656: MCOPY instruction. +#[inline] +pub fn mcopy(state: &mut Machine) -> Control { + pop_u256!(state, dst, src, len); + try_or_fail!(state.memory.resize_offset(max(dst, src), len)); + + if len.is_zero() { + return Control::Continue; + } + + let dst = as_usize_or_fail!(dst); + let src = as_usize_or_fail!(src); + let len = as_usize_or_fail!(len); + state.memory.copy(dst, src, len); + Control::Continue +} + #[inline] pub fn mstore(state: &mut Machine) -> Control { pop_u256!(state, index); diff --git a/interpreter/src/eval/mod.rs b/interpreter/src/eval/mod.rs index c3554053b..241bcf96e 100644 --- a/interpreter/src/eval/mod.rs +++ b/interpreter/src/eval/mod.rs @@ -386,6 +386,15 @@ pub fn eval_jumpdest( Control::Continue } +pub fn eval_mcopy( + machine: &mut Machine, + _handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::misc::mcopy(machine) +} + macro_rules! eval_push { ($($num:expr),*) => { $(paste::paste! { diff --git a/interpreter/src/machine/memory.rs b/interpreter/src/machine/memory.rs index 98abcc707..04d93ecf2 100644 --- a/interpreter/src/machine/memory.rs +++ b/interpreter/src/machine/memory.rs @@ -1,6 +1,6 @@ use alloc::{vec, vec::Vec}; use core::{ - cmp::min, + cmp::{max, min}, mem, ops::{BitAnd, Not, Range}, }; @@ -222,6 +222,15 @@ impl Memory { self.set(memory_offset, data, Some(ulen)) } + + /// Copies part of the memory inside another part of itself. + pub fn copy(&mut self, dst: usize, src: usize, len: usize) { + let resize_offset = max(dst, src); + if self.data.len() < resize_offset + len { + self.data.resize(resize_offset + len, 0); + } + self.data.copy_within(src..src + len, dst); + } } /// Rounds up `x` to the closest multiple of 32. If `x % 32 == 0` then `x` is returned. @@ -233,7 +242,7 @@ fn next_multiple_of_32(x: U256) -> Option { #[cfg(test)] mod tests { - use super::{next_multiple_of_32, U256}; + use super::{next_multiple_of_32, Memory, U256}; #[test] fn test_next_multiple_of_32() { @@ -266,4 +275,49 @@ mod tests { } } } + + #[test] + fn test_memory_copy_works() { + // Create a new instance of memory + let mut memory = Memory::new(100usize); + + // Set the [0,0,0,1,2,3,4] array as memory data. + // + // We insert the [1,2,3,4] array on index 3, + // that's why we have the zero padding at the beginning. + memory.set(3usize, &[1u8, 2u8, 3u8, 4u8], None).unwrap(); + assert_eq!(memory.data(), &[0u8, 0u8, 0u8, 1u8, 2u8, 3u8, 4u8].to_vec()); + + // Copy 1 byte into index 0. + // As the length is 1, we only copy the byte present on index 3. + memory.copy(0usize, 3usize, 1usize); + + // Now the new memory data results in [1,0,0,1,2,3,4] + assert_eq!(memory.data(), &[1u8, 0u8, 0u8, 1u8, 2u8, 3u8, 4u8].to_vec()); + } + + #[test] + fn test_memory_copy_resize() { + // Create a new instance of memory + let mut memory = Memory::new(100usize); + + // Set the [0,0,0,1,2,3,4] array as memory data. + // + // We insert the [1,2,3,4] array on index 3, + // that's why we have the zero padding at the beginning. + memory.set(3usize, &[1u8, 2u8, 3u8, 4u8], None).unwrap(); + assert_eq!(memory.data(), &[0u8, 0u8, 0u8, 1u8, 2u8, 3u8, 4u8].to_vec()); + + // Copy 2 bytes into index 3. + // As the length is 2, we copy the bytes present on indexes 6 and 7, + // which are [4,0]. + memory.copy(3usize, 6usize, 2usize); + + // Now the new memory data results in [0, 0, 0, 4, 0, 3, 4, 0]. + // An extra element is added due to resizing. + assert_eq!( + memory.data(), + &[0u8, 0u8, 0u8, 4u8, 0u8, 3u8, 4u8, 0u8].to_vec() + ); + } } diff --git a/interpreter/src/opcode.rs b/interpreter/src/opcode.rs index 337cec153..1c54642e7 100644 --- a/interpreter/src/opcode.rs +++ b/interpreter/src/opcode.rs @@ -95,6 +95,8 @@ impl Opcode { /// `JUMPDEST` pub const JUMPDEST: Opcode = Opcode(0x5b); + /// `MCOPY` + pub const MCOPY: Opcode = Opcode(0x5e); /// `PUSHn` pub const PUSH0: Opcode = Opcode(0x5f); diff --git a/src/standard/config.rs b/src/standard/config.rs index 5cb65b5d8..25619c0c5 100644 --- a/src/standard/config.rs +++ b/src/standard/config.rs @@ -99,6 +99,8 @@ pub struct Config { pub has_push0: bool, /// Enables transient storage. See [EIP-1153](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1153.md) pub eip_1153_enabled: bool, + /// Enables MCOPY instruction. See [EIP-5656](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-5656.md) + pub eip_5656_enabled: bool, } impl Config { @@ -153,6 +155,7 @@ impl Config { has_base_fee: false, has_push0: false, eip_1153_enabled: false, + eip_5656_enabled: false, } } @@ -207,6 +210,7 @@ impl Config { has_base_fee: false, has_push0: false, eip_1153_enabled: false, + eip_5656_enabled: false, } } @@ -242,6 +246,7 @@ impl Config { warm_coinbase_address, max_initcode_size, eip_1153_enabled, + eip_5656_enabled, } = inputs; // See https://eips.ethereum.org/EIPS/eip-2929 @@ -305,6 +310,7 @@ impl Config { has_base_fee, has_push0, eip_1153_enabled, + eip_5656_enabled, } } } @@ -322,6 +328,7 @@ struct DerivedConfigInputs { warm_coinbase_address: bool, max_initcode_size: Option, eip_1153_enabled: bool, + eip_5656_enabled: bool, } impl DerivedConfigInputs { @@ -337,6 +344,7 @@ impl DerivedConfigInputs { warm_coinbase_address: false, max_initcode_size: None, eip_1153_enabled: false, + eip_5656_enabled: false, } } @@ -352,6 +360,7 @@ impl DerivedConfigInputs { warm_coinbase_address: false, max_initcode_size: None, eip_1153_enabled: false, + eip_5656_enabled: false, } } @@ -367,6 +376,7 @@ impl DerivedConfigInputs { warm_coinbase_address: false, max_initcode_size: None, eip_1153_enabled: false, + eip_5656_enabled: false, } } @@ -383,6 +393,7 @@ impl DerivedConfigInputs { // 2 * 24576 as per EIP-3860 max_initcode_size: Some(0xC000), eip_1153_enabled: false, + eip_5656_enabled: false, } } } diff --git a/src/standard/gasometer/mod.rs b/src/standard/gasometer/mod.rs index 5d6355f0b..5321451d6 100644 --- a/src/standard/gasometer/mod.rs +++ b/src/standard/gasometer/mod.rs @@ -354,6 +354,9 @@ fn dynamic_opcode_cost( len: U256::from_big_endian(&stack.peek(3)?[..]), } } + Opcode::MCOPY if config.eip_5656_enabled => GasCost::VeryLowCopy { + len: U256::from_big_endian(&stack.peek(2)?[..]), + }, Opcode::CALLDATACOPY | Opcode::CODECOPY => GasCost::VeryLowCopy { len: U256::from_big_endian(&stack.peek(2)?[..]), }, @@ -459,6 +462,16 @@ fn dynamic_opcode_cost( len: U256::from_big_endian(&stack.peek(1)?[..]), }), + Opcode::MCOPY => { + let top0 = U256::from_big_endian(&stack.peek(0)?[..]); + let top1 = U256::from_big_endian(&stack.peek(1)?[..]); + let offset = top0.max(top1); + Some(MemoryCost { + offset, + len: U256::from_big_endian(&stack.peek(2)?[..]), + }) + } + Opcode::CODECOPY | Opcode::CALLDATACOPY | Opcode::RETURNDATACOPY => Some(MemoryCost { offset: U256::from_big_endian(&stack.peek(0)?[..]), len: U256::from_big_endian(&stack.peek(2)?[..]), From 8168ca150abce577a4fb78d2da0918d229939b9e Mon Sep 17 00:00:00 2001 From: Rodrigo Quelhas <22591718+RomarQ@users.noreply.github.com> Date: Tue, 4 Jun 2024 12:23:49 +0100 Subject: [PATCH 4/7] feat: burn base fee when eip-1559 is enabled (>= london fork) (#289) --- jsontests/src/run.rs | 4 ++-- jsontests/src/types.rs | 7 ++++++- src/standard/config.rs | 11 +++++++++++ src/standard/invoker/mod.rs | 21 +++++++++++++++++---- 4 files changed, 36 insertions(+), 7 deletions(-) diff --git a/jsontests/src/run.rs b/jsontests/src/run.rs index fcc649f8c..3f113652a 100644 --- a/jsontests/src/run.rs +++ b/jsontests/src/run.rs @@ -121,8 +121,8 @@ pub fn run_test( block_difficulty: test.env.current_difficulty, block_randomness: Some(test.env.current_random), block_gas_limit: test.env.current_gas_limit, - block_base_fee_per_gas: U256::zero(), // TODO: fill in this field. - chain_id: U256::zero(), // TODO: fill in this field. + block_base_fee_per_gas: test.transaction.gas_price, + chain_id: U256::zero(), // TODO: fill in this field. }; let state = test diff --git a/jsontests/src/types.rs b/jsontests/src/types.rs index 9e93a29a2..28179d328 100644 --- a/jsontests/src/types.rs +++ b/jsontests/src/types.rs @@ -94,7 +94,11 @@ impl TestMulti { transaction: TestTransaction { data: self.transaction.data[post_state.indexes.data].0.clone(), gas_limit: self.transaction.gas_limit[post_state.indexes.gas], - gas_price: self.transaction.gas_price.unwrap_or(U256::zero()), + gas_price: self + .transaction + .gas_price + .unwrap_or(self.env.current_base_fee), + gas_priority_fee: self.transaction.max_priority_fee_per_gas, nonce: self.transaction.nonce, secret_key: self.transaction.secret_key, sender: self.transaction.sender, @@ -236,6 +240,7 @@ pub struct TestTransaction { pub data: Vec, pub gas_limit: U256, pub gas_price: U256, + pub gas_priority_fee: Option, pub nonce: U256, pub secret_key: H256, pub sender: H160, diff --git a/src/standard/config.rs b/src/standard/config.rs index 25619c0c5..1dd41a355 100644 --- a/src/standard/config.rs +++ b/src/standard/config.rs @@ -101,6 +101,8 @@ pub struct Config { pub eip_1153_enabled: bool, /// Enables MCOPY instruction. See [EIP-5656](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-5656.md) pub eip_5656_enabled: bool, + /// Uses EIP-1559 (Base fee is burned when this flag is enabled) [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) + pub eip_1559_enabled: bool, } impl Config { @@ -156,6 +158,7 @@ impl Config { has_push0: false, eip_1153_enabled: false, eip_5656_enabled: false, + eip_1559_enabled: false, } } @@ -211,6 +214,7 @@ impl Config { has_push0: false, eip_1153_enabled: false, eip_5656_enabled: false, + eip_1559_enabled: false, } } @@ -247,6 +251,7 @@ impl Config { max_initcode_size, eip_1153_enabled, eip_5656_enabled, + eip_1559_enabled, } = inputs; // See https://eips.ethereum.org/EIPS/eip-2929 @@ -311,6 +316,7 @@ impl Config { has_push0, eip_1153_enabled, eip_5656_enabled, + eip_1559_enabled, } } } @@ -329,6 +335,7 @@ struct DerivedConfigInputs { max_initcode_size: Option, eip_1153_enabled: bool, eip_5656_enabled: bool, + eip_1559_enabled: bool, } impl DerivedConfigInputs { @@ -345,6 +352,7 @@ impl DerivedConfigInputs { max_initcode_size: None, eip_1153_enabled: false, eip_5656_enabled: false, + eip_1559_enabled: false, } } @@ -361,6 +369,7 @@ impl DerivedConfigInputs { max_initcode_size: None, eip_1153_enabled: false, eip_5656_enabled: false, + eip_1559_enabled: true, } } @@ -377,6 +386,7 @@ impl DerivedConfigInputs { max_initcode_size: None, eip_1153_enabled: false, eip_5656_enabled: false, + eip_1559_enabled: true, } } @@ -394,6 +404,7 @@ impl DerivedConfigInputs { max_initcode_size: Some(0xC000), eip_1153_enabled: false, eip_5656_enabled: false, + eip_1559_enabled: true, } } } diff --git a/src/standard/invoker/mod.rs b/src/standard/invoker/mod.rs index 70bf27454..d0529f668 100644 --- a/src/standard/invoker/mod.rs +++ b/src/standard/invoker/mod.rs @@ -76,7 +76,7 @@ pub enum TransactValue { /// The invoke used in a top-layer transaction stack. pub struct TransactInvoke { pub create_address: Option, - pub gas_fee: U256, + pub gas_limit: U256, pub gas_price: U256, pub caller: H160, } @@ -241,7 +241,7 @@ where let value = args.value(); let invoke = TransactInvoke { - gas_fee, + gas_limit: args.gas_limit(), gas_price: args.gas_price(), caller: args.caller(), create_address: match &args { @@ -398,8 +398,6 @@ where Ok(_) | Err(ExitError::Reverted) => left_gas, Err(_) => U256::zero(), }; - let refunded_fee = refunded_gas.saturating_mul(invoke.gas_price); - let coinbase_reward = invoke.gas_fee.saturating_sub(refunded_fee); match &result { Ok(_) => { @@ -410,7 +408,22 @@ where } } + let refunded_fee = refunded_gas.saturating_mul(invoke.gas_price); handler.deposit(invoke.caller, refunded_fee); + // Reward coinbase address + // EIP-1559 updated the fee system so that miners only get to keep the priority fee. + // The base fee is always burned. + let coinbase_gas_price = if substate.config().eip_1559_enabled { + invoke + .gas_price + .saturating_sub(handler.block_base_fee_per_gas()) + } else { + invoke.gas_price + }; + let coinbase_reward = invoke + .gas_limit + .saturating_mul(coinbase_gas_price) + .saturating_sub(refunded_fee); handler.deposit(handler.block_coinbase(), coinbase_reward); result From a5a79c4de4efc3fde78aa2e590ac33de29e5467b Mon Sep 17 00:00:00 2001 From: Rodrigo Quelhas <22591718+RomarQ@users.noreply.github.com> Date: Tue, 4 Jun 2024 12:42:50 +0100 Subject: [PATCH 5/7] Fix eip2929 implementation (#280) * burn base fee after eip-1559 included in london fork * feat: add Cancun configuration * fix(eip-2929): some addresses were considered cold when they shouldn't * Revert "feat: add Cancun configuration" This reverts commit 654fb20800753f164af96b0f2fd41e62fc8794b2. * Revert "burn base fee after eip-1559 included in london fork" This reverts commit 1e318692de81bec946662dac4ccf438220787c51. * add eip-2929 test * remove duplicated test --- .github/workflows/rust.yml | 4 +- interpreter/src/eval/system.rs | 7 --- jsontests/src/lib.rs | 8 ++++ src/standard/gasometer/mod.rs | 87 ++++++++++++++++++++++++++-------- 4 files changed, 78 insertions(+), 28 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 8de37da97..b4dc811bd 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -52,4 +52,6 @@ jobs: jsontests/res/ethtests/GeneralStateTests/VMTests/vmBitwiseLogicOperation/ \ jsontests/res/ethtests/GeneralStateTests/VMTests/vmIOandFlowOperations/ \ jsontests/res/ethtests/GeneralStateTests/VMTests/vmLogTest/ \ - jsontests/res/ethtests/GeneralStateTests/VMTests/vmTests/ + jsontests/res/ethtests/GeneralStateTests/VMTests/vmTests/ \ + jsontests/res/ethtests/GeneralStateTests/stEIP150singleCodeGasPrices/eip2929.json + diff --git a/interpreter/src/eval/system.rs b/interpreter/src/eval/system.rs index 2521fe587..6d2e07459 100644 --- a/interpreter/src/eval/system.rs +++ b/interpreter/src/eval/system.rs @@ -50,7 +50,6 @@ pub fn balance, H: RuntimeEnvironment + RuntimeBackend, T handler: &mut H, ) -> Control { pop!(machine, address); - handler.mark_hot(address.into(), None); push_u256!(machine, handler.balance(address.into())); Control::Continue @@ -130,7 +129,6 @@ pub fn extcodesize, H: RuntimeEnvironment + RuntimeBacken handler: &mut H, ) -> Control { pop!(machine, address); - handler.mark_hot(address.into(), None); let code_size = handler.code_size(address.into()); push_u256!(machine, code_size); @@ -142,7 +140,6 @@ pub fn extcodehash, H: RuntimeEnvironment + RuntimeBacken handler: &mut H, ) -> Control { pop!(machine, address); - handler.mark_hot(address.into(), None); let code_hash = handler.code_hash(address.into()); push!(machine, code_hash); @@ -155,8 +152,6 @@ pub fn extcodecopy, H: RuntimeEnvironment + RuntimeBacken ) -> Control { pop!(machine, address); pop_u256!(machine, memory_offset, code_offset, len); - - handler.mark_hot(address.into(), None); try_or_fail!(machine.memory.resize_offset(memory_offset, len)); let code = handler.code(address.into()); @@ -266,7 +261,6 @@ pub fn sload, H: RuntimeEnvironment + RuntimeBackend, Tr> handler: &mut H, ) -> Control { pop!(machine, index); - handler.mark_hot(machine.state.as_ref().context.address, Some(index)); let value = handler.storage(machine.state.as_ref().context.address, index); push!(machine, value); @@ -278,7 +272,6 @@ pub fn sstore, H: RuntimeEnvironment + RuntimeBackend, Tr handler: &mut H, ) -> Control { pop!(machine, index, value); - handler.mark_hot(machine.state.as_ref().context.address, Some(index)); match handler.set_storage(machine.state.as_ref().context.address, index, value) { Ok(()) => Control::Continue, diff --git a/jsontests/src/lib.rs b/jsontests/src/lib.rs index 3dca24ae6..28661212f 100644 --- a/jsontests/src/lib.rs +++ b/jsontests/src/lib.rs @@ -73,3 +73,11 @@ fn vm_tests() { let tests_status = run::run_single(JSON_FILENAME, false).unwrap(); tests_status.print_total(); } + +#[test] +fn sqt_eip_2930() { + const JSON_FILENAME: &str = + "res/ethtests/GeneralStateTests/stEIP150singleCodeGasPrices/eip2929.json"; + let tests_status = run::run_single(JSON_FILENAME, false).unwrap(); + tests_status.print_total(); +} diff --git a/src/standard/gasometer/mod.rs b/src/standard/gasometer/mod.rs index 5321451d6..12eeea477 100644 --- a/src/standard/gasometer/mod.rs +++ b/src/standard/gasometer/mod.rs @@ -283,7 +283,7 @@ fn dynamic_opcode_cost( stack: &Stack, is_static: bool, config: &Config, - handler: &H, + handler: &mut H, ) -> Result<(GasCost, Option), ExitError> { let gas_cost = match opcode { Opcode::RETURN => GasCost::Zero, @@ -307,40 +307,59 @@ fn dynamic_opcode_cost( Opcode::EXTCODESIZE => { let target = stack.peek(0)?.into(); - GasCost::ExtCodeSize { - target_is_cold: handler.is_cold(target, None), - } + + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(target, None); + handler.mark_hot(target, None); + + GasCost::ExtCodeSize { target_is_cold } } Opcode::BALANCE => { let target = stack.peek(0)?.into(); - GasCost::Balance { - target_is_cold: handler.is_cold(target, None), - } + + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(target, None); + handler.mark_hot(target, None); + + GasCost::Balance { target_is_cold } } Opcode::BLOCKHASH => GasCost::BlockHash, Opcode::EXTCODEHASH if config.has_ext_code_hash => { let target = stack.peek(0)?.into(); - GasCost::ExtCodeHash { - target_is_cold: handler.is_cold(target, None), - } + + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(target, None); + handler.mark_hot(target, None); + + GasCost::ExtCodeHash { target_is_cold } } Opcode::EXTCODEHASH => GasCost::Invalid(opcode), Opcode::CALLCODE => { let target = stack.peek(1)?.into(); + + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(target, None); + handler.mark_hot(target, None); + GasCost::CallCode { 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_is_cold, target_exists: { handler.exists(target) }, } } Opcode::STATICCALL => { let target = stack.peek(1)?.into(); + + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(target, None); + handler.mark_hot(target, None); + GasCost::StaticCall { gas: U256::from_big_endian(&stack.peek(0)?[..]), - target_is_cold: handler.is_cold(target, None), + target_is_cold, target_exists: { handler.exists(target) }, } } @@ -349,8 +368,13 @@ fn dynamic_opcode_cost( }, Opcode::EXTCODECOPY => { let target = stack.peek(0)?.into(); + + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(target, None); + handler.mark_hot(target, None); + GasCost::ExtCodeCopy { - target_is_cold: handler.is_cold(target, None), + target_is_cold, len: U256::from_big_endian(&stack.peek(3)?[..]), } } @@ -365,17 +389,25 @@ fn dynamic_opcode_cost( }, Opcode::SLOAD => { let index = stack.peek(0)?; - GasCost::SLoad { - target_is_cold: handler.is_cold(address, Some(index)), - } + + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(address, Some(index)); + handler.mark_hot(address, Some(index)); + + GasCost::SLoad { target_is_cold } } Opcode::TLOAD if config.eip_1153_enabled => GasCost::TLoad, Opcode::DELEGATECALL if config.has_delegate_call => { let target = stack.peek(1)?.into(); + + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(target, None); + handler.mark_hot(target, None); + GasCost::DelegateCall { gas: U256::from_big_endian(&stack.peek(0)?[..]), - target_is_cold: handler.is_cold(target, None), + target_is_cold, target_exists: { handler.exists(target) }, } } @@ -390,11 +422,16 @@ fn dynamic_opcode_cost( Opcode::SSTORE if !is_static => { let index = stack.peek(0)?; let value = stack.peek(1)?; + + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(address, Some(index)); + handler.mark_hot(address, Some(index)); + GasCost::SStore { original: handler.original_storage(address, index), current: handler.storage(address, index), new: value, - target_is_cold: handler.is_cold(address, Some(index)), + target_is_cold, } } Opcode::TSTORE if !is_static && config.eip_1153_enabled => GasCost::TStore, @@ -424,9 +461,14 @@ fn dynamic_opcode_cost( }, Opcode::SUICIDE if !is_static => { let target = stack.peek(0)?.into(); + + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(target, None); + handler.mark_hot(target, None); + GasCost::Suicide { value: handler.balance(address), - target_is_cold: handler.is_cold(target, None), + target_is_cold, target_exists: { handler.exists(target) }, already_removed: handler.deleted(address), } @@ -436,10 +478,15 @@ fn dynamic_opcode_cost( || (is_static && U256::from_big_endian(&stack.peek(2)?[..]) == U256::zero()) => { let target = stack.peek(1)?.into(); + + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(target, None); + handler.mark_hot(target, None); + GasCost::Call { 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_is_cold, target_exists: { handler.exists(target) }, } } From a388f69de4f114460aa8c94131de98153f43ec34 Mon Sep 17 00:00:00 2001 From: Clayton Rabenda Date: Tue, 4 Jun 2024 07:32:17 -0500 Subject: [PATCH 6/7] Chore: silent clippy blocks_in_conditions warning (#267) --- .cargo/config.toml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 000000000..7c1cbcb8c --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,4 @@ +[target.'cfg(all())'] +rustflags = [ +"-Aclippy::blocks_in_conditions", +] \ No newline at end of file From fda8a4082bb3fd98b089ceb30ce690e4cd4d6721 Mon Sep 17 00:00:00 2001 From: Rodrigo Quelhas <22591718+RomarQ@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:59:19 +0100 Subject: [PATCH 7/7] feat: add Cancun configuration (#288) * feat: add Cancun configuration * add missing fields to config * add missing field --- src/standard/config.rs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/standard/config.rs b/src/standard/config.rs index 1dd41a355..da2be1409 100644 --- a/src/standard/config.rs +++ b/src/standard/config.rs @@ -238,6 +238,11 @@ impl Config { Self::config_with_derived_values(DerivedConfigInputs::shanghai()) } + /// Cancun hard fork configuration. + pub const fn cancun() -> Config { + Self::config_with_derived_values(DerivedConfigInputs::cancun()) + } + const fn config_with_derived_values(inputs: DerivedConfigInputs) -> Config { let DerivedConfigInputs { gas_storage_read_warm, @@ -324,8 +329,11 @@ impl Config { /// Independent inputs that are used to derive other config values. /// See `Config::config_with_derived_values` implementation for details. struct DerivedConfigInputs { + /// `WARM_STORAGE_READ_COST` (see EIP-2929). gas_storage_read_warm: u64, + /// `COLD_SLOAD_COST` (see EIP-2929). gas_sload_cold: u64, + /// `ACCESS_LIST_STORAGE_KEY_COST` (see EIP-2930). gas_access_list_storage_key: u64, decrease_clears_refund: bool, has_base_fee: bool, @@ -407,4 +415,22 @@ impl DerivedConfigInputs { eip_1559_enabled: true, } } + + const fn cancun() -> Self { + Self { + gas_storage_read_warm: 100, + gas_sload_cold: 2100, + gas_access_list_storage_key: 1900, + decrease_clears_refund: true, + has_base_fee: true, + has_push0: true, + disallow_executable_format: true, + warm_coinbase_address: true, + // 2 * (MAX_CODE_SIZE = `24576`) = (0xC000 = 49152) as per EIP-3860 + max_initcode_size: Some(0xC000), + eip_1153_enabled: true, + eip_5656_enabled: true, + eip_1559_enabled: true, + } + } }