use alloy::eips::BlockId;
use alloy::network::primitives::{BlockTransactionsKind, HeaderResponse};
use alloy::providers::{network::BlockResponse, Network, Provider};
use alloy::transports::Transport;
use eyre::ErrReport;
use revm::{
primitives::{AccountInfo, Address, Bytecode, B256, U256},
Database, DatabaseRef,
};
use std::future::IntoFuture;
use tokio::runtime::{Handle, Runtime};
#[derive(Debug)]
pub(crate) enum HandleOrRuntime {
Handle(Handle),
Runtime(Runtime),
}
impl HandleOrRuntime {
#[inline]
pub(crate) fn block_on<F>(&self, f: F) -> F::Output
where
F: std::future::Future + Send,
F::Output: Send,
{
match self {
Self::Handle(handle) => tokio::task::block_in_place(move || handle.block_on(f)),
Self::Runtime(rt) => rt.block_on(f),
}
}
}
#[derive(Debug)]
pub struct AlloyDB<T: Transport + Clone, N: Network, P: Provider<T, N>> {
provider: P,
block_number: BlockId,
rt: HandleOrRuntime,
_marker: std::marker::PhantomData<fn() -> (T, N)>,
}
#[allow(dead_code)]
impl<T: Transport + Clone, N: Network, P: Provider<T, N>> AlloyDB<T, N, P> {
pub fn new(provider: P, block_number: BlockId) -> Option<Self> {
let rt = match Handle::try_current() {
Ok(handle) => match handle.runtime_flavor() {
tokio::runtime::RuntimeFlavor::CurrentThread => return None,
_ => HandleOrRuntime::Handle(handle),
},
Err(_) => return None,
};
Some(Self { provider, block_number, rt, _marker: std::marker::PhantomData })
}
pub fn with_runtime(provider: P, block_number: BlockId, runtime: Runtime) -> Self {
let rt = HandleOrRuntime::Runtime(runtime);
Self { provider, block_number, rt, _marker: std::marker::PhantomData }
}
pub fn with_handle(provider: P, block_number: BlockId, handle: Handle) -> Self {
let rt = HandleOrRuntime::Handle(handle);
Self { provider, block_number, rt, _marker: std::marker::PhantomData }
}
#[inline]
fn block_on<F>(&self, f: F) -> F::Output
where
F: std::future::Future + Send,
F::Output: Send,
{
self.rt.block_on(f)
}
pub fn set_block_number(&mut self, block_number: BlockId) {
self.block_number = block_number;
}
}
impl<T: Transport + Clone, N: Network, P: Provider<T, N>> DatabaseRef for AlloyDB<T, N, P> {
type Error = ErrReport;
fn basic_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
let f = async {
let nonce = self.provider.get_transaction_count(address).block_id(self.block_number);
let balance = self.provider.get_balance(address).block_id(self.block_number);
let code = self.provider.get_code_at(address).block_id(self.block_number);
tokio::join!(nonce.into_future(), balance.into_future(), code.into_future())
};
let (nonce, balance, code) = self.block_on(f);
let balance = balance?;
let code = Bytecode::new_raw(code?.0.into());
let code_hash = code.hash_slow();
let nonce = nonce?;
Ok(Some(AccountInfo::new(balance, nonce, code_hash, code)))
}
fn code_by_hash_ref(&self, _code_hash: B256) -> Result<Bytecode, Self::Error> {
panic!("This should not be called, as the code is already loaded");
}
fn storage_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
let f = self.provider.get_storage_at(address, index).block_id(self.block_number);
let slot_val = self.block_on(f.into_future())?;
Ok(slot_val)
}
fn block_hash_ref(&self, number: u64) -> Result<B256, Self::Error> {
let block = self.block_on(
self.provider
.get_block_by_number(number.into(), BlockTransactionsKind::Hashes),
)?;
Ok(B256::new(*block.unwrap().header().hash()))
}
}
impl<T: Transport + Clone, N: Network, P: Provider<T, N>> Database for AlloyDB<T, N, P> {
type Error = ErrReport;
#[inline]
fn basic(&mut self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
<Self as DatabaseRef>::basic_ref(self, address)
}
#[inline]
fn code_by_hash(&mut self, code_hash: B256) -> Result<Bytecode, Self::Error> {
<Self as DatabaseRef>::code_by_hash_ref(self, code_hash)
}
#[inline]
fn storage(&mut self, address: Address, index: U256) -> Result<U256, Self::Error> {
<Self as DatabaseRef>::storage_ref(self, address, index)
}
#[inline]
fn block_hash(&mut self, number: u64) -> Result<B256, Self::Error> {
<Self as DatabaseRef>::block_hash_ref(self, number)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy::providers::ProviderBuilder;
use std::env;
use url::Url;
#[test]
#[ignore = "flaky RPC"]
fn can_get_basic() {
let node_url = env::var("MAINNET_HTTP").unwrap();
let node_url = Url::parse(node_url.as_str()).unwrap();
let client = ProviderBuilder::new().on_http(node_url);
let alloydb = AlloyDB::new(client, BlockId::from(16148323));
if alloydb.is_none() {
println!("Alloydb is None");
}
let address: Address = "0x0d4a11d5EEaaC28EC3F61d100daF4d40471f1852".parse().unwrap();
let acc_info = alloydb.unwrap().basic_ref(address).unwrap().unwrap();
assert!(acc_info.exists());
}
}