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();