diff --git a/crates/interpreter/src/host.rs b/crates/interpreter/src/host.rs index 57afa405aa..257b93c80b 100644 --- a/crates/interpreter/src/host.rs +++ b/crates/interpreter/src/host.rs @@ -2,6 +2,7 @@ use crate::primitives::Bytecode; use crate::{ primitives::{Address, Bytes, Env, B256, U256}, CallInputs, CreateInputs, Gas, InstructionResult, Interpreter, SelfDestructResult, + SharedMemory, }; use alloc::vec::Vec; pub use dummy::DummyHost; @@ -51,7 +52,12 @@ pub trait Host { fn create( &mut self, inputs: &mut CreateInputs, + shared_memory: &mut SharedMemory, ) -> (InstructionResult, Option
, Gas, Bytes); /// Invoke a call operation. - fn call(&mut self, input: &mut CallInputs) -> (InstructionResult, Gas, Bytes); + fn call( + &mut self, + input: &mut CallInputs, + shared_memory: &mut SharedMemory, + ) -> (InstructionResult, Gas, Bytes); } diff --git a/crates/interpreter/src/host/dummy.rs b/crates/interpreter/src/host/dummy.rs index 72b7699da2..ed6b768286 100644 --- a/crates/interpreter/src/host/dummy.rs +++ b/crates/interpreter/src/host/dummy.rs @@ -2,6 +2,7 @@ use crate::primitives::{hash_map::Entry, Bytecode, Bytes, HashMap, U256}; use crate::{ primitives::{Address, Env, Log, B256, KECCAK_EMPTY}, CallInputs, CreateInputs, Gas, Host, InstructionResult, Interpreter, SelfDestructResult, + SharedMemory, }; use alloc::vec::Vec; @@ -137,12 +138,17 @@ impl Host for DummyHost { fn create( &mut self, _inputs: &mut CreateInputs, + _shared_memory: &mut SharedMemory, ) -> (InstructionResult, Option
, Gas, Bytes) { panic!("Create is not supported for this host") } #[inline] - fn call(&mut self, _input: &mut CallInputs) -> (InstructionResult, Gas, Bytes) { + fn call( + &mut self, + _input: &mut CallInputs, + _shared_memory: &mut SharedMemory, + ) -> (InstructionResult, Gas, Bytes) { panic!("Call is not supported for this host") } } diff --git a/crates/interpreter/src/instructions/control.rs b/crates/interpreter/src/instructions/control.rs index 9c28a1af97..ed5a3086cb 100644 --- a/crates/interpreter/src/instructions/control.rs +++ b/crates/interpreter/src/instructions/control.rs @@ -53,7 +53,7 @@ fn return_inner(interpreter: &mut Interpreter, result: InstructionResult) { // important: offset must be ignored if len is zero if len != 0 { let offset = as_usize_or_fail!(interpreter, offset); - memory_resize!(interpreter, offset, len); + shared_memory_resize!(interpreter, offset, len); interpreter.return_offset = offset; } interpreter.return_len = len; diff --git a/crates/interpreter/src/instructions/host.rs b/crates/interpreter/src/instructions/host.rs index babeba926c..928c6b5000 100644 --- a/crates/interpreter/src/instructions/host.rs +++ b/crates/interpreter/src/instructions/host.rs @@ -109,11 +109,11 @@ pub fn extcodecopy(interpreter: &mut Interpreter, host: &mu } let memory_offset = as_usize_or_fail!(interpreter, memory_offset); let code_offset = min(as_usize_saturated!(code_offset), code.len()); - memory_resize!(interpreter, memory_offset, len); + shared_memory_resize!(interpreter, memory_offset, len); // Safety: set_data is unsafe function and memory_resize ensures us that it is safe to call it interpreter - .memory + .shared_memory .set_data(memory_offset, code_offset, len, code.bytes()); } @@ -197,8 +197,8 @@ pub fn log(interpreter: &mut Interpreter, host: &mut H) Bytes::new() } else { let offset = as_usize_or_fail!(interpreter, offset); - memory_resize!(interpreter, offset, len); - Bytes::copy_from_slice(interpreter.memory.slice(offset, len)) + shared_memory_resize!(interpreter, offset, len); + Bytes::copy_from_slice(interpreter.shared_memory.slice(offset, len)) }; if interpreter.stack.len() < N { @@ -273,8 +273,8 @@ pub fn prepare_create_inputs( } let code_offset = as_usize_or_fail!(interpreter, code_offset); - memory_resize!(interpreter, code_offset, len); - Bytes::copy_from_slice(interpreter.memory.slice(code_offset, len)) + shared_memory_resize!(interpreter, code_offset, len); + Bytes::copy_from_slice(interpreter.shared_memory.slice(code_offset, len)) }; let scheme = if IS_CREATE2 { @@ -315,7 +315,8 @@ pub fn create( return; }; - let (return_reason, address, gas, return_data) = host.create(&mut create_input); + let (return_reason, address, gas, return_data) = + host.create(&mut create_input, interpreter.shared_memory); interpreter.return_data_buffer = match return_reason { // Save data to return data buffer if the create reverted @@ -399,8 +400,8 @@ fn prepare_call_inputs( let in_len = as_usize_or_fail!(interpreter, in_len); let input = if in_len != 0 { let in_offset = as_usize_or_fail!(interpreter, in_offset); - memory_resize!(interpreter, in_offset, in_len); - Bytes::copy_from_slice(interpreter.memory.slice(in_offset, in_len)) + shared_memory_resize!(interpreter, in_offset, in_len); + Bytes::copy_from_slice(interpreter.shared_memory.slice(in_offset, in_len)) } else { Bytes::new() }; @@ -408,7 +409,7 @@ fn prepare_call_inputs( *result_len = as_usize_or_fail!(interpreter, out_len); *result_offset = if *result_len != 0 { let out_offset = as_usize_or_fail!(interpreter, out_offset); - memory_resize!(interpreter, out_offset, *result_len); + shared_memory_resize!(interpreter, out_offset, *result_len); out_offset } else { usize::MAX //unrealistic value so we are sure it is not used @@ -535,10 +536,9 @@ pub fn call_inner( }; // Call host to interact with target contract - let (reason, gas, return_data) = host.call(&mut call_input); + let (reason, gas, return_data) = host.call(&mut call_input, interpreter.shared_memory); interpreter.return_data_buffer = return_data; - let target_len = min(out_len, interpreter.return_data_buffer.len()); match reason { @@ -549,7 +549,7 @@ pub fn call_inner( interpreter.gas.record_refund(gas.refunded()); } interpreter - .memory + .shared_memory .set(out_offset, &interpreter.return_data_buffer[..target_len]); push!(interpreter, U256::from(1)); } @@ -558,7 +558,7 @@ pub fn call_inner( interpreter.gas.erase_cost(gas.remaining()); } interpreter - .memory + .shared_memory .set(out_offset, &interpreter.return_data_buffer[..target_len]); push!(interpreter, U256::ZERO); } diff --git a/crates/interpreter/src/instructions/macros.rs b/crates/interpreter/src/instructions/macros.rs index 714a83368b..41063fca4a 100644 --- a/crates/interpreter/src/instructions/macros.rs +++ b/crates/interpreter/src/instructions/macros.rs @@ -50,18 +50,12 @@ macro_rules! gas_or_fail { }; } -macro_rules! memory_resize { +macro_rules! shared_memory_resize { ($interp:expr, $offset:expr, $len:expr) => { if let Some(new_size) = - crate::interpreter::memory::next_multiple_of_32($offset.saturating_add($len)) + crate::interpreter::shared_memory::next_multiple_of_32($offset.saturating_add($len)) { - #[cfg(feature = "memory_limit")] - if new_size > ($interp.memory_limit as usize) { - $interp.instruction_result = InstructionResult::MemoryLimitOOG; - return; - } - - if new_size > $interp.memory.len() { + if new_size > $interp.shared_memory.len() { if crate::USE_GAS { let num_bytes = new_size / 32; if !$interp.gas.record_memory(crate::gas::memory_gas(num_bytes)) { @@ -69,7 +63,7 @@ macro_rules! memory_resize { return; } } - $interp.memory.resize(new_size); + $interp.shared_memory.resize(new_size); } } else { $interp.instruction_result = InstructionResult::MemoryOOG; diff --git a/crates/interpreter/src/instructions/memory.rs b/crates/interpreter/src/instructions/memory.rs index ec3a9bbabb..b3ae69cacb 100644 --- a/crates/interpreter/src/instructions/memory.rs +++ b/crates/interpreter/src/instructions/memory.rs @@ -9,10 +9,16 @@ pub fn mload(interpreter: &mut Interpreter, _host: &mut H) { gas!(interpreter, gas::VERYLOW); pop!(interpreter, index); let index = as_usize_or_fail!(interpreter, index); - memory_resize!(interpreter, index, 32); + shared_memory_resize!(interpreter, index, 32); push!( interpreter, - U256::from_be_bytes::<32>(interpreter.memory.slice(index, 32).try_into().unwrap()) + U256::from_be_bytes::<32>( + interpreter + .shared_memory + .slice(index, 32) + .try_into() + .unwrap() + ) ); } @@ -20,21 +26,21 @@ pub fn mstore(interpreter: &mut Interpreter, _host: &mut H) { gas!(interpreter, gas::VERYLOW); pop!(interpreter, index, value); let index = as_usize_or_fail!(interpreter, index); - memory_resize!(interpreter, index, 32); - interpreter.memory.set_u256(index, value); + shared_memory_resize!(interpreter, index, 32); + interpreter.shared_memory.set_u256(index, value); } pub fn mstore8(interpreter: &mut Interpreter, _host: &mut H) { gas!(interpreter, gas::VERYLOW); pop!(interpreter, index, value); let index = as_usize_or_fail!(interpreter, index); - memory_resize!(interpreter, index, 1); - interpreter.memory.set_byte(index, value.byte(0)) + shared_memory_resize!(interpreter, index, 1); + interpreter.shared_memory.set_byte(index, value.byte(0)) } pub fn msize(interpreter: &mut Interpreter, _host: &mut H) { gas!(interpreter, gas::BASE); - push!(interpreter, U256::from(interpreter.memory.len())); + push!(interpreter, U256::from(interpreter.shared_memory.len())); } // EIP-5656: MCOPY - Memory copying instruction @@ -53,7 +59,7 @@ pub fn mcopy(interpreter: &mut Interpreter, _host: &mut H) let dst = as_usize_or_fail!(interpreter, dst); let src = as_usize_or_fail!(interpreter, src); // resize memory - memory_resize!(interpreter, max(dst, src), len); + shared_memory_resize!(interpreter, max(dst, src), len); // copy memory in place - interpreter.memory.copy(dst, src, len); + interpreter.shared_memory.copy(dst, src, len); } diff --git a/crates/interpreter/src/instructions/system.rs b/crates/interpreter/src/instructions/system.rs index e4976058ed..9ae285bb1e 100644 --- a/crates/interpreter/src/instructions/system.rs +++ b/crates/interpreter/src/instructions/system.rs @@ -12,8 +12,8 @@ pub fn keccak256(interpreter: &mut Interpreter, _host: &mut H) { KECCAK_EMPTY } else { let from = as_usize_or_fail!(interpreter, from); - memory_resize!(interpreter, from, len); - crate::primitives::keccak256(interpreter.memory.slice(from, len)) + shared_memory_resize!(interpreter, from, len); + crate::primitives::keccak256(interpreter.shared_memory.slice(from, len)) }; push_b256!(interpreter, hash); @@ -43,10 +43,10 @@ pub fn codecopy(interpreter: &mut Interpreter, _host: &mut H) { } let memory_offset = as_usize_or_fail!(interpreter, memory_offset); let code_offset = as_usize_saturated!(code_offset); - memory_resize!(interpreter, memory_offset, len); + shared_memory_resize!(interpreter, memory_offset, len); // Safety: set_data is unsafe function and memory_resize ensures us that it is safe to call it - interpreter.memory.set_data( + interpreter.shared_memory.set_data( memory_offset, code_offset, len, @@ -89,12 +89,15 @@ pub fn calldatacopy(interpreter: &mut Interpreter, _host: &mut H) { } let memory_offset = as_usize_or_fail!(interpreter, memory_offset); let data_offset = as_usize_saturated!(data_offset); - memory_resize!(interpreter, memory_offset, len); + shared_memory_resize!(interpreter, memory_offset, len); // Safety: set_data is unsafe function and memory_resize ensures us that it is safe to call it - interpreter - .memory - .set_data(memory_offset, data_offset, len, &interpreter.contract.input); + interpreter.shared_memory.set_data( + memory_offset, + data_offset, + len, + &interpreter.contract.input, + ); } /// EIP-211: New opcodes: RETURNDATASIZE and RETURNDATACOPY @@ -121,8 +124,8 @@ pub fn returndatacopy(interpreter: &mut Interpreter, _host: } if len != 0 { let memory_offset = as_usize_or_fail!(interpreter, memory_offset); - memory_resize!(interpreter, memory_offset, len); - interpreter.memory.set( + shared_memory_resize!(interpreter, memory_offset, len); + interpreter.shared_memory.set( memory_offset, &interpreter.return_data_buffer[data_offset..data_end], ); diff --git a/crates/interpreter/src/interpreter.rs b/crates/interpreter/src/interpreter.rs index d13bd1998a..59d9ae1ba8 100644 --- a/crates/interpreter/src/interpreter.rs +++ b/crates/interpreter/src/interpreter.rs @@ -1,6 +1,6 @@ pub mod analysis; mod contract; -pub mod memory; +pub(crate) mod shared_memory; mod stack; use crate::primitives::{Bytes, Spec}; @@ -8,7 +8,7 @@ use crate::{alloc::boxed::Box, opcode::eval, Gas, Host, InstructionResult}; pub use analysis::BytecodeLocked; pub use contract::Contract; -pub use memory::Memory; +pub use shared_memory::SharedMemory; pub use stack::{Stack, STACK_LIMIT}; pub const CALL_STACK_LIMIT: u64 = 1024; @@ -22,8 +22,8 @@ pub const MAX_CODE_SIZE: usize = 0x6000; pub const MAX_INITCODE_SIZE: usize = 2 * MAX_CODE_SIZE; #[derive(Debug)] -pub struct Interpreter { - /// Contract information and invoking data. +pub struct Interpreter<'a> { + /// Contract information and invoking data pub contract: Box, /// The current instruction pointer. pub instruction_pointer: *const u8, @@ -32,9 +32,9 @@ pub struct Interpreter { pub instruction_result: InstructionResult, /// The gas state. pub gas: Gas, - /// The memory. - pub memory: Memory, - /// The stack. + /// Shared memory. + pub shared_memory: &'a mut SharedMemory, + /// Stack. pub stack: Stack, /// The return data buffer for internal calls. pub return_data_buffer: Bytes, @@ -46,43 +46,27 @@ pub struct Interpreter { pub return_len: usize, /// Whether the interpreter is in "staticcall" mode, meaning no state changes can happen. pub is_static: bool, - /// Memory limit. See [`crate::CfgEnv`]. - #[cfg(feature = "memory_limit")] - pub memory_limit: u64, } -impl Interpreter { - /// Instantiates a new interpreter. - #[inline] - pub fn new(contract: Box, gas_limit: u64, is_static: bool) -> Self { +impl<'a> Interpreter<'a> { + /// Create new interpreter + pub fn new( + contract: Box, + gas_limit: u64, + is_static: bool, + shared_memory: &'a mut SharedMemory, + ) -> Self { Self { instruction_pointer: contract.bytecode.as_ptr(), contract, - instruction_result: InstructionResult::Continue, gas: Gas::new(gas_limit), - memory: Memory::new(), - stack: Stack::new(), + instruction_result: InstructionResult::Continue, + is_static, return_data_buffer: Bytes::new(), - return_offset: 0, return_len: 0, - is_static, - #[cfg(feature = "memory_limit")] - memory_limit: u64::MAX, - } - } - - /// Instantiates a new interpreter with the given memory limit. - #[cfg(feature = "memory_limit")] - #[inline] - pub fn new_with_memory_limit( - contract: Box, - gas_limit: u64, - is_static: bool, - memory_limit: u64, - ) -> Self { - Self { - memory_limit, - ..Self::new(contract, gas_limit, is_static) + return_offset: 0, + shared_memory, + stack: Stack::new(), } } @@ -104,12 +88,6 @@ impl Interpreter { &self.gas } - /// Returns a reference to the interpreter's memory. - #[inline] - pub fn memory(&self) -> &Memory { - &self.memory - } - /// Returns a reference to the interpreter's stack. #[inline] pub fn stack(&self) -> &Stack { @@ -179,7 +157,8 @@ impl Interpreter { if self.return_len == 0 { &[] } else { - self.memory.slice(self.return_offset, self.return_len) + self.shared_memory + .slice(self.return_offset, self.return_len) } } } diff --git a/crates/interpreter/src/interpreter/memory.rs b/crates/interpreter/src/interpreter/memory.rs deleted file mode 100644 index aa3a43fdb0..0000000000 --- a/crates/interpreter/src/interpreter/memory.rs +++ /dev/null @@ -1,234 +0,0 @@ -use crate::primitives::U256; -use alloc::vec::Vec; -use core::{ - cmp::min, - fmt, - ops::{BitAnd, Not}, -}; - -/// A sequential memory. It uses Rust's `Vec` for internal -/// representation. -#[derive(Clone, Eq, PartialEq)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -pub struct Memory { - data: Vec, -} - -impl fmt::Debug for Memory { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Memory") - .field("data", &crate::primitives::hex::encode(&self.data)) - .finish() - } -} - -impl Default for Memory { - #[inline] - fn default() -> Self { - Self { - data: Vec::with_capacity(4 * 1024), // took it from evmone - } - } -} - -impl Memory { - /// Create a new memory with the given limit. - #[inline] - pub fn new() -> Self { - Self { - data: Vec::with_capacity(4 * 1024), // took it from evmone - } - } - - #[deprecated = "Use `len` instead"] - #[doc(hidden)] - #[inline] - pub fn effective_len(&self) -> usize { - self.len() - } - - /// Returns the length of the current memory range. - #[inline] - pub fn len(&self) -> usize { - self.data.len() - } - - /// Returns true if current memory range length is zero. - #[inline] - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Return a reference to the full memory. - #[inline] - pub fn data(&self) -> &Vec { - &self.data - } - - /// Consumes the type and returns the full memory. - #[inline] - pub fn into_data(self) -> Vec { - self.data - } - - /// Shrinks the capacity of the data buffer as much as possible. - #[inline] - pub fn shrink_to_fit(&mut self) { - self.data.shrink_to_fit() - } - - /// Resizes the stack in-place so that then length is equal to `new_size`. - /// - /// `new_size` should be a multiple of 32. - #[inline] - pub fn resize(&mut self, new_size: usize) { - self.data.resize(new_size, 0); - } - - /// Returns a byte slice of the memory region at the given offset. - /// - /// Panics on out of bounds. - #[inline(always)] - #[cfg_attr(debug_assertions, track_caller)] - pub fn slice(&self, offset: usize, size: usize) -> &[u8] { - match self.data.get(offset..offset + size) { - Some(slice) => slice, - None => debug_unreachable!("slice OOB: {offset}..{size}; len: {}", self.len()), - } - } - - #[deprecated = "use `slice` instead"] - #[inline(always)] - #[cfg_attr(debug_assertions, track_caller)] - pub fn get_slice(&self, offset: usize, size: usize) -> &[u8] { - self.slice(offset, size) - } - - /// Returns a mutable byte slice of the memory region at the given offset. - /// - /// Panics on out of bounds. - #[inline(always)] - #[cfg_attr(debug_assertions, track_caller)] - pub fn slice_mut(&mut self, offset: usize, size: usize) -> &mut [u8] { - let _len = self.len(); - match self.data.get_mut(offset..offset + size) { - Some(slice) => slice, - None => debug_unreachable!("slice_mut OOB: {offset}..{size}; len: {_len}"), - } - } - - /// Sets the `byte` at the given `index`. - /// - /// Panics when `index` is out of bounds. - #[inline(always)] - #[cfg_attr(debug_assertions, track_caller)] - pub fn set_byte(&mut self, index: usize, byte: u8) { - match self.data.get_mut(index) { - Some(b) => *b = byte, - None => debug_unreachable!("set_byte OOB: {index}; len: {}", self.len()), - } - } - - /// Sets the given `value` to the memory region at the given `offset`. - /// - /// Panics on out of bounds. - #[inline(always)] - #[cfg_attr(debug_assertions, track_caller)] - pub fn set_u256(&mut self, offset: usize, value: U256) { - self.set(offset, &value.to_be_bytes::<32>()); - } - - /// Set memory region at given `offset`. - /// - /// Panics on out of bounds. - #[inline(always)] - #[cfg_attr(debug_assertions, track_caller)] - pub fn set(&mut self, offset: usize, value: &[u8]) { - if !value.is_empty() { - self.slice_mut(offset, value.len()).copy_from_slice(value); - } - } - - /// Set memory from data. Our memory offset+len is expected to be correct but we - /// are doing bound checks on data/data_offeset/len and zeroing parts that is not copied. - /// - /// Panics on out of bounds. - #[inline(always)] - #[cfg_attr(debug_assertions, track_caller)] - pub fn set_data(&mut self, memory_offset: usize, data_offset: usize, len: usize, data: &[u8]) { - if data_offset >= data.len() { - // nullify all memory slots - self.slice_mut(memory_offset, len).fill(0); - return; - } - let data_end = min(data_offset + len, data.len()); - let data_len = data_end - data_offset; - debug_assert!(data_offset < data.len() && data_end <= data.len()); - let data = unsafe { data.get_unchecked(data_offset..data_end) }; - self.slice_mut(memory_offset, data_len) - .copy_from_slice(data); - - // nullify rest of memory slots - // Safety: Memory is assumed to be valid. And it is commented where that assumption is made - self.slice_mut(memory_offset + data_len, len - data_len) - .fill(0); - } - - /// Copies elements from one part of the memory to another part of itself. - /// - /// Panics on out of bounds. - #[inline(always)] - #[cfg_attr(debug_assertions, track_caller)] - pub fn copy(&mut self, dst: usize, src: usize, len: usize) { - 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. -#[inline] -pub(crate) fn next_multiple_of_32(x: usize) -> Option { - let r = x.bitand(31).not().wrapping_add(1).bitand(31); - x.checked_add(r) -} - -#[cfg(test)] -mod tests { - use super::next_multiple_of_32; - use crate::Memory; - - #[test] - fn test_copy() { - // Create a sample memory instance - let mut memory = Memory::new(); - - // Set up initial memory data - let data: Vec = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; - memory.resize(data.len()); - memory.set_data(0, 0, data.len(), &data); - - // Perform a copy operation - memory.copy(5, 0, 4); - - // Verify the copied data - let copied_data = memory.slice(5, 4); - assert_eq!(copied_data, &[1, 2, 3, 4]); - } - - #[test] - fn test_next_multiple_of_32() { - // next_multiple_of_32 returns x when it is a multiple of 32 - for i in 0..32 { - let x = i * 32; - assert_eq!(Some(x), next_multiple_of_32(x)); - } - - // next_multiple_of_32 rounds up to the nearest multiple of 32 when `x % 32 != 0` - for x in 0..1024 { - if x % 32 == 0 { - continue; - } - let next_multiple = x + 32 - (x % 32); - assert_eq!(Some(next_multiple), next_multiple_of_32(x)); - } - } -} diff --git a/crates/interpreter/src/interpreter/shared_memory.rs b/crates/interpreter/src/interpreter/shared_memory.rs new file mode 100644 index 0000000000..d076993e9e --- /dev/null +++ b/crates/interpreter/src/interpreter/shared_memory.rs @@ -0,0 +1,258 @@ +use revm_primitives::U256; + +use crate::alloc::vec::Vec; +use core::{ + cmp::min, + fmt, + ops::{BitAnd, Not}, +}; + +/// A sequential memory shared between calls, which uses +/// a `Vec` for internal representation. +/// A [SharedMemory] instance should always be obtained using +/// the `new` static method to ensure memory safety. +pub struct SharedMemory { + /// Shared buffer + data: Vec, + /// Memory checkpoints for each depth + checkpoints: Vec, + /// How much memory has been used in the current context + current_len: usize, +} + +impl fmt::Debug for SharedMemory { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SharedMemory") + .field( + "current_slice", + &crate::primitives::hex::encode(self.context_memory()), + ) + .finish() + } +} + +impl Default for SharedMemory { + fn default() -> Self { + Self::new() + } +} + +impl SharedMemory { + /// Allocate memory to be shared between calls. + /// Memory size is estimated using https://2π.com/22/eth-max-mem + /// which depends on transaction [gas_limit]. + /// Maximum allocation size is 2^32 - 1 bytes; + pub fn new() -> Self { + Self { + data: Vec::with_capacity(4 * 1024), // from evmone + checkpoints: Vec::with_capacity(32), + current_len: 0, + } + } + + /// Prepares the shared memory for a new context + pub fn new_context_memory(&mut self) { + let base_offset = self.last_checkpoint(); + let new_checkpoint = base_offset + self.current_len; + + self.checkpoints.push(new_checkpoint); + self.current_len = 0; + } + + /// Prepares the shared memory for returning to the previous context + pub fn free_context_memory(&mut self) { + if let Some(old_checkpoint) = self.checkpoints.pop() { + let last_checkpoint = self.last_checkpoint(); + self.current_len = old_checkpoint - last_checkpoint; + } + } + + /// Get the length of the current memory range. + #[inline(always)] + pub fn len(&self) -> usize { + self.current_len + } + + /// Returns true if the current memory range is empty. + #[inline(always)] + pub fn is_empty(&self) -> bool { + self.current_len == 0 + } + + /// Resize the memory. assume that we already checked if: + /// - we have enough gas to resize this vector + /// - we made new_size as multiply of 32 + /// - [new_size] is greater than `self.len()` + #[inline(always)] + pub fn resize(&mut self, new_size: usize) { + let last_checkpoint = self.last_checkpoint(); + let range = last_checkpoint + self.current_len..last_checkpoint + new_size; + + if let Some(available_memory) = self.data.get_mut(range) { + available_memory.fill(0); + } else { + self.data + .resize(last_checkpoint + usize::max(new_size, 4 * 1024), 0); + } + + self.current_len = new_size; + } + + /// Returns a byte slice of the memory region at the given offset. + /// + /// Panics on out of bounds. + #[inline(always)] + #[cfg_attr(debug_assertions, track_caller)] + pub fn slice(&self, offset: usize, size: usize) -> &[u8] { + let end = offset + size; + let last_checkpoint = self.last_checkpoint(); + + match self + .data + .get(last_checkpoint + offset..last_checkpoint + offset + size) + { + Some(slice) => slice, + None => debug_unreachable!("slice OOB: {offset}..{end}; len: {}", self.len()), + } + } + + /// Returns a byte slice of the memory region at the given offset. + /// + /// Panics on out of bounds. + #[inline(always)] + #[cfg_attr(debug_assertions, track_caller)] + pub fn slice_mut(&mut self, offset: usize, size: usize) -> &mut [u8] { + let len = self.len(); + let end = offset + size; + let last_checkpoint = self.last_checkpoint(); + + match self + .data + .get_mut(last_checkpoint + offset..last_checkpoint + offset + size) + { + Some(slice) => slice, + None => debug_unreachable!("slice OOB: {offset}..{end}; len: {}", len), + } + } + + /// Sets the `byte` at the given `index`. + /// + /// Panics when `index` is out of bounds. + #[inline(always)] + #[cfg_attr(debug_assertions, track_caller)] + pub fn set_byte(&mut self, index: usize, byte: u8) { + let last_checkpoint = self.last_checkpoint(); + match self.data.get_mut(last_checkpoint + index) { + Some(b) => *b = byte, + None => debug_unreachable!("set_byte OOB: {index}; len: {}", self.len()), + } + } + + /// Sets the given `value` to the memory region at the given `offset`. + /// + /// Panics on out of bounds. + #[inline(always)] + #[cfg_attr(debug_assertions, track_caller)] + pub fn set_u256(&mut self, offset: usize, value: U256) { + self.set(offset, &value.to_be_bytes::<32>()); + } + + /// Set memory region at given `offset`. + /// + /// Panics on out of bounds. + #[inline(always)] + #[cfg_attr(debug_assertions, track_caller)] + pub fn set(&mut self, offset: usize, value: &[u8]) { + if !value.is_empty() { + self.slice_mut(offset, value.len()).copy_from_slice(value); + } + } + + /// Set memory from data. Our memory offset+len is expected to be correct but we + /// are doing bound checks on data/data_offeset/len and zeroing parts that is not copied. + /// + /// Panics on out of bounds. + #[inline(always)] + #[cfg_attr(debug_assertions, track_caller)] + pub fn set_data(&mut self, memory_offset: usize, data_offset: usize, len: usize, data: &[u8]) { + if data_offset >= data.len() { + // nullify all memory slots + self.slice_mut(memory_offset, len).fill(0); + return; + } + let data_end = min(data_offset + len, data.len()); + let data_len = data_end - data_offset; + debug_assert!(data_offset < data.len() && data_end <= data.len()); + let data = unsafe { data.get_unchecked(data_offset..data_end) }; + self.slice_mut(memory_offset, data_len) + .copy_from_slice(data); + + // nullify rest of memory slots + // Safety: Memory is assumed to be valid. And it is commented where that assumption is made + self.slice_mut(memory_offset + data_len, len - data_len) + .fill(0); + } + + /// Copies elements from one part of the memory to another part of itself. + /// + /// Panics on out of bounds. + #[inline(always)] + #[cfg_attr(debug_assertions, track_caller)] + pub fn copy(&mut self, dst: usize, src: usize, len: usize) { + self.context_memory_mut().copy_within(src..src + len, dst); + } + + /// Get a reference to the memory of the current context + #[inline(always)] + fn context_memory(&self) -> &[u8] { + let last_checkpoint = self.last_checkpoint(); + let current_len = self.current_len; + // Safety: it is a valid pointer to a slice of `self.data` + &self.data[last_checkpoint..last_checkpoint + current_len] + } + + /// Get a mutable reference to the memory of the current context + #[inline(always)] + fn context_memory_mut(&mut self) -> &mut [u8] { + let last_checkpoint = self.last_checkpoint(); + let current_len = self.current_len; + // Safety: it is a valid pointer to a slice of `self.data` + &mut self.data[last_checkpoint..last_checkpoint + current_len] + } + + /// Get the last memory checkpoint + #[inline(always)] + fn last_checkpoint(&self) -> usize { + *self.checkpoints.last().unwrap_or(&0) + } +} + +/// Rounds up `x` to the closest multiple of 32. If `x % 32 == 0` then `x` is returned. +#[inline] +pub(crate) fn next_multiple_of_32(x: usize) -> Option { + let r = x.bitand(31).not().wrapping_add(1).bitand(31); + x.checked_add(r) +} + +#[cfg(test)] +mod tests { + use super::next_multiple_of_32; + + #[test] + fn test_next_multiple_of_32() { + // next_multiple_of_32 returns x when it is a multiple of 32 + for i in 0..32 { + let x = i * 32; + assert_eq!(Some(x), next_multiple_of_32(x)); + } + + // next_multiple_of_32 rounds up to the nearest multiple of 32 when `x % 32 != 0` + for x in 0..1024 { + if x % 32 == 0 { + continue; + } + let next_multiple = x + 32 - (x % 32); + assert_eq!(Some(next_multiple), next_multiple_of_32(x)); + } + } +} diff --git a/crates/interpreter/src/lib.rs b/crates/interpreter/src/lib.rs index a55265d791..0fcd745e73 100644 --- a/crates/interpreter/src/lib.rs +++ b/crates/interpreter/src/lib.rs @@ -22,7 +22,7 @@ pub use inner_models::*; pub use instruction_result::InstructionResult; pub use instructions::{opcode, Instruction, OpCode, OPCODE_JUMPMAP}; pub use interpreter::{ - analysis, BytecodeLocked, Contract, Interpreter, Memory, Stack, CALL_STACK_LIMIT, + analysis, BytecodeLocked, Contract, Interpreter, SharedMemory, Stack, CALL_STACK_LIMIT, MAX_CODE_SIZE, MAX_INITCODE_SIZE, }; diff --git a/crates/revm/benches/bench.rs b/crates/revm/benches/bench.rs index 37056277f9..4dadb40c67 100644 --- a/crates/revm/benches/bench.rs +++ b/crates/revm/benches/bench.rs @@ -8,6 +8,7 @@ use revm::{ address, bytes, hex, BerlinSpec, Bytecode, BytecodeState, Bytes, TransactTo, U256, }, }; +use revm_interpreter::SharedMemory; use std::time::Duration; type Evm = revm::EVM; @@ -57,7 +58,7 @@ fn snailtracer(c: &mut Criterion) { .measurement_time(Duration::from_secs(10)) .sample_size(10); bench_transact(&mut g, &mut evm); - bench_eval(&mut g, &evm); + bench_eval(&mut g, &mut evm); g.finish(); } @@ -85,7 +86,9 @@ fn bench_transact(g: &mut BenchmarkGroup<'_, WallTime>, evm: &mut Evm) { g.bench_function(id, |b| b.iter(|| evm.transact().unwrap())); } -fn bench_eval(g: &mut BenchmarkGroup<'_, WallTime>, evm: &Evm) { +fn bench_eval(g: &mut BenchmarkGroup<'_, WallTime>, evm: &mut Evm) { + let mut shared_memory = SharedMemory::new(); + g.bench_function("eval", |b| { let contract = Contract { input: evm.env.tx.data.clone(), @@ -94,7 +97,12 @@ fn bench_eval(g: &mut BenchmarkGroup<'_, WallTime>, evm: &Evm) { }; let mut host = DummyHost::new(evm.env.clone()); b.iter(|| { - let mut interpreter = Interpreter::new(Box::new(contract.clone()), u64::MAX, false); + let mut interpreter = Interpreter::new( + Box::new(contract.clone()), + u64::MAX, + false, + &mut shared_memory, + ); let res = interpreter.run::<_, BerlinSpec>(&mut host); host.clear(); res diff --git a/crates/revm/src/evm_impl.rs b/crates/revm/src/evm_impl.rs index 577b219e3f..792a06c5fc 100644 --- a/crates/revm/src/evm_impl.rs +++ b/crates/revm/src/evm_impl.rs @@ -15,7 +15,7 @@ use alloc::boxed::Box; use alloc::vec::Vec; use core::marker::PhantomData; use revm_interpreter::gas::initial_tx_gas; -use revm_interpreter::MAX_CODE_SIZE; +use revm_interpreter::{SharedMemory, MAX_CODE_SIZE}; use revm_precompile::{Precompile, Precompiles}; #[cfg(feature = "optimism")] @@ -285,40 +285,48 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> Transact let transact_gas_limit = tx_gas_limit - initial_gas_spend; + let mut shared_memory = SharedMemory::new(); + // call inner handling of call/create let (call_result, ret_gas, output) = match self.data.env.tx.transact_to { TransactTo::Call(address) => { // Nonce is already checked caller_account.info.nonce = caller_account.info.nonce.saturating_add(1); - let (exit, gas, bytes) = self.call(&mut CallInputs { - contract: address, - transfer: Transfer { - source: tx_caller, - target: address, - value: tx_value, - }, - input: tx_data, - gas_limit: transact_gas_limit, - context: CallContext { - caller: tx_caller, - address, - code_address: address, - apparent_value: tx_value, - scheme: CallScheme::Call, + let (exit, gas, bytes) = self.call( + &mut CallInputs { + contract: address, + transfer: Transfer { + source: tx_caller, + target: address, + value: tx_value, + }, + input: tx_data, + gas_limit: transact_gas_limit, + context: CallContext { + caller: tx_caller, + address, + code_address: address, + apparent_value: tx_value, + scheme: CallScheme::Call, + }, + is_static: false, }, - is_static: false, - }); + &mut shared_memory, + ); (exit, gas, Output::Call(bytes)) } TransactTo::Create(scheme) => { - let (exit, address, ret_gas, bytes) = self.create(&mut CreateInputs { - caller: tx_caller, - scheme, - value: tx_value, - init_code: tx_data, - gas_limit: transact_gas_limit, - }); + let (exit, address, ret_gas, bytes) = self.create( + &mut CreateInputs { + caller: tx_caller, + scheme, + value: tx_value, + init_code: tx_data, + gas_limit: transact_gas_limit, + }, + &mut shared_memory, + ); (exit, ret_gas, Output::Create(bytes, address)) } }; @@ -525,7 +533,11 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, } /// EVM create opcode for both initial crate and CREATE and CREATE2 opcodes. - fn create_inner(&mut self, inputs: &CreateInputs) -> CreateResult { + fn create_inner( + &mut self, + inputs: &CreateInputs, + shared_memory: &mut SharedMemory, + ) -> CreateResult { // Prepare crate. let prepared_create = match self.prepare_create(inputs) { Ok(o) => o, @@ -533,15 +545,18 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, }; // Create new interpreter and execute initcode - let (exit_reason, mut interpreter) = - self.run_interpreter(prepared_create.contract, prepared_create.gas.limit(), false); + let (exit_reason, mut bytes, mut gas) = self.run_interpreter( + prepared_create.contract, + prepared_create.gas.limit(), + false, + shared_memory, + ); // Host error if present on execution match exit_reason { return_ok!() => { // if ok, check contract creation limit and calculate gas deduction on output len. - let mut bytes = interpreter.return_value(); - + // // EIP-3541: Reject new contract code starting with the 0xEF byte if GSPEC::enabled(LONDON) && !bytes.is_empty() && bytes.first() == Some(&0xEF) { self.data @@ -550,7 +565,7 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, return CreateResult { result: InstructionResult::CreateContractStartingWithEF, created_address: Some(prepared_create.created_address), - gas: interpreter.gas, + gas, return_value: bytes, }; } @@ -572,13 +587,13 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, return CreateResult { result: InstructionResult::CreateContractSizeLimit, created_address: Some(prepared_create.created_address), - gas: interpreter.gas, + gas, return_value: bytes, }; } if crate::USE_GAS { let gas_for_code = bytes.len() as u64 * gas::CODEDEPOSIT; - if !interpreter.gas.record_cost(gas_for_code) { + if !gas.record_cost(gas_for_code) { // record code deposit gas cost and check if we are out of gas. // EIP-2 point 3: If contract creation does not have enough gas to pay for the // final gas fee for adding the contract code to the state, the contract @@ -590,7 +605,7 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, return CreateResult { result: InstructionResult::OutOfGas, created_address: Some(prepared_create.created_address), - gas: interpreter.gas, + gas, return_value: bytes, }; } else { @@ -612,7 +627,7 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, CreateResult { result: InstructionResult::Return, created_address: Some(prepared_create.created_address), - gas: interpreter.gas, + gas, return_value: bytes, } } @@ -623,32 +638,30 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, CreateResult { result: exit_reason, created_address: Some(prepared_create.created_address), - gas: interpreter.gas, - return_value: interpreter.return_value(), + gas, + return_value: bytes, } } } } /// Create a Interpreter and run it. - /// Returns the exit reason and created interpreter as it contains return values and gas spend. + /// Returns the exit reason, return value and gas from interpreter pub fn run_interpreter( &mut self, contract: Box, gas_limit: u64, is_static: bool, - ) -> (InstructionResult, Box) { - // Create inspector - #[cfg(feature = "memory_limit")] - let mut interpreter = Box::new(Interpreter::new_with_memory_limit( + shared_memory: &mut SharedMemory, + ) -> (InstructionResult, Bytes, Gas) { + let mut interpreter = Box::new(Interpreter::new( contract, gas_limit, is_static, - self.data.env.cfg.memory_limit, + shared_memory, )); - #[cfg(not(feature = "memory_limit"))] - let mut interpreter = Box::new(Interpreter::new(contract, gas_limit, is_static)); + interpreter.shared_memory.new_context_memory(); if INSPECT { self.inspector @@ -660,7 +673,11 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, interpreter.run::(self) }; - (exit_reason, interpreter) + let (return_value, gas) = (interpreter.return_value(), *interpreter.gas()); + + interpreter.shared_memory.free_context_memory(); + + (exit_reason, return_value, gas) } /// Call precompile contract @@ -777,7 +794,7 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, } /// Main contract call of the EVM. - fn call_inner(&mut self, inputs: &CallInputs) -> CallResult { + fn call_inner(&mut self, inputs: &CallInputs, shared_memory: &mut SharedMemory) -> CallResult { // Prepare call let prepared_call = match self.prepare_call(inputs) { Ok(o) => o, @@ -788,15 +805,16 @@ impl<'a, GSPEC: Spec, DB: Database, const INSPECT: bool> EVMImpl<'a, GSPEC, DB, self.call_precompile(inputs, prepared_call.gas) } else if !prepared_call.contract.bytecode.is_empty() { // Create interpreter and execute subcall - let (exit_reason, interpreter) = self.run_interpreter( + let (exit_reason, bytes, gas) = self.run_interpreter( prepared_call.contract, prepared_call.gas.limit(), inputs.is_static, + shared_memory, ); CallResult { result: exit_reason, - gas: interpreter.gas, - return_value: interpreter.return_value(), + gas, + return_value: bytes, } } else { CallResult { @@ -948,6 +966,7 @@ impl<'a, GSPEC: Spec, DB: Database + 'a, const INSPECT: bool> Host fn create( &mut self, inputs: &mut CreateInputs, + shared_memory: &mut SharedMemory, ) -> (InstructionResult, Option
, Gas, Bytes) { // Call inspector if INSPECT { @@ -958,7 +977,7 @@ impl<'a, GSPEC: Spec, DB: Database + 'a, const INSPECT: bool> Host .create_end(&mut self.data, inputs, ret, address, gas, out); } } - let ret = self.create_inner(inputs); + let ret = self.create_inner(inputs, shared_memory); if INSPECT { self.inspector.create_end( &mut self.data, @@ -973,7 +992,11 @@ impl<'a, GSPEC: Spec, DB: Database + 'a, const INSPECT: bool> Host } } - fn call(&mut self, inputs: &mut CallInputs) -> (InstructionResult, Gas, Bytes) { + fn call( + &mut self, + inputs: &mut CallInputs, + shared_memory: &mut SharedMemory, + ) -> (InstructionResult, Gas, Bytes) { if INSPECT { let (ret, gas, out) = self.inspector.call(&mut self.data, inputs); if ret != InstructionResult::Continue { @@ -982,7 +1005,7 @@ impl<'a, GSPEC: Spec, DB: Database + 'a, const INSPECT: bool> Host .call_end(&mut self.data, inputs, gas, ret, out); } } - let ret = self.call_inner(inputs); + let ret = self.call_inner(inputs, shared_memory); if INSPECT { self.inspector.call_end( &mut self.data, diff --git a/crates/revm/src/inspector/customprinter.rs b/crates/revm/src/inspector/customprinter.rs index f0b8a4ae88..31c18e92f9 100644 --- a/crates/revm/src/inspector/customprinter.rs +++ b/crates/revm/src/inspector/customprinter.rs @@ -38,7 +38,7 @@ impl Inspector for CustomPrintTracer { interp.gas.refunded(), interp.gas.refunded(), interp.stack.data(), - interp.memory.data().len(), + interp.shared_memory.len(), ); self.gas_inspector.step(interp, data); diff --git a/crates/revm/src/inspector/tracer_eip3155.rs b/crates/revm/src/inspector/tracer_eip3155.rs index 5625225f40..926e70bdf2 100644 --- a/crates/revm/src/inspector/tracer_eip3155.rs +++ b/crates/revm/src/inspector/tracer_eip3155.rs @@ -5,7 +5,7 @@ use crate::interpreter::{CallInputs, CreateInputs, Gas, InstructionResult}; use crate::primitives::{db::Database, hex, Address, Bytes}; use crate::{evm_impl::EVMData, Inspector}; use revm_interpreter::primitives::U256; -use revm_interpreter::{opcode, Interpreter, Memory, Stack}; +use revm_interpreter::{opcode, Interpreter, SharedMemory, Stack}; use serde_json::json; use std::io::Write; @@ -24,7 +24,7 @@ pub struct TracerEip3155 { gas: u64, mem_size: usize, #[allow(dead_code)] - memory: Option, + memory: Option, skip: bool, } @@ -63,7 +63,7 @@ impl Inspector for TracerEip3155 { self.stack = interp.stack.clone(); self.pc = interp.program_counter(); self.opcode = interp.current_opcode(); - self.mem_size = interp.memory.len(); + self.mem_size = interp.shared_memory.len(); self.gas = self.gas_inspector.gas_remaining(); InstructionResult::Continue }