1use crate::node::{InMemoryNode, MAX_TX_SIZE};
2use crate::utils::create_debug_output;
3use once_cell::sync::OnceCell;
4use std::sync::Arc;
5use zksync_multivm::interface::storage::StorageView;
6use zksync_multivm::interface::{VmFactory, VmInterface};
7use zksync_multivm::tracers::CallTracer;
8use zksync_multivm::vm_latest::constants::ETH_CALL_GAS_LIMIT;
9use zksync_multivm::vm_latest::{HistoryDisabled, ToTracerPointer, Vm};
10use zksync_types::l2::L2Tx;
11use zksync_types::transaction_request::CallRequest;
12use zksync_types::web3::Bytes;
13use zksync_types::{api, PackedEthSignature, Transaction, H256};
14use zksync_web3_decl::error::Web3Error;
15
16use super::boojumos::BOOJUM_CALL_GAS_LIMIT;
17
18impl InMemoryNode {
19 pub async fn trace_block_impl(
20 &self,
21 block_id: api::BlockId,
22 options: Option<api::TracerConfig>,
23 ) -> anyhow::Result<api::CallTracerBlockResult> {
24 let only_top = options.is_some_and(|o| o.tracer_config.only_top_call);
25 let tx_hashes = self
26 .blockchain
27 .get_block_tx_hashes_by_id(block_id)
28 .await
29 .ok_or_else(|| anyhow::anyhow!("Block (id={block_id}) not found"))?;
30
31 let mut debug_calls = Vec::with_capacity(tx_hashes.len());
32 for tx_hash in tx_hashes {
33 let result = self.blockchain
34 .get_tx_debug_info(&tx_hash, only_top)
35 .await
36 .ok_or_else(|| {
37 anyhow::anyhow!(
38 "Unexpectedly transaction (hash={tx_hash}) belongs to a block but could not be found"
39 )
40 })?;
41 debug_calls.push(api::ResultDebugCall { result });
42 }
43
44 Ok(api::CallTracerBlockResult::CallTrace(debug_calls))
45 }
46
47 pub async fn trace_call_impl(
48 &self,
49 request: CallRequest,
50 block: Option<api::BlockId>,
51 options: Option<api::TracerConfig>,
52 ) -> Result<api::CallTracerResult, Web3Error> {
53 let only_top = options.is_some_and(|o| o.tracer_config.only_top_call);
54 let inner = self.inner.read().await;
55 let system_contracts = self.system_contracts.contracts_for_l2_call();
56 if block.is_some() && !matches!(block, Some(api::BlockId::Number(api::BlockNumber::Latest)))
57 {
58 return Err(Web3Error::InternalError(anyhow::anyhow!(
59 "tracing only supported at `latest` block"
60 )));
61 }
62
63 let mut l2_tx = L2Tx::from_request(
64 request.into(),
65 MAX_TX_SIZE,
66 self.system_contracts.allow_no_target(),
67 )
68 .map_err(Web3Error::SerializationError)?;
69 let execution_mode = zksync_multivm::interface::TxExecutionMode::EthCall;
70
71 let (mut l1_batch_env, _block_context) = inner.create_l1_batch_env().await;
73
74 l1_batch_env.enforced_base_fee = Some(l2_tx.common_data.fee.max_fee_per_gas.as_u64());
76 let system_env = inner.create_system_env(system_contracts.clone(), execution_mode);
77 let storage = StorageView::new(inner.read_storage()).to_rc_ptr();
78 let mut vm: Vm<_, HistoryDisabled> = Vm::new(l1_batch_env, system_env, storage);
79
80 if l2_tx.common_data.signature.is_empty() {
82 l2_tx.common_data.signature = PackedEthSignature::default().serialize_packed().into();
83 }
84
85 if self.system_contracts.boojum.use_boojum {
89 l2_tx.common_data.fee.gas_limit = BOOJUM_CALL_GAS_LIMIT.into();
90 } else {
91 l2_tx.common_data.fee.gas_limit = ETH_CALL_GAS_LIMIT.into();
92 }
93
94 let tx: Transaction = l2_tx.clone().into();
95 vm.push_transaction(tx);
96
97 let call_tracer_result = Arc::new(OnceCell::default());
98 let tracer = CallTracer::new(call_tracer_result.clone()).into_tracer_pointer();
99
100 let tx_result = vm.inspect(
101 &mut tracer.into(),
102 zksync_multivm::interface::InspectExecutionMode::OneTx,
103 );
104 let call_traces = if only_top {
105 vec![]
106 } else {
107 Arc::try_unwrap(call_tracer_result)
108 .unwrap()
109 .take()
110 .unwrap_or_default()
111 };
112
113 let debug = create_debug_output(&l2_tx.into(), &tx_result, call_traces)?;
114
115 Ok(api::CallTracerResult::CallTrace(debug))
116 }
117
118 pub async fn trace_transaction_impl(
119 &self,
120 tx_hash: H256,
121 options: Option<api::TracerConfig>,
122 ) -> anyhow::Result<Option<api::CallTracerResult>> {
123 let only_top = options.is_some_and(|o| o.tracer_config.only_top_call);
124 Ok(self
125 .blockchain
126 .get_tx_debug_info(&tx_hash, only_top)
127 .await
128 .map(api::CallTracerResult::CallTrace))
129 }
130
131 pub async fn get_raw_transaction_impl(&self, tx_hash: H256) -> anyhow::Result<Option<Bytes>> {
132 Ok(self.blockchain.get_raw_transaction(tx_hash).await)
133 }
134
135 pub async fn get_raw_transactions_impl(
136 &self,
137 block_number: api::BlockId,
138 ) -> anyhow::Result<Vec<Bytes>> {
139 Ok(self.blockchain.get_raw_transactions(block_number).await)
140 }
141}
142
143#[cfg(test)]
144mod tests {
145 use alloy::dyn_abi::{DynSolValue, FunctionExt, JsonAbiExt};
146 use alloy::json_abi::{Function, Param, StateMutability};
147 use alloy::primitives::{Address as AlloyAddress, U256 as AlloyU256};
148 use anvil_zksync_config::constants::DEFAULT_ACCOUNT_BALANCE;
149 use zksync_types::{
150 transaction_request::CallRequestBuilder, utils::deployed_address_create, Address,
151 K256PrivateKey, L2BlockNumber, Nonce, H160, U256,
152 };
153
154 use super::*;
155 use crate::{
156 deps::system_contracts::bytecode_from_slice,
157 node::{InMemoryNode, TransactionResult},
158 testing::{self, LogBuilder},
159 };
160
161 async fn deploy_test_contracts(node: &InMemoryNode) -> (Address, Address) {
162 let private_key = K256PrivateKey::from_bytes(H256::repeat_byte(0xee)).unwrap();
163 let from_account = private_key.address();
164 node.set_rich_account(from_account, U256::from(DEFAULT_ACCOUNT_BALANCE))
165 .await;
166
167 let secondary_bytecode = bytecode_from_slice(
169 "Secondary",
170 include_bytes!("../deps/test-contracts/Secondary.json"),
171 );
172 let secondary_deployed_address = deployed_address_create(from_account, U256::zero());
173 let alloy_secondary_address = AlloyAddress::from(secondary_deployed_address.0);
174 let secondary_constructor_calldata =
175 DynSolValue::Uint(AlloyU256::from(2), 256).abi_encode();
176
177 testing::deploy_contract(
178 node,
179 &private_key,
180 secondary_bytecode,
181 Some(secondary_constructor_calldata),
182 Nonce(0),
183 )
184 .await;
185
186 let primary_bytecode = bytecode_from_slice(
188 "Primary",
189 include_bytes!("../deps/test-contracts/Primary.json"),
190 );
191 let primary_deployed_address = deployed_address_create(from_account, U256::one());
192 let primary_constructor_calldata =
193 DynSolValue::Address(alloy_secondary_address).abi_encode();
194
195 testing::deploy_contract(
196 node,
197 &private_key,
198 primary_bytecode,
199 Some(primary_constructor_calldata),
200 Nonce(1),
201 )
202 .await;
203
204 (primary_deployed_address, secondary_deployed_address)
205 }
206
207 #[tokio::test]
208 async fn test_trace_deployed_contract() {
209 let node = InMemoryNode::test(None);
210
211 let (primary_deployed_address, secondary_deployed_address) =
212 deploy_test_contracts(&node).await;
213
214 let func = Function {
215 name: "calculate".to_string(),
216 inputs: vec![Param {
217 name: "value".to_string(),
218 ty: "uint256".to_string(),
219 components: vec![],
220 internal_type: None,
221 }],
222 outputs: vec![Param {
223 name: "".to_string(),
224 ty: "uint256".to_string(),
225 components: vec![],
226 internal_type: None,
227 }],
228 state_mutability: StateMutability::NonPayable,
229 };
230
231 let calldata = func
232 .abi_encode_input(&[DynSolValue::Uint(AlloyU256::from(42), 256)])
233 .expect("failed to encode function input");
234
235 let request = CallRequestBuilder::default()
236 .to(Some(primary_deployed_address))
237 .data(calldata.clone().into())
238 .gas(80_000_000.into())
239 .build();
240 let trace = node
241 .trace_call_impl(request.clone(), None, None)
242 .await
243 .expect("trace call")
244 .unwrap_default();
245
246 assert!(trace.error.is_none());
248 assert!(trace.revert_reason.is_none());
249
250 let output = func
252 .abi_decode_output(trace.output.0.as_slice(), true)
253 .expect("failed to decode output");
254 assert_eq!(
255 output[0],
256 DynSolValue::Uint(AlloyU256::from(84), 256),
257 "unexpected output"
258 );
259
260 let contract_call = trace.calls.last().unwrap().calls.first().unwrap();
262
263 assert_eq!(contract_call.to, primary_deployed_address);
264 assert_eq!(contract_call.input, calldata.into());
265
266 let subcall = contract_call.calls.first().unwrap();
268 assert_eq!(subcall.to, secondary_deployed_address);
269 assert_eq!(subcall.from, primary_deployed_address);
270 assert_eq!(
271 subcall.output,
272 func.abi_encode_output(&[DynSolValue::Uint(AlloyU256::from(84), 256)])
273 .expect("failed to encode function output")
274 .into()
275 );
276 }
277
278 #[tokio::test]
279 async fn test_trace_only_top() {
280 let node = InMemoryNode::test(None);
281
282 let (primary_deployed_address, _) = deploy_test_contracts(&node).await;
283
284 let func = Function {
286 name: "calculate".to_string(),
287 inputs: vec![Param {
288 name: "value".to_string(),
289 ty: "uint256".to_string(),
290 components: vec![],
291 internal_type: None,
292 }],
293 outputs: vec![],
294 state_mutability: StateMutability::NonPayable,
295 };
296
297 let calldata = func
298 .abi_encode_input(&[DynSolValue::Uint(AlloyU256::from(42), 256)])
299 .expect("failed to encode function input");
300
301 let request = CallRequestBuilder::default()
302 .to(Some(primary_deployed_address))
303 .data(calldata.into())
304 .gas(80_000_000.into())
305 .build();
306
307 let trace = node
309 .trace_call_impl(
310 request,
311 None,
312 Some(api::TracerConfig {
313 tracer: api::SupportedTracers::CallTracer,
314 tracer_config: api::CallTracerConfig {
315 only_top_call: true,
316 },
317 }),
318 )
319 .await
320 .expect("trace call")
321 .unwrap_default();
322 assert!(trace.error.is_none());
324 assert!(trace.revert_reason.is_none());
325
326 assert!(trace.calls.is_empty());
328 }
329
330 #[tokio::test]
331 async fn test_trace_reverts() {
332 let node = InMemoryNode::test(None);
333
334 let (primary_deployed_address, _) = deploy_test_contracts(&node).await;
335
336 let func = Function {
337 name: "shouldRevert".to_string(),
338 inputs: vec![],
339 outputs: vec![],
340 state_mutability: StateMutability::NonPayable,
341 };
342
343 let request = CallRequestBuilder::default()
345 .to(Some(primary_deployed_address))
346 .data(func.selector().to_vec().into())
347 .gas(80_000_000.into())
348 .build();
349 let trace = node
350 .trace_call_impl(request, None, None)
351 .await
352 .expect("trace call")
353 .unwrap_default();
354
355 assert!(trace.revert_reason.is_some());
357 let contract_call = trace.calls.last().unwrap().calls.first().unwrap();
359
360 assert!(contract_call.revert_reason.is_some());
362 }
363
364 #[tokio::test]
365 async fn test_trace_transaction_impl() {
366 let node = InMemoryNode::test(None);
367 {
368 let mut writer = node.inner.write().await;
369 writer
370 .insert_tx_result(
371 H256::repeat_byte(0x1),
372 TransactionResult {
373 info: testing::default_tx_execution_info(),
374 new_bytecodes: vec![],
375 receipt: api::TransactionReceipt {
376 logs: vec![LogBuilder::new()
377 .set_address(H160::repeat_byte(0xa1))
378 .build()],
379 ..Default::default()
380 },
381 debug: testing::default_tx_debug_info(),
382 },
383 )
384 .await;
385 }
386 let result = node
387 .trace_transaction_impl(H256::repeat_byte(0x1), None)
388 .await
389 .unwrap()
390 .unwrap()
391 .unwrap_default();
392 assert_eq!(result.calls.len(), 1);
393 }
394
395 #[tokio::test]
396 async fn test_trace_transaction_only_top() {
397 let node = InMemoryNode::test(None);
398 node.inner
399 .write()
400 .await
401 .insert_tx_result(
402 H256::repeat_byte(0x1),
403 TransactionResult {
404 info: testing::default_tx_execution_info(),
405 new_bytecodes: vec![],
406 receipt: api::TransactionReceipt {
407 logs: vec![LogBuilder::new()
408 .set_address(H160::repeat_byte(0xa1))
409 .build()],
410 ..Default::default()
411 },
412 debug: testing::default_tx_debug_info(),
413 },
414 )
415 .await;
416 let result = node
417 .trace_transaction_impl(
418 H256::repeat_byte(0x1),
419 Some(api::TracerConfig {
420 tracer: api::SupportedTracers::CallTracer,
421 tracer_config: api::CallTracerConfig {
422 only_top_call: true,
423 },
424 }),
425 )
426 .await
427 .unwrap()
428 .unwrap()
429 .unwrap_default();
430 assert!(result.calls.is_empty());
431 }
432
433 #[tokio::test]
434 async fn test_trace_transaction_not_found() {
435 let node = InMemoryNode::test(None);
436 let result = node
437 .trace_transaction_impl(H256::repeat_byte(0x1), None)
438 .await
439 .unwrap();
440 assert!(result.is_none());
441 }
442
443 #[tokio::test]
444 async fn test_trace_block_by_hash_empty() {
445 let node = InMemoryNode::test(None);
446 let block = api::Block::<api::TransactionVariant>::default();
447 node.inner
448 .write()
449 .await
450 .insert_block(H256::repeat_byte(0x1), block)
451 .await;
452 let result = node
453 .trace_block_impl(api::BlockId::Hash(H256::repeat_byte(0x1)), None)
454 .await
455 .unwrap()
456 .unwrap_default();
457 assert_eq!(result.len(), 0);
458 }
459
460 #[tokio::test]
461 async fn test_trace_block_by_hash_impl() {
462 let node = InMemoryNode::test(None);
463 let tx = api::Transaction::default();
464 let tx_hash = tx.hash;
465 let mut block = api::Block::<api::TransactionVariant>::default();
466 block.transactions.push(api::TransactionVariant::Full(tx));
467 {
468 let mut writer = node.inner.write().await;
469 writer.insert_block(H256::repeat_byte(0x1), block).await;
470 writer
471 .insert_tx_result(
472 tx_hash,
473 TransactionResult {
474 info: testing::default_tx_execution_info(),
475 new_bytecodes: vec![],
476 receipt: api::TransactionReceipt::default(),
477 debug: testing::default_tx_debug_info(),
478 },
479 )
480 .await;
481 }
482 let result = node
483 .trace_block_impl(api::BlockId::Hash(H256::repeat_byte(0x1)), None)
484 .await
485 .unwrap()
486 .unwrap_default();
487 assert_eq!(result.len(), 1);
488 assert_eq!(result[0].result.calls.len(), 1);
489 }
490
491 #[tokio::test]
492 async fn test_trace_block_by_number_impl() {
493 let node = InMemoryNode::test(None);
494 let tx = api::Transaction::default();
495 let tx_hash = tx.hash;
496 let mut block = api::Block::<api::TransactionVariant>::default();
497 block.transactions.push(api::TransactionVariant::Full(tx));
498 {
499 let mut writer = node.inner.write().await;
500 writer.insert_block(H256::repeat_byte(0x1), block).await;
501 writer
502 .insert_block_hash(L2BlockNumber(0), H256::repeat_byte(0x1))
503 .await;
504 writer
505 .insert_tx_result(
506 tx_hash,
507 TransactionResult {
508 info: testing::default_tx_execution_info(),
509 new_bytecodes: vec![],
510 receipt: api::TransactionReceipt::default(),
511 debug: testing::default_tx_debug_info(),
512 },
513 )
514 .await;
515 }
516 let result = node
518 .trace_block_impl(api::BlockId::Number(api::BlockNumber::Latest), None)
519 .await
520 .unwrap()
521 .unwrap_default();
522 assert_eq!(result.len(), 1);
523 assert_eq!(result[0].result.calls.len(), 1);
524
525 let result = node
527 .trace_block_impl(
528 api::BlockId::Number(api::BlockNumber::Number(0.into())),
529 None,
530 )
531 .await
532 .unwrap()
533 .unwrap_default();
534 assert_eq!(result.len(), 1);
535 assert_eq!(result[0].result.calls.len(), 1);
536 }
537}