EIP2929 with journaling + Yolov3 (#79)

This commit is contained in:
adria0.eth 2020-11-04 19:11:05 +01:00 committed by GitHub
parent 50a4d5fa57
commit 6078eeaed7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 921 additions and 199 deletions

1
Cargo.lock generated
View File

@ -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",

View File

@ -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]

View File

@ -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!(

View File

@ -15,9 +15,10 @@
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
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<Gas: evm::CostType> Gasometer<Gas> {
ext: &dyn vm::Ext,
instruction: Instruction,
info: &InstructionInfo,
stack: &dyn Stack<U256>,
stack: &VecStack<U256>,
current_address: &Address,
current_mem_size: usize,
) -> vm::Result<InstructionRequirements<Gas>> {
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<Gas: evm::CostType> Gasometer<Gas> {
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<Gas: evm::CostType> Gasometer<Gas> {
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<Gas: evm::CostType> Gasometer<Gas> {
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<Gas: evm::CostType> Gasometer<Gas> {
);
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<Gas: evm::CostType> Gasometer<Gas> {
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<Gas: evm::CostType>(value: Gas) -> (Gas, bool) {
}
#[inline]
fn calculate_eip1283_sstore_gas<Gas: evm::CostType>(
fn calculate_eip1283_eip2929_sstore_gas<Gas: evm::CostType>(
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(

View File

@ -398,8 +398,14 @@ impl<Cost: CostType> Interpreter<Cost> {
.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<Cost: CostType> Interpreter<Cost> {
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<Cost: CostType> Interpreter<Cost> {
))
.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<Cost: CostType> Interpreter<Cost> {
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<Cost: CostType> Interpreter<Cost> {
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<Cost: CostType> Interpreter<Cost> {
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<Cost: CostType> Interpreter<Cost> {
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<Cost: CostType> Interpreter<Cost> {
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<Cost: CostType> Interpreter<Cost> {
&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());

View File

@ -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;

View File

@ -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,

View File

@ -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": {

View File

@ -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
}
}
}
}
}
}

View File

@ -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

View File

@ -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"))

View File

@ -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<Address> = 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(&params.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(&params, 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(&params, 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<T, V>(
&mut self,
params: ActionParams,
substate: &mut Substate,
stack_depth: usize,
tracer: &mut T,
vm_tracer: &mut V,
) -> vm::Result<FinalizationResult>
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<T, V>(
&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<T, V>(
&mut self,
params: ActionParams,
substate: &mut Substate,
stack_depth: usize,
tracer: &mut T,
vm_tracer: &mut V,
) -> vm::Result<FinalizationResult>
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<T, V>(
&mut self,

View File

@ -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<Address> {
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)]

View File

@ -167,6 +167,10 @@ where
Ok(ContractCreateResult::Created(contract_address, *gas))
}
fn calc_address(&self, code: &[u8], address: CreateContractAddress) -> Option<Address> {
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

View File

@ -68,7 +68,6 @@ pub fn json_chain_test<H: FnMut(&str, HookType)>(
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<H: FnMut(&str, HookType)>(
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<H: FnMut(&str, HookType)>(
}
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<H: FnMut(&str, HookType)>(
..
})) 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);

View File

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

View File

@ -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<ethjson::spec::Params> 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();

View File

@ -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<Address>,
/// 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) {

View File

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

View File

@ -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<A, B> {
fn a(&self) -> &A;
fn b(&self) -> &B;
}
impl<'a, A, B> Borrow<dyn KeyPair<A, B> + 'a> for (A, B)
where
A: Eq + Hash + 'a,
B: Eq + Hash + 'a,
{
fn borrow(&self) -> &(dyn KeyPair<A, B> + 'a) {
self
}
}
impl<A: Hash, B: Hash> Hash for (dyn KeyPair<A, B> + '_) {
fn hash<H: Hasher>(&self, state: &mut H) {
self.a().hash(state);
self.b().hash(state);
}
}
impl<A: Eq, B: Eq> PartialEq for (dyn KeyPair<A, B> + '_) {
fn eq(&self, other: &Self) -> bool {
self.a() == other.a() && self.b() == other.b()
}
}
impl<A: Eq, B: Eq> Eq for (dyn KeyPair<A, B> + '_) {}
impl<A, B> KeyPair<A, B> for (A, B) {
fn a(&self) -> &A {
&self.0
}
fn b(&self) -> &B {
&self.1
}
}
impl<A, B> KeyPair<A, B> 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<Address, usize>,
storage_keys: HashMap<(Address, H256), usize>,
}
#[derive(Debug)]
pub struct AccessList {
id: usize,
journal: Rc<RefCell<Journal>>,
}
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<Address, H256>)
} 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<Address, H256>)
{
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))
);
}
}

View File

@ -15,13 +15,13 @@
// along with OpenEthereum. If not, see <http://www.gnu.org/licenses/>.
//! 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<ethjson::vm::Transaction> for ActionParams {
false => CallType::Call,
}, // TODO @debris is this correct?
params_type: ParamsType::Separate,
access_list: AccessList::default(),
}
}
}

View File

@ -101,6 +101,9 @@ pub trait Ext {
trap: bool,
) -> ::std::result::Result<ContractCreateResult, TrapKind>;
/// Returns the address that will be created in the create call
fn calc_address(&self, code: &[u8], address: CreateContractAddress) -> Option<Address>;
/// 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);
}

View File

@ -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<Self>, result: MessageCallResult) -> Box<dyn Exec>;
}
/// 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<Self>, result: ContractCreateResult) -> Box<dyn Exec>;
}

View File

@ -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<WasmCosts>,
/// 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,
}
}

View File

@ -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<Address, U256>,
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<U256> {
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<Address> {
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)
}
}

View File

@ -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<T: Informant>(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<spec::Spec, String> {
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<ethjson::spec::ForkSpec, _> =
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()),
})

View File

@ -106,6 +106,8 @@ pub struct Params {
/// See `CommonParams` docs.
pub eip2315_transition: Option<Uint>,
/// See `CommonParams` docs.
pub eip2929_transition: Option<Uint>,
/// See `CommonParams` docs.
pub dust_protection_transition: Option<Uint>,
/// See `CommonParams` docs.
pub nonce_cap_increment: Option<Uint>,

View File

@ -38,6 +38,7 @@ pub enum ForkSpec {
ByzantiumToConstantinopleAt5,
ByzantiumToConstantinopleFixAt5,
Berlin,
Yolo3,
}
/// Spec deserialization.

View File

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