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;
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
}
}
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()))
}
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
};
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(())
}
pub fn build_trace(
env: &mut Environment,
address: &[i64],
trace: ExecutionTrace,
) -> anyhow::Result<(Option<EthTrace>, Option<ExecutionTrace>)> {
if !address.is_empty()
&& Into::<ExitCode>::into(trace.msg_rct.exit_code) == ExitCode::SYS_INSUFFICIENT_FUNDS
{
return Ok((None, None));
}
if trace.invoked_actor.is_none() {
return Ok((None, None));
}
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)));
}
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);
}
_ => (),
}
}
_ => (),
}
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)))
}
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")
}
}
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,
)),
)
}
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))
}
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() {
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 trace.msg_rct.exit_code.is_success() {
bail!("successful Exec/Exec4 call failed to call a constructor");
}
return Ok((None, None));
};
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() {
output = EthBytes(vec![0xFE]);
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(),
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()),
))
}
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()))
}
fn trace_eth_create(
env: &mut Environment,
address: &[i64],
trace: &ExecutionTrace,
) -> anyhow::Result<(Option<EthTrace>, Option<ExecutionTrace>)> {
if trace.msg.read_only.unwrap_or_default() {
return Ok((None, None));
}
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();
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));
};
let (init_code, create_addr) = decode_create_via_eam(trace)?;
let output = match trace.msg_rct.exit_code.value() {
0 => {
EthBytes(vec![0xFE])
}
33 => {
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()),
))
}
fn trace_evm_private(
env: &mut Environment,
address: &[i64],
trace: &ExecutionTrace,
) -> anyhow::Result<(Option<EthTrace>, Option<ExecutionTrace>)> {
match EVMMethod::from_u64(trace.msg.method) {
Some(EVMMethod::GetBytecode) => {
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) => {
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()),
))
}
_ => {
Ok((None, None))
}
}
}
pub struct TipsetTraceEntry {
pub tx_hash: EthHash,
pub msg_position: i64,
pub invoc_result: crate::rpc::state::ApiInvocResult,
}
impl TipsetTraceEntry {
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)
}
}