Skip to main content

forest/rpc/methods/eth/trace/
parity.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4//! Parity-style trace construction from Filecoin execution traces.
5//!
6//! Converts FVM [`ExecutionTrace`] trees into Parity-compatible [`EthTrace`]
7//! entries. Handles EVM calls, delegate calls, and contract creation through
8//! both native (`Init`) and EVM (`EAM`) paths.
9
10use 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
33/// Returns `true` if the invoked actor is an EVM contract or the Ethereum Account Manager.
34fn 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
45/// Converts a trace's exit code into a typed [`TraceError`].
46/// Returns `None` when the trace completed successfully.
47fn 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
86/// Recursively builds the traces for a given ExecutionTrace by walking the subcalls
87pub 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    // Skip if there's nothing more to do and/or `build_trace` told us to skip this one.
104    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
134// `build_trace` processes the passed execution trace and updates the environment, if necessary.
135//
136// On success, it returns a trace to add (or `None` to skip) and the trace to recurse into (or `None` to skip).
137pub fn build_trace(
138    env: &mut Environment,
139    address: &[i64],
140    trace: ExecutionTrace,
141) -> anyhow::Result<(Option<EthTrace>, Option<ExecutionTrace>)> {
142    // This function first assumes that the call is a "native" call, then handles all the "not
143    // native" cases. If we get any unexpected results in any of these special cases, we just
144    // keep the "native" interpretation and move on.
145    //
146    // 1. If we're invoking a contract (even if the caller is a native account/actor), we
147    //    attempt to decode the params/return value as a contract invocation.
148    // 2. If we're calling the EAM and/or init actor, we try to treat the call as a CREATE.
149    // 3. Finally, if the caller is an EVM smart contract and it's calling a "private" (1-1023)
150    //    method, we know something special is going on. We look for calls related to
151    //    DELEGATECALL and drop everything else (everything else includes calls triggered by,
152    //    e.g., EXTCODEHASH).
153
154    // If we don't have sufficient funds, or we have a fatal error, or we have some
155    // other syscall error: skip the entire trace to mimic Ethereum (Ethereum records
156    // traces _after_ checking things like this).
157    //
158    // NOTE: The FFI currently folds all unknown syscall errors into "sys assertion
159    // failed" which is turned into SysErrFatal.
160    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    // We may fail before we can even invoke the actor. In that case, we have no 100% reliable
167    // way of getting its address (e.g., due to reverts) so we're just going to drop the entire
168    // trace. This is OK (ish) because the call never really "happened".
169    if trace.invoked_actor.is_none() {
170        return Ok((None, None));
171    }
172
173    // Step 2: Decode as a contract invocation
174    //
175    // Normal EVM calls. We don't care if the caller/receiver are actually EVM actors, we only
176    // care if the call _looks_ like an EVM call. If we fail to decode it as an EVM call, we
177    // fallback on interpreting it as a native call.
178    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    // Step 3: Decode as a contract deployment
185    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    // Step 4: Handle DELEGATECALL
210    //
211    // EVM contracts cannot call methods in the range 1-1023, only the EVM itself can. So, if we
212    // see a call in this range, we know it's an implementation detail of the EVM and not an
213    // explicit call by the user.
214    //
215    // While the EVM calls several methods in this range (some we've already handled above with
216    // respect to the EAM), we only care about the ones relevant DELEGATECALL and can _ignore_
217    // all the others.
218    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
225// Build an EthTrace for a "call" with the given input & output.
226fn 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
265// Build an EthTrace for a "call", parsing the inputs & outputs as a "native" FVM call.
266fn 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
284// Build an EthTrace for a "call", parsing the inputs & outputs as an EVM call (falling back on
285// treating it as a native call).
286fn 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
308// Build an EthTrace for a native "create" operation. This should only be called with an
309// ExecutionTrace is an Exec or Exec4 method invocation on the Init actor.
310
311fn 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        // "create" isn't valid in a staticcall, so we just skip this trace
318        // (couldn't have created an actor anyways).
319        // This mimic's the EVM: it doesn't trace CREATE calls when in
320        // read-only mode.
321        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 we succeed in calling Exec/Exec4 but don't even try to construct
333        // something, we have a bug in our tracing logic or a mismatch between our
334        // tracing logic and the actors.
335        if trace.msg_rct.exit_code.is_success() {
336            bail!("successful Exec/Exec4 call failed to call a constructor");
337        }
338        // Otherwise, this can happen if creation fails early (bad params,
339        // out of gas, contract already exists, etc.). The EVM wouldn't
340        // trace such cases, so we don't either.
341        //
342        // NOTE: It's actually impossible to run out of gas before calling
343        // initcode in the EVM (without running out of gas in the calling
344        // contract), but this is an equivalent edge-case to InvokedActor
345        // being nil, so we treat it the same way and skip the entire
346        // operation.
347        return Ok((None, None));
348    };
349
350    // Native actors that aren't the EAM can attempt to call Exec4, but such
351    // call should fail immediately without ever attempting to construct an
352    // actor. I'm catching this here because it likely means that there's a bug
353    // in our trace-conversion logic.
354    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        // We're supposed to put the "installed bytecode" here. But this
362        // isn't an EVM actor, so we just put some invalid bytecode (this is
363        // the answer you'd get if you called EXTCODECOPY on a native
364        // non-account actor, anyways).
365        output = EthBytes(vec![0xFE]);
366
367        // Extract the address of the created actor from the return value.
368        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                // If we get here, this isn't a native EVM create. Those always go through
381                // the EAM. So we have no "real" initcode and must use the sentinel value
382                // for "invalid" initcode.
383                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
398// Decode the parameters and return value of an EVM smart contract creation through the EAM. This
399// should only be called with an ExecutionTrace for a Create, Create2, or CreateExternal method
400// invocation on the EAM.
401fn 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
427// Build an EthTrace for an EVM "create" operation. This should only be called with an
428// ExecutionTrace for a Create, Create2, or CreateExternal method invocation on the EAM.
429fn trace_eth_create(
430    env: &mut Environment,
431    address: &[i64],
432    trace: &ExecutionTrace,
433) -> anyhow::Result<(Option<EthTrace>, Option<ExecutionTrace>)> {
434    // Same as the Init actor case above, see the comment there.
435    if trace.msg.read_only.unwrap_or_default() {
436        return Ok((None, None));
437    }
438
439    // Look for a call to either a constructor or the EVM's resurrect method.
440    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    // Same as the Init actor case above, see the comment there.
458    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    // Decode inputs & determine create type.
468    let (init_code, create_addr) = decode_create_via_eam(trace)?;
469
470    // Handle the output.
471    let output = match trace.msg_rct.exit_code.value() {
472        0 => {
473            // success
474            // We're _supposed_ to include the contracts bytecode here, but we
475            // can't do that reliably (e.g., if some part of the trace reverts).
476            // So we don't try and include a sentinel "impossible bytecode"
477            // value (the value specified by EIP-3541).
478            EthBytes(vec![0xFE])
479        }
480        33 => {
481            // Reverted, parse the revert message.
482            // If we managed to call the constructor, parse/return its revert message. If we
483            // fail, we just return no output.
484            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
515// Build an EthTrace for a "private" method invocation from the EVM. This should only be called with
516// an ExecutionTrace from an EVM instance and on a method between 1 and 1023 inclusive.
517fn trace_evm_private(
518    env: &mut Environment,
519    address: &[i64],
520    trace: &ExecutionTrace,
521) -> anyhow::Result<(Option<EthTrace>, Option<ExecutionTrace>)> {
522    // The EVM actor implements DELEGATECALL by:
523    //
524    // 1. Asking the callee for its bytecode by calling it on the GetBytecode method.
525    // 2. Recursively invoking the currently executing contract on the
526    //    InvokeContractDelegate method.
527    //
528    // The code below "reconstructs" that delegate call by:
529    //
530    // 1. Remembering the last contract on which we called GetBytecode.
531    // 2. Treating the contract invoked in step 1 as the DELEGATECALL receiver.
532    //
533    // Note, however: GetBytecode will be called, e.g., if the user invokes the
534    // EXTCODECOPY instruction. It's not an error to see multiple GetBytecode calls
535    // before we see an InvokeContractDelegate.
536    match EVMMethod::from_u64(trace.msg.method) {
537        Some(EVMMethod::GetBytecode) => {
538            // NOTE: I'm not checking anything about the receiver here. The EVM won't
539            // DELEGATECALL any non-EVM actor, but there's no need to encode that fact
540            // here in case we decide to loosen this up in the future.
541            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            // NOTE: We return errors in all the failure cases below instead of trying
552            // to continue because the caller is an EVM actor. If something goes wrong
553            // here, there's a bug in our EVM implementation.
554
555            // Handle delegate calls
556            //
557            // 1) Look for trace from an EVM actor to itself on InvokeContractDelegate,
558            //    method 6.
559            // 2) Check that the previous trace calls another actor on method 3
560            //    (GetByteCode) and they are at the same level (same parent)
561            // 3) Treat this as a delegate call to actor A.
562            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            // We drop all other "private" calls from FEVM. We _forbid_ explicit calls between 0 and
606            // 1024 (exclusive), so any calls in this range must be implementation details.
607            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    /// Builds Parity-style traces for this entry using the given state tree.
620    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    /// A minimal invoked actor with the given id.
656    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        // The contract's creation bytecode — the `CreateExternal` call's input params.
705        let contract_init_code = vec![0x60u8, 0x80, 0x60, 0x40];
706        // The reverted call's return value: arbitrary bytes that are not a
707        // valid `CreateReturn`, so decoding them would fail.
708        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        // The init code is recovered from the params; the address is absent (not decoded,
723        // because the creation reverted) instead of the decoding failing outright.
724        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}