use std::hint::black_box;
use std::sync::Arc;
use alloy_primitives::{Address, Bytes, U256, hex};
use alloy_provider::RootProvider;
use alloy_provider::network::AnyNetwork;
use alloy_rpc_client::RpcClient;
use alloy_transport::mock::Asserter;
use criterion::{BatchSize, BenchmarkId, Criterion, Throughput, criterion_group, criterion_main};
use evm_fork_cache::cache::EvmCache;
use evm_fork_cache::{AccountPatch, PurgeScope, SlotDelta, StateUpdate};
use revm::state::{AccountInfo, Bytecode};
use tokio::runtime::{Builder, Runtime};
const MOCK_ERC20_RUNTIME_HEX: &str = include_str!("../fixtures/mock_erc20_runtime.hex");
const POOL: Address = Address::repeat_byte(0xAA);
fn current_thread_rt() -> Runtime {
Builder::new_current_thread().enable_all().build().unwrap()
}
fn pool_cache(rt: &Runtime) -> EvmCache {
let provider = RootProvider::<AnyNetwork>::new(RpcClient::mocked(Asserter::new()));
let mut cache = rt.block_on(EvmCache::new(Arc::new(provider)));
let runtime = Bytecode::new_raw(Bytes::from(
hex::decode(MOCK_ERC20_RUNTIME_HEX.trim()).unwrap(),
));
let code_hash = runtime.hash_slow();
cache.db_mut().insert_account_info(
POOL,
AccountInfo {
balance: U256::ZERO,
nonce: 0,
code: Some(runtime),
code_hash,
account_id: None,
},
);
cache
.db_mut()
.replace_account_storage(POOL, Default::default())
.unwrap();
cache
}
fn bench_apply_slots_batch(c: &mut Criterion) {
let rt = current_thread_rt();
let mut cache = pool_cache(&rt);
let mut group = c.benchmark_group("apply_slots_batch");
for &n in &[1usize, 10, 100, 1_000] {
let updates_a: Vec<StateUpdate> = (0..n)
.map(|i| StateUpdate::slot(POOL, U256::from(i as u64), U256::from(1u64)))
.collect();
let updates_b: Vec<StateUpdate> = (0..n)
.map(|i| StateUpdate::slot(POOL, U256::from(i as u64), U256::from(2u64)))
.collect();
group.throughput(Throughput::Elements(n as u64));
group.bench_with_input(BenchmarkId::from_parameter(n), &n, |b, _| {
let mut toggle = false;
b.iter(|| {
let updates = if toggle { &updates_a } else { &updates_b };
toggle = !toggle;
black_box(cache.apply_updates(black_box(updates)));
})
});
}
group.finish();
}
fn bench_apply_per_variant(c: &mut Criterion) {
let rt = current_thread_rt();
let mut group = c.benchmark_group("apply_per_variant");
group.bench_function("slot", |b| {
let mut cache = pool_cache(&rt);
let mut toggle = false;
b.iter(|| {
let value = if toggle { U256::from(1) } else { U256::from(2) };
toggle = !toggle;
black_box(cache.apply_update(black_box(&StateUpdate::slot(
POOL,
U256::from(0),
value,
))));
})
});
group.bench_function("account_balance", |b| {
let mut cache = pool_cache(&rt);
let mut toggle = false;
b.iter(|| {
let value = if toggle { U256::from(1) } else { U256::from(2) };
toggle = !toggle;
black_box(cache.apply_update(black_box(&StateUpdate::Account {
address: POOL,
patch: AccountPatch::default().balance(value),
})));
})
});
group.bench_function("slot_delta_hot", |b| {
let mut cache = pool_cache(&rt);
cache.inject_storage_batch(&[(POOL, U256::from(0), U256::from(1))]);
b.iter(|| {
black_box(cache.apply_update(black_box(&StateUpdate::slot_delta(
POOL,
U256::from(0),
SlotDelta::Add(U256::ZERO),
))));
})
});
group.bench_function("slot_delta_cold", |b| {
let mut cache = pool_cache(&rt);
b.iter(|| {
black_box(cache.apply_update(black_box(&StateUpdate::slot_delta(
Address::repeat_byte(0xCD),
U256::from(0),
SlotDelta::Add(U256::from(1)),
))));
})
});
group.bench_function("modify_slot", |b| {
let mut cache = pool_cache(&rt);
cache.inject_storage_batch(&[(POOL, U256::from(0), U256::from(1))]);
b.iter(|| {
black_box(cache.modify_slot(POOL, U256::from(0), |cur| {
cur.map(|v| v.saturating_add(U256::ZERO))
}));
})
});
group.bench_function("account_code", |b| {
let mut cache = pool_cache(&rt);
let code_a = Bytes::from_static(&[0x60, 0x00, 0x60, 0x00, 0xf3]);
let code_b = Bytes::from_static(&[0x60, 0x01, 0x60, 0x01, 0xf3]);
let mut toggle = false;
b.iter(|| {
let code = if toggle {
code_a.clone()
} else {
code_b.clone()
};
toggle = !toggle;
black_box(cache.apply_update(black_box(&StateUpdate::code(POOL, code))));
})
});
group.bench_function("purge_all_storage", |b| {
b.iter_batched(
|| {
let mut cache = pool_cache(&rt);
cache.inject_storage_batch(&[
(POOL, U256::from(0), U256::from(1)),
(POOL, U256::from(1), U256::from(2)),
(POOL, U256::from(2), U256::from(3)),
]);
cache
},
|mut cache| {
black_box(
cache
.apply_update(black_box(&StateUpdate::purge(POOL, PurgeScope::AllStorage))),
);
},
BatchSize::SmallInput,
)
});
group.bench_function("purge_account", |b| {
b.iter_batched(
|| {
let mut cache = pool_cache(&rt);
cache.inject_storage_batch(&[
(POOL, U256::from(0), U256::from(1)),
(POOL, U256::from(1), U256::from(2)),
]);
cache
},
|mut cache| {
black_box(
cache.apply_update(black_box(&StateUpdate::purge(POOL, PurgeScope::Account))),
);
},
BatchSize::SmallInput,
)
});
group.bench_function("purge_slots", |b| {
b.iter_batched(
|| {
let mut cache = pool_cache(&rt);
cache.inject_storage_batch(&[
(POOL, U256::from(0), U256::from(1)),
(POOL, U256::from(1), U256::from(2)),
(POOL, U256::from(2), U256::from(3)),
]);
cache
},
|mut cache| {
black_box(cache.apply_update(black_box(&StateUpdate::purge(
POOL,
PurgeScope::Slots(vec![U256::from(0), U256::from(2)]),
))));
},
BatchSize::SmallInput,
)
});
group.finish();
}
fn bench_apply_heterogeneous(c: &mut Criterion) {
let rt = current_thread_rt();
let mut group = c.benchmark_group("apply_updates_mixed");
group.bench_function("slot_account_purge", |b| {
b.iter_batched(
|| {
let mut cache = pool_cache(&rt);
cache.inject_storage_batch(&[(POOL, U256::from(9), U256::from(1))]);
cache
},
|mut cache| {
let value = U256::from(2);
black_box(cache.apply_updates(black_box(&[
StateUpdate::slot(POOL, U256::from(0), value),
StateUpdate::slot(POOL, U256::from(1), value),
StateUpdate::balance(POOL, value),
StateUpdate::purge(POOL, PurgeScope::Slots(vec![U256::from(9)])),
StateUpdate::slot(POOL, U256::from(2), value),
])));
},
BatchSize::SmallInput,
)
});
group.finish();
}
fn bench_apply_distinct_addresses(c: &mut Criterion) {
let rt = current_thread_rt();
let mut group = c.benchmark_group("apply_distinct_addresses");
for &n in &[10usize, 100, 1_000] {
let updates_a: Vec<StateUpdate> = (0..n)
.map(|i| StateUpdate::slot(Address::repeat_byte(i as u8), U256::from(0), U256::from(1)))
.collect();
let updates_b: Vec<StateUpdate> = (0..n)
.map(|i| StateUpdate::slot(Address::repeat_byte(i as u8), U256::from(0), U256::from(2)))
.collect();
group.throughput(Throughput::Elements(n as u64));
group.bench_with_input(BenchmarkId::from_parameter(n), &n, |b, _| {
let mut cache = pool_cache(&rt);
let mut toggle = false;
b.iter(|| {
let updates = if toggle { &updates_a } else { &updates_b };
toggle = !toggle;
black_box(cache.apply_updates(black_box(updates)));
})
});
}
group.finish();
}
criterion_group!(
benches,
bench_apply_slots_batch,
bench_apply_per_variant,
bench_apply_heterogeneous,
bench_apply_distinct_addresses,
);
criterion_main!(benches);