1use super::super::EthBigInt;
10use super::super::types::{EthAddress, EthHash};
11use super::super::utils::{ActorStateEthExt as _, EvmStorageKamt, evm_kamt_config, live_evm_state};
12use super::types::{AccountDiff, ChangedType, Delta, StateDiff};
13use super::utils::{ZERO_HASH, u256_to_eth_hash};
14use crate::prelude::*;
15use crate::shim::actors::evm::U256;
16use crate::shim::actors::is_evm_actor;
17use crate::shim::state_tree::{ActorState, StateTree};
18use ahash::{HashMap, HashSet};
19use std::collections::BTreeMap;
20use tracing::debug;
21
22pub fn build_state_diff<S: Blockstore, T: Blockstore>(
24 store: &S,
25 pre_state: &StateTree<T>,
26 post_state: &StateTree<T>,
27 touched_addresses: &HashSet<EthAddress>,
28) -> anyhow::Result<StateDiff> {
29 let mut state_diff = StateDiff::new();
30
31 for eth_addr in touched_addresses {
32 let fil_addr = eth_addr.to_filecoin_address()?;
33
34 let pre_actor = pre_state
36 .get_actor(&fil_addr)
37 .context("failed to get actor state")?;
38
39 let post_actor = post_state
40 .get_actor(&fil_addr)
41 .context("failed to get actor state")?;
42
43 let account_diff = build_account_diff(store, pre_actor.as_ref(), post_actor.as_ref())?;
44
45 state_diff.insert_if_changed(*eth_addr, account_diff);
47 }
48
49 Ok(state_diff)
50}
51
52fn build_account_diff<DB: Blockstore>(
54 store: &DB,
55 pre_actor: Option<&ActorState>,
56 post_actor: Option<&ActorState>,
57) -> anyhow::Result<AccountDiff> {
58 let mut diff = AccountDiff::default();
59
60 let pre_balance = pre_actor.map(|a| EthBigInt::from(a.balance.atto()));
62 let post_balance = post_actor.map(|a| EthBigInt::from(a.balance.atto()));
63 diff.balance = Delta::from_comparison(pre_balance, post_balance);
64
65 let pre_nonce = pre_actor.map(|a| a.eth_nonce(store)).transpose()?;
67 let post_nonce = post_actor.map(|a| a.eth_nonce(store)).transpose()?;
68 diff.nonce = Delta::from_comparison(pre_nonce, post_nonce);
69
70 let pre_code = pre_actor
72 .map(|a| a.eth_bytecode(store))
73 .transpose()?
74 .flatten();
75 let post_code = post_actor
76 .map(|a| a.eth_bytecode(store))
77 .transpose()?
78 .flatten();
79 diff.code = Delta::from_comparison(pre_code, post_code);
80
81 diff.storage = diff_evm_storage_for_actors(store, pre_actor, post_actor)?;
83
84 Ok(diff)
85}
86
87fn diff_evm_storage_for_actors<DB: Blockstore>(
97 store: &DB,
98 pre_actor: Option<&ActorState>,
99 post_actor: Option<&ActorState>,
100) -> anyhow::Result<BTreeMap<EthHash, Delta<EthHash>>> {
101 let pre_is_evm = pre_actor.is_some_and(|a| is_evm_actor(&a.code));
102 let post_is_evm = post_actor.is_some_and(|a| is_evm_actor(&a.code));
103
104 let pre_entries = extract_evm_storage_entries(store, pre_actor);
106 let post_entries = extract_evm_storage_entries(store, post_actor);
107
108 if pre_entries.is_empty() && post_entries.is_empty() {
110 return Ok(BTreeMap::new());
111 }
112
113 let mut diff = BTreeMap::new();
114
115 match (pre_is_evm, post_is_evm) {
116 (false, true) => {
117 for (key_bytes, value) in &post_entries {
118 let key_hash = EthHash(ethereum_types::H256(*key_bytes));
119 diff.insert(key_hash, Delta::Added(u256_to_eth_hash(value)));
120 }
121 }
122 (true, false) => {
123 for (key_bytes, value) in &pre_entries {
124 let key_hash = EthHash(ethereum_types::H256(*key_bytes));
125 diff.insert(key_hash, Delta::Removed(u256_to_eth_hash(value)));
126 }
127 }
128 (true, true) => {
129 for (key_bytes, pre_value) in &pre_entries {
130 let key_hash = EthHash(ethereum_types::H256(*key_bytes));
131 let pre_hash = u256_to_eth_hash(pre_value);
132
133 match post_entries.get(key_bytes) {
134 Some(post_value) if pre_value != post_value => {
135 diff.insert(
137 key_hash,
138 Delta::Changed(ChangedType {
139 from: pre_hash,
140 to: u256_to_eth_hash(post_value),
141 }),
142 );
143 }
144 Some(_) => {
145 }
147 None => {
148 diff.insert(
150 key_hash,
151 Delta::Changed(ChangedType {
152 from: pre_hash,
153 to: ZERO_HASH,
154 }),
155 );
156 }
157 }
158 }
159
160 for (key_bytes, post_value) in &post_entries {
162 if !pre_entries.contains_key(key_bytes) {
163 let key_hash = EthHash(ethereum_types::H256(*key_bytes));
164 diff.insert(
165 key_hash,
166 Delta::Changed(ChangedType {
167 from: ZERO_HASH,
168 to: u256_to_eth_hash(post_value),
169 }),
170 );
171 }
172 }
173 }
174 (false, false) => {}
176 }
177
178 Ok(diff)
179}
180
181pub fn extract_evm_storage_entries<DB: Blockstore>(
185 store: &DB,
186 actor: Option<&ActorState>,
187) -> HashMap<[u8; 32], U256> {
188 let Some(actor) = actor else {
189 return HashMap::default();
190 };
191 let evm_state = match live_evm_state(store, actor) {
192 Ok(Some(state)) => state,
193 Ok(None) => return HashMap::default(),
195 Err(e) => {
196 debug!("failed to load EVM state for storage extraction: {e:#}");
197 return HashMap::default();
198 }
199 };
200
201 let storage_cid = evm_state.contract_state();
202 let config = evm_kamt_config();
203
204 let kamt = match EvmStorageKamt::load_with_config(&storage_cid, store, config) {
205 Ok(k) => k,
206 Err(e) => {
207 debug!("failed to load storage KAMT: {e}");
208 return HashMap::default();
209 }
210 };
211
212 let mut entries = HashMap::default();
213 if let Err(e) = kamt.for_each(|key, value| {
214 entries.insert(key.to_big_endian(), *value);
215 Ok(())
216 }) {
217 debug!("failed to iterate storage KAMT: {e}");
218 return HashMap::default();
219 }
220
221 entries
222}
223
224pub fn diff_entry_keys(
226 pre_entries: &HashMap<[u8; 32], U256>,
227 post_entries: &HashMap<[u8; 32], U256>,
228) -> HashSet<[u8; 32]> {
229 let mut changed = HashSet::default();
230
231 for (k, v) in pre_entries {
232 match post_entries.get(k) {
233 Some(pv) if pv == v => {} _ => {
235 changed.insert(*k);
236 }
237 }
238 }
239
240 for k in post_entries.keys() {
241 if !pre_entries.contains_key(k) {
242 changed.insert(*k);
243 }
244 }
245
246 changed
247}
248
249#[cfg(test)]
250mod tests {
251 use super::super::test_helpers::*;
252 use super::*;
253 use crate::db::MemoryDB;
254 use crate::rpc::eth::EthUint64;
255 use crate::rpc::eth::types::EthBytes;
256 use crate::shim::address::Address as FilecoinAddress;
257 use crate::shim::econ::TokenAmount;
258 use crate::shim::state_tree::StateTreeVersion;
259
260 #[test]
261 fn test_build_state_diff_empty_touched_addresses() {
262 let trees = TestStateTrees::new().unwrap();
263 let touched_addresses = HashSet::new();
264
265 let state_diff = trees.build_diff(&touched_addresses).unwrap();
266
267 assert!(state_diff.0.is_empty());
269 }
270
271 #[test]
272 fn test_build_state_diff_nonexistent_address() {
273 let trees = TestStateTrees::new().unwrap();
274 let mut touched_addresses = HashSet::new();
275 touched_addresses.insert(create_masked_id_eth_address(9999));
276
277 let state_diff = trees.build_diff(&touched_addresses).unwrap();
278
279 assert!(state_diff.0.is_empty());
281 }
282
283 #[test]
284 fn test_build_state_diff_balance_increase() {
285 let actor_id = 1001u64;
286 let pre_actor = create_test_actor(1000, 5);
287 let post_actor = create_test_actor(2000, 5);
288 let trees = TestStateTrees::with_changed_actor(actor_id, pre_actor, post_actor).unwrap();
289
290 let mut touched_addresses = HashSet::new();
291 touched_addresses.insert(create_masked_id_eth_address(actor_id));
292
293 let state_diff = trees.build_diff(&touched_addresses).unwrap();
294
295 assert_eq!(state_diff.0.len(), 1);
296 let eth_addr = create_masked_id_eth_address(actor_id);
297 let diff = state_diff.0.get(ð_addr).unwrap();
298 match &diff.balance {
299 Delta::Changed(change) => {
300 assert_eq!(change.from, EthBigInt::from(1000));
301 assert_eq!(change.to, EthBigInt::from(2000));
302 }
303 _ => panic!("Expected Delta::Changed for balance"),
304 }
305 assert!(diff.nonce.is_unchanged());
306 }
307
308 #[test]
309 fn test_build_state_diff_balance_decrease() {
310 let actor_id = 1002u64;
311 let pre_actor = create_test_actor(5000, 10);
312 let post_actor = create_test_actor(3000, 10);
313 let trees = TestStateTrees::with_changed_actor(actor_id, pre_actor, post_actor).unwrap();
314
315 let mut touched_addresses = HashSet::new();
316 touched_addresses.insert(create_masked_id_eth_address(actor_id));
317
318 let state_diff = trees.build_diff(&touched_addresses).unwrap();
319
320 let eth_addr = create_masked_id_eth_address(actor_id);
321 let diff = state_diff.0.get(ð_addr).unwrap();
322 match &diff.balance {
323 Delta::Changed(change) => {
324 assert_eq!(change.from, EthBigInt::from(5000));
325 assert_eq!(change.to, EthBigInt::from(3000));
326 }
327 _ => panic!("Expected Delta::Changed for balance"),
328 }
329 assert!(diff.nonce.is_unchanged());
330 }
331
332 #[test]
333 fn test_build_state_diff_nonce_increment() {
334 let actor_id = 1003u64;
335 let pre_actor = create_test_actor(1000, 5);
336 let post_actor = create_test_actor(1000, 6);
337 let trees = TestStateTrees::with_changed_actor(actor_id, pre_actor, post_actor).unwrap();
338
339 let mut touched_addresses = HashSet::new();
340 touched_addresses.insert(create_masked_id_eth_address(actor_id));
341
342 let state_diff = trees.build_diff(&touched_addresses).unwrap();
343
344 let eth_addr = create_masked_id_eth_address(actor_id);
345 let diff = state_diff.0.get(ð_addr).unwrap();
346 assert!(diff.balance.is_unchanged());
347 match &diff.nonce {
348 Delta::Changed(change) => {
349 assert_eq!(change.from.0, 5);
350 assert_eq!(change.to.0, 6);
351 }
352 _ => panic!("Expected Delta::Changed for nonce"),
353 }
354 }
355
356 #[test]
357 fn test_build_state_diff_both_balance_and_nonce_change() {
358 let actor_id = 1004u64;
359 let pre_actor = create_test_actor(10000, 100);
360 let post_actor = create_test_actor(9000, 101);
361 let trees = TestStateTrees::with_changed_actor(actor_id, pre_actor, post_actor).unwrap();
362
363 let mut touched_addresses = HashSet::new();
364 touched_addresses.insert(create_masked_id_eth_address(actor_id));
365
366 let state_diff = trees.build_diff(&touched_addresses).unwrap();
367
368 let eth_addr = create_masked_id_eth_address(actor_id);
369 let diff = state_diff.0.get(ð_addr).unwrap();
370 match &diff.balance {
371 Delta::Changed(change) => {
372 assert_eq!(change.from, EthBigInt::from(10000));
373 assert_eq!(change.to, EthBigInt::from(9000));
374 }
375 _ => panic!("Expected Delta::Changed for balance"),
376 }
377 match &diff.nonce {
378 Delta::Changed(change) => {
379 assert_eq!(change.from.0, 100);
380 assert_eq!(change.to.0, 101);
381 }
382 _ => panic!("Expected Delta::Changed for nonce"),
383 }
384 }
385
386 #[test]
387 fn test_build_state_diff_account_creation() {
388 let actor_id = 1005u64;
389 let post_actor = create_test_actor(5000, 0);
390 let trees = TestStateTrees::with_created_actor(actor_id, post_actor).unwrap();
391
392 let mut touched_addresses = HashSet::new();
393 touched_addresses.insert(create_masked_id_eth_address(actor_id));
394
395 let state_diff = trees.build_diff(&touched_addresses).unwrap();
396
397 let eth_addr = create_masked_id_eth_address(actor_id);
398 let diff = state_diff.0.get(ð_addr).unwrap();
399 match &diff.balance {
400 Delta::Added(balance) => {
401 assert_eq!(balance, &EthBigInt::from(5000));
402 }
403 _ => panic!("Expected Delta::Added for balance"),
404 }
405 match &diff.nonce {
406 Delta::Added(nonce) => {
407 assert_eq!(nonce.0, 0);
408 }
409 _ => panic!("Expected Delta::Added for nonce"),
410 }
411 }
412
413 #[test]
414 fn test_build_state_diff_account_deletion() {
415 let actor_id = 1006u64;
416 let pre_actor = create_test_actor(3000, 10);
417 let trees = TestStateTrees::with_deleted_actor(actor_id, pre_actor).unwrap();
418
419 let mut touched_addresses = HashSet::new();
420 touched_addresses.insert(create_masked_id_eth_address(actor_id));
421
422 let state_diff = trees.build_diff(&touched_addresses).unwrap();
423
424 let eth_addr = create_masked_id_eth_address(actor_id);
425 let diff = state_diff.0.get(ð_addr).unwrap();
426 match &diff.balance {
427 Delta::Removed(balance) => {
428 assert_eq!(balance, &EthBigInt::from(3000));
429 }
430 _ => panic!("Expected Delta::Removed for balance"),
431 }
432 match &diff.nonce {
433 Delta::Removed(nonce) => {
434 assert_eq!(nonce.0, 10);
435 }
436 _ => panic!("Expected Delta::Removed for nonce"),
437 }
438 }
439
440 #[test]
441 fn test_build_state_diff_multiple_addresses() {
442 let store = Arc::new(MemoryDB::default());
443 let mut pre_state = StateTree::new(&store, StateTreeVersion::V5).unwrap();
444 let mut post_state = StateTree::new(&store, StateTreeVersion::V5).unwrap();
445
446 let addr1 = FilecoinAddress::new_id(2001);
448 pre_state
449 .set_actor(&addr1, create_test_actor(1000, 0))
450 .unwrap();
451 post_state
452 .set_actor(&addr1, create_test_actor(2000, 0))
453 .unwrap();
454
455 let addr2 = FilecoinAddress::new_id(2002);
457 pre_state
458 .set_actor(&addr2, create_test_actor(500, 5))
459 .unwrap();
460 post_state
461 .set_actor(&addr2, create_test_actor(500, 6))
462 .unwrap();
463
464 let addr3 = FilecoinAddress::new_id(2003);
466 pre_state
467 .set_actor(&addr3, create_test_actor(100, 1))
468 .unwrap();
469 post_state
470 .set_actor(&addr3, create_test_actor(100, 1))
471 .unwrap();
472
473 let mut touched_addresses = HashSet::new();
474 touched_addresses.insert(create_masked_id_eth_address(2001));
475 touched_addresses.insert(create_masked_id_eth_address(2002));
476 touched_addresses.insert(create_masked_id_eth_address(2003));
477
478 let state_diff =
479 build_state_diff(store.as_ref(), &pre_state, &post_state, &touched_addresses).unwrap();
480
481 assert_eq!(state_diff.0.len(), 2);
482 assert!(
483 state_diff
484 .0
485 .contains_key(&create_masked_id_eth_address(2001))
486 );
487 assert!(
488 state_diff
489 .0
490 .contains_key(&create_masked_id_eth_address(2002))
491 );
492 assert!(
493 !state_diff
494 .0
495 .contains_key(&create_masked_id_eth_address(2003))
496 );
497 }
498
499 #[test]
500 fn test_build_state_diff_evm_actor_scenarios() {
501 struct TestCase {
502 name: &'static str,
503 pre: Option<(u64, u64, Option<&'static [u8]>)>, post: Option<(u64, u64, Option<&'static [u8]>)>,
505 expected_balance: Delta<EthBigInt>,
506 expected_nonce: Delta<EthUint64>,
507 expected_code: Delta<EthBytes>,
508 }
509
510 let bytecode1: &[u8] = &[0x60, 0x80, 0x60, 0x40, 0x52];
511 let bytecode2: &[u8] = &[0x60, 0x80, 0x60, 0x40, 0x52, 0x00];
512
513 let cases = vec![
514 TestCase {
515 name: "No change",
516 pre: Some((1000, 5, Some(bytecode1))),
517 post: Some((1000, 5, Some(bytecode1))),
518 expected_balance: Delta::Unchanged,
519 expected_nonce: Delta::Unchanged,
520 expected_code: Delta::Unchanged,
521 },
522 TestCase {
523 name: "Balance increase",
524 pre: Some((1000, 5, Some(bytecode1))),
525 post: Some((2000, 5, Some(bytecode1))),
526 expected_balance: Delta::Changed(ChangedType {
527 from: EthBigInt::from(1000),
528 to: EthBigInt::from(2000),
529 }),
530 expected_nonce: Delta::Unchanged,
531 expected_code: Delta::Unchanged,
532 },
533 TestCase {
534 name: "Nonce increment",
535 pre: Some((1000, 5, Some(bytecode1))),
536 post: Some((1000, 6, Some(bytecode1))),
537 expected_balance: Delta::Unchanged,
538 expected_nonce: Delta::Changed(ChangedType {
539 from: EthUint64(5),
540 to: EthUint64(6),
541 }),
542 expected_code: Delta::Unchanged,
543 },
544 TestCase {
545 name: "Bytecode change",
546 pre: Some((1000, 5, Some(bytecode1))),
547 post: Some((1000, 5, Some(bytecode2))),
548 expected_balance: Delta::Unchanged,
549 expected_nonce: Delta::Unchanged,
550 expected_code: Delta::Changed(ChangedType {
551 from: EthBytes(bytecode1.to_vec()),
552 to: EthBytes(bytecode2.to_vec()),
553 }),
554 },
555 TestCase {
556 name: "Balance and Nonce change",
557 pre: Some((1000, 5, Some(bytecode1))),
558 post: Some((2000, 6, Some(bytecode1))),
559 expected_balance: Delta::Changed(ChangedType {
560 from: EthBigInt::from(1000),
561 to: EthBigInt::from(2000),
562 }),
563 expected_nonce: Delta::Changed(ChangedType {
564 from: EthUint64(5),
565 to: EthUint64(6),
566 }),
567 expected_code: Delta::Unchanged,
568 },
569 TestCase {
570 name: "Creation",
571 pre: None,
572 post: Some((5000, 0, Some(bytecode1))),
573 expected_balance: Delta::Added(EthBigInt::from(5000)),
574 expected_nonce: Delta::Added(EthUint64(0)),
575 expected_code: Delta::Added(EthBytes(bytecode1.to_vec())),
576 },
577 TestCase {
578 name: "Deletion",
579 pre: Some((3000, 10, Some(bytecode1))),
580 post: None,
581 expected_balance: Delta::Removed(EthBigInt::from(3000)),
582 expected_nonce: Delta::Removed(EthUint64(10)),
583 expected_code: Delta::Removed(EthBytes(bytecode1.to_vec())),
584 },
585 ];
586
587 for case in cases {
588 let store = Arc::new(MemoryDB::default());
589 let actor_id = 10000u64; let pre_actor = case.pre.and_then(|(bal, nonce, code)| {
592 create_evm_actor_with_bytecode(&store, bal, 0, nonce, code)
593 });
594 let post_actor = case.post.and_then(|(bal, nonce, code)| {
595 create_evm_actor_with_bytecode(&store, bal, 0, nonce, code)
596 });
597
598 let mut pre_state = StateTree::new(&store, StateTreeVersion::V5).unwrap();
599 let mut post_state = StateTree::new(&store, StateTreeVersion::V5).unwrap();
600 let addr = FilecoinAddress::new_id(actor_id);
601
602 if let Some(actor) = pre_actor {
603 pre_state.set_actor(&addr, actor).unwrap();
604 }
605 if let Some(actor) = post_actor {
606 post_state.set_actor(&addr, actor).unwrap();
607 }
608
609 let mut touched_addresses = HashSet::new();
610 touched_addresses.insert(create_masked_id_eth_address(actor_id));
611
612 let state_diff =
613 build_state_diff(store.as_ref(), &pre_state, &post_state, &touched_addresses)
614 .unwrap();
615
616 if case.expected_balance == Delta::Unchanged
617 && case.expected_nonce == Delta::Unchanged
618 && case.expected_code == Delta::Unchanged
619 {
620 assert!(
621 state_diff.0.is_empty(),
622 "Test case '{}' failed: expected empty diff",
623 case.name
624 );
625 } else {
626 let eth_addr = create_masked_id_eth_address(actor_id);
627 let diff = state_diff.0.get(ð_addr).unwrap_or_else(|| {
628 panic!("Test case '{}' failed: missing diff entry", case.name)
629 });
630
631 assert_eq!(
632 diff.balance, case.expected_balance,
633 "Test case '{}' failed: balance mismatch",
634 case.name
635 );
636 assert_eq!(
637 diff.nonce, case.expected_nonce,
638 "Test case '{}' failed: nonce mismatch",
639 case.name
640 );
641 assert_eq!(
642 diff.code, case.expected_code,
643 "Test case '{}' failed: code mismatch",
644 case.name
645 );
646 }
647 }
648 }
649
650 #[test]
651 fn test_build_state_diff_non_evm_actor_no_code() {
652 let actor_id = 4005u64;
654 let pre_actor = create_test_actor(1000, 5);
655 let post_actor = create_test_actor(2000, 6);
656 let trees = TestStateTrees::with_changed_actor(actor_id, pre_actor, post_actor).unwrap();
657
658 let mut touched_addresses = HashSet::new();
659 touched_addresses.insert(create_masked_id_eth_address(actor_id));
660
661 let state_diff = trees.build_diff(&touched_addresses).unwrap();
662
663 let eth_addr = create_masked_id_eth_address(actor_id);
664 let diff = state_diff.0.get(ð_addr).unwrap();
665
666 assert!(!diff.balance.is_unchanged());
668 assert!(!diff.nonce.is_unchanged());
669
670 assert!(diff.code.is_unchanged());
672 }
673
674 #[test]
675 fn test_actor_nonce_non_evm() {
676 let store = MemoryDB::default();
677 let actor = create_test_actor(1000, 42);
678 let nonce = actor.eth_nonce(&store).unwrap();
679 assert_eq!(nonce.0, 42);
680 }
681
682 #[test]
683 fn test_actor_nonce_evm() {
684 let store = Arc::new(MemoryDB::default());
685 let actor = create_evm_actor_with_bytecode(&store, 1000, 0, 7, Some(&[0x60]))
686 .expect("failed to create EVM actor fixture");
687 let nonce = actor.eth_nonce(store.as_ref()).unwrap();
688 assert_eq!(nonce.0, 7);
690 }
691
692 #[test]
693 fn test_actor_bytecode_non_evm() {
694 let store = MemoryDB::default();
695 let actor = create_test_actor(1000, 0);
696 assert!(actor.eth_bytecode(&store).unwrap().is_none());
697 }
698
699 #[test]
700 fn test_actor_bytecode_evm() {
701 let store = Arc::new(MemoryDB::default());
702 let bytecode = &[0x60, 0x80, 0x60, 0x40, 0x52];
703 let actor = create_evm_actor_with_bytecode(&store, 1000, 0, 1, Some(bytecode))
704 .expect("failed to create EVM actor fixture");
705 let result = actor.eth_bytecode(store.as_ref()).unwrap();
706 assert_eq!(result, Some(EthBytes(bytecode.to_vec())));
707 }
708
709 #[test]
710 fn test_actor_bytecode_evm_no_bytecode() {
711 let store = Arc::new(MemoryDB::default());
712 let actor = create_evm_actor_with_bytecode(&store, 1000, 0, 1, None)
713 .expect("failed to create EVM actor fixture");
714 let result = actor.eth_bytecode(store.as_ref()).unwrap();
716 assert!(result.is_none());
717 }
718
719 #[test]
720 fn test_actor_bytecode_evm_tombstoned() {
721 let store = Arc::new(MemoryDB::default());
722 let actor =
723 create_tombstoned_evm_actor(&store, &[0x60, 0x80, 0x60, 0x40, 0x52], 0, Cid::default())
724 .expect("failed to create tombstoned EVM actor fixture");
725 assert!(actor.eth_bytecode(store.as_ref()).unwrap().is_none());
728 }
729
730 fn storage_with_slot(store: &MemoryDB, slot: u64, value: u64) -> Cid {
732 let mut kamt = EvmStorageKamt::new_with_config(store, evm_kamt_config());
733 kamt.set(U256::from(slot), U256::from(value)).unwrap();
734 kamt.flush().unwrap()
735 }
736
737 #[test]
740 fn test_actor_nonce_evm_tombstoned() {
741 let store = MemoryDB::default();
742 let alive = create_evm_actor_with_bytecode(&store, 0, 0, 7, Some(&[0x60]))
744 .expect("failed to create EVM actor fixture");
745 assert_eq!(alive.eth_nonce(&store).unwrap().0, 7);
746
747 let mut dead = create_tombstoned_evm_actor(&store, &[0x60], 7, Cid::default())
748 .expect("failed to create tombstoned EVM actor fixture");
749 dead.sequence = 9;
751 assert_eq!(dead.eth_nonce(&store).unwrap().0, 0);
752 }
753
754 #[test]
756 fn test_extract_evm_storage_entries_tombstoned() {
757 let store = MemoryDB::default();
758 let contract_state = storage_with_slot(&store, 5, 42);
759
760 let alive = create_evm_actor_with_storage(&store, contract_state)
762 .expect("failed to create EVM actor fixture");
763 assert_eq!(
764 extract_evm_storage_entries(&store, Some(&alive)).len(),
765 1,
766 "live contract must expose its slots"
767 );
768
769 let dead = create_tombstoned_evm_actor(&store, &[0x60], 0, contract_state)
770 .expect("failed to create tombstoned EVM actor fixture");
771 assert!(
772 extract_evm_storage_entries(&store, Some(&dead)).is_empty(),
773 "self-destructed contract must read as empty storage"
774 );
775 }
776
777 #[test]
779 fn test_extract_evm_storage_entries_absent_or_unloadable() {
780 let store = MemoryDB::default();
781 assert!(extract_evm_storage_entries(&store, None).is_empty());
782
783 let unloadable = ActorState::new(
785 get_evm_actor_code_cid().expect("bundled EVM actor code CID"),
786 Cid::default(),
787 TokenAmount::default(),
788 0,
789 None,
790 );
791 assert!(
792 extract_evm_storage_entries(&store, Some(&unloadable)).is_empty(),
793 "unloadable EVM state must read as empty storage"
794 );
795 }
796
797 #[test]
800 fn test_diff_evm_storage_reports_slots_cleared_on_selfdestruct() {
801 let store = MemoryDB::default();
802 let contract_state = storage_with_slot(&store, 5, 42);
803 let pre = create_evm_actor_with_storage(&store, contract_state)
804 .expect("failed to create EVM actor fixture");
805 let post = create_tombstoned_evm_actor(&store, &[0x60], 0, contract_state)
806 .expect("failed to create tombstoned EVM actor fixture");
807
808 let diff = diff_evm_storage_for_actors(&store, Some(&pre), Some(&post)).unwrap();
809 let slot = u256_to_eth_hash(&U256::from(5u64));
810 assert_eq!(
811 diff.get(&slot),
812 Some(&Delta::Changed(ChangedType {
813 from: u256_to_eth_hash(&U256::from(42u64)),
814 to: ZERO_HASH,
815 })),
816 "destroyed contract's slot must be reported as cleared"
817 );
818 }
819
820 #[test]
821 fn test_diff_entry_keys_both_empty() {
822 let pre = HashMap::default();
823 let post = HashMap::default();
824 let keys = diff_entry_keys(&pre, &post);
825 assert!(keys.is_empty());
826 }
827
828 #[test]
829 fn test_diff_entry_keys_non_evm_actors() {
830 let store = MemoryDB::default();
831 let pre = create_test_actor(1000, 5);
832 let post = create_test_actor(2000, 6);
833 let pre_entries = extract_evm_storage_entries(&store, Some(&pre));
835 let post_entries = extract_evm_storage_entries(&store, Some(&post));
836 let keys = diff_entry_keys(&pre_entries, &post_entries);
837 assert!(keys.is_empty());
838 }
839
840 #[test]
841 fn test_diff_entry_keys_pre_none_post_non_evm() {
842 let store = MemoryDB::default();
843 let post = create_test_actor(1000, 0);
844 let pre_entries = extract_evm_storage_entries(&store, None);
845 let post_entries = extract_evm_storage_entries(&store, Some(&post));
846 let keys = diff_entry_keys(&pre_entries, &post_entries);
847 assert!(keys.is_empty());
848 }
849}