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] 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)?[..]),