forest-filecoin 0.33.2

Rust Filecoin implementation.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
// Copyright 2019-2026 ChainSafe Systems
// SPDX-License-Identifier: Apache-2.0, MIT

//! Parity-style trace construction from Filecoin execution traces.
//!
//! Converts FVM [`ExecutionTrace`] trees into Parity-compatible [`EthTrace`]
//! entries. Handles EVM calls, delegate calls, and contract creation through
//! both native (`Init`) and EVM (`EAM`) paths.

use super::super::types::{EthAddress, EthBytes, EthHash};
use super::super::utils::{decode_params, decode_return};
use super::super::{decode_payload, encode_filecoin_params_as_abi, encode_filecoin_returns_as_abi};
use super::Environment;
use super::types::{
    EthCallTraceAction, EthCallTraceResult, EthCreateTraceAction, EthCreateTraceResult, EthTrace,
    TraceAction, TraceError, TraceResult,
};
use super::utils::trace_to_address;
use crate::eth::{EAMMethod, EVMMethod};
use crate::rpc::methods::state::ExecutionTrace;
use crate::shim::fvm_shared_latest::METHOD_CONSTRUCTOR;
use crate::shim::{actors::is_evm_actor, address::Address, error::ExitCode, state_tree::StateTree};
use anyhow::bail;
use fil_actor_eam_state::v12 as eam12;
use fil_actor_evm_state::v15 as evm12;
use fil_actor_init_state::v12::ExecReturn;
use fil_actor_init_state::v15::Method as InitMethod;
use fvm_ipld_blockstore::Blockstore;
use num::FromPrimitive;
use tracing::debug;

/// Returns `true` if the invoked actor is an EVM contract or the Ethereum Account Manager.
fn trace_is_evm_or_eam(trace: &ExecutionTrace) -> bool {
    if let Some(invoked_actor) = &trace.invoked_actor {
        is_evm_actor(&invoked_actor.state.code)
            || invoked_actor.id != Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR.id().unwrap()
    } else {
        false
    }
}

/// Converts a trace's exit code into a typed [`TraceError`].
/// Returns `None` when the trace completed successfully.
fn trace_err_msg(trace: &ExecutionTrace) -> Option<TraceError> {
    let code = trace.msg_rct.exit_code;

    if code.is_success() {
        return None;
    }

    if code == ExitCode::SYS_OUT_OF_GAS {
        return Some(TraceError::OutOfGas);
    }

    if code < ExitCode::FIRST_ACTOR_ERROR_CODE.into() {
        return Some(TraceError::VmError(code.value()));
    }

    if trace_is_evm_or_eam(trace) {
        match code.into() {
            evm12::EVM_CONTRACT_REVERTED => return Some(TraceError::Reverted),
            evm12::EVM_CONTRACT_INVALID_INSTRUCTION => {
                return Some(TraceError::InvalidInstruction);
            }
            evm12::EVM_CONTRACT_UNDEFINED_INSTRUCTION => {
                return Some(TraceError::UndefinedInstruction);
            }
            evm12::EVM_CONTRACT_STACK_UNDERFLOW => return Some(TraceError::StackUnderflow),
            evm12::EVM_CONTRACT_STACK_OVERFLOW => return Some(TraceError::StackOverflow),
            evm12::EVM_CONTRACT_ILLEGAL_MEMORY_ACCESS => {
                return Some(TraceError::IllegalMemoryAccess);
            }
            evm12::EVM_CONTRACT_BAD_JUMPDEST => return Some(TraceError::BadJumpDest),
            evm12::EVM_CONTRACT_SELFDESTRUCT_FAILED => {
                return Some(TraceError::SelfDestructFailed);
            }
            _ => (),
        }
    }
    Some(TraceError::ActorError(code.value()))
}

/// Recursively builds the traces for a given ExecutionTrace by walking the subcalls
pub fn build_traces(
    env: &mut Environment,
    address: &[i64],
    trace: ExecutionTrace,
) -> anyhow::Result<()> {
    let (trace, recurse_into) = build_trace(env, address, trace)?;

    let last_trace_idx = if let Some(trace) = trace {
        let len = env.traces.len();
        env.traces.push(trace);
        env.subtrace_count += 1;
        Some(len)
    } else {
        None
    };

    // Skip if there's nothing more to do and/or `build_trace` told us to skip this one.
    let (recurse_into, invoked_actor) = if let Some(trace) = recurse_into {
        if let Some(invoked_actor) = &trace.invoked_actor {
            let invoked_actor = invoked_actor.clone();
            (trace, invoked_actor)
        } else {
            return Ok(());
        }
    } else {
        return Ok(());
    };

    let mut sub_env = Environment {
        caller: trace_to_address(&invoked_actor),
        is_evm: is_evm_actor(&invoked_actor.state.code),
        traces: env.traces.clone(),
        ..Environment::default()
    };
    for subcall in recurse_into.subcalls.into_iter() {
        let mut new_address = address.to_vec();
        new_address.push(sub_env.subtrace_count);
        build_traces(&mut sub_env, &new_address, subcall)?;
    }
    env.traces = sub_env.traces;
    if let Some(idx) = last_trace_idx {
        env.traces.get_mut(idx).expect("Infallible").subtraces = sub_env.subtrace_count;
    }

    Ok(())
}

// `build_trace` processes the passed execution trace and updates the environment, if necessary.
//
// On success, it returns a trace to add (or `None` to skip) and the trace to recurse into (or `None` to skip).
pub fn build_trace(
    env: &mut Environment,
    address: &[i64],
    trace: ExecutionTrace,
) -> anyhow::Result<(Option<EthTrace>, Option<ExecutionTrace>)> {
    // This function first assumes that the call is a "native" call, then handles all the "not
    // native" cases. If we get any unexpected results in any of these special cases, we just
    // keep the "native" interpretation and move on.
    //
    // 1. If we're invoking a contract (even if the caller is a native account/actor), we
    //    attempt to decode the params/return value as a contract invocation.
    // 2. If we're calling the EAM and/or init actor, we try to treat the call as a CREATE.
    // 3. Finally, if the caller is an EVM smart contract and it's calling a "private" (1-1023)
    //    method, we know something special is going on. We look for calls related to
    //    DELEGATECALL and drop everything else (everything else includes calls triggered by,
    //    e.g., EXTCODEHASH).

    // If we don't have sufficient funds, or we have a fatal error, or we have some
    // other syscall error: skip the entire trace to mimic Ethereum (Ethereum records
    // traces _after_ checking things like this).
    //
    // NOTE: The FFI currently folds all unknown syscall errors into "sys assertion
    // failed" which is turned into SysErrFatal.
    if !address.is_empty()
        && Into::<ExitCode>::into(trace.msg_rct.exit_code) == ExitCode::SYS_INSUFFICIENT_FUNDS
    {
        return Ok((None, None));
    }

    // We may fail before we can even invoke the actor. In that case, we have no 100% reliable
    // way of getting its address (e.g., due to reverts) so we're just going to drop the entire
    // trace. This is OK (ish) because the call never really "happened".
    if trace.invoked_actor.is_none() {
        return Ok((None, None));
    }

    // Step 2: Decode as a contract invocation
    //
    // Normal EVM calls. We don't care if the caller/receiver are actually EVM actors, we only
    // care if the call _looks_ like an EVM call. If we fail to decode it as an EVM call, we
    // fallback on interpreting it as a native call.
    let method = EVMMethod::from_u64(trace.msg.method);
    if let Some(EVMMethod::InvokeContract) = method {
        let (trace, exec_trace) = trace_evm_call(env, address, trace)?;
        return Ok((Some(trace), Some(exec_trace)));
    }

    // Step 3: Decode as a contract deployment
    match trace.msg.to {
        Address::INIT_ACTOR => {
            let method = InitMethod::from_u64(trace.msg.method);
            match method {
                Some(InitMethod::Exec) | Some(InitMethod::Exec4) => {
                    return trace_native_create(env, address, &trace);
                }
                _ => (),
            }
        }
        Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR => {
            let method = EAMMethod::from_u64(trace.msg.method);
            match method {
                Some(EAMMethod::Create)
                | Some(EAMMethod::Create2)
                | Some(EAMMethod::CreateExternal) => {
                    return trace_eth_create(env, address, &trace);
                }
                _ => (),
            }
        }
        _ => (),
    }

    // Step 4: Handle DELEGATECALL
    //
    // EVM contracts cannot call methods in the range 1-1023, only the EVM itself can. So, if we
    // see a call in this range, we know it's an implementation detail of the EVM and not an
    // explicit call by the user.
    //
    // While the EVM calls several methods in this range (some we've already handled above with
    // respect to the EAM), we only care about the ones relevant DELEGATECALL and can _ignore_
    // all the others.
    if env.is_evm && trace.msg.method > 0 && trace.msg.method < 1024 {
        return trace_evm_private(env, address, &trace);
    }

    Ok((Some(trace_native_call(env, address, &trace)?), Some(trace)))
}

// Build an EthTrace for a "call" with the given input & output.
fn trace_call(
    env: &mut Environment,
    address: &[i64],
    trace: &ExecutionTrace,
    input: EthBytes,
    output: EthBytes,
) -> anyhow::Result<EthTrace> {
    if let Some(invoked_actor) = &trace.invoked_actor {
        let to = trace_to_address(invoked_actor);
        let call_type: String = if trace.msg.read_only.unwrap_or_default() {
            "staticcall"
        } else {
            "call"
        }
        .into();

        Ok(EthTrace {
            r#type: "call".into(),
            action: TraceAction::Call(EthCallTraceAction {
                call_type,
                from: env.caller,
                to: Some(to),
                gas: trace.msg.gas_limit.unwrap_or_default().into(),
                value: trace.msg.value.clone().into(),
                input,
            }),
            result: TraceResult::Call(EthCallTraceResult {
                gas_used: trace.sum_gas().total_gas.into(),
                output,
            }),
            trace_address: Vec::from(address),
            error: trace_err_msg(trace),
            ..EthTrace::default()
        })
    } else {
        bail!("no invoked actor")
    }
}

// Build an EthTrace for a "call", parsing the inputs & outputs as a "native" FVM call.
fn trace_native_call(
    env: &mut Environment,
    address: &[i64],
    trace: &ExecutionTrace,
) -> anyhow::Result<EthTrace> {
    trace_call(
        env,
        address,
        trace,
        encode_filecoin_params_as_abi(trace.msg.method, trace.msg.params_codec, &trace.msg.params)?,
        EthBytes(encode_filecoin_returns_as_abi(
            trace.msg_rct.exit_code.value().into(),
            trace.msg_rct.return_codec,
            &trace.msg_rct.r#return,
        )),
    )
}

// Build an EthTrace for a "call", parsing the inputs & outputs as an EVM call (falling back on
// treating it as a native call).
fn trace_evm_call(
    env: &mut Environment,
    address: &[i64],
    trace: ExecutionTrace,
) -> anyhow::Result<(EthTrace, ExecutionTrace)> {
    let input = match decode_payload(&trace.msg.params, trace.msg.params_codec) {
        Ok(value) => value,
        Err(err) => {
            debug!("failed to decode contract invocation payload: {err}");
            return Ok((trace_native_call(env, address, &trace)?, trace));
        }
    };
    let output = match decode_payload(&trace.msg_rct.r#return, trace.msg_rct.return_codec) {
        Ok(value) => value,
        Err(err) => {
            debug!("failed to decode contract invocation return: {err}");
            return Ok((trace_native_call(env, address, &trace)?, trace));
        }
    };
    Ok((trace_call(env, address, &trace, input, output)?, trace))
}

// Build an EthTrace for a native "create" operation. This should only be called with an
// ExecutionTrace is an Exec or Exec4 method invocation on the Init actor.

fn trace_native_create(
    env: &mut Environment,
    address: &[i64],
    trace: &ExecutionTrace,
) -> anyhow::Result<(Option<EthTrace>, Option<ExecutionTrace>)> {
    if trace.msg.read_only.unwrap_or_default() {
        // "create" isn't valid in a staticcall, so we just skip this trace
        // (couldn't have created an actor anyways).
        // This mimic's the EVM: it doesn't trace CREATE calls when in
        // read-only mode.
        return Ok((None, None));
    }

    let sub_trace = trace
        .subcalls
        .iter()
        .find(|c| c.msg.method == METHOD_CONSTRUCTOR);

    let sub_trace = if let Some(sub_trace) = sub_trace {
        sub_trace
    } else {
        // If we succeed in calling Exec/Exec4 but don't even try to construct
        // something, we have a bug in our tracing logic or a mismatch between our
        // tracing logic and the actors.
        if trace.msg_rct.exit_code.is_success() {
            bail!("successful Exec/Exec4 call failed to call a constructor");
        }
        // Otherwise, this can happen if creation fails early (bad params,
        // out of gas, contract already exists, etc.). The EVM wouldn't
        // trace such cases, so we don't either.
        //
        // NOTE: It's actually impossible to run out of gas before calling
        // initcode in the EVM (without running out of gas in the calling
        // contract), but this is an equivalent edge-case to InvokedActor
        // being nil, so we treat it the same way and skip the entire
        // operation.
        return Ok((None, None));
    };

    // Native actors that aren't the EAM can attempt to call Exec4, but such
    // call should fail immediately without ever attempting to construct an
    // actor. I'm catching this here because it likely means that there's a bug
    // in our trace-conversion logic.
    if trace.msg.method == (InitMethod::Exec4 as u64) {
        bail!("direct call to Exec4 successfully called a constructor!");
    }

    let mut output = EthBytes::default();
    let mut create_addr = None;
    if trace.msg_rct.exit_code.is_success() {
        // We're supposed to put the "installed bytecode" here. But this
        // isn't an EVM actor, so we just put some invalid bytecode (this is
        // the answer you'd get if you called EXTCODECOPY on a native
        // non-account actor, anyways).
        output = EthBytes(vec![0xFE]);

        // Extract the address of the created actor from the return value.
        let init_return: ExecReturn = decode_return(&trace.msg_rct)?;
        let actor_id = init_return.id_address.id()?;
        create_addr = Some(EthAddress::from_actor_id(actor_id));
    }

    Ok((
        Some(EthTrace {
            r#type: "create".into(),
            action: TraceAction::Create(EthCreateTraceAction {
                from: env.caller,
                gas: trace.msg.gas_limit.unwrap_or_default().into(),
                value: trace.msg.value.clone().into(),
                // If we get here, this isn't a native EVM create. Those always go through
                // the EAM. So we have no "real" initcode and must use the sentinel value
                // for "invalid" initcode.
                init: EthBytes(vec![0xFE]),
            }),
            result: TraceResult::Create(EthCreateTraceResult {
                gas_used: trace.sum_gas().total_gas.into(),
                address: create_addr,
                code: output,
            }),
            trace_address: Vec::from(address),
            error: trace_err_msg(trace),
            ..EthTrace::default()
        }),
        Some(sub_trace.clone()),
    ))
}

// Decode the parameters and return value of an EVM smart contract creation through the EAM. This
// should only be called with an ExecutionTrace for a Create, Create2, or CreateExternal method
// invocation on the EAM.
fn decode_create_via_eam(trace: &ExecutionTrace) -> anyhow::Result<(Vec<u8>, EthAddress)> {
    let init_code = match EAMMethod::from_u64(trace.msg.method) {
        Some(EAMMethod::Create) => {
            let params = decode_params::<eam12::CreateParams>(&trace.msg)?;
            params.initcode
        }
        Some(EAMMethod::Create2) => {
            let params = decode_params::<eam12::Create2Params>(&trace.msg)?;
            params.initcode
        }
        Some(EAMMethod::CreateExternal) => {
            decode_payload(&trace.msg.params, trace.msg.params_codec)?.into()
        }
        _ => bail!("unexpected CREATE method {}", trace.msg.method),
    };
    let ret = decode_return::<eam12::CreateReturn>(&trace.msg_rct)?;

    Ok((init_code, ret.eth_address.0.into()))
}

// Build an EthTrace for an EVM "create" operation. This should only be called with an
// ExecutionTrace for a Create, Create2, or CreateExternal method invocation on the EAM.
fn trace_eth_create(
    env: &mut Environment,
    address: &[i64],
    trace: &ExecutionTrace,
) -> anyhow::Result<(Option<EthTrace>, Option<ExecutionTrace>)> {
    // Same as the Init actor case above, see the comment there.
    if trace.msg.read_only.unwrap_or_default() {
        return Ok((None, None));
    }

    // Look for a call to either a constructor or the EVM's resurrect method.
    let sub_trace = trace
        .subcalls
        .iter()
        .filter_map(|et| {
            if et.msg.to == Address::INIT_ACTOR {
                et.subcalls
                    .iter()
                    .find(|et| et.msg.method == METHOD_CONSTRUCTOR)
            } else {
                match EVMMethod::from_u64(et.msg.method) {
                    Some(EVMMethod::Resurrect) => Some(et),
                    _ => None,
                }
            }
        })
        .next();

    // Same as the Init actor case above, see the comment there.
    let sub_trace = if let Some(sub_trace) = sub_trace {
        sub_trace
    } else {
        if trace.msg_rct.exit_code.is_success() {
            bail!("successful Create/Create2 call failed to call a constructor");
        }
        return Ok((None, None));
    };

    // Decode inputs & determine create type.
    let (init_code, create_addr) = decode_create_via_eam(trace)?;

    // Handle the output.
    let output = match trace.msg_rct.exit_code.value() {
        0 => {
            // success
            // We're _supposed_ to include the contracts bytecode here, but we
            // can't do that reliably (e.g., if some part of the trace reverts).
            // So we don't try and include a sentinel "impossible bytecode"
            // value (the value specified by EIP-3541).
            EthBytes(vec![0xFE])
        }
        33 => {
            // Reverted, parse the revert message.
            // If we managed to call the constructor, parse/return its revert message. If we
            // fail, we just return no output.
            decode_payload(&sub_trace.msg_rct.r#return, sub_trace.msg_rct.return_codec)
                .unwrap_or_else(|err| {
                    debug!("failed to decode create revert payload: {err}");
                    EthBytes::default()
                })
        }
        _ => EthBytes::default(),
    };

    Ok((
        Some(EthTrace {
            r#type: "create".into(),
            action: TraceAction::Create(EthCreateTraceAction {
                from: env.caller,
                gas: trace.msg.gas_limit.unwrap_or_default().into(),
                value: trace.msg.value.clone().into(),
                init: init_code.into(),
            }),
            result: TraceResult::Create(EthCreateTraceResult {
                gas_used: trace.sum_gas().total_gas.into(),
                address: Some(create_addr),
                code: output,
            }),
            trace_address: Vec::from(address),
            error: trace_err_msg(trace),
            ..EthTrace::default()
        }),
        Some(sub_trace.clone()),
    ))
}

// Build an EthTrace for a "private" method invocation from the EVM. This should only be called with
// an ExecutionTrace from an EVM instance and on a method between 1 and 1023 inclusive.
fn trace_evm_private(
    env: &mut Environment,
    address: &[i64],
    trace: &ExecutionTrace,
) -> anyhow::Result<(Option<EthTrace>, Option<ExecutionTrace>)> {
    // The EVM actor implements DELEGATECALL by:
    //
    // 1. Asking the callee for its bytecode by calling it on the GetBytecode method.
    // 2. Recursively invoking the currently executing contract on the
    //    InvokeContractDelegate method.
    //
    // The code below "reconstructs" that delegate call by:
    //
    // 1. Remembering the last contract on which we called GetBytecode.
    // 2. Treating the contract invoked in step 1 as the DELEGATECALL receiver.
    //
    // Note, however: GetBytecode will be called, e.g., if the user invokes the
    // EXTCODECOPY instruction. It's not an error to see multiple GetBytecode calls
    // before we see an InvokeContractDelegate.
    match EVMMethod::from_u64(trace.msg.method) {
        Some(EVMMethod::GetBytecode) => {
            // NOTE: I'm not checking anything about the receiver here. The EVM won't
            // DELEGATECALL any non-EVM actor, but there's no need to encode that fact
            // here in case we decide to loosen this up in the future.
            env.last_byte_code = None;
            if trace.msg_rct.exit_code.is_success()
                && let Option::Some(actor_trace) = &trace.invoked_actor
            {
                let to = trace_to_address(actor_trace);
                env.last_byte_code = Some(to);
            }
            Ok((None, None))
        }
        Some(EVMMethod::InvokeContractDelegate) => {
            // NOTE: We return errors in all the failure cases below instead of trying
            // to continue because the caller is an EVM actor. If something goes wrong
            // here, there's a bug in our EVM implementation.

            // Handle delegate calls
            //
            // 1) Look for trace from an EVM actor to itself on InvokeContractDelegate,
            //    method 6.
            // 2) Check that the previous trace calls another actor on method 3
            //    (GetByteCode) and they are at the same level (same parent)
            // 3) Treat this as a delegate call to actor A.
            if env.last_byte_code.is_none() {
                bail!("unknown bytecode for delegate call");
            }

            if let Option::Some(actor_trace) = &trace.invoked_actor {
                let to = trace_to_address(actor_trace);
                if env.caller != to {
                    bail!(
                        "delegate-call not from address to self: {:?} != {:?}",
                        env.caller,
                        to
                    );
                }
            }

            let dp = decode_params::<evm12::DelegateCallParams>(&trace.msg)?;

            let output = decode_payload(&trace.msg_rct.r#return, trace.msg_rct.return_codec)
                .map_err(|e| anyhow::anyhow!("failed to decode delegate-call return: {}", e))?;

            Ok((
                Some(EthTrace {
                    r#type: "call".into(),
                    action: TraceAction::Call(EthCallTraceAction {
                        call_type: "delegatecall".into(),
                        from: env.caller,
                        to: env.last_byte_code,
                        gas: trace.msg.gas_limit.unwrap_or_default().into(),
                        value: trace.msg.value.clone().into(),
                        input: dp.input.into(),
                    }),
                    result: TraceResult::Call(EthCallTraceResult {
                        gas_used: trace.sum_gas().total_gas.into(),
                        output,
                    }),
                    trace_address: Vec::from(address),
                    error: trace_err_msg(trace),
                    ..EthTrace::default()
                }),
                Some(trace.clone()),
            ))
        }
        _ => {
            // We drop all other "private" calls from FEVM. We _forbid_ explicit calls between 0 and
            // 1024 (exclusive), so any calls in this range must be implementation details.
            Ok((None, None))
        }
    }
}

pub struct TipsetTraceEntry {
    pub tx_hash: EthHash,
    pub msg_position: i64,
    pub invoc_result: crate::rpc::state::ApiInvocResult,
}

impl TipsetTraceEntry {
    /// Builds Parity-style traces for this entry using the given state tree.
    pub fn build_parity_traces<DB: Blockstore + Send + Sync>(
        &self,
        state: &StateTree<DB>,
    ) -> Result<Vec<EthTrace>, crate::rpc::error::ServerError> {
        let mut env = super::base_environment(state, &self.invoc_result.msg.from).map_err(|e| {
            format!(
                "when processing message {}: {}",
                self.invoc_result.msg_cid, e
            )
        })?;
        if let Some(ref execution_trace) = self.invoc_result.execution_trace {
            build_traces(&mut env, &[], execution_trace.clone())?;
        }
        Ok(env.traces)
    }
}