1use super::types::{
5 EthAddress, EthBytes, EthCallTraceAction, EthHash, EthTrace, TraceAction, TraceResult,
6};
7use super::utils::{decode_params, decode_return};
8use super::{
9 EthCallTraceResult, EthCreateTraceAction, EthCreateTraceResult, decode_payload,
10 encode_filecoin_params_as_abi, encode_filecoin_returns_as_abi,
11};
12use crate::eth::{EAMMethod, EVMMethod};
13use crate::rpc::eth::types::{AccountDiff, Delta, StateDiff};
14use crate::rpc::eth::{EthBigInt, EthUint64};
15use crate::rpc::methods::eth::lookup_eth_address;
16use crate::rpc::methods::state::ExecutionTrace;
17use crate::rpc::state::ActorTrace;
18use crate::shim::actors::{EVMActorStateLoad, evm};
19use crate::shim::fvm_shared_latest::METHOD_CONSTRUCTOR;
20use crate::shim::state_tree::ActorState;
21use crate::shim::{actors::is_evm_actor, address::Address, error::ExitCode, state_tree::StateTree};
22use ahash::{HashMap, HashSet};
23use anyhow::{Context, bail};
24use fil_actor_eam_state::v12 as eam12;
25use fil_actor_evm_state::evm_shared::v17::uints::U256;
26use fil_actor_evm_state::v15 as evm12;
27use fil_actor_init_state::v12::ExecReturn;
28use fil_actor_init_state::v15::Method as InitMethod;
29use fvm_ipld_blockstore::Blockstore;
30use fvm_ipld_kamt::{AsHashedKey, Config as KamtConfig, HashedKey, Kamt};
31use num::FromPrimitive;
32use std::borrow::Cow;
33use std::collections::BTreeMap;
34use tracing::debug;
35
36fn evm_kamt_config() -> KamtConfig {
39 KamtConfig {
40 bit_width: 5, min_data_depth: 0, max_array_width: 1, }
44}
45
46pub struct EvmStateHashAlgorithm;
49
50impl AsHashedKey<U256, 32> for EvmStateHashAlgorithm {
51 fn as_hashed_key(key: &U256) -> Cow<'_, HashedKey<32>> {
52 Cow::Owned(key.to_big_endian())
53 }
54}
55
56type EvmStorageKamt<BS> = Kamt<BS, U256, U256, EvmStateHashAlgorithm>;
58
59fn u256_to_eth_hash(value: &U256) -> EthHash {
60 EthHash(ethereum_types::H256(value.to_big_endian()))
61}
62
63const ZERO_HASH: EthHash = EthHash(ethereum_types::H256([0u8; 32]));
64
65#[derive(Default)]
66pub struct Environment {
67 caller: EthAddress,
68 is_evm: bool,
69 subtrace_count: i64,
70 pub traces: Vec<EthTrace>,
71 last_byte_code: Option<EthAddress>,
72}
73
74pub fn base_environment<BS: Blockstore + Send + Sync>(
75 state: &StateTree<BS>,
76 from: &Address,
77) -> anyhow::Result<Environment> {
78 let sender = lookup_eth_address(from, state)?
79 .with_context(|| format!("top-level message sender {from} s could not be found"))?;
80 Ok(Environment {
81 caller: sender,
82 ..Environment::default()
83 })
84}
85
86fn trace_to_address(trace: &ActorTrace) -> EthAddress {
87 if let Some(addr) = trace.state.delegated_address
88 && let Ok(eth_addr) = EthAddress::from_filecoin_address(&addr.into())
89 {
90 return eth_addr;
91 }
92 EthAddress::from_actor_id(trace.id)
93}
94
95fn trace_is_evm_or_eam(trace: &ExecutionTrace) -> bool {
97 if let Some(invoked_actor) = &trace.invoked_actor {
98 is_evm_actor(&invoked_actor.state.code)
99 || invoked_actor.id != Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR.id().unwrap()
100 } else {
101 false
102 }
103}
104
105fn trace_err_msg(trace: &ExecutionTrace) -> Option<String> {
107 let code = trace.msg_rct.exit_code;
108
109 if code.is_success() {
110 return None;
111 }
112
113 if code == ExitCode::SYS_OUT_OF_GAS {
115 return Some("out of gas".into());
116 }
117
118 if code < ExitCode::FIRST_ACTOR_ERROR_CODE.into() {
120 return Some(format!("vm error: {code}"));
121 }
122
123 if trace_is_evm_or_eam(trace) {
125 match code.into() {
126 evm12::EVM_CONTRACT_REVERTED => return Some("Reverted".into()), evm12::EVM_CONTRACT_INVALID_INSTRUCTION => return Some("invalid instruction".into()),
128 evm12::EVM_CONTRACT_UNDEFINED_INSTRUCTION => {
129 return Some("undefined instruction".into());
130 }
131 evm12::EVM_CONTRACT_STACK_UNDERFLOW => return Some("stack underflow".into()),
132 evm12::EVM_CONTRACT_STACK_OVERFLOW => return Some("stack overflow".into()),
133 evm12::EVM_CONTRACT_ILLEGAL_MEMORY_ACCESS => {
134 return Some("illegal memory access".into());
135 }
136 evm12::EVM_CONTRACT_BAD_JUMPDEST => return Some("invalid jump destination".into()),
137 evm12::EVM_CONTRACT_SELFDESTRUCT_FAILED => return Some("self destruct failed".into()),
138 _ => (),
139 }
140 }
141 Some(format!("actor error: {code}"))
143}
144
145pub fn build_traces(
147 env: &mut Environment,
148 address: &[i64],
149 trace: ExecutionTrace,
150) -> anyhow::Result<()> {
151 let (trace, recurse_into) = build_trace(env, address, trace)?;
152
153 let last_trace_idx = if let Some(trace) = trace {
154 let len = env.traces.len();
155 env.traces.push(trace);
156 env.subtrace_count += 1;
157 Some(len)
158 } else {
159 None
160 };
161
162 let (recurse_into, invoked_actor) = if let Some(trace) = recurse_into {
164 if let Some(invoked_actor) = &trace.invoked_actor {
165 let invoked_actor = invoked_actor.clone();
166 (trace, invoked_actor)
167 } else {
168 return Ok(());
169 }
170 } else {
171 return Ok(());
172 };
173
174 let mut sub_env = Environment {
175 caller: trace_to_address(&invoked_actor),
176 is_evm: is_evm_actor(&invoked_actor.state.code),
177 traces: env.traces.clone(),
178 ..Environment::default()
179 };
180 for subcall in recurse_into.subcalls.into_iter() {
181 let mut new_address = address.to_vec();
182 new_address.push(sub_env.subtrace_count);
183 build_traces(&mut sub_env, &new_address, subcall)?;
184 }
185 env.traces = sub_env.traces;
186 if let Some(idx) = last_trace_idx {
187 env.traces.get_mut(idx).expect("Infallible").subtraces = sub_env.subtrace_count;
188 }
189
190 Ok(())
191}
192
193fn build_trace(
197 env: &mut Environment,
198 address: &[i64],
199 trace: ExecutionTrace,
200) -> anyhow::Result<(Option<EthTrace>, Option<ExecutionTrace>)> {
201 if !address.is_empty()
220 && Into::<ExitCode>::into(trace.msg_rct.exit_code) == ExitCode::SYS_INSUFFICIENT_FUNDS
221 {
222 return Ok((None, None));
223 }
224
225 if trace.invoked_actor.is_none() {
229 return Ok((None, None));
230 }
231
232 let method = EVMMethod::from_u64(trace.msg.method);
238 if let Some(EVMMethod::InvokeContract) = method {
239 let (trace, exec_trace) = trace_evm_call(env, address, trace)?;
240 return Ok((Some(trace), Some(exec_trace)));
241 }
242
243 match trace.msg.to {
245 Address::INIT_ACTOR => {
246 let method = InitMethod::from_u64(trace.msg.method);
247 match method {
248 Some(InitMethod::Exec) | Some(InitMethod::Exec4) => {
249 return trace_native_create(env, address, &trace);
250 }
251 _ => (),
252 }
253 }
254 Address::ETHEREUM_ACCOUNT_MANAGER_ACTOR => {
255 let method = EAMMethod::from_u64(trace.msg.method);
256 match method {
257 Some(EAMMethod::Create)
258 | Some(EAMMethod::Create2)
259 | Some(EAMMethod::CreateExternal) => {
260 return trace_eth_create(env, address, &trace);
261 }
262 _ => (),
263 }
264 }
265 _ => (),
266 }
267
268 if env.is_evm && trace.msg.method > 0 && trace.msg.method < 1024 {
278 return trace_evm_private(env, address, &trace);
279 }
280
281 Ok((Some(trace_native_call(env, address, &trace)?), Some(trace)))
282}
283
284fn trace_call(
286 env: &mut Environment,
287 address: &[i64],
288 trace: &ExecutionTrace,
289 input: EthBytes,
290 output: EthBytes,
291) -> anyhow::Result<EthTrace> {
292 if let Some(invoked_actor) = &trace.invoked_actor {
293 let to = trace_to_address(invoked_actor);
294 let call_type: String = if trace.msg.read_only.unwrap_or_default() {
295 "staticcall"
296 } else {
297 "call"
298 }
299 .into();
300
301 Ok(EthTrace {
302 r#type: "call".into(),
303 action: TraceAction::Call(EthCallTraceAction {
304 call_type,
305 from: env.caller,
306 to: Some(to),
307 gas: trace.msg.gas_limit.unwrap_or_default().into(),
308 value: trace.msg.value.clone().into(),
309 input,
310 }),
311 result: TraceResult::Call(EthCallTraceResult {
312 gas_used: trace.sum_gas().total_gas.into(),
313 output,
314 }),
315 trace_address: Vec::from(address),
316 error: trace_err_msg(trace),
317 ..EthTrace::default()
318 })
319 } else {
320 bail!("no invoked actor")
321 }
322}
323
324fn trace_native_call(
326 env: &mut Environment,
327 address: &[i64],
328 trace: &ExecutionTrace,
329) -> anyhow::Result<EthTrace> {
330 trace_call(
331 env,
332 address,
333 trace,
334 encode_filecoin_params_as_abi(trace.msg.method, trace.msg.params_codec, &trace.msg.params)?,
335 EthBytes(encode_filecoin_returns_as_abi(
336 trace.msg_rct.exit_code.value().into(),
337 trace.msg_rct.return_codec,
338 &trace.msg_rct.r#return,
339 )),
340 )
341}
342
343fn trace_evm_call(
346 env: &mut Environment,
347 address: &[i64],
348 trace: ExecutionTrace,
349) -> anyhow::Result<(EthTrace, ExecutionTrace)> {
350 let input = match decode_payload(&trace.msg.params, trace.msg.params_codec) {
351 Ok(value) => value,
352 Err(err) => {
353 debug!("failed to decode contract invocation payload: {err}");
354 return Ok((trace_native_call(env, address, &trace)?, trace));
355 }
356 };
357 let output = match decode_payload(&trace.msg_rct.r#return, trace.msg_rct.return_codec) {
358 Ok(value) => value,
359 Err(err) => {
360 debug!("failed to decode contract invocation return: {err}");
361 return Ok((trace_native_call(env, address, &trace)?, trace));
362 }
363 };
364 Ok((trace_call(env, address, &trace, input, output)?, trace))
365}
366
367fn trace_native_create(
371 env: &mut Environment,
372 address: &[i64],
373 trace: &ExecutionTrace,
374) -> anyhow::Result<(Option<EthTrace>, Option<ExecutionTrace>)> {
375 if trace.msg.read_only.unwrap_or_default() {
376 return Ok((None, None));
381 }
382
383 let sub_trace = trace
384 .subcalls
385 .iter()
386 .find(|c| c.msg.method == METHOD_CONSTRUCTOR);
387
388 let sub_trace = if let Some(sub_trace) = sub_trace {
389 sub_trace
390 } else {
391 if trace.msg_rct.exit_code.is_success() {
395 bail!("successful Exec/Exec4 call failed to call a constructor");
396 }
397 return Ok((None, None));
407 };
408
409 if trace.msg.method == (InitMethod::Exec4 as u64) {
414 bail!("direct call to Exec4 successfully called a constructor!");
415 }
416
417 let mut output = EthBytes::default();
418 let mut create_addr = EthAddress::default();
419 if trace.msg_rct.exit_code.is_success() {
420 output = EthBytes(vec![0xFE]);
425
426 let init_return: ExecReturn = decode_return(&trace.msg_rct)?;
428 let actor_id = init_return.id_address.id()?;
429 let eth_addr = EthAddress::from_actor_id(actor_id);
430 create_addr = eth_addr;
431 }
432
433 Ok((
434 Some(EthTrace {
435 r#type: "create".into(),
436 action: TraceAction::Create(EthCreateTraceAction {
437 from: env.caller,
438 gas: trace.msg.gas_limit.unwrap_or_default().into(),
439 value: trace.msg.value.clone().into(),
440 init: EthBytes(vec![0xFE]),
444 }),
445 result: TraceResult::Create(EthCreateTraceResult {
446 gas_used: trace.sum_gas().total_gas.into(),
447 address: Some(create_addr),
448 code: output,
449 }),
450 trace_address: Vec::from(address),
451 error: trace_err_msg(trace),
452 ..EthTrace::default()
453 }),
454 Some(sub_trace.clone()),
455 ))
456}
457
458fn decode_create_via_eam(trace: &ExecutionTrace) -> anyhow::Result<(Vec<u8>, EthAddress)> {
462 let init_code = match EAMMethod::from_u64(trace.msg.method) {
463 Some(EAMMethod::Create) => {
464 let params = decode_params::<eam12::CreateParams>(&trace.msg)?;
465 params.initcode
466 }
467 Some(EAMMethod::Create2) => {
468 let params = decode_params::<eam12::Create2Params>(&trace.msg)?;
469 params.initcode
470 }
471 Some(EAMMethod::CreateExternal) => {
472 decode_payload(&trace.msg.params, trace.msg.params_codec)?.into()
473 }
474 _ => bail!("unexpected CREATE method {}", trace.msg.method),
475 };
476 let ret = decode_return::<eam12::CreateReturn>(&trace.msg_rct)?;
477
478 Ok((init_code, ret.eth_address.0.into()))
479}
480
481fn trace_eth_create(
484 env: &mut Environment,
485 address: &[i64],
486 trace: &ExecutionTrace,
487) -> anyhow::Result<(Option<EthTrace>, Option<ExecutionTrace>)> {
488 if trace.msg.read_only.unwrap_or_default() {
490 return Ok((None, None));
491 }
492
493 let sub_trace = trace
495 .subcalls
496 .iter()
497 .filter_map(|et| {
498 if et.msg.to == Address::INIT_ACTOR {
499 et.subcalls
500 .iter()
501 .find(|et| et.msg.method == METHOD_CONSTRUCTOR)
502 } else {
503 match EVMMethod::from_u64(et.msg.method) {
504 Some(EVMMethod::Resurrect) => Some(et),
505 _ => None,
506 }
507 }
508 })
509 .next();
510
511 let sub_trace = if let Some(sub_trace) = sub_trace {
513 sub_trace
514 } else {
515 if trace.msg_rct.exit_code.is_success() {
516 bail!("successful Create/Create2 call failed to call a constructor");
517 }
518 return Ok((None, None));
519 };
520
521 let (init_code, create_addr) = decode_create_via_eam(trace)?;
523
524 let output = match trace.msg_rct.exit_code.value() {
526 0 => {
527 EthBytes(vec![0xFE])
533 }
534 33 => {
535 decode_payload(&sub_trace.msg_rct.r#return, sub_trace.msg_rct.return_codec)?
539 }
540 _ => EthBytes::default(),
541 };
542
543 Ok((
544 Some(EthTrace {
545 r#type: "create".into(),
546 action: TraceAction::Create(EthCreateTraceAction {
547 from: env.caller,
548 gas: trace.msg.gas_limit.unwrap_or_default().into(),
549 value: trace.msg.value.clone().into(),
550 init: init_code.into(),
551 }),
552 result: TraceResult::Create(EthCreateTraceResult {
553 gas_used: trace.sum_gas().total_gas.into(),
554 address: Some(create_addr),
555 code: output,
556 }),
557 trace_address: Vec::from(address),
558 error: trace_err_msg(trace),
559 ..EthTrace::default()
560 }),
561 Some(sub_trace.clone()),
562 ))
563}
564
565fn trace_evm_private(
568 env: &mut Environment,
569 address: &[i64],
570 trace: &ExecutionTrace,
571) -> anyhow::Result<(Option<EthTrace>, Option<ExecutionTrace>)> {
572 match EVMMethod::from_u64(trace.msg.method) {
587 Some(EVMMethod::GetBytecode) => {
588 env.last_byte_code = None;
592 if trace.msg_rct.exit_code.is_success()
593 && let Option::Some(actor_trace) = &trace.invoked_actor
594 {
595 let to = trace_to_address(actor_trace);
596 env.last_byte_code = Some(to);
597 }
598 Ok((None, None))
599 }
600 Some(EVMMethod::InvokeContractDelegate) => {
601 if env.last_byte_code.is_none() {
613 bail!("unknown bytecode for delegate call");
614 }
615
616 if let Option::Some(actor_trace) = &trace.invoked_actor {
617 let to = trace_to_address(actor_trace);
618 if env.caller != to {
619 bail!(
620 "delegate-call not from address to self: {:?} != {:?}",
621 env.caller,
622 to
623 );
624 }
625 }
626
627 let dp = decode_params::<evm12::DelegateCallParams>(&trace.msg)?;
628
629 let output = decode_payload(&trace.msg_rct.r#return, trace.msg_rct.return_codec)
630 .map_err(|e| anyhow::anyhow!("failed to decode delegate-call return: {}", e))?;
631
632 Ok((
633 Some(EthTrace {
634 r#type: "call".into(),
635 action: TraceAction::Call(EthCallTraceAction {
636 call_type: "delegatecall".into(),
637 from: env.caller,
638 to: env.last_byte_code,
639 gas: trace.msg.gas_limit.unwrap_or_default().into(),
640 value: trace.msg.value.clone().into(),
641 input: dp.input.into(),
642 }),
643 result: TraceResult::Call(EthCallTraceResult {
644 gas_used: trace.sum_gas().total_gas.into(),
645 output,
646 }),
647 trace_address: Vec::from(address),
648 error: trace_err_msg(trace),
649 ..EthTrace::default()
650 }),
651 Some(trace.clone()),
652 ))
653 }
654 _ => {
655 Ok((None, None))
658 }
659 }
660}
661
662pub(crate) fn build_state_diff<S: Blockstore, T: Blockstore>(
664 store: &S,
665 pre_state: &StateTree<T>,
666 post_state: &StateTree<T>,
667 touched_addresses: &HashSet<EthAddress>,
668) -> anyhow::Result<StateDiff> {
669 let mut state_diff = StateDiff::new();
670
671 for eth_addr in touched_addresses {
672 let fil_addr = eth_addr.to_filecoin_address()?;
673
674 let pre_actor = pre_state
676 .get_actor(&fil_addr)
677 .map_err(|e| anyhow::anyhow!("failed to get actor state: {e}"))?;
678
679 let post_actor = post_state
680 .get_actor(&fil_addr)
681 .map_err(|e| anyhow::anyhow!("failed to get actor state: {e}"))?;
682
683 let account_diff = build_account_diff(store, pre_actor.as_ref(), post_actor.as_ref())?;
684
685 state_diff.insert_if_changed(*eth_addr, account_diff);
687 }
688
689 Ok(state_diff)
690}
691
692fn build_account_diff<DB: Blockstore>(
694 store: &DB,
695 pre_actor: Option<&ActorState>,
696 post_actor: Option<&ActorState>,
697) -> anyhow::Result<AccountDiff> {
698 let mut diff = AccountDiff::default();
699
700 let pre_balance = pre_actor.map(|a| EthBigInt(a.balance.atto().clone()));
702 let post_balance = post_actor.map(|a| EthBigInt(a.balance.atto().clone()));
703 diff.balance = Delta::from_comparison(pre_balance, post_balance);
704
705 let get_nonce = |actor: &ActorState| -> EthUint64 {
707 if is_evm_actor(&actor.code) {
708 EthUint64::from(
709 evm::State::load(store, actor.code, actor.state)
710 .map(|s| s.nonce())
711 .unwrap_or(actor.sequence),
712 )
713 } else {
714 EthUint64::from(actor.sequence)
715 }
716 };
717
718 let get_bytecode = |actor: &ActorState| -> Option<EthBytes> {
720 if !is_evm_actor(&actor.code) {
721 return None;
722 }
723
724 let evm_state = evm::State::load(store, actor.code, actor.state).ok()?;
725 store
726 .get(&evm_state.bytecode())
727 .ok()
728 .flatten()
729 .map(EthBytes)
730 };
731
732 let pre_nonce = pre_actor.map(get_nonce);
734 let post_nonce = post_actor.map(get_nonce);
735 diff.nonce = Delta::from_comparison(pre_nonce, post_nonce);
736
737 let pre_code = pre_actor.and_then(get_bytecode);
739 let post_code = post_actor.and_then(get_bytecode);
740 diff.code = Delta::from_comparison(pre_code, post_code);
741
742 diff.storage = diff_evm_storage_for_actors(store, pre_actor, post_actor)?;
744
745 Ok(diff)
746}
747
748fn diff_evm_storage_for_actors<DB: Blockstore>(
756 store: &DB,
757 pre_actor: Option<&ActorState>,
758 post_actor: Option<&ActorState>,
759) -> anyhow::Result<BTreeMap<EthHash, Delta<EthHash>>> {
760 let pre_is_evm = pre_actor.is_some_and(|a| is_evm_actor(&a.code));
761 let post_is_evm = post_actor.is_some_and(|a| is_evm_actor(&a.code));
762
763 let pre_entries = extract_evm_storage_entries(store, pre_actor);
765 let post_entries = extract_evm_storage_entries(store, post_actor);
766
767 if pre_entries.is_empty() && post_entries.is_empty() {
769 return Ok(BTreeMap::new());
770 }
771
772 let mut diff = BTreeMap::new();
773
774 match (pre_is_evm, post_is_evm) {
775 (false, true) => {
776 for (key_bytes, value) in &post_entries {
777 let key_hash = EthHash(ethereum_types::H256(*key_bytes));
778 diff.insert(key_hash, Delta::Added(u256_to_eth_hash(value)));
779 }
780 }
781 (true, false) => {
782 for (key_bytes, value) in &pre_entries {
783 let key_hash = EthHash(ethereum_types::H256(*key_bytes));
784 diff.insert(key_hash, Delta::Removed(u256_to_eth_hash(value)));
785 }
786 }
787 (true, true) => {
788 for (key_bytes, pre_value) in &pre_entries {
789 let key_hash = EthHash(ethereum_types::H256(*key_bytes));
790 let pre_hash = u256_to_eth_hash(pre_value);
791
792 match post_entries.get(key_bytes) {
793 Some(post_value) if pre_value != post_value => {
794 diff.insert(
796 key_hash,
797 Delta::Changed(super::types::ChangedType {
798 from: pre_hash,
799 to: u256_to_eth_hash(post_value),
800 }),
801 );
802 }
803 Some(_) => {
804 }
806 None => {
807 diff.insert(
809 key_hash,
810 Delta::Changed(super::types::ChangedType {
811 from: pre_hash,
812 to: ZERO_HASH,
813 }),
814 );
815 }
816 }
817 }
818
819 for (key_bytes, post_value) in &post_entries {
821 if !pre_entries.contains_key(key_bytes) {
822 let key_hash = EthHash(ethereum_types::H256(*key_bytes));
823 diff.insert(
824 key_hash,
825 Delta::Changed(super::types::ChangedType {
826 from: ZERO_HASH,
827 to: u256_to_eth_hash(post_value),
828 }),
829 );
830 }
831 }
832 }
833 (false, false) => {}
835 }
836
837 Ok(diff)
838}
839
840fn extract_evm_storage_entries<DB: Blockstore>(
843 store: &DB,
844 actor: Option<&ActorState>,
845) -> HashMap<[u8; 32], U256> {
846 let actor = match actor {
847 Some(a) if is_evm_actor(&a.code) => a,
848 _ => return HashMap::default(),
849 };
850
851 let evm_state = match evm::State::load(store, actor.code, actor.state) {
852 Ok(state) => state,
853 Err(e) => {
854 debug!("failed to load EVM state for storage extraction: {e}");
855 return HashMap::default();
856 }
857 };
858
859 let storage_cid = evm_state.contract_state();
860 let config = evm_kamt_config();
861
862 let kamt: EvmStorageKamt<&DB> = match Kamt::load_with_config(&storage_cid, store, config) {
863 Ok(k) => k,
864 Err(e) => {
865 debug!("failed to load storage KAMT: {e}");
866 return HashMap::default();
867 }
868 };
869
870 let mut entries = HashMap::default();
871 if let Err(e) = kamt.for_each(|key, value| {
872 entries.insert(key.to_big_endian(), *value);
873 Ok(())
874 }) {
875 debug!("failed to iterate storage KAMT: {e}");
876 return HashMap::default();
877 }
878
879 entries
880}
881
882#[cfg(test)]
883mod tests {
884 use super::*;
885 use crate::db::MemoryDB;
886 use crate::networks::ACTOR_BUNDLES_METADATA;
887 use crate::rpc::eth::types::ChangedType;
888 use crate::shim::address::Address as FilecoinAddress;
889 use crate::shim::econ::TokenAmount;
890 use crate::shim::machine::BuiltinActor;
891 use crate::shim::state_tree::{StateTree, StateTreeVersion};
892 use crate::utils::db::CborStoreExt as _;
893 use ahash::HashSetExt as _;
894 use cid::Cid;
895 use num::BigInt;
896 use std::sync::Arc;
897
898 fn create_test_actor(balance_atto: u64, sequence: u64) -> ActorState {
899 ActorState::new(
900 Cid::default(), Cid::default(), TokenAmount::from_atto(balance_atto),
903 sequence,
904 None, )
906 }
907
908 fn get_evm_actor_code_cid() -> Option<Cid> {
909 for bundle in ACTOR_BUNDLES_METADATA.values() {
910 if bundle.actor_major_version().ok() == Some(17)
911 && let Ok(cid) = bundle.manifest.get(BuiltinActor::EVM)
912 {
913 return Some(cid);
914 }
915 }
916 None
917 }
918
919 fn create_evm_actor_with_bytecode(
920 store: &MemoryDB,
921 balance_atto: u64,
922 actor_sequence: u64,
923 evm_nonce: u64,
924 bytecode: Option<&[u8]>,
925 ) -> Option<ActorState> {
926 use fvm_ipld_blockstore::Blockstore as _;
927
928 let evm_code_cid = get_evm_actor_code_cid()?;
929
930 let bytecode_cid = if let Some(code) = bytecode {
932 use multihash_codetable::MultihashDigest;
933 let mh = multihash_codetable::Code::Blake2b256.digest(code);
934 let cid = Cid::new_v1(fvm_ipld_encoding::IPLD_RAW, mh);
935 store.put_keyed(&cid, code).ok()?;
936 cid
937 } else {
938 Cid::default()
939 };
940
941 let bytecode_hash = if let Some(code) = bytecode {
942 use keccak_hash::keccak;
943 let hash = keccak(code);
944 fil_actor_evm_state::v17::BytecodeHash::from(hash.0)
945 } else {
946 fil_actor_evm_state::v17::BytecodeHash::EMPTY
947 };
948
949 let evm_state = fil_actor_evm_state::v17::State {
950 bytecode: bytecode_cid,
951 bytecode_hash,
952 contract_state: Cid::default(),
953 transient_data: None,
954 nonce: evm_nonce,
955 tombstone: None,
956 };
957
958 let state_cid = store.put_cbor_default(&evm_state).ok()?;
959
960 Some(ActorState::new(
961 evm_code_cid,
962 state_cid,
963 TokenAmount::from_atto(balance_atto),
964 actor_sequence,
965 None,
966 ))
967 }
968
969 fn create_masked_id_eth_address(actor_id: u64) -> EthAddress {
970 EthAddress::from_actor_id(actor_id)
971 }
972
973 struct TestStateTrees {
974 store: Arc<MemoryDB>,
975 pre_state: StateTree<MemoryDB>,
976 post_state: StateTree<MemoryDB>,
977 }
978
979 impl TestStateTrees {
980 fn new() -> anyhow::Result<Self> {
981 let store = Arc::new(MemoryDB::default());
982 let pre_state = StateTree::new(store.clone(), StateTreeVersion::V5)?;
984 let post_state = StateTree::new(store.clone(), StateTreeVersion::V5)?;
985 Ok(Self {
986 store,
987 pre_state,
988 post_state,
989 })
990 }
991
992 fn with_changed_actor(
994 actor_id: u64,
995 pre_actor: ActorState,
996 post_actor: ActorState,
997 ) -> anyhow::Result<Self> {
998 let store = Arc::new(MemoryDB::default());
999 let mut pre_state = StateTree::new(store.clone(), StateTreeVersion::V5)?;
1000 let mut post_state = StateTree::new(store.clone(), StateTreeVersion::V5)?;
1001 let addr = FilecoinAddress::new_id(actor_id);
1002 pre_state.set_actor(&addr, pre_actor)?;
1003 post_state.set_actor(&addr, post_actor)?;
1004 Ok(Self {
1005 store,
1006 pre_state,
1007 post_state,
1008 })
1009 }
1010
1011 fn with_created_actor(actor_id: u64, post_actor: ActorState) -> anyhow::Result<Self> {
1013 let store = Arc::new(MemoryDB::default());
1014 let pre_state = StateTree::new(store.clone(), StateTreeVersion::V5)?;
1015 let mut post_state = StateTree::new(store.clone(), StateTreeVersion::V5)?;
1016 let addr = FilecoinAddress::new_id(actor_id);
1017 post_state.set_actor(&addr, post_actor)?;
1018 Ok(Self {
1019 store,
1020 pre_state,
1021 post_state,
1022 })
1023 }
1024
1025 fn with_deleted_actor(actor_id: u64, pre_actor: ActorState) -> anyhow::Result<Self> {
1027 let store = Arc::new(MemoryDB::default());
1028 let mut pre_state = StateTree::new(store.clone(), StateTreeVersion::V5)?;
1029 let post_state = StateTree::new(store.clone(), StateTreeVersion::V5)?;
1030 let addr = FilecoinAddress::new_id(actor_id);
1031 pre_state.set_actor(&addr, pre_actor)?;
1032 Ok(Self {
1033 store,
1034 pre_state,
1035 post_state,
1036 })
1037 }
1038
1039 fn build_diff(&self, touched_addresses: &HashSet<EthAddress>) -> anyhow::Result<StateDiff> {
1041 build_state_diff(
1042 self.store.as_ref(),
1043 &self.pre_state,
1044 &self.post_state,
1045 touched_addresses,
1046 )
1047 }
1048 }
1049
1050 #[test]
1051 fn test_build_state_diff_empty_touched_addresses() {
1052 let trees = TestStateTrees::new().unwrap();
1053 let touched_addresses = HashSet::new();
1054
1055 let state_diff = trees.build_diff(&touched_addresses).unwrap();
1056
1057 assert!(state_diff.0.is_empty());
1059 }
1060
1061 #[test]
1062 fn test_build_state_diff_nonexistent_address() {
1063 let trees = TestStateTrees::new().unwrap();
1064 let mut touched_addresses = HashSet::new();
1065 touched_addresses.insert(create_masked_id_eth_address(9999));
1066
1067 let state_diff = trees.build_diff(&touched_addresses).unwrap();
1068
1069 assert!(state_diff.0.is_empty());
1071 }
1072
1073 #[test]
1074 fn test_build_state_diff_balance_increase() {
1075 let actor_id = 1001u64;
1076 let pre_actor = create_test_actor(1000, 5);
1077 let post_actor = create_test_actor(2000, 5);
1078 let trees = TestStateTrees::with_changed_actor(actor_id, pre_actor, post_actor).unwrap();
1079
1080 let mut touched_addresses = HashSet::new();
1081 touched_addresses.insert(create_masked_id_eth_address(actor_id));
1082
1083 let state_diff = trees.build_diff(&touched_addresses).unwrap();
1084
1085 assert_eq!(state_diff.0.len(), 1);
1086 let eth_addr = create_masked_id_eth_address(actor_id);
1087 let diff = state_diff.0.get(ð_addr).unwrap();
1088 match &diff.balance {
1089 Delta::Changed(change) => {
1090 assert_eq!(change.from.0, BigInt::from(1000));
1091 assert_eq!(change.to.0, BigInt::from(2000));
1092 }
1093 _ => panic!("Expected Delta::Changed for balance"),
1094 }
1095 assert!(diff.nonce.is_unchanged());
1096 }
1097
1098 #[test]
1099 fn test_build_state_diff_balance_decrease() {
1100 let actor_id = 1002u64;
1101 let pre_actor = create_test_actor(5000, 10);
1102 let post_actor = create_test_actor(3000, 10);
1103 let trees = TestStateTrees::with_changed_actor(actor_id, pre_actor, post_actor).unwrap();
1104
1105 let mut touched_addresses = HashSet::new();
1106 touched_addresses.insert(create_masked_id_eth_address(actor_id));
1107
1108 let state_diff = trees.build_diff(&touched_addresses).unwrap();
1109
1110 let eth_addr = create_masked_id_eth_address(actor_id);
1111 let diff = state_diff.0.get(ð_addr).unwrap();
1112 match &diff.balance {
1113 Delta::Changed(change) => {
1114 assert_eq!(change.from.0, BigInt::from(5000));
1115 assert_eq!(change.to.0, BigInt::from(3000));
1116 }
1117 _ => panic!("Expected Delta::Changed for balance"),
1118 }
1119 assert!(diff.nonce.is_unchanged());
1120 }
1121
1122 #[test]
1123 fn test_build_state_diff_nonce_increment() {
1124 let actor_id = 1003u64;
1125 let pre_actor = create_test_actor(1000, 5);
1126 let post_actor = create_test_actor(1000, 6);
1127 let trees = TestStateTrees::with_changed_actor(actor_id, pre_actor, post_actor).unwrap();
1128
1129 let mut touched_addresses = HashSet::new();
1130 touched_addresses.insert(create_masked_id_eth_address(actor_id));
1131
1132 let state_diff = trees.build_diff(&touched_addresses).unwrap();
1133
1134 let eth_addr = create_masked_id_eth_address(actor_id);
1135 let diff = state_diff.0.get(ð_addr).unwrap();
1136 assert!(diff.balance.is_unchanged());
1137 match &diff.nonce {
1138 Delta::Changed(change) => {
1139 assert_eq!(change.from.0, 5);
1140 assert_eq!(change.to.0, 6);
1141 }
1142 _ => panic!("Expected Delta::Changed for nonce"),
1143 }
1144 }
1145
1146 #[test]
1147 fn test_build_state_diff_both_balance_and_nonce_change() {
1148 let actor_id = 1004u64;
1149 let pre_actor = create_test_actor(10000, 100);
1150 let post_actor = create_test_actor(9000, 101);
1151 let trees = TestStateTrees::with_changed_actor(actor_id, pre_actor, post_actor).unwrap();
1152
1153 let mut touched_addresses = HashSet::new();
1154 touched_addresses.insert(create_masked_id_eth_address(actor_id));
1155
1156 let state_diff = trees.build_diff(&touched_addresses).unwrap();
1157
1158 let eth_addr = create_masked_id_eth_address(actor_id);
1159 let diff = state_diff.0.get(ð_addr).unwrap();
1160 match &diff.balance {
1161 Delta::Changed(change) => {
1162 assert_eq!(change.from.0, BigInt::from(10000));
1163 assert_eq!(change.to.0, BigInt::from(9000));
1164 }
1165 _ => panic!("Expected Delta::Changed for balance"),
1166 }
1167 match &diff.nonce {
1168 Delta::Changed(change) => {
1169 assert_eq!(change.from.0, 100);
1170 assert_eq!(change.to.0, 101);
1171 }
1172 _ => panic!("Expected Delta::Changed for nonce"),
1173 }
1174 }
1175
1176 #[test]
1177 fn test_build_state_diff_account_creation() {
1178 let actor_id = 1005u64;
1179 let post_actor = create_test_actor(5000, 0);
1180 let trees = TestStateTrees::with_created_actor(actor_id, post_actor).unwrap();
1181
1182 let mut touched_addresses = HashSet::new();
1183 touched_addresses.insert(create_masked_id_eth_address(actor_id));
1184
1185 let state_diff = trees.build_diff(&touched_addresses).unwrap();
1186
1187 let eth_addr = create_masked_id_eth_address(actor_id);
1188 let diff = state_diff.0.get(ð_addr).unwrap();
1189 match &diff.balance {
1190 Delta::Added(balance) => {
1191 assert_eq!(balance.0, BigInt::from(5000));
1192 }
1193 _ => panic!("Expected Delta::Added for balance"),
1194 }
1195 match &diff.nonce {
1196 Delta::Added(nonce) => {
1197 assert_eq!(nonce.0, 0);
1198 }
1199 _ => panic!("Expected Delta::Added for nonce"),
1200 }
1201 }
1202
1203 #[test]
1204 fn test_build_state_diff_account_deletion() {
1205 let actor_id = 1006u64;
1206 let pre_actor = create_test_actor(3000, 10);
1207 let trees = TestStateTrees::with_deleted_actor(actor_id, pre_actor).unwrap();
1208
1209 let mut touched_addresses = HashSet::new();
1210 touched_addresses.insert(create_masked_id_eth_address(actor_id));
1211
1212 let state_diff = trees.build_diff(&touched_addresses).unwrap();
1213
1214 let eth_addr = create_masked_id_eth_address(actor_id);
1215 let diff = state_diff.0.get(ð_addr).unwrap();
1216 match &diff.balance {
1217 Delta::Removed(balance) => {
1218 assert_eq!(balance.0, BigInt::from(3000));
1219 }
1220 _ => panic!("Expected Delta::Removed for balance"),
1221 }
1222 match &diff.nonce {
1223 Delta::Removed(nonce) => {
1224 assert_eq!(nonce.0, 10);
1225 }
1226 _ => panic!("Expected Delta::Removed for nonce"),
1227 }
1228 }
1229
1230 #[test]
1231 fn test_build_state_diff_multiple_addresses() {
1232 let store = Arc::new(MemoryDB::default());
1233 let mut pre_state = StateTree::new(store.clone(), StateTreeVersion::V5).unwrap();
1234 let mut post_state = StateTree::new(store.clone(), StateTreeVersion::V5).unwrap();
1235
1236 let addr1 = FilecoinAddress::new_id(2001);
1238 pre_state
1239 .set_actor(&addr1, create_test_actor(1000, 0))
1240 .unwrap();
1241 post_state
1242 .set_actor(&addr1, create_test_actor(2000, 0))
1243 .unwrap();
1244
1245 let addr2 = FilecoinAddress::new_id(2002);
1247 pre_state
1248 .set_actor(&addr2, create_test_actor(500, 5))
1249 .unwrap();
1250 post_state
1251 .set_actor(&addr2, create_test_actor(500, 6))
1252 .unwrap();
1253
1254 let addr3 = FilecoinAddress::new_id(2003);
1256 pre_state
1257 .set_actor(&addr3, create_test_actor(100, 1))
1258 .unwrap();
1259 post_state
1260 .set_actor(&addr3, create_test_actor(100, 1))
1261 .unwrap();
1262
1263 let mut touched_addresses = HashSet::new();
1264 touched_addresses.insert(create_masked_id_eth_address(2001));
1265 touched_addresses.insert(create_masked_id_eth_address(2002));
1266 touched_addresses.insert(create_masked_id_eth_address(2003));
1267
1268 let state_diff =
1269 build_state_diff(store.as_ref(), &pre_state, &post_state, &touched_addresses).unwrap();
1270
1271 assert_eq!(state_diff.0.len(), 2);
1272 assert!(
1273 state_diff
1274 .0
1275 .contains_key(&create_masked_id_eth_address(2001))
1276 );
1277 assert!(
1278 state_diff
1279 .0
1280 .contains_key(&create_masked_id_eth_address(2002))
1281 );
1282 assert!(
1283 !state_diff
1284 .0
1285 .contains_key(&create_masked_id_eth_address(2003))
1286 );
1287 }
1288
1289 #[test]
1290 fn test_build_state_diff_evm_actor_scenarios() {
1291 struct TestCase {
1292 name: &'static str,
1293 pre: Option<(u64, u64, Option<&'static [u8]>)>, post: Option<(u64, u64, Option<&'static [u8]>)>,
1295 expected_balance: Delta<EthBigInt>,
1296 expected_nonce: Delta<EthUint64>,
1297 expected_code: Delta<EthBytes>,
1298 }
1299
1300 let bytecode1: &[u8] = &[0x60, 0x80, 0x60, 0x40, 0x52];
1301 let bytecode2: &[u8] = &[0x60, 0x80, 0x60, 0x40, 0x52, 0x00];
1302
1303 let cases = vec![
1304 TestCase {
1305 name: "No change",
1306 pre: Some((1000, 5, Some(bytecode1))),
1307 post: Some((1000, 5, Some(bytecode1))),
1308 expected_balance: Delta::Unchanged,
1309 expected_nonce: Delta::Unchanged,
1310 expected_code: Delta::Unchanged,
1311 },
1312 TestCase {
1313 name: "Balance increase",
1314 pre: Some((1000, 5, Some(bytecode1))),
1315 post: Some((2000, 5, Some(bytecode1))),
1316 expected_balance: Delta::Changed(ChangedType {
1317 from: EthBigInt(BigInt::from(1000)),
1318 to: EthBigInt(BigInt::from(2000)),
1319 }),
1320 expected_nonce: Delta::Unchanged,
1321 expected_code: Delta::Unchanged,
1322 },
1323 TestCase {
1324 name: "Nonce increment",
1325 pre: Some((1000, 5, Some(bytecode1))),
1326 post: Some((1000, 6, Some(bytecode1))),
1327 expected_balance: Delta::Unchanged,
1328 expected_nonce: Delta::Changed(ChangedType {
1329 from: EthUint64(5),
1330 to: EthUint64(6),
1331 }),
1332 expected_code: Delta::Unchanged,
1333 },
1334 TestCase {
1335 name: "Bytecode change",
1336 pre: Some((1000, 5, Some(bytecode1))),
1337 post: Some((1000, 5, Some(bytecode2))),
1338 expected_balance: Delta::Unchanged,
1339 expected_nonce: Delta::Unchanged,
1340 expected_code: Delta::Changed(ChangedType {
1341 from: EthBytes(bytecode1.to_vec()),
1342 to: EthBytes(bytecode2.to_vec()),
1343 }),
1344 },
1345 TestCase {
1346 name: "Balance and Nonce change",
1347 pre: Some((1000, 5, Some(bytecode1))),
1348 post: Some((2000, 6, Some(bytecode1))),
1349 expected_balance: Delta::Changed(ChangedType {
1350 from: EthBigInt(BigInt::from(1000)),
1351 to: EthBigInt(BigInt::from(2000)),
1352 }),
1353 expected_nonce: Delta::Changed(ChangedType {
1354 from: EthUint64(5),
1355 to: EthUint64(6),
1356 }),
1357 expected_code: Delta::Unchanged,
1358 },
1359 TestCase {
1360 name: "Creation",
1361 pre: None,
1362 post: Some((5000, 0, Some(bytecode1))),
1363 expected_balance: Delta::Added(EthBigInt(BigInt::from(5000))),
1364 expected_nonce: Delta::Added(EthUint64(0)),
1365 expected_code: Delta::Added(EthBytes(bytecode1.to_vec())),
1366 },
1367 TestCase {
1368 name: "Deletion",
1369 pre: Some((3000, 10, Some(bytecode1))),
1370 post: None,
1371 expected_balance: Delta::Removed(EthBigInt(BigInt::from(3000))),
1372 expected_nonce: Delta::Removed(EthUint64(10)),
1373 expected_code: Delta::Removed(EthBytes(bytecode1.to_vec())),
1374 },
1375 ];
1376
1377 for case in cases {
1378 let store = Arc::new(MemoryDB::default());
1379 let actor_id = 10000u64; let pre_actor = case.pre.and_then(|(bal, nonce, code)| {
1382 create_evm_actor_with_bytecode(&store, bal, 0, nonce, code)
1383 });
1384 let post_actor = case.post.and_then(|(bal, nonce, code)| {
1385 create_evm_actor_with_bytecode(&store, bal, 0, nonce, code)
1386 });
1387
1388 let mut pre_state = StateTree::new(store.clone(), StateTreeVersion::V5).unwrap();
1389 let mut post_state = StateTree::new(store.clone(), StateTreeVersion::V5).unwrap();
1390 let addr = FilecoinAddress::new_id(actor_id);
1391
1392 if let Some(actor) = pre_actor {
1393 pre_state.set_actor(&addr, actor).unwrap();
1394 }
1395 if let Some(actor) = post_actor {
1396 post_state.set_actor(&addr, actor).unwrap();
1397 }
1398
1399 let mut touched_addresses = HashSet::new();
1400 touched_addresses.insert(create_masked_id_eth_address(actor_id));
1401
1402 let state_diff =
1403 build_state_diff(store.as_ref(), &pre_state, &post_state, &touched_addresses)
1404 .unwrap();
1405
1406 if case.expected_balance == Delta::Unchanged
1407 && case.expected_nonce == Delta::Unchanged
1408 && case.expected_code == Delta::Unchanged
1409 {
1410 assert!(
1411 state_diff.0.is_empty(),
1412 "Test case '{}' failed: expected empty diff",
1413 case.name
1414 );
1415 } else {
1416 let eth_addr = create_masked_id_eth_address(actor_id);
1417 let diff = state_diff.0.get(ð_addr).unwrap_or_else(|| {
1418 panic!("Test case '{}' failed: missing diff entry", case.name)
1419 });
1420
1421 assert_eq!(
1422 diff.balance, case.expected_balance,
1423 "Test case '{}' failed: balance mismatch",
1424 case.name
1425 );
1426 assert_eq!(
1427 diff.nonce, case.expected_nonce,
1428 "Test case '{}' failed: nonce mismatch",
1429 case.name
1430 );
1431 assert_eq!(
1432 diff.code, case.expected_code,
1433 "Test case '{}' failed: code mismatch",
1434 case.name
1435 );
1436 }
1437 }
1438 }
1439
1440 #[test]
1441 fn test_build_state_diff_non_evm_actor_no_code() {
1442 let actor_id = 4005u64;
1444 let pre_actor = create_test_actor(1000, 5);
1445 let post_actor = create_test_actor(2000, 6);
1446 let trees = TestStateTrees::with_changed_actor(actor_id, pre_actor, post_actor).unwrap();
1447
1448 let mut touched_addresses = HashSet::new();
1449 touched_addresses.insert(create_masked_id_eth_address(actor_id));
1450
1451 let state_diff = trees.build_diff(&touched_addresses).unwrap();
1452
1453 let eth_addr = create_masked_id_eth_address(actor_id);
1454 let diff = state_diff.0.get(ð_addr).unwrap();
1455
1456 assert!(!diff.balance.is_unchanged());
1458 assert!(!diff.nonce.is_unchanged());
1459
1460 assert!(diff.code.is_unchanged());
1462 }
1463}