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