Skip to content

feat: support memory copying instruction (EIP-5656) #279

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions interpreter/src/etable.rs
Original file line number Diff line number Diff line change
@@ -186,6 +186,7 @@ impl<S, H, Tr> Etable<S, H, Tr> {
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 _;
19 changes: 18 additions & 1 deletion interpreter/src/eval/misc.rs
Original file line number Diff line number Diff line change
@@ -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<S, Tr>(state: &mut Machine<S>) -> Control<Tr> {
Control::Continue
}

/// Support for EIP-5656: MCOPY instruction.
#[inline]
pub fn mcopy<S, Tr>(state: &mut Machine<S>) -> Control<Tr> {
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<S, Tr>(state: &mut Machine<S>) -> Control<Tr> {
pop_u256!(state, index);
9 changes: 9 additions & 0 deletions interpreter/src/eval/mod.rs
Original file line number Diff line number Diff line change
@@ -386,6 +386,15 @@ pub fn eval_jumpdest<S, H, Tr>(
Control::Continue
}

pub fn eval_mcopy<S, H, Tr>(
machine: &mut Machine<S>,
_handle: &mut H,
_opcode: Opcode,
_position: usize,
) -> Control<Tr> {
self::misc::mcopy(machine)
}

macro_rules! eval_push {
($($num:expr),*) => {
$(paste::paste! {
58 changes: 56 additions & 2 deletions interpreter/src/machine/memory.rs
Original file line number Diff line number Diff line change
@@ -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<U256> {

#[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()
);
}
}
2 changes: 2 additions & 0 deletions interpreter/src/opcode.rs
Original file line number Diff line number Diff line change
@@ -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);
11 changes: 11 additions & 0 deletions src/standard/config.rs
Original file line number Diff line number Diff line change
@@ -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<usize>,
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,
}
}
}
13 changes: 13 additions & 0 deletions src/standard/gasometer/mod.rs
Original file line number Diff line number Diff line change
@@ -354,6 +354,9 @@ fn dynamic_opcode_cost<H: RuntimeBackend>(
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<H: RuntimeBackend>(
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)?[..]),