1use std::collections::{hash_map::Entry, HashMap};
2
3use deepsize::DeepSizeOf;
4use serde::{Deserialize, Serialize};
5use tracing::warn;
6
7use crate::{
8 keccak256,
9 models::{
10 blockchain::Transaction,
11 protocol::{ComponentBalance, ProtocolComponent},
12 Address, Balance, Chain, ChangeType, Code, CodeHash, ComponentId, ContractId,
13 ContractStore, ContractStoreDeltas, MergeError, StoreKey, TxHash,
14 },
15 Bytes,
16};
17
18#[derive(Clone, Debug, PartialEq)]
19pub struct Account {
20 pub chain: Chain,
21 pub address: Address,
22 pub title: String,
23 pub slots: ContractStore,
24 pub native_balance: Balance,
25 pub token_balances: HashMap<Address, AccountBalance>,
26 pub code: Code,
27 pub code_hash: CodeHash,
28 pub balance_modify_tx: TxHash,
29 pub code_modify_tx: TxHash,
30 pub creation_tx: Option<TxHash>,
31}
32
33impl Account {
34 #[allow(clippy::too_many_arguments)]
35 pub fn new(
36 chain: Chain,
37 address: Address,
38 title: String,
39 slots: ContractStore,
40 native_balance: Balance,
41 token_balances: HashMap<Address, AccountBalance>,
42 code: Code,
43 code_hash: CodeHash,
44 balance_modify_tx: TxHash,
45 code_modify_tx: TxHash,
46 creation_tx: Option<TxHash>,
47 ) -> Self {
48 Self {
49 chain,
50 address,
51 title,
52 slots,
53 native_balance,
54 token_balances,
55 code,
56 code_hash,
57 balance_modify_tx,
58 code_modify_tx,
59 creation_tx,
60 }
61 }
62
63 pub fn set_balance(&mut self, new_balance: &Balance, modified_at: &Balance) {
64 self.native_balance = new_balance.clone();
65 self.balance_modify_tx = modified_at.clone();
66 }
67
68 pub fn apply_delta(&mut self, delta: &AccountDelta) -> Result<(), MergeError> {
69 let self_id = (self.chain, &self.address);
70 let other_id = (delta.chain, &delta.address);
71 if self_id != other_id {
72 return Err(MergeError::IdMismatch(
73 "AccountDeltas".to_string(),
74 format!("{self_id:?}"),
75 format!("{other_id:?}"),
76 ));
77 }
78 if let Some(balance) = delta.balance.as_ref() {
79 self.native_balance.clone_from(balance);
80 }
81 if let Some(code) = delta.code.as_ref() {
82 self.code.clone_from(code);
83 }
84 self.slots.extend(
85 delta
86 .slots
87 .clone()
88 .into_iter()
89 .map(|(k, v)| (k, v.unwrap_or_default())),
90 );
91 Ok(())
93 }
94}
95
96#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default, DeepSizeOf)]
97pub struct AccountDelta {
98 pub chain: Chain,
99 pub address: Address,
100 pub slots: ContractStoreDeltas,
101 pub balance: Option<Balance>,
102 code: Option<Code>,
103 change: ChangeType,
104}
105
106impl AccountDelta {
107 pub fn deleted(chain: &Chain, address: &Address) -> Self {
108 Self {
109 chain: *chain,
110 address: address.clone(),
111 change: ChangeType::Deletion,
112 ..Default::default()
113 }
114 }
115
116 pub fn new(
117 chain: Chain,
118 address: Address,
119 slots: ContractStoreDeltas,
120 balance: Option<Balance>,
121 code: Option<Code>,
122 change: ChangeType,
123 ) -> Self {
124 if code.is_none() && matches!(change, ChangeType::Creation) {
125 warn!(?address, "Instantiated AccountDelta without code marked as creation!")
126 }
127 Self { chain, address, slots, balance, code, change }
128 }
129
130 pub fn contract_id(&self) -> ContractId {
131 ContractId::new(self.chain, self.address.clone())
132 }
133
134 pub fn into_account(self, tx: &Transaction) -> Account {
135 let empty_hash = keccak256(Vec::new());
136 Account::new(
137 self.chain,
138 self.address.clone(),
139 format!("{:#020x}", self.address),
140 self.slots
141 .into_iter()
142 .map(|(k, v)| (k, v.unwrap_or_default()))
143 .collect(),
144 self.balance.unwrap_or_default(),
145 HashMap::new(),
147 self.code.clone().unwrap_or_default(),
148 self.code
149 .as_ref()
150 .map(keccak256)
151 .unwrap_or(empty_hash)
152 .into(),
153 tx.hash.clone(),
154 tx.hash.clone(),
155 Some(tx.hash.clone()),
156 )
157 }
158
159 pub fn into_account_without_tx(self) -> Account {
162 let empty_hash = keccak256(Vec::new());
163 Account::new(
164 self.chain,
165 self.address.clone(),
166 format!("{:#020x}", self.address),
167 self.slots
168 .into_iter()
169 .map(|(k, v)| (k, v.unwrap_or_default()))
170 .collect(),
171 self.balance.unwrap_or_default(),
172 HashMap::new(),
174 self.code.clone().unwrap_or_default(),
175 self.code
176 .as_ref()
177 .map(keccak256)
178 .unwrap_or(empty_hash)
179 .into(),
180 Bytes::from("0x00"),
181 Bytes::from("0x00"),
182 None,
183 )
184 }
185
186 pub fn ref_into_account(&self, tx: &Transaction) -> Account {
188 let empty_hash = keccak256(Vec::new());
189 if self.change != ChangeType::Creation {
190 warn!("Creating an account from a partial change!")
191 }
192
193 Account::new(
194 self.chain,
195 self.address.clone(),
196 format!("{:#020x}", self.address),
197 self.slots
198 .clone()
199 .into_iter()
200 .map(|(k, v)| (k, v.unwrap_or_default()))
201 .collect(),
202 self.balance.clone().unwrap_or_default(),
203 HashMap::new(),
205 self.code.clone().unwrap_or_default(),
206 self.code
207 .as_ref()
208 .map(keccak256)
209 .unwrap_or(empty_hash)
210 .into(),
211 tx.hash.clone(),
212 tx.hash.clone(),
213 Some(tx.hash.clone()),
214 )
215 }
216
217 pub fn merge(&mut self, other: AccountDelta) -> Result<(), MergeError> {
240 if self.address != other.address {
241 return Err(MergeError::IdMismatch(
242 "AccountDelta".to_string(),
243 format!("{:#020x}", self.address),
244 format!("{:#020x}", other.address),
245 ));
246 }
247
248 self.slots.extend(other.slots);
249
250 if let Some(balance) = other.balance {
251 self.balance = Some(balance)
252 }
253 self.code = other.code.or(self.code.take());
254
255 if self.code.is_none() && matches!(self.change, ChangeType::Creation) {
256 warn!(address=?self.address, "AccountDelta without code marked as creation after merge!")
257 }
258
259 Ok(())
260 }
261
262 pub fn is_update(&self) -> bool {
263 self.change == ChangeType::Update
264 }
265
266 pub fn is_creation(&self) -> bool {
267 self.change == ChangeType::Creation
268 }
269
270 pub fn change_type(&self) -> ChangeType {
271 self.change
272 }
273
274 pub fn code(&self) -> &Option<Code> {
275 &self.code
276 }
277
278 pub fn set_code(&mut self, code: Bytes) {
279 self.code = Some(code)
280 }
281}
282
283impl From<Account> for AccountDelta {
284 fn from(value: Account) -> Self {
285 Self::new(
286 value.chain,
287 value.address,
288 value
289 .slots
290 .into_iter()
291 .map(|(k, v)| (k, Some(v)))
292 .collect(),
293 Some(value.native_balance),
294 Some(value.code),
295 ChangeType::Creation,
296 )
297 }
298}
299
300#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, DeepSizeOf)]
301pub struct AccountBalance {
302 pub account: Address,
303 pub token: Address,
304 pub balance: Balance,
305 pub modify_tx: TxHash,
306}
307
308impl AccountBalance {
309 pub fn new(account: Address, token: Address, balance: Balance, modify_tx: TxHash) -> Self {
310 Self { account, token, balance, modify_tx }
311 }
312}
313
314#[derive(Debug, Clone, PartialEq)]
316pub struct AccountChangesWithTx {
317 pub account_deltas: HashMap<Address, AccountDelta>,
319 pub protocol_components: HashMap<ComponentId, ProtocolComponent>,
321 pub component_balances: HashMap<ComponentId, HashMap<Address, ComponentBalance>>,
323 pub account_balances: HashMap<Address, HashMap<Address, AccountBalance>>,
325 pub tx: Transaction,
327}
328
329impl AccountChangesWithTx {
330 pub fn new(
331 account_deltas: HashMap<Address, AccountDelta>,
332 protocol_components: HashMap<ComponentId, ProtocolComponent>,
333 component_balances: HashMap<ComponentId, HashMap<Address, ComponentBalance>>,
334 account_balances: HashMap<Address, HashMap<Address, AccountBalance>>,
335 tx: Transaction,
336 ) -> Self {
337 Self { account_deltas, protocol_components, component_balances, account_balances, tx }
338 }
339
340 pub fn merge(&mut self, other: &AccountChangesWithTx) -> Result<(), MergeError> {
357 if self.tx.block_hash != other.tx.block_hash {
358 return Err(MergeError::BlockMismatch(
359 "AccountChangesWithTx".to_string(),
360 self.tx.block_hash.clone(),
361 other.tx.block_hash.clone(),
362 ));
363 }
364 if self.tx.hash == other.tx.hash {
365 return Err(MergeError::SameTransaction(
366 "AccountChangesWithTx".to_string(),
367 self.tx.hash.clone(),
368 ));
369 }
370 if self.tx.index > other.tx.index {
371 return Err(MergeError::TransactionOrderError(
372 "AccountChangesWithTx".to_string(),
373 self.tx.index,
374 other.tx.index,
375 ));
376 }
377
378 self.tx = other.tx.clone();
379
380 for (address, update) in other.account_deltas.clone().into_iter() {
381 match self.account_deltas.entry(address) {
382 Entry::Occupied(mut e) => {
383 e.get_mut().merge(update)?;
384 }
385 Entry::Vacant(e) => {
386 e.insert(update);
387 }
388 }
389 }
390
391 self.protocol_components
393 .extend(other.protocol_components.clone());
394
395 for (component_id, balance_by_token_map) in other
397 .component_balances
398 .clone()
399 .into_iter()
400 {
401 if let Some(existing_inner_map) = self
403 .component_balances
404 .get_mut(&component_id)
405 {
406 for (token, value) in balance_by_token_map {
408 existing_inner_map.insert(token, value);
409 }
410 } else {
411 self.component_balances
412 .insert(component_id, balance_by_token_map);
413 }
414 }
415
416 for (account_addr, balance_by_token_map) in other
418 .account_balances
419 .clone()
420 .into_iter()
421 {
422 if let Some(existing_inner_map) = self
424 .account_balances
425 .get_mut(&account_addr)
426 {
427 for (token, value) in balance_by_token_map {
429 existing_inner_map.insert(token, value);
430 }
431 } else {
432 self.account_balances
433 .insert(account_addr, balance_by_token_map);
434 }
435 }
436
437 Ok(())
438 }
439}
440
441impl From<&AccountChangesWithTx> for Vec<Account> {
442 fn from(value: &AccountChangesWithTx) -> Self {
452 value
453 .account_deltas
454 .clone()
455 .into_values()
456 .map(|update| {
457 let acc = Account::new(
458 update.chain,
459 update.address.clone(),
460 format!("{:#020x}", update.address),
461 update
462 .slots
463 .into_iter()
464 .map(|(k, v)| (k, v.unwrap_or_default())) .collect(),
466 update.balance.unwrap_or_default(),
467 value
468 .account_balances
469 .get(&update.address)
470 .cloned()
471 .unwrap_or_default(),
472 update.code.clone().unwrap_or_default(),
473 update
474 .code
475 .as_ref()
476 .map(keccak256)
477 .unwrap_or_default()
478 .into(),
479 value.tx.hash.clone(),
480 value.tx.hash.clone(),
481 Some(value.tx.hash.clone()),
482 );
483 acc
484 })
485 .collect()
486 }
487}
488
489#[derive(Debug, PartialEq, Clone, DeepSizeOf)]
490pub struct ContractStorageChange {
491 pub value: Bytes,
492 pub previous: Bytes,
493}
494
495impl ContractStorageChange {
496 pub fn new(value: impl Into<Bytes>, previous: impl Into<Bytes>) -> Self {
497 Self { value: value.into(), previous: previous.into() }
498 }
499
500 pub fn initial(value: impl Into<Bytes>) -> Self {
501 Self { value: value.into(), previous: Bytes::default() }
502 }
503}
504
505#[derive(Debug, PartialEq, Default, Clone, DeepSizeOf)]
506pub struct ContractChanges {
507 pub account: Address,
508 pub slots: HashMap<StoreKey, ContractStorageChange>,
509 pub native_balance: Option<Balance>,
510}
511
512impl ContractChanges {
513 pub fn new(
514 account: Address,
515 slots: HashMap<StoreKey, ContractStorageChange>,
516 native_balance: Option<Balance>,
517 ) -> Self {
518 Self { account, slots, native_balance }
519 }
520}
521
522pub type AccountToContractChanges = HashMap<Address, ContractChanges>;
524
525#[cfg(test)]
526mod test {
527 use std::str::FromStr;
528
529 use chrono::DateTime;
530 use rstest::rstest;
531
532 use super::*;
533 use crate::models::blockchain::fixtures as block_fixtures;
534
535 const HASH_256_0: &str = "0x0000000000000000000000000000000000000000000000000000000000000000";
536 const HASH_256_1: &str = "0x0000000000000000000000000000000000000000000000000000000000000001";
537
538 fn update_balance_delta() -> AccountDelta {
539 AccountDelta::new(
540 Chain::Ethereum,
541 Bytes::from_str("e688b84b23f322a994A53dbF8E15FA82CDB71127").unwrap(),
542 HashMap::new(),
543 Some(Bytes::from(420u64).lpad(32, 0)),
544 None,
545 ChangeType::Update,
546 )
547 }
548
549 fn update_slots_delta() -> AccountDelta {
550 AccountDelta::new(
551 Chain::Ethereum,
552 Bytes::from_str("e688b84b23f322a994A53dbF8E15FA82CDB71127").unwrap(),
553 slots([(0, 1), (1, 2)]),
554 None,
555 None,
556 ChangeType::Update,
557 )
558 }
559
560 pub fn slots(data: impl IntoIterator<Item = (u64, u64)>) -> HashMap<Bytes, Option<Bytes>> {
563 data.into_iter()
564 .map(|(s, v)| (Bytes::from(s).lpad(32, 0), Some(Bytes::from(v).lpad(32, 0))))
565 .collect()
566 }
567
568 #[test]
569 fn test_merge_account_deltas() {
570 let mut update_left = update_balance_delta();
571 let update_right = update_slots_delta();
572 let mut exp = update_slots_delta();
573 exp.balance = Some(Bytes::from(420u64).lpad(32, 0));
574
575 update_left.merge(update_right).unwrap();
576
577 assert_eq!(update_left, exp);
578 }
579
580 #[test]
581 fn test_merge_account_delta_wrong_address() {
582 let mut update_left = update_balance_delta();
583 let mut update_right = update_slots_delta();
584 update_right.address = Bytes::zero(20);
585 let exp = Err(MergeError::IdMismatch(
586 "AccountDelta".to_string(),
587 format!("{:#020x}", update_left.address),
588 format!("{:#020x}", update_right.address),
589 ));
590
591 let res = update_left.merge(update_right);
592
593 assert_eq!(res, exp);
594 }
595
596 fn tx_vm_update() -> AccountChangesWithTx {
597 let code = vec![0, 0, 0, 0];
598 let mut account_updates = HashMap::new();
599 account_updates.insert(
600 "0xe688b84b23f322a994A53dbF8E15FA82CDB71127"
601 .parse()
602 .unwrap(),
603 AccountDelta::new(
604 Chain::Ethereum,
605 Bytes::from_str("e688b84b23f322a994A53dbF8E15FA82CDB71127").unwrap(),
606 HashMap::new(),
607 Some(Bytes::from(10000u64).lpad(32, 0)),
608 Some(code.into()),
609 ChangeType::Update,
610 ),
611 );
612
613 AccountChangesWithTx::new(
614 account_updates,
615 HashMap::new(),
616 HashMap::new(),
617 HashMap::new(),
618 block_fixtures::transaction01(),
619 )
620 }
621
622 fn account() -> Account {
623 let code = vec![0, 0, 0, 0];
624 let code_hash = Bytes::from(keccak256(&code));
625 Account::new(
626 Chain::Ethereum,
627 "0xe688b84b23f322a994A53dbF8E15FA82CDB71127"
628 .parse()
629 .unwrap(),
630 "0xe688b84b23f322a994a53dbf8e15fa82cdb71127".into(),
631 HashMap::new(),
632 Bytes::from(10000u64).lpad(32, 0),
633 HashMap::new(),
634 code.into(),
635 code_hash,
636 Bytes::zero(32),
637 Bytes::zero(32),
638 Some(Bytes::zero(32)),
639 )
640 }
641
642 #[test]
643 fn test_account_from_update_w_tx() {
644 let update = tx_vm_update();
645 let exp = account();
646
647 assert_eq!(
648 update
649 .account_deltas
650 .values()
651 .next()
652 .unwrap()
653 .ref_into_account(&update.tx),
654 exp
655 );
656 }
657
658 #[rstest]
659 #[case::diff_block(
660 block_fixtures::create_transaction(HASH_256_1, HASH_256_1, 11),
661 Err(MergeError::BlockMismatch(
662 "AccountChangesWithTx".to_string(),
663 Bytes::zero(32),
664 HASH_256_1.into(),
665 ))
666 )]
667 #[case::same_tx(
668 block_fixtures::create_transaction(HASH_256_0, HASH_256_0, 11),
669 Err(MergeError::SameTransaction(
670 "AccountChangesWithTx".to_string(),
671 Bytes::zero(32),
672 ))
673 )]
674 #[case::lower_idx(
675 block_fixtures::create_transaction(HASH_256_1, HASH_256_0, 1),
676 Err(MergeError::TransactionOrderError(
677 "AccountChangesWithTx".to_string(),
678 10,
679 1,
680 ))
681 )]
682 fn test_merge_vm_updates_w_tx(#[case] tx: Transaction, #[case] exp: Result<(), MergeError>) {
683 let mut left = tx_vm_update();
684 let mut right = left.clone();
685 right.tx = tx;
686
687 let res = left.merge(&right);
688
689 assert_eq!(res, exp);
690 }
691
692 fn create_protocol_component(tx_hash: Bytes) -> ProtocolComponent {
693 ProtocolComponent {
694 id: "d417ff54652c09bd9f31f216b1a2e5d1e28c1dce1ba840c40d16f2b4d09b5902".to_owned(),
695 protocol_system: "ambient".to_string(),
696 protocol_type_name: String::from("WeightedPool"),
697 chain: Chain::Ethereum,
698 tokens: vec![
699 Bytes::from_str("0x6B175474E89094C44Da98b954EedeAC495271d0F").unwrap(),
700 Bytes::from_str("0x6B175474E89094C44Da98b954EedeAC495271d0F").unwrap(),
701 ],
702 contract_addresses: vec![
703 Bytes::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap(),
704 Bytes::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap(),
705 ],
706 static_attributes: HashMap::from([
707 ("key1".to_string(), Bytes::from(b"value1".to_vec())),
708 ("key2".to_string(), Bytes::from(b"value2".to_vec())),
709 ]),
710 change: ChangeType::Creation,
711 creation_tx: tx_hash,
712 created_at: DateTime::from_timestamp(1000, 0)
713 .unwrap()
714 .naive_utc(),
715 }
716 }
717
718 #[rstest]
719 fn test_merge_transaction_vm_updates() {
720 let tx_first_update = block_fixtures::transaction01();
721 let tx_second_update = block_fixtures::create_transaction(HASH_256_1, HASH_256_0, 15);
722 let protocol_component_first_tx = create_protocol_component(tx_first_update.hash.clone());
723 let protocol_component_second_tx = create_protocol_component(tx_second_update.hash.clone());
724 let account_address =
725 Bytes::from_str("0x0000000000000000000000000000000061626364").unwrap();
726 let token_address = Bytes::from_str("0x0000000000000000000000000000000066666666").unwrap();
727
728 let first_update = AccountChangesWithTx {
729 account_deltas: [(
730 account_address.clone(),
731 AccountDelta::new(
732 Chain::Ethereum,
733 account_address.clone(),
734 slots([(2711790500, 2981278644), (3250766788, 3520254932)]),
735 Some(Bytes::from(1903326068u64).lpad(32, 0)),
736 Some(vec![129, 130, 131, 132].into()),
737 ChangeType::Update,
738 ),
739 )]
740 .into_iter()
741 .collect(),
742 protocol_components: [(
743 protocol_component_first_tx.id.clone(),
744 protocol_component_first_tx.clone(),
745 )]
746 .into_iter()
747 .collect(),
748 component_balances: [(
749 protocol_component_first_tx.id.clone(),
750 [(
751 token_address.clone(),
752 ComponentBalance {
753 token: token_address.clone(),
754 balance: Bytes::from(0_i32.to_be_bytes()),
755 modify_tx: Default::default(),
756 component_id: protocol_component_first_tx.id.clone(),
757 balance_float: 0.0,
758 },
759 )]
760 .into_iter()
761 .collect(),
762 )]
763 .into_iter()
764 .collect(),
765 account_balances: [(
766 account_address.clone(),
767 [(
768 token_address.clone(),
769 AccountBalance {
770 token: token_address.clone(),
771 balance: Bytes::from(0_i32.to_be_bytes()),
772 modify_tx: Default::default(),
773 account: account_address.clone(),
774 },
775 )]
776 .into_iter()
777 .collect(),
778 )]
779 .into_iter()
780 .collect(),
781 tx: tx_first_update,
782 };
783 let second_update = AccountChangesWithTx {
784 account_deltas: [(
785 account_address.clone(),
786 AccountDelta::new(
787 Chain::Ethereum,
788 account_address.clone(),
789 slots([(2981278644, 3250766788), (2442302356, 2711790500)]),
790 Some(Bytes::from(4059231220u64).lpad(32, 0)),
791 Some(vec![1, 2, 3, 4].into()),
792 ChangeType::Update,
793 ),
794 )]
795 .into_iter()
796 .collect(),
797 protocol_components: [(
798 protocol_component_second_tx.id.clone(),
799 protocol_component_second_tx.clone(),
800 )]
801 .into_iter()
802 .collect(),
803 component_balances: [(
804 protocol_component_second_tx.id.clone(),
805 [(
806 token_address.clone(),
807 ComponentBalance {
808 token: token_address.clone(),
809 balance: Bytes::from(500000_i32.to_be_bytes()),
810 modify_tx: Default::default(),
811 component_id: protocol_component_first_tx.id.clone(),
812 balance_float: 500000.0,
813 },
814 )]
815 .into_iter()
816 .collect(),
817 )]
818 .into_iter()
819 .collect(),
820 account_balances: [(
821 account_address.clone(),
822 [(
823 token_address.clone(),
824 AccountBalance {
825 token: token_address,
826 balance: Bytes::from(20000_i32.to_be_bytes()),
827 modify_tx: Default::default(),
828 account: account_address,
829 },
830 )]
831 .into_iter()
832 .collect(),
833 )]
834 .into_iter()
835 .collect(),
836 tx: tx_second_update,
837 };
838
839 let mut to_merge_on = first_update.clone();
841 to_merge_on
842 .merge(&second_update)
843 .unwrap();
844
845 let expected_protocol_components: HashMap<ComponentId, ProtocolComponent> = [
847 (protocol_component_first_tx.id.clone(), protocol_component_first_tx.clone()),
848 (protocol_component_second_tx.id.clone(), protocol_component_second_tx.clone()),
849 ]
850 .into_iter()
851 .collect();
852 assert_eq!(to_merge_on.component_balances, second_update.component_balances);
853 assert_eq!(to_merge_on.account_balances, second_update.account_balances);
854 assert_eq!(to_merge_on.protocol_components, expected_protocol_components);
855
856 let mut acc_update = second_update
857 .account_deltas
858 .clone()
859 .into_values()
860 .next()
861 .unwrap();
862
863 acc_update.slots = slots([
864 (2442302356, 2711790500),
865 (2711790500, 2981278644),
866 (3250766788, 3520254932),
867 (2981278644, 3250766788),
868 ]);
869
870 let acc_update = [(acc_update.address.clone(), acc_update)]
871 .iter()
872 .cloned()
873 .collect();
874
875 assert_eq!(to_merge_on.account_deltas, acc_update);
876 }
877}