diff --git a/core/src/eval/misc.rs b/core/src/eval/misc.rs index 398bcc34c..685981b66 100644 --- a/core/src/eval/misc.rs +++ b/core/src/eval/misc.rs @@ -1,6 +1,6 @@ use super::Control; use crate::{ExitError, ExitFatal, ExitRevert, ExitSucceed, Machine}; -use core::cmp::min; +use core::cmp::{max, min}; use primitive_types::{H256, U256}; #[inline] @@ -92,6 +92,23 @@ pub fn mload(state: &mut Machine) -> Control { Control::Continue(1) } +/// Support for EIP-5656: MCOPY instruction. +#[inline] +pub fn mcopy(state: &mut Machine) -> Control { + pop_u256!(state, dst, src, len); + try_or_fail!(state.memory.resize_offset(max(dst, src), len)); + + if len.is_zero() { + return Control::Continue(1); + } + + let dst = as_usize_or_fail!(dst); + let src = as_usize_or_fail!(src); + let len = as_usize_or_fail!(len); + state.memory.copy(dst, src, len); + Control::Continue(1) +} + #[inline] pub fn mstore(state: &mut Machine) -> Control { pop_u256!(state, index); diff --git a/core/src/eval/mod.rs b/core/src/eval/mod.rs index 519f5d925..51b222de6 100644 --- a/core/src/eval/mod.rs +++ b/core/src/eval/mod.rs @@ -176,6 +176,10 @@ fn eval_jumpdest(_state: &mut Machine, _opcode: Opcode, _position: usize) -> Con Control::Continue(1) } +fn eval_mcopy(state: &mut Machine, _opcode: Opcode, _position: usize) -> Control { + self::misc::mcopy(state) +} + fn eval_push0(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { self::misc::push(state, 0, position) } @@ -497,6 +501,7 @@ pub fn eval(state: &mut Machine, opcode: Opcode, position: usize) -> Control { table[Opcode::PC.as_usize()] = eval_pc as _; table[Opcode::MSIZE.as_usize()] = eval_msize as _; table[Opcode::JUMPDEST.as_usize()] = eval_jumpdest as _; + table[Opcode::MCOPY.as_usize()] = eval_mcopy as _; table[Opcode::PUSH0.as_usize()] = eval_push0 as _; table[Opcode::PUSH1.as_usize()] = eval_push1 as _; diff --git a/core/src/memory.rs b/core/src/memory.rs index 7bb09629b..1f1d51d51 100644 --- a/core/src/memory.rs +++ b/core/src/memory.rs @@ -1,6 +1,6 @@ use crate::{ExitError, ExitFatal}; use alloc::vec::Vec; -use core::cmp::min; +use core::cmp::{max, min}; use core::ops::{BitAnd, Not}; use primitive_types::U256; @@ -181,6 +181,15 @@ impl Memory { self.set(memory_offset, data, Some(ulen)) } + + /// Copies part of the memory inside another part of itself. + pub fn copy(&mut self, dst: usize, src: usize, len: usize) { + let resize_offset = max(dst, src); + if self.data.len() < resize_offset + len { + self.data.resize(resize_offset + len, 0); + } + self.data.copy_within(src..src + len, dst); + } } /// Rounds up `x` to the closest multiple of 32. If `x % 32 == 0` then `x` is returned. @@ -192,7 +201,7 @@ fn next_multiple_of_32(x: U256) -> Option { #[cfg(test)] mod tests { - use super::{next_multiple_of_32, U256}; + use super::{next_multiple_of_32, Memory, U256}; #[test] fn test_next_multiple_of_32() { @@ -225,4 +234,49 @@ mod tests { } } } + + #[test] + fn test_memory_copy_works() { + // Create a new instance of memory + let mut memory = Memory::new(100usize); + + // Set the [0,0,0,1,2,3,4] array as memory data. + // + // We insert the [1,2,3,4] array on index 3, + // that's why we have the zero padding at the beginning. + memory.set(3usize, &[1u8, 2u8, 3u8, 4u8], None).unwrap(); + assert_eq!(memory.data(), &[0u8, 0u8, 0u8, 1u8, 2u8, 3u8, 4u8].to_vec()); + + // Copy 1 byte into index 0. + // As the length is 1, we only copy the byte present on index 3. + memory.copy(0usize, 3usize, 1usize); + + // Now the new memory data results in [1,0,0,1,2,3,4] + assert_eq!(memory.data(), &[1u8, 0u8, 0u8, 1u8, 2u8, 3u8, 4u8].to_vec()); + } + + #[test] + fn test_memory_copy_resize() { + // Create a new instance of memory + let mut memory = Memory::new(100usize); + + // Set the [0,0,0,1,2,3,4] array as memory data. + // + // We insert the [1,2,3,4] array on index 3, + // that's why we have the zero padding at the beginning. + memory.set(3usize, &[1u8, 2u8, 3u8, 4u8], None).unwrap(); + assert_eq!(memory.data(), &[0u8, 0u8, 0u8, 1u8, 2u8, 3u8, 4u8].to_vec()); + + // Copy 2 bytes into index 3. + // As the length is 2, we copy the bytes present on indexes 6 and 7, + // which are [4,0]. + memory.copy(3usize, 6usize, 2usize); + + // Now the new memory data results in [0, 0, 0, 4, 0, 3, 4, 0]. + // An extra element is added due to rezising. + assert_eq!( + memory.data(), + &[0u8, 0u8, 0u8, 4u8, 0u8, 3u8, 4u8, 0u8].to_vec() + ); + } } diff --git a/core/src/opcode.rs b/core/src/opcode.rs index 15814b0ac..97cd18f3a 100644 --- a/core/src/opcode.rs +++ b/core/src/opcode.rs @@ -93,6 +93,8 @@ impl Opcode { pub const MSIZE: Opcode = Opcode(0x59); /// `JUMPDEST` pub const JUMPDEST: Opcode = Opcode(0x5b); + /// `MCOPY` + pub const MCOPY: Opcode = Opcode(0x5e); /// `PUSHn` pub const PUSH0: Opcode = Opcode(0x5f); @@ -225,6 +227,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/gasometer/src/costs.rs b/gasometer/src/costs.rs index c25f5db9c..2ea134739 100644 --- a/gasometer/src/costs.rs +++ b/gasometer/src/costs.rs @@ -245,6 +245,14 @@ 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; let should_charge_topup = if eip161 { diff --git a/gasometer/src/lib.rs b/gasometer/src/lib.rs index 8e4e6a143..62942f1f3 100644 --- a/gasometer/src/lib.rs +++ b/gasometer/src/lib.rs @@ -586,7 +586,7 @@ pub fn dynamic_opcode_cost( len: U256::from_big_endian(&stack.peek(3)?[..]), } } - Opcode::CALLDATACOPY | Opcode::CODECOPY => GasCost::VeryLowCopy { + Opcode::CALLDATACOPY | Opcode::CODECOPY | Opcode::MCOPY => GasCost::VeryLowCopy { len: U256::from_big_endian(&stack.peek(2)?[..]), }, Opcode::EXP => GasCost::Exp { @@ -599,6 +599,7 @@ pub fn dynamic_opcode_cost( target_is_cold: handler.is_cold(address, Some(index))?, } } + Opcode::TLOAD => GasCost::TLoad, Opcode::DELEGATECALL if config.has_delegate_call => { let target = stack.peek(1)?.into(); @@ -632,6 +633,7 @@ pub fn dynamic_opcode_cost( target_is_cold: handler.is_cold(address, Some(index))?, } } + Opcode::TSTORE if !is_static => GasCost::TStore, Opcode::LOG0 if !is_static => GasCost::Log { n: 0, len: U256::from_big_endian(&stack.peek(1)?[..]), @@ -704,6 +706,16 @@ pub fn dynamic_opcode_cost( len: U256::from_big_endian(&stack.peek(1)?[..]), }), + Opcode::MCOPY => { + let top0 = U256::from_big_endian(&stack.peek(0)?[..]); + let top1 = U256::from_big_endian(&stack.peek(1)?[..]); + let offset = top0.max(top1); + Some(MemoryCost { + offset, + len: U256::from_big_endian(&stack.peek(2)?[..]), + }) + } + Opcode::CODECOPY | Opcode::CALLDATACOPY | Opcode::RETURNDATACOPY => Some(MemoryCost { offset: U256::from_big_endian(&stack.peek(0)?[..]), len: U256::from_big_endian(&stack.peek(2)?[..]), @@ -867,6 +879,9 @@ impl<'config> Inner<'config> { target_is_cold, } => costs::sstore_cost(original, current, new, gas, target_is_cold, self.config)?, + GasCost::TLoad => costs::tload_cost(self.config)?, + GasCost::TStore => costs::tstore_cost(self.config)?, + GasCost::Sha3 { len } => costs::sha3_cost(len)?, GasCost::Log { n, len } => costs::log_cost(n, len)?, GasCost::VeryLowCopy { len } => costs::verylowcopy_cost(len)?, @@ -1053,6 +1068,10 @@ pub 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, } /// Storage opcode will access. Used for tracking accessed storage (EIP-2929). diff --git a/runtime/src/eval/mod.rs b/runtime/src/eval/mod.rs index b2965335f..f26a7def3 100644 --- a/runtime/src/eval/mod.rs +++ b/runtime/src/eval/mod.rs @@ -45,6 +45,8 @@ pub fn eval(state: &mut Runtime, opcode: Opcode, handler: &mut H) -> Opcode::SLOAD => system::sload(state, handler), Opcode::SSTORE => system::sstore(state, handler), Opcode::GAS => system::gas(state, handler), + Opcode::TLOAD => system::tload(state, handler), + Opcode::TSTORE => system::tstore(state, handler), Opcode::LOG0 => system::log(state, 0, handler), Opcode::LOG1 => system::log(state, 1, handler), Opcode::LOG2 => system::log(state, 2, handler), diff --git a/runtime/src/eval/system.rs b/runtime/src/eval/system.rs index adf6bffd1..b22e5dc77 100644 --- a/runtime/src/eval/system.rs +++ b/runtime/src/eval/system.rs @@ -252,6 +252,23 @@ pub fn gas(runtime: &mut Runtime, handler: &H) -> Control { Control::Continue } +pub fn tload(runtime: &mut Runtime, handler: &mut H) -> Control { + pop!(runtime, index); + let value = handler.transient_storage(runtime.context.address, index); + push!(runtime, value); + + Control::Continue +} + +pub fn tstore(runtime: &mut Runtime, handler: &mut H) -> Control { + pop!(runtime, index, value); + + match handler.set_transient_storage(runtime.context.address, index, value) { + Ok(()) => Control::Continue, + Err(e) => Control::Exit(e.into()), + } +} + pub fn log(runtime: &mut Runtime, n: u8, handler: &mut H) -> Control { pop_u256!(runtime, offset, len); diff --git a/runtime/src/handler.rs b/runtime/src/handler.rs index 10eb976c7..02b02ccd5 100644 --- a/runtime/src/handler.rs +++ b/runtime/src/handler.rs @@ -35,6 +35,9 @@ pub trait Handler { 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; + /// Get original storage value of address at index. fn original_storage(&self, address: H160, index: H256) -> H256; @@ -77,6 +80,13 @@ pub trait Handler { /// 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, address: H160, topics: Vec, data: Vec) -> Result<(), ExitError>; /// Mark an address to be deleted, with funds transferred to target. diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 8809e58f3..5d5bcc061 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -288,6 +288,8 @@ pub struct Config { pub has_push0: bool, /// Whether the gasometer is running in estimate mode. pub estimate: bool, + /// Has EIP-6780. See [EIP-6780](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-6780.md) + pub has_eip_6780: bool, } impl Config { @@ -342,6 +344,7 @@ impl Config { has_base_fee: false, has_push0: false, estimate: false, + has_eip_6780: false, } } @@ -396,6 +399,7 @@ impl Config { has_base_fee: false, has_push0: false, estimate: false, + has_eip_6780: false, } } @@ -419,6 +423,11 @@ impl Config { Self::config_with_derived_values(DerivedConfigInputs::shanghai()) } + /// Cancun hard fork configuration. + pub const fn cancun() -> Config { + Self::config_with_derived_values(DerivedConfigInputs::cancun()) + } + const fn config_with_derived_values(inputs: DerivedConfigInputs) -> Config { let DerivedConfigInputs { gas_storage_read_warm, @@ -430,6 +439,7 @@ impl Config { disallow_executable_format, warm_coinbase_address, max_initcode_size, + has_eip_6780, } = inputs; // See https://eips.ethereum.org/EIPS/eip-2929 @@ -493,6 +503,7 @@ impl Config { has_base_fee, has_push0, estimate: false, + has_eip_6780, } } } @@ -509,6 +520,7 @@ struct DerivedConfigInputs { disallow_executable_format: bool, warm_coinbase_address: bool, max_initcode_size: Option, + has_eip_6780: bool, } impl DerivedConfigInputs { @@ -523,6 +535,7 @@ impl DerivedConfigInputs { disallow_executable_format: false, warm_coinbase_address: false, max_initcode_size: None, + has_eip_6780: false, } } @@ -537,6 +550,7 @@ impl DerivedConfigInputs { disallow_executable_format: true, warm_coinbase_address: false, max_initcode_size: None, + has_eip_6780: false, } } @@ -551,6 +565,7 @@ impl DerivedConfigInputs { disallow_executable_format: true, warm_coinbase_address: false, max_initcode_size: None, + has_eip_6780: false, } } @@ -566,6 +581,23 @@ impl DerivedConfigInputs { warm_coinbase_address: true, // 2 * 24576 as per EIP-3860 max_initcode_size: Some(0xC000), + has_eip_6780: false, + } + } + + const fn cancun() -> Self { + Self { + gas_storage_read_warm: 100, + gas_sload_cold: 2100, + gas_access_list_storage_key: 1900, + decrease_clears_refund: true, + has_base_fee: true, + has_push0: true, + disallow_executable_format: true, + warm_coinbase_address: true, + // 2 * 24576 as per EIP-3860 + max_initcode_size: Some(0xC000), + has_eip_6780: true, } } } diff --git a/src/backend/memory.rs b/src/backend/memory.rs index 37e7718b1..631c9aa96 100644 --- a/src/backend/memory.rs +++ b/src/backend/memory.rs @@ -61,6 +61,8 @@ pub struct MemoryAccount { pub struct MemoryBackend<'vicinity> { vicinity: &'vicinity MemoryVicinity, state: BTreeMap, + /// Account transient storage (discarded after every transaction. (see EIP-1153)) + transient_storage: BTreeMap<(H160, H256), H256>, logs: Vec, } @@ -70,6 +72,7 @@ impl<'vicinity> MemoryBackend<'vicinity> { Self { vicinity, state, + transient_storage: Default::default(), logs: Vec::new(), } } @@ -157,6 +160,13 @@ impl<'vicinity> Backend for MemoryBackend<'vicinity> { .unwrap_or_default() } + fn transient_storage(&self, address: H160, index: H256) -> H256 { + self.transient_storage + .get(&(address, index)) + .copied() + .unwrap_or_default() + } + fn original_storage(&self, address: H160, index: H256) -> Option { Some(self.storage(address, index)) } diff --git a/src/backend/mod.rs b/src/backend/mod.rs index 297f475f7..97532750d 100644 --- a/src/backend/mod.rs +++ b/src/backend/mod.rs @@ -81,6 +81,8 @@ pub trait Backend { 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; /// Get original storage value of address at index, if available. fn original_storage(&self, address: H160, index: H256) -> Option; } diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index 95b3e175b..1860d3136 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -201,14 +201,17 @@ pub trait StackState<'config>: Backend { fn is_empty(&self, address: H160) -> bool; fn deleted(&self, address: H160) -> bool; + fn created(&self, address: H160) -> bool; fn is_cold(&self, address: H160) -> bool; fn is_storage_cold(&self, address: H160, key: H256) -> bool; fn inc_nonce(&mut self, address: H160) -> Result<(), ExitError>; fn set_storage(&mut self, address: H160, key: H256, value: H256); + fn set_transient_storage(&mut self, address: H160, key: H256, value: H256); fn reset_storage(&mut self, address: H160); fn log(&mut self, address: H160, topics: Vec, data: Vec); fn set_deleted(&mut self, address: H160); + fn set_created(&mut self, address: H160); fn set_code(&mut self, address: H160, code: Vec); fn transfer(&mut self, transfer: Transfer) -> Result<(), ExitError>; fn reset_balance(&mut self, address: H160); @@ -738,6 +741,8 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> return Capture::Exit((e.into(), None, Vec::new())); } + self.state.set_created(address); + let after_gas = if take_l64 && self.config.call_l64_after_gas { if self.config.estimate { let initial_after_gas = self.state.metadata().gasometer.gas(); @@ -1110,6 +1115,10 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Handler self.state.storage(address, index) } + fn transient_storage(&self, address: H160, index: H256) -> H256 { + self.state.transient_storage(address, index) + } + fn original_storage(&self, address: H160, index: H256) -> H256 { self.state .original_storage(address, index) @@ -1197,6 +1206,16 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Handler Ok(()) } + fn set_transient_storage( + &mut self, + address: H160, + index: H256, + value: H256, + ) -> Result<(), ExitError> { + self.state.set_transient_storage(address, index, value); + Ok(()) + } + fn log(&mut self, address: H160, topics: Vec, data: Vec) -> Result<(), ExitError> { self.state.log(address, topics, data); Ok(()) @@ -1211,13 +1230,23 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Handler balance, }); - self.state.transfer(Transfer { - source: address, - target, - value: balance, - })?; - self.state.reset_balance(address); - self.state.set_deleted(address); + if self.config.has_eip_6780 && !self.state.created(address) { + if address != target { + self.state.transfer(Transfer { + source: address, + target, + value: balance, + })?; + } + } else { + self.state.transfer(Transfer { + source: address, + target, + value: balance, + })?; + self.state.reset_balance(address); + self.state.set_deleted(address); + } Ok(()) } diff --git a/src/executor/stack/memory.rs b/src/executor/stack/memory.rs index d9658f033..7de7eff4e 100644 --- a/src/executor/stack/memory.rs +++ b/src/executor/stack/memory.rs @@ -23,7 +23,9 @@ pub struct MemoryStackSubstate<'config> { logs: Vec, accounts: BTreeMap, storages: BTreeMap<(H160, H256), H256>, + transient_storage: BTreeMap<(H160, H256), H256>, deletes: BTreeSet, + creates: BTreeSet, } impl<'config> MemoryStackSubstate<'config> { @@ -34,7 +36,9 @@ impl<'config> MemoryStackSubstate<'config> { logs: Vec::new(), accounts: BTreeMap::new(), storages: BTreeMap::new(), + transient_storage: BTreeMap::new(), deletes: BTreeSet::new(), + creates: BTreeSet::new(), } } @@ -119,7 +123,9 @@ impl<'config> MemoryStackSubstate<'config> { logs: Vec::new(), accounts: BTreeMap::new(), storages: BTreeMap::new(), + transient_storage: BTreeMap::new(), deletes: BTreeSet::new(), + creates: BTreeSet::new(), }; mem::swap(&mut entering, self); @@ -151,6 +157,7 @@ impl<'config> MemoryStackSubstate<'config> { self.accounts.append(&mut exited.accounts); self.storages.append(&mut exited.storages); + self.transient_storage.append(&mut exited.transient_storage); self.deletes.append(&mut exited.deletes); Ok(()) @@ -246,6 +253,16 @@ impl<'config> MemoryStackSubstate<'config> { None } + 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 is_cold(&self, address: H160) -> bool { self.recursive_is_cold(&|a| a.accessed_addresses.contains(&address)) } @@ -278,6 +295,18 @@ impl<'config> MemoryStackSubstate<'config> { false } + pub fn created(&self, address: H160) -> bool { + if self.creates.contains(&address) { + return true; + } + + if let Some(parent) = self.parent.as_ref() { + return parent.created(address); + } + + false + } + #[allow(clippy::map_entry)] fn account_mut(&mut self, address: H160, backend: &B) -> &mut MemoryStackAccount { if !self.accounts.contains_key(&address) { @@ -314,6 +343,10 @@ impl<'config> MemoryStackSubstate<'config> { self.storages.insert((address, key), value); } + pub fn set_transient_storage(&mut self, address: H160, key: H256, value: H256) { + self.transient_storage.insert((address, key), value); + } + pub fn reset_storage(&mut self, address: H160, backend: &B) { let mut removing = Vec::new(); @@ -342,6 +375,10 @@ impl<'config> MemoryStackSubstate<'config> { self.deletes.insert(address); } + pub fn set_created(&mut self, address: H160) { + self.creates.insert(address); + } + pub fn set_code(&mut self, address: H160, code: Vec, backend: &B) { self.account_mut(address, backend).code = Some(code); } @@ -463,6 +500,12 @@ impl<'backend, 'config, B: Backend> Backend for MemoryStackState<'backend, 'conf .unwrap_or_else(|| self.backend.storage(address, key)) } + fn transient_storage(&self, address: H160, index: H256) -> H256 { + self.substate + .known_transient_storage(address, index) + .unwrap_or_else(|| self.backend.transient_storage(address, index)) + } + fn original_storage(&self, address: H160, key: H256) -> Option { if let Some(value) = self.substate.known_original_storage(address) { return Some(value); @@ -511,6 +554,10 @@ impl<'backend, 'config, B: Backend> StackState<'config> for MemoryStackState<'ba self.substate.deleted(address) } + fn created(&self, address: H160) -> bool { + self.substate.created(address) + } + fn is_cold(&self, address: H160) -> bool { self.substate.is_cold(address) } @@ -527,6 +574,10 @@ impl<'backend, 'config, B: Backend> StackState<'config> for MemoryStackState<'ba self.substate.set_storage(address, key, value) } + fn set_transient_storage(&mut self, address: H160, key: H256, value: H256) { + self.substate.set_transient_storage(address, key, value) + } + fn reset_storage(&mut self, address: H160) { self.substate.reset_storage(address, self.backend); } @@ -539,6 +590,10 @@ impl<'backend, 'config, B: Backend> StackState<'config> for MemoryStackState<'ba self.substate.set_deleted(address) } + fn set_created(&mut self, address: H160) { + self.substate.set_created(address) + } + fn set_code(&mut self, address: H160, code: Vec) { self.substate.set_code(address, code, self.backend); }