diff --git a/Cargo.lock b/Cargo.lock index e9fe52db7..ccb225e0e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1505,6 +1505,7 @@ version = "0.1.0" dependencies = [ "bit-set", "criterion 0.2.11", + "ethcore-builtin", "ethereum-types 0.4.2", "heapsize", "hex-literal", diff --git a/ethcore/evm/Cargo.toml b/ethcore/evm/Cargo.toml index b5f4d0685..e59bf7e6d 100644 --- a/ethcore/evm/Cargo.toml +++ b/ethcore/evm/Cargo.toml @@ -15,6 +15,7 @@ vm = { path = "../vm" } keccak-hash = "0.1" parking_lot = "0.7" memory-cache = { path = "../../util/memory-cache" } +ethcore-builtin = { path = "../builtin" } num-bigint = "0.2" [dev-dependencies] diff --git a/ethcore/evm/benches/basic.rs b/ethcore/evm/benches/basic.rs index 9d0b1575b..f6b7fa472 100644 --- a/ethcore/evm/benches/basic.rs +++ b/ethcore/evm/benches/basic.rs @@ -33,7 +33,7 @@ use criterion::{black_box, Bencher, Criterion}; use ethereum_types::{Address, U256}; use evm::Factory; use rustc_hex::FromHex; -use std::{str::FromStr, sync::Arc}; +use std::{collections::BTreeMap, str::FromStr, sync::Arc}; use vm::{tests::FakeExt, ActionParams, Ext, GasLeft, Result}; criterion_group!( diff --git a/ethcore/evm/src/interpreter/gasometer.rs b/ethcore/evm/src/interpreter/gasometer.rs index 8bb514aac..8a710069e 100644 --- a/ethcore/evm/src/interpreter/gasometer.rs +++ b/ethcore/evm/src/interpreter/gasometer.rs @@ -15,9 +15,10 @@ // along with OpenEthereum. If not, see . use super::u256_to_address; -use ethereum_types::{H256, U256}; +use ethereum_types::{Address, H256, U256}; use std::cmp; +use super::stack::VecStack; use evm; use instructions::{self, Instruction, InstructionInfo}; use interpreter::stack::Stack; @@ -115,26 +116,38 @@ impl Gasometer { ext: &dyn vm::Ext, instruction: Instruction, info: &InstructionInfo, - stack: &dyn Stack, + stack: &VecStack, + current_address: &Address, current_mem_size: usize, ) -> vm::Result> { let schedule = ext.schedule(); + let tier = info.tier.idx(); let default_gas = Gas::from(schedule.tier_step_gas[tier]); + let accessed_addresses_gas = |addr: &Address, cold_cost: usize| -> Gas { + if ext.al_contains_address(addr) { + schedule.warm_storage_read_cost.into() + } else { + cold_cost.into() + } + }; + let cost = match instruction { instructions::JUMPDEST => Request::Gas(Gas::from(1)), instructions::SSTORE => { if schedule.eip1706 && self.current_gas <= Gas::from(schedule.call_stipend) { return Err(vm::Error::OutOfGas); } - let address = H256::from(stack.peek(0)); + let key = H256::from(stack.peek(0)); let newval = stack.peek(1); - let val = U256::from(&*ext.storage_at(&address)?); + let val = U256::from(&*ext.storage_at(&key)?); + + let is_cold = !ext.al_contains_storage_key(current_address, &key); let gas = if schedule.eip1283 { - let orig = U256::from(&*ext.initial_storage_at(&address)?); - calculate_eip1283_sstore_gas(schedule, &orig, &val, &newval) + let orig = U256::from(&*ext.initial_storage_at(&key)?); + calculate_eip1283_eip2929_sstore_gas(schedule, is_cold, &orig, &val, &newval) } else { if val.is_zero() && !newval.is_zero() { schedule.sstore_set_gas @@ -144,17 +157,40 @@ impl Gasometer { schedule.sstore_reset_gas } }; - Request::Gas(Gas::from(gas)) + + Request::Gas(gas.into()) + } + instructions::SLOAD => { + let key = H256::from(stack.peek(0)); + let gas = if ext.al_is_enabled() { + if ext.al_contains_storage_key(current_address, &key) { + schedule.warm_storage_read_cost + } else { + schedule.cold_sload_cost + } + } else { + schedule.sload_gas + }; + Request::Gas(gas.into()) + } + instructions::BALANCE => { + let address = u256_to_address(stack.peek(0)); + Request::Gas(accessed_addresses_gas(&address, schedule.balance_gas)) + } + instructions::EXTCODESIZE => { + let address = u256_to_address(stack.peek(0)); + Request::Gas(accessed_addresses_gas(&address, schedule.extcodesize_gas)) + } + instructions::EXTCODEHASH => { + let address = u256_to_address(stack.peek(0)); + Request::Gas(accessed_addresses_gas(&address, schedule.extcodehash_gas)) } - instructions::SLOAD => Request::Gas(Gas::from(schedule.sload_gas)), - instructions::BALANCE => Request::Gas(Gas::from(schedule.balance_gas)), - instructions::EXTCODESIZE => Request::Gas(Gas::from(schedule.extcodesize_gas)), - instructions::EXTCODEHASH => Request::Gas(Gas::from(schedule.extcodehash_gas)), instructions::SUICIDE => { let mut gas = Gas::from(schedule.suicide_gas); let is_value_transfer = !ext.origin_balance()?.is_zero(); let address = u256_to_address(stack.peek(0)); + if (!schedule.no_empty && !ext.exists(&address)?) || (schedule.no_empty && is_value_transfer @@ -164,6 +200,13 @@ impl Gasometer { overflowing!(gas.overflow_add(schedule.suicide_to_new_account_cost.into())); } + if !ext.al_contains_address(&address) { + // EIP2929 If the ETH recipient of a SELFDESTRUCT is not in accessed_addresses + // (regardless of whether or not the amount sent is nonzero), + // charge an additional COLD_ACCOUNT_ACCESS_COST on top of the existing gas costs, + gas = Gas::from(gas.as_usize() + schedule.cold_account_access_cost); + } + Request::Gas(gas) } instructions::MSTORE | instructions::MLOAD => { @@ -189,11 +232,15 @@ impl Gasometer { Gas::from_u256(*stack.peek(2))?, ) } - instructions::EXTCODECOPY => Request::GasMemCopy( - schedule.extcodecopy_base_gas.into(), - mem_needed(stack.peek(1), stack.peek(3))?, - Gas::from_u256(*stack.peek(3))?, - ), + instructions::EXTCODECOPY => { + let address = u256_to_address(stack.peek(0)); + let gas = accessed_addresses_gas(&address, schedule.extcodecopy_base_gas); + Request::GasMemCopy( + gas, + mem_needed(stack.peek(1), stack.peek(3))?, + Gas::from_u256(*stack.peek(3))?, + ) + } instructions::LOG0 | instructions::LOG1 | instructions::LOG2 @@ -218,6 +265,8 @@ impl Gasometer { ); let address = u256_to_address(stack.peek(1)); + gas = accessed_addresses_gas(&address, gas.as_usize()); + let is_value_transfer = !stack.peek(2).is_zero(); if instruction == instructions::CALL @@ -238,7 +287,10 @@ impl Gasometer { Request::GasMemProvide(gas, mem, Some(requested)) } instructions::DELEGATECALL | instructions::STATICCALL => { - let gas = Gas::from(schedule.call_gas); + let mut gas = Gas::from(schedule.call_gas); + let address = u256_to_address(stack.peek(1)); + gas = accessed_addresses_gas(&address, gas.as_usize()); + let mem = cmp::max( mem_needed(stack.peek(4), stack.peek(5))?, mem_needed(stack.peek(2), stack.peek(3))?, @@ -389,41 +441,39 @@ fn to_word_size(value: Gas) -> (Gas, bool) { } #[inline] -fn calculate_eip1283_sstore_gas( +fn calculate_eip1283_eip2929_sstore_gas( schedule: &Schedule, + is_cold: bool, original: &U256, current: &U256, new: &U256, ) -> Gas { - Gas::from(if current == new { - // 1. If current value equals new value (this is a no-op), 200 gas is deducted. - schedule.sload_gas - } else { - // 2. If current value does not equal new value - if original == current { - // 2.1. If original value equals current value (this storage slot has not been changed by the current execution context) - if original.is_zero() { - // 2.1.1. If original value is 0, 20000 gas is deducted. - schedule.sstore_set_gas - } else { - // 2.1.2. Otherwise, 5000 gas is deducted. - schedule.sstore_reset_gas - - // 2.1.2.1. If new value is 0, add 15000 gas to refund counter. - } - } else { - // 2.2. If original value does not equal current value (this storage slot is dirty), 200 gas is deducted. Apply both of the following clauses. + Gas::from( + if current == new { + // 1. If current value equals new value (this is a no-op). schedule.sload_gas - - // 2.2.1. If original value is not 0 - // 2.2.1.1. If current value is 0 (also means that new value is not 0), remove 15000 gas from refund counter. We can prove that refund counter will never go below 0. - // 2.2.1.2. If new value is 0 (also means that current value is not 0), add 15000 gas to refund counter. - - // 2.2.2. If original value equals new value (this storage slot is reset) - // 2.2.2.1. If original value is 0, add 19800 gas to refund counter. - // 2.2.2.2. Otherwise, add 4800 gas to refund counter. - } - }) + } else { + // 2. If current value does not equal new value + if original == current { + // 2.1. If original value equals current value (this storage slot has not been changed by the current execution context) + if original.is_zero() { + // 2.1.1. If original value is 0. + schedule.sstore_set_gas + } else { + // 2.1.2. Otherwise. + schedule.sstore_reset_gas + } + } else { + // 2.2. If original value does not equal current value (this storage slot is dirty). + schedule.sload_gas + } + } + if is_cold { + // EIP2929 SSTORE changes section + schedule.cold_sload_cost + } else { + 0 + }, + ) } pub fn handle_eip1283_sstore_clears_refund( diff --git a/ethcore/evm/src/interpreter/mod.rs b/ethcore/evm/src/interpreter/mod.rs index 332b268fb..418c3572a 100644 --- a/ethcore/evm/src/interpreter/mod.rs +++ b/ethcore/evm/src/interpreter/mod.rs @@ -398,8 +398,14 @@ impl Interpreter { .gasometer .as_mut() .expect(GASOMETER_PROOF) - .requirements(ext, instruction, info, &self.stack, self.mem.size()) - { + .requirements( + ext, + instruction, + info, + &self.stack, + &self.params.address, + self.mem.size(), + ) { Ok(t) => t, Err(e) => return InterpreterResult::Done(Err(e)), }; @@ -709,6 +715,15 @@ impl Interpreter { return Ok(InstructionResult::UnusedGas(create_gas)); } + let contract_address = { + let contract_code = self.mem.read_slice(init_off, init_size); + ext.calc_address(contract_code, address_scheme) + }; + + if let Some(contract_address) = contract_address { + ext.al_insert_address(contract_address); + } + let contract_code = self.mem.read_slice(init_off, init_size); let create_result = ext.create( @@ -777,6 +792,8 @@ impl Interpreter { )) .0; + ext.al_insert_address(code_address); + // Get sender & receive addresses, check if we have balance let (sender_address, receive_address, has_balance, call_type) = match instruction { instructions::CALL => { @@ -903,8 +920,9 @@ impl Interpreter { return Ok(InstructionResult::StopExecution); } instructions::SUICIDE => { - let address = self.stack.pop_back(); - ext.suicide(&u256_to_address(&address))?; + let address = u256_to_address(&self.stack.pop_back()); + ext.al_insert_address(address.clone()); + ext.suicide(&address)?; return Ok(InstructionResult::StopExecution); } instructions::LOG0 @@ -991,15 +1009,17 @@ impl Interpreter { let key = H256::from(&self.stack.pop_back()); let word = U256::from(&*ext.storage_at(&key)?); self.stack.push(word); + + ext.al_insert_storage_key(self.params.address, key); } instructions::SSTORE => { - let address = H256::from(&self.stack.pop_back()); + let key = H256::from(&self.stack.pop_back()); let val = self.stack.pop_back(); - let current_val = U256::from(&*ext.storage_at(&address)?); + let current_val = U256::from(&*ext.storage_at(&key)?); // Increase refund for clear if ext.schedule().eip1283 { - let original_val = U256::from(&*ext.initial_storage_at(&address)?); + let original_val = U256::from(&*ext.initial_storage_at(&key)?); gasometer::handle_eip1283_sstore_clears_refund( ext, &original_val, @@ -1012,7 +1032,8 @@ impl Interpreter { ext.add_sstore_refund(sstore_clears_schedule); } } - ext.set_storage(address, H256::from(&val))?; + ext.set_storage(key, H256::from(&val))?; + ext.al_insert_storage_key(self.params.address, key); } instructions::PC => { self.stack.push(U256::from(self.reader.position - 1)); @@ -1031,6 +1052,7 @@ impl Interpreter { let address = u256_to_address(&self.stack.pop_back()); let balance = ext.balance(&address)?; self.stack.push(balance); + ext.al_insert_address(address); } instructions::CALLER => { self.stack.push(address_to_u256(self.params.sender.clone())); @@ -1068,11 +1090,15 @@ impl Interpreter { instructions::EXTCODESIZE => { let address = u256_to_address(&self.stack.pop_back()); let len = ext.extcodesize(&address)?.unwrap_or(0); + + ext.al_insert_address(address); self.stack.push(U256::from(len)); } instructions::EXTCODEHASH => { let address = u256_to_address(&self.stack.pop_back()); let hash = ext.extcodehash(&address)?.unwrap_or_else(H256::zero); + + ext.al_insert_address(address); self.stack.push(U256::from(hash)); } instructions::CALLDATACOPY => { @@ -1108,6 +1134,7 @@ impl Interpreter { &mut self.stack, code.as_ref().map(|c| &(*c)[..]).unwrap_or(&[]), ); + ext.al_insert_address(address); } instructions::GASPRICE => { self.stack.push(self.params.gas_price.clone()); diff --git a/ethcore/evm/src/lib.rs b/ethcore/evm/src/lib.rs index 486e0d8b0..144cbf895 100644 --- a/ethcore/evm/src/lib.rs +++ b/ethcore/evm/src/lib.rs @@ -17,6 +17,7 @@ //! Ethereum virtual machine. extern crate bit_set; +extern crate ethcore_builtin as builtin; extern crate ethereum_types; extern crate heapsize; extern crate keccak_hash as hash; diff --git a/ethcore/evm/src/tests.rs b/ethcore/evm/src/tests.rs index 93fe5bd70..a6d359d5a 100644 --- a/ethcore/evm/src/tests.rs +++ b/ethcore/evm/src/tests.rs @@ -1554,6 +1554,113 @@ fn test_sar(factory: super::Factory) { ); } +// from https://gist.github.com/holiman/174548cad102096858583c6fbbb0649a +evm_test! {test_access_list_ext_at_precompiles: test_access_list_ext_at_precompiles_int} +fn test_access_list_ext_at_precompiles(factory: super::Factory) { + // 6001 3f 50 + // 6002 3b 50 + // 6003 31 50 + // 60f1 3f 50 + // 60f2 3b 50 + // 60f3 31 50 + // 60f2 3f 50 + // 60f3 3b 50 + // 60f1 31 50 + // 32 31 50 + // 30 31 50 + // 00 + + let code = hex!( + "60013f5060023b506003315060f13f5060f23b5060f3315060f23f5060f33b5060f1315032315030315000" + ) + .to_vec(); + + let mut params = ActionParams::default(); + params.gas = U256::from(8653); + params.code = Some(Arc::new(code)); + let mut ext = FakeExt::new_yolo3( + Address::from("0x0000000000000000000000000000000000000000"), + Address::from("0x000000000000000000000000636F6E7472616374"), + &[ + Address::from("0x0000000000000000000000000000000000000001"), + Address::from("0x0000000000000000000000000000000000000002"), + Address::from("0x0000000000000000000000000000000000000003"), + ], + ); + let gas_left = { + let vm = factory.create(params, ext.schedule(), ext.depth()); + test_finalize(vm.exec(&mut ext).ok().unwrap()).unwrap() + }; + + assert_eq!(gas_left, U256::from(0)); +} + +evm_test! {test_access_list_extcodecopy_twice: test_access_list_extcodecopy_twice_int} +fn test_access_list_extcodecopy_twice(factory: super::Factory) { + let code = hex!("60006000600060ff3c60006000600060ff3c600060006000303c").to_vec(); + + let mut params = ActionParams::default(); + params.gas = U256::from(2835); + params.code = Some(Arc::new(code)); + let mut ext = FakeExt::new_yolo3( + Address::from("0x0000000000000000000000000000000000000000"), + Address::from("0x000000000000000000000000636F6E7472616374"), + &[], + ); + let gas_left = { + let vm = factory.create(params, ext.schedule(), ext.depth()); + test_finalize(vm.exec(&mut ext).ok().unwrap()).unwrap() + }; + + assert_eq!(gas_left, U256::from(0)); +} + +evm_test! {test_access_list_sload_sstore: test_access_list_sload_sstore_int} +fn test_access_list_sload_sstore(factory: super::Factory) { + // 6001 54 50 sload( 0x1) pop + // 6011 6001 55 sstore(loc: 0x01, val:0x11) 20000 + // 6011 6002 55 sstore(loc: 0x02, val:0x11) 20000 + 2100 + // 6011 6002 55 sstore(loc: 0x02, val:0x11) 100 + // 6002 54 sload(0x2) + // 6001 54 sload(0x1) + let code = hex!("60015450 6011600155 6011600255 6011600255 600254 600154").to_vec(); + + let mut params = ActionParams::default(); + params.gas = U256::from(44529); + params.code = Some(Arc::new(code)); + let mut ext = FakeExt::new_yolo3( + Address::from("0x0000000000000000000000000000000000000000"), + Address::from("0x000000000000000000000000636F6E7472616374"), + &[], + ); + let gas_left = { + let vm = factory.create(params, ext.schedule(), ext.depth()); + test_finalize(vm.exec(&mut ext).ok().unwrap()).unwrap() + }; + + assert_eq!(gas_left, U256::from(0)); +} + +evm_test! {test_access_list_cheap_expensive_cheap: test_access_list_cheap_expensive_cheap_int} +fn test_access_list_cheap_expensive_cheap(factory: super::Factory) { + let code = + hex!("60008080808060046000f15060008080808060ff6000f15060008080808060ff6000fa50").to_vec(); + let mut params = ActionParams::default(); + params.gas = U256::from(2869); + params.code = Some(Arc::new(code)); + let mut ext = FakeExt::new_yolo3( + Address::from("0x0000000000000000000000000000000000000000"), + Address::from("0x000000000000000000000000636F6E7472616374"), + &[Address::from("0x0000000000000000000000000000000000000004")], + ); + let gas_left = { + let vm = factory.create(params, ext.schedule(), ext.depth()); + test_finalize(vm.exec(&mut ext).ok().unwrap()).unwrap() + }; + + assert_eq!(gas_left, U256::from(0)); +} + fn push_two_pop_one_constantinople_test( factory: &super::Factory, opcode: u8, diff --git a/ethcore/res/ethereum/berlin_test.json b/ethcore/res/ethereum/berlin_test.json index fc3458649..6e161c8b2 100644 --- a/ethcore/res/ethereum/berlin_test.json +++ b/ethcore/res/ethereum/berlin_test.json @@ -1,5 +1,5 @@ { - "name": "Istanbul (test)", + "name": "Berlin (test)", "engine": { "Ethash": { "params": { @@ -44,7 +44,6 @@ "eip1884Transition": "0x0", "eip2028Transition": "0x0", "eip2315Transition": "0x0" - }, "genesis": { "seal": { diff --git a/ethcore/res/ethereum/yolo3_test.json b/ethcore/res/ethereum/yolo3_test.json new file mode 100644 index 000000000..d8767f6a3 --- /dev/null +++ b/ethcore/res/ethereum/yolo3_test.json @@ -0,0 +1,123 @@ +{ + "name": "Yolo (test)", + "engine": { + "Ethash": { + "params": { + "minimumDifficulty": "0x020000", + "difficultyBoundDivisor": "0x0800", + "durationLimit": "0x0d", + "blockReward": "0x1BC16D674EC80000", + "homesteadTransition": "0x0", + "eip100bTransition": "0x0", + "difficultyBombDelays": { + "0": 5000000 + } + } + } + }, + "params": { + "gasLimitBoundDivisor": "0x0400", + "registrar" : "0xc6d9d2cd449a754c494264e1809c50e34d64562b", + "accountStartNonce": "0x00", + "maximumExtraDataSize": "0x20", + "minGasLimit": "0x1388", + "networkID" : "0x1", + "maxCodeSize": 24576, + "maxCodeSizeTransition": "0x0", + "eip150Transition": "0x0", + "eip160Transition": "0x0", + "eip161abcTransition": "0x0", + "eip161dTransition": "0x0", + "eip140Transition": "0x0", + "eip211Transition": "0x0", + "eip214Transition": "0x0", + "eip155Transition": "0x0", + "eip658Transition": "0x0", + "eip145Transition": "0x0", + "eip1014Transition": "0x0", + "eip1052Transition": "0x0", + "eip1283Transition": "0x0", + "eip1283DisableTransition": "0x0", + "eip1283ReenableTransition": "0x0", + "eip1344Transition": "0x0", + "eip1706Transition": "0x0", + "eip1884Transition": "0x0", + "eip2028Transition": "0x0", + "eip2315Transition": "0x0", + "eip2929Transition": "0x0" + }, + "genesis": { + "seal": { + "ethereum": { + "nonce": "0x0000000000000042", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" + } + }, + "difficulty": "0x400000000", + "author": "0x0000000000000000000000000000000000000000", + "timestamp": "0x00", + "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", + "extraData": "0x11bbe8db4e347b4e8c937c1c8370e4b5ed33adb3db69cbdb7a38e1e50b1b82fa", + "gasLimit": "0x1388" + }, + "accounts": { + "0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } }, + "0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } }, + "0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } }, + "0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } }, + "0000000000000000000000000000000000000005": { "builtin": { "name": "modexp", "activate_at": "0x00", "pricing": { "modexp": { "divisor": 20 } } } }, + "0000000000000000000000000000000000000006": { + "builtin": { + "name": "alt_bn128_add", + "pricing": { + "0": { + "price": { "alt_bn128_const_operations": { "price": 500 }} + }, + "0": { + "info": "EIP 1108 transition", + "price": { "alt_bn128_const_operations": { "price": 150 }} + } + } + } + }, + "0000000000000000000000000000000000000007": { + "builtin": { + "name": "alt_bn128_mul", + "pricing": { + "0": { + "price": { "alt_bn128_const_operations": { "price": 40000 }} + }, + "0": { + "info": "EIP 1108 transition", + "price": { "alt_bn128_const_operations": { "price": 6000 }} + } + } + } + }, + "0000000000000000000000000000000000000008": { + "builtin": { + "name": "alt_bn128_pairing", + "pricing": { + "0": { + "price": { "alt_bn128_pairing": { "base": 100000, "pair": 80000 }} + }, + "0": { + "info": "EIP 1108 transition", + "price": { "alt_bn128_pairing": { "base": 45000, "pair": 34000 }} + } + } + } + }, + "0000000000000000000000000000000000000009": { + "builtin": { + "name": "blake2_f", + "activate_at": "0x00", + "pricing": { + "blake2_f": { + "gas_per_round": 1 + } + } + } + } + } +} diff --git a/ethcore/src/client/evm_test_client.rs b/ethcore/src/client/evm_test_client.rs index 94e21bcd9..8a9327f75 100644 --- a/ethcore/src/client/evm_test_client.rs +++ b/ethcore/src/client/evm_test_client.rs @@ -108,6 +108,7 @@ impl<'a> EvmTestClient<'a> { Some(ethereum::new_byzantium_to_constantinoplefixat5_test()) } ForkSpec::Berlin => Some(ethereum::new_berlin_test()), + ForkSpec::Yolo3 => Some(ethereum::new_yolo3_test()), ForkSpec::FrontierToHomesteadAt5 | ForkSpec::HomesteadToDaoAt5 | ForkSpec::HomesteadToEIP150At5 diff --git a/ethcore/src/ethereum/mod.rs b/ethcore/src/ethereum/mod.rs index 7a38fecd9..736d0d5ae 100644 --- a/ethcore/src/ethereum/mod.rs +++ b/ethcore/src/ethereum/mod.rs @@ -252,6 +252,11 @@ pub fn new_berlin_test() -> Spec { load(None, include_bytes!("../../res/ethereum/berlin_test.json")) } +/// Create a new YOLO spec +pub fn new_yolo3_test() -> Spec { + load(None, include_bytes!("../../res/ethereum/yolo3_test.json")) +} + /// Create a new Musicoin-MCIP3-era spec. pub fn new_mcip3_test() -> Spec { load(None, include_bytes!("../../res/ethereum/mcip3_test.json")) diff --git a/ethcore/src/executive.rs b/ethcore/src/executive.rs index ec0b7a896..85394fa3d 100644 --- a/ethcore/src/executive.rs +++ b/ethcore/src/executive.rs @@ -16,7 +16,6 @@ //! Transaction Execution environment. use bytes::{Bytes, BytesRef}; -use crossbeam_utils::thread; use ethereum_types::{Address, H256, U256, U512}; use evm::{CallType, FinalizationResult, Finalize}; use executed::ExecutionError; @@ -31,26 +30,10 @@ use trace::{self, Tracer, VMTracer}; use transaction_ext::Transaction; use types::transaction::{Action, SignedTransaction}; use vm::{ - self, ActionParams, ActionValue, CleanDustMode, CreateContractAddress, EnvInfo, ResumeCall, - ResumeCreate, ReturnData, Schedule, TrapError, + self, AccessList, ActionParams, ActionValue, CleanDustMode, CreateContractAddress, EnvInfo, + ResumeCall, ResumeCreate, ReturnData, Schedule, TrapError, }; -#[cfg(debug_assertions)] -/// Roughly estimate what stack size each level of evm depth will use. (Debug build) -const STACK_SIZE_PER_DEPTH: usize = 128 * 1024; - -#[cfg(not(debug_assertions))] -/// Roughly estimate what stack size each level of evm depth will use. -const STACK_SIZE_PER_DEPTH: usize = 24 * 1024; - -#[cfg(debug_assertions)] -/// Entry stack overhead prior to execution. (Debug build) -const STACK_SIZE_ENTRY_OVERHEAD: usize = 100 * 1024; - -#[cfg(not(debug_assertions))] -/// Entry stack overhead prior to execution. -const STACK_SIZE_ENTRY_OVERHEAD: usize = 20 * 1024; - #[cfg(any(test, feature = "test-helpers"))] /// Precompile that can never be prunned from state trie (0x3, only in tests) const UNPRUNABLE_PRECOMPILE_ADDRESS: Option
= Some(ethereum_types::H160([ @@ -252,6 +235,18 @@ pub struct CallCreateExecutive<'a> { } impl<'a> CallCreateExecutive<'a> { + /// Create new state with access list. + pub fn new_substate(params: &ActionParams, schedule: &'a Schedule) -> Substate { + if schedule.eip2929 { + let mut substate = Substate::from_access_list(¶ms.access_list); + substate.access_list.insert_address(params.address); + substate.access_list.insert_address(params.sender); + substate + } else { + Substate::default() + } + } + /// Create a new call executive using raw data. pub fn new_call_raw( params: ActionParams, @@ -287,7 +282,8 @@ impl<'a> CallCreateExecutive<'a> { CallCreateExecutiveKind::CallBuiltin(params) } else { if params.code.is_some() { - CallCreateExecutiveKind::ExecCall(params, Substate::new()) + let substate = Self::new_substate(¶ms, schedule); + CallCreateExecutiveKind::ExecCall(params, substate) } else { CallCreateExecutiveKind::Transfer(params) } @@ -327,7 +323,8 @@ impl<'a> CallCreateExecutive<'a> { let gas = params.gas; - let kind = CallCreateExecutiveKind::ExecCreate(params, Substate::new()); + let substate = Self::new_substate(¶ms, schedule); + let kind = CallCreateExecutiveKind::ExecCreate(params, substate); Self { info, @@ -457,6 +454,7 @@ impl<'a> CallCreateExecutive<'a> { } } state.revert_to_checkpoint(); + un_substate.access_list.rollback(); } Ok(_) | Err(vm::Error::Internal(_)) => { state.discard_checkpoint(); @@ -1188,7 +1186,14 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { }); } - let mut substate = Substate::new(); + let mut access_list = AccessList::new(schedule.eip2929); + if schedule.eip2929 { + for (address, _) in self.machine.builtins() { + access_list.insert_address(*address); + } + } + + let mut substate = Substate::from_access_list(&access_list); // NOTE: there can be no invalid transactions from this point. if !schedule.keep_unsigned_nonce || !t.is_unsigned() { @@ -1221,6 +1226,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { data: None, call_type: CallType::None, params_type: vm::ParamsType::Embedded, + access_list: access_list, }; let res = self.create(params, &mut substate, &mut tracer, &mut vm_tracer); let out = match &res { @@ -1243,6 +1249,7 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { data: Some(t.data.clone()), call_type: CallType::Call, params_type: vm::ParamsType::Separate, + access_list: access_list, }; let res = self.call(params, &mut substate, &mut tracer, &mut vm_tracer); let out = match &res { @@ -1325,48 +1332,6 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { result } - /// Calls contract function with given contract params, if the stack depth is above a threshold, create a new thread - /// to execute it. - pub fn call_with_crossbeam( - &mut self, - params: ActionParams, - substate: &mut Substate, - stack_depth: usize, - tracer: &mut T, - vm_tracer: &mut V, - ) -> vm::Result - where - T: Tracer, - V: VMTracer, - { - let local_stack_size = ::io::LOCAL_STACK_SIZE.with(|sz| sz.get()); - let depth_threshold = - local_stack_size.saturating_sub(STACK_SIZE_ENTRY_OVERHEAD) / STACK_SIZE_PER_DEPTH; - - if stack_depth != depth_threshold { - self.call_with_stack_depth(params, substate, stack_depth, tracer, vm_tracer) - } else { - thread::scope(|scope| { - let stack_size = cmp::max( - self.schedule.max_depth.saturating_sub(depth_threshold) * STACK_SIZE_PER_DEPTH, - local_stack_size, - ); - scope - .builder() - .stack_size(stack_size) - .spawn(|_| { - self.call_with_stack_depth(params, substate, stack_depth, tracer, vm_tracer) - }) - .expect( - "Sub-thread creation cannot fail; the host might run out of resources; qed", - ) - .join() - }) - .expect("Sub-thread never panics; qed") - .expect("Sub-thread never panics; qed") - } - } - /// Calls contract function with given contract params. pub fn call( &mut self, @@ -1437,54 +1402,6 @@ impl<'a, B: 'a + StateBackend> Executive<'a, B> { result } - /// Creates contract with given contract params, if the stack depth is above a threshold, create a new thread to - /// execute it. - pub fn create_with_crossbeam( - &mut self, - params: ActionParams, - substate: &mut Substate, - stack_depth: usize, - tracer: &mut T, - vm_tracer: &mut V, - ) -> vm::Result - where - T: Tracer, - V: VMTracer, - { - let local_stack_size = ::io::LOCAL_STACK_SIZE.with(|sz| sz.get()); - let depth_threshold = - local_stack_size.saturating_sub(STACK_SIZE_ENTRY_OVERHEAD) / STACK_SIZE_PER_DEPTH; - - if stack_depth != depth_threshold { - self.create_with_stack_depth(params, substate, stack_depth, tracer, vm_tracer) - } else { - thread::scope(|scope| { - let stack_size = cmp::max( - self.schedule.max_depth.saturating_sub(depth_threshold) * STACK_SIZE_PER_DEPTH, - local_stack_size, - ); - scope - .builder() - .stack_size(stack_size) - .spawn(|_| { - self.create_with_stack_depth( - params, - substate, - stack_depth, - tracer, - vm_tracer, - ) - }) - .expect( - "Sub-thread creation cannot fail; the host might run out of resources; qed", - ) - .join() - }) - .expect("Sub-thread never panics; qed") - .expect("Sub-thread never panics; qed") - } - } - /// Creates contract with given contract params. pub fn create( &mut self, diff --git a/ethcore/src/externalities.rs b/ethcore/src/externalities.rs index 46e77c83b..d1910dd27 100644 --- a/ethcore/src/externalities.rs +++ b/ethcore/src/externalities.rs @@ -24,8 +24,8 @@ use std::{cmp, sync::Arc}; use trace::{Tracer, VMTracer}; use types::transaction::UNSIGNED_SENDER; use vm::{ - self, ActionParams, ActionValue, CallType, ContractCreateResult, CreateContractAddress, - EnvInfo, Ext, MessageCallResult, ReturnData, Schedule, TrapKind, + self, AccessList, ActionParams, ActionValue, CallType, ContractCreateResult, + CreateContractAddress, EnvInfo, Ext, MessageCallResult, ReturnData, Schedule, TrapKind, }; /// Policy for handling output data on `RETURN` opcode. @@ -200,10 +200,11 @@ where data: Some(H256::from(number).to_vec()), call_type: CallType::Call, params_type: vm::ParamsType::Separate, + access_list: AccessList::default(), }; let mut ex = Executive::new(self.state, self.env_info, self.machine, self.schedule); - let r = ex.call_with_crossbeam( + let r = ex.call_with_stack_depth( params, self.substate, self.stack_depth + 1, @@ -288,6 +289,7 @@ where data: None, call_type: CallType::None, params_type: vm::ParamsType::Embedded, + access_list: self.substate.access_list.clone(), }; if !self.static_flag { @@ -312,7 +314,7 @@ where self.depth, self.static_flag, ); - let out = ex.create_with_crossbeam( + let out = ex.create_with_stack_depth( params, self.substate, self.stack_depth + 1, @@ -322,6 +324,15 @@ where Ok(into_contract_create_result(out, &address, self.substate)) } + fn calc_address(&self, code: &[u8], address_scheme: CreateContractAddress) -> Option
{ + match self.state.nonce(&self.origin_info.address) { + Ok(nonce) => { + Some(contract_address(address_scheme, &self.origin_info.address, &nonce, &code).0) + } + Err(_) => None, + } + } + fn call( &mut self, gas: &U256, @@ -358,6 +369,7 @@ where data: Some(data.to_vec()), call_type: call_type, params_type: vm::ParamsType::Separate, + access_list: self.substate.access_list.clone(), }; if let Some(value) = value { @@ -376,7 +388,7 @@ where self.depth, self.static_flag, ); - let out = ex.call_with_crossbeam( + let out = ex.call_with_stack_depth( params, self.substate, self.stack_depth + 1, @@ -518,6 +530,26 @@ where fn trace_executed(&mut self, gas_used: U256, stack_push: &[U256], mem: &[u8]) { self.vm_tracer.trace_executed(gas_used, stack_push, mem) } + + fn al_is_enabled(&self) -> bool { + self.substate.access_list.is_enabled() + } + + fn al_contains_storage_key(&self, address: &Address, key: &H256) -> bool { + self.substate.access_list.contains_storage_key(address, key) + } + + fn al_insert_storage_key(&mut self, address: Address, key: H256) { + self.substate.access_list.insert_storage_key(address, key) + } + + fn al_contains_address(&self, address: &Address) -> bool { + self.substate.access_list.contains_address(address) + } + + fn al_insert_address(&mut self, address: Address) { + self.substate.access_list.insert_address(address) + } } #[cfg(test)] diff --git a/ethcore/src/json_tests/executive.rs b/ethcore/src/json_tests/executive.rs index 11288ae2d..515b73045 100644 --- a/ethcore/src/json_tests/executive.rs +++ b/ethcore/src/json_tests/executive.rs @@ -167,6 +167,10 @@ where Ok(ContractCreateResult::Created(contract_address, *gas)) } + fn calc_address(&self, code: &[u8], address: CreateContractAddress) -> Option
{ + Some(contract_address(address, &self.sender, &self.nonce, &code).0) + } + fn call( &mut self, gas: &U256, @@ -238,6 +242,26 @@ where fn sub_sstore_refund(&mut self, value: usize) { self.ext.sub_sstore_refund(value) } + + fn al_is_enabled(&self) -> bool { + self.ext.al_is_enabled() + } + + fn al_contains_storage_key(&self, address: &Address, key: &H256) -> bool { + self.ext.al_contains_storage_key(address, key) + } + + fn al_insert_storage_key(&mut self, address: Address, key: H256) { + self.ext.al_insert_storage_key(address, key) + } + + fn al_contains_address(&self, address: &Address) -> bool { + self.ext.al_contains_address(address) + } + + fn al_insert_address(&mut self, address: Address) { + self.ext.al_insert_address(address) + } } /// run an json executive test diff --git a/ethcore/src/json_tests/state.rs b/ethcore/src/json_tests/state.rs index 000088ee7..2b1c89ebc 100644 --- a/ethcore/src/json_tests/state.rs +++ b/ethcore/src/json_tests/state.rs @@ -68,7 +68,6 @@ pub fn json_chain_test( let pre: PodState = test.pre_state.into(); for (spec_name, states) in test.post_states { - let total = states.len(); let spec = match EvmTestClient::spec_from_json(&spec_name) { Some(spec) => spec, None => { @@ -81,11 +80,10 @@ pub fn json_chain_test( for (i, state) in states.into_iter().enumerate() { let info = format!( - " - state: {} | {:?} ({}/{}) ...", - name, + "TestState/{}/{:?}/{}/trie", + path.to_string_lossy(), spec_name, - i + 1, - total + i ); if skip_test(&state_test, &name, &spec.name, i + 1) { println!("{}: SKIPPED", info); @@ -112,7 +110,7 @@ pub fn json_chain_test( } Ok(Ok(TransactSuccess { state_root, .. })) if state_root != post_root => { println!( - "{} !!! State mismatch (got: {}, expect: {}", + "{}: post state root mismatch: got {:?}, want {:?}", info, state_root, post_root ); flushln!("{} fail", info); @@ -124,7 +122,7 @@ pub fn json_chain_test( .. })) if state_root != post_root => { println!( - "{} !!! State mismatch (got: {}, expect: {}", + "{}: post state root mismatch: got {:?}, want {:?}", info, state_root, post_root ); println!("{} !!! Execution error: {:?}", info, error); diff --git a/ethcore/src/machine/impls.rs b/ethcore/src/machine/impls.rs index f848a320d..8b98cf08d 100644 --- a/ethcore/src/machine/impls.rs +++ b/ethcore/src/machine/impls.rs @@ -32,7 +32,8 @@ use types::{ BlockNumber, }; use vm::{ - ActionParams, ActionValue, CallType, CreateContractAddress, EnvInfo, ParamsType, Schedule, + AccessList, ActionParams, ActionValue, CallType, CreateContractAddress, EnvInfo, ParamsType, + Schedule, }; use block::ExecutedBlock; @@ -201,6 +202,7 @@ impl EthereumMachine { data, call_type: call_type.unwrap_or(CallType::Call), params_type: ParamsType::Separate, + access_list: AccessList::default(), }; let schedule = self.schedule(env_info.number); let mut ex = Executive::new(&mut state, &env_info, self, &schedule); diff --git a/ethcore/src/spec/spec.rs b/ethcore/src/spec/spec.rs index c38e19adf..85a7589e4 100644 --- a/ethcore/src/spec/spec.rs +++ b/ethcore/src/spec/spec.rs @@ -32,7 +32,7 @@ use parking_lot::RwLock; use rlp::{Rlp, RlpStream}; use rustc_hex::FromHex; use types::{header::Header, BlockNumber}; -use vm::{ActionParams, ActionValue, CallType, EnvInfo, ParamsType}; +use vm::{AccessList, ActionParams, ActionValue, CallType, EnvInfo, ParamsType}; use builtin::Builtin; use engines::{ @@ -135,6 +135,8 @@ pub struct CommonParams { pub eip2028_transition: BlockNumber, /// Number of first block where EIP-2315 rules begin. pub eip2315_transition: BlockNumber, + /// Number of first block where EIP-2929 rules begin. + pub eip2929_transition: BlockNumber, /// Number of first block where dust cleanup rules (EIP-168 and EIP169) begin. pub dust_protection_transition: BlockNumber, /// Nonce cap increase per block. Nonce cap is only checked if dust protection is enabled. @@ -209,6 +211,7 @@ impl CommonParams { || block_number >= self.eip1283_reenable_transition; schedule.eip1706 = block_number >= self.eip1706_transition; schedule.have_subs = block_number >= self.eip2315_transition; + schedule.eip2929 = block_number >= self.eip2929_transition; if block_number >= self.eip1884_transition { schedule.have_selfbalance = true; @@ -222,6 +225,24 @@ impl CommonParams { if block_number >= self.eip210_transition { schedule.blockhash_gas = 800; } + if block_number >= self.eip2929_transition { + schedule.eip2929 = true; + schedule.eip1283 = true; + + schedule.call_gas = ::vm::schedule::EIP2929_COLD_ACCOUNT_ACCESS_COST; + schedule.balance_gas = ::vm::schedule::EIP2929_COLD_ACCOUNT_ACCESS_COST; + schedule.extcodecopy_base_gas = ::vm::schedule::EIP2929_COLD_ACCOUNT_ACCESS_COST; + schedule.extcodehash_gas = ::vm::schedule::EIP2929_COLD_ACCOUNT_ACCESS_COST; + schedule.extcodesize_gas = ::vm::schedule::EIP2929_COLD_ACCOUNT_ACCESS_COST; + + schedule.cold_sload_cost = ::vm::schedule::EIP2929_COLD_SLOAD_COST; + schedule.cold_account_access_cost = ::vm::schedule::EIP2929_COLD_ACCOUNT_ACCESS_COST; + schedule.warm_storage_read_cost = ::vm::schedule::EIP2929_WARM_STORAGE_READ_COST; + + schedule.sload_gas = ::vm::schedule::EIP2929_WARM_STORAGE_READ_COST; + schedule.sstore_reset_gas = ::vm::schedule::EIP2929_SSTORE_RESET_GAS; + } + if block_number >= self.dust_protection_transition { schedule.kill_dust = match self.remove_dust_contracts { true => ::vm::CleanDustMode::WithCodeAndStorage, @@ -346,6 +367,9 @@ impl From for CommonParams { eip2315_transition: p .eip2315_transition .map_or_else(BlockNumber::max_value, Into::into), + eip2929_transition: p + .eip2929_transition + .map_or_else(BlockNumber::max_value, Into::into), dust_protection_transition: p .dust_protection_transition .map_or_else(BlockNumber::max_value, Into::into), @@ -757,6 +781,7 @@ impl Spec { data: None, call_type: CallType::None, params_type: ParamsType::Embedded, + access_list: AccessList::default(), }; let mut substate = Substate::new(); diff --git a/ethcore/src/state/substate.rs b/ethcore/src/state/substate.rs index ec23e85e6..e2fe02483 100644 --- a/ethcore/src/state/substate.rs +++ b/ethcore/src/state/substate.rs @@ -20,6 +20,7 @@ use ethereum_types::Address; use evm::{CleanDustMode, Schedule}; use std::collections::HashSet; use types::log_entry::LogEntry; +use vm::access_list::AccessList; /// State changes which should be applied in finalize, /// after transaction is fully executed. @@ -39,6 +40,9 @@ pub struct Substate { /// Created contracts. pub contracts_created: Vec
, + + /// List of accesses addresses and slots + pub access_list: AccessList, } impl Substate { @@ -46,6 +50,17 @@ impl Substate { pub fn new() -> Self { Substate::default() } + /// Creates a new substate from an access list + pub fn from_access_list(access_list: &AccessList) -> Self { + Self { + suicides: HashSet::default(), + touched: HashSet::default(), + logs: Vec::default(), + sstore_clears_refund: 0, + contracts_created: Vec::default(), + access_list: access_list.clone(), + } + } /// Merge secondary substate `s` into self, accruing each element correspondingly. pub fn accrue(&mut self, s: Substate) { diff --git a/ethcore/src/tests/evm.rs b/ethcore/src/tests/evm.rs index 555207d0a..c3ba39d78 100644 --- a/ethcore/src/tests/evm.rs +++ b/ethcore/src/tests/evm.rs @@ -24,7 +24,7 @@ use std::sync::Arc; use test_helpers::get_temp_state_with_factory; use trace::{NoopTracer, NoopVMTracer}; use types::transaction::SYSTEM_ADDRESS; -use vm::{ActionParams, ActionValue, CallType, EnvInfo, ParamsType}; +use vm::{AccessList, ActionParams, ActionValue, CallType, EnvInfo, ParamsType}; use rustc_hex::FromHex; @@ -62,6 +62,7 @@ fn test_blockhash_eip210(factory: Factory) { data: Some(H256::from(i - 1).to_vec()), call_type: CallType::Call, params_type: ParamsType::Separate, + access_list: AccessList::default(), }; let schedule = machine.schedule(env_info.number); let mut ex = Executive::new(&mut state, &env_info, &machine, &schedule); @@ -85,6 +86,7 @@ fn test_blockhash_eip210(factory: Factory) { data: None, call_type: CallType::Call, params_type: ParamsType::Separate, + access_list: AccessList::default(), }; let schedule = machine.schedule(env_info.number); let mut ex = Executive::new(&mut state, &env_info, &machine, &schedule); diff --git a/ethcore/vm/src/access_list.rs b/ethcore/vm/src/access_list.rs new file mode 100644 index 000000000..a4a434368 --- /dev/null +++ b/ethcore/vm/src/access_list.rs @@ -0,0 +1,260 @@ +use ethereum_types::{Address, H256}; +use std::{ + borrow::Borrow, + collections::HashMap, + hash::{Hash, Hasher}, +}; + +use std::{cell::RefCell, rc::Rc}; + +// Implementation of a hasheable borrowed pair +trait KeyPair { + fn a(&self) -> &A; + fn b(&self) -> &B; +} +impl<'a, A, B> Borrow + 'a> for (A, B) +where + A: Eq + Hash + 'a, + B: Eq + Hash + 'a, +{ + fn borrow(&self) -> &(dyn KeyPair + 'a) { + self + } +} +impl Hash for (dyn KeyPair + '_) { + fn hash(&self, state: &mut H) { + self.a().hash(state); + self.b().hash(state); + } +} +impl PartialEq for (dyn KeyPair + '_) { + fn eq(&self, other: &Self) -> bool { + self.a() == other.a() && self.b() == other.b() + } +} +impl Eq for (dyn KeyPair + '_) {} +impl KeyPair for (A, B) { + fn a(&self) -> &A { + &self.0 + } + fn b(&self) -> &B { + &self.1 + } +} +impl KeyPair for (&A, &B) { + fn a(&self) -> &A { + self.0 + } + fn b(&self) -> &B { + self.1 + } +} + +#[derive(Debug)] +struct Journal { + enabled: bool, + last_id: usize, + addresses: HashMap, + storage_keys: HashMap<(Address, H256), usize>, +} +#[derive(Debug)] +pub struct AccessList { + id: usize, + journal: Rc>, +} + +impl Clone for AccessList { + fn clone(&self) -> Self { + let mut journal = self.journal.as_ref().borrow_mut(); + let id = journal.last_id + 1; + journal.last_id = id; + Self { + id: id, + journal: self.journal.clone(), + } + } +} + +impl Default for AccessList { + fn default() -> Self { + AccessList::new(false) + } +} + +impl std::fmt::Display for AccessList { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let journal = self.journal.as_ref().borrow(); + for (addr, id) in journal.addresses.iter() { + write!(f, "| ADDR {} -> {}\n", addr, id)?; + } + for ((addr, slot), id) in journal.storage_keys.iter() { + write!(f, "| SLOT {}:{} -> {}\n", addr, slot, id)?; + } + Ok(()) + } +} + +impl AccessList { + /// Returns if the list is enabled + pub fn new(enabled: bool) -> Self { + let journal = Journal { + enabled, + last_id: 0, + addresses: HashMap::new(), + storage_keys: HashMap::new(), + }; + Self { + id: 0, + journal: Rc::new(RefCell::new(journal)), + } + } + + /// Returns if the list is enabled + pub fn is_enabled(&self) -> bool { + let journal = self.journal.as_ref().borrow(); + journal.enabled + } + + /// Enable the access list control + pub fn enable(&mut self) { + let mut journal = self.journal.as_ref().borrow_mut(); + journal.enabled = true; + } + + /// Checks if contains an storage key + pub fn contains_storage_key(&self, address: &Address, key: &H256) -> bool { + let journal = self.journal.as_ref().borrow(); + if journal.enabled { + journal + .storage_keys + .contains_key(&(address, key) as &dyn KeyPair) + } else { + false + } + } + + /// Inserts a storage key + pub fn insert_storage_key(&mut self, address: Address, key: H256) { + let mut journal = self.journal.as_ref().borrow_mut(); + if journal.enabled + && !journal + .storage_keys + .contains_key(&(address, key) as &dyn KeyPair) + { + journal.storage_keys.insert((address, key), self.id); + } + } + + /// Checks if contains an address + pub fn contains_address(&self, address: &Address) -> bool { + let journal = self.journal.as_ref().borrow(); + if journal.enabled { + journal.addresses.contains_key(&address) + } else { + false + } + } + /// Inserts an address + pub fn insert_address(&mut self, address: Address) { + let mut journal = self.journal.as_ref().borrow_mut(); + if journal.enabled && !journal.addresses.contains_key(&address) { + journal.addresses.insert(address, self.id); + } + } + /// Removes all changes in journal + pub fn rollback(&self) { + let mut journal = self.journal.as_ref().borrow_mut(); + // `id < self.id` instead `id != self.if` is to take care about recursive calls + journal.addresses.retain(|_, id| *id < self.id); + journal.storage_keys.retain(|_, id| *id < self.id); + } +} + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn default_accesslist_is_disabled() { + let access_list = AccessList::default(); + assert_eq!(false, access_list.is_enabled()); + } + + #[test] + fn default_disabled_accesslist_does_nothing() { + let mut access_list = AccessList::default(); + access_list.insert_address(Address::from(1)); + access_list.insert_storage_key(Address::from(2), H256::from(3)); + assert_eq!(false, access_list.contains_address(&Address::from(1))); + assert_eq!( + false, + access_list.contains_storage_key(&Address::from(2), &H256::from(3)) + ); + } + + #[test] + fn default_enabled_accesslist_registers() { + let mut access_list = AccessList::default(); + access_list.enable(); + assert_eq!(true, access_list.is_enabled()); + access_list.insert_address(Address::from(1)); + access_list.insert_storage_key(Address::from(2), H256::from(3)); + assert_eq!(true, access_list.contains_address(&Address::from(1))); + assert_eq!( + true, + access_list.contains_storage_key(&Address::from(2), &H256::from(3)) + ); + } + + #[test] + fn cloned_accesslist_registers_in_parent() { + let mut access_list = AccessList::default(); + access_list.enable(); + assert_eq!(true, access_list.is_enabled()); + access_list.insert_address(Address::from(1)); + access_list.insert_storage_key(Address::from(2), H256::from(3)); + + let mut access_list_call = access_list.clone(); + assert_eq!(true, access_list_call.contains_address(&Address::from(1))); + assert_eq!( + true, + access_list_call.contains_storage_key(&Address::from(2), &H256::from(3)) + ); + access_list.insert_address(Address::from(4)); + assert_eq!(true, access_list_call.contains_address(&Address::from(4))); + + assert_eq!(true, access_list.contains_address(&Address::from(4))); + } + #[test] + fn cloned_accesslist_rollbacks_in_parent() { + let mut access_list = AccessList::default(); + access_list.enable(); + assert_eq!(true, access_list.is_enabled()); + access_list.insert_address(Address::from(1)); + access_list.insert_storage_key(Address::from(2), H256::from(3)); + + let mut access_list_call = access_list.clone(); + access_list_call.insert_address(Address::from(1)); + access_list_call.insert_storage_key(Address::from(2), H256::from(3)); + access_list_call.insert_address(Address::from(4)); + + let mut access_list_call_call = access_list.clone(); + access_list_call_call.insert_address(Address::from(1)); + access_list_call_call.insert_storage_key(Address::from(2), H256::from(3)); + access_list_call_call.insert_address(Address::from(5)); + access_list_call_call.insert_storage_key(Address::from(6), H256::from(7)); + + access_list_call.rollback(); + + assert_eq!(true, access_list.contains_address(&Address::from(1))); + assert_eq!(false, access_list.contains_address(&Address::from(4))); + assert_eq!(false, access_list.contains_address(&Address::from(5))); + assert_eq!( + true, + access_list.contains_storage_key(&Address::from(2), &H256::from(3)) + ); + assert_eq!( + false, + access_list.contains_storage_key(&Address::from(6), &H256::from(7)) + ); + } +} diff --git a/ethcore/vm/src/action_params.rs b/ethcore/vm/src/action_params.rs index b46dc919b..b9aaf38bc 100644 --- a/ethcore/vm/src/action_params.rs +++ b/ethcore/vm/src/action_params.rs @@ -15,13 +15,13 @@ // along with OpenEthereum. If not, see . //! Evm input params. +use super::access_list::AccessList; use bytes::Bytes; +use call_type::CallType; use ethereum_types::{Address, H256, U256}; use ethjson; use hash::{keccak, KECCAK_EMPTY}; -use call_type::CallType; - use std::sync::Arc; /// Transaction value @@ -90,6 +90,8 @@ pub struct ActionParams { pub call_type: CallType, /// Param types encoding pub params_type: ParamsType, + /// Current access list + pub access_list: AccessList, } impl Default for ActionParams { @@ -108,6 +110,7 @@ impl Default for ActionParams { data: None, call_type: CallType::None, params_type: ParamsType::Separate, + access_list: AccessList::default(), } } } @@ -131,6 +134,7 @@ impl From for ActionParams { false => CallType::Call, }, // TODO @debris is this correct? params_type: ParamsType::Separate, + access_list: AccessList::default(), } } } diff --git a/ethcore/vm/src/ext.rs b/ethcore/vm/src/ext.rs index 752b60a2b..fccb42e78 100644 --- a/ethcore/vm/src/ext.rs +++ b/ethcore/vm/src/ext.rs @@ -101,6 +101,9 @@ pub trait Ext { trap: bool, ) -> ::std::result::Result; + /// Returns the address that will be created in the create call + fn calc_address(&self, code: &[u8], address: CreateContractAddress) -> Option
; + /// Message call. /// /// Returns Err, if we run out of gas. @@ -184,4 +187,19 @@ pub trait Ext { /// Check if running in static context. fn is_static(&self) -> bool; + + /// Returns if the list is enabled + fn al_is_enabled(&self) -> bool; + + /// Checks if contains an storage key + fn al_contains_storage_key(&self, address: &Address, key: &H256) -> bool; + + /// Inserts an storage key into the list + fn al_insert_storage_key(&mut self, address: Address, key: H256); + + /// Checks if contains an address + fn al_contains_address(&self, address: &Address) -> bool; + + /// Inserts an address into the list + fn al_insert_address(&mut self, address: Address); } diff --git a/ethcore/vm/src/lib.rs b/ethcore/vm/src/lib.rs index 4f9b747ef..f662921c7 100644 --- a/ethcore/vm/src/lib.rs +++ b/ethcore/vm/src/lib.rs @@ -23,16 +23,18 @@ extern crate parity_bytes as bytes; extern crate patricia_trie_ethereum as ethtrie; extern crate rlp; +pub mod access_list; mod action_params; mod call_type; mod env_info; mod error; mod ext; mod return_data; -mod schedule; +pub mod schedule; pub mod tests; +pub use access_list::AccessList; pub use action_params::{ActionParams, ActionValue, ParamsType}; pub use call_type::CallType; pub use env_info::{EnvInfo, LastHashes}; @@ -42,7 +44,7 @@ pub use return_data::{GasLeft, ReturnData}; pub use schedule::{CleanDustMode, Schedule, WasmCosts}; /// Virtual Machine interface -pub trait Exec: Send { +pub trait Exec { /// This function should be used to execute transaction. /// It returns either an error, a known amount of gas left, or parameters to be used /// to compute the final gas left. @@ -50,13 +52,13 @@ pub trait Exec: Send { } /// Resume call interface -pub trait ResumeCall: Send { +pub trait ResumeCall { /// Resume an execution for call, returns back the Vm interface. fn resume_call(self: Box, result: MessageCallResult) -> Box; } /// Resume create interface -pub trait ResumeCreate: Send { +pub trait ResumeCreate { /// Resume an execution from create, returns back the Vm interface. fn resume_create(self: Box, result: ContractCreateResult) -> Box; } diff --git a/ethcore/vm/src/schedule.rs b/ethcore/vm/src/schedule.rs index 50a5d5e6e..b6b27bd21 100644 --- a/ethcore/vm/src/schedule.rs +++ b/ethcore/vm/src/schedule.rs @@ -16,6 +16,15 @@ //! Cost schedule and other parameterisations for the EVM. +// Gas per non accessed address when sload +pub const EIP2929_COLD_SLOAD_COST: usize = 2100; +// Gas per non accessed address accessing account from other opcodes defined in EIP2929 +pub const EIP2929_COLD_ACCOUNT_ACCESS_COST: usize = 2600; +// Gas per already accessed address +pub const EIP2929_WARM_STORAGE_READ_COST: usize = 100; +// Gas per sstore reset +pub const EIP2929_SSTORE_RESET_GAS: usize = 5000 - EIP2929_COLD_SLOAD_COST; + /// Definition of the cost schedule and other parameterisations for the EVM. #[derive(Debug)] pub struct Schedule { @@ -63,6 +72,12 @@ pub struct Schedule { pub create_gas: usize, /// Gas price for `*CALL*` opcodes pub call_gas: usize, + /// EIP-2929 COLD_SLOAD_COST + pub cold_sload_cost: usize, + /// EIP-2929 COLD_ACCOUNT_ACCESS_COST + pub cold_account_access_cost: usize, + /// EIP-2929 WARM_STORAGE_READ_COST + pub warm_storage_read_cost: usize, /// Stipend for transfer for `CALL|CALLCODE` opcode when `value>0` pub call_stipend: usize, /// Additional gas required for value transfer (`CALL|CALLCODE`) @@ -132,6 +147,8 @@ pub struct Schedule { pub keep_unsigned_nonce: bool, /// Wasm extra schedule settings, if wasm activated pub wasm: Option, + /// Enable EIP-2929 rules + pub eip2929: bool, } /// Wasm cost table @@ -245,6 +262,9 @@ impl Schedule { log_topic_gas: 375, create_gas: 32000, call_gas: 700, + cold_account_access_cost: 0, + cold_sload_cost: 0, + warm_storage_read_cost: 0, call_stipend: 2300, call_value_transfer_gas: 9000, call_new_account_gas: 25000, @@ -274,6 +294,7 @@ impl Schedule { eip1706: false, keep_unsigned_nonce: false, wasm: None, + eip2929: false, } } @@ -290,7 +311,8 @@ impl Schedule { /// Schedule for the Constantinople fork of the Ethereum main net. pub fn new_constantinople() -> Schedule { let mut schedule = Self::new_byzantium(); - schedule.have_bitwise_shifting = true; + schedule.have_bitwise_shifting = true; // EIP 145 + schedule.have_extcodehash = true; // EIP 1052 schedule } @@ -310,6 +332,30 @@ impl Schedule { pub fn new_berlin() -> Schedule { let mut schedule = Self::new_istanbul(); schedule.have_subs = true; // EIP 2315 + + schedule + } + + /// Schedule for the Yolov3 testnet of the Ethereum main net. + pub fn new_yolo3() -> Schedule { + let mut schedule = Self::new_istanbul(); + schedule.have_subs = true; // EIP 2315 + + schedule.eip1283 = true; + schedule.eip2929 = true; + + schedule.cold_sload_cost = EIP2929_COLD_SLOAD_COST; + schedule.cold_account_access_cost = EIP2929_COLD_ACCOUNT_ACCESS_COST; + schedule.warm_storage_read_cost = EIP2929_WARM_STORAGE_READ_COST; + + schedule.sload_gas = EIP2929_WARM_STORAGE_READ_COST; + schedule.call_gas = EIP2929_COLD_ACCOUNT_ACCESS_COST; + schedule.balance_gas = EIP2929_COLD_ACCOUNT_ACCESS_COST; + schedule.extcodecopy_base_gas = EIP2929_COLD_ACCOUNT_ACCESS_COST; + schedule.extcodehash_gas = EIP2929_COLD_ACCOUNT_ACCESS_COST; + schedule.extcodesize_gas = EIP2929_COLD_ACCOUNT_ACCESS_COST; + schedule.sstore_reset_gas = EIP2929_SSTORE_RESET_GAS; + schedule } @@ -342,6 +388,9 @@ impl Schedule { log_topic_gas: 375, create_gas: 32000, call_gas: 40, + cold_account_access_cost: 0, + cold_sload_cost: 0, + warm_storage_read_cost: 0, call_stipend: 2300, call_value_transfer_gas: 9000, call_new_account_gas: 25000, @@ -371,6 +420,7 @@ impl Schedule { eip1706: false, keep_unsigned_nonce: false, wasm: None, + eip2929: false, } } diff --git a/ethcore/vm/src/tests.rs b/ethcore/vm/src/tests.rs index a5ada1dd2..17a3f09d7 100644 --- a/ethcore/vm/src/tests.rs +++ b/ethcore/vm/src/tests.rs @@ -19,6 +19,7 @@ use std::{ sync::Arc, }; +use crate::access_list::AccessList; use bytes::Bytes; use error::TrapKind; use ethereum_types::{Address, H256, U256}; @@ -75,6 +76,7 @@ pub struct FakeExt { pub balances: HashMap, pub tracing: bool, pub is_static: bool, + pub access_list: AccessList, chain_id: u64, } @@ -122,6 +124,19 @@ impl FakeExt { ext } + /// New fake externalities with YoloV2 schedule rules + pub fn new_yolo3(from: Address, to: Address, builtins: &[Address]) -> Self { + let mut ext = FakeExt::default(); + ext.schedule = Schedule::new_yolo3(); + ext.access_list.enable(); + ext.access_list.insert_address(from); + ext.access_list.insert_address(to); + for builtin in builtins { + ext.access_list.insert_address(*builtin); + } + ext + } + /// Alter fake externalities to allow wasm pub fn with_wasm(mut self) -> Self { self.schedule.wasm = Some(Default::default()); @@ -162,7 +177,7 @@ impl Ext for FakeExt { } fn balance(&self, address: &Address) -> Result { - Ok(self.balances[address]) + Ok(self.balances.get(address).cloned().unwrap_or(U256::zero())) } fn blockhash(&mut self, number: &U256) -> H256 { @@ -191,6 +206,10 @@ impl Ext for FakeExt { Ok(ContractCreateResult::Failed) } + fn calc_address(&self, _code: &[u8], _address: CreateContractAddress) -> Option
{ + None + } + fn call( &mut self, gas: &U256, @@ -276,4 +295,24 @@ impl Ext for FakeExt { fn trace_next_instruction(&mut self, _pc: usize, _instruction: u8, _gas: U256) -> bool { self.tracing } + + fn al_is_enabled(&self) -> bool { + self.access_list.is_enabled() + } + + fn al_contains_storage_key(&self, address: &Address, key: &H256) -> bool { + self.access_list.contains_storage_key(address, key) + } + + fn al_insert_storage_key(&mut self, address: Address, key: H256) { + self.access_list.insert_storage_key(address, key) + } + + fn al_contains_address(&self, address: &Address) -> bool { + self.access_list.contains_address(address) + } + + fn al_insert_address(&mut self, address: Address) { + self.access_list.insert_address(address) + } } diff --git a/evmbin/src/main.rs b/evmbin/src/main.rs index 7853788c7..46585818e 100644 --- a/evmbin/src/main.rs +++ b/evmbin/src/main.rs @@ -84,7 +84,7 @@ State test options: --chain CHAIN Run only from specific chain name (i.e. one of EIP150, EIP158, Frontier, Homestead, Byzantium, Constantinople, ConstantinopleFix, Istanbul, EIP158ToByzantiumAt5, FrontierToHomesteadAt5, - HomesteadToDaoAt5, HomesteadToEIP150At5). + HomesteadToDaoAt5, HomesteadToEIP150At5, Berlin, Yolo3). --only NAME Runs only a single test matching the name. General options: @@ -197,7 +197,6 @@ fn run_state_test(args: Args) { { continue; } - for (idx, state) in states.into_iter().enumerate() { let post_root = state.hash.into(); let transaction = multitransaction.select(&state.indexes).into(); @@ -287,8 +286,16 @@ fn run_call(args: Args, informant: T) { if code.is_none() && to == Address::default() { die("Either --code or --to is required."); } - let mut params = ActionParams::default(); + if spec.engine.params().eip2929_transition == 0 { + params.access_list.enable(); + params.access_list.insert_address(from); + params.access_list.insert_address(to); + for (builtin, _) in spec.engine.builtins() { + params.access_list.insert_address(*builtin); + } + } + params.call_type = if code.is_none() { CallType::Call } else { @@ -378,9 +385,16 @@ impl Args { pub fn spec(&self) -> Result { Ok(match self.flag_chain { - Some(ref filename) => { - let file = fs::File::open(filename).map_err(|e| format!("{}", e))?; - spec::Spec::load(&::std::env::temp_dir(), file)? + Some(ref spec_name) => { + let fork_spec: Result = + serde_json::from_str(&format!("{:?}", spec_name)); + if let Ok(fork_spec) = fork_spec { + ethcore::client::EvmTestClient::spec_from_json(&fork_spec) + .expect("this forkspec is not defined") + } else { + let file = fs::File::open(spec_name).map_err(|e| format!("{}", e))?; + spec::Spec::load(&::std::env::temp_dir(), file)? + } } None => ethcore::ethereum::new_foundation(&::std::env::temp_dir()), }) diff --git a/json/src/spec/params.rs b/json/src/spec/params.rs index 150e9ce1a..6366ebcb7 100644 --- a/json/src/spec/params.rs +++ b/json/src/spec/params.rs @@ -106,6 +106,8 @@ pub struct Params { /// See `CommonParams` docs. pub eip2315_transition: Option, /// See `CommonParams` docs. + pub eip2929_transition: Option, + /// See `CommonParams` docs. pub dust_protection_transition: Option, /// See `CommonParams` docs. pub nonce_cap_increment: Option, diff --git a/json/src/spec/spec.rs b/json/src/spec/spec.rs index e5e23be77..250562d86 100644 --- a/json/src/spec/spec.rs +++ b/json/src/spec/spec.rs @@ -38,6 +38,7 @@ pub enum ForkSpec { ByzantiumToConstantinopleAt5, ByzantiumToConstantinopleFixAt5, Berlin, + Yolo3, } /// Spec deserialization. diff --git a/rpc/src/v1/tests/mocked/personal.rs b/rpc/src/v1/tests/mocked/personal.rs index f940351e0..c3dd68198 100644 --- a/rpc/src/v1/tests/mocked/personal.rs +++ b/rpc/src/v1/tests/mocked/personal.rs @@ -27,7 +27,6 @@ use parking_lot::Mutex; use types::transaction::{Action, Transaction}; use ethkey::Secret; -use rustc_hex::ToHex; use serde_json::to_value; use v1::{ helpers::{ @@ -92,6 +91,9 @@ fn setup_with(c: Config) -> PersonalTester { tester } +#[cfg(test)] +use rustc_hex::ToHex; + #[test] fn accounts() { let tester = setup();