use alloy::{
network::{Ethereum, Network},
primitives::{Address, Bytes as AlBytes, U256},
providers::{Provider, ProviderBuilder, ReqwestProvider},
transports::http::reqwest,
};
use async_trait::async_trait;
use bytes::Bytes;
use xenith_core::{ChainId, XenithError};
use crate::provider::ChainProvider;
type EthTxRequest = <Ethereum as Network>::TransactionRequest;
pub struct AlloyProvider {
inner: ReqwestProvider,
chain_id: ChainId,
}
impl AlloyProvider {
pub fn new(rpc_url: &str, chain_id: ChainId) -> xenith_core::Result<Self> {
let url = rpc_url
.parse::<reqwest::Url>()
.map_err(|e| XenithError::Transport {
chain: chain_id,
message: format!("invalid RPC URL: {e}"),
})?;
Ok(Self {
inner: ProviderBuilder::new().on_http(url),
chain_id,
})
}
}
#[async_trait]
impl ChainProvider for AlloyProvider {
async fn read_storage(
&self,
address: [u8; 20],
slot: [u8; 32],
) -> xenith_core::Result<[u8; 32]> {
let addr = Address::from(address);
let key = U256::from_be_bytes::<32>(slot);
let result =
self.inner
.get_storage_at(addr, key)
.await
.map_err(|e| XenithError::Transport {
chain: self.chain_id,
message: e.to_string(),
})?;
Ok(result.to_be_bytes::<32>())
}
async fn call(&self, address: [u8; 20], calldata: Bytes) -> xenith_core::Result<Bytes> {
let req = EthTxRequest::default()
.to(Address::from(address))
.input(AlBytes::from(calldata).into());
let result = self
.inner
.call(&req)
.await
.map_err(|e| XenithError::Transport {
chain: self.chain_id,
message: e.to_string(),
})?;
Ok(result.into())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alloy_provider_is_send_sync() {
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<AlloyProvider>();
}
#[test]
fn new_rejects_invalid_url() {
let err = AlloyProvider::new("not a url %%", ChainId(1)).unwrap_err();
assert!(matches!(
err,
XenithError::Transport {
chain: ChainId(1),
..
}
));
}
}