1use 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 crate::shim::actors::evm::U256;
16use anyhow::{Result, bail};
17use cbor4ii::core::Value;
18use cbor4ii::core::dec::Decode as _;
19use fvm_ipld_encoding::{CBOR, DAG_CBOR, IPLD_RAW, RawBytes};
20use fvm_ipld_kamt::{AsHashedKey, Config as KamtConfig, HashedKey, Kamt};
21use serde::de;
22use std::borrow::Cow;
23use std::sync::LazyLock;
24
25pub(crate) fn evm_kamt_config() -> KamtConfig {
28 KamtConfig {
29 bit_width: 5,
30 min_data_depth: 0,
31 max_array_width: 1,
32 }
33}
34
35pub(crate) struct EvmStateHashAlgorithm;
38
39impl AsHashedKey<U256, 32> for EvmStateHashAlgorithm {
40 fn as_hashed_key(key: &U256) -> Cow<'_, HashedKey<32>> {
41 Cow::Owned(key.to_big_endian())
42 }
43}
44
45pub(crate) type EvmStorageKamt<BS> = Kamt<BS, U256, U256, EvmStateHashAlgorithm>;
46
47pub fn lookup_eth_address<DB: Blockstore>(
48 addr: &FilecoinAddress,
49 state: &StateTree<DB>,
50) -> Result<Option<EthAddress>> {
51 if let Ok(eth_addr) = EthAddress::from_filecoin_address(addr)
53 && !eth_addr.is_masked_id()
54 {
55 return Ok(Some(eth_addr));
56 }
57
58 let id_addr = match state.lookup_id(addr)? {
60 Some(id) => id,
61 _ => return Ok(None),
62 };
63
64 let result = state.get_actor(addr);
66 if let Ok(Some(actor_state)) = result {
67 if let Some(addr) = actor_state.delegated_address {
68 if let Ok(eth_addr) = EthAddress::from_filecoin_address(&addr.into())
69 && !eth_addr.is_masked_id()
70 {
71 return Ok(Some(eth_addr));
73 }
74 } else {
75 }
77 } else if let Ok(None) = result {
78 } else {
80 result?;
82 }
83
84 Ok(Some(EthAddress::from_actor_id(id_addr)))
86}
87
88fn evm_state<DB: Blockstore>(store: &DB, actor: &ActorState) -> anyhow::Result<Option<evm::State>> {
90 if !is_evm_actor(&actor.code) {
91 return Ok(None);
92 }
93 Ok(Some(
94 evm::State::load(store, actor.code, actor.state).context("failed to load EVM state")?,
95 ))
96}
97
98pub(crate) fn live_evm_state<DB: Blockstore>(
101 store: &DB,
102 actor: &ActorState,
103) -> anyhow::Result<Option<evm::State>> {
104 Ok(evm_state(store, actor)?.filter(|state| state.is_alive()))
105}
106
107pub(crate) trait ActorStateEthExt {
109 fn eth_nonce<DB: Blockstore>(&self, store: &DB) -> anyhow::Result<EthUint64>;
112 fn eth_bytecode<DB: Blockstore>(&self, store: &DB) -> anyhow::Result<Option<EthBytes>>;
114 fn eth_storage_at<DB: Blockstore>(
117 &self,
118 store: &DB,
119 position: &[u8; EVM_WORD_LENGTH],
120 ) -> anyhow::Result<[u8; EVM_WORD_LENGTH]>;
121}
122
123impl ActorStateEthExt for ActorState {
124 fn eth_nonce<DB: Blockstore>(&self, store: &DB) -> anyhow::Result<EthUint64> {
125 Ok(EthUint64(match evm_state(store, self)? {
126 Some(state) if state.is_alive() => state.nonce(),
127 Some(_) => 0,
129 None => self.sequence,
130 }))
131 }
132
133 fn eth_bytecode<DB: Blockstore>(&self, store: &DB) -> anyhow::Result<Option<EthBytes>> {
134 let Some(evm_state) = live_evm_state(store, self)? else {
135 return Ok(None);
136 };
137 let bytecode = store
138 .get(&evm_state.bytecode())
139 .context("failed to read EVM bytecode")?;
140 Ok(bytecode.map(EthBytes))
141 }
142
143 fn eth_storage_at<DB: Blockstore>(
144 &self,
145 store: &DB,
146 position: &[u8; EVM_WORD_LENGTH],
147 ) -> anyhow::Result<[u8; EVM_WORD_LENGTH]> {
148 let Some(evm_state) = live_evm_state(store, self)? else {
151 return Ok([0; EVM_WORD_LENGTH]);
152 };
153 let kamt =
154 EvmStorageKamt::load_with_config(&evm_state.contract_state(), store, evm_kamt_config())
155 .context("failed to load EVM storage KAMT")?;
156 let value = kamt
157 .get(&U256::from_big_endian(position))
158 .context("failed to read EVM storage slot")?
159 .copied()
160 .unwrap_or_default();
161 Ok(value.to_big_endian())
162 }
163}
164
165pub fn decode_payload(payload: &RawBytes, codec: u64) -> Result<EthBytes> {
167 match codec {
168 IDENTITY_HASH => Ok(EthBytes::default()),
169 DAG_CBOR | CBOR => {
170 let mut reader = cbor4ii::core::utils::SliceReader::new(payload.bytes());
171 match Value::decode(&mut reader) {
172 Ok(Value::Bytes(bytes)) => Ok(EthBytes(bytes)),
173 other => {
174 tracing::debug!(
175 "failed to decode params byte array: {other:?}, codec: {codec}, payload: {}",
176 hex::encode(payload.bytes())
177 );
178 bail!("failed to decode params byte array");
179 }
180 }
181 }
182 IPLD_RAW => Ok(EthBytes(payload.to_vec())),
183 _ => bail!("decode_payload: unsupported codec {codec}"),
184 }
185}
186
187pub fn decode_params<'a, T>(trace: &'a MessageTrace) -> anyhow::Result<T>
189where
190 T: de::Deserialize<'a>,
191{
192 let codec = trace.params_codec;
193 match codec {
194 DAG_CBOR | CBOR => fvm_ipld_encoding::from_slice(&trace.params)
195 .map_err(|e| anyhow::anyhow!("failed to decode params: {e}")),
196 _ => bail!("Method called an unexpected codec {codec}"),
197 }
198}
199
200pub fn decode_return<'a, T>(trace: &'a ReturnTrace) -> anyhow::Result<T>
202where
203 T: de::Deserialize<'a>,
204{
205 let codec = trace.return_codec;
206 match codec {
207 DAG_CBOR | CBOR => fvm_ipld_encoding::from_slice(trace.r#return.bytes())
208 .map_err(|e| anyhow::anyhow!("failed to decode return value: {e}")),
209 _ => bail!("Method returned an unexpected codec {codec}"),
210 }
211}
212
213pub fn decode_revert_reason(return_data: RawBytes) -> (Vec<u8>, String) {
215 match decode_payload(&return_data, CBOR) {
216 Err(_) => (Vec::new(), String::new()),
217 Ok(data) if !data.is_empty() => {
218 let reason = parse_eth_revert(data.as_slice());
219 (data.0, reason)
220 }
221 Ok(data) => (data.0, "none".to_string()),
222 }
223}
224
225const ERROR_FUNCTION_SELECTOR: [u8; 4] = [0x08, 0xc3, 0x79, 0xa0]; const PANIC_FUNCTION_SELECTOR: [u8; 4] = [0x4e, 0x48, 0x7b, 0x71]; static PANIC_ERROR_CODES: LazyLock<HashMap<u64, &'static str>> = LazyLock::new(|| {
230 let mut m = HashMap::new();
231 m.insert(0x00, "Panic()");
232 m.insert(0x01, "Assert()");
233 m.insert(0x11, "ArithmeticOverflow()");
234 m.insert(0x12, "DivideByZero()");
235 m.insert(0x21, "InvalidEnumVariant()");
236 m.insert(0x22, "InvalidStorageArray()");
237 m.insert(0x31, "PopEmptyArray()");
238 m.insert(0x32, "ArrayIndexOutOfBounds()");
239 m.insert(0x41, "OutOfMemory()");
240 m.insert(0x51, "CalledUninitializedFunction()");
241 m
242});
243
244const EVM_FUNC_SELECTOR_LENGTH: usize = 4;
246const EVM_PANIC_CODE_LENGTH: usize = 32;
247const EVM_UINT_PADDING_LENGTH: usize = 24;
248
249pub(crate) fn parse_eth_revert(data: &[u8]) -> String {
256 if data.len() < EVM_FUNC_SELECTOR_LENGTH + EVM_WORD_LENGTH {
258 return hex::encode_prefixed(data);
259 }
260
261 let selector = data
263 .get(..EVM_FUNC_SELECTOR_LENGTH)
264 .expect("checked data length >= 4");
265
266 match selector {
267 selector if selector == PANIC_FUNCTION_SELECTOR.as_slice() => parse_panic_revert(data),
268 selector if selector == ERROR_FUNCTION_SELECTOR.as_slice() => parse_error_revert(data),
269 _ => hex::encode_prefixed(data),
270 }
271}
272
273fn parse_error_revert(data: &[u8]) -> String {
274 let fallback = || hex::encode_prefixed(data);
275
276 let parse_result: Result<String, ()> = (|| {
277 let data = data
278 .get(EVM_FUNC_SELECTOR_LENGTH..)
279 .filter(|d| d.len() >= EVM_WORD_LENGTH)
280 .ok_or(())?;
281
282 let offset_bytes = data.get(..EVM_WORD_LENGTH).ok_or(())?;
284 let offset = EthUint64::from_bytes(offset_bytes).map_err(|_| ())?.0 as usize;
285
286 if offset >= data.len() || data.len().saturating_sub(offset) < EVM_WORD_LENGTH {
288 return Err(());
289 }
290
291 let length_bytes = data.get(offset..offset + EVM_WORD_LENGTH).ok_or(())?;
293 let len = EthUint64::from_bytes(length_bytes).map_err(|_| ())?.0 as usize;
294
295 let string_start = offset + EVM_WORD_LENGTH;
297 if string_start > data.len() || len > data.len() - string_start {
298 return Err(());
299 }
300
301 let string = data.get(string_start..string_start + len).ok_or(())?;
303 Ok(format!(
304 "Error({})",
305 std::str::from_utf8(string).map_err(|_| ())?
306 ))
307 })();
308
309 parse_result.unwrap_or_else(|_| fallback())
310}
311
312fn parse_panic_revert(data: &[u8]) -> String {
313 let fallback = || hex::encode_prefixed(data);
314
315 let parse_result: Result<String, ()> = (|| {
316 let code_bytes = data
317 .get(EVM_FUNC_SELECTOR_LENGTH..EVM_FUNC_SELECTOR_LENGTH + EVM_PANIC_CODE_LENGTH)
318 .ok_or(())?;
319
320 if !code_bytes
322 .get(..EVM_UINT_PADDING_LENGTH)
323 .ok_or(())?
324 .iter()
325 .all(|&v| v == 0)
326 {
327 return Ok(format!("Panic(0x{})", hex::encode(code_bytes)));
328 }
329
330 let code_data = code_bytes.get(..EVM_WORD_LENGTH).ok_or(())?;
331 let code = EthUint64::from_bytes(code_data).map_err(|_| ())?.0;
332 Ok(PANIC_ERROR_CODES
333 .get(&code)
334 .map(|s| s.to_string())
335 .unwrap_or_else(|| format!("Panic(0x{code:x})")))
336 })();
337
338 parse_result.unwrap_or_else(|_| fallback())
339}
340
341#[cfg(test)]
342mod test {
343 use super::*;
344 use rstest::rstest;
345
346 #[test]
347 fn eth_storage_at_matches_evm_storage() {
348 use crate::db::MemoryDB;
349 use crate::networks::ACTOR_BUNDLES_METADATA;
350 use crate::shim::econ::TokenAmount;
351 use crate::shim::machine::BuiltinActor;
352 use crate::utils::db::CborStoreExt as _;
353
354 fn word(n: u8) -> [u8; EVM_WORD_LENGTH] {
355 let mut w = [0; EVM_WORD_LENGTH];
356 w[EVM_WORD_LENGTH - 1] = n;
357 w
358 }
359 let store = MemoryDB::default();
360 let zero = word(0);
361 let evm_code_cid = ACTOR_BUNDLES_METADATA
363 .values()
364 .filter_map(|bundle| {
365 Some((
366 bundle.actor_major_version().ok()?,
367 bundle.manifest.get(BuiltinActor::EVM).ok()?,
368 ))
369 })
370 .max_by_key(|&(version, _)| version)
371 .expect("bundled EVM actor code CID")
372 .1;
373 let evm_actor = |state: &evm::State| {
374 let state_cid = store.put_cbor_default(state).unwrap();
375 ActorState::new(evm_code_cid, state_cid, TokenAmount::default(), 0, None)
376 };
377
378 let non_evm = ActorState::new(
380 Cid::default(),
381 Cid::default(),
382 TokenAmount::default(),
383 0,
384 None,
385 );
386 assert_eq!(non_evm.eth_storage_at(&store, &zero).unwrap(), zero);
387
388 let mut slots = EvmStorageKamt::new_with_config(&store, evm_kamt_config());
390 slots.set(U256::from(5u64), U256::from(42u64)).unwrap();
391 let contract_state = slots.flush().unwrap();
392 let with_tombstone = |tombstone| {
393 evm_actor(&evm::State::default_latest_version(
394 Cid::default(),
395 [0; 32],
396 contract_state,
397 None,
398 0,
399 tombstone,
400 ))
401 };
402 let alive = with_tombstone(None);
403 assert_eq!(alive.eth_storage_at(&store, &word(5)).unwrap(), word(42));
404 assert_eq!(alive.eth_storage_at(&store, &word(6)).unwrap(), zero);
405
406 let dead = with_tombstone(Some(evm::Tombstone {
408 origin: 100,
409 nonce: 1,
410 }));
411 assert_eq!(dead.eth_storage_at(&store, &word(5)).unwrap(), zero);
412 }
413
414 fn create_error_data(msg: &str) -> Vec<u8> {
415 let mut encoded = Vec::new();
416
417 encoded.extend_from_slice(&[0x08, 0xc3, 0x79, 0xa0]);
419
420 let mut offset_bytes = [0u8; 32];
423 offset_bytes[24..32].copy_from_slice(&32u64.to_be_bytes());
424 encoded.extend_from_slice(&offset_bytes);
425
426 let mut length_bytes = [0u8; 32];
428 length_bytes[24..32].copy_from_slice(&(msg.len() as u64).to_be_bytes());
429 encoded.extend_from_slice(&length_bytes);
430
431 encoded.extend_from_slice(msg.as_bytes());
433
434 let padding_needed = (32 - (msg.len() % 32)) % 32;
436 encoded.extend_from_slice(&vec![0; padding_needed]);
437
438 encoded
439 }
440
441 fn create_panic_data(code: u64) -> Vec<u8> {
442 let mut data = Vec::new();
443 data.extend_from_slice(&PANIC_FUNCTION_SELECTOR);
444
445 data.extend_from_slice(&[0; 24]);
447 data.extend_from_slice(&code.to_be_bytes());
448 data
449 }
450
451 #[test]
452 fn test_all_valid_parse_panic_revert() {
453 for (code, msg) in PANIC_ERROR_CODES.iter() {
454 let data = create_panic_data(*code);
455 assert_eq!(parse_panic_revert(&data), msg.to_string());
456 }
457 }
458
459 #[test]
460 fn test_all_valid_hex_parse_error_revert() {
461 let panic_data =
462 hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000000")
463 .unwrap();
464 assert_eq!(parse_panic_revert(&panic_data), "Panic()");
465
466 let assert_data =
467 hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000001")
468 .unwrap();
469 assert_eq!(parse_panic_revert(&assert_data), "Assert()");
470
471 let arithmetic_overflow_data =
472 hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000011")
473 .unwrap();
474 assert_eq!(
475 parse_panic_revert(&arithmetic_overflow_data),
476 "ArithmeticOverflow()"
477 );
478
479 let divide_by_zero_data =
480 hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000012")
481 .unwrap();
482 assert_eq!(parse_panic_revert(÷_by_zero_data), "DivideByZero()");
483
484 let invalid_enum_variant_data =
485 hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000021")
486 .unwrap();
487 assert_eq!(
488 parse_panic_revert(&invalid_enum_variant_data),
489 "InvalidEnumVariant()"
490 );
491
492 let invalid_storage_array_data =
493 hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000022")
494 .unwrap();
495 assert_eq!(
496 parse_panic_revert(&invalid_storage_array_data),
497 "InvalidStorageArray()"
498 );
499
500 let pop_empty_array_data =
501 hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000031")
502 .unwrap();
503 assert_eq!(parse_panic_revert(&pop_empty_array_data), "PopEmptyArray()");
504
505 let array_index_out_of_bounds_data =
506 hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000032")
507 .unwrap();
508 assert_eq!(
509 parse_panic_revert(&array_index_out_of_bounds_data),
510 "ArrayIndexOutOfBounds()"
511 );
512
513 let out_of_memory_data =
514 hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000041")
515 .unwrap();
516 assert_eq!(parse_panic_revert(&out_of_memory_data), "OutOfMemory()");
517
518 let call_uninitialized_data =
519 hex::decode("4e487b710000000000000000000000000000000000000000000000000000000000000051")
520 .unwrap();
521 assert_eq!(
522 parse_panic_revert(&call_uninitialized_data),
523 "CalledUninitializedFunction()"
524 );
525 }
526
527 #[test]
528 fn test_parse_error_revert() {
529 let err_msg = "Not enough Ether provided";
530 let error_data = create_error_data(err_msg);
531 assert_eq!(parse_error_revert(&error_data), format!("Error({err_msg})"));
532
533 let err_data = hex::decode(
535 "\
536 08c379a0\
537 0000000000000000000000000000000000000000000000000000000000000020\
538 000000000000000000000000000000000000000000000000000000000000000b\
539 48656c6c6f20576f726c64000000000000000000000000000000000000000000\
540 ",
541 )
542 .unwrap();
543 assert_eq!(parse_error_revert(&err_data), "Error(Hello World)");
544
545 let insufficient = hex::decode(
547 "08c379a0\
548 0000000000000000000000000000000000000000000000000000000000000020\
549 0000000000000000000000000000000000000000000000000000000000000026\
550 45524332303a207472616e7366657220616d6f756e7420657863656564732062\
551 616c616e63650000000000000000000000000000000000000000000000000000",
552 )
553 .unwrap();
554 assert_eq!(
555 parse_eth_revert(&insufficient),
556 "Error(ERC20: transfer amount exceeds balance)"
557 );
558 }
559
560 #[test]
561 fn test_parse_eth_revert_main_function() {
562 let message = "Transaction failed";
564 let data = create_error_data(message);
565 assert_eq!(parse_eth_revert(&data), format!("Error({message})"));
566
567 let panic_data = create_panic_data(0x01); assert_eq!(parse_eth_revert(&panic_data), "Assert()");
570
571 let short_data = vec![0x1, 0x2, 0x3];
573 assert_eq!(
574 parse_eth_revert(&short_data),
575 format!("0x{}", hex::encode(&short_data))
576 );
577
578 let mut unknown_selector = vec![0; EVM_FUNC_SELECTOR_LENGTH + EVM_WORD_LENGTH];
580 unknown_selector[0] = 0xAA;
581 unknown_selector[1] = 0xBB;
582 unknown_selector[2] = 0xCC;
583 unknown_selector[3] = 0xDD;
584 assert_eq!(
585 parse_eth_revert(&unknown_selector),
586 format!("0x{}", hex::encode(&unknown_selector))
587 );
588 }
589
590 #[test]
591 fn test_parse_error_revert_special_cases() {
592 let data = create_error_data("");
594 assert_eq!(parse_error_revert(&data), "Error()");
595
596 let special = "Error message with special chars: !@#$%6^&*()_+{}|:<>!?";
598 let data = create_error_data(special);
599 assert_eq!(parse_error_revert(&data), format!("Error({special})"));
600
601 let unicode = "Error with Unicode: 你好世界";
603 let data = create_error_data(unicode);
604 assert_eq!(parse_error_revert(&data), format!("Error({unicode})"));
605
606 let mut invalid_offset = create_error_data("Test");
608 invalid_offset
610 .iter_mut()
611 .skip(24)
612 .take(8)
613 .for_each(|byte| *byte = 0xFF);
614 assert_eq!(
615 parse_error_revert(&invalid_offset),
616 format!("0x{}", hex::encode(&invalid_offset))
617 );
618
619 let mut invalid_length = create_error_data("Test");
621 invalid_length
623 .iter_mut()
624 .skip(32 + 24)
625 .take(8)
626 .for_each(|byte| *byte = 0xFF);
627 assert_eq!(
628 parse_error_revert(&invalid_length),
629 format!("0x{}", hex::encode(&invalid_length))
630 );
631
632 let truncated = create_error_data("Test");
634 let truncated = &truncated[0..70]; assert_eq!(
636 parse_error_revert(truncated),
637 format!("0x{}", hex::encode(truncated))
638 );
639
640 let mut invalid_utf8 = create_error_data("Test string");
642 let string_start = 32 + 32;
644 invalid_utf8[string_start + 2] = 0xFF;
645 assert_eq!(
646 parse_error_revert(&invalid_utf8),
647 format!("0x{}", hex::encode(&invalid_utf8))
648 );
649 }
650
651 #[test]
652 fn test_eth_revert_boundary_conditions() {
653 let min_size = vec![0; EVM_FUNC_SELECTOR_LENGTH + EVM_WORD_LENGTH];
655 assert_eq!(
656 parse_eth_revert(&min_size),
657 format!("0x{}", hex::encode(&min_size))
658 );
659
660 let too_small = vec![0; EVM_FUNC_SELECTOR_LENGTH + EVM_WORD_LENGTH - 1];
662 assert_eq!(
663 parse_eth_revert(&too_small),
664 format!("0x{}", hex::encode(&too_small))
665 );
666 }
667
668 #[test]
669 fn test_decode_payload() {
670 let result = decode_payload(&RawBytes::default(), 0);
672 assert!(result.unwrap().0.is_empty());
673
674 let result = decode_payload(&RawBytes::default(), IPLD_RAW);
676 assert!(result.unwrap().0.is_empty());
677
678 let result = decode_payload(&RawBytes::new(vec![1]), IPLD_RAW);
680 assert_eq!(result.unwrap(), EthBytes(vec![1]));
681
682 let result = decode_payload(&RawBytes::default(), DAG_CBOR);
684 assert!(result.is_err());
685
686 let encoded = cbor_bytes(vec![1]);
688
689 let result = decode_payload(&encoded, DAG_CBOR);
690 assert_eq!(result.unwrap(), EthBytes(vec![1]));
691
692 let result = decode_payload(&encoded, CBOR);
694 assert_eq!(result.unwrap(), EthBytes(vec![1]));
695
696 let result = decode_payload(&RawBytes::default(), 42);
698 assert!(result.is_err());
699
700 assert_eq!(
702 decode_payload(
703 &RawBytes::new(
704 hex::decode(
705 "58200000000000000000000000000000000000000000000000000000000000002710"
706 )
707 .unwrap(),
708 ),
709 CBOR
710 )
711 .unwrap(),
712 EthBytes(vec![
713 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,
714 0, 0, 39, 16,
715 ])
716 );
717
718 let result = decode_payload(&RawBytes::new(vec![1]), IDENTITY_HASH);
720 assert!(result.unwrap().0.is_empty());
721 }
722
723 fn cbor_bytes(inner: Vec<u8>) -> RawBytes {
724 RawBytes::new(cbor4ii::serde::to_vec(Vec::new(), &Value::Bytes(inner)).unwrap())
725 }
726
727 #[rstest]
729 #[case(RawBytes::new(vec![0x18]), vec![], "")]
730 #[case(cbor_bytes(vec![]), vec![], "none")]
731 #[case(
732 cbor_bytes(create_error_data("boom")),
733 create_error_data("boom"),
734 "Error(boom)"
735 )]
736 #[case(
737 cbor_bytes(create_panic_data(0x01)),
738 create_panic_data(0x01),
739 "Assert()"
740 )]
741 #[case(cbor_bytes(vec![0xde, 0xad, 0xbe, 0xef]), vec![0xde, 0xad, 0xbe, 0xef], "0xdeadbeef")]
743 fn test_decode_revert_reason(
744 #[case] input: RawBytes,
745 #[case] expected_data: Vec<u8>,
746 #[case] expected_reason: &str,
747 ) {
748 let (data, reason) = decode_revert_reason(input);
749 assert_eq!(data, expected_data);
750 assert_eq!(reason, expected_reason);
751 }
752}