Skip to content
Open
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
31 changes: 25 additions & 6 deletions tooling/ef_tests/state_v2/src/modules/block_runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,26 @@ use crate::modules::{
};

pub async fn run_tests(tests: Vec<Test>) -> Result<(), RunnerError> {
// Fusaka EIPs that block-mode supports; mirrors the allowlist in runner.rs.
// TODO: drop once all Fusaka EIPs land.
let fusaka_eips_to_test: Vec<&str> =
vec!["eip-7594", "eip-7939", "eip-7918", "eip-7892", "eip-7883"];

for test in &tests {
// Apply the same gating runner.rs uses so we don't unconditionally run
// every Osaka fixture in block mode. Fixtures without `_info` (e.g.
// goevmlab-generated) bypass the filter — we can't read the EIP list,
// so silently dropping them would be wrong.
if test.path.to_str().unwrap().contains("osaka")
&& let Some(spec) = test
._info
.as_ref()
.and_then(|info| info.reference_spec.as_deref())
&& !fusaka_eips_to_test.iter().any(|eip| spec.contains(eip))
{
continue;
}

println!("Running test group: {}", test.name);
for test_case in &test.test_cases {
let res = run_test(test, test_case).await;
Expand All @@ -43,7 +62,7 @@ pub async fn run_test(test: &Test, test_case: &TestCase) -> Result<(), RunnerErr
let tracer = LevmCallTracer::disabled();

let (mut db, initial_block_hash, store, _genesis) =
load_initial_state(test, &test_case.fork).await;
load_initial_state(test, &test_case.fork, false).await;
let mut vm = VM::new(env.clone(), &mut db, &tx, tracer, VMType::L1, &NativeCrypto)
.map_err(RunnerError::VMError)?;
let execution_result = vm.execute();
Expand Down Expand Up @@ -81,7 +100,7 @@ pub async fn run_test(test: &Test, test_case: &TestCase) -> Result<(), RunnerErr
// So they could be specified in the test but if the fork is e.g. Paris we should set them to None despite that.
// Otherwise it will fail block header validations
let (excess_blob_gas, blob_gas_used, parent_beacon_block_root, requests_hash) = match fork {
Fork::Prague | Fork::Cancun => {
Fork::Cancun | Fork::Prague | Fork::Osaka => {
let blob_gas_used = match tx {
Transaction::EIP4844Transaction(blob_tx) => {
Some(get_total_blob_gas(&blob_tx) as u64)
Expand All @@ -97,10 +116,10 @@ pub async fn run_test(test: &Test, test_case: &TestCase) -> Result<(), RunnerErr
.unwrap(),
);
let parent_beacon_block_root = Some(H256::zero());
let requests_hash = if fork == Fork::Prague {
Some(*DEFAULT_REQUESTS_HASH)
} else {
None
// Prague added requests; Osaka inherits the same mechanism.
let requests_hash = match fork {
Fork::Prague | Fork::Osaka => Some(*DEFAULT_REQUESTS_HASH),
_ => None,
};
(
excess_blob_gas,
Expand Down
27 changes: 12 additions & 15 deletions tooling/ef_tests/state_v2/src/modules/report.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,26 +75,23 @@ pub fn write_failing_test_to_report(test: &Test, failing_test_cases: Vec<PostChe
Cell::new("Path"),
Cell::new(&test.path.display().to_string()),
]));
let description = test
._info
.as_ref()
.and_then(|info| info.description.clone().or_else(|| info.comment.clone()))
.unwrap_or_else(|| "No description or comment".to_string());
header_table.add_row(Row::new(vec![
Cell::new("Description"),
Cell::new(
&test._info.description.clone().unwrap_or(
test._info
.comment
.clone()
.unwrap_or("No description or comment".to_string()),
),
),
Cell::new(&description),
]));
let reference_spec = test
._info
.as_ref()
.and_then(|info| info.reference_spec.clone())
.unwrap_or_else(|| "No reference spec".to_string());
header_table.add_row(Row::new(vec![
Cell::new("Reference"),
Cell::new(
&test
._info
.reference_spec
.clone()
.unwrap_or("No reference spec".to_string()),
),
Cell::new(&reference_spec),
]));

let header_content = format!("{}\n", header_table);
Expand Down
14 changes: 10 additions & 4 deletions tooling/ef_tests/state_v2/src/modules/runner.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,16 @@ pub async fn run_tests(tests: Vec<Test>) -> Result<(), RunnerError> {
vec!["eip-7594", "eip-7939", "eip-7918", "eip-7892", "eip-7883"];

for test in tests {
let test_eip = test._info.clone().reference_spec.unwrap_or_default();

// Fusaka EIP allowlist only applies when `_info.reference_spec` is
// present. Fixtures without it (e.g. goevmlab-generated) bypass the
// filter so they aren't silently dropped just because we can't read
// the EIP list.
if test.path.to_str().unwrap().contains("osaka")
&& !fusaka_eips_to_test.iter().any(|eip| test_eip.contains(eip))
&& let Some(spec) = test
._info
.as_ref()
.and_then(|info| info.reference_spec.as_deref())
&& !fusaka_eips_to_test.iter().any(|eip| spec.contains(eip))
{
continue;
}
Expand All @@ -66,7 +72,7 @@ pub async fn run_test(
for test_case in &test.test_cases {
// Setup VM for transaction.
let (mut db, initial_block_hash, storage, genesis) =
load_initial_state(test, &test_case.fork).await;
load_initial_state(test, &test_case.fork, true).await;
let env = get_vm_env_for_test(test.env, test_case)?;
let tx = get_tx_from_test_case(test_case).await?;
let tracer = LevmCallTracer::disabled();
Expand Down
2 changes: 1 addition & 1 deletion tooling/ef_tests/state_v2/src/modules/statetest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ async fn run_case(
emit_trace: bool,
) -> Result<bool, RunnerError> {
let (mut db, initial_block_hash, storage, _genesis) =
load_initial_state(test, &test_case.fork).await;
load_initial_state(test, &test_case.fork, true).await;
let env = get_vm_env_for_test(test.env, test_case)?;
let tx = get_tx_from_test_case(test_case).await?;

Expand Down
117 changes: 103 additions & 14 deletions tooling/ef_tests/state_v2/src/modules/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,14 @@ use std::{
path::PathBuf,
};

const DEFAULT_FORKS: [&str; 5] = ["Merge", "Shanghai", "Cancun", "Prague", "Amsterdam"];
const DEFAULT_FORKS: [&str; 6] = [
"Merge",
"Shanghai",
"Cancun",
"Prague",
"Osaka",
"Amsterdam",
];

/// `Tests` structure is the result of parsing a whole `.json` file from the EF tests. This file includes at
/// least one general test enviroment and different test cases inside each enviroment.
Expand Down Expand Up @@ -117,17 +124,19 @@ impl Tests {
test_data: &HashMap<String, Value>,
test_cases: Vec<TestCase>,
) -> Result<Test, serde_json::Error> {
// Obtain the value of the `info` field in the JSON.
let info_field = test_data
.get("_info")
.ok_or(serde::de::Error::missing_field("_info"))?;
// Parse the field value as `Info`.
let test_info = serde_json::from_value(info_field.clone()).map_err(|err| {
serde::de::Error::custom(format!(
"Failed to deserialize `info` field in test {}. Serde error: {}",
test_name, err
))
})?;
// The `_info` field is optional — EF fixtures populate it but
// goevmlab-generated fixtures may omit it.
let test_info = match test_data.get("_info") {
Some(info_field) => {
Some(serde_json::from_value(info_field.clone()).map_err(|err| {
serde::de::Error::custom(format!(
"Failed to deserialize `info` field in test {}. Serde error: {}",
test_name, err
))
})?)
}
None => None,
};
// Obtain the value of the `env` field in the JSON.
let env_field = test_data
.get("env")
Expand Down Expand Up @@ -226,8 +235,10 @@ impl Tests {
pub struct Test {
pub name: String, // The name of the test object inside the .json file.
pub path: PathBuf, // The path of the .json file the Test can be found at.
pub _info: Info, // General information about the test.
pub env: Env, // The block enviroment before the test transaction happens.
/// General information about the test (optional — present in EF fixtures,
/// may be absent in goevmlab-generated ones).
pub _info: Option<Info>,
pub env: Env, // The block enviroment before the test transaction happens.
pub pre: HashMap<Address, AccountState>, // The accounts state previous to the test transaction.
pub test_cases: Vec<TestCase>, // A vector of specific cases to be tested under these conditions (transactions).
}
Expand Down Expand Up @@ -295,6 +306,8 @@ pub fn genesis_from_test_and_fork(test: &Test, fork: &Fork) -> Genesis {
schedule.cancun.target
} else if *fork == Fork::Prague {
schedule.prague.target
} else if *fork == Fork::Osaka {
schedule.osaka.target
} else {
0
};
Expand Down Expand Up @@ -560,3 +573,79 @@ pub struct RawTransaction {
#[serde(default, deserialize_with = "deserialize_authorization_lists")]
pub authorization_list: Option<Vec<AuthorizationListTuple>>,
}

#[cfg(test)]
mod tests {
use super::*;

/// Minimal fixture body — Tests::deserialize parses every test object as a
/// (test_name -> raw fields) map, so the inner fields just need to be
/// shape-correct enough for the per-field parsers downstream.
fn fixture_json(with_info: bool) -> String {
let info = if with_info {
r#""_info": { "comment": "goevmlab-generated" },"#
} else {
""
};
format!(
r#"{{
"blockhash_divergence": {{
{info}
"env": {{
"currentCoinbase": "0xb94f5374fce5edbc8e2a8697c15331677e6ebf0b",
"currentDifficulty": "0x200000",
"currentRandom": "0x0000000000000000000000000000000000000000000000000000000000200000",
"currentGasLimit": "0x26e1f476fe1e22",
"currentNumber": "0x1",
"currentTimestamp": "0x3e8",
"previousHash": "0x044852b2a670ade5407e78fb2863c51de9fcb96542a07186fe3aeda6bb8a116d",
"currentBaseFee": "0x10"
}},
"pre": {{}},
"transaction": {{
"gasPrice": "0x10",
"nonce": "0x0",
"to": "0x00000000000000000000000000000000000000f1",
"data": ["0x"],
"gasLimit": ["0x5f5e100"],
"value": ["0x0"],
"secretKey": "0x45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8",
"sender": "0xa94f5374fce5edbc8e2a8697c15331677e6ebf0b"
}},
"post": {{
"Prague": [
{{
"hash": "0x0000000000000000000000000000000000000000000000000000000000000000",
"logs": "0x0000000000000000000000000000000000000000000000000000000000000000",
"indexes": {{ "data": 0, "gas": 0, "value": 0 }}
}}
]
}}
}}
}}"#
)
}

#[test]
fn fixture_without_info_parses() {
let json = fixture_json(false);
let tests: Tests = serde_json::from_str(&json).expect("must parse without _info");
assert_eq!(tests.0.len(), 1);
assert!(
tests.0[0]._info.is_none(),
"_info should be None when absent"
);
}

#[test]
fn fixture_with_info_still_parses() {
let json = fixture_json(true);
let tests: Tests = serde_json::from_str(&json).expect("must parse with _info");
assert_eq!(tests.0.len(), 1);
let info = tests.0[0]
._info
.as_ref()
.expect("_info should be Some when present");
assert_eq!(info.comment.as_deref(), Some("goevmlab-generated"));
}
}
Loading
Loading