geth_tracer/
utils.rs

1//! Util functions for revm related ops
2use crate::config::TraceStyle;
3use alloy_primitives::{hex, B256};
4use alloy_sol_types::{ContractError, GenericRevertReason};
5use cfx_types::{Address, H160, H256, U256};
6use cfx_vm_interpreter::instructions::{
7    INSTRUCTIONS, INSTRUCTIONS_CANCUN, INSTRUCTIONS_CIP645,
8};
9use revm::interpreter::InstructionResult;
10use revm_primitives::{Address as RAddress, U256 as RU256};
11
12/// Converts a non successful [`InstructionResult`] to an error message.
13///
14/// Returns `None` if [`InstructionResult::is_ok`].
15///
16/// See also <https://github.com/ethereum/go-ethereum/blob/34d507215951fb3f4a5983b65e127577989a6db8/eth/tracers/native/call_flat.go#L39-L55>
17pub(crate) fn fmt_error_msg(
18    res: InstructionResult, kind: TraceStyle,
19) -> Option<String> {
20    if res.is_ok() {
21        return None;
22    }
23    let msg = match res {
24        InstructionResult::Revert => if kind.is_parity() {
25            "Reverted"
26        } else {
27            "execution reverted"
28        }
29        .to_string(),
30        InstructionResult::OutOfGas | InstructionResult::PrecompileOOG => {
31            if kind.is_parity() {
32                "Out of gas"
33            } else {
34                "out of gas"
35            }
36            .to_string()
37        }
38        InstructionResult::OutOfFunds => if kind.is_parity() {
39            "Insufficient balance for transfer"
40        } else {
41            "insufficient balance for transfer"
42        }
43        .to_string(),
44        InstructionResult::MemoryOOG => if kind.is_parity() {
45            "Out of gas"
46        } else {
47            "out of gas: out of memory"
48        }
49        .to_string(),
50        InstructionResult::MemoryLimitOOG => if kind.is_parity() {
51            "Out of gas"
52        } else {
53            "out of gas: reach memory limit"
54        }
55        .to_string(),
56        InstructionResult::InvalidOperandOOG => if kind.is_parity() {
57            "Out of gas"
58        } else {
59            "out of gas: invalid operand"
60        }
61        .to_string(),
62        InstructionResult::OpcodeNotFound => if kind.is_parity() {
63            "Bad instruction"
64        } else {
65            "invalid opcode"
66        }
67        .to_string(),
68        InstructionResult::StackOverflow => "Out of stack".to_string(),
69        InstructionResult::InvalidJump => if kind.is_parity() {
70            "Bad jump destination"
71        } else {
72            "invalid jump destination"
73        }
74        .to_string(),
75        InstructionResult::PrecompileError => if kind.is_parity() {
76            "Built-in failed"
77        } else {
78            "precompiled failed"
79        }
80        .to_string(),
81        InstructionResult::InvalidFEOpcode => if kind.is_parity() {
82            "Bad instruction"
83        } else {
84            "invalid opcode: INVALID"
85        }
86        .to_string(),
87        InstructionResult::ReentrancySentryOOG => if kind.is_parity() {
88            "Out of gas"
89        } else {
90            "out of gas: not enough gas for reentrancy sentry"
91        }
92        .to_string(),
93        status => format!("{status:?}"),
94    };
95
96    Some(msg)
97}
98
99/// creates the memory data in 32byte chunks
100/// see <https://github.com/ethereum/go-ethereum/blob/366d2169fbc0e0f803b68c042b77b6b480836dbc/eth/tracers/logger/logger.go#L450-L452>
101#[inline]
102pub(crate) fn convert_memory(data: &[u8]) -> Vec<String> {
103    let mut memory = Vec::with_capacity((data.len() + 31) / 32);
104    for idx in (0..data.len()).step_by(32) {
105        let len = std::cmp::min(idx + 32, data.len());
106        memory.push(hex::encode(&data[idx..len]));
107    }
108    memory
109}
110
111/// Get the gas used, accounting for refunds
112#[inline]
113pub(crate) fn gas_used(spent: u64, refunded: u64) -> u64 {
114    let refund_quotient = 5;
115    spent - (refunded).min(spent / refund_quotient)
116}
117
118/// Returns a non empty revert reason if the output is a revert/error.
119#[inline]
120pub(crate) fn maybe_revert_reason(output: &[u8]) -> Option<String> {
121    let reason = match GenericRevertReason::decode(output)? {
122        GenericRevertReason::ContractError(err) => {
123            match err {
124                // return the raw revert reason and don't use the revert's
125                // display message
126                ContractError::Revert(revert) => revert.reason,
127                err => err.to_string(),
128            }
129        }
130        GenericRevertReason::RawString(err) => err,
131    };
132    if reason.is_empty() {
133        None
134    } else {
135        Some(reason)
136    }
137}
138
139/// Returns the number of items pushed on the stack by a given opcode.
140/// This used to determine how many stack etries to put in the `push` element
141/// in a parity vmTrace.
142/// The value is obvious for most opcodes, but SWAP* and DUP* are a bit weird,
143/// and we handle those as they are handled in parity vmtraces.
144/// For reference: <https://github.com/ledgerwatch/erigon/blob/9b74cf0384385817459f88250d1d9c459a18eab1/turbo/jsonrpc/trace_adhoc.go#L451>
145pub(crate) fn stack_push_count(
146    step_op: u8, cancun_enabled: bool, cip645: bool,
147) -> usize {
148    let instuction_set = if !cancun_enabled {
149        &*INSTRUCTIONS
150    } else if cip645 {
151        &*INSTRUCTIONS_CANCUN
152    } else {
153        &*INSTRUCTIONS_CIP645
154    };
155    match instuction_set.get(step_op as usize) {
156        Some(Some(instruct)) => instruct.ret,
157        _ => 0,
158    }
159}
160
161// convert from cfx U256 to alloy U256
162pub fn to_alloy_u256(u: U256) -> RU256 {
163    let mut be_bytes: [u8; 32] = [0; 32];
164    u.to_big_endian(&mut be_bytes);
165    RU256::from_be_bytes(be_bytes)
166}
167
168pub fn to_alloy_address(h: H160) -> RAddress {
169    RAddress::from_slice(h.as_bytes())
170}
171
172pub fn to_alloy_h256(h: H256) -> B256 { B256::from(h.0) }
173
174pub fn from_alloy_address(address: RAddress) -> Address {
175    Address::from_slice(address.as_slice())
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181    use alloy_sol_types::{GenericContractError, SolInterface};
182
183    #[test]
184    fn decode_revert_reason() {
185        let err = GenericContractError::Revert("my revert".into());
186        let encoded = err.abi_encode();
187        let reason = maybe_revert_reason(&encoded).unwrap();
188        assert_eq!(reason, "my revert");
189    }
190
191    // <https://etherscan.io/tx/0x105707c8e3b3675a8424a7b0820b271cbe394eaf4d5065b03c273298e3a81314>
192    #[test]
193    fn decode_revert_reason_with_error() {
194        let err = hex!("08c379a000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000024556e697377617056323a20494e53554646494349454e545f494e5055545f414d4f554e5400000000000000000000000000000000000000000000000000000080");
195        let reason = maybe_revert_reason(&err[..]).unwrap();
196        assert_eq!(reason, "UniswapV2: INSUFFICIENT_INPUT_AMOUNT");
197    }
198}