From fed455472079dd30db36a9bce56328d05ff8ce9c Mon Sep 17 00:00:00 2001 From: Mathieu <60658558+enitrat@users.noreply.github.com> Date: Tue, 17 Sep 2024 16:01:26 +0200 Subject: [PATCH] fix: saturate memory offset taken from stack (#950) * fix: saturate memory offset taken from stack * fix tests * fmt * fix test' --- crates/evm/src/create_helpers.cairo | 2 +- .../environmental_information.cairo | 16 +- .../src/instructions/logging_operations.cairo | 138 ++++++++---------- .../src/instructions/memory_operations.cairo | 4 +- .../src/instructions/system_operations.cairo | 20 +-- crates/evm/src/stack.cairo | 39 +++++ 6 files changed, 120 insertions(+), 99 deletions(-) diff --git a/crates/evm/src/create_helpers.cairo b/crates/evm/src/create_helpers.cairo index baf8f048..010995f9 100644 --- a/crates/evm/src/create_helpers.cairo +++ b/crates/evm/src/create_helpers.cairo @@ -39,7 +39,7 @@ pub impl CreateHelpersImpl of CreateHelpers { /// As part of the CREATE family of opcodes. fn prepare_create(ref self: VM, create_type: CreateType) -> Result { let value = self.stack.pop()?; - let offset = self.stack.pop_usize()?; + let offset = self.stack.pop_saturating_usize()?; let size = self.stack.pop_usize()?; let memory_expansion = gas::memory_expansion(self.memory.size(), [(offset, size)].span())?; diff --git a/crates/evm/src/instructions/environmental_information.cairo b/crates/evm/src/instructions/environmental_information.cairo index f6bcd1ca..1f5bc4da 100644 --- a/crates/evm/src/instructions/environmental_information.cairo +++ b/crates/evm/src/instructions/environmental_information.cairo @@ -111,8 +111,8 @@ pub impl EnvironmentInformationImpl of EnvironmentInformationTrait { /// Save word to memory. /// # Specification: https://www.evm.codes/#37?fork=shanghai fn exec_calldatacopy(ref self: VM) -> Result<(), EVMError> { - let dest_offset = self.stack.pop_usize()?; - let offset = self.stack.pop_usize()?; + let dest_offset = self.stack.pop_saturating_usize()?; + let offset = self.stack.pop_saturating_usize()?; let size = self.stack.pop_usize()?; let words_size = (ceil32(size) / 32).into(); @@ -141,8 +141,8 @@ pub impl EnvironmentInformationImpl of EnvironmentInformationTrait { /// Copies slice of bytecode to memory. /// # Specification: https://www.evm.codes/#39?fork=shanghai fn exec_codecopy(ref self: VM) -> Result<(), EVMError> { - let dest_offset = self.stack.pop_usize()?; - let offset = self.stack.pop_usize()?; + let dest_offset = self.stack.pop_saturating_usize()?; + let offset = self.stack.pop_saturating_usize()?; let size = self.stack.pop_usize()?; let words_size = (ceil32(size) / 32).into(); @@ -190,8 +190,8 @@ pub impl EnvironmentInformationImpl of EnvironmentInformationTrait { /// # Specification: https://www.evm.codes/#3c?fork=shanghai fn exec_extcodecopy(ref self: VM) -> Result<(), EVMError> { let evm_address = self.stack.pop_eth_address()?; - let dest_offset = self.stack.pop_usize()?; - let offset = self.stack.pop_usize()?; + let dest_offset = self.stack.pop_saturating_usize()?; + let offset = self.stack.pop_saturating_usize()?; let size = self.stack.pop_usize()?; // GAS @@ -227,8 +227,8 @@ pub impl EnvironmentInformationImpl of EnvironmentInformationTrait { /// Save word to memory. /// # Specification: https://www.evm.codes/#3e?fork=shanghai fn exec_returndatacopy(ref self: VM) -> Result<(), EVMError> { - let dest_offset = self.stack.pop_usize()?; - let offset = self.stack.pop_usize()?; + let dest_offset = self.stack.pop_saturating_usize()?; + let offset = self.stack.pop_saturating_usize()?; let size = self.stack.pop_usize()?; let return_data: Span = self.return_data(); diff --git a/crates/evm/src/instructions/logging_operations.cairo b/crates/evm/src/instructions/logging_operations.cairo index 571e1573..66cb952f 100644 --- a/crates/evm/src/instructions/logging_operations.cairo +++ b/crates/evm/src/instructions/logging_operations.cairo @@ -60,7 +60,7 @@ fn exec_log_i(ref self: VM, topics_len: u8) -> Result<(), EVMError> { ensure(!self.message().read_only, EVMError::WriteInStaticContext)?; // TODO(optimization): check benefits of n `pop` instead of `pop_n` - let offset = self.stack.pop_usize()?; + let offset = self.stack.pop_saturating_usize()?; let size = self.stack.pop_usize()?; let topics: Array = self.stack.pop_n(topics_len.into())?; @@ -107,18 +107,18 @@ mod tests { let result = vm.exec_log0(); // Then - assert(result.is_ok(), 'should have succeeded'); - assert(vm.stack.len() == 0, 'stack should be empty'); + assert!(result.is_ok()); + assert_eq!(vm.stack.len(), 0); let mut events = vm.env.state.events; - assert(events.len() == 1, 'context should have one event'); + assert_eq!(events.len(), 1); let event = events.pop_front().unwrap(); - assert(event.keys.len() == 0, 'event should not have keys'); + assert_eq!(event.keys.len(), 0); - assert(event.data.len() == 31, 'event should have 31 bytes'); + assert_eq!(event.data.len(), 31); let data_expected = u256_to_bytes_array(Bounded::::MAX).span().slice(0, 31); - assert(event.data.span() == data_expected, 'event data are incorrect'); + assert_eq!(event.data.span(), data_expected); } #[test] @@ -136,19 +136,19 @@ mod tests { let result = vm.exec_log1(); // Then - assert(result.is_ok(), 'should have succeeded'); - assert(vm.stack.len() == 0, 'stack should be empty'); + assert!(result.is_ok()); + assert_eq!(vm.stack.len(), 0); let mut events = vm.env.state.events; - assert(events.len() == 1, 'context should have one event'); + assert_eq!(events.len(), 1); let event = events.pop_front().unwrap(); - assert(event.keys.len() == 1, 'event should have one key'); - assert(*event.keys[0] == 0x0123456789ABCDEF, 'event key is not correct'); + assert_eq!(event.keys.len(), 1); + assert_eq!(event.keys.span(), [0x0123456789ABCDEF].span()); - assert(event.data.len() == 32, 'event should have 32 bytes'); + assert_eq!(event.data.len(), 32); let data_expected = u256_to_bytes_array(Bounded::::MAX).span().slice(0, 32); - assert(event.data.span() == data_expected, 'event data are incorrect'); + assert_eq!(event.data.span(), data_expected); } #[test] @@ -167,20 +167,19 @@ mod tests { let result = vm.exec_log2(); // Then - assert(result.is_ok(), 'should have succeeded'); - assert(vm.stack.len() == 0, 'stack should be empty'); + assert!(result.is_ok()); + assert_eq!(vm.stack.len(), 0); let mut events = vm.env.state.events; - assert(events.len() == 1, 'context should have one event'); + assert_eq!(events.len(), 1); let event = events.pop_front().unwrap(); - assert(event.keys.len() == 2, 'event should have two keys'); - assert(*event.keys[0] == 0x0123456789ABCDEF, 'event key is not correct'); - assert(*event.keys[1] == Bounded::::MAX, 'event key is not correct'); + assert_eq!(event.keys.len(), 2); + assert_eq!(event.keys.span(), [0x0123456789ABCDEF, Bounded::::MAX].span()); - assert(event.data.len() == 5, 'event should have 5 bytes'); + assert_eq!(event.data.len(), 5); let data_expected = u256_to_bytes_array(Bounded::::MAX).span().slice(0, 5); - assert(event.data.span() == data_expected, 'event data are incorrect'); + assert_eq!(event.data.span(), data_expected); } #[test] @@ -205,23 +204,20 @@ mod tests { let result = vm.exec_log3(); // Then - assert(result.is_ok(), 'should have succeeded'); - assert(vm.stack.len() == 0, 'stack should be empty'); + assert!(result.is_ok()); + assert_eq!(vm.stack.len(), 0); let mut events = vm.env.state.events; - assert(events.len() == 1, 'context should have one event'); + assert_eq!(events.len(), 1); let event = events.pop_front().unwrap(); - assert(event.keys.len() == 3, 'event should have 3 keys'); - assert(*event.keys[0] == 0x0123456789ABCDEF, 'event key is not correct'); - assert(*event.keys[1] == Bounded::::MAX, 'event key is not correct'); - assert(*event.keys[2] == 0x00, 'event key is not correct'); + assert_eq!(event.keys.span(), [0x0123456789ABCDEF, Bounded::::MAX, 0x00].span()); - assert(event.data.len() == 40, 'event should have 40 bytes'); + assert_eq!(event.data.len(), 40); let data_expected = u256_to_bytes_array(Bounded::::MAX).span(); - assert(event.data.span().slice(0, 32) == data_expected, 'event data are incorrect'); + assert_eq!(event.data.span().slice(0, 32), data_expected); let data_expected = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF].span(); - assert(event.data.span().slice(32, 8) == data_expected, 'event data are incorrect'); + assert_eq!(event.data.span().slice(32, 8), data_expected); } #[test] @@ -247,22 +243,21 @@ mod tests { let result = vm.exec_log4(); // Then - assert(result.is_ok(), 'should have succeeded'); - assert(vm.stack.len() == 0, 'stack should be empty'); + assert!(result.is_ok()); + assert_eq!(vm.stack.len(), 0); let mut events = vm.env.state.events; - assert(events.len() == 1, 'context should have one event'); + assert_eq!(events.len(), 1); let event = events.pop_front().unwrap(); - assert(event.keys.len() == 4, 'event should have 4 keys'); - assert(*event.keys[0] == 0x0123456789ABCDEF, 'event key is not correct'); - assert(*event.keys[1] == Bounded::::MAX, 'event key is not correct'); - assert(*event.keys[2] == 0x00, 'event key is not correct'); - assert(*event.keys[3] == Bounded::::MAX, 'event key is not correct'); + assert_eq!( + event.keys.span(), + [0x0123456789ABCDEF, Bounded::::MAX, 0x00, Bounded::::MAX].span() + ); - assert(event.data.len() == 10, 'event should have 10 bytes'); + assert_eq!(event.data.len(), 10); let data_expected = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x00, 0x00].span(); - assert(event.data.span() == data_expected, 'event data are incorrect'); + assert_eq!(event.data.span(), data_expected); } #[test] @@ -280,10 +275,8 @@ mod tests { let result = vm.exec_log1(); // Then - assert(result.is_err(), 'should have returned an error'); - assert( - result.unwrap_err() == EVMError::WriteInStaticContext, 'err != WriteInStaticContext' - ); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), EVMError::WriteInStaticContext); } #[test] @@ -301,17 +294,16 @@ mod tests { let result = vm.exec_log1(); // Then - assert(result.is_ok(), 'should have succeeded'); - assert(vm.stack.len() == 0, 'stack should be empty'); + assert!(result.is_ok()); + assert_eq!(vm.stack.len(), 0); let mut events = vm.env.state.events; - assert(events.len() == 1, 'context should have one event'); + assert_eq!(events.len(), 1); let event = events.pop_front().unwrap(); - assert(event.keys.len() == 1, 'event should have one key'); - assert(*event.keys[0] == 0x0123456789ABCDEF, 'event key is not correct'); + assert_eq!(event.keys.span(), [0x0123456789ABCDEF].span()); - assert(event.data.len() == 0, 'event data should be empty'); + assert_eq!(event.data.len(), 0); } #[test] @@ -329,11 +321,8 @@ mod tests { let result = vm.exec_log1(); // Then - assert(result.is_err(), 'should return an error'); - assert( - result.unwrap_err() == EVMError::TypeConversionError(TYPE_CONVERSION_ERROR), - 'err != TypeConversionError' - ); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), EVMError::TypeConversionError(TYPE_CONVERSION_ERROR)); } #[test] @@ -351,11 +340,8 @@ mod tests { let result = vm.exec_log1(); // Then - assert(result.is_err(), 'should return an error'); - assert( - result.unwrap_err() == EVMError::TypeConversionError(TYPE_CONVERSION_ERROR), - 'err != TypeConversionError' - ); + assert!(result.is_err()); + assert_eq!(result.unwrap_err(), EVMError::MemoryLimitOOG); } #[test] @@ -387,32 +373,28 @@ mod tests { vm.exec_log4().expect('exec_log4 failed'); // Then - assert(vm.stack.len() == 0, 'stack size should be 0'); + assert!(vm.stack.is_empty()); let mut events = vm.env.state.events; - assert(events.len() == 2, 'context should have 2 events'); + assert_eq!(events.len(), 2); let event1 = events.pop_front().unwrap(); - assert(event1.keys.len() == 3, 'event1 should have 3 keys'); - assert(*event1.keys[0] == 0x0123456789ABCDEF, 'event1 key is not correct'); - assert(*event1.keys[1] == Bounded::::MAX, 'event1 key is not correct'); - assert(*event1.keys[2] == 0x00, 'event1 key is not correct'); + assert_eq!(event1.keys.span(), [0x0123456789ABCDEF, Bounded::::MAX, 0x00].span()); - assert(event1.data.len() == 40, 'event1 should have 40 bytes'); + assert_eq!(event1.data.len(), 40); let data_expected = u256_to_bytes_array(Bounded::::MAX).span(); - assert(event1.data.span().slice(0, 32) == data_expected, 'event1 data are incorrect'); + assert_eq!(event1.data.span().slice(0, 32), data_expected); let data_expected = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF].span(); - assert(event1.data.span().slice(32, 8) == data_expected, 'event1 data are incorrect'); + assert_eq!(event1.data.span().slice(32, 8), data_expected); let event2 = events.pop_front().unwrap(); - assert(event2.keys.len() == 4, 'event2 should have 4 keys'); - assert(*event2.keys[0] == 0x0123456789ABCDEF, 'event2 key is not correct'); - assert(*event2.keys[1] == Bounded::::MAX, 'event2 key is not correct'); - assert(*event2.keys[2] == 0x00, 'event2 key is not correct'); - assert(*event2.keys[3] == Bounded::::MAX, 'event2 key is not correct'); + assert_eq!( + event2.keys.span(), + [0x0123456789ABCDEF, Bounded::::MAX, 0x00, Bounded::::MAX].span() + ); - assert(event2.data.len() == 10, 'event2 should have 10 bytes'); + assert_eq!(event2.data.len(), 10); let data_expected = [0x01, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xEF, 0x00, 0x00].span(); - assert(event2.data.span() == data_expected, 'event2 data are incorrect'); + assert_eq!(event2.data.span(), data_expected); } } diff --git a/crates/evm/src/instructions/memory_operations.cairo b/crates/evm/src/instructions/memory_operations.cairo index b6c11b0a..3af514df 100644 --- a/crates/evm/src/instructions/memory_operations.cairo +++ b/crates/evm/src/instructions/memory_operations.cairo @@ -64,7 +64,7 @@ pub impl MemoryOperation of MemoryOperationTrait { /// Save single byte to memory /// # Specification: https://www.evm.codes/#53?fork=shanghai fn exec_mstore8(ref self: VM) -> Result<(), EVMError> { - let offset = self.stack.pop_usize()?; + let offset = self.stack.pop_saturating_usize()?; let value = self.stack.pop()?; let value: u8 = (value.low & 0xFF).try_into().unwrap(); @@ -282,7 +282,7 @@ pub impl MemoryOperation of MemoryOperationTrait { /// Copy memory from one location to another. /// # Specification: https://www.evm.codes/#5e?fork=cancun fn exec_mcopy(ref self: VM) -> Result<(), EVMError> { - let dest_offset = self.stack.pop_usize()?; + let dest_offset = self.stack.pop_saturating_usize()?; let source_offset = self.stack.pop_usize()?; let size = self.stack.pop_usize()?; diff --git a/crates/evm/src/instructions/system_operations.cairo b/crates/evm/src/instructions/system_operations.cairo index b0ae2867..d62dbc79 100644 --- a/crates/evm/src/instructions/system_operations.cairo +++ b/crates/evm/src/instructions/system_operations.cairo @@ -28,9 +28,9 @@ pub impl SystemOperations of SystemOperationsTrait { let gas = self.stack.pop_saturating_u64()?; let to = self.stack.pop_eth_address()?; let value = self.stack.pop()?; - let args_offset = self.stack.pop_usize()?; + let args_offset = self.stack.pop_saturating_usize()?; let args_size = self.stack.pop_usize()?; - let ret_offset = self.stack.pop_usize()?; + let ret_offset = self.stack.pop_saturating_usize()?; let ret_size = self.stack.pop_usize()?; // GAS @@ -107,9 +107,9 @@ pub impl SystemOperations of SystemOperationsTrait { let gas = self.stack.pop_saturating_u64()?; let code_address = self.stack.pop_eth_address()?; let value = self.stack.pop()?; - let args_offset = self.stack.pop_usize()?; + let args_offset = self.stack.pop_saturating_usize()?; let args_size = self.stack.pop_usize()?; - let ret_offset = self.stack.pop_usize()?; + let ret_offset = self.stack.pop_saturating_usize()?; let ret_size = self.stack.pop_usize()?; let to = self.message().target.evm; @@ -170,7 +170,7 @@ pub impl SystemOperations of SystemOperationsTrait { /// RETURN /// # Specification: https://www.evm.codes/#f3?fork=shanghai fn exec_return(ref self: VM) -> Result<(), EVMError> { - let offset = self.stack.pop_usize()?; + let offset = self.stack.pop_saturating_usize()?; let size = self.stack.pop_usize()?; let memory_expansion = gas::memory_expansion(self.memory.size(), [(offset, size)].span())?; self.memory.ensure_length(memory_expansion.new_size); @@ -191,9 +191,9 @@ pub impl SystemOperations of SystemOperationsTrait { fn exec_delegatecall(ref self: VM) -> Result<(), EVMError> { let gas = self.stack.pop_saturating_u64()?; let code_address = self.stack.pop_eth_address()?; - let args_offset = self.stack.pop_usize()?; + let args_offset = self.stack.pop_saturating_usize()?; let args_size = self.stack.pop_usize()?; - let ret_offset = self.stack.pop_usize()?; + let ret_offset = self.stack.pop_saturating_usize()?; let ret_size = self.stack.pop_usize()?; // GAS @@ -244,9 +244,9 @@ pub impl SystemOperations of SystemOperationsTrait { fn exec_staticcall(ref self: VM) -> Result<(), EVMError> { let gas = self.stack.pop_saturating_u64()?; let to = self.stack.pop_eth_address()?; - let args_offset = self.stack.pop_usize()?; + let args_offset = self.stack.pop_saturating_usize()?; let args_size = self.stack.pop_usize()?; - let ret_offset = self.stack.pop_usize()?; + let ret_offset = self.stack.pop_saturating_usize()?; let ret_size = self.stack.pop_usize()?; // GAS @@ -287,7 +287,7 @@ pub impl SystemOperations of SystemOperationsTrait { /// REVERT /// # Specification: https://www.evm.codes/#fd?fork=shanghai fn exec_revert(ref self: VM) -> Result<(), EVMError> { - let offset = self.stack.pop_usize()?; + let offset = self.stack.pop_saturating_usize()?; let size = self.stack.pop_usize()?; let memory_expansion = gas::memory_expansion(self.memory.size(), [(offset, size)].span())?; diff --git a/crates/evm/src/stack.cairo b/crates/evm/src/stack.cairo index 4e3ad80a..0037b969 100644 --- a/crates/evm/src/stack.cairo +++ b/crates/evm/src/stack.cairo @@ -36,6 +36,7 @@ pub trait StackTrait { fn push(ref self: Stack, item: u256) -> Result<(), EVMError>; fn pop(ref self: Stack) -> Result; fn pop_usize(ref self: Stack) -> Result; + fn pop_saturating_usize(ref self: Stack) -> Result; fn pop_u64(ref self: Stack) -> Result; fn pop_saturating_u64(ref self: Stack) -> Result; fn pop_u128(ref self: Stack) -> Result; @@ -107,6 +108,19 @@ impl StackImpl of StackTrait { Result::Ok(item) } + /// Calls `Stack::pop` and saturates the result to usize + #[inline(always)] + fn pop_saturating_usize(ref self: Stack) -> Result { + let item: u256 = self.pop()?; + if item.high != 0 { + return Result::Ok(Bounded::::MAX); + }; + match item.low.try_into() { + Option::Some(value) => Result::Ok(value), + Option::None => Result::Ok(Bounded::::MAX), + } + } + /// Calls `Stack::pop` and tries to convert it to u64 /// /// # Errors @@ -420,6 +434,31 @@ mod tests { ); } + #[test] + fn test_pop_saturating_usize_should_return_max_when_overflow() { + // Given + let mut stack = StackTrait::new(); + stack.push(Bounded::::MAX.into()).unwrap(); + + // When + let result = stack.pop_saturating_usize(); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), Bounded::::MAX); + } + + #[test] + fn test_pop_saturating_usize_should_return_value_when_no_overflow() { + // Given + let mut stack = StackTrait::new(); + stack.push(1234567890).unwrap(); + + // When + let result = stack.pop_saturating_usize(); + assert!(result.is_ok()); + assert_eq!(result.unwrap(), 1234567890); + } + + #[test] fn test_pop_saturating_u64_should_return_max_when_overflow() { // Given