From ee159556fcc19cb349a6f4f12984c9deaa9248e2 Mon Sep 17 00:00:00 2001 From: Rodrigo Quelhas Date: Wed, 1 May 2024 19:39:34 +0100 Subject: [PATCH] feat: support transient storage opcodes (EIP-1153) --- interpreter/src/etable.rs | 2 + interpreter/src/eval/mod.rs | 18 +++++++ interpreter/src/eval/system.rs | 29 +++++++--- interpreter/src/opcode.rs | 4 ++ interpreter/src/runtime.rs | 9 ++++ interpreter/tests/usability.rs | 8 +++ jsontests/src/in_memory.rs | 22 ++++++++ jsontests/src/run.rs | 1 + src/backend/overlayed.rs | 39 ++++++++++++++ src/standard/gasometer/costs.rs | 7 +++ src/standard/gasometer/mod.rs | 95 +++++++++++++++++++++++++-------- 11 files changed, 206 insertions(+), 28 deletions(-) diff --git a/interpreter/src/etable.rs b/interpreter/src/etable.rs index 45aa98917..7778c6bda 100644 --- a/interpreter/src/etable.rs +++ b/interpreter/src/etable.rs @@ -282,6 +282,8 @@ where table.0[Opcode::SLOAD.as_usize()] = eval_sload as _; table.0[Opcode::SSTORE.as_usize()] = eval_sstore as _; table.0[Opcode::GAS.as_usize()] = eval_gas as _; + table.0[Opcode::TLOAD.as_usize()] = eval_tload as _; + table.0[Opcode::TSTORE.as_usize()] = eval_tstore as _; table.0[Opcode::LOG0.as_usize()] = eval_log0 as _; table.0[Opcode::LOG1.as_usize()] = eval_log1 as _; table.0[Opcode::LOG2.as_usize()] = eval_log2 as _; diff --git a/interpreter/src/eval/mod.rs b/interpreter/src/eval/mod.rs index ffd7aa296..bfc4cffa9 100644 --- a/interpreter/src/eval/mod.rs +++ b/interpreter/src/eval/mod.rs @@ -1200,6 +1200,24 @@ pub fn eval_gas( self::system::gas(machine, handle) } +pub fn eval_tload, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::tload(machine, handle) +} + +pub fn eval_tstore, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handle: &mut H, + _opcode: Opcode, + _position: usize, +) -> Control { + self::system::tstore(machine, handle) +} + pub fn eval_log0, H: RuntimeEnvironment + RuntimeBackend, Tr>( machine: &mut Machine, handle: &mut H, diff --git a/interpreter/src/eval/system.rs b/interpreter/src/eval/system.rs index 88256632a..a43e382e9 100644 --- a/interpreter/src/eval/system.rs +++ b/interpreter/src/eval/system.rs @@ -47,7 +47,6 @@ pub fn balance, H: RuntimeEnvironment + RuntimeBackend, T handler: &mut H, ) -> Control { pop!(machine, address); - handler.mark_hot(address.into(), None); push_u256!(machine, handler.balance(address.into())); Control::Continue @@ -127,7 +126,6 @@ pub fn extcodesize, H: RuntimeEnvironment + RuntimeBacken handler: &mut H, ) -> Control { pop!(machine, address); - handler.mark_hot(address.into(), None); let code_size = handler.code_size(address.into()); push_u256!(machine, code_size); @@ -139,7 +137,6 @@ pub fn extcodehash, H: RuntimeEnvironment + RuntimeBacken handler: &mut H, ) -> Control { pop!(machine, address); - handler.mark_hot(address.into(), None); let code_hash = handler.code_hash(address.into()); push!(machine, code_hash); @@ -152,8 +149,6 @@ pub fn extcodecopy, H: RuntimeEnvironment + RuntimeBacken ) -> Control { pop!(machine, address); pop_u256!(machine, memory_offset, code_offset, len); - - handler.mark_hot(address.into(), None); try_or_fail!(machine.memory.resize_offset(memory_offset, len)); let code = handler.code(address.into()); @@ -263,7 +258,6 @@ pub fn sload, H: RuntimeEnvironment + RuntimeBackend, Tr> handler: &mut H, ) -> Control { pop!(machine, index); - handler.mark_hot(machine.state.as_ref().context.address, Some(index)); let value = handler.storage(machine.state.as_ref().context.address, index); push!(machine, value); @@ -275,7 +269,6 @@ pub fn sstore, H: RuntimeEnvironment + RuntimeBackend, Tr handler: &mut H, ) -> Control { pop!(machine, index, value); - handler.mark_hot(machine.state.as_ref().context.address, Some(index)); match handler.set_storage(machine.state.as_ref().context.address, index, value) { Ok(()) => Control::Continue, @@ -292,6 +285,28 @@ pub fn gas( Control::Continue } +pub fn tload, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handler: &mut H, +) -> Control { + pop!(machine, index); + let value = handler.transient_storage(machine.state.as_ref().context.address, index); + push!(machine, value); + + Control::Continue +} + +pub fn tstore, H: RuntimeEnvironment + RuntimeBackend, Tr>( + machine: &mut Machine, + handler: &mut H, +) -> Control { + pop!(machine, index, value); + match handler.set_transient_storage(machine.state.as_ref().context.address, index, value) { + Ok(()) => Control::Continue, + Err(e) => Control::Exit(e.into()), + } +} + pub fn log, H: RuntimeEnvironment + RuntimeBackend, Tr>( machine: &mut Machine, n: u8, diff --git a/interpreter/src/opcode.rs b/interpreter/src/opcode.rs index b4d0a4746..4f389d739 100644 --- a/interpreter/src/opcode.rs +++ b/interpreter/src/opcode.rs @@ -225,6 +225,10 @@ impl Opcode { pub const SSTORE: Opcode = Opcode(0x55); /// `GAS` pub const GAS: Opcode = Opcode(0x5a); + /// `TLOAD` + pub const TLOAD: Opcode = Opcode(0x5c); + /// `TSTORE` + pub const TSTORE: Opcode = Opcode(0x5d); /// `LOGn` pub const LOG0: Opcode = Opcode(0xa0); pub const LOG1: Opcode = Opcode(0xa1); diff --git a/interpreter/src/runtime.rs b/interpreter/src/runtime.rs index 7dbce8a49..d1b2c5696 100644 --- a/interpreter/src/runtime.rs +++ b/interpreter/src/runtime.rs @@ -113,6 +113,8 @@ pub trait RuntimeBaseBackend { fn code(&self, address: H160) -> Vec; /// Get storage value of address at index. fn storage(&self, address: H160, index: H256) -> H256; + /// Get transient storage value of address at index. + fn transient_storage(&self, address: H160, index: H256) -> H256; /// Check whether an address exists. fn exists(&self, address: H160) -> bool; @@ -138,6 +140,13 @@ pub trait RuntimeBackend: RuntimeBaseBackend { fn mark_hot(&mut self, address: H160, index: Option); /// Set storage value of address at index. fn set_storage(&mut self, address: H160, index: H256, value: H256) -> Result<(), ExitError>; + /// Set transient storage value of address at index, transient storage gets discarded after every transaction. (see EIP-1153) + fn set_transient_storage( + &mut self, + address: H160, + index: H256, + value: H256, + ) -> Result<(), ExitError>; /// Create a log owned by address with given topics and data. fn log(&mut self, log: Log) -> Result<(), ExitError>; /// Mark an address to be deleted. diff --git a/interpreter/tests/usability.rs b/interpreter/tests/usability.rs index 02421a987..c51ac8098 100644 --- a/interpreter/tests/usability.rs +++ b/interpreter/tests/usability.rs @@ -137,6 +137,14 @@ impl RuntimeBackend for UnimplementedHandler { fn set_storage(&mut self, _address: H160, _index: H256, _value: H256) -> Result<(), ExitError> { unimplemented!() } + fn set_transient_storage( + &mut self, + _address: H160, + _index: H256, + _value: H256, + ) -> Result<(), ExitError> { + unimplemented!() + } fn log(&mut self, _log: Log) -> Result<(), ExitError> { unimplemented!() } diff --git a/jsontests/src/in_memory.rs b/jsontests/src/in_memory.rs index 05fc87e69..1bda6b0bf 100644 --- a/jsontests/src/in_memory.rs +++ b/jsontests/src/in_memory.rs @@ -21,6 +21,7 @@ pub struct InMemoryAccount { pub code: Vec, pub nonce: U256, pub storage: BTreeMap, + pub transient_storage: BTreeMap, } #[derive(Clone, Debug)] @@ -62,6 +63,16 @@ impl InMemoryBackend { } } + for ((address, key), value) in changeset.transient_storage.clone() { + let account = self.state.entry(address).or_default(); + + if value == H256::default() { + account.transient_storage.remove(&key); + } else { + account.transient_storage.insert(key, value); + } + } + for address in changeset.deletes.clone() { self.state.remove(&address); } @@ -142,6 +153,17 @@ impl RuntimeBaseBackend for InMemoryBackend { .unwrap_or(H256::default()) } + fn transient_storage(&self, address: H160, index: H256) -> H256 { + self.state + .get(&address) + .cloned() + .unwrap_or(Default::default()) + .transient_storage + .get(&index) + .cloned() + .unwrap_or(H256::default()) + } + fn nonce(&self, address: H160) -> U256 { self.state .get(&address) diff --git a/jsontests/src/run.rs b/jsontests/src/run.rs index 5fa6e8c6a..9808a222d 100644 --- a/jsontests/src/run.rs +++ b/jsontests/src/run.rs @@ -138,6 +138,7 @@ pub fn run_test( code: account.code.0, nonce: account.nonce, storage, + transient_storage: Default::default(), }, ) }) diff --git a/src/backend/overlayed.rs b/src/backend/overlayed.rs index 7ffd43df9..398009913 100644 --- a/src/backend/overlayed.rs +++ b/src/backend/overlayed.rs @@ -18,6 +18,7 @@ pub struct OverlayedChangeSet { pub nonces: BTreeMap, pub storage_resets: BTreeSet, pub storages: BTreeMap<(H160, H256), H256>, + pub transient_storage: BTreeMap<(H160, H256), H256>, pub deletes: BTreeSet, } @@ -46,6 +47,7 @@ impl OverlayedBackend { nonces: self.substate.nonces, storage_resets: self.substate.storage_resets, storages: self.substate.storages, + transient_storage: self.substate.transient_storage, deletes: self.substate.deletes, }, ) @@ -115,6 +117,14 @@ impl RuntimeBaseBackend for OverlayedBackend { } } + fn transient_storage(&self, address: H160, index: H256) -> H256 { + if let Some(value) = self.substate.known_transient_storage(address, index) { + value + } else { + self.backend.transient_storage(address, index) + } + } + fn exists(&self, address: H160) -> bool { if let Some(exists) = self.substate.known_exists(address) { exists @@ -154,6 +164,18 @@ impl RuntimeBackend for OverlayedBackend { Ok(()) } + fn set_transient_storage( + &mut self, + address: H160, + index: H256, + value: H256, + ) -> Result<(), ExitError> { + self.substate + .transient_storage + .insert((address, index), value); + Ok(()) + } + fn log(&mut self, log: Log) -> Result<(), ExitError> { self.substate.logs.push(log); Ok(()) @@ -240,6 +262,11 @@ impl TransactionalBackend for OverlayedBackend { for ((address, key), value) in child.storages { self.substate.storages.insert((address, key), value); } + for ((address, key), value) in child.transient_storage { + self.substate + .transient_storage + .insert((address, key), value); + } for address in child.deletes { self.substate.deletes.insert(address); } @@ -257,6 +284,7 @@ struct Substate { nonces: BTreeMap, storage_resets: BTreeSet, storages: BTreeMap<(H160, H256), H256>, + transient_storage: BTreeMap<(H160, H256), H256>, deletes: BTreeSet, } @@ -270,6 +298,7 @@ impl Substate { nonces: Default::default(), storage_resets: Default::default(), storages: Default::default(), + transient_storage: Default::default(), deletes: Default::default(), } } @@ -316,6 +345,16 @@ impl Substate { } } + pub fn known_transient_storage(&self, address: H160, key: H256) -> Option { + if let Some(value) = self.transient_storage.get(&(address, key)) { + Some(*value) + } else if let Some(parent) = self.parent.as_ref() { + parent.known_transient_storage(address, key) + } else { + None + } + } + pub fn known_exists(&self, address: H160) -> Option { if self.balances.contains_key(&address) || self.nonces.contains_key(&address) diff --git a/src/standard/gasometer/costs.rs b/src/standard/gasometer/costs.rs index dda497e66..3bc54bc37 100644 --- a/src/standard/gasometer/costs.rs +++ b/src/standard/gasometer/costs.rs @@ -241,6 +241,13 @@ pub fn sstore_cost( }, ) } +pub fn tload_cost(config: &Config) -> Result { + Ok(config.gas_sload) +} + +pub fn tstore_cost(config: &Config) -> Result { + Ok(config.gas_sload) +} pub fn suicide_cost(value: U256, is_cold: bool, target_exists: bool, config: &Config) -> u64 { let eip161 = !config.empty_considered_exists; diff --git a/src/standard/gasometer/mod.rs b/src/standard/gasometer/mod.rs index a526de53b..8d3a5024c 100644 --- a/src/standard/gasometer/mod.rs +++ b/src/standard/gasometer/mod.rs @@ -278,7 +278,7 @@ fn dynamic_opcode_cost( stack: &Stack, is_static: bool, config: &Config, - handler: &H, + handler: &mut H, ) -> Result<(GasCost, Option), ExitError> { let gas_cost = match opcode { Opcode::RETURN => GasCost::Zero, @@ -302,40 +302,59 @@ fn dynamic_opcode_cost( Opcode::EXTCODESIZE => { let target = stack.peek(0)?.into(); - GasCost::ExtCodeSize { - target_is_cold: handler.is_cold(target, None), - } + + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(target, None); + handler.mark_hot(target, None); + + GasCost::ExtCodeSize { target_is_cold } } Opcode::BALANCE => { let target = stack.peek(0)?.into(); - GasCost::Balance { - target_is_cold: handler.is_cold(target, None), - } + + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(target, None); + handler.mark_hot(target, None); + + GasCost::Balance { target_is_cold } } Opcode::BLOCKHASH => GasCost::BlockHash, Opcode::EXTCODEHASH if config.has_ext_code_hash => { let target = stack.peek(0)?.into(); - GasCost::ExtCodeHash { - target_is_cold: handler.is_cold(target, None), - } + + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(target, None); + handler.mark_hot(target, None); + + GasCost::ExtCodeHash { target_is_cold } } Opcode::EXTCODEHASH => GasCost::Invalid(opcode), Opcode::CALLCODE => { let target = stack.peek(1)?.into(); + + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(target, None); + handler.mark_hot(target, None); + GasCost::CallCode { value: U256::from_big_endian(&stack.peek(2)?[..]), gas: U256::from_big_endian(&stack.peek(0)?[..]), - target_is_cold: handler.is_cold(target, None), + target_is_cold, target_exists: { handler.exists(target) }, } } Opcode::STATICCALL => { let target = stack.peek(1)?.into(); + + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(target, None); + handler.mark_hot(target, None); + GasCost::StaticCall { gas: U256::from_big_endian(&stack.peek(0)?[..]), - target_is_cold: handler.is_cold(target, None), + target_is_cold, target_exists: { handler.exists(target) }, } } @@ -344,8 +363,13 @@ fn dynamic_opcode_cost( }, Opcode::EXTCODECOPY => { let target = stack.peek(0)?.into(); + + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(target, None); + handler.mark_hot(target, None); + GasCost::ExtCodeCopy { - target_is_cold: handler.is_cold(target, None), + target_is_cold, len: U256::from_big_endian(&stack.peek(3)?[..]), } } @@ -357,16 +381,25 @@ fn dynamic_opcode_cost( }, Opcode::SLOAD => { let index = stack.peek(0)?; - GasCost::SLoad { - target_is_cold: handler.is_cold(address, Some(index)), - } + + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(address, Some(index)); + handler.mark_hot(address, Some(index)); + + GasCost::SLoad { target_is_cold } } + Opcode::TLOAD => GasCost::TLoad, Opcode::DELEGATECALL if config.has_delegate_call => { let target = stack.peek(1)?.into(); + + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(target, None); + handler.mark_hot(target, None); + GasCost::DelegateCall { gas: U256::from_big_endian(&stack.peek(0)?[..]), - target_is_cold: handler.is_cold(target, None), + target_is_cold, target_exists: { handler.exists(target) }, } } @@ -382,13 +415,18 @@ fn dynamic_opcode_cost( let index = stack.peek(0)?; let value = stack.peek(1)?; + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(address, Some(index)); + handler.mark_hot(address, Some(index)); + GasCost::SStore { original: handler.original_storage(address, index), current: handler.storage(address, index), new: value, - target_is_cold: handler.is_cold(address, Some(index)), + target_is_cold, } } + Opcode::TSTORE if !is_static => GasCost::TStore, Opcode::LOG0 if !is_static => GasCost::Log { n: 0, len: U256::from_big_endian(&stack.peek(1)?[..]), @@ -415,9 +453,14 @@ fn dynamic_opcode_cost( }, Opcode::SUICIDE if !is_static => { let target = stack.peek(0)?.into(); + + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(target, None); + handler.mark_hot(target, None); + GasCost::Suicide { value: handler.balance(address), - target_is_cold: handler.is_cold(target, None), + target_is_cold, target_exists: { handler.exists(target) }, already_removed: handler.deleted(address), } @@ -427,10 +470,15 @@ fn dynamic_opcode_cost( || (is_static && U256::from_big_endian(&stack.peek(2)?[..]) == U256::zero()) => { let target = stack.peek(1)?.into(); + + // https://eips.ethereum.org/EIPS/eip-2929 + let target_is_cold = handler.is_cold(target, None); + handler.mark_hot(target, None); + GasCost::Call { value: U256::from_big_endian(&stack.peek(2)?[..]), gas: U256::from_big_endian(&stack.peek(0)?[..]), - target_is_cold: handler.is_cold(target, None), + target_is_cold, target_exists: { handler.exists(target) }, } } @@ -600,6 +648,10 @@ enum GasCost { /// True if target has not been previously accessed in this transaction target_is_cold: bool, }, + /// Gas cost for `TLOAD`. + TLoad, + /// Gas cost for `TSTORE`. + TStore, /// Gas cost for `SHA3`. Sha3 { /// Length of the data. @@ -696,7 +748,8 @@ impl GasCost { new, target_is_cold, } => costs::sstore_cost(original, current, new, gas, target_is_cold, config)?, - + GasCost::TLoad => costs::tload_cost(config)?, + GasCost::TStore => costs::tstore_cost(config)?, GasCost::Sha3 { len } => costs::sha3_cost(len)?, GasCost::Log { n, len } => costs::log_cost(n, len)?, GasCost::VeryLowCopy { len } => costs::verylowcopy_cost(len)?,