Skip to content
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
60 changes: 35 additions & 25 deletions cmd/ef_tests/test_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use ethereum_rust_core::{
types::{Account as CoreAccount, Block as CoreBlock, Transaction as CoreTransaction},
Address,
};
use ethereum_rust_evm::{apply_state_transitions, evm_state, execute_tx, EvmState, SpecId};
use ethereum_rust_evm::{
apply_state_transitions, evm_state, execute_tx, process_withdrawals, EvmState, SpecId,
};
use ethereum_rust_storage::{EngineType, Store};

pub fn execute_test(test_key: &str, test: &TestUnit, check_post_state: bool) {
Expand All @@ -16,30 +18,46 @@ pub fn execute_test(test_key: &str, test: &TestUnit, check_post_state: bool) {
let blocks = test.blocks.clone();
// Execute all txs in the test unit
for block in blocks.iter() {
let block_header = block.block_header.clone().unwrap();
let transactions = block.transactions.as_ref().unwrap();
for transaction in transactions.iter() {
let block_header = block.header().clone();
for (tx_index, transaction) in block.transactions().iter().enumerate() {
assert_eq!(
transaction.clone().sender,
CoreTransaction::from(transaction.clone()).sender(),
"Expected sender address differs from derived sender address on test: {}",
test_key
);
assert!(
execute_tx(
&transaction.clone().into(),
&block_header.clone().into(),
&mut evm_state,
SpecId::CANCUN,
)
.is_ok(), //TODO: Assert ExecutionResult depending on test case
"Transaction execution failed on test: {}",
test_key
let execution_result = execute_tx(
&transaction.clone().into(),
&block_header.clone().into(),
&mut evm_state,
SpecId::CANCUN,
);
// If this is the last tx in a block that is expecting an exception then we must make sure it fails
// TODO: Check that the exception is the one in the test unit
let is_last_tx = block.transactions().len() == tx_index + 1;
if block.expect_exception.is_some() && is_last_tx {
assert!(
execution_result.is_err(),
"Expected transaction execution to fail on test: {}",
test_key
)
} else {
assert!(
execution_result.is_ok(),
"Transaction execution failed on test: {} with error: {}",
test_key,
execution_result.unwrap_err()
)
}
}
// Apply state transitions
apply_state_transitions(&mut evm_state).expect("Failed to update DB state");
// Process withdrawals (if present)
if let Some(withdrawals) = block.withdrawals() {
process_withdrawals(evm_state.database(), withdrawals)
.expect("DB error when processing withdrawals")
}
}
// Apply state transitions
apply_state_transitions(&mut evm_state).expect("Failed to update DB state");
// Check post state
if check_post_state {
check_poststate_against_db(&test.post_state, evm_state.database())
Expand All @@ -52,9 +70,6 @@ pub fn parse_test_file(path: &Path) -> HashMap<String, TestUnit> {
tests
}

//TODO: We shouldn't skip validating the tests with the field expect_exception.
// We should run them and assert that those return the specified exception.
// From the vectors/cancun tests, only tests in eip4844_blobs expect exceptions.
pub fn validate_test(test: &TestUnit) {
// check that the decoded genesis block header matches the deserialized one
let genesis_rlp = test.genesis_rlp.clone();
Expand All @@ -66,15 +81,10 @@ pub fn validate_test(test: &TestUnit) {

// check that blocks can be decoded
for block in &test.blocks {
// skip the blocks with exceptions expected
if block.expect_exception.is_some() {
continue;
}

match CoreBlock::decode(block.rlp.as_ref()) {
Ok(decoded_block) => {
// check that the decoded block matches the deserialized one
assert_eq!(decoded_block, (block.clone()).into());
assert_eq!(decoded_block, (block.block().clone()).into());
let mut rlp_block = Vec::new();
// check that encoding the decoded block matches the rlp field
decoded_block.encode(&mut rlp_block);
Expand Down
6 changes: 3 additions & 3 deletions cmd/ef_tests/tests/shanghai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ datatest_stable::harness!(
// parse_and_execute,
// "vectors/shanghai/eip3860_initcode/",
// r"^.*/*",
// parse_and_execute,
// "vectors/shanghai/eip4895_withdrawals/",
// r"^.*/*",
parse_and_execute,
"vectors/shanghai/eip4895_withdrawals/",
r"^.*/*",
);
101 changes: 53 additions & 48 deletions cmd/ef_tests/types.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use bytes::Bytes;
use ethereum_rust_core::types::Withdrawal;
use ethereum_rust_core::types::{
code_hash, Account as ethereum_rustAccount, AccountInfo, Block as CoreBlock, BlockBody,
EIP1559Transaction, EIP2930Transaction, EIP4844Transaction, LegacyTransaction,
Transaction as ethereum_rustTransaction, TxKind, Withdrawal as CoreWithdrawal,
Transaction as ethereum_rustTransaction, TxKind,
};
use ethereum_rust_core::{types::BlockHeader, Address, Bloom, H160, H256, H64, U256};
use serde::{Deserialize, Serialize};
Expand All @@ -13,7 +14,7 @@ use std::collections::HashMap;
pub struct TestUnit {
#[serde(default, rename = "_info")]
pub info: Option<serde_json::Value>,
pub blocks: Vec<Block>,
pub blocks: Vec<BlockWithRLP>,
pub genesis_block_header: Header,
#[serde(rename = "genesisRLP", with = "ethereum_rust_core::serde_utils::bytes")]
pub genesis_rlp: Bytes,
Expand Down Expand Up @@ -90,39 +91,65 @@ pub struct Header {

#[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Block {
pub block_header: Option<Header>,
pub struct BlockWithRLP {
#[serde(with = "ethereum_rust_core::serde_utils::bytes")]
pub rlp: Bytes,
pub transactions: Option<Vec<Transaction>>,
pub uncle_headers: Option<Vec<Header>>,
pub withdrawals: Option<Vec<Withdrawal>>,
#[serde(flatten)]
inner: BlockInner,
pub expect_exception: Option<String>,
}

#[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
#[serde(untagged)]
pub enum BlockInner {
Block(Block),
DecodedRLP(DecodedRLPBlock),
}

#[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
pub struct DecodedRLPBlock {
rlp_decoded: Block,
}

#[derive(Debug, PartialEq, Eq, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Block {
pub block_header: Header,
#[serde(default)]
pub transactions: Vec<Transaction>,
#[serde(default)]
pub uncle_headers: Vec<Header>,
pub withdrawals: Option<Vec<Withdrawal>>,
}

impl BlockWithRLP {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

pub fn block(&self) -> &Block {
match self.inner {
BlockInner::Block(ref block) => block,
BlockInner::DecodedRLP(ref decoded) => &decoded.rlp_decoded,
}
}

pub fn header(&self) -> &Header {
&self.block().block_header
}

pub fn transactions(&self) -> &Vec<Transaction> {
&self.block().transactions
}

pub fn withdrawals(&self) -> Option<&Vec<Withdrawal>> {
self.block().withdrawals.as_ref()
}
}
impl From<Block> for CoreBlock {
fn from(val: Block) -> Self {
Self {
header: val.block_header.unwrap().into(),
header: val.block_header.into(),
body: BlockBody {
transactions: val
.transactions
.unwrap_or_default()
.iter()
.map(|t| t.clone().into())
.collect(),
ommers: val
.uncle_headers
.unwrap_or_default()
.iter()
.map(|h| h.clone().into())
.collect(),
withdrawals: match val.withdrawals {
Some(withdrawals) => {
withdrawals.iter().map(|w| Some(w.clone().into())).collect()
}
None => None,
},
transactions: val.transactions.iter().map(|t| t.clone().into()).collect(),
ommers: val.uncle_headers.iter().map(|h| h.clone().into()).collect(),
withdrawals: val.withdrawals,
},
}
}
Expand Down Expand Up @@ -154,17 +181,6 @@ pub struct Transaction {
pub to: Address,
}

#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Withdrawal {
#[serde(deserialize_with = "crate::serde_utils::u64::hex_str::deserialize")]
pub index: u64,
#[serde(deserialize_with = "crate::serde_utils::u64::hex_str::deserialize")]
pub validator_index: u64,
pub address: Address,
pub amount: U256,
}

// Conversions between EFtests & ethereum_rust types

impl From<Header> for BlockHeader {
Expand Down Expand Up @@ -314,17 +330,6 @@ impl From<Transaction> for EIP2930Transaction {
}
}

impl From<Withdrawal> for CoreWithdrawal {
fn from(value: Withdrawal) -> Self {
CoreWithdrawal {
index: value.index,
validator_index: value.validator_index,
address: value.address,
amount: value.amount,
}
}
}

impl From<Account> for ethereum_rustAccount {
fn from(val: Account) -> Self {
ethereum_rustAccount {
Expand Down
1 change: 1 addition & 0 deletions crates/core/types/constants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ pub const ELASTICITY_MULTIPLIER: u64 = 2;
pub const BASE_FEE_MAX_CHANGE_DENOMINATOR: u64 = 8;
pub const GAS_LIMIT_ADJUSTMENT_FACTOR: u64 = 1024;
pub const GAS_LIMIT_MINIMUM: u64 = 5000;
pub const GWEI_TO_WEI: u64 = 1_000_000_000;
14 changes: 13 additions & 1 deletion crates/evm/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ mod execution_result;

use db::StoreWrapper;
use ethereum_rust_core::{
types::{AccountInfo, BlockHeader, GenericTransaction, Transaction, TxKind},
types::{
AccountInfo, BlockHeader, GenericTransaction, Transaction, TxKind, Withdrawal, GWEI_TO_WEI,
},
Address, BigEndianHash, H256, U256,
};
use ethereum_rust_storage::{error::StoreError, Store};
Expand Down Expand Up @@ -219,6 +221,16 @@ pub fn apply_state_transitions(state: &mut EvmState) -> Result<(), StoreError> {
Ok(())
}

/// Processes a block's withdrawals, updating the account balances in the state
pub fn process_withdrawals(state: &Store, withdrawals: &[Withdrawal]) -> Result<(), StoreError> {
for withdrawal in withdrawals {
if !withdrawal.amount.is_zero() {
state.increment_balance(withdrawal.address, withdrawal.amount * GWEI_TO_WEI)?
}
}
Ok(())
}

/// Builds EvmState from a Store
pub fn evm_state(store: Store) -> EvmState {
EvmState(
Expand Down
11 changes: 10 additions & 1 deletion crates/storage/engines/api.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::fmt::Debug;

use bytes::Bytes;
use ethereum_types::{Address, H256};
use ethereum_types::{Address, H256, U256};

use ethereum_rust_core::types::{
Account, AccountInfo, BlockBody, BlockHash, BlockHeader, BlockNumber, Index, Receipt,
Expand Down Expand Up @@ -152,4 +152,13 @@ pub trait StoreEngine: Debug + Send {
self.remove_account_info(address)?;
self.remove_account_storage(address)
}

/// Increments the balance of an account by a given ammount (if it exists)
fn increment_balance(&mut self, address: Address, amount: U256) -> Result<(), StoreError> {
if let Some(mut account_info) = self.get_account_info(address)? {
account_info.balance = account_info.balance.saturating_add(amount);
self.add_account_info(address, account_info)?;
}
Ok(())
}
}
24 changes: 23 additions & 1 deletion crates/storage/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use ethereum_rust_core::types::{
Account, AccountInfo, Block, BlockBody, BlockHash, BlockHeader, BlockNumber, Genesis, Index,
Receipt, Transaction,
};
use ethereum_types::{Address, H256};
use ethereum_types::{Address, H256, U256};
use std::fmt::Debug;
use std::sync::{Arc, Mutex};
use tracing::info;
Expand Down Expand Up @@ -288,6 +288,13 @@ impl Store {
pub fn remove_account(&self, address: Address) -> Result<(), StoreError> {
self.engine.lock().unwrap().remove_account(address)
}

pub fn increment_balance(&self, address: Address, amount: U256) -> Result<(), StoreError> {
self.engine
.lock()
.unwrap()
.increment_balance(address, amount)
}
}

#[cfg(test)]
Expand Down Expand Up @@ -330,6 +337,7 @@ mod tests {
test_store_account_code(store.clone());
test_store_account_storage(store.clone());
test_remove_account_storage(store.clone());
test_increment_balance(store.clone());
}

fn test_store_account(store: Store) {
Expand Down Expand Up @@ -566,4 +574,18 @@ mod tests {
assert!(stored_value_beta_a.is_some());
assert!(stored_value_beta_b.is_some());
}

fn test_increment_balance(store: Store) {
let address = Address::random();
let account_info = AccountInfo {
balance: 50.into(),
..Default::default()
};
store.add_account_info(address, account_info).unwrap();
store.increment_balance(address, 25.into()).unwrap();

let stored_account_info = store.get_account_info(address).unwrap().unwrap();

assert_eq!(stored_account_info.balance, 75.into());
}
}