Skip to main content

forest/rpc/methods/eth/
utils.rs

1// Copyright 2019-2026 ChainSafe Systems
2// SPDX-License-Identifier: Apache-2.0, MIT
3
4use super::types::{EthAddress, EthBytes};
5use crate::rpc::state::{MessageTrace, ReturnTrace};
6use crate::shim::actors::{EVMActorStateLoad as _, evm, is_evm_actor};
7use crate::shim::address::Address as FilecoinAddress;
8use crate::shim::fvm_shared_latest::IDENTITY_HASH;
9use crate::shim::state_tree::{ActorState, StateTree};
10use ahash::{HashMap, HashMapExt};
11
12use crate::rpc::eth::{EVM_WORD_LENGTH, EthUint64};
13use anyhow::{Context as _, Result, bail};
14use cbor4ii::core::Value;
15use cbor4ii::core::dec::Decode as _;
16use fvm_ipld_blockstore::Blockstore;
17use fvm_ipld_encoding::{CBOR, DAG_CBOR, IPLD_RAW, RawBytes};
18use serde::de;
19use std::sync::LazyLock;
20use tracing::log;
21
22pub fn lookup_eth_address<DB: Blockstore>(
23    addr: &FilecoinAddress,
24    state: &StateTree<DB>,
25) -> Result<Option<EthAddress>> {
26    // Attempt to convert directly, if it's an f4 address.
27    if let Ok(eth_addr) = EthAddress::from_filecoin_address(addr)
28        && !eth_addr.is_masked_id()
29    {
30        return Ok(Some(eth_addr));
31    }
32
33    // Otherwise, resolve the ID addr.
34    let id_addr = match state.lookup_id(addr)? {
35        Some(id) => id,
36        _ => return Ok(None),
37    };
38
39    // Lookup on the target actor and try to get an f410 address.
40    let result = state.get_actor(addr);
41    if let Ok(Some(actor_state)) = result {
42        if let Some(addr) = actor_state.delegated_address {
43            if let Ok(eth_addr) = EthAddress::from_filecoin_address(&addr.into())
44                && !eth_addr.is_masked_id()
45            {
46                // Conversable into an eth address, use it.
47                return Ok(Some(eth_addr));
48            }
49        } else {
50            // No delegated address -> use a masked ID address
51        }
52    } else if let Ok(None) = result {
53        // Not found -> use a masked ID address
54    } else {
55        // Any other error -> fail.
56        result?;
57    }
58
59    // Otherwise, use the masked address.
60    Ok(Some(EthAddress::from_actor_id(id_addr)))
61}
62
63/// Extension trait for querying Ethereum-relevant state from a Filecoin actor.
64pub(crate) trait ActorStateEthExt {
65    /// Returns the effective nonce: EVM nonce for EVM actors, sequence otherwise.
66    fn eth_nonce<DB: Blockstore>(&self, store: &DB) -> anyhow::Result<EthUint64>;
67    /// Returns the deployed bytecode of an EVM actor, or `None` for non-EVM actors.
68    fn eth_bytecode<DB: Blockstore>(&self, store: &DB) -> anyhow::Result<Option<EthBytes>>;
69}
70
71impl ActorStateEthExt for ActorState {
72    fn eth_nonce<DB: Blockstore>(&self, store: &DB) -> anyhow::Result<EthUint64> {
73        if is_evm_actor(&self.code) {
74            let evm_state = evm::State::load(store, self.code, self.state)
75                .context("failed to load EVM state for nonce")?;
76            Ok(EthUint64::from(evm_state.nonce()))
77        } else {
78            Ok(EthUint64::from(self.sequence))
79        }
80    }
81
82    fn eth_bytecode<DB: Blockstore>(&self, store: &DB) -> anyhow::Result<Option<EthBytes>> {
83        if !is_evm_actor(&self.code) {
84            return Ok(None);
85        }
86        let evm_state = evm::State::load(store, self.code, self.state)
87            .context("failed to load EVM state for bytecode")?;
88        let bytecode = store
89            .get(&evm_state.bytecode())
90            .context("failed to read EVM bytecode")?;
91        Ok(bytecode.map(EthBytes))
92    }
93}
94
95/// Decodes the payload using the given codec.
96pub fn decode_payload(payload: &RawBytes, codec: u64) -> Result<EthBytes> {
97    match codec {
98        IDENTITY_HASH => Ok(EthBytes::default()),
99        DAG_CBOR | CBOR => {
100            let mut reader = cbor4ii::core::utils::SliceReader::new(payload.bytes());
101            match Value::decode(&mut reader) {
102                Ok(Value::Bytes(bytes)) => Ok(EthBytes(bytes)),
103                other => {
104                    tracing::debug!(
105                        "failed to decode params byte array: {other:?}, codec: {codec}, payload: {}",
106                        hex::encode(payload.bytes())
107                    );
108                    bail!("failed to decode params byte array");
109                }
110            }
111        }
112        IPLD_RAW => Ok(EthBytes(payload.to_vec())),
113        _ => bail!("decode_payload: unsupported codec {codec}"),
114    }
115}
116
117/// Decodes the message trace params using the message trace codec.
118pub fn decode_params<'a, T>(trace: &'a MessageTrace) -> anyhow::Result<T>
119where
120    T: de::Deserialize<'a>,
121{
122    let codec = trace.params_codec;
123    match codec {
124        DAG_CBOR | CBOR => fvm_ipld_encoding::from_slice(&trace.params)
125            .map_err(|e| anyhow::anyhow!("failed to decode params: {}", e)),
126        _ => bail!("Method called an unexpected codec {codec}"),
127    }
128}
129
130/// Decodes the return bytes using the return trace codec.
131pub fn decode_return<'a, T>(trace: &'a ReturnTrace) -> anyhow::Result<T>
132where
133    T: de::Deserialize<'a>,
134{
135    let codec = trace.return_codec;
136    match codec {
137        DAG_CBOR | CBOR => fvm_ipld_encoding::from_slice(trace.r#return.bytes())
138            .map_err(|e| anyhow::anyhow!("failed to decode return value: {}", e)),
139        _ => bail!("Method returned an unexpected codec {codec}"),
140    }
141}
142
143/// Extract and decode Ethereum revert reason from receipt return data
144pub fn decode_revert_reason(return_data: RawBytes) -> (Vec<u8>, String) {
145    let (data, reason) = match decode_payload(&return_data, CBOR) {
146        Err(e) => {
147            log::warn!("failed to unmarshal cbor bytes from message receipt return error: {e}");
148            (EthBytes::default(), String::default())
149        }
150        Ok(data) if !data.is_empty() => (data.clone(), parse_eth_revert(data.as_slice())),
151        Ok(data) => (data.clone(), "none".to_string()),
152    };
153
154    (data.0, reason)
155}
156
157const ERROR_FUNCTION_SELECTOR: [u8; 4] = [0x08, 0xc3, 0x79, 0xa0]; // keccak256("Error(string)") [first 4 bytes]
158const PANIC_FUNCTION_SELECTOR: [u8; 4] = [0x4e, 0x48, 0x7b, 0x71]; // keccak256("Panic(uint256)") [first 4 bytes]
159
160// Lazily initialized HashMap for panic codes
161static PANIC_ERROR_CODES: LazyLock<HashMap<u64, &'static str>> = LazyLock::new(|| {
162    let mut m = HashMap::new();
163    m.insert(0x00, "Panic()");
164    m.insert(0x01, "Assert()");
165    m.insert(0x11, "ArithmeticOverflow()");
166    m.insert(0x12, "DivideByZero()");
167    m.insert(0x21, "InvalidEnumVariant()");
168    m.insert(0x22, "InvalidStorageArray()");
169    m.insert(0x31, "PopEmptyArray()");
170    m.insert(0x32, "ArrayIndexOutOfBounds()");
171    m.insert(0x41, "OutOfMemory()");
172    m.insert(0x51, "CalledUninitializedFunction()");
173    m
174});
175
176/// EVM error and panic related constants
177const EVM_FUNC_SELECTOR_LENGTH: usize = 4;
178const EVM_PANIC_CODE_LENGTH: usize = 32;
179const EVM_UINT_PADDING_LENGTH: usize = 24;
180
181/// Parse an ABI encoded revert reason from a raw return value.
182///
183/// Handles both `Error(string)` and `Panic(uint256)` formats according to
184/// Solidity's revert conventions.
185///
186/// See https://docs.soliditylang.org/en/latest/control-structures.html#panic-via-assert-and-error-via-require
187pub(crate) fn parse_eth_revert(data: &[u8]) -> String {
188    // If it's not long enough to contain an ABI encoded response, return immediately.
189    if data.len() < EVM_FUNC_SELECTOR_LENGTH + EVM_WORD_LENGTH {
190        return format!("0x{}", hex::encode(data));
191    }
192
193    // Extract function selector (first 4 bytes)
194    let selector = data
195        .get(..EVM_FUNC_SELECTOR_LENGTH)
196        .expect("checked data length >= 4");
197
198    match selector {
199        selector if selector == PANIC_FUNCTION_SELECTOR.as_slice() => parse_panic_revert(data),
200        selector if selector == ERROR_FUNCTION_SELECTOR.as_slice() => parse_error_revert(data),
201        _ => format!("0x{}", hex::encode(data)),
202    }
203}
204
205fn parse_error_revert(data: &[u8]) -> String {
206    let fallback = || format!("0x{}", hex::encode(data));
207
208    let parse_result: Result<String, ()> = (|| {
209        let data = data
210            .get(EVM_FUNC_SELECTOR_LENGTH..)
211            .filter(|d| d.len() >= EVM_WORD_LENGTH)
212            .ok_or(())?;
213
214        // Get offset, from the first 32 bytes of the data
215        let offset_bytes = data.get(..EVM_WORD_LENGTH).ok_or(())?;
216        let offset = EthUint64::from_bytes(offset_bytes).map_err(|_| ())?.0 as usize;
217
218        // Validate offset range
219        if offset >= data.len() || data.len().saturating_sub(offset) < EVM_WORD_LENGTH {
220            return Err(());
221        }
222
223        // Get string length, from the offset + 32 bytes of the data
224        let length_bytes = data.get(offset..offset + EVM_WORD_LENGTH).ok_or(())?;
225        let len = EthUint64::from_bytes(length_bytes).map_err(|_| ())?.0 as usize;
226
227        // Validate string length
228        let string_start = offset + EVM_WORD_LENGTH;
229        if string_start > data.len() || len > data.len() - string_start {
230            return Err(());
231        }
232
233        // Attempt to decode valid UTF-8
234        let string = data.get(string_start..string_start + len).ok_or(())?;
235        Ok(format!(
236            "Error({})",
237            std::str::from_utf8(string).map_err(|_| ())?
238        ))
239    })();
240
241    parse_result.unwrap_or_else(|_| fallback())
242}
243
244fn parse_panic_revert(data: &[u8]) -> String {
245    let fallback = || format!("0x{}", hex::encode(data));
246
247    let parse_result: Result<String, ()> = (|| {
248        let code_bytes = data
249            .get(EVM_FUNC_SELECTOR_LENGTH..EVM_FUNC_SELECTOR_LENGTH + EVM_PANIC_CODE_LENGTH)
250            .ok_or(())?;
251
252        // Check if first 24 bytes are all zeros
253        if !code_bytes
254            .get(..EVM_UINT_PADDING_LENGTH)
255            .ok_or(())?
256            .iter()
257            .all(|&v| v == 0)
258        {
259            return Ok(format!("Panic(0x{})", hex::encode(code_bytes)));
260        }
261
262        let code_data = code_bytes.get(..EVM_WORD_LENGTH).ok_or(())?;
263        let code = EthUint64::from_bytes(code_data).map_err(|_| ())?.0;
264        Ok(PANIC_ERROR_CODES
265            .get(&code)
266            .map(|s| s.to_string())
267            .unwrap_or_else(|| format!("Panic(0x{code:x})")))
268    })();
269
270    parse_result.unwrap_or_else(|_| fallback())
271}
272
273#[cfg(test)]
274mod test {
275    use super::*;
276    use cbor4ii::core::{enc::Encode, utils::BufWriter};
277    use cbor4ii::serde::Serializer;
278
279    fn create_error_data(msg: &str) -> Vec<u8> {
280        let mut encoded = Vec::new();
281
282        // Step 1: Add function selector (keccak256("Error(string)") first 4 bytes)
283        encoded.extend_from_slice(&[0x08, 0xc3, 0x79, 0xa0]);
284
285        // Add offset to string data (32 bytes, value = 32)
286        // This points to where the string length is stored
287        let mut offset_bytes = [0u8; 32];
288        offset_bytes[24..32].copy_from_slice(&32u64.to_be_bytes());
289        encoded.extend_from_slice(&offset_bytes);
290
291        // Add string length (32 bytes)
292        let mut length_bytes = [0u8; 32];
293        length_bytes[24..32].copy_from_slice(&(msg.len() as u64).to_be_bytes());
294        encoded.extend_from_slice(&length_bytes);
295
296        // Add string data
297        encoded.extend_from_slice(msg.as_bytes());
298
299        // Pad to 32-byte boundary
300        let padding_needed = (32 - (msg.len() % 32)) % 32;
301        encoded.extend_from_slice(&vec![0; padding_needed]);
302
303        encoded
304    }
305
306    fn create_panic_data(code: u64) -> Vec<u8> {
307        let mut data = Vec::new();
308        data.extend_from_slice(&PANIC_FUNCTION_SELECTOR);
309
310        // Add padding (24 bytes) + code (32 bytes)
311        data.extend_from_slice(&[0; 24]);
312        data.extend_from_slice(&code.to_be_bytes());
313        data
314    }
315
316    #[test]
317    fn test_all_valid_parse_panic_revert() {
318        for (code, msg) in PANIC_ERROR_CODES.iter() {
319            let data = create_panic_data(*code);
320            assert_eq!(parse_panic_revert(&data), format!("{msg}"));
321        }
322    }
323
324    #[test]
325    fn test_all_valid_hex_parse_error_revert() {
326        let panic_data =
327            hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000000")
328                .unwrap();
329        assert_eq!(parse_panic_revert(&panic_data), "Panic()");
330
331        let assert_data =
332            hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000001")
333                .unwrap();
334        assert_eq!(parse_panic_revert(&assert_data), "Assert()");
335
336        let arithmetic_overflow_data =
337            hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000011")
338                .unwrap();
339        assert_eq!(
340            parse_panic_revert(&arithmetic_overflow_data),
341            "ArithmeticOverflow()"
342        );
343
344        let divide_by_zero_data =
345            hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000012")
346                .unwrap();
347        assert_eq!(parse_panic_revert(&divide_by_zero_data), "DivideByZero()");
348
349        let invalid_enum_variant_data =
350            hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000021")
351                .unwrap();
352        assert_eq!(
353            parse_panic_revert(&invalid_enum_variant_data),
354            "InvalidEnumVariant()"
355        );
356
357        let invalid_storage_array_data =
358            hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000022")
359                .unwrap();
360        assert_eq!(
361            parse_panic_revert(&invalid_storage_array_data),
362            "InvalidStorageArray()"
363        );
364
365        let pop_empty_array_data =
366            hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000031")
367                .unwrap();
368        assert_eq!(parse_panic_revert(&pop_empty_array_data), "PopEmptyArray()");
369
370        let array_index_out_of_bounds_data =
371            hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000032")
372                .unwrap();
373        assert_eq!(
374            parse_panic_revert(&array_index_out_of_bounds_data),
375            "ArrayIndexOutOfBounds()"
376        );
377
378        let out_of_memory_data =
379            hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000041")
380                .unwrap();
381        assert_eq!(parse_panic_revert(&out_of_memory_data), "OutOfMemory()");
382
383        let call_uninitialized_data =
384            hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000051")
385                .unwrap();
386        assert_eq!(
387            parse_panic_revert(&call_uninitialized_data),
388            "CalledUninitializedFunction()"
389        );
390    }
391
392    #[test]
393    fn test_parse_error_revert() {
394        let err_msg = "Not enough Ether provided";
395        let error_data = create_error_data(err_msg);
396        assert_eq!(parse_error_revert(&error_data), format!("Error({err_msg})"));
397
398        // ABI-encoded Error("Hello World")
399        let err_data = hex::decode(
400            "\
401            08c379a0\
402            0000000000000000000000000000000000000000000000000000000000000020\
403            000000000000000000000000000000000000000000000000000000000000000b\
404            48656c6c6f20576f726c64000000000000000000000000000000000000000000\
405            ",
406        )
407        .unwrap();
408        assert_eq!(parse_error_revert(&err_data), "Error(Hello World)");
409
410        // ERC20 insufficient balance
411        let insufficient = hex::decode(
412            "08c379a0\
413                0000000000000000000000000000000000000000000000000000000000000020\
414                0000000000000000000000000000000000000000000000000000000000000026\
415                45524332303a207472616e7366657220616d6f756e7420657863656564732062\
416                616c616e63650000000000000000000000000000000000000000000000000000",
417        )
418        .unwrap();
419        assert_eq!(
420            parse_eth_revert(&insufficient),
421            "Error(ERC20: transfer amount exceeds balance)"
422        );
423    }
424
425    #[test]
426    fn test_parse_eth_revert_main_function() {
427        // Test normal Error case
428        let message = "Transaction failed";
429        let data = create_error_data(message);
430        assert_eq!(parse_eth_revert(&data), format!("Error({message})"));
431
432        // Test normal Panic case
433        let panic_data = create_panic_data(0x01); // Assert()
434        assert_eq!(parse_eth_revert(&panic_data), "Assert()");
435
436        // Test data too short for any revert reason
437        let short_data = vec![0x1, 0x2, 0x3];
438        assert_eq!(
439            parse_eth_revert(&short_data),
440            format!("0x{}", hex::encode(&short_data))
441        );
442
443        // Test unknown function selector
444        let mut unknown_selector = vec![0; EVM_FUNC_SELECTOR_LENGTH + EVM_WORD_LENGTH];
445        unknown_selector[0] = 0xAA;
446        unknown_selector[1] = 0xBB;
447        unknown_selector[2] = 0xCC;
448        unknown_selector[3] = 0xDD;
449        assert_eq!(
450            parse_eth_revert(&unknown_selector),
451            format!("0x{}", hex::encode(&unknown_selector))
452        );
453    }
454
455    #[test]
456    fn test_parse_error_revert_special_cases() {
457        // Test with empty error message
458        let data = create_error_data("");
459        assert_eq!(parse_error_revert(&data), "Error()");
460
461        // Test with special characters
462        let special = "Error message with special chars: !@#$%6^&*()_+{}|:<>!?";
463        let data = create_error_data(special);
464        assert_eq!(parse_error_revert(&data), format!("Error({special})"));
465
466        // Test with Unicode characters
467        let unicode = "Error with Unicode: 你好世界";
468        let data = create_error_data(unicode);
469        assert_eq!(parse_error_revert(&data), format!("Error({unicode})"));
470
471        // Test with invalid offset (points outside data)
472        let mut invalid_offset = create_error_data("Test");
473        // Modify offset to point outside available data
474        invalid_offset
475            .iter_mut()
476            .skip(24)
477            .take(8)
478            .for_each(|byte| *byte = 0xFF);
479        assert_eq!(
480            parse_error_revert(&invalid_offset),
481            format!("0x{}", hex::encode(&invalid_offset))
482        );
483
484        // Test with invalid length (exceeds available data)
485        let mut invalid_length = create_error_data("Test");
486        // Set offset to valid 32, but make length too large
487        invalid_length
488            .iter_mut()
489            .skip(32 + 24)
490            .take(8)
491            .for_each(|byte| *byte = 0xFF);
492        assert_eq!(
493            parse_error_revert(&invalid_length),
494            format!("0x{}", hex::encode(&invalid_length))
495        );
496
497        // Test with truncated data (not enough for string data)
498        let truncated = create_error_data("Test");
499        let truncated = &truncated[0..70]; // Cut off after length field
500        assert_eq!(
501            parse_error_revert(truncated),
502            format!("0x{}", hex::encode(truncated))
503        );
504
505        // Test with invalid UTF-8 in the string
506        let mut invalid_utf8 = create_error_data("Test string");
507        // Insert invalid UTF-8 sequence
508        let string_start = 32 + 32;
509        invalid_utf8[string_start + 2] = 0xFF;
510        assert_eq!(
511            parse_error_revert(&invalid_utf8),
512            format!("0x{}", hex::encode(&invalid_utf8))
513        );
514    }
515
516    #[test]
517    fn test_eth_revert_boundary_conditions() {
518        // Test with exactly minimum size data
519        let min_size = vec![0; EVM_FUNC_SELECTOR_LENGTH + EVM_WORD_LENGTH];
520        assert_eq!(
521            parse_eth_revert(&min_size),
522            format!("0x{}", hex::encode(&min_size))
523        );
524
525        // Test with exactly one byte less than minimum
526        let too_small = vec![0; EVM_FUNC_SELECTOR_LENGTH + EVM_WORD_LENGTH - 1];
527        assert_eq!(
528            parse_eth_revert(&too_small),
529            format!("0x{}", hex::encode(&too_small))
530        );
531    }
532
533    #[test]
534    fn test_decode_payload() {
535        // empty
536        let result = decode_payload(&RawBytes::default(), 0);
537        assert!(result.unwrap().0.is_empty());
538
539        // raw empty
540        let result = decode_payload(&RawBytes::default(), IPLD_RAW);
541        assert!(result.unwrap().0.is_empty());
542
543        // raw non-empty
544        let result = decode_payload(&RawBytes::new(vec![1]), IPLD_RAW);
545        assert_eq!(result.unwrap(), EthBytes(vec![1]));
546
547        // invalid cbor bytes
548        let result = decode_payload(&RawBytes::default(), DAG_CBOR);
549        assert!(result.is_err());
550
551        // valid cbor bytes
552        let mut writer = BufWriter::new(Vec::new());
553        Value::Bytes(vec![1]).encode(&mut writer).unwrap();
554        let serializer = Serializer::new(writer);
555        let encoded = serializer.into_inner().into_inner();
556
557        let result = decode_payload(&RawBytes::new(encoded.clone()), DAG_CBOR);
558        assert_eq!(result.unwrap(), EthBytes(vec![1]));
559
560        // regular cbor also works
561        let result = decode_payload(&RawBytes::new(encoded), CBOR);
562        assert_eq!(result.unwrap(), EthBytes(vec![1]));
563
564        // random codec should fail
565        let result = decode_payload(&RawBytes::default(), 42);
566        assert!(result.is_err());
567
568        // some payload taken from calibnet
569        assert_eq!(
570            decode_payload(
571                &RawBytes::new(
572                    hex::decode(
573                        "58200000000000000000000000000000000000000000000000000000000000002710"
574                    )
575                    .unwrap(),
576                ),
577                CBOR
578            )
579            .unwrap(),
580            EthBytes(vec![
581                0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
582                0, 0, 39, 16,
583            ])
584        );
585
586        // identity
587        let result = decode_payload(&RawBytes::new(vec![1]), IDENTITY_HASH);
588        assert!(result.unwrap().0.is_empty());
589    }
590}