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