pub use alloy_eips::BlockId;
use alloy_provider::{
network::{
primitives::{BlockTransactionsKind, HeaderResponse},
BlockResponse,
},
Network, Provider,
};
use alloy_transport::{Transport, TransportError};
use database_interface::{async_db::DatabaseAsyncRef, DBErrorMarker};
use primitives::{Address, B256, U256};
use state::{AccountInfo, Bytecode};
#[derive(Debug)]
pub struct DBTransportError(pub TransportError);
impl DBErrorMarker for DBTransportError {}
impl From<TransportError> for DBTransportError {
fn from(e: TransportError) -> Self {
Self(e)
}
}
#[derive(Debug)]
pub struct AlloyDB<T: Transport + Clone, N: Network, P: Provider<T, N>> {
provider: P,
block_number: BlockId,
_marker: core::marker::PhantomData<fn() -> (T, N)>,
}
impl<T: Transport + Clone, N: Network, P: Provider<T, N>> AlloyDB<T, N, P> {
pub fn new(provider: P, block_number: BlockId) -> Self {
Self {
provider,
block_number,
_marker: core::marker::PhantomData,
}
}
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>> DatabaseAsyncRef for AlloyDB<T, N, P> {
type Error = DBTransportError;
async fn basic_async_ref(&self, address: Address) -> Result<Option<AccountInfo>, Self::Error> {
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);
let (nonce, balance, code) = tokio::join!(nonce, balance, code,);
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)))
}
async fn block_hash_async_ref(&self, number: u64) -> Result<B256, Self::Error> {
let block = self
.provider
.get_block_by_number(number.into(), BlockTransactionsKind::Hashes)
.await?;
Ok(B256::new(*block.unwrap().header().hash()))
}
async fn code_by_hash_async_ref(&self, _code_hash: B256) -> Result<Bytecode, Self::Error> {
panic!("This should not be called, as the code is already loaded");
}
async fn storage_async_ref(&self, address: Address, index: U256) -> Result<U256, Self::Error> {
Ok(self
.provider
.get_storage_at(address, index)
.block_id(self.block_number)
.await?)
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloy_provider::ProviderBuilder;
use database_interface::{DatabaseRef, WrapDatabaseAsync};
#[test]
#[ignore = "flaky RPC"]
fn can_get_basic() {
let client = ProviderBuilder::new().on_http(
"https://mainnet.infura.io/v3/c60b0bb42f8a4c6481ecd229eddaca27"
.parse()
.unwrap(),
);
let alloydb = AlloyDB::new(client, BlockId::from(16148323));
let wrapped_alloydb = WrapDatabaseAsync::new(alloydb).unwrap();
let address: Address = "0x0d4a11d5EEaaC28EC3F61d100daF4d40471f1852"
.parse()
.unwrap();
let acc_info = wrapped_alloydb.basic_ref(address).unwrap().unwrap();
assert!(acc_info.exists());
}
}