Skip to main content

anvil_polkadot/api_server/
trace_helpers.rs

1use crate::api_server::{error::Error, revive_conversions::AlloyU256};
2use alloy_primitives::{Address, B256};
3use alloy_rpc_types::trace::parity::{
4    Action, CallAction, CallOutput, CallType, CreateAction, CreateOutput, CreationMethod,
5    LocalizedTransactionTrace, SelfdestructAction, TraceOutput, TransactionTrace,
6};
7use pallet_revive_eth_rpc::EthRpcError;
8use polkadot_sdk::pallet_revive::evm::{
9    Block, CallTrace, CallType as ReviveCallType, HashesOrTransactionInfos, Trace, TransactionInfo,
10    TransactionTrace as ReviveTransactionTrace,
11};
12
13/// Builds a Parity block trace from a vector of TransactionTrace objects returned by the
14/// debug_traceBlockByNumber endpoint of pallet revive and a Substrate Block object recovered from
15/// pallet revive. The block must be "hydrated" with all the transactions details.
16/// This is used to build the output for Parity client's RPC method `trace_block`.
17pub fn parity_block_trace_builder(
18    traces: Vec<ReviveTransactionTrace>,
19    block: Block,
20) -> Result<Vec<LocalizedTransactionTrace>, Error> {
21    let mut parity_block_traces = Vec::new();
22    let HashesOrTransactionInfos::TransactionInfos(transaction_infos) = block.transactions else {
23        return Err(Error::InternalError(
24            "Block transactions infos are not available in the block".to_string(),
25        ));
26    };
27    for revive_transaction_trace in traces {
28        let tx_info = transaction_infos
29            .iter()
30            .find(|item| item.hash == revive_transaction_trace.tx_hash)
31            .ok_or(Error::InternalError("Transaction info not found".to_string()))?;
32        let parity_transaction_trace = parity_transaction_trace_builder(
33            revive_transaction_trace.trace,
34            Some(tx_info.clone()),
35        )?;
36        parity_block_traces.extend(parity_transaction_trace);
37    }
38    Ok(parity_block_traces)
39}
40
41/// Builds a Parity transaction trace from a Trace object returned by the debug_traceTransaction
42/// endpoint of pallet revive and a TransactionInfo object recovered from pallet revive.
43/// This is used to build the output for Parity client's RPC method `trace_transaction`.
44pub fn parity_transaction_trace_builder(
45    trace: Trace,
46    tx_info: Option<TransactionInfo>,
47) -> Result<Vec<LocalizedTransactionTrace>, Error> {
48    let call_trace = match trace {
49        Trace::Call(call_trace) => call_trace,
50        Trace::Prestate(_) => {
51            return Err(Error::InternalError("Trace is not a call trace".to_string()));
52        }
53    };
54    let mut parity_tx_traces = Vec::new();
55    let mut next_traces = vec![(vec![], call_trace)];
56    while let Some((trace_address, trace)) = next_traces.pop() {
57        let transaction_trace =
58            parity_transaction_trace_from_call_trace(trace.clone(), trace_address.clone())?;
59        let localized_trace = LocalizedTransactionTrace {
60            trace: transaction_trace,
61            block_hash: tx_info
62                .as_ref()
63                .map(|tx_info| B256::from_slice(tx_info.block_hash.as_ref())),
64            block_number: tx_info
65                .as_ref()
66                .map(|tx_info| tx_info.block_number.try_into().unwrap_or_default()),
67            transaction_hash: tx_info
68                .as_ref()
69                .map(|tx_info| B256::from_slice(tx_info.hash.as_ref())),
70            transaction_position: tx_info
71                .as_ref()
72                .map(|tx_info| tx_info.transaction_index.try_into().unwrap_or_default()),
73        };
74        parity_tx_traces.push(localized_trace);
75        for (call_index, call) in trace.calls.iter().enumerate() {
76            let mut new_trace_address = trace_address.clone();
77            new_trace_address.push(call_index);
78            next_traces.push((new_trace_address, call.clone()));
79        }
80    }
81    Ok(parity_tx_traces)
82}
83
84/// Builds a Parity TransactionTrace from a CallTrace from pallet revive and a trace address,
85/// which is built with the indices of the path from the transaction root callto the specific call
86/// in the call tree.
87fn parity_transaction_trace_from_call_trace(
88    trace: CallTrace,
89    trace_address: Vec<usize>,
90) -> Result<TransactionTrace, Error> {
91    match trace.call_type {
92        ReviveCallType::Call | ReviveCallType::StaticCall | ReviveCallType::DelegateCall => {
93            Ok(TransactionTrace {
94                action: Action::Call(CallAction {
95                    from: Address::from_slice(trace.from.as_ref()),
96                    call_type: CallType::Call,
97                    gas: trace.gas.try_into().map_err(|_| EthRpcError::ConversionError)?,
98                    input: trace.input.0.into(),
99                    to: Address::from_slice(trace.to.as_ref()),
100                    value: AlloyU256::from(trace.value.unwrap_or_default()).inner(),
101                }),
102                error: trace.error,
103                result: Some(TraceOutput::Call(CallOutput {
104                    gas_used: trace
105                        .gas_used
106                        .try_into()
107                        .map_err(|_| EthRpcError::ConversionError)?,
108                    output: trace.output.0.into(),
109                })),
110                subtraces: trace
111                    .child_call_count
112                    .try_into()
113                    .map_err(|_| EthRpcError::ConversionError)?,
114                trace_address,
115            })
116        }
117        ReviveCallType::Create | ReviveCallType::Create2 => {
118            let creation_method = match trace.call_type {
119                ReviveCallType::Create => CreationMethod::Create,
120                ReviveCallType::Create2 => CreationMethod::Create2,
121                _ => unreachable!("Unexpected call type: should be Create or Create2"),
122            };
123            Ok(TransactionTrace {
124                action: Action::Create(CreateAction {
125                    from: Address::from_slice(trace.from.as_ref()),
126                    gas: trace.gas.try_into().map_err(|_| EthRpcError::ConversionError)?,
127                    init: trace.input.0.into(),
128                    value: AlloyU256::from(trace.value.unwrap_or_default()).inner(),
129                    creation_method,
130                }),
131                error: trace.error,
132                result: Some(TraceOutput::Create(CreateOutput {
133                    address: Address::from_slice(trace.to.as_ref()),
134                    code: Default::default(),
135                    gas_used: trace
136                        .gas_used
137                        .try_into()
138                        .map_err(|_| EthRpcError::ConversionError)?,
139                })),
140                subtraces: trace
141                    .child_call_count
142                    .try_into()
143                    .map_err(|_| EthRpcError::ConversionError)?,
144                trace_address,
145            })
146        }
147        ReviveCallType::Selfdestruct => Ok(TransactionTrace {
148            action: Action::Selfdestruct(SelfdestructAction {
149                address: Address::from_slice(trace.from.as_ref()),
150                balance: AlloyU256::from(trace.value.unwrap_or_default()).inner(),
151                refund_address: Address::from_slice(trace.to.as_ref()),
152            }),
153            error: trace.error,
154            result: None,
155            subtraces: trace
156                .child_call_count
157                .try_into()
158                .map_err(|_| EthRpcError::ConversionError)?,
159            trace_address,
160        }),
161    }
162}