diff --git a/interpreter/src/etable.rs b/interpreter/src/etable.rs index a11c2bde..1e7d3d82 100644 --- a/interpreter/src/etable.rs +++ b/interpreter/src/etable.rs @@ -304,6 +304,9 @@ where 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 238f572e..c3554053 100644 --- a/interpreter/src/eval/mod.rs +++ b/interpreter/src/eval/mod.rs @@ -674,6 +674,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) +} + macro_rules! eval_log { ($($num:expr),*) => { $(paste::paste! { diff --git a/interpreter/src/eval/system.rs b/interpreter/src/eval/system.rs index 4a30b907..2521fe58 100644 --- a/interpreter/src/eval/system.rs +++ b/interpreter/src/eval/system.rs @@ -295,6 +295,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 b6003ff5..337cec15 100644 --- a/interpreter/src/opcode.rs +++ b/interpreter/src/opcode.rs @@ -236,6 +236,11 @@ impl Opcode { /// `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 ed9056cb..992099e0 100644 --- a/interpreter/src/runtime.rs +++ b/interpreter/src/runtime.rs @@ -115,6 +115,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; @@ -140,6 +142,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 5a8f5e4a..d174e82a 100644 --- a/interpreter/tests/usability.rs +++ b/interpreter/tests/usability.rs @@ -114,6 +114,9 @@ impl RuntimeBaseBackend for UnimplementedHandler { fn storage(&self, _address: H160, _index: H256) -> H256 { unimplemented!() } + fn transient_storage(&self, _address: H160, _index: H256) -> H256 { + unimplemented!() + } fn exists(&self, _address: H160) -> bool { unimplemented!() @@ -143,6 +146,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 f25c60a8..63c84ef8 100644 --- a/jsontests/src/in_memory.rs +++ b/jsontests/src/in_memory.rs @@ -25,6 +25,7 @@ pub struct InMemoryAccount { pub code: Vec, pub nonce: U256, pub storage: BTreeMap, + pub transient_storage: BTreeMap, } #[derive(Clone, Debug)] @@ -66,6 +67,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); } @@ -146,6 +157,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 68080cf0..fcc649f8 100644 --- a/jsontests/src/run.rs +++ b/jsontests/src/run.rs @@ -144,6 +144,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 20bf960f..c8fe2296 100644 --- a/src/backend/overlayed.rs +++ b/src/backend/overlayed.rs @@ -21,6 +21,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, } @@ -49,6 +50,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, }, ) @@ -118,6 +120,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 @@ -157,6 +167,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(()) @@ -243,6 +265,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); } @@ -260,6 +287,7 @@ struct Substate { nonces: BTreeMap, storage_resets: BTreeSet, storages: BTreeMap<(H160, H256), H256>, + transient_storage: BTreeMap<(H160, H256), H256>, deletes: BTreeSet, } @@ -273,6 +301,7 @@ impl Substate { nonces: Default::default(), storage_resets: Default::default(), storages: Default::default(), + transient_storage: Default::default(), deletes: Default::default(), } } @@ -319,6 +348,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/config.rs b/src/standard/config.rs index 63300bab..5cb65b5d 100644 --- a/src/standard/config.rs +++ b/src/standard/config.rs @@ -97,6 +97,8 @@ pub struct Config { pub has_base_fee: bool, /// Has PUSH0 opcode. See [EIP-3855](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3855.md) pub has_push0: bool, + /// Enables transient storage. See [EIP-1153](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1153.md) + pub eip_1153_enabled: bool, } impl Config { @@ -150,6 +152,7 @@ impl Config { has_ext_code_hash: false, has_base_fee: false, has_push0: false, + eip_1153_enabled: false, } } @@ -203,6 +206,7 @@ impl Config { has_ext_code_hash: true, has_base_fee: false, has_push0: false, + eip_1153_enabled: false, } } @@ -237,6 +241,7 @@ impl Config { disallow_executable_format, warm_coinbase_address, max_initcode_size, + eip_1153_enabled, } = inputs; // See https://eips.ethereum.org/EIPS/eip-2929 @@ -299,6 +304,7 @@ impl Config { has_ext_code_hash: true, has_base_fee, has_push0, + eip_1153_enabled, } } } @@ -315,6 +321,7 @@ struct DerivedConfigInputs { disallow_executable_format: bool, warm_coinbase_address: bool, max_initcode_size: Option, + eip_1153_enabled: bool, } impl DerivedConfigInputs { @@ -329,6 +336,7 @@ impl DerivedConfigInputs { disallow_executable_format: false, warm_coinbase_address: false, max_initcode_size: None, + eip_1153_enabled: false, } } @@ -343,6 +351,7 @@ impl DerivedConfigInputs { disallow_executable_format: true, warm_coinbase_address: false, max_initcode_size: None, + eip_1153_enabled: false, } } @@ -357,6 +366,7 @@ impl DerivedConfigInputs { disallow_executable_format: true, warm_coinbase_address: false, max_initcode_size: None, + eip_1153_enabled: false, } } @@ -372,6 +382,7 @@ impl DerivedConfigInputs { warm_coinbase_address: true, // 2 * 24576 as per EIP-3860 max_initcode_size: Some(0xC000), + eip_1153_enabled: false, } } } diff --git a/src/standard/gasometer/costs.rs b/src/standard/gasometer/costs.rs index cbf611ff..92586e4d 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_storage_read_warm) +} + +pub fn tstore_cost(config: &Config) -> Result { + Ok(config.gas_storage_read_warm) +} 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 2b09bda0..5d6355f0 100644 --- a/src/standard/gasometer/mod.rs +++ b/src/standard/gasometer/mod.rs @@ -366,6 +366,7 @@ fn dynamic_opcode_cost( target_is_cold: handler.is_cold(address, Some(index)), } } + Opcode::TLOAD if config.eip_1153_enabled => GasCost::TLoad, Opcode::DELEGATECALL if config.has_delegate_call => { let target = stack.peek(1)?.into(); @@ -386,7 +387,6 @@ fn dynamic_opcode_cost( Opcode::SSTORE if !is_static => { let index = stack.peek(0)?; let value = stack.peek(1)?; - GasCost::SStore { original: handler.original_storage(address, index), current: handler.storage(address, index), @@ -394,6 +394,7 @@ fn dynamic_opcode_cost( target_is_cold: handler.is_cold(address, Some(index)), } } + Opcode::TSTORE if !is_static && config.eip_1153_enabled => GasCost::TStore, Opcode::LOG0 if !is_static => GasCost::Log { n: 0, len: U256::from_big_endian(&stack.peek(1)?[..]), @@ -605,6 +606,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. @@ -701,7 +706,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)?,