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