diff --git a/crates/evm/src/instructions/memory_operations.cairo b/crates/evm/src/instructions/memory_operations.cairo index 02e8f6432..fdbfaf3d2 100644 --- a/crates/evm/src/instructions/memory_operations.cairo +++ b/crates/evm/src/instructions/memory_operations.cairo @@ -250,6 +250,33 @@ impl MemoryOperation of MemoryOperationTrait { Result::Ok(()) } + /// 0x5c - TLOAD operation. + /// Load a word from the transient storage. + /// # Specification: https://www.evm.codes/#5c?fork=cancun + fn exec_tload(ref self: VM) -> Result<(), EVMError> { + let key = self.stack.pop()?; + let evm_address = self.message().target.evm; + + self.charge_gas(gas::WARM_ACCESS_COST)?; + + let value = self.env.state.read_transient_storage(evm_address, key); + self.stack.push(value) + } + + /// 0x5d - TSTORE operation. + /// Save a word to the transient storage. + /// # Specification: https://www.evm.codes/#5d?fork=cancun + fn exec_tstore(ref self: VM) -> Result<(), EVMError> { + ensure(!self.message().read_only, EVMError::WriteInStaticContext)?; + let key = self.stack.pop()?; + let value = self.stack.pop()?; + + self.charge_gas(gas::WARM_ACCESS_COST)?; + self.env.state.write_transient_storage(self.message().target.evm, key, value); + + return Result::Ok(()); + } + /// 0x5e - MCOPY operation. /// Copy memory from one location to another. /// # Specification: https://www.evm.codes/#5e?fork=cancun @@ -940,6 +967,65 @@ mod tests { assert(result == vm.gas_left().into(), 'stack top should be gas_limit'); } + #[test] + fn test_tload_should_load_a_value_from_transient_storage() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + let key: u256 = 0x100000000000000000000000000000001; + let value: u256 = 0xABDE1E11A5; + vm.env.state.write_transient_storage(vm.message().target.evm, key, value); + vm.stack.push(key.into()).expect('push failed'); + + // When + let gas_before = vm.gas_left(); + vm.exec_tload().expect('exec_tload failed'); + let gas_after = vm.gas_left(); + + // Then + assert(vm.stack.len() == 1, 'stack should have one element'); + assert(vm.stack.pop().unwrap() == value, 'tload failed'); + assert(gas_before - gas_after == gas::WARM_ACCESS_COST, 'gas charged error'); + } + + #[test] + fn test_tstore_should_fail_staticcall() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().with_read_only().build(); + let key: u256 = 0x100000000000000000000000000000001; + let value: u256 = 0xABDE1E11A5; + vm.stack.push(value).expect('push failed'); + vm.stack.push(key.into()).expect('push failed'); + + // When + let result = vm.exec_tstore(); + + // Then + assert(result.is_err(), 'should have errored'); + assert(result.unwrap_err() == EVMError::WriteInStaticContext, 'wrong error returned'); + } + + #[test] + fn test_tstore_should_store_a_value_to_transient_storage() { + // Given + let mut vm = VMBuilderTrait::new_with_presets().build(); + let key: u256 = 0x100000000000000000000000000000001; + let value: u256 = 0xABDE1E11A5; + vm.stack.push(value).expect('push failed'); + vm.stack.push(key.into()).expect('push failed'); + + // When + let gas_before = vm.gas_left(); + vm.exec_tstore().expect('exec_tstore failed'); + let gas_after = vm.gas_left(); + + // Then + assert( + vm.env.state.read_transient_storage(vm.message().target.evm, key) == value, + 'tstore failed' + ); + assert(gas_before - gas_after == gas::WARM_ACCESS_COST, 'gas charged error'); + } + #[test] fn test_exec_mcopy_should_copy_two_words_at_destination_offset() { let values: Array = array![0xFF, 0xEE]; diff --git a/crates/evm/src/state.cairo b/crates/evm/src/state.cairo index 350e6a9ec..1151975dd 100644 --- a/crates/evm/src/state.cairo +++ b/crates/evm/src/state.cairo @@ -113,6 +113,10 @@ struct State { events: Array, /// Pending transfers transfers: Array, + /// Account transient storage states. `EthAddress` indicates the target contract, + /// `u256` indicates the storage key. + /// `u256` indicates the value stored. + transient_account_storage: StateChangeLog<(EthAddress, u256, u256)>, } #[generate_trait] @@ -184,12 +188,29 @@ impl StateImpl of StateTrait { Result::Ok(()) } + #[inline(always)] + fn read_transient_storage(ref self: State, evm_address: EthAddress, key: u256) -> u256 { + let internal_key = compute_state_key(evm_address, key); + let maybe_entry = self.transient_account_storage.read(internal_key); + match maybe_entry { + Option::Some((_, _, value)) => { return value; }, + Option::None => { return 0; } + } + } + + #[inline(always)] + fn write_transient_storage(ref self: State, evm_address: EthAddress, key: u256, value: u256) { + let internal_key = compute_state_key(evm_address, key); + self.transient_account_storage.write(internal_key.into(), (evm_address, key, value)); + } + fn clone(ref self: State) -> State { State { accounts: self.accounts.clone(), accounts_storage: self.accounts_storage.clone(), events: self.events.clone(), transfers: self.transfers.clone(), + transient_account_storage: self.transient_account_storage.clone(), } } @@ -574,7 +595,6 @@ mod tests { assert(balance == read_balance, 'Balance mismatch'); } - #[test] fn test_read_balance_from_storage() { // Transfer native tokens to sender @@ -592,5 +612,18 @@ mod tests { assert(read_balance == 10000, 'Balance mismatch'); } + + #[test] + fn test_write_read_transient_storage() { + let mut state: State = Default::default(); + let evm_address: EthAddress = test_utils::evm_address(); + let key = 10; + let value = 100; + + state.write_transient_storage(evm_address, key, value); + let read_value = state.read_transient_storage(evm_address, key); + + assert(value == read_value, 'Transient storage mismatch'); + } } }