use std::marker::PhantomData;
use std::sync::Arc;
use alloy::eips::BlockId;
use alloy::{
network::Ethereum,
node_bindings::{Anvil, AnvilInstance},
primitives::{BlockHash, BlockNumber, U64},
providers::{ext::DebugApi, Network, Provider, ProviderBuilder, ProviderCall, RootProvider},
rpc::{
client::{NoParams, WsConnect},
types::trace::geth::{GethDebugTracingCallOptions, GethDebugTracingOptions, GethTrace, TraceResult},
types::{BlockNumberOrTag, BlockTransactionsKind, TransactionRequest},
},
transports::{BoxTransport, Transport, TransportResult},
};
use async_trait::async_trait;
use eyre::{eyre, Result};
use k256::SecretKey;
use crate::HttpCachedTransport;
#[derive(Clone, Debug)]
pub struct AnvilDebugProvider<PN, PA, TN, TA, N>
where
N: Network,
TA: Transport + Clone,
TN: Transport + Clone,
PN: Provider<TN, N> + Send + Sync + Clone + 'static,
PA: Provider<TA, N> + Send + Sync + Clone + 'static,
{
_node: PN,
_anvil: PA,
_anvil_instance: Option<Arc<AnvilInstance>>,
block_number: BlockNumberOrTag,
_ta: PhantomData<TA>,
_tn: PhantomData<TN>,
_n: PhantomData<N>,
}
pub struct AnvilDebugProviderFactory {}
pub type AnvilDebugProviderType =
AnvilDebugProvider<RootProvider<BoxTransport, Ethereum>, RootProvider<BoxTransport, Ethereum>, BoxTransport, BoxTransport, Ethereum>;
impl AnvilDebugProviderFactory {
pub async fn from_node_on_block(node_url: String, block: BlockNumber) -> Result<AnvilDebugProviderType> {
let node_ws = WsConnect::new(node_url.clone());
let node_provider = ProviderBuilder::new().on_ws(node_ws).await?.boxed();
let anvil = Anvil::new().fork_block_number(block).fork(node_url.clone()).chain_id(1).arg("--disable-console-log").spawn();
let anvil_url = anvil.ws_endpoint_url();
let anvil_ws = WsConnect::new(anvil_url.clone());
let anvil_provider = ProviderBuilder::new().on_ws(anvil_ws).await?.boxed();
let curblock = anvil_provider.get_block_by_number(BlockNumberOrTag::Latest, BlockTransactionsKind::Hashes).await?;
match curblock {
Some(curblock) => {
if curblock.header.number != block {
return Err(eyre!("INCORRECT_BLOCK_NUMBER"));
}
}
_ => {
return Err(eyre!("CANNOT_GET_BLOCK"));
}
}
let ret = AnvilDebugProvider {
_node: node_provider,
_anvil: anvil_provider,
_anvil_instance: Some(Arc::new(anvil)),
block_number: BlockNumberOrTag::Number(block),
_ta: PhantomData::<BoxTransport>,
_tn: PhantomData::<BoxTransport>,
_n: PhantomData::<Ethereum>,
};
let curblock = ret._anvil.get_block_by_number(BlockNumberOrTag::Latest, BlockTransactionsKind::Hashes).await?;
match curblock {
Some(curblock) => {
if curblock.header.number != block {
return Err(eyre!("INCORRECT_BLOCK_NUMBER"));
}
}
_ => {
return Err(eyre!("CANNOT_GET_BLOCK"));
}
}
Ok(ret)
}
}
impl<PN, PA, TN, TA, N> AnvilDebugProvider<PN, PA, TN, TA, N>
where
TN: Transport + Clone,
TA: Transport + Clone,
N: Network,
PA: Provider<TA, N> + Send + Sync + Clone + 'static,
PN: Provider<TN, N> + Send + Sync + Clone + 'static,
{
pub fn new(_node: PN, _anvil: PA, block_number: BlockNumberOrTag) -> Self {
Self { _node, _anvil, _anvil_instance: None, block_number, _ta: PhantomData, _tn: PhantomData, _n: PhantomData }
}
pub fn node(&self) -> &PN {
&self._node
}
pub fn anvil(&self) -> &PA {
&self._anvil
}
pub fn privkey(&self) -> Result<SecretKey> {
match &self._anvil_instance {
Some(anvil) => Ok(anvil.clone().keys()[0].clone()),
_ => Err(eyre!("NO_ANVIL_INSTANCE")),
}
}
}
impl<PN, PA, TA> Provider<TA, Ethereum> for AnvilDebugProvider<PN, PA, BoxTransport, TA, Ethereum>
where
TA: Transport + Clone,
PN: Provider<BoxTransport, Ethereum> + Send + Sync + Clone + 'static,
PA: Provider<TA, Ethereum> + Send + Sync + Clone + 'static,
{
#[inline(always)]
fn root(&self) -> &RootProvider<TA, Ethereum> {
self._anvil.root()
}
#[allow(clippy::type_complexity)]
fn get_block_number(&self) -> ProviderCall<TA, NoParams, U64, BlockNumber> {
self._anvil.get_block_number()
}
}
#[async_trait]
pub trait DebugProviderExt<T = BoxTransport, N = Ethereum> {
async fn geth_debug_trace_call(
&self,
tx: TransactionRequest,
block: BlockId,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<GethTrace>;
async fn geth_debug_trace_block_by_number(
&self,
block: BlockNumberOrTag,
trace_options: GethDebugTracingOptions,
) -> TransportResult<Vec<TraceResult>>;
async fn geth_debug_trace_block_by_hash(
&self,
block: BlockHash,
trace_options: GethDebugTracingOptions,
) -> TransportResult<Vec<TraceResult>>;
}
#[async_trait]
impl<T, N> DebugProviderExt<T, N> for RootProvider<BoxTransport>
where
T: Transport + Clone,
N: Network,
{
async fn geth_debug_trace_call(
&self,
tx: TransactionRequest,
block: BlockId,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<GethTrace> {
self.debug_trace_call(tx, block, trace_options).await
}
async fn geth_debug_trace_block_by_number(
&self,
block: BlockNumberOrTag,
trace_options: GethDebugTracingOptions,
) -> TransportResult<Vec<TraceResult>> {
self.debug_trace_block_by_number(block, trace_options).await
}
async fn geth_debug_trace_block_by_hash(
&self,
block: BlockHash,
trace_options: GethDebugTracingOptions,
) -> TransportResult<Vec<TraceResult>> {
self.debug_trace_block_by_hash(block, trace_options).await
}
}
#[async_trait]
impl<T, N> DebugProviderExt<T, N> for RootProvider<HttpCachedTransport>
where
T: Transport + Clone,
N: Network,
{
async fn geth_debug_trace_call(
&self,
tx: TransactionRequest,
block: BlockId,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<GethTrace> {
self.debug_trace_call(tx, block, trace_options).await
}
async fn geth_debug_trace_block_by_number(
&self,
block: BlockNumberOrTag,
trace_options: GethDebugTracingOptions,
) -> TransportResult<Vec<TraceResult>> {
self.debug_trace_block_by_number(block, trace_options).await
}
async fn geth_debug_trace_block_by_hash(
&self,
block: BlockHash,
trace_options: GethDebugTracingOptions,
) -> TransportResult<Vec<TraceResult>> {
self.debug_trace_block_by_hash(block, trace_options).await
}
}
#[async_trait]
impl<PN, PA, TN, TA, N> DebugProviderExt<TA, N> for AnvilDebugProvider<PN, PA, TN, TA, N>
where
TN: Transport + Clone,
TA: Transport + Clone,
N: Network,
PN: Provider<TN, N> + Send + Sync + Clone + 'static,
PA: Provider<TA, N> + Send + Sync + Clone + 'static,
{
async fn geth_debug_trace_call(
&self,
tx: TransactionRequest,
block: BlockId,
trace_options: GethDebugTracingCallOptions,
) -> TransportResult<GethTrace> {
let block = match block {
BlockId::Hash(hash) => BlockId::Hash(hash),
BlockId::Number(number) => match number {
BlockNumberOrTag::Number(number) => BlockId::Number(BlockNumberOrTag::Number(number)),
BlockNumberOrTag::Latest => BlockId::Number(self.block_number),
_ => block,
},
};
self._node.debug_trace_call(tx, block, trace_options).await
}
async fn geth_debug_trace_block_by_number(
&self,
block: BlockNumberOrTag,
trace_options: GethDebugTracingOptions,
) -> TransportResult<Vec<TraceResult>> {
self._node.debug_trace_block_by_number(block, trace_options).await
}
async fn geth_debug_trace_block_by_hash(
&self,
block: BlockHash,
trace_options: GethDebugTracingOptions,
) -> TransportResult<Vec<TraceResult>> {
self._node.debug_trace_block_by_hash(block, trace_options).await
}
}
#[cfg(test)]
mod test {
use super::*;
use alloy::primitives::{Address, U256};
use alloy_provider::ProviderBuilder;
use alloy_rpc_client::ClientBuilder;
use env_logger::Env as EnvLog;
use eyre::Result;
use tracing::{debug, error};
#[tokio::test]
async fn test_debug_trace_call() -> Result<()> {
let _ = env_logger::try_init_from_env(
EnvLog::default().default_filter_or("info,hyper_util=off,alloy_transport_http=off,alloy_rpc_client=off,reqwest=off"),
);
let node_url = url::Url::parse(std::env::var("MAINNET_HTTP")?.as_str())?;
let provider_anvil =
ProviderBuilder::new().on_anvil_with_config(|x| x.chain_id(1).fork(node_url.clone()).fork_block_number(20322777));
let client_node = ClientBuilder::default().http(node_url).boxed();
let provider_node = ProviderBuilder::new().on_client(client_node).boxed();
let provider = AnvilDebugProvider::new(provider_node, provider_anvil, BlockNumberOrTag::Number(10));
let client = provider;
let block_number = client.get_block_number().await?;
let contract: Address = "0x90e7a93e0a6514cb0c84fc7acc1cb5c0793352d2".parse()?;
let location: U256 = U256::from(0);
let cell0 = client.get_storage_at(contract, location).block_id(BlockNumberOrTag::Latest.into()).await?;
debug!("{} {}", block_number, cell0);
match client
.geth_debug_trace_call(
TransactionRequest::default(),
BlockId::Number(BlockNumberOrTag::Latest),
GethDebugTracingCallOptions::default(),
)
.await
{
Ok(trace) => {
debug!("Ok {:?}", trace);
}
Err(e) => {
error!("Error :{}", e);
panic!("DEBUG_TRACE_CALL_FAILED");
}
}
Ok(())
}
}