diff --git a/actors/evm/src/interpreter/execution.rs b/actors/evm/src/interpreter/execution.rs index 5fcc105fc..22f013b8d 100644 --- a/actors/evm/src/interpreter/execution.rs +++ b/actors/evm/src/interpreter/execution.rs @@ -163,6 +163,7 @@ pub mod opcodes { 0x59: MSIZE, 0x5a: GAS, 0x5b: JUMPDEST, + 0x5e: MCOPY, 0x5F: PUSH0, 0x60: PUSH1, 0x61: PUSH2, diff --git a/actors/evm/src/interpreter/instructions/memory.rs b/actors/evm/src/interpreter/instructions/memory.rs index 8e410fe27..ba9e532ce 100644 --- a/actors/evm/src/interpreter/instructions/memory.rs +++ b/actors/evm/src/interpreter/instructions/memory.rs @@ -53,6 +53,48 @@ pub fn get_memory_region( })) } +#[inline] +pub fn mcopy( + state: &mut ExecutionState, + _: &System, + 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, @@ -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! { diff --git a/actors/evm/src/interpreter/instructions/mod.rs b/actors/evm/src/interpreter/instructions/mod.rs index 5c95e2fa5..b97b407e1 100644 --- a/actors/evm/src/interpreter/instructions/mod.rs +++ b/actors/evm/src/interpreter/instructions/mod.rs @@ -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 }