Skip to content

Commit

Permalink
feat: tload and tstore
Browse files Browse the repository at this point in the history
  • Loading branch information
obatirou committed Aug 27, 2024
1 parent 11ac169 commit a5bb49d
Show file tree
Hide file tree
Showing 2 changed files with 120 additions and 1 deletion.
86 changes: 86 additions & 0 deletions crates/evm/src/instructions/memory_operations.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<u32> = array![0xFF, 0xEE];
Expand Down
35 changes: 34 additions & 1 deletion crates/evm/src/state.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,10 @@ struct State {
events: Array<Event>,
/// Pending transfers
transfers: Array<Transfer>,
/// 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]
Expand Down Expand Up @@ -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(),
}
}

Expand Down Expand Up @@ -574,7 +595,6 @@ mod tests {
assert(balance == read_balance, 'Balance mismatch');
}


#[test]
fn test_read_balance_from_storage() {
// Transfer native tokens to sender
Expand All @@ -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');
}
}
}

0 comments on commit a5bb49d

Please sign in to comment.