Skip to content
This repository was archived by the owner on Nov 20, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
[submodule "jsontests/res/ethtests"]
path = jsontests/res/ethtests
url = https://github.com/ethereum/tests
tag = 9.0.5
50 changes: 44 additions & 6 deletions ethjson/src/test_helpers/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ use crate::{
uint::Uint,
vm::Env,
};
use ethereum_types::U256;
use serde::Deserialize;
use std::collections::BTreeMap;

Expand All @@ -54,11 +55,18 @@ pub struct MultiTransaction {
pub data: Vec<Bytes>,
/// Access lists (see EIP-2930)
#[serde(default)]
pub access_lists: Vec<AccessList>,
pub access_lists: Vec<Option<AccessList>>,
/// Gas limit set.
pub gas_limit: Vec<Uint>,
/// Gas price.
#[serde(default)]
pub gas_price: Uint,
/// for details on `maxFeePerGas` see EIP-1559
#[serde(default)]
pub max_fee_per_gas: Uint,
/// for details on `maxPriorityFeePerGas` see EIP-1559
#[serde(default)]
pub max_priority_fee_per_gas: Uint,
/// Nonce.
pub nonce: Uint,
/// Secret key.
Expand All @@ -71,21 +79,51 @@ pub struct MultiTransaction {
}

impl MultiTransaction {
/// max_priority_fee_per_gas (see EIP-1559)
pub fn max_priority_fee_per_gas(&self) -> U256 {
if self.max_priority_fee_per_gas.0.is_zero() {
self.gas_price.0
} else {
self.max_priority_fee_per_gas.0
}
}

/// max_fee_per_gas (see EIP-1559)
pub fn max_fee_per_gas(&self) -> U256 {
if self.max_fee_per_gas.0.is_zero() {
self.gas_price.0
} else {
self.max_fee_per_gas.0
}
}

/// Build transaction with given indexes.
pub fn select(&self, indexes: &PostStateIndexes) -> Transaction {
let data_index = indexes.data as usize;
let access_list = if data_index < self.access_lists.len() {
self.access_lists[data_index]
.iter()
.map(|a| (a.address, a.storage_keys.clone()))
self.access_lists
.get(data_index)
.unwrap()
.as_ref()
.cloned()
.unwrap_or_default()
.into_iter()
.map(|a| (a.address, a.storage_keys))
.collect()
} else {
Vec::new()
};

let gas_price = if self.gas_price.0.is_zero() {
self.max_fee_per_gas.0 + self.max_priority_fee_per_gas.0
} else {
self.gas_price.0
};

Transaction {
data: self.data[data_index].clone(),
gas_limit: self.gas_limit[indexes.gas as usize],
gas_price: self.gas_price,
gas_price: Uint(gas_price),
nonce: self.nonce,
to: self.to.clone(),
value: self.value[indexes.value as usize],
Expand All @@ -103,7 +141,7 @@ pub type AccessList = Vec<AccessListTuple>;

/// Access list tuple (see https://eips.ethereum.org/EIPS/eip-2930).
/// Example test spec: https://github.com/ethereum/tests/blob/5490db3ff58d371c0c74826280256ba016b0bd5c/GeneralStateTests/stExample/accessListExample.json
#[derive(Debug, PartialEq, Deserialize)]
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct AccessListTuple {
/// Address to access
Expand Down
7 changes: 6 additions & 1 deletion ethjson/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,10 @@ pub struct Env {
/// Timestamp.
#[serde(rename = "currentTimestamp")]
pub timestamp: Uint,
/// Block base fee (see EIP-1559)
#[serde(rename = "currentBaseFee")]
#[serde(default)]
pub block_base_fee_per_gas: Uint,
}

#[cfg(test)]
Expand Down Expand Up @@ -187,7 +191,8 @@ mod tests {
difficulty: Uint(0x0100.into()),
gas_limit: Uint(0x0f4240.into()),
number: Uint(0.into()),
timestamp: Uint(1.into())
timestamp: Uint(1.into()),
block_base_fee_per_gas: Uint(0.into())
}
);
assert_eq!(
Expand Down
2 changes: 1 addition & 1 deletion evm
Submodule evm updated from 6d8535 to 81132c
2 changes: 1 addition & 1 deletion jsontests/res/ethtests
170 changes: 120 additions & 50 deletions jsontests/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,44 @@ impl Test {
sender
}

pub fn unwrap_to_vicinity(&self) -> MemoryVicinity {
MemoryVicinity {
gas_price: self.0.transaction.gas_price.clone().into(),
pub fn unwrap_to_vicinity(&self, spec: &ForkSpec) -> Option<MemoryVicinity> {
let block_base_fee_per_gas = self.0.env.block_base_fee_per_gas.0;
let gas_price = if self.0.transaction.gas_price.0.is_zero() {
let max_fee_per_gas = self.0.transaction.max_fee_per_gas.0;

// max_fee_per_gas is only defined for London and later
if !max_fee_per_gas.is_zero() && spec < &ForkSpec::London {
return None;
}

// Cannot specify a lower fee than the base fee
if max_fee_per_gas < block_base_fee_per_gas {
return None;
}

let max_priority_fee_per_gas = self.0.transaction.max_priority_fee_per_gas.0;

// priority fee must be lower than regaular fee
if max_fee_per_gas < max_priority_fee_per_gas {
return None;
}

let priority_fee_per_gas = std::cmp::min(
max_priority_fee_per_gas,
max_fee_per_gas - block_base_fee_per_gas,
);
priority_fee_per_gas + block_base_fee_per_gas
} else {
self.0.transaction.gas_price.0
};

// gas price cannot be lower than base fee
if gas_price < block_base_fee_per_gas {
return None;
}

Some(MemoryVicinity {
gas_price,
origin: self.unwrap_caller(),
block_hashes: Vec::new(),
block_number: self.0.env.number.clone().into(),
Expand All @@ -44,7 +79,8 @@ impl Test {
block_difficulty: self.0.env.difficulty.clone().into(),
block_gas_limit: self.0.env.gas_limit.clone().into(),
chain_id: U256::one(),
}
block_base_fee_per_gas,
})
}
}

Expand Down Expand Up @@ -105,6 +141,8 @@ impl JsonPrecompile {
precompile_entry!(map, BERLIN_BUILTINS, 9);
Some(map)
}
// precompiles for London and Berlin are the same
ForkSpec::London => Self::precompile(&ForkSpec::Berlin),
_ => None,
}
}
Expand Down Expand Up @@ -175,71 +213,103 @@ fn test_run(name: &str, test: Test) {
let (gasometer_config, delete_empty) = match spec {
ethjson::spec::ForkSpec::Istanbul => (Config::istanbul(), true),
ethjson::spec::ForkSpec::Berlin => (Config::berlin(), true),
ethjson::spec::ForkSpec::London => (Config::london(), true),
spec => {
println!("Skip spec {:?}", spec);
continue;
}
};

let original_state = test.unwrap_to_pre_state();
let vicinity = test.unwrap_to_vicinity();
let vicinity = test.unwrap_to_vicinity(spec);
if vicinity.is_none() {
// if vicinity could not be computed then the transaction was invalid so we simply
// check the original state and move on
assert_valid_hash(&states.first().unwrap().hash.0, &original_state);
continue;
}
let vicinity = vicinity.unwrap();
let caller = test.unwrap_caller();
let caller_balance = original_state.get(&caller).unwrap().balance;

for (i, state) in states.iter().enumerate() {
print!("Running {}:{:?}:{} ... ", name, spec, i);
flush();

let transaction = test.0.transaction.select(&state.indexes);
let gas_limit: u64 = transaction.gas_limit.into();
let data: Vec<u8> = transaction.data.into();

let mut backend = MemoryBackend::new(&vicinity, original_state.clone());
let metadata =
StackSubstateMetadata::new(transaction.gas_limit.into(), &gasometer_config);
let executor_state = MemoryStackState::new(metadata, &backend);
let precompile = JsonPrecompile::precompile(spec).unwrap();
let mut executor =
StackExecutor::new_with_precompiles(executor_state, &gasometer_config, &precompile);
let total_fee = vicinity.gas_price * gas_limit;

executor.state_mut().withdraw(caller, total_fee).unwrap();

let access_list = transaction
.access_list
.into_iter()
.map(|(address, keys)| (address.0, keys.into_iter().map(|k| k.0).collect()))
.collect();

match transaction.to {
ethjson::maybe::MaybeEmpty::Some(to) => {
let data = data;
let value = transaction.value.into();

let _reason = executor.transact_call(
caller,
to.into(),
value,
data,
gas_limit,
access_list,
);
}
ethjson::maybe::MaybeEmpty::None => {
let code = data;
let value = transaction.value.into();

let _reason =
executor.transact_create(caller, value, code, gas_limit, access_list);
// Only execute valid transactions
if let Ok(transaction) = crate::utils::transaction::validate(
transaction,
test.0.env.gas_limit.0,
caller_balance,
&gasometer_config,
) {
let gas_limit: u64 = transaction.gas_limit.into();
let data: Vec<u8> = transaction.data.into();
let metadata =
StackSubstateMetadata::new(transaction.gas_limit.into(), &gasometer_config);
let executor_state = MemoryStackState::new(metadata, &backend);
let precompile = JsonPrecompile::precompile(spec).unwrap();
let mut executor = StackExecutor::new_with_precompiles(
executor_state,
&gasometer_config,
&precompile,
);
let total_fee = vicinity.gas_price * gas_limit;

executor.state_mut().withdraw(caller, total_fee).unwrap();

let access_list = transaction
.access_list
.into_iter()
.map(|(address, keys)| (address.0, keys.into_iter().map(|k| k.0).collect()))
.collect();

match transaction.to {
ethjson::maybe::MaybeEmpty::Some(to) => {
let data = data;
let value = transaction.value.into();

let _reason = executor.transact_call(
caller,
to.into(),
value,
data,
gas_limit,
access_list,
);
}
ethjson::maybe::MaybeEmpty::None => {
let code = data;
let value = transaction.value.into();

let _reason =
executor.transact_create(caller, value, code, gas_limit, access_list);
}
}

let actual_fee = executor.fee(vicinity.gas_price);
let mniner_reward = if let ForkSpec::London = spec {
// see EIP-1559
let max_priority_fee_per_gas = test.0.transaction.max_priority_fee_per_gas();
let max_fee_per_gas = test.0.transaction.max_fee_per_gas();
let base_fee_per_gas = vicinity.block_base_fee_per_gas;
let priority_fee_per_gas =
std::cmp::min(max_priority_fee_per_gas, max_fee_per_gas - base_fee_per_gas);
executor.fee(priority_fee_per_gas)
} else {
actual_fee
};
executor
.state_mut()
.deposit(vicinity.block_coinbase, mniner_reward);
executor.state_mut().deposit(caller, total_fee - actual_fee);
let (values, logs) = executor.into_state().deconstruct();
backend.apply(values, logs, delete_empty);
}

let actual_fee = executor.fee(vicinity.gas_price);
executor
.state_mut()
.deposit(vicinity.block_coinbase, actual_fee);
executor.state_mut().deposit(caller, total_fee - actual_fee);
let (values, logs) = executor.into_state().deconstruct();
backend.apply(values, logs, delete_empty);
assert_valid_hash(&state.hash.0, backend.state());

println!("passed");
Expand Down
Loading