diff --git a/core/src/error.rs b/core/src/error.rs index 510d6df4a..0cd0daf72 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -154,6 +154,10 @@ pub enum ExitError { /// Other normal errors. #[cfg_attr(feature = "with-codec", codec(index = 13))] Other(Cow<'static, str>), + + /// Init code exceeds limit (runtime). + #[cfg_attr(feature = "with-codec", codec(index = 7))] + InitCodeLimit, } impl From for ExitReason { diff --git a/core/src/eval/mod.rs b/core/src/eval/mod.rs index 954ca83de..519f5d925 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_push0(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { + self::misc::push(state, 0, position) +} + fn eval_push1(state: &mut Machine, _opcode: Opcode, position: usize) -> Control { self::misc::push(state, 1, position) } @@ -494,6 +498,7 @@ pub fn eval(state: &mut Machine, opcode: Opcode, position: usize) -> Control { table[Opcode::MSIZE.as_usize()] = eval_msize as _; table[Opcode::JUMPDEST.as_usize()] = eval_jumpdest as _; + table[Opcode::PUSH0.as_usize()] = eval_push0 as _; table[Opcode::PUSH1.as_usize()] = eval_push1 as _; table[Opcode::PUSH2.as_usize()] = eval_push2 as _; table[Opcode::PUSH3.as_usize()] = eval_push3 as _; diff --git a/core/src/opcode.rs b/core/src/opcode.rs index c35a6ec5e..15814b0ac 100644 --- a/core/src/opcode.rs +++ b/core/src/opcode.rs @@ -95,6 +95,7 @@ impl Opcode { pub const JUMPDEST: Opcode = Opcode(0x5b); /// `PUSHn` + pub const PUSH0: Opcode = Opcode(0x5f); pub const PUSH1: Opcode = Opcode(0x60); pub const PUSH2: Opcode = Opcode(0x61); pub const PUSH3: Opcode = Opcode(0x62); diff --git a/gasometer/src/lib.rs b/gasometer/src/lib.rs index 564a2d3e0..d41924201 100644 --- a/gasometer/src/lib.rs +++ b/gasometer/src/lib.rs @@ -238,12 +238,17 @@ impl<'config> Gasometer<'config> { non_zero_data_len, access_list_address_len, access_list_storage_len, + initcode_cost, } => { - self.config.gas_transaction_create + let mut cost = self.config.gas_transaction_create + zero_data_len as u64 * self.config.gas_transaction_zero_data + non_zero_data_len as u64 * self.config.gas_transaction_non_zero_data + access_list_address_len as u64 * self.config.gas_access_list_address - + access_list_storage_len as u64 * self.config.gas_access_list_storage_key + + access_list_storage_len as u64 * self.config.gas_access_list_storage_key; + if self.config.max_initcode_size.is_some() { + cost += initcode_cost; + } + cost } }; @@ -293,15 +298,21 @@ pub fn create_transaction_cost(data: &[u8], access_list: &[(H160, Vec)]) - let zero_data_len = data.iter().filter(|v| **v == 0).count(); let non_zero_data_len = data.len() - zero_data_len; let (access_list_address_len, access_list_storage_len) = count_access_list(access_list); + let initcode_cost = init_code_cost(data); TransactionCost::Create { zero_data_len, non_zero_data_len, access_list_address_len, access_list_storage_len, + initcode_cost, } } +pub fn init_code_cost(data: &[u8]) -> u64 { + 2 * (data.len() as u64 / 32) +} + /// Counts the number of addresses and storage keys in the access list fn count_access_list(access_list: &[(H160, Vec)]) -> (usize, usize) { let access_list_address_len = access_list.len(); @@ -610,6 +621,8 @@ pub fn dynamic_opcode_cost( } } + Opcode::PUSH0 if config.has_push0 => GasCost::Base, + _ => GasCost::Invalid(opcode), }; @@ -1021,6 +1034,8 @@ pub enum TransactionCost { access_list_address_len: usize, /// Total number of storage keys in transaction access list (see EIP-2930) access_list_storage_len: usize, + /// Cost of initcode = 2 * ceil(len(initcode) / 32) (see EIP-3860) + initcode_cost: u64, }, } diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 3cf482715..4e82443d0 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -243,6 +243,8 @@ pub struct Config { pub decrease_clears_refund: bool, /// EIP-3541 pub disallow_executable_format: bool, + /// EIP-3651 + pub warm_coinbase_address: bool, /// Whether to throw out of gas error when /// CALL/CALLCODE/DELEGATECALL requires more than maximum amount /// of gas. @@ -261,6 +263,8 @@ pub struct Config { pub call_stack_limit: usize, /// Create contract limit. pub create_contract_limit: Option, + /// EIP-3860, maximum size limit of init_code. + pub max_initcode_size: Option, /// Call stipend. pub call_stipend: u64, /// Has delegate call. @@ -281,6 +285,8 @@ pub struct Config { pub has_ext_code_hash: bool, /// Has ext block fee. See [EIP-3198](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-3198.md) 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, /// Whether the gasometer is running in estimate mode. pub estimate: bool, } @@ -315,6 +321,7 @@ impl Config { increase_state_access_gas: false, decrease_clears_refund: false, disallow_executable_format: false, + warm_coinbase_address: false, err_on_call_with_more_gas: true, empty_considered_exists: true, create_increase_nonce: false, @@ -323,6 +330,7 @@ impl Config { memory_limit: usize::MAX, call_stack_limit: 1024, create_contract_limit: None, + max_initcode_size: None, call_stipend: 2300, has_delegate_call: false, has_create2: false, @@ -333,6 +341,7 @@ impl Config { has_self_balance: false, has_ext_code_hash: false, has_base_fee: false, + has_push0: false, estimate: false, } } @@ -366,6 +375,7 @@ impl Config { increase_state_access_gas: false, decrease_clears_refund: false, disallow_executable_format: false, + warm_coinbase_address: false, err_on_call_with_more_gas: false, empty_considered_exists: false, create_increase_nonce: true, @@ -374,6 +384,7 @@ impl Config { memory_limit: usize::MAX, call_stack_limit: 1024, create_contract_limit: Some(0x6000), + max_initcode_size: None, call_stipend: 2300, has_delegate_call: true, has_create2: true, @@ -384,6 +395,7 @@ impl Config { has_self_balance: true, has_ext_code_hash: true, has_base_fee: false, + has_push0: false, estimate: false, } } @@ -398,6 +410,11 @@ impl Config { Self::config_with_derived_values(DerivedConfigInputs::london()) } + /// Shanghai hard fork configuration. + pub const fn shanghai() -> Config { + Self::config_with_derived_values(DerivedConfigInputs::shanghai()) + } + const fn config_with_derived_values(inputs: DerivedConfigInputs) -> Config { let DerivedConfigInputs { gas_storage_read_warm, @@ -405,7 +422,10 @@ impl Config { gas_access_list_storage_key, decrease_clears_refund, has_base_fee, + has_push0, disallow_executable_format, + warm_coinbase_address, + max_initcode_size, } = inputs; // See https://eips.ethereum.org/EIPS/eip-2929 @@ -447,6 +467,7 @@ impl Config { increase_state_access_gas: true, decrease_clears_refund, disallow_executable_format, + warm_coinbase_address, err_on_call_with_more_gas: false, empty_considered_exists: false, create_increase_nonce: true, @@ -455,6 +476,7 @@ impl Config { memory_limit: usize::MAX, call_stack_limit: 1024, create_contract_limit: Some(0x6000), + max_initcode_size, call_stipend: 2300, has_delegate_call: true, has_create2: true, @@ -465,6 +487,7 @@ impl Config { has_self_balance: true, has_ext_code_hash: true, has_base_fee, + has_push0, estimate: false, } } @@ -478,7 +501,10 @@ struct DerivedConfigInputs { gas_access_list_storage_key: u64, decrease_clears_refund: bool, has_base_fee: bool, + has_push0: bool, disallow_executable_format: bool, + warm_coinbase_address: bool, + max_initcode_size: Option, } impl DerivedConfigInputs { @@ -489,7 +515,10 @@ impl DerivedConfigInputs { gas_access_list_storage_key: 1900, decrease_clears_refund: false, has_base_fee: false, + has_push0: false, disallow_executable_format: false, + warm_coinbase_address: false, + max_initcode_size: None, } } @@ -500,7 +529,24 @@ impl DerivedConfigInputs { gas_access_list_storage_key: 1900, decrease_clears_refund: true, has_base_fee: true, + has_push0: false, + disallow_executable_format: true, + warm_coinbase_address: false, + max_initcode_size: None, + } + } + + const fn shanghai() -> 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, + max_initcode_size: Some(0xC000), } } } diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index 6f04d06ce..add4675b5 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -399,6 +399,23 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> gasometer.record_transaction(transaction_cost) } + fn maybe_record_init_code_cost(&mut self, init_code: &[u8]) -> Result<(), ExitError> { + if let Some(limit) = self.config.max_initcode_size { + // EIP-3860 + if init_code.len() > limit { + self.state.metadata_mut().gasometer.fail(); + let _ = self.exit_substate(StackExitKind::Failed); + return Err(ExitError::OutOfGas); + } + return self + .state + .metadata_mut() + .gasometer + .record_cost(gasometer::init_code_cost(init_code)); + } + Ok(()) + } + /// Execute a `CREATE` transaction. pub fn transact_create( &mut self, @@ -416,6 +433,14 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> address: self.create_address(CreateScheme::Legacy { caller }), }); + if let Some(limit) = self.config.max_initcode_size { + if init_code.len() > limit { + self.state.metadata_mut().gasometer.fail(); + let _ = self.exit_substate(StackExitKind::Failed); + return emit_exit!(ExitError::InitCodeLimit.into(), Vec::new()); + } + } + if let Err(e) = self.record_create_transaction_cost(&init_code, &access_list) { return emit_exit!(e.into(), Vec::new()); } @@ -449,6 +474,14 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> gas_limit: u64, access_list: Vec<(H160, Vec)>, // See EIP-2930 ) -> (ExitReason, Vec) { + if let Some(limit) = self.config.max_initcode_size { + if init_code.len() > limit { + self.state.metadata_mut().gasometer.fail(); + let _ = self.exit_substate(StackExitKind::Failed); + return emit_exit!(ExitError::InitCodeLimit.into(), Vec::new()); + } + } + let code_hash = H256::from_slice(Keccak256::digest(&init_code).as_slice()); event!(TransactCreate2 { caller, @@ -522,8 +555,16 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> // Initialize initial addresses for EIP-2929 if self.config.increase_state_access_gas { - let addresses = core::iter::once(caller).chain(core::iter::once(address)); - self.state.metadata_mut().access_addresses(addresses); + if self.config.warm_coinbase_address { + // Warm coinbase address for EIP-3651 + let addresses = core::iter::once(caller) + .chain(core::iter::once(address)) + .chain(core::iter::once(self.block_coinbase())); + self.state.metadata_mut().access_addresses(addresses); + } else { + let addresses = core::iter::once(caller).chain(core::iter::once(address)); + self.state.metadata_mut().access_addresses(addresses); + } self.initialize_with_access_list(access_list); } @@ -1095,6 +1136,12 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Handler init_code: Vec, target_gas: Option, ) -> Capture<(ExitReason, Option, Vec), Self::CreateInterrupt> { + if let Err(e) = self.maybe_record_init_code_cost(&init_code) { + let reason: ExitReason = e.into(); + emit_exit!(reason.clone()); + return Capture::Exit((reason, None, Vec::new())); + } + self.create_inner(caller, scheme, value, init_code, target_gas, true) } @@ -1107,6 +1154,12 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Handler init_code: Vec, target_gas: Option, ) -> Capture<(ExitReason, Option, Vec), Self::CreateInterrupt> { + if let Err(e) = self.maybe_record_init_code_cost(&init_code) { + let reason: ExitReason = e.into(); + emit_exit!(reason.clone()); + return Capture::Exit((reason, None, Vec::new())); + } + let capture = self.create_inner(caller, scheme, value, init_code, target_gas, true); if let Capture::Exit((ref reason, _, ref return_value)) = capture {