use crate::{Provider, RpcWithBlock};
use alloy_eips::BlockId;
use alloy_network::Network;
use alloy_primitives::TxHash;
use alloy_rpc_types_eth::Index;
use alloy_rpc_types_trace::{
filter::TraceFilter,
parity::{LocalizedTransactionTrace, TraceResults, TraceResultsWithTransactionHash, TraceType},
};
use alloy_transport::{Transport, TransportResult};
pub type TraceCallList<'a, N> = &'a [(<N as Network>::TransactionRequest, &'a [TraceType])];
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
pub trait TraceApi<N, T>: Send + Sync
where
N: Network,
T: Transport + Clone,
{
fn trace_call<'a, 'b>(
&self,
request: &'a N::TransactionRequest,
trace_type: &'b [TraceType],
) -> RpcWithBlock<T, (&'a N::TransactionRequest, &'b [TraceType]), TraceResults>;
fn trace_call_many<'a>(
&self,
request: TraceCallList<'a, N>,
) -> RpcWithBlock<T, (TraceCallList<'a, N>,), Vec<TraceResults>>;
async fn trace_transaction(
&self,
hash: TxHash,
) -> TransportResult<Vec<LocalizedTransactionTrace>>;
async fn trace_get(
&self,
hash: TxHash,
index: usize,
) -> TransportResult<LocalizedTransactionTrace>;
async fn trace_raw_transaction(
&self,
data: &[u8],
trace_type: &[TraceType],
) -> TransportResult<TraceResults>;
async fn trace_filter(
&self,
tracer: &TraceFilter,
) -> TransportResult<Vec<LocalizedTransactionTrace>>;
async fn trace_block(&self, block: BlockId) -> TransportResult<Vec<LocalizedTransactionTrace>>;
async fn trace_replay_transaction(
&self,
hash: TxHash,
trace_types: &[TraceType],
) -> TransportResult<TraceResults>;
async fn trace_replay_block_transactions(
&self,
block: BlockId,
trace_types: &[TraceType],
) -> TransportResult<Vec<TraceResultsWithTransactionHash>>;
}
#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
impl<N, T, P> TraceApi<N, T> for P
where
N: Network,
T: Transport + Clone,
P: Provider<T, N>,
{
fn trace_call<'a, 'b>(
&self,
request: &'a <N as Network>::TransactionRequest,
trace_types: &'b [TraceType],
) -> RpcWithBlock<T, (&'a <N as Network>::TransactionRequest, &'b [TraceType]), TraceResults>
{
self.client().request("trace_call", (request, trace_types)).into()
}
fn trace_call_many<'a>(
&self,
request: TraceCallList<'a, N>,
) -> RpcWithBlock<T, (TraceCallList<'a, N>,), Vec<TraceResults>> {
self.client().request("trace_callMany", (request,)).into()
}
async fn trace_transaction(
&self,
hash: TxHash,
) -> TransportResult<Vec<LocalizedTransactionTrace>> {
self.client().request("trace_transaction", (hash,)).await
}
async fn trace_get(
&self,
hash: TxHash,
index: usize,
) -> TransportResult<LocalizedTransactionTrace> {
self.client().request("trace_get", (hash, (Index::from(index),))).await
}
async fn trace_raw_transaction(
&self,
data: &[u8],
trace_types: &[TraceType],
) -> TransportResult<TraceResults> {
self.client().request("trace_rawTransaction", (data, trace_types)).await
}
async fn trace_filter(
&self,
tracer: &TraceFilter,
) -> TransportResult<Vec<LocalizedTransactionTrace>> {
self.client().request("trace_filter", (tracer,)).await
}
async fn trace_block(&self, block: BlockId) -> TransportResult<Vec<LocalizedTransactionTrace>> {
self.client().request("trace_block", (block,)).await
}
async fn trace_replay_transaction(
&self,
hash: TxHash,
trace_types: &[TraceType],
) -> TransportResult<TraceResults> {
self.client().request("trace_replayTransaction", (hash, trace_types)).await
}
async fn trace_replay_block_transactions(
&self,
block: BlockId,
trace_types: &[TraceType],
) -> TransportResult<Vec<TraceResultsWithTransactionHash>> {
self.client().request("trace_replayBlockTransactions", (block, trace_types)).await
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{ext::test::async_ci_only, ProviderBuilder};
use alloy_eips::BlockNumberOrTag;
use alloy_network::TransactionBuilder;
use alloy_node_bindings::{utils::run_with_tempdir, Reth};
use alloy_primitives::address;
use alloy_rpc_types_eth::TransactionRequest;
#[tokio::test]
async fn trace_block() {
let provider = ProviderBuilder::new().on_anvil();
let traces = provider.trace_block(BlockId::Number(BlockNumberOrTag::Latest)).await.unwrap();
assert_eq!(traces.len(), 0);
}
#[tokio::test]
#[cfg(not(windows))]
async fn trace_call() {
async_ci_only(|| async move {
run_with_tempdir("reth-test-", |temp_dir| async move {
let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn();
let provider = ProviderBuilder::new().on_http(reth.endpoint_url());
let tx = TransactionRequest::default()
.with_from(address!("0000000000000000000000000000000000000123"))
.with_to(address!("0000000000000000000000000000000000000456"));
let result = provider.trace_call(&tx, &[TraceType::Trace]).await;
assert!(result.is_ok());
let traces = result.unwrap();
assert_eq!(
serde_json::to_string_pretty(&traces).unwrap().trim(),
r#"
{
"output": "0x",
"stateDiff": null,
"trace": [
{
"type": "call",
"action": {
"from": "0x0000000000000000000000000000000000000123",
"callType": "call",
"gas": "0x2fa9e78",
"input": "0x",
"to": "0x0000000000000000000000000000000000000456",
"value": "0x0"
},
"result": {
"gasUsed": "0x0",
"output": "0x"
},
"subtraces": 0,
"traceAddress": []
}
],
"vmTrace": null
}
"#
.trim(),
);
})
.await;
})
.await;
}
#[tokio::test]
#[cfg(not(windows))]
async fn trace_call_many() {
async_ci_only(|| async move {
run_with_tempdir("reth-test-", |temp_dir| async move {
let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn();
let provider = ProviderBuilder::new().on_http(reth.endpoint_url());
let tx1 = TransactionRequest::default()
.with_from(address!("0000000000000000000000000000000000000123"))
.with_to(address!("0000000000000000000000000000000000000456"));
let tx2 = TransactionRequest::default()
.with_from(address!("0000000000000000000000000000000000000456"))
.with_to(address!("0000000000000000000000000000000000000789"));
let result = provider
.trace_call_many(&[(tx1, &[TraceType::Trace]), (tx2, &[TraceType::Trace])])
.await;
assert!(result.is_ok());
let traces = result.unwrap();
assert_eq!(
serde_json::to_string_pretty(&traces).unwrap().trim(),
r#"
[
{
"output": "0x",
"stateDiff": null,
"trace": [
{
"type": "call",
"action": {
"from": "0x0000000000000000000000000000000000000123",
"callType": "call",
"gas": "0x2fa9e78",
"input": "0x",
"to": "0x0000000000000000000000000000000000000456",
"value": "0x0"
},
"result": {
"gasUsed": "0x0",
"output": "0x"
},
"subtraces": 0,
"traceAddress": []
}
],
"vmTrace": null
},
{
"output": "0x",
"stateDiff": null,
"trace": [
{
"type": "call",
"action": {
"from": "0x0000000000000000000000000000000000000456",
"callType": "call",
"gas": "0x2fa9e78",
"input": "0x",
"to": "0x0000000000000000000000000000000000000789",
"value": "0x0"
},
"result": {
"gasUsed": "0x0",
"output": "0x"
},
"subtraces": 0,
"traceAddress": []
}
],
"vmTrace": null
}
]
"#
.trim()
);
})
.await;
})
.await;
}
}