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_family = "wasm", async_trait::async_trait(?Send))]
21#[cfg_attr(not(target_family = "wasm"), 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_family = "wasm", async_trait::async_trait(?Send))]
102#[cfg_attr(not(target_family = "wasm"), 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().connect_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().connect_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().connect_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 =
330 ProviderBuilder::new().wallet(wallet).connect_http(reth.endpoint_url());
331
332 let tx = TransactionRequest::default()
333 .with_from(address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"))
334 .value(U256::from(1000))
335 .with_to(address!("0000000000000000000000000000000000000456"));
336
337 let res = provider.send_transaction(tx).await.unwrap();
338
339 let receipt = res.get_receipt().await.unwrap();
340
341 let hash = receipt.transaction_hash;
342
343 let result = provider.trace_replay_transaction(hash).await;
344 assert!(result.is_ok());
345
346 let traces = result.unwrap();
347 similar_asserts::assert_eq!(
348 serde_json::to_string_pretty(&traces).unwrap(),
349 r#"{
350 "output": "0x",
351 "stateDiff": null,
352 "trace": [
353 {
354 "type": "call",
355 "action": {
356 "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
357 "callType": "call",
358 "gas": "0x0",
359 "input": "0x",
360 "to": "0x0000000000000000000000000000000000000456",
361 "value": "0x3e8"
362 },
363 "result": {
364 "gasUsed": "0x0",
365 "output": "0x"
366 },
367 "subtraces": 0,
368 "traceAddress": []
369 }
370 ],
371 "vmTrace": null
372}"#
373 );
374 })
375 .await;
376 })
377 .await;
378 }
379
380 #[tokio::test]
381 #[cfg_attr(windows, ignore = "no reth on windows")]
382 async fn trace_raw_tx() {
383 async_ci_only(|| async move {
384 run_with_tempdir("reth-test-", |temp_dir| async move {
385 let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn();
386 let pk: PrivateKeySigner =
387 "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
388 .parse()
389 .unwrap();
390
391 let provider = ProviderBuilder::new().connect_http(reth.endpoint_url());
392
393 let tx = TransactionRequest::default()
394 .with_from(address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"))
395 .gas_limit(21000)
396 .nonce(0)
397 .value(U256::from(1000))
398 .with_chain_id(provider.get_chain_id().await.unwrap())
399 .with_to(address!("0000000000000000000000000000000000000456"))
400 .with_max_priority_fee_per_gas(1_000_000_000)
401 .with_max_fee_per_gas(20_000_000_000);
402
403 let wallet = EthereumWallet::new(pk);
404
405 let raw = tx.build(&wallet).await.unwrap().encoded_2718();
406
407 let result = provider.trace_raw_transaction(&raw).await;
408
409 let traces = result.unwrap();
410
411 similar_asserts::assert_eq!(
412 serde_json::to_string_pretty(&traces).unwrap(),
413 r#"{
414 "output": "0x",
415 "stateDiff": null,
416 "trace": [
417 {
418 "type": "call",
419 "action": {
420 "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
421 "callType": "call",
422 "gas": "0x0",
423 "input": "0x",
424 "to": "0x0000000000000000000000000000000000000456",
425 "value": "0x3e8"
426 },
427 "result": {
428 "gasUsed": "0x0",
429 "output": "0x"
430 },
431 "subtraces": 0,
432 "traceAddress": []
433 }
434 ],
435 "vmTrace": null
436}"#
437 );
438 })
439 .await;
440 })
441 .await;
442 }
443
444 #[tokio::test]
445 #[cfg_attr(windows, ignore = "no reth on windows")]
446 async fn trace_replay_block_transactions() {
447 async_ci_only(|| async move {
448 run_with_tempdir("reth-test-", |temp_dir| async move {
449 let reth = Reth::new().dev().disable_discovery().data_dir(temp_dir).spawn();
450 let pk: PrivateKeySigner =
451 "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
452 .parse()
453 .unwrap();
454
455 let wallet = EthereumWallet::new(pk);
456 let provider =
457 ProviderBuilder::new().wallet(wallet).connect_http(reth.endpoint_url());
458
459 let tx = TransactionRequest::default()
460 .with_from(address!("f39Fd6e51aad88F6F4ce6aB8827279cffFb92266"))
461 .value(U256::from(1000))
462 .with_to(address!("0000000000000000000000000000000000000456"));
463
464 let res = provider.send_transaction(tx).await.unwrap();
465
466 let receipt = res.get_receipt().await.unwrap();
467
468 let block_num = receipt.block_number.unwrap();
469
470 let result =
471 provider.trace_replay_block_transactions(BlockId::number(block_num)).await;
472 assert!(result.is_ok());
473
474 let traces = result.unwrap();
475 similar_asserts::assert_eq!(
476 serde_json::to_string_pretty(&traces).unwrap().trim(),
477 r#"[
478 {
479 "output": "0x",
480 "stateDiff": null,
481 "trace": [
482 {
483 "type": "call",
484 "action": {
485 "from": "0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266",
486 "callType": "call",
487 "gas": "0x0",
488 "input": "0x",
489 "to": "0x0000000000000000000000000000000000000456",
490 "value": "0x3e8"
491 },
492 "result": {
493 "gasUsed": "0x0",
494 "output": "0x"
495 },
496 "subtraces": 0,
497 "traceAddress": []
498 }
499 ],
500 "vmTrace": null,
501 "transactionHash": "0x744426e308ba55f122913c74009be469da45153a941932d520aa959d8547da7b"
502 }
503]"#
504 .trim()
505 );
506 })
507 .await;
508 })
509 .await;
510 }
511}