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, 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 pub code: Option<Code>,
102 pub 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 Self { chain, address, slots, balance, code, change }
124 }
125
126 pub fn contract_id(&self) -> ContractId {
127 ContractId::new(self.chain, self.address.clone())
128 }
129
130 pub fn into_account(self, tx: &Transaction) -> Account {
131 let empty_hash = keccak256(Vec::new());
132 Account::new(
133 self.chain,
134 self.address.clone(),
135 format!("{:#020x}", self.address),
136 self.slots
137 .into_iter()
138 .map(|(k, v)| (k, v.unwrap_or_default()))
139 .collect(),
140 self.balance.unwrap_or_default(),
141 HashMap::new(),
143 self.code.clone().unwrap_or_default(),
144 self.code
145 .as_ref()
146 .map(keccak256)
147 .unwrap_or(empty_hash)
148 .into(),
149 tx.hash.clone(),
150 tx.hash.clone(),
151 Some(tx.hash.clone()),
152 )
153 }
154
155 pub fn into_account_without_tx(self) -> Account {
158 let empty_hash = keccak256(Vec::new());
159 Account::new(
160 self.chain,
161 self.address.clone(),
162 format!("{:#020x}", self.address),
163 self.slots
164 .into_iter()
165 .map(|(k, v)| (k, v.unwrap_or_default()))
166 .collect(),
167 self.balance.unwrap_or_default(),
168 HashMap::new(),
170 self.code.clone().unwrap_or_default(),
171 self.code
172 .as_ref()
173 .map(keccak256)
174 .unwrap_or(empty_hash)
175 .into(),
176 Bytes::from("0x00"),
177 Bytes::from("0x00"),
178 None,
179 )
180 }
181
182 pub fn ref_into_account(&self, tx: &Transaction) -> Account {
184 let empty_hash = keccak256(Vec::new());
185 if self.change != ChangeType::Creation {
186 warn!("Creating an account from a partial change!")
187 }
188
189 Account::new(
190 self.chain,
191 self.address.clone(),
192 format!("{:#020x}", self.address),
193 self.slots
194 .clone()
195 .into_iter()
196 .map(|(k, v)| (k, v.unwrap_or_default()))
197 .collect(),
198 self.balance.clone().unwrap_or_default(),
199 HashMap::new(),
201 self.code.clone().unwrap_or_default(),
202 self.code
203 .as_ref()
204 .map(keccak256)
205 .unwrap_or(empty_hash)
206 .into(),
207 tx.hash.clone(),
208 tx.hash.clone(),
209 Some(tx.hash.clone()),
210 )
211 }
212
213 pub fn merge(&mut self, other: AccountDelta) -> Result<(), MergeError> {
236 if self.address != other.address {
237 return Err(MergeError::IdMismatch(
238 "AccountDelta".to_string(),
239 format!("{:#020x}", self.address),
240 format!("{:#020x}", other.address),
241 ));
242 }
243
244 self.slots.extend(other.slots);
245
246 if let Some(balance) = other.balance {
247 self.balance = Some(balance)
248 }
249 self.code = other.code.or(self.code.take());
250
251 Ok(())
252 }
253
254 pub fn is_update(&self) -> bool {
255 self.change == ChangeType::Update
256 }
257
258 pub fn is_creation(&self) -> bool {
259 self.change == ChangeType::Creation
260 }
261}
262
263impl From<Account> for AccountDelta {
264 fn from(value: Account) -> Self {
265 Self {
266 chain: value.chain,
267 address: value.address,
268 slots: value
269 .slots
270 .into_iter()
271 .map(|(k, v)| (k, Some(v)))
272 .collect(),
273 balance: Some(value.native_balance),
274 code: Some(value.code),
275 change: ChangeType::Creation,
276 }
277 }
278}
279
280#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
281pub struct AccountBalance {
282 pub account: Address,
283 pub token: Address,
284 pub balance: Balance,
285 pub modify_tx: TxHash,
286}
287
288impl AccountBalance {
289 pub fn new(account: Address, token: Address, balance: Balance, modify_tx: TxHash) -> Self {
290 Self { account, token, balance, modify_tx }
291 }
292}
293
294#[derive(Debug, Clone, PartialEq)]
296pub struct AccountChangesWithTx {
297 pub account_deltas: HashMap<Address, AccountDelta>,
299 pub protocol_components: HashMap<ComponentId, ProtocolComponent>,
301 pub component_balances: HashMap<ComponentId, HashMap<Address, ComponentBalance>>,
303 pub account_balances: HashMap<Address, HashMap<Address, AccountBalance>>,
305 pub tx: Transaction,
307}
308
309impl AccountChangesWithTx {
310 pub fn new(
311 account_deltas: HashMap<Address, AccountDelta>,
312 protocol_components: HashMap<ComponentId, ProtocolComponent>,
313 component_balances: HashMap<ComponentId, HashMap<Address, ComponentBalance>>,
314 account_balances: HashMap<Address, HashMap<Address, AccountBalance>>,
315 tx: Transaction,
316 ) -> Self {
317 Self { account_deltas, protocol_components, component_balances, account_balances, tx }
318 }
319
320 pub fn merge(&mut self, other: &AccountChangesWithTx) -> Result<(), MergeError> {
337 if self.tx.block_hash != other.tx.block_hash {
338 return Err(MergeError::BlockMismatch(
339 "AccountChangesWithTx".to_string(),
340 self.tx.block_hash.clone(),
341 other.tx.block_hash.clone(),
342 ));
343 }
344 if self.tx.hash == other.tx.hash {
345 return Err(MergeError::SameTransaction(
346 "AccountChangesWithTx".to_string(),
347 self.tx.hash.clone(),
348 ));
349 }
350 if self.tx.index > other.tx.index {
351 return Err(MergeError::TransactionOrderError(
352 "AccountChangesWithTx".to_string(),
353 self.tx.index,
354 other.tx.index,
355 ));
356 }
357
358 self.tx = other.tx.clone();
359
360 for (address, update) in other.account_deltas.clone().into_iter() {
361 match self.account_deltas.entry(address) {
362 Entry::Occupied(mut e) => {
363 e.get_mut().merge(update)?;
364 }
365 Entry::Vacant(e) => {
366 e.insert(update);
367 }
368 }
369 }
370
371 self.protocol_components
373 .extend(other.protocol_components.clone());
374
375 for (component_id, balance_by_token_map) in other
377 .component_balances
378 .clone()
379 .into_iter()
380 {
381 if let Some(existing_inner_map) = self
383 .component_balances
384 .get_mut(&component_id)
385 {
386 for (token, value) in balance_by_token_map {
388 existing_inner_map.insert(token, value);
389 }
390 } else {
391 self.component_balances
392 .insert(component_id, balance_by_token_map);
393 }
394 }
395
396 for (account_addr, balance_by_token_map) in other
398 .account_balances
399 .clone()
400 .into_iter()
401 {
402 if let Some(existing_inner_map) = self
404 .account_balances
405 .get_mut(&account_addr)
406 {
407 for (token, value) in balance_by_token_map {
409 existing_inner_map.insert(token, value);
410 }
411 } else {
412 self.account_balances
413 .insert(account_addr, balance_by_token_map);
414 }
415 }
416
417 Ok(())
418 }
419}
420
421impl From<&AccountChangesWithTx> for Vec<Account> {
422 fn from(value: &AccountChangesWithTx) -> Self {
432 value
433 .account_deltas
434 .clone()
435 .into_values()
436 .map(|update| {
437 let acc = Account::new(
438 update.chain,
439 update.address.clone(),
440 format!("{:#020x}", update.address),
441 update
442 .slots
443 .into_iter()
444 .map(|(k, v)| (k, v.unwrap_or_default())) .collect(),
446 update.balance.unwrap_or_default(),
447 value
448 .account_balances
449 .get(&update.address)
450 .cloned()
451 .unwrap_or_default(),
452 update.code.clone().unwrap_or_default(),
453 update
454 .code
455 .as_ref()
456 .map(keccak256)
457 .unwrap_or_default()
458 .into(),
459 value.tx.hash.clone(),
460 value.tx.hash.clone(),
461 Some(value.tx.hash.clone()),
462 );
463 acc
464 })
465 .collect()
466 }
467}
468
469#[cfg(test)]
470mod test {
471 use std::str::FromStr;
472
473 use chrono::NaiveDateTime;
474 use rstest::rstest;
475
476 use super::*;
477 use crate::models::blockchain::fixtures as block_fixtures;
478
479 const HASH_256_0: &str = "0x0000000000000000000000000000000000000000000000000000000000000000";
480 const HASH_256_1: &str = "0x0000000000000000000000000000000000000000000000000000000000000001";
481
482 fn update_balance_delta() -> AccountDelta {
483 AccountDelta::new(
484 Chain::Ethereum,
485 Bytes::from_str("e688b84b23f322a994A53dbF8E15FA82CDB71127").unwrap(),
486 HashMap::new(),
487 Some(Bytes::from(420u64).lpad(32, 0)),
488 None,
489 ChangeType::Update,
490 )
491 }
492
493 fn update_slots_delta() -> AccountDelta {
494 AccountDelta::new(
495 Chain::Ethereum,
496 Bytes::from_str("e688b84b23f322a994A53dbF8E15FA82CDB71127").unwrap(),
497 slots([(0, 1), (1, 2)]),
498 None,
499 None,
500 ChangeType::Update,
501 )
502 }
503
504 pub fn slots(data: impl IntoIterator<Item = (u64, u64)>) -> HashMap<Bytes, Option<Bytes>> {
507 data.into_iter()
508 .map(|(s, v)| (Bytes::from(s).lpad(32, 0), Some(Bytes::from(v).lpad(32, 0))))
509 .collect()
510 }
511
512 #[test]
513 fn test_merge_account_deltas() {
514 let mut update_left = update_balance_delta();
515 let update_right = update_slots_delta();
516 let mut exp = update_slots_delta();
517 exp.balance = Some(Bytes::from(420u64).lpad(32, 0));
518
519 update_left.merge(update_right).unwrap();
520
521 assert_eq!(update_left, exp);
522 }
523
524 #[test]
525 fn test_merge_account_delta_wrong_address() {
526 let mut update_left = update_balance_delta();
527 let mut update_right = update_slots_delta();
528 update_right.address = Bytes::zero(20);
529 let exp = Err(MergeError::IdMismatch(
530 "AccountDelta".to_string(),
531 format!("{:#020x}", update_left.address),
532 format!("{:#020x}", update_right.address),
533 ));
534
535 let res = update_left.merge(update_right);
536
537 assert_eq!(res, exp);
538 }
539
540 fn tx_vm_update() -> AccountChangesWithTx {
541 let code = vec![0, 0, 0, 0];
542 let mut account_updates = HashMap::new();
543 account_updates.insert(
544 "0xe688b84b23f322a994A53dbF8E15FA82CDB71127"
545 .parse()
546 .unwrap(),
547 AccountDelta::new(
548 Chain::Ethereum,
549 Bytes::from_str("e688b84b23f322a994A53dbF8E15FA82CDB71127").unwrap(),
550 HashMap::new(),
551 Some(Bytes::from(10000u64).lpad(32, 0)),
552 Some(code.into()),
553 ChangeType::Update,
554 ),
555 );
556
557 AccountChangesWithTx::new(
558 account_updates,
559 HashMap::new(),
560 HashMap::new(),
561 HashMap::new(),
562 block_fixtures::transaction01(),
563 )
564 }
565
566 fn account() -> Account {
567 let code = vec![0, 0, 0, 0];
568 let code_hash = Bytes::from(keccak256(&code));
569 Account::new(
570 Chain::Ethereum,
571 "0xe688b84b23f322a994A53dbF8E15FA82CDB71127"
572 .parse()
573 .unwrap(),
574 "0xe688b84b23f322a994a53dbf8e15fa82cdb71127".into(),
575 HashMap::new(),
576 Bytes::from(10000u64).lpad(32, 0),
577 HashMap::new(),
578 code.into(),
579 code_hash,
580 Bytes::zero(32),
581 Bytes::zero(32),
582 Some(Bytes::zero(32)),
583 )
584 }
585
586 #[test]
587 fn test_account_from_update_w_tx() {
588 let update = tx_vm_update();
589 let exp = account();
590
591 assert_eq!(
592 update
593 .account_deltas
594 .values()
595 .next()
596 .unwrap()
597 .ref_into_account(&update.tx),
598 exp
599 );
600 }
601
602 #[rstest]
603 #[case::diff_block(
604 block_fixtures::create_transaction(HASH_256_1, HASH_256_1, 11),
605 Err(MergeError::BlockMismatch(
606 "AccountChangesWithTx".to_string(),
607 Bytes::zero(32),
608 HASH_256_1.into(),
609 ))
610 )]
611 #[case::same_tx(
612 block_fixtures::create_transaction(HASH_256_0, HASH_256_0, 11),
613 Err(MergeError::SameTransaction(
614 "AccountChangesWithTx".to_string(),
615 Bytes::zero(32),
616 ))
617 )]
618 #[case::lower_idx(
619 block_fixtures::create_transaction(HASH_256_1, HASH_256_0, 1),
620 Err(MergeError::TransactionOrderError(
621 "AccountChangesWithTx".to_string(),
622 10,
623 1,
624 ))
625 )]
626 fn test_merge_vm_updates_w_tx(#[case] tx: Transaction, #[case] exp: Result<(), MergeError>) {
627 let mut left = tx_vm_update();
628 let mut right = left.clone();
629 right.tx = tx;
630
631 let res = left.merge(&right);
632
633 assert_eq!(res, exp);
634 }
635
636 fn create_protocol_component(tx_hash: Bytes) -> ProtocolComponent {
637 ProtocolComponent {
638 id: "d417ff54652c09bd9f31f216b1a2e5d1e28c1dce1ba840c40d16f2b4d09b5902".to_owned(),
639 protocol_system: "ambient".to_string(),
640 protocol_type_name: String::from("WeightedPool"),
641 chain: Chain::Ethereum,
642 tokens: vec![
643 Bytes::from_str("0x6B175474E89094C44Da98b954EedeAC495271d0F").unwrap(),
644 Bytes::from_str("0x6B175474E89094C44Da98b954EedeAC495271d0F").unwrap(),
645 ],
646 contract_addresses: vec![
647 Bytes::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap(),
648 Bytes::from_str("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2").unwrap(),
649 ],
650 static_attributes: HashMap::from([
651 ("key1".to_string(), Bytes::from(b"value1".to_vec())),
652 ("key2".to_string(), Bytes::from(b"value2".to_vec())),
653 ]),
654 change: ChangeType::Creation,
655 creation_tx: tx_hash,
656 created_at: NaiveDateTime::from_timestamp_opt(1000, 0).unwrap(),
657 }
658 }
659
660 #[rstest]
661 fn test_merge_transaction_vm_updates() {
662 let tx_first_update = block_fixtures::transaction01();
663 let tx_second_update = block_fixtures::create_transaction(HASH_256_1, HASH_256_0, 15);
664 let protocol_component_first_tx = create_protocol_component(tx_first_update.hash.clone());
665 let protocol_component_second_tx = create_protocol_component(tx_second_update.hash.clone());
666 let account_address =
667 Bytes::from_str("0x0000000000000000000000000000000061626364").unwrap();
668 let token_address = Bytes::from_str("0x0000000000000000000000000000000066666666").unwrap();
669
670 let first_update = AccountChangesWithTx {
671 account_deltas: [(
672 account_address.clone(),
673 AccountDelta::new(
674 Chain::Ethereum,
675 account_address.clone(),
676 slots([(2711790500, 2981278644), (3250766788, 3520254932)]),
677 Some(Bytes::from(1903326068u64).lpad(32, 0)),
678 Some(vec![129, 130, 131, 132].into()),
679 ChangeType::Update,
680 ),
681 )]
682 .into_iter()
683 .collect(),
684 protocol_components: [(
685 protocol_component_first_tx.id.clone(),
686 protocol_component_first_tx.clone(),
687 )]
688 .into_iter()
689 .collect(),
690 component_balances: [(
691 protocol_component_first_tx.id.clone(),
692 [(
693 token_address.clone(),
694 ComponentBalance {
695 token: token_address.clone(),
696 balance: Bytes::from(0_i32.to_be_bytes()),
697 modify_tx: Default::default(),
698 component_id: protocol_component_first_tx.id.clone(),
699 balance_float: 0.0,
700 },
701 )]
702 .into_iter()
703 .collect(),
704 )]
705 .into_iter()
706 .collect(),
707 account_balances: [(
708 account_address.clone(),
709 [(
710 token_address.clone(),
711 AccountBalance {
712 token: token_address.clone(),
713 balance: Bytes::from(0_i32.to_be_bytes()),
714 modify_tx: Default::default(),
715 account: account_address.clone(),
716 },
717 )]
718 .into_iter()
719 .collect(),
720 )]
721 .into_iter()
722 .collect(),
723 tx: tx_first_update,
724 };
725 let second_update = AccountChangesWithTx {
726 account_deltas: [(
727 account_address.clone(),
728 AccountDelta::new(
729 Chain::Ethereum,
730 account_address.clone(),
731 slots([(2981278644, 3250766788), (2442302356, 2711790500)]),
732 Some(Bytes::from(4059231220u64).lpad(32, 0)),
733 Some(vec![1, 2, 3, 4].into()),
734 ChangeType::Update,
735 ),
736 )]
737 .into_iter()
738 .collect(),
739 protocol_components: [(
740 protocol_component_second_tx.id.clone(),
741 protocol_component_second_tx.clone(),
742 )]
743 .into_iter()
744 .collect(),
745 component_balances: [(
746 protocol_component_second_tx.id.clone(),
747 [(
748 token_address.clone(),
749 ComponentBalance {
750 token: token_address.clone(),
751 balance: Bytes::from(500000_i32.to_be_bytes()),
752 modify_tx: Default::default(),
753 component_id: protocol_component_first_tx.id.clone(),
754 balance_float: 500000.0,
755 },
756 )]
757 .into_iter()
758 .collect(),
759 )]
760 .into_iter()
761 .collect(),
762 account_balances: [(
763 account_address.clone(),
764 [(
765 token_address.clone(),
766 AccountBalance {
767 token: token_address,
768 balance: Bytes::from(20000_i32.to_be_bytes()),
769 modify_tx: Default::default(),
770 account: account_address,
771 },
772 )]
773 .into_iter()
774 .collect(),
775 )]
776 .into_iter()
777 .collect(),
778 tx: tx_second_update,
779 };
780
781 let mut to_merge_on = first_update.clone();
783 to_merge_on
784 .merge(&second_update)
785 .unwrap();
786
787 let expected_protocol_components: HashMap<ComponentId, ProtocolComponent> = [
789 (protocol_component_first_tx.id.clone(), protocol_component_first_tx.clone()),
790 (protocol_component_second_tx.id.clone(), protocol_component_second_tx.clone()),
791 ]
792 .into_iter()
793 .collect();
794 assert_eq!(to_merge_on.component_balances, second_update.component_balances);
795 assert_eq!(to_merge_on.account_balances, second_update.account_balances);
796 assert_eq!(to_merge_on.protocol_components, expected_protocol_components);
797
798 let mut acc_update = second_update
799 .account_deltas
800 .clone()
801 .into_values()
802 .next()
803 .unwrap();
804
805 acc_update.slots = slots([
806 (2442302356, 2711790500),
807 (2711790500, 2981278644),
808 (3250766788, 3520254932),
809 (2981278644, 3250766788),
810 ]);
811
812 let acc_update = [(acc_update.address.clone(), acc_update)]
813 .iter()
814 .cloned()
815 .collect();
816
817 assert_eq!(to_merge_on.account_deltas, acc_update);
818 }
819}