1use super::super::types::{EthAddress, EthBytes, EthHash};
11use super::super::utils::{decode_params, decode_return};
12use super::super::{decode_payload, encode_filecoin_params_as_abi, encode_filecoin_returns_as_abi};
13use super::Environment;
14use super::types::{
15 EthCallTraceAction, EthCallTraceResult, EthCreateTraceAction, EthCreateTraceResult, EthTrace,
16 TraceAction, TraceError, TraceResult,
17};
18use super::utils::trace_to_address;
19use crate::eth::{EAMMethod, EVMMethod};
20use crate::prelude::*;
21use crate::rpc::methods::state::ExecutionTrace;
22use crate::shim::fvm_shared_latest::METHOD_CONSTRUCTOR;
23use crate::shim::{actors::is_evm_actor, address::Address, error::ExitCode, state_tree::StateTree};
24use anyhow::bail;
25use fil_actor_eam_state::v12 as eam12;
26use fil_actor_evm_state::v15 as evm12;
27use fil_actor_init_state::v12::ExecReturn;
28use fil_actor_init_state::v15::Method as InitMethod;
29use fvm_ipld_blockstore::Blockstore;
30use num::FromPrimitive;
31use tracing::debug;
32
33fn trace_is_evm_or_eam(trace: &ExecutionTrace) -> bool {
35 if let Some(invoked_actor) = &trace.invoked_actor {
36 let eam_actor_id = Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR
37 .id()
38 .expect("EAM actor address should be an ID address");
39 is_evm_actor(&invoked_actor.state.code) || invoked_actor.id == eam_actor_id
40 } else {
41 false
42 }
43}
44
45fn trace_err_msg(trace: &ExecutionTrace) -> Option<TraceError> {
48 let code = trace.msg_rct.exit_code;
49
50 if code.is_success() {
51 return None;
52 }
53
54 if code == ExitCode::SYS_OUT_OF_GAS {
55 return Some(TraceError::OutOfGas);
56 }
57
58 if code < ExitCode::FIRST_ACTOR_ERROR_CODE.into() {
59 return Some(TraceError::VmError(code.value()));
60 }
61
62 if trace_is_evm_or_eam(trace) {
63 match code.into() {
64 evm12::EVM_CONTRACT_REVERTED => return Some(TraceError::Reverted),
65 evm12::EVM_CONTRACT_INVALID_INSTRUCTION => {
66 return Some(TraceError::InvalidInstruction);
67 }
68 evm12::EVM_CONTRACT_UNDEFINED_INSTRUCTION => {
69 return Some(TraceError::UndefinedInstruction);
70 }
71 evm12::EVM_CONTRACT_STACK_UNDERFLOW => return Some(TraceError::StackUnderflow),
72 evm12::EVM_CONTRACT_STACK_OVERFLOW => return Some(TraceError::StackOverflow),
73 evm12::EVM_CONTRACT_ILLEGAL_MEMORY_ACCESS => {
74 return Some(TraceError::IllegalMemoryAccess);
75 }
76 evm12::EVM_CONTRACT_BAD_JUMPDEST => return Some(TraceError::BadJumpDest),
77 evm12::EVM_CONTRACT_SELFDESTRUCT_FAILED => {
78 return Some(TraceError::SelfDestructFailed);
79 }
80 _ => (),
81 }
82 }
83 Some(TraceError::ActorError(code.value()))
84}
85
86pub fn build_traces(
88 env: &mut Environment,
89 address: &[i64],
90 trace: ExecutionTrace,
91) -> anyhow::Result<()> {
92 let (trace, recurse_into) = build_trace(env, address, trace)?;
93
94 let last_trace_idx = if let Some(trace) = trace {
95 let len = env.traces.len();
96 env.traces.push(trace);
97 env.subtrace_count += 1;
98 Some(len)
99 } else {
100 None
101 };
102
103 let (recurse_into, invoked_actor) = if let Some(trace) = recurse_into {
105 if let Some(invoked_actor) = &trace.invoked_actor {
106 let invoked_actor = invoked_actor.clone();
107 (trace, invoked_actor)
108 } else {
109 return Ok(());
110 }
111 } else {
112 return Ok(());
113 };
114
115 let mut sub_env = Environment {
116 caller: trace_to_address(&invoked_actor),
117 is_evm: is_evm_actor(&invoked_actor.state.code),
118 traces: env.traces.clone(),
119 ..Environment::default()
120 };
121 for subcall in recurse_into.subcalls.into_iter() {
122 let mut new_address = address.to_vec();
123 new_address.push(sub_env.subtrace_count);
124 build_traces(&mut sub_env, &new_address, subcall)?;
125 }
126 env.traces = sub_env.traces;
127 if let Some(idx) = last_trace_idx {
128 env.traces.get_mut(idx).expect("Infallible").subtraces = sub_env.subtrace_count;
129 }
130
131 Ok(())
132}
133
134pub fn build_trace(
138 env: &mut Environment,
139 address: &[i64],
140 trace: ExecutionTrace,
141) -> anyhow::Result<(Option<EthTrace>, Option<ExecutionTrace>)> {
142 if !address.is_empty()
161 && Into::<ExitCode>::into(trace.msg_rct.exit_code) == ExitCode::SYS_INSUFFICIENT_FUNDS
162 {
163 return Ok((None, None));
164 }
165
166 if trace.invoked_actor.is_none() {
170 return Ok((None, None));
171 }
172
173 let method = EVMMethod::from_u64(trace.msg.method);
179 if let Some(EVMMethod::InvokeContract) = method {
180 let (trace, exec_trace) = trace_evm_call(env, address, trace)?;
181 return Ok((Some(trace), Some(exec_trace)));
182 }
183
184 match trace.msg.to {
186 Address::INIT_ACTOR => {
187 let method = InitMethod::from_u64(trace.msg.method);
188 match method {
189 Some(InitMethod::Exec) | Some(InitMethod::Exec4) => {
190 return trace_native_create(env, address, &trace);
191 }
192 _ => (),
193 }
194 }
195 Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR => {
196 let method = EAMMethod::from_u64(trace.msg.method);
197 match method {
198 Some(EAMMethod::Create)
199 | Some(EAMMethod::Create2)
200 | Some(EAMMethod::CreateExternal) => {
201 return trace_eth_create(env, address, &trace);
202 }
203 _ => (),
204 }
205 }
206 _ => (),
207 }
208
209 if env.is_evm && trace.msg.method > 0 && trace.msg.method < 1024 {
219 return trace_evm_private(env, address, &trace);
220 }
221
222 Ok((Some(trace_native_call(env, address, &trace)?), Some(trace)))
223}
224
225fn trace_call(
227 env: &mut Environment,
228 address: &[i64],
229 trace: &ExecutionTrace,
230 input: EthBytes,
231 output: EthBytes,
232) -> anyhow::Result<EthTrace> {
233 if let Some(invoked_actor) = &trace.invoked_actor {
234 let to = trace_to_address(invoked_actor);
235 let call_type: String = if trace.msg.read_only.unwrap_or_default() {
236 "staticcall"
237 } else {
238 "call"
239 }
240 .into();
241
242 Ok(EthTrace {
243 r#type: "call".into(),
244 action: TraceAction::Call(EthCallTraceAction {
245 call_type,
246 from: env.caller,
247 to: Some(to),
248 gas: trace.msg.gas_limit.unwrap_or_default().into(),
249 value: trace.msg.value.clone().into(),
250 input,
251 }),
252 result: TraceResult::Call(EthCallTraceResult {
253 gas_used: trace.sum_gas().total_gas.into(),
254 output,
255 }),
256 trace_address: Vec::from(address),
257 error: trace_err_msg(trace),
258 ..EthTrace::default()
259 })
260 } else {
261 bail!("no invoked actor")
262 }
263}
264
265fn trace_native_call(
267 env: &mut Environment,
268 address: &[i64],
269 trace: &ExecutionTrace,
270) -> anyhow::Result<EthTrace> {
271 trace_call(
272 env,
273 address,
274 trace,
275 encode_filecoin_params_as_abi(trace.msg.method, trace.msg.params_codec, &trace.msg.params)?,
276 EthBytes(encode_filecoin_returns_as_abi(
277 trace.msg_rct.exit_code.value().into(),
278 trace.msg_rct.return_codec,
279 &trace.msg_rct.r#return,
280 )),
281 )
282}
283
284fn trace_evm_call(
287 env: &mut Environment,
288 address: &[i64],
289 trace: ExecutionTrace,
290) -> anyhow::Result<(EthTrace, ExecutionTrace)> {
291 let input = match decode_payload(&trace.msg.params, trace.msg.params_codec) {
292 Ok(value) => value,
293 Err(err) => {
294 debug!("failed to decode contract invocation payload: {err}");
295 return Ok((trace_native_call(env, address, &trace)?, trace));
296 }
297 };
298 let output = match decode_payload(&trace.msg_rct.r#return, trace.msg_rct.return_codec) {
299 Ok(value) => value,
300 Err(err) => {
301 debug!("failed to decode contract invocation return: {err}");
302 return Ok((trace_native_call(env, address, &trace)?, trace));
303 }
304 };
305 Ok((trace_call(env, address, &trace, input, output)?, trace))
306}
307
308fn trace_native_create(
312 env: &mut Environment,
313 address: &[i64],
314 trace: &ExecutionTrace,
315) -> anyhow::Result<(Option<EthTrace>, Option<ExecutionTrace>)> {
316 if trace.msg.read_only.unwrap_or_default() {
317 return Ok((None, None));
322 }
323
324 let sub_trace = trace
325 .subcalls
326 .iter()
327 .find(|c| c.msg.method == METHOD_CONSTRUCTOR);
328
329 let sub_trace = if let Some(sub_trace) = sub_trace {
330 sub_trace
331 } else {
332 if trace.msg_rct.exit_code.is_success() {
336 bail!("successful Exec/Exec4 call failed to call a constructor");
337 }
338 return Ok((None, None));
348 };
349
350 if trace.msg.method == (InitMethod::Exec4 as u64) {
355 bail!("direct call to Exec4 successfully called a constructor!");
356 }
357
358 let mut output = EthBytes::default();
359 let mut create_addr = None;
360 if trace.msg_rct.exit_code.is_success() {
361 output = EthBytes(vec![0xFE]);
366
367 let init_return: ExecReturn = decode_return(&trace.msg_rct)?;
369 let actor_id = init_return.id_address.id()?;
370 create_addr = Some(EthAddress::from_actor_id(actor_id));
371 }
372
373 Ok((
374 Some(EthTrace {
375 r#type: "create".into(),
376 action: TraceAction::Create(EthCreateTraceAction {
377 from: env.caller,
378 gas: trace.msg.gas_limit.unwrap_or_default().into(),
379 value: trace.msg.value.clone().into(),
380 init: EthBytes(vec![0xFE]),
384 }),
385 result: TraceResult::Create(EthCreateTraceResult {
386 gas_used: trace.sum_gas().total_gas.into(),
387 address: create_addr,
388 code: output,
389 }),
390 trace_address: Vec::from(address),
391 error: trace_err_msg(trace),
392 ..EthTrace::default()
393 }),
394 Some(sub_trace.clone()),
395 ))
396}
397
398fn decode_create_via_eam(trace: &ExecutionTrace) -> anyhow::Result<(Vec<u8>, Option<EthAddress>)> {
402 let init_code = match EAMMethod::from_u64(trace.msg.method) {
403 Some(EAMMethod::Create) => {
404 let params = decode_params::<eam12::CreateParams>(&trace.msg)?;
405 params.initcode
406 }
407 Some(EAMMethod::Create2) => {
408 let params = decode_params::<eam12::Create2Params>(&trace.msg)?;
409 params.initcode
410 }
411 Some(EAMMethod::CreateExternal) => {
412 decode_payload(&trace.msg.params, trace.msg.params_codec)?.into()
413 }
414 _ => bail!("unexpected CREATE method {}", trace.msg.method),
415 };
416
417 let create_addr = if trace.msg_rct.exit_code.is_success() {
418 let ret = decode_return::<eam12::CreateReturn>(&trace.msg_rct)?;
419 Some(ret.eth_address.0.into())
420 } else {
421 None
422 };
423
424 Ok((init_code, create_addr))
425}
426
427fn trace_eth_create(
430 env: &mut Environment,
431 address: &[i64],
432 trace: &ExecutionTrace,
433) -> anyhow::Result<(Option<EthTrace>, Option<ExecutionTrace>)> {
434 if trace.msg.read_only.unwrap_or_default() {
436 return Ok((None, None));
437 }
438
439 let sub_trace = trace
441 .subcalls
442 .iter()
443 .filter_map(|et| {
444 if et.msg.to == Address::INIT_ACTOR {
445 et.subcalls
446 .iter()
447 .find(|et| et.msg.method == METHOD_CONSTRUCTOR)
448 } else {
449 match EVMMethod::from_u64(et.msg.method) {
450 Some(EVMMethod::Resurrect) => Some(et),
451 _ => None,
452 }
453 }
454 })
455 .next();
456
457 let sub_trace = if let Some(sub_trace) = sub_trace {
459 sub_trace
460 } else {
461 if trace.msg_rct.exit_code.is_success() {
462 bail!("successful Create/Create2 call failed to call a constructor");
463 }
464 return Ok((None, None));
465 };
466
467 let (init_code, create_addr) = decode_create_via_eam(trace)?;
469
470 let output = match trace.msg_rct.exit_code.value() {
472 0 => {
473 EthBytes(vec![0xFE])
479 }
480 33 => {
481 decode_payload(&sub_trace.msg_rct.r#return, sub_trace.msg_rct.return_codec)
485 .unwrap_or_else(|err| {
486 debug!("failed to decode create revert payload: {err}");
487 EthBytes::default()
488 })
489 }
490 _ => EthBytes::default(),
491 };
492
493 Ok((
494 Some(EthTrace {
495 r#type: "create".into(),
496 action: TraceAction::Create(EthCreateTraceAction {
497 from: env.caller,
498 gas: trace.msg.gas_limit.unwrap_or_default().into(),
499 value: trace.msg.value.clone().into(),
500 init: init_code.into(),
501 }),
502 result: TraceResult::Create(EthCreateTraceResult {
503 gas_used: trace.sum_gas().total_gas.into(),
504 address: create_addr,
505 code: output,
506 }),
507 trace_address: Vec::from(address),
508 error: trace_err_msg(trace),
509 ..EthTrace::default()
510 }),
511 Some(sub_trace.clone()),
512 ))
513}
514
515fn trace_evm_private(
518 env: &mut Environment,
519 address: &[i64],
520 trace: &ExecutionTrace,
521) -> anyhow::Result<(Option<EthTrace>, Option<ExecutionTrace>)> {
522 match EVMMethod::from_u64(trace.msg.method) {
537 Some(EVMMethod::GetBytecode) => {
538 env.last_byte_code = None;
542 if trace.msg_rct.exit_code.is_success()
543 && let Option::Some(actor_trace) = &trace.invoked_actor
544 {
545 let to = trace_to_address(actor_trace);
546 env.last_byte_code = Some(to);
547 }
548 Ok((None, None))
549 }
550 Some(EVMMethod::InvokeContractDelegate) => {
551 if env.last_byte_code.is_none() {
563 bail!("unknown bytecode for delegate call");
564 }
565
566 if let Option::Some(actor_trace) = &trace.invoked_actor {
567 let to = trace_to_address(actor_trace);
568 if env.caller != to {
569 bail!(
570 "delegate-call not from address to self: {:?} != {:?}",
571 env.caller,
572 to
573 );
574 }
575 }
576
577 let dp = decode_params::<evm12::DelegateCallParams>(&trace.msg)?;
578
579 let output = decode_payload(&trace.msg_rct.r#return, trace.msg_rct.return_codec)
580 .map_err(|e| anyhow::anyhow!("failed to decode delegate-call return: {e}"))?;
581
582 Ok((
583 Some(EthTrace {
584 r#type: "call".into(),
585 action: TraceAction::Call(EthCallTraceAction {
586 call_type: "delegatecall".into(),
587 from: env.caller,
588 to: env.last_byte_code,
589 gas: trace.msg.gas_limit.unwrap_or_default().into(),
590 value: trace.msg.value.clone().into(),
591 input: dp.input.into(),
592 }),
593 result: TraceResult::Call(EthCallTraceResult {
594 gas_used: trace.sum_gas().total_gas.into(),
595 output,
596 }),
597 trace_address: Vec::from(address),
598 error: trace_err_msg(trace),
599 ..EthTrace::default()
600 }),
601 Some(trace.clone()),
602 ))
603 }
604 _ => {
605 Ok((None, None))
608 }
609 }
610}
611
612pub struct TipsetTraceEntry {
613 pub tx_hash: EthHash,
614 pub msg_position: i64,
615 pub invoc_result: Arc<crate::rpc::state::ApiInvocResult>,
616}
617
618impl TipsetTraceEntry {
619 pub fn build_parity_traces<DB: Blockstore + ShallowClone + Send + Sync>(
621 &self,
622 state: &StateTree<DB>,
623 ) -> Result<Vec<EthTrace>, crate::rpc::error::ServerError> {
624 let mut env = super::base_environment(state, &self.invoc_result.msg.from).map_err(|e| {
625 format!(
626 "when processing message {}: {}",
627 self.invoc_result.msg_cid, e
628 )
629 })?;
630 if let Some(ref execution_trace) = self.invoc_result.execution_trace {
631 build_traces(&mut env, &[], execution_trace.clone())?;
632 }
633 Ok(env.traces)
634 }
635}
636
637#[cfg(test)]
638mod tests {
639 use super::super::test_helpers::create_test_actor;
640 use super::super::types::TraceError;
641 use super::{decode_create_via_eam, trace_err_msg, trace_is_evm_or_eam};
642 use crate::eth::EAMMethod;
643 use crate::rpc::methods::state::{ActorTrace, ExecutionTrace, MessageTrace, ReturnTrace};
644 use crate::shim::address::Address;
645 use crate::shim::econ::TokenAmount;
646 use crate::shim::error::ExitCode;
647 use fil_actor_evm_state::v15 as evm15;
648 use fvm_ipld_encoding::{CBOR, RawBytes};
649 use rstest::rstest;
650
651 fn eam_actor_id() -> u64 {
652 Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR.id().unwrap()
653 }
654
655 fn actor(id: u64) -> ActorTrace {
657 ActorTrace {
658 id,
659 state: create_test_actor(0, 0),
660 }
661 }
662
663 fn trace_with(invoked_actor: Option<ActorTrace>, exit_code: u32) -> ExecutionTrace {
664 ExecutionTrace {
665 msg: MessageTrace {
666 from: Address::new_id(1000),
667 to: Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR,
668 value: TokenAmount::default(),
669 method: 0,
670 params: RawBytes::default(),
671 params_codec: 0,
672 gas_limit: None,
673 read_only: None,
674 },
675 msg_rct: ReturnTrace {
676 exit_code: ExitCode::from(exit_code),
677 r#return: RawBytes::default(),
678 return_codec: 0,
679 },
680 invoked_actor,
681 gas_charges: vec![],
682 subcalls: vec![],
683 logs: vec![],
684 ipld_ops: vec![],
685 }
686 }
687
688 #[rstest]
689 #[case::eam(Some(actor(eam_actor_id())), true, Some(TraceError::Reverted))]
690 #[case::native_actor(Some(actor(1234)), false, Some(TraceError::ActorError(evm15::EVM_CONTRACT_REVERTED.value())))]
691 #[case::no_actor(None, false, Some(TraceError::ActorError(evm15::EVM_CONTRACT_REVERTED.value())))]
692 fn actor_kind_decides_trace_error(
693 #[case] invoked_actor: Option<ActorTrace>,
694 #[case] is_evm_or_eam: bool,
695 #[case] classified: Option<TraceError>,
696 ) {
697 let trace = trace_with(invoked_actor, evm15::EVM_CONTRACT_REVERTED.value());
698 assert_eq!(trace_is_evm_or_eam(&trace), is_evm_or_eam);
699 assert_eq!(trace_err_msg(&trace), classified);
700 }
701
702 #[test]
703 fn reverted_eam_create_decodes_without_an_address() {
704 let contract_init_code = vec![0x60u8, 0x80, 0x60, 0x40];
706 let revert_payload = vec![0xde, 0xad];
709
710 let params = cbor4ii::serde::to_vec(
711 Vec::new(),
712 &cbor4ii::core::Value::Bytes(contract_init_code.clone()),
713 )
714 .unwrap();
715
716 let mut trace = trace_with(None, evm15::EVM_CONTRACT_REVERTED.value());
717 trace.msg.method = EAMMethod::CreateExternal as u64;
718 trace.msg.params = RawBytes::new(params);
719 trace.msg.params_codec = CBOR;
720 trace.msg_rct.r#return = RawBytes::new(revert_payload);
721
722 let (decoded_init_code, address) =
725 decode_create_via_eam(&trace).expect("a reverted create must still decode");
726 assert_eq!(decoded_init_code, contract_init_code);
727 assert_eq!(address, None);
728 }
729}