alloy_provider/ext/trace/
mod.rs1use crate::Provider;
3use alloy_eips::BlockId;
4use alloy_network::Network;
5use alloy_primitives::TxHash;
6use alloy_rpc_types_eth::Index;
7use alloy_rpc_types_trace::{
8 filter::TraceFilter,
9 parity::{LocalizedTransactionTrace, TraceResults, TraceResultsWithTransactionHash, TraceType},
10};
11use alloy_transport::TransportResult;
12
13mod with_block;
14pub use with_block::{TraceBuilder, TraceParams};
15
16pub type TraceCallList<'a, N> = &'a [(<N as Network>::TransactionRequest, &'a [TraceType])];
18
19#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
21#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
22pub trait TraceApi<N>: Send + Sync
23where
24 N: Network,
25{
26 fn trace_call<'a>(
34 &self,
35 request: &'a N::TransactionRequest,
36 ) -> TraceBuilder<&'a N::TransactionRequest, TraceResults>;
37
38 fn trace_call_many<'a>(
49 &self,
50 request: TraceCallList<'a, N>,
51 ) -> TraceBuilder<TraceCallList<'a, N>, Vec<TraceResults>>;
52
53 async fn trace_transaction(
55 &self,
56 hash: TxHash,
57 ) -> TransportResult<Vec<LocalizedTransactionTrace>>;
58
59 async fn trace_get(
66 &self,
67 hash: TxHash,
68 index: usize,
69 ) -> TransportResult<LocalizedTransactionTrace>;
70
71 fn trace_raw_transaction<'a>(&self, data: &'a [u8]) -> TraceBuilder<&'a [u8], TraceResults>;
73
74 async fn trace_filter(
76 &self,
77 tracer: &TraceFilter,
78 ) -> TransportResult<Vec<LocalizedTransactionTrace>>;
79
80 async fn trace_block(&self, block: BlockId) -> TransportResult<Vec<LocalizedTransactionTrace>>;
86
87 fn trace_replay_transaction(&self, hash: TxHash) -> TraceBuilder<TxHash, TraceResults>;
91
92 fn trace_replay_block_transactions(
96 &self,
97 block: BlockId,
98 ) -> TraceBuilder<BlockId, Vec<TraceResultsWithTransactionHash>>;
99}
100
101#[cfg_attr(target_arch = "wasm32", async_trait::async_trait(?Send))]
102#[cfg_attr(not(target_arch = "wasm32"), async_trait::async_trait)]
103impl<N, P> TraceApi<N> for P
104where
105 N: Network,
106 P: Provider<N>,
107{
108 fn trace_call<'a>(
109 &self,
110 request: &'a <N as Network>::TransactionRequest,
111 ) -> TraceBuilder<&'a <N as Network>::TransactionRequest, TraceResults> {
112 TraceBuilder::new_rpc(self.client().request("trace_call", request)).pending()
113 }
114
115 fn trace_call_many<'a>(
116 &self,
117 request: TraceCallList<'a, N>,
118 ) -> TraceBuilder<TraceCallList<'a, N>, Vec<TraceResults>> {
119 TraceBuilder::new_rpc(self.client().request("trace_callMany", request)).pending()
120 }
121
122 async fn trace_transaction(
123 &self,
124 hash: TxHash,
125 ) -> TransportResult<Vec<LocalizedTransactionTrace>> {
126 self.client().request("trace_transaction", (hash,)).await
127 }
128
129 async fn trace_get(
130 &self,
131 hash: TxHash,
132 index: usize,
133 ) -> TransportResult<LocalizedTransactionTrace> {
134 self.client().request("trace_get", (hash, (Index::from(index),))).await
136 }
137
138 fn trace_raw_transaction<'a>(&self, data: &'a [u8]) -> TraceBuilder<&'a [u8], TraceResults> {
139 TraceBuilder::new_rpc(self.client().request("trace_rawTransaction", data))
140 }
141
142 async fn trace_filter(
143 &self,
144 tracer: &TraceFilter,
145 ) -> TransportResult<Vec<LocalizedTransactionTrace>> {
146 self.client().request("trace_filter", (tracer,)).await
147 }
148
149 async fn trace_block(&self, block: BlockId) -> TransportResult<Vec<LocalizedTransactionTrace>> {
150 self.client().request("trace_block", (block,)).await
151 }
152
153 fn trace_replay_transaction(&self, hash: TxHash) -> TraceBuilder<TxHash, TraceResults> {
154 TraceBuilder::new_rpc(self.client().request("trace_replayTransaction", hash))
155 }
156
157 fn trace_replay_block_transactions(
158 &self,
159 block: BlockId,
160 ) -> TraceBuilder<BlockId, Vec<TraceResultsWithTransactionHash>> {
161 TraceBuilder::new_rpc(self.client().request("trace_replayBlockTransactions", block))
162 }
163}
164
165#[cfg(test)]
166mod test {
167 use super::*;
168 use crate::{ext::test::async_ci_only, ProviderBuilder};
169 use alloy_eips::{BlockNumberOrTag, Encodable2718};
170 use alloy_network::{EthereumWallet, TransactionBuilder};
171 use alloy_node_bindings::{utils::run_with_tempdir, Reth};
172 use alloy_primitives::{address, U256};
173 use alloy_rpc_types_eth::TransactionRequest;
174 use alloy_signer_local::PrivateKeySigner;
175
176 #[tokio::test]
177 async fn trace_block() {
178 let provider = ProviderBuilder::new().on_anvil();
179 let traces = provider.trace_block(BlockId::Number(BlockNumberOrTag::Latest)).await.unwrap();
180 assert_eq!(traces.len(), 0);
181 }
182
183 #[tokio::test]
184 #[cfg_attr(windows, ignore = "no reth on windows")]
185 async fn trace_call() {
186 async_ci_only(|| async move {
187 run_with_tempdir("reth-test-", |temp_dir| async move {
188 let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn();
189 let provider = ProviderBuilder::new().on_http(reth.endpoint_url());
190
191 let tx = TransactionRequest::default()
192 .with_from(address!("0000000000000000000000000000000000000123"))
193 .with_to(address!("0000000000000000000000000000000000000456"));
194
195 let result = provider.trace_call(&tx).await;
196
197 let traces = result.unwrap();
198 similar_asserts::assert_eq!(
199 serde_json::to_string_pretty(&traces).unwrap().trim(),
200 r#"
201{
202 "output": "0x",
203 "stateDiff": null,
204 "trace": [
205 {
206 "type": "call",
207 "action": {
208 "from": "0x0000000000000000000000000000000000000123",
209 "callType": "call",
210 "gas": "0x2fa9e78",
211 "input": "0x",
212 "to": "0x0000000000000000000000000000000000000456",
213 "value": "0x0"
214 },
215 "result": {
216 "gasUsed": "0x0",
217 "output": "0x"
218 },
219 "subtraces": 0,
220 "traceAddress": []
221 }
222 ],
223 "vmTrace": null
224}
225"#
226 .trim(),
227 );
228 })
229 .await;
230 })
231 .await;
232 }
233
234 #[tokio::test]
235 #[cfg_attr(windows, ignore = "no reth on windows")]
236 async fn trace_call_many() {
237 async_ci_only(|| async move {
238 run_with_tempdir("reth-test-", |temp_dir| async move {
239 let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn();
240 let provider = ProviderBuilder::new().on_http(reth.endpoint_url());
241
242 let tx1 = TransactionRequest::default()
243 .with_from(address!("0000000000000000000000000000000000000123"))
244 .with_to(address!("0000000000000000000000000000000000000456"));
245
246 let tx2 = TransactionRequest::default()
247 .with_from(address!("0000000000000000000000000000000000000456"))
248 .with_to(address!("0000000000000000000000000000000000000789"));
249
250 let result = provider
251 .trace_call_many(&[(tx1, &[TraceType::Trace]), (tx2, &[TraceType::Trace])])
252 .await;
253
254 let traces = result.unwrap();
255 similar_asserts::assert_eq!(
256 serde_json::to_string_pretty(&traces).unwrap().trim(),
257 r#"
258[
259 {
260 "output": "0x",
261 "stateDiff": null,
262 "trace": [
263 {
264 "type": "call",
265 "action": {
266 "from": "0x0000000000000000000000000000000000000123",
267 "callType": "call",
268 "gas": "0x2fa9e78",
269 "input": "0x",
270 "to": "0x0000000000000000000000000000000000000456",
271 "value": "0x0"
272 },
273 "result": {
274 "gasUsed": "0x0",
275 "output": "0x"
276 },
277 "subtraces": 0,
278 "traceAddress": []
279 }
280 ],
281 "vmTrace": null
282 },
283 {
284 "output": "0x",
285 "stateDiff": null,
286 "trace": [
287 {
288 "type": "call",
289 "action": {
290 "from": "0x0000000000000000000000000000000000000456",
291 "callType": "call",
292 "gas": "0x2fa9e78",
293 "input": "0x",
294 "to": "0x0000000000000000000000000000000000000789",
295 "value": "0x0"
296 },
297 "result": {
298 "gasUsed": "0x0",
299 "output": "0x"
300 },
301 "subtraces": 0,
302 "traceAddress": []
303 }
304 ],
305 "vmTrace": null
306 }
307]
308"#
309 .trim()
310 );
311 })
312 .await;
313 })
314 .await;
315 }
316
317 #[tokio::test]
318 #[cfg_attr(windows, ignore = "no reth on windows")]
319 async fn test_replay_tx() {
320 async_ci_only(|| async move {
321 run_with_tempdir("reth-test-", |temp_dir| async move {
322 let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn();
323 let pk: PrivateKeySigner =
324 "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
325 .parse()
326 .unwrap();
327
328 let wallet = EthereumWallet::new(pk);
329 let provider = ProviderBuilder::new().wallet(wallet).on_http(reth.endpoint_url());
330
331 let tx = TransactionRequest::default()
332 .with_from(address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"))
333 .value(U256::from(1000))
334 .with_to(address!("0000000000000000000000000000000000000456"));
335
336 let res = provider.send_transaction(tx).await.unwrap();
337
338 let receipt = res.get_receipt().await.unwrap();
339
340 let hash = receipt.transaction_hash;
341
342 let result = provider.trace_replay_transaction(hash).await;
343 assert!(result.is_ok());
344
345 let traces = result.unwrap();
346 similar_asserts::assert_eq!(
347 serde_json::to_string_pretty(&traces).unwrap(),
348 r#"{
349 "output": "0x",
350 "stateDiff": null,
351 "trace": [
352 {
353 "type": "call",
354 "action": {
355 "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
356 "callType": "call",
357 "gas": "0x0",
358 "input": "0x",
359 "to": "0x0000000000000000000000000000000000000456",
360 "value": "0x3e8"
361 },
362 "result": {
363 "gasUsed": "0x0",
364 "output": "0x"
365 },
366 "subtraces": 0,
367 "traceAddress": []
368 }
369 ],
370 "vmTrace": null
371}"#
372 );
373 })
374 .await;
375 })
376 .await;
377 }
378
379 #[tokio::test]
380 #[cfg_attr(windows, ignore = "no reth on windows")]
381 async fn trace_raw_tx() {
382 async_ci_only(|| async move {
383 run_with_tempdir("reth-test-", |temp_dir| async move {
384 let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn();
385 let pk: PrivateKeySigner =
386 "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
387 .parse()
388 .unwrap();
389
390 let provider = ProviderBuilder::new().on_http(reth.endpoint_url());
391
392 let tx = TransactionRequest::default()
393 .with_from(address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"))
394 .gas_limit(21000)
395 .nonce(0)
396 .value(U256::from(1000))
397 .with_chain_id(provider.get_chain_id().await.unwrap())
398 .with_to(address!("0000000000000000000000000000000000000456"))
399 .with_max_priority_fee_per_gas(1_000_000_000)
400 .with_max_fee_per_gas(20_000_000_000);
401
402 let wallet = EthereumWallet::new(pk);
403
404 let raw = tx.build(&wallet).await.unwrap().encoded_2718();
405
406 let result = provider.trace_raw_transaction(&raw).await;
407
408 let traces = result.unwrap();
409
410 similar_asserts::assert_eq!(
411 serde_json::to_string_pretty(&traces).unwrap(),
412 r#"{
413 "output": "0x",
414 "stateDiff": null,
415 "trace": [
416 {
417 "type": "call",
418 "action": {
419 "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
420 "callType": "call",
421 "gas": "0x0",
422 "input": "0x",
423 "to": "0x0000000000000000000000000000000000000456",
424 "value": "0x3e8"
425 },
426 "result": {
427 "gasUsed": "0x0",
428 "output": "0x"
429 },
430 "subtraces": 0,
431 "traceAddress": []
432 }
433 ],
434 "vmTrace": null
435}"#
436 );
437 })
438 .await;
439 })
440 .await;
441 }
442
443 #[tokio::test]
444 #[cfg_attr(windows, ignore = "no reth on windows")]
445 async fn trace_replay_block_transactions() {
446 async_ci_only(|| async move {
447 run_with_tempdir("reth-test-", |temp_dir| async move {
448 let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn();
449 let pk: PrivateKeySigner =
450 "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
451 .parse()
452 .unwrap();
453
454 let wallet = EthereumWallet::new(pk);
455 let provider = ProviderBuilder::new().wallet(wallet).on_http(reth.endpoint_url());
456
457 let tx = TransactionRequest::default()
458 .with_from(address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"))
459 .value(U256::from(1000))
460 .with_to(address!("0000000000000000000000000000000000000456"));
461
462 let res = provider.send_transaction(tx).await.unwrap();
463
464 let receipt = res.get_receipt().await.unwrap();
465
466 let block_num = receipt.block_number.unwrap();
467
468 let result =
469 provider.trace_replay_block_transactions(BlockId::number(block_num)).await;
470 assert!(result.is_ok());
471
472 let traces = result.unwrap();
473 similar_asserts::assert_eq!(
474 serde_json::to_string_pretty(&traces).unwrap().trim(),
475 r#"[
476 {
477 "output": "0x",
478 "stateDiff": null,
479 "trace": [
480 {
481 "type": "call",
482 "action": {
483 "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
484 "callType": "call",
485 "gas": "0x0",
486 "input": "0x",
487 "to": "0x0000000000000000000000000000000000000456",
488 "value": "0x3e8"
489 },
490 "result": {
491 "gasUsed": "0x0",
492 "output": "0x"
493 },
494 "subtraces": 0,
495 "traceAddress": []
496 }
497 ],
498 "vmTrace": null,
499 "transactionHash": "0x744426e308ba55f122913c74009be469da45153a941932d520aa959d8547da7b"
500 }
501]"#
502 .trim()
503 );
504 })
505 .await;
506 })
507 .await;
508 }
509}