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
504pub type AccountToContractChange = HashMap<Address, HashMap<StoreKey, ContractStorageChange>>;
506
507#[cfg(test)]
508mod test {
509 use std::str::FromStr;
510
511 use chrono::DateTime;
512 use rstest::rstest;
513
514 use super::*;
515 use crate::models::blockchain::fixtures as block_fixtures;
516
517 const HASH_256_0: &str = "0x0000000000000000000000000000000000000000000000000000000000000000";
518 const HASH_256_1: &str = "0x0000000000000000000000000000000000000000000000000000000000000001";
519
520 fn update_balance_delta() -> AccountDelta {
521 AccountDelta::new(
522 Chain::Ethereum,
523 Bytes::from_str("e688b84b23f322a994A53dbF8E15FA82CDB71127").unwrap(),
524 HashMap::new(),
525 Some(Bytes::from(420u64).lpad(32, 0)),
526 None,
527 ChangeType::Update,
528 )
529 }
530
531 fn update_slots_delta() -> AccountDelta {
532 AccountDelta::new(
533 Chain::Ethereum,
534 Bytes::from_str("e688b84b23f322a994A53dbF8E15FA82CDB71127").unwrap(),
535 slots([(0, 1), (1, 2)]),
536 None,
537 None,
538 ChangeType::Update,
539 )
540 }
541
542 pub fn slots(data: impl IntoIterator<Item = (u64, u64)>) -> HashMap<Bytes, Option<Bytes>> {
545 data.into_iter()
546 .map(|(s, v)| (Bytes::from(s).lpad(32, 0), Some(Bytes::from(v).lpad(32, 0))))
547 .collect()
548 }
549
550 #[test]
551 fn test_merge_account_deltas() {
552 let mut update_left = update_balance_delta();
553 let update_right = update_slots_delta();
554 let mut exp = update_slots_delta();
555 exp.balance = Some(Bytes::from(420u64).lpad(32, 0));
556
557 update_left.merge(update_right).unwrap();
558
559 assert_eq!(update_left, exp);
560 }
561
562 #[test]
563 fn test_merge_account_delta_wrong_address() {
564 let mut update_left = update_balance_delta();
565 let mut update_right = update_slots_delta();
566 update_right.address = Bytes::zero(20);
567 let exp = Err(MergeError::IdMismatch(
568 "AccountDelta".to_string(),
569 format!("{:#020x}", update_left.address),
570 format!("{:#020x}", update_right.address),
571 ));
572
573 let res = update_left.merge(update_right);
574
575 assert_eq!(res, exp);
576 }
577
578 fn tx_vm_update() -> AccountChangesWithTx {
579 let code = vec![0, 0, 0, 0];
580 let mut account_updates = HashMap::new();
581 account_updates.insert(
582 "0xe688b84b23f322a994A53dbF8E15FA82CDB71127"
583 .parse()
584 .unwrap(),
585 AccountDelta::new(
586 Chain::Ethereum,
587 Bytes::from_str("e688b84b23f322a994A53dbF8E15FA82CDB71127").unwrap(),
588 HashMap::new(),
589 Some(Bytes::from(10000u64).lpad(32, 0)),
590 Some(code.into()),
591 ChangeType::Update,
592 ),
593 );
594
595 AccountChangesWithTx::new(
596 account_updates,
597 HashMap::new(),
598 HashMap::new(),
599 HashMap::new(),
600 block_fixtures::transaction01(),
601 )
602 }
603
604 fn account() -> Account {
605 let code = vec![0, 0, 0, 0];
606 let code_hash = Bytes::from(keccak256(&code));
607 Account::new(
608 Chain::Ethereum,
609 "0xe688b84b23f322a994A53dbF8E15FA82CDB71127"
610 .parse()
611 .unwrap(),
612 "0xe688b84b23f322a994a53dbf8e15fa82cdb71127".into(),
613 HashMap::new(),
614 Bytes::from(10000u64).lpad(32, 0),
615 HashMap::new(),
616 code.into(),
617 code_hash,
618 Bytes::zero(32),
619 Bytes::zero(32),
620 Some(Bytes::zero(32)),
621 )
622 }
623
624 #[test]
625 fn test_account_from_update_w_tx() {
626 let update = tx_vm_update();
627 let exp = account();
628
629 assert_eq!(
630 update
631 .account_deltas
632 .values()
633 .next()
634 .unwrap()
635 .ref_into_account(&update.tx),
636 exp
637 );
638 }
639
640 #[rstest]
641 #[case::diff_block(
642 block_fixtures::create_transaction(HASH_256_1, HASH_256_1, 11),
643 Err(MergeError::BlockMismatch(
644 "AccountChangesWithTx".to_string(),
645 Bytes::zero(32),
646 HASH_256_1.into(),
647 ))
648 )]
649 #[case::same_tx(
650 block_fixtures::create_transaction(HASH_256_0, HASH_256_0, 11),
651 Err(MergeError::SameTransaction(
652 "AccountChangesWithTx".to_string(),
653 Bytes::zero(32),
654 ))
655 )]
656 #[case::lower_idx(
657 block_fixtures::create_transaction(HASH_256_1, HASH_256_0, 1),
658 Err(MergeError::TransactionOrderError(
659 "AccountChangesWithTx".to_string(),
660 10,
661 1,
662 ))
663 )]
664 fn test_merge_vm_updates_w_tx(#[case] tx: Transaction, #[case] exp: Result<(), MergeError>) {
665 let mut left = tx_vm_update();
666 let mut right = left.clone();
667 right.tx = tx;
668
669 let res = left.merge(&right);
670
671 assert_eq!(res, exp);
672 }
673
674 fn create_protocol_component(tx_hash: Bytes) -> ProtocolComponent {
675 ProtocolComponent {
676 id: "d417ff54652c09bd9f31f216b1a2e5d1e28c1dce1ba840c40d16f2b4d09b5902".to_owned(),
677 protocol_system: "ambient".to_string(),
678 protocol_type_name: String::from("WeightedPool"),
679 chain: Chain::Ethereum,
680 tokens: vec![
681 Bytes::from_str("0x6B175474E89094C44Da98b954EedeAC495271d0F").unwrap(),
682 Bytes::from_str("0x6B175474E89094C44Da98b954EedeAC495271d0F").unwrap(),
683 ],
684 contract_addresses: vec![
685 Bytes::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap(),
686 Bytes::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap(),
687 ],
688 static_attributes: HashMap::from([
689 ("key1".to_string(), Bytes::from(b"value1".to_vec())),
690 ("key2".to_string(), Bytes::from(b"value2".to_vec())),
691 ]),
692 change: ChangeType::Creation,
693 creation_tx: tx_hash,
694 created_at: DateTime::from_timestamp(1000, 0)
695 .unwrap()
696 .naive_utc(),
697 }
698 }
699
700 #[rstest]
701 fn test_merge_transaction_vm_updates() {
702 let tx_first_update = block_fixtures::transaction01();
703 let tx_second_update = block_fixtures::create_transaction(HASH_256_1, HASH_256_0, 15);
704 let protocol_component_first_tx = create_protocol_component(tx_first_update.hash.clone());
705 let protocol_component_second_tx = create_protocol_component(tx_second_update.hash.clone());
706 let account_address =
707 Bytes::from_str("0x0000000000000000000000000000000061626364").unwrap();
708 let token_address = Bytes::from_str("0x0000000000000000000000000000000066666666").unwrap();
709
710 let first_update = AccountChangesWithTx {
711 account_deltas: [(
712 account_address.clone(),
713 AccountDelta::new(
714 Chain::Ethereum,
715 account_address.clone(),
716 slots([(2711790500, 2981278644), (3250766788, 3520254932)]),
717 Some(Bytes::from(1903326068u64).lpad(32, 0)),
718 Some(vec![129, 130, 131, 132].into()),
719 ChangeType::Update,
720 ),
721 )]
722 .into_iter()
723 .collect(),
724 protocol_components: [(
725 protocol_component_first_tx.id.clone(),
726 protocol_component_first_tx.clone(),
727 )]
728 .into_iter()
729 .collect(),
730 component_balances: [(
731 protocol_component_first_tx.id.clone(),
732 [(
733 token_address.clone(),
734 ComponentBalance {
735 token: token_address.clone(),
736 balance: Bytes::from(0_i32.to_be_bytes()),
737 modify_tx: Default::default(),
738 component_id: protocol_component_first_tx.id.clone(),
739 balance_float: 0.0,
740 },
741 )]
742 .into_iter()
743 .collect(),
744 )]
745 .into_iter()
746 .collect(),
747 account_balances: [(
748 account_address.clone(),
749 [(
750 token_address.clone(),
751 AccountBalance {
752 token: token_address.clone(),
753 balance: Bytes::from(0_i32.to_be_bytes()),
754 modify_tx: Default::default(),
755 account: account_address.clone(),
756 },
757 )]
758 .into_iter()
759 .collect(),
760 )]
761 .into_iter()
762 .collect(),
763 tx: tx_first_update,
764 };
765 let second_update = AccountChangesWithTx {
766 account_deltas: [(
767 account_address.clone(),
768 AccountDelta::new(
769 Chain::Ethereum,
770 account_address.clone(),
771 slots([(2981278644, 3250766788), (2442302356, 2711790500)]),
772 Some(Bytes::from(4059231220u64).lpad(32, 0)),
773 Some(vec![1, 2, 3, 4].into()),
774 ChangeType::Update,
775 ),
776 )]
777 .into_iter()
778 .collect(),
779 protocol_components: [(
780 protocol_component_second_tx.id.clone(),
781 protocol_component_second_tx.clone(),
782 )]
783 .into_iter()
784 .collect(),
785 component_balances: [(
786 protocol_component_second_tx.id.clone(),
787 [(
788 token_address.clone(),
789 ComponentBalance {
790 token: token_address.clone(),
791 balance: Bytes::from(500000_i32.to_be_bytes()),
792 modify_tx: Default::default(),
793 component_id: protocol_component_first_tx.id.clone(),
794 balance_float: 500000.0,
795 },
796 )]
797 .into_iter()
798 .collect(),
799 )]
800 .into_iter()
801 .collect(),
802 account_balances: [(
803 account_address.clone(),
804 [(
805 token_address.clone(),
806 AccountBalance {
807 token: token_address,
808 balance: Bytes::from(20000_i32.to_be_bytes()),
809 modify_tx: Default::default(),
810 account: account_address,
811 },
812 )]
813 .into_iter()
814 .collect(),
815 )]
816 .into_iter()
817 .collect(),
818 tx: tx_second_update,
819 };
820
821 let mut to_merge_on = first_update.clone();
823 to_merge_on
824 .merge(&second_update)
825 .unwrap();
826
827 let expected_protocol_components: HashMap<ComponentId, ProtocolComponent> = [
829 (protocol_component_first_tx.id.clone(), protocol_component_first_tx.clone()),
830 (protocol_component_second_tx.id.clone(), protocol_component_second_tx.clone()),
831 ]
832 .into_iter()
833 .collect();
834 assert_eq!(to_merge_on.component_balances, second_update.component_balances);
835 assert_eq!(to_merge_on.account_balances, second_update.account_balances);
836 assert_eq!(to_merge_on.protocol_components, expected_protocol_components);
837
838 let mut acc_update = second_update
839 .account_deltas
840 .clone()
841 .into_values()
842 .next()
843 .unwrap();
844
845 acc_update.slots = slots([
846 (2442302356, 2711790500),
847 (2711790500, 2981278644),
848 (3250766788, 3520254932),
849 (2981278644, 3250766788),
850 ]);
851
852 let acc_update = [(acc_update.address.clone(), acc_update)]
853 .iter()
854 .cloned()
855 .collect();
856
857 assert_eq!(to_merge_on.account_deltas, acc_update);
858 }
859}