diff --git a/gasometer/src/lib.rs b/gasometer/src/lib.rs index 8e4e6a143..cc76e8f23 100644 --- a/gasometer/src/lib.rs +++ b/gasometer/src/lib.rs @@ -172,6 +172,14 @@ impl<'config> Gasometer<'config> { Ok(()) } + #[inline] + /// Check gas limit before `CREATE` code deposit. (See EIP-2) + pub fn can_deposit(&self, len: usize) -> bool { + let cost = len as u64 * consts::G_CODEDEPOSIT; + let all_gas_cost = self.total_used_gas() + cost; + self.gas_limit >= all_gas_cost + } + #[inline] /// Record `CREATE` code deposit. pub fn record_deposit(&mut self, len: usize) -> Result<(), ExitError> { diff --git a/runtime/src/lib.rs b/runtime/src/lib.rs index 8809e58f3..6bb18e836 100644 --- a/runtime/src/lib.rs +++ b/runtime/src/lib.rs @@ -252,6 +252,8 @@ pub struct Config { pub call_l64_after_gas: bool, /// Whether empty account is considered exists. pub empty_considered_exists: bool, + /// Create empty contract if there's no gas for paying code deposit. + pub can_create_empty_contract: bool, /// Whether create transactions and create opcode increases nonce by one. pub create_increase_nonce: bool, /// Stack limit. @@ -278,6 +280,8 @@ pub struct Config { pub has_bitwise_shifting: bool, /// Has chain ID. pub has_chain_id: bool, + /// Has static_call. See [EIP-214](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-214.md) + pub has_static_call: bool, /// Has self balance. pub has_self_balance: bool, /// Has ext code hash. @@ -323,6 +327,7 @@ impl Config { warm_coinbase_address: false, err_on_call_with_more_gas: true, empty_considered_exists: true, + can_create_empty_contract: true, create_increase_nonce: false, call_l64_after_gas: false, stack_limit: 1024, @@ -337,6 +342,7 @@ impl Config { has_return_data: false, has_bitwise_shifting: false, has_chain_id: false, + has_static_call: false, has_self_balance: false, has_ext_code_hash: false, has_base_fee: false, @@ -345,6 +351,101 @@ impl Config { } } + /// Homestead hard fork configuration. + /// mainnet block number: 1,150,000 + pub const fn homestead() -> Config { + let mut config = Self::frontier(); + // makes edits to contract creation process + // see [EIP-2](https://eips.ethereum.org/EIPS/eip-2) + config.gas_transaction_create = 53_000; + config.can_create_empty_contract = false; + config.empty_considered_exists = true; + + // Enable delegate call opcode + // see [EIP-7](https://eips.ethereum.org/EIPS/eip-7) + config.has_delegate_call = true; + config + } + + /// Tangerine whistle hard fork configuration. + pub const fn tangerine_whistle() -> Config { + let mut config = Self::homestead(); + // Gas cost changes for IO-heavy operations + // see [EIP-150](https://eips.ethereum.org/EIPS/eip-150) + config.gas_ext_code = 700; + config.gas_balance = 400; + config.gas_sload = 200; + config.gas_call = 700; + config.gas_suicide = 5_000; + config.gas_suicide_new_account = 25_000; + config.err_on_call_with_more_gas = false; + config.call_l64_after_gas = true; + config + } + + /// Spurious Dragon hard fork configuration. + pub const fn spurious_dragon() -> Config { + let mut config = Self::tangerine_whistle(); + // EXP cost increase + // see [EIP-160](https://eips.ethereum.org/EIPS/eip-160) + config.gas_expbyte = 50; + + // State trie clearing + // - An account is considered empty when it has no code and zero nonce and zero balance. It should be noted + // that very few state changes can ultimately result in accounts that are empty following the execution of the transaction: + // - an empty account has zero value transferred to it through CALL; + // - an empty account has zero value transferred to it through SUICIDE; + // - an empty account has zero value transferred to it through a message-call transaction; + // - an empty account has zero value transferred to it through a zero-gas-price fees transfer. + // - empty account deletions are reverted when the state is reverted. + // - An account is considered dead when either it is non-existent or it is empty. + // see [EIP-161](https://eips.ethereum.org/EIPS/eip-161) + config.create_increase_nonce = true; + config.empty_considered_exists = false; + + // Contract code size limit + // see [EIP-170](https://eips.ethereum.org/EIPS/eip-170) + config.create_contract_limit = Some(0x6000); + config + } + + /// Byzantium hard fork configuration. + pub const fn byzantium() -> Config { + let mut config = Self::spurious_dragon(); + + // adds REVERT opcode + // see [EIP-140](https://eips.ethereum.org/EIPS/eip-140) + config.has_revert = true; + + // adds support for variable length return values + // see [EIP-211](https://eips.ethereum.org/EIPS/eip-211) + config.has_return_data = true; + + // adds STATICCALL opcode, allowing non-state-changing calls to other contracts. + // see [EIP-214](https://eips.ethereum.org/EIPS/eip-214) + config.has_static_call = true; + config + } + + /// Constantinople hard fork configuration. + pub const fn constantinople() -> Config { + let mut config = Self::byzantium(); + + // adds Bitwise shifting instructions + // see [EIP-145](https://eips.ethereum.org/EIPS/eip-145) + config.has_bitwise_shifting = true; + + // allows you to interact with addresses that have yet to be created. + // see [EIP-1014](https://eips.ethereum.org/EIPS/eip-1014) + config.has_create2 = true; + + // adds EXTCODEHASH opcode + // see [EIP-1052](https://eips.ethereum.org/EIPS/eip-1052) + config.has_ext_code_hash = true; + config.gas_ext_code_hash = 400; + config + } + /// Istanbul hard fork configuration. pub const fn istanbul() -> Config { Config { @@ -377,6 +478,7 @@ impl Config { warm_coinbase_address: false, err_on_call_with_more_gas: false, empty_considered_exists: false, + can_create_empty_contract: false, create_increase_nonce: true, call_l64_after_gas: true, stack_limit: 1024, @@ -391,6 +493,7 @@ impl Config { has_return_data: true, has_bitwise_shifting: true, has_chain_id: true, + has_static_call: true, has_self_balance: true, has_ext_code_hash: true, has_base_fee: false, @@ -474,6 +577,7 @@ impl Config { warm_coinbase_address, err_on_call_with_more_gas: false, empty_considered_exists: false, + can_create_empty_contract: false, create_increase_nonce: true, call_l64_after_gas: true, stack_limit: 1024, @@ -488,6 +592,7 @@ impl Config { has_return_data: true, has_bitwise_shifting: true, has_chain_id: true, + has_static_call: true, has_self_balance: true, has_ext_code_hash: true, has_base_fee, diff --git a/src/executor/stack/executor.rs b/src/executor/stack/executor.rs index e35bae13b..ae8b1083b 100644 --- a/src/executor/stack/executor.rs +++ b/src/executor/stack/executor.rs @@ -1002,20 +1002,35 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> } } - match self - .state - .metadata_mut() - .gasometer - .record_deposit(out.len()) + // Before EIP-2 is possible to create empty contract + let bytecode = if self.config.can_create_empty_contract + && !self.state.metadata().gasometer.can_deposit(out.len()) { - Ok(()) => { + Ok(None) + } else { + self.state + .metadata_mut() + .gasometer + .record_deposit(out.len()) + .map(|_| Some(out)) + }; + + match bytecode { + Ok(Some(bytecode)) => { let exit_result = self.exit_substate(StackExitKind::Succeeded); if let Err(e) = self.record_external_operation( - crate::ExternalOperation::Write(U256::from(out.len())), + crate::ExternalOperation::Write(U256::from(bytecode.len())), ) { return (e.into(), None, Vec::new()); } - self.state.set_code(address, out); + self.state.set_code(address, bytecode); + if let Err(e) = exit_result { + return (e.into(), None, Vec::new()); + } + (ExitReason::Succeed(s), Some(address), Vec::new()) + } + Ok(None) => { + let exit_result = self.exit_substate(StackExitKind::Succeeded); if let Err(e) = exit_result { return (e.into(), None, Vec::new()); } @@ -1320,6 +1335,7 @@ impl<'config, 'precompiles, S: StackState<'config>, P: PrecompileSet> Handler stack: &Stack, ) -> Result<(), ExitError> { // log::trace!(target: "evm", "Running opcode: {:?}, Pre gas-left: {:?}", opcode, gasometer.gas()); + // log::trace!(target: "evm", "Running opcode: {:?}, Pre gas-left: {:?}", opcode, self.state.metadata().gasometer.gas()); if let Some(cost) = gasometer::static_opcode_cost(opcode) { self.state.metadata_mut().gasometer.record_cost(cost)?;