use std::collections::HashMap;
use std::sync::Arc;
use alloy_eips::BlockId;
use alloy_primitives::{Address, Bytes, U256, keccak256};
use alloy_sol_types::{SolCall, SolValue};
use anyhow::Result;
use evm_fork_cache::cache::{SimStatus, StorageBatchFetchFn};
use evm_fork_cache::freshness::{
AlwaysVerify, FreshnessController, FreshnessRegistry, SimRequest, Validation,
};
#[path = "support/mock.rs"]
mod mock;
fn balance_slot(owner: Address) -> U256 {
let key = keccak256((owner, U256::from(mock::MOCK_ERC20_BALANCE_SLOT)).abi_encode());
U256::from_be_bytes(key.0)
}
#[tokio::main(flavor = "multi_thread")]
async fn main() -> Result<()> {
let mut cache = mock::offline_cache().await?;
let token = Address::repeat_byte(0x11);
let owner = Address::repeat_byte(0x22);
let recipient = Address::repeat_byte(0x33);
mock::install_default_account(&mut cache, Address::ZERO);
mock::install_default_account(&mut cache, owner);
mock::install_mock_erc20(&mut cache, token);
let owner_slot = balance_slot(owner);
cache.inject_storage_batch(&[(token, owner_slot, U256::from(1000))]);
let fresh: HashMap<(Address, U256), U256> =
HashMap::from([((token, owner_slot), U256::from(50))]);
let fetcher: StorageBatchFetchFn =
Arc::new(move |requests: Vec<(Address, U256)>, _block: BlockId| {
requests
.into_iter()
.map(|(addr, slot)| {
let value = fresh.get(&(addr, slot)).copied().unwrap_or(U256::ZERO);
(addr, slot, Ok(value))
})
.collect()
});
cache.set_storage_batch_fetcher(fetcher);
let mut registry = FreshnessRegistry::new();
registry.pin_slot(token, U256::from(6));
println!(
"classification — slot 0 volatile? {} | slot 6 (pinned) volatile? {}",
registry.is_volatile(token, U256::from(0), 0),
registry.is_volatile(token, U256::from(6), 0),
);
let mut controller = FreshnessController::new(registry, AlwaysVerify);
let calldata = Bytes::from(
mock::MockERC20::transferCall {
to: recipient,
amount: U256::from(100),
}
.abi_encode(),
);
let request = SimRequest::new(owner, token, calldata);
let sim = controller.run(&mut cache, vec![request])?;
let optimistic = &sim.optimistic()[0];
let optimistic_succeeded = matches!(optimistic.status, SimStatus::Success);
println!("optimistic result (computed immediately, against the snapshot):");
println!(" gas_used = {}", optimistic.gas_used);
println!(
" transfer {} (emitted {} log(s))\n",
if optimistic_succeeded {
"SUCCEEDED"
} else {
"reverted"
},
optimistic.logs.len()
);
match sim.validate().await? {
Validation::ConfirmedStorage => {
println!(
"validation: ConfirmedStorage — no volatile storage slot the sim read \
had changed (account-level balance/nonce/code was NOT verified)"
);
}
Validation::ConfirmedFull => {
println!(
"validation: ConfirmedFull — storage + account fields verified, \
nothing the sim read had changed"
);
}
Validation::Corrected {
results,
changed_slots,
..
} => {
println!("validation: Corrected — a slot the sim read had changed:");
for c in &changed_slots {
println!(" {} slot {} : {} -> {}", c.address, c.slot, c.old, c.new);
}
let corrected = &results[0];
let corrected_succeeded = matches!(corrected.status, SimStatus::Success);
println!(
"\ncorrected re-run: gas_used = {}, transfer {} (emitted {} log(s))",
corrected.gas_used,
if corrected_succeeded {
"SUCCEEDED"
} else {
"REVERTED (insufficient fresh balance)"
},
corrected.logs.len()
);
assert!(
optimistic_succeeded && !corrected_succeeded,
"this example demonstrates an optimistic success corrected to a revert"
);
}
Validation::Unverified { reason } => {
println!("validation: Unverified — {reason}");
}
}
Ok(())
}