Skip to content
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

feat(evm): add MCOPY opcode #1572

Merged
merged 13 commits into from
Sep 30, 2024
1 change: 1 addition & 0 deletions actors/evm/src/interpreter/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ pub mod opcodes {
0x59: MSIZE,
0x5a: GAS,
0x5b: JUMPDEST,
0x5e: MCOPY,
0x5F: PUSH0,
0x60: PUSH1,
0x61: PUSH2,
Expand Down
265 changes: 265 additions & 0 deletions actors/evm/src/interpreter/instructions/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,30 @@ pub fn get_memory_region(
}))
}

#[inline]
pub fn mcopy(
state: &mut ExecutionState,
_: &System<impl Runtime>,
dest_index: U256,
src_index: U256,
size: U256,
) -> Result<(), ActorError> {
// We are copying between two potentially overlapping slices in the same memory.
// MCOPY spec: Copying takes place as if an intermediate buffer was used,
// allowing the destination and source to overlap.

// expand memory to accomodate requested src_index + size
let region = get_memory_region(&mut state.memory, src_index, size)?.expect("empty region");
let memory_slice = state.memory[region.offset..region.offset + region.size.get()].to_vec();

// expand memory to match dest_index + size
let _destination_region =
get_memory_region(&mut state.memory, dest_index, size)?.expect("empty region");

//copy
copy_to_memory(&mut state.memory, dest_index, size, U256::zero(), &memory_slice, true)
}

pub fn copy_to_memory(
memory: &mut Memory,
dest_offset: U256,
Expand Down Expand Up @@ -200,6 +224,247 @@ mod tests {
assert_eq!(&mem[0..4], result_data);
}

#[test]
fn test_mcopy() {
const LENGTH: usize = 2;
const OFFSET: usize = 1;
const DEST_OFFSET: usize = 0;

evm_unit_test! {
(m) {
MCOPY;
}

// Grow memory and set initial values
m.state.memory.grow(32);
m.state.memory[..3].copy_from_slice(&[0x00, 0x01, 0x02]);

// Set up stack
m.state.stack.push(U256::from(LENGTH)).unwrap();
m.state.stack.push(U256::from(OFFSET)).unwrap();
m.state.stack.push(U256::from(DEST_OFFSET)).unwrap();

// Execute and assert
assert!(m.step().is_ok(), "execution step failed");
assert_eq!(m.state.stack.len(), 0);

// Setup expected memory and assert
let mut expected = [0u8; 32];
expected[..3].copy_from_slice(&[0x01, 0x02, 0x02]);
assert_eq!(&*m.state.memory, &expected);
};
}

#[test]
fn test_mcopy_0_32_32() {
evm_unit_test! {
(m) {
MCOPY;
}

// Grow memory and set initial values
m.state.memory.grow(64);
m.state.memory[32..64].copy_from_slice(&[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f
]);

// Set up stack
m.state.stack.push(U256::from(32)).unwrap(); // length
m.state.stack.push(U256::from(32)).unwrap(); // source offset
m.state.stack.push(U256::from(0)).unwrap(); // destination offset

// Execute and assert
assert!(m.step().is_ok(), "execution step failed");
assert_eq!(m.state.stack.len(), 0);

// Setup expected memory and assert
let mut expected = [0u8; 64];
expected[0..64].copy_from_slice(&[
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f
]);
assert_eq!(&*m.state.memory, &expected);
};
}

#[test]
fn test_mcopy_0_0_32() {
evm_unit_test! {
(m) {
MCOPY;
}

// Grow memory and set initial values
m.state.memory.grow(32);
m.state.memory[..32].copy_from_slice(&[0x01; 32]);

// Set up stack
m.state.stack.push(U256::from(32)).unwrap(); // length
m.state.stack.push(U256::from(0)).unwrap(); // source offset
m.state.stack.push(U256::from(0)).unwrap(); // destination offset

// Execute and assert
assert!(m.step().is_ok(), "execution step failed");
assert_eq!(m.state.stack.len(), 0);

// Setup expected memory and assert
let expected = [0x01; 32];
assert_eq!(&m.state.memory[..32], &expected);
};
}

#[test]
fn test_mcopy_0_1_8() {
evm_unit_test! {
(m) {
MCOPY;
}

// Grow memory and set initial values
m.state.memory.grow(32);
m.state.memory[..8].copy_from_slice(&[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]);
m.state.memory[8] = 0x08;

// Set up stack
m.state.stack.push(U256::from(8)).unwrap(); // length
m.state.stack.push(U256::from(1)).unwrap(); // source offset
m.state.stack.push(U256::from(0)).unwrap(); // destination offset

// Execute and assert
assert!(m.step().is_ok(), "execution step failed");
assert_eq!(m.state.stack.len(), 0);

// Setup expected memory and assert
let mut expected = [0u8; 32];
expected[..8].copy_from_slice(&[0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08]);
expected[8] = 0x08;
assert_eq!(&m.state.memory[..9], &expected[..9]);
};
}

#[test]
fn test_mcopy_1_0_8() {
evm_unit_test! {
(m) {
MCOPY;
}

// Grow memory and set initial values
m.state.memory.grow(32);
m.state.memory[..8].copy_from_slice(&[0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07]);
m.state.memory[8] = 0x08;

// Set up stack
m.state.stack.push(U256::from(8)).unwrap(); // length
m.state.stack.push(U256::from(0)).unwrap(); // source offset
m.state.stack.push(U256::from(1)).unwrap(); // destination offset

// Execute and assert
assert!(m.step().is_ok(), "execution step failed");
assert_eq!(m.state.stack.len(), 0);

// Setup expected memory and assert
let mut expected = [0u8; 32];
expected[..8].copy_from_slice(&[0x00, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06]);
expected[8] = 0x07;
assert_eq!(&m.state.memory[..9], &expected[..9]);
};
}

#[test]
fn test_mcopy_out_of_range_dest() {
evm_unit_test! {
(m) {
MCOPY;
}

// Initial memory setup
m.state.memory.grow(32);
m.state.memory[..4].copy_from_slice(&[0x01, 0x02, 0x03, 0x04]);

// Set up stack: Attempt to copy to a destination beyond the current memory range
m.state.stack.push(U256::from(4)).unwrap(); // length
m.state.stack.push(U256::from(0)).unwrap(); // source offset
m.state.stack.push(U256::from(64)).unwrap(); // out of range destination offset

// Execute and expect memory expansion
assert!(m.step().is_ok(), "execution step failed");
assert_eq!(m.state.stack.len(), 0);

// Check that memory was expanded correctly
assert_eq!(m.state.memory.len(), 96);

// Check the memory contents
let mut expected = [0u8; 96];
expected[..4].copy_from_slice(&[0x01, 0x02, 0x03, 0x04]);
expected[64..68].copy_from_slice(&[0x01, 0x02, 0x03, 0x04]);
assert_eq!(&*m.state.memory, &expected[..]);
};
}

#[test]
fn test_mcopy_partially_out_of_range_source() {
evm_unit_test! {
(m) {
MCOPY;
}

// Initial memory setup
m.state.memory.grow(32);
m.state.memory[..28].copy_from_slice(&[0x01; 28]);

// Set up stack: Source partially out of range
m.state.stack.push(U256::from(10)).unwrap(); // length
m.state.stack.push(U256::from(24)).unwrap(); // source offset (partially out of range)
m.state.stack.push(U256::from(0)).unwrap(); // destination offset

// Execute and expect memory expansion
assert!(m.step().is_ok(), "execution step failed");
assert_eq!(m.state.stack.len(), 0);

// Check the length of the memory after the operation
assert_eq!(m.state.memory.len(), 32+EVM_WORD_SIZE); // Memory should remain at 32 bytes after the operation

// Check that memory was expanded correctly
let mut expected = vec![0x01; 4]; // First 4 bytes copied
expected.extend_from_slice(&[0x00; 4]); // Remaining 4 bytes unchanged
assert_eq!(&m.state.memory[..8], &expected[..8]);
};
}

#[test]
fn test_mcopy_fully_out_of_range_dest_fails() {
evm_unit_test! {
(m) {
MCOPY;
}

// Initial memory setup
m.state.memory.grow(32);
m.state.memory[..4].copy_from_slice(&[0x01, 0x02, 0x03, 0x04]);

// Set up stack: Attempt to copy to a destination fully out of range
m.state.stack.push(U256::from(4)).unwrap(); // length
m.state.stack.push(U256::from(0)).unwrap(); // source offset
m.state.stack.push(U256::from(128)).unwrap(); // fully out of range destination offset


// Execute and assert memory grows
assert!(m.step().is_ok(), "expected step to succeed and grow memory");
assert_eq!(m.state.memory.len(), 160); // Expected memory to grow

// Check the memory contents
let mut expected = [0u8; 132];
expected[..4].copy_from_slice(&[0x01, 0x02, 0x03, 0x04]);
expected[128..132].copy_from_slice(&[0x01, 0x02, 0x03, 0x04]);
assert_eq!(&m.state.memory[0..132], &expected[0..132]);

};
}

#[test]
fn test_mload_nothing() {
evm_unit_test! {
Expand Down
1 change: 1 addition & 0 deletions actors/evm/src/interpreter/instructions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,7 @@ def_stdfun_code! { CODESIZE() => call::codesize }
def_stdproc_code! { CODECOPY(a, b, c) => call::codecopy }
def_stdfun! { CREATE(a, b, c) => lifecycle::create }
def_stdfun! { CREATE2(a, b, c, d) => lifecycle::create2 }
def_stdproc! { MCOPY(a,b,c) => memory::mcopy }
def_stdproc! { JUMPDEST() => control::nop }
def_stdproc! { INVALID() => control::invalid }
def_exit! { RETURN(a, b) => control::ret }
Expand Down
Loading