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
283 changes: 283 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,48 @@ 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> {
// Copy memory from src_index to dest_index.
// Handles overlapping slices as if using an intermediate buffer, ensuring correct copying.
// Expands memory if src_index + size or dest_index + size exceeds current bounds.
// Returns an error if memory regions are invalid or cannot be allocated.

if size > 0 {
copy_within_memory(&mut state.memory, dest_index, src_index, size)
} else {
Ok(())
}
}

pub fn copy_within_memory(
memory: &mut Memory,
dest_index: U256,
src_index: U256,
size: U256,
) -> Result<(), ActorError> {
// Expand memory to accommodate requested src_index + size
let src_region = get_memory_region(memory, src_index, size)?.expect("empty region");

// Expand memory to match dest_index + size
let destination_region = get_memory_region(memory, dest_index, size)?.expect("empty region");

// Named variables for clarity
let source_range = src_region.offset..(src_region.offset + src_region.size.get());
let destination_index = destination_region.offset;

// Copy memory
memory.copy_within(source_range, destination_index);

Ok(())
}

pub fn copy_to_memory(
memory: &mut Memory,
dest_offset: U256,
Expand Down Expand Up @@ -200,6 +242,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