use std::str::FromStr;
use alloy_primitives::{Address, B256, U256};
use clap::Parser;
use mega_evm::revm::DatabaseRef;
use mega_evme::common::{PreStateArgs, RpcArgs};
use tempfile::tempdir;
mod common;
use common::{test_rpc_args_cached, MockRpcServer};
#[tokio::test]
async fn test_create_initial_state_non_fork_store_is_noop() {
let dir = tempdir().expect("tempdir");
let prestate_args = PreStateArgs::parse_from(["mega-evme"]);
let rpc_args = test_rpc_args_cached("http://localhost:8545", dir.path(), None);
let sender = Address::ZERO;
let (_state, cache_store) =
prestate_args.create_initial_state(&sender, &rpc_args).await.expect("create_initial_state");
assert!(cache_store.is_noop(), "non-fork must wire a no-op cache store");
cache_store.persist().expect("persist");
assert!(
!dir.path().join("rpc-cache-4326.json").exists(),
"non-fork persist must not produce any cache file",
);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_create_initial_state_fork_store_persists_cache() {
let dir = tempdir().expect("tempdir");
let server = MockRpcServer::start().await;
server.respond_eth_chain_id(4326, 1).await;
let prestate_args =
PreStateArgs::parse_from(["mega-evme", "--fork", "--fork.block", "1000000"]);
let rpc_args = test_rpc_args_cached(&server.uri(), dir.path(), None);
let sender = Address::ZERO;
let (_state, cache_store) =
prestate_args.create_initial_state(&sender, &rpc_args).await.expect("create_initial_state");
assert!(!cache_store.is_noop(), "fork + cache_dir must wire a real store");
cache_store.persist().expect("persist");
assert!(
dir.path().join("rpc-cache-4326.json").exists(),
"fork persist must write the cache file",
);
}
#[tokio::test(flavor = "multi_thread")]
async fn test_storage_cache_hit_round_trip_via_mock() {
let dir = tempdir().expect("tempdir");
let block_number: u64 = 1_234_567;
let block_number_str = block_number.to_string();
let chain_id: u64 = 4326;
let oracle =
Address::from_str("0x6342000000000000000000000000000000000001").expect("address parse");
let slot = U256::ZERO;
let storage_hex = "0x000000000000000000000000000000000000000000000000000000000000002a";
let expected_value = U256::from(0x2a);
let phase1_value = {
let server = MockRpcServer::start().await;
server.respond_eth_chain_id(chain_id, 1).await;
server.respond_jsonrpc_result(storage_hex, 2).await;
let prestate_args =
PreStateArgs::parse_from(["mega-evme", "--fork", "--fork.block", &block_number_str]);
let rpc_args = test_rpc_args_cached(&server.uri(), dir.path(), None);
let sender = Address::ZERO;
let (state, cache_store) = prestate_args
.create_initial_state(&sender, &rpc_args)
.await
.expect("create_initial_state phase 1");
let value = state.storage_ref(oracle, slot).expect("storage_ref phase 1");
cache_store.persist().expect("persist");
assert!(
server.received_request_count().await > 0,
"phase 1 must reach the mock to populate the cache",
);
value
};
assert_eq!(phase1_value, expected_value, "phase 1 must parse the mock's hex result");
assert!(
dir.path().join(format!("rpc-cache-{chain_id}.json")).exists(),
"phase 1 persist must produce the cache file",
);
let phase2_value = {
let server = MockRpcServer::start().await;
server.respond_eth_chain_id(chain_id, 1).await;
let prestate_args =
PreStateArgs::parse_from(["mega-evme", "--fork", "--fork.block", &block_number_str]);
let rpc_args = test_rpc_args_cached(&server.uri(), dir.path(), None);
let sender = Address::ZERO;
let (state, _cache_store) = prestate_args
.create_initial_state(&sender, &rpc_args)
.await
.expect("create_initial_state phase 2");
let value = state.storage_ref(oracle, slot).expect("storage_ref phase 2 — must hit cache");
assert_eq!(
server.received_request_count().await,
1,
"cache hit: only the eth_chainId request must reach the mock",
);
value
};
assert_eq!(phase1_value, phase2_value, "cache reload must return the same value");
}
#[tokio::test(flavor = "multi_thread")]
#[ignore = "requires network access; run with cargo test -- --ignored"]
async fn test_create_initial_state_fork_real_rpc_smoke() {
let dir = tempdir().expect("tempdir");
let block_number: u64 = 100_000;
let rpc_url = std::env::var("MEGA_EVME_TEST_RPC_URL")
.unwrap_or_else(|_| "https://mainnet.megaeth.com/rpc".to_string());
let block_number_str = block_number.to_string();
let prestate_args =
PreStateArgs::parse_from(["mega-evme", "--fork", "--fork.block", &block_number_str]);
let rpc_args = RpcArgs::parse_from([
"mega-evme",
"--rpc",
&rpc_url,
"--rpc.cache-size",
"256",
"--rpc.cache-dir",
dir.path().to_str().unwrap(),
]);
let sender = Address::ZERO;
let (state, cache_store) =
prestate_args.create_initial_state(&sender, &rpc_args).await.expect("create_initial_state");
assert!(!cache_store.is_noop(), "fork session must own a real cache store");
let oracle =
Address::from_str("0x6342000000000000000000000000000000000001").expect("address parse");
let account = state
.basic_ref(oracle)
.expect("basic_ref against real RPC")
.expect("MegaETH Oracle must exist on MegaETH mainnet at this block");
assert!(
account.code_hash != B256::from(alloy_primitives::KECCAK256_EMPTY),
"MegaETH Oracle must be a contract account, not an EOA",
);
cache_store.persist().expect("persist");
let cache_file = dir.path().join("rpc-cache-4326.json");
assert!(cache_file.exists(), "cache file must exist after persist");
let metadata = std::fs::metadata(&cache_file).expect("metadata");
assert!(metadata.len() > 0, "cache file must be non-empty after a real query");
}
#[tokio::test(flavor = "multi_thread")]
#[ignore = "requires network access for phase 1"]
async fn test_create_initial_state_fork_real_rpc_storage_cache_hit() {
let dir = tempdir().expect("tempdir");
let block_number: u64 = 100_000;
let block_number_str = block_number.to_string();
let rpc_url = std::env::var("MEGA_EVME_TEST_RPC_URL")
.unwrap_or_else(|_| "https://mainnet.megaeth.com/rpc".to_string());
let oracle =
Address::from_str("0x6342000000000000000000000000000000000001").expect("address parse");
let slot = U256::ZERO;
let phase1_value = {
let prestate_args =
PreStateArgs::parse_from(["mega-evme", "--fork", "--fork.block", &block_number_str]);
let rpc_args = RpcArgs::parse_from([
"mega-evme",
"--rpc",
&rpc_url,
"--rpc.cache-size",
"256",
"--rpc.cache-dir",
dir.path().to_str().unwrap(),
]);
let sender = Address::ZERO;
let (state, cache_store) = prestate_args
.create_initial_state(&sender, &rpc_args)
.await
.expect("create_initial_state phase 1");
let value =
state.storage_ref(oracle, slot).expect("storage_ref phase 1 — real RPC must succeed");
cache_store.persist().expect("persist");
value
};
let cache_file = dir.path().join("rpc-cache-4326.json");
assert!(cache_file.exists(), "cache file must exist after phase 1 persist");
let metadata = std::fs::metadata(&cache_file).expect("metadata");
assert!(metadata.len() > 0, "cache file must be non-empty after phase 1");
let phase2_value = {
let prestate_args =
PreStateArgs::parse_from(["mega-evme", "--fork", "--fork.block", &block_number_str]);
let rpc_args = RpcArgs::parse_from([
"mega-evme",
"--rpc",
&rpc_url,
"--rpc.cache-size",
"256",
"--rpc.cache-dir",
dir.path().to_str().unwrap(),
]);
let sender = Address::ZERO;
let (state, _cache_store) = prestate_args
.create_initial_state(&sender, &rpc_args)
.await
.expect("create_initial_state phase 2");
state.storage_ref(oracle, slot).expect("storage_ref phase 2 — must hit cache, not network")
};
assert_eq!(phase1_value, phase2_value, "cache reload must return the same storage value");
}