1use std::collections::HashMap;
2
3use deepsize::DeepSizeOf;
4use serde::{Deserialize, Serialize};
5use tracing::warn;
6
7use crate::{
8 dto, keccak256,
9 models::{
10 blockchain::Transaction, Address, Balance, Chain, ChangeType, Code, CodeHash, ContractId,
11 ContractStore, ContractStoreDeltas, MergeError, StoreKey, TxHash,
12 },
13 Bytes,
14};
15
16#[derive(Clone, Debug, PartialEq)]
17pub struct Account {
18 pub chain: Chain,
19 pub address: Address,
20 pub title: String,
21 pub slots: ContractStore,
22 pub native_balance: Balance,
23 pub token_balances: HashMap<Address, AccountBalance>,
24 pub code: Code,
25 pub code_hash: CodeHash,
26 pub balance_modify_tx: TxHash,
27 pub code_modify_tx: TxHash,
28 pub creation_tx: Option<TxHash>,
29}
30
31impl Account {
32 #[allow(clippy::too_many_arguments)]
33 pub fn new(
34 chain: Chain,
35 address: Address,
36 title: String,
37 slots: ContractStore,
38 native_balance: Balance,
39 token_balances: HashMap<Address, AccountBalance>,
40 code: Code,
41 code_hash: CodeHash,
42 balance_modify_tx: TxHash,
43 code_modify_tx: TxHash,
44 creation_tx: Option<TxHash>,
45 ) -> Self {
46 Self {
47 chain,
48 address,
49 title,
50 slots,
51 native_balance,
52 token_balances,
53 code,
54 code_hash,
55 balance_modify_tx,
56 code_modify_tx,
57 creation_tx,
58 }
59 }
60
61 pub fn set_balance(&mut self, new_balance: &Balance, modified_at: &Balance) {
62 self.native_balance = new_balance.clone();
63 self.balance_modify_tx = modified_at.clone();
64 }
65
66 pub fn apply_delta(&mut self, delta: &AccountDelta) -> Result<(), MergeError> {
67 let self_id = (self.chain, &self.address);
68 let other_id = (delta.chain, &delta.address);
69 if self_id != other_id {
70 return Err(MergeError::IdMismatch(
71 "AccountDeltas".to_string(),
72 format!("{self_id:?}"),
73 format!("{other_id:?}"),
74 ));
75 }
76 if let Some(balance) = delta.balance.as_ref() {
77 self.native_balance.clone_from(balance);
78 }
79 if let Some(code) = delta.code.as_ref() {
80 self.code.clone_from(code);
81 }
82 self.slots.extend(
83 delta
84 .slots
85 .clone()
86 .into_iter()
87 .map(|(k, v)| (k, v.unwrap_or_default())),
88 );
89 Ok(())
91 }
92}
93
94#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default, DeepSizeOf)]
95pub struct AccountDelta {
96 pub chain: Chain,
97 pub address: Address,
98 pub slots: ContractStoreDeltas,
99 pub balance: Option<Balance>,
100 code: Option<Code>,
101 change: ChangeType,
102}
103
104impl AccountDelta {
105 pub fn deleted(chain: &Chain, address: &Address) -> Self {
106 Self {
107 chain: *chain,
108 address: address.clone(),
109 change: ChangeType::Deletion,
110 ..Default::default()
111 }
112 }
113
114 pub fn new(
115 chain: Chain,
116 address: Address,
117 slots: ContractStoreDeltas,
118 balance: Option<Balance>,
119 code: Option<Code>,
120 change: ChangeType,
121 ) -> Self {
122 if code.is_none() && matches!(change, ChangeType::Creation) {
123 warn!(?address, "Instantiated AccountDelta without code marked as creation!")
124 }
125 Self { chain, address, slots, balance, code, change }
126 }
127
128 pub fn contract_id(&self) -> ContractId {
129 ContractId::new(self.chain, self.address.clone())
130 }
131
132 pub fn into_account(self, tx: &Transaction) -> Account {
133 let empty_hash = keccak256(Vec::new());
134 Account::new(
135 self.chain,
136 self.address.clone(),
137 format!("{:#020x}", self.address),
138 self.slots
139 .into_iter()
140 .map(|(k, v)| (k, v.unwrap_or_default()))
141 .collect(),
142 self.balance.unwrap_or_default(),
143 HashMap::new(),
145 self.code.clone().unwrap_or_default(),
146 self.code
147 .as_ref()
148 .map(keccak256)
149 .unwrap_or(empty_hash)
150 .into(),
151 tx.hash.clone(),
152 tx.hash.clone(),
153 Some(tx.hash.clone()),
154 )
155 }
156
157 pub fn into_account_without_tx(self) -> Account {
160 let empty_hash = keccak256(Vec::new());
161 Account::new(
162 self.chain,
163 self.address.clone(),
164 format!("{:#020x}", self.address),
165 self.slots
166 .into_iter()
167 .map(|(k, v)| (k, v.unwrap_or_default()))
168 .collect(),
169 self.balance.unwrap_or_default(),
170 HashMap::new(),
172 self.code.clone().unwrap_or_default(),
173 self.code
174 .as_ref()
175 .map(keccak256)
176 .unwrap_or(empty_hash)
177 .into(),
178 Bytes::from("0x00"),
179 Bytes::from("0x00"),
180 None,
181 )
182 }
183
184 pub fn ref_into_account(&self, tx: &Transaction) -> Account {
186 let empty_hash = keccak256(Vec::new());
187 if self.change != ChangeType::Creation {
188 warn!("Creating an account from a partial change!")
189 }
190
191 Account::new(
192 self.chain,
193 self.address.clone(),
194 format!("{:#020x}", self.address),
195 self.slots
196 .clone()
197 .into_iter()
198 .map(|(k, v)| (k, v.unwrap_or_default()))
199 .collect(),
200 self.balance.clone().unwrap_or_default(),
201 HashMap::new(),
203 self.code.clone().unwrap_or_default(),
204 self.code
205 .as_ref()
206 .map(keccak256)
207 .unwrap_or(empty_hash)
208 .into(),
209 tx.hash.clone(),
210 tx.hash.clone(),
211 Some(tx.hash.clone()),
212 )
213 }
214
215 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 if other.change == ChangeType::Creation {
254 self.change = ChangeType::Creation;
255 }
256
257 if self.code.is_none() && matches!(self.change, ChangeType::Creation) {
258 warn!(address=?self.address, "AccountDelta without code marked as creation after merge!")
259 }
260
261 Ok(())
262 }
263
264 pub fn is_update(&self) -> bool {
265 self.change == ChangeType::Update
266 }
267
268 pub fn is_creation(&self) -> bool {
269 self.change == ChangeType::Creation
270 }
271
272 pub fn change_type(&self) -> ChangeType {
273 self.change
274 }
275
276 pub fn code(&self) -> &Option<Code> {
277 &self.code
278 }
279
280 pub fn set_code(&mut self, code: Bytes) {
281 self.code = Some(code)
282 }
283}
284
285impl From<Account> for AccountDelta {
286 fn from(value: Account) -> Self {
287 Self::new(
288 value.chain,
289 value.address,
290 value
291 .slots
292 .into_iter()
293 .map(|(k, v)| (k, Some(v)))
294 .collect(),
295 Some(value.native_balance),
296 Some(value.code),
297 ChangeType::Creation,
298 )
299 }
300}
301
302#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, DeepSizeOf)]
303pub struct AccountBalance {
304 pub account: Address,
305 pub token: Address,
306 pub balance: Balance,
307 pub modify_tx: TxHash,
308}
309
310impl AccountBalance {
311 pub fn new(account: Address, token: Address, balance: Balance, modify_tx: TxHash) -> Self {
312 Self { account, token, balance, modify_tx }
313 }
314}
315
316#[derive(Debug, PartialEq, Clone, DeepSizeOf)]
317pub struct ContractStorageChange {
318 pub value: Bytes,
319 pub previous: Bytes,
320}
321
322impl ContractStorageChange {
323 pub fn new(value: impl Into<Bytes>, previous: impl Into<Bytes>) -> Self {
324 Self { value: value.into(), previous: previous.into() }
325 }
326
327 pub fn initial(value: impl Into<Bytes>) -> Self {
328 Self { value: value.into(), previous: Bytes::default() }
329 }
330}
331
332#[derive(Debug, PartialEq, Default, Clone, DeepSizeOf)]
333pub struct ContractChanges {
334 pub account: Address,
335 pub slots: HashMap<StoreKey, ContractStorageChange>,
336 pub native_balance: Option<Balance>,
337}
338
339impl ContractChanges {
340 pub fn new(
341 account: Address,
342 slots: HashMap<StoreKey, ContractStorageChange>,
343 native_balance: Option<Balance>,
344 ) -> Self {
345 Self { account, slots, native_balance }
346 }
347}
348
349pub type AccountToContractChanges = HashMap<Address, ContractChanges>;
351
352impl From<dto::AccountBalance> for AccountBalance {
353 fn from(value: dto::AccountBalance) -> Self {
354 Self {
355 token: value.token,
356 balance: value.balance,
357 modify_tx: value.modify_tx,
358 account: value.account,
359 }
360 }
361}
362
363impl From<dto::AccountUpdate> for AccountDelta {
364 fn from(value: dto::AccountUpdate) -> Self {
365 AccountDelta::new(
368 value.chain.into(),
369 value.address,
370 value
371 .slots
372 .into_iter()
373 .map(|(k, v)| (k, Some(v)))
374 .collect(),
375 value.balance,
376 value.code,
377 value.change.into(),
378 )
379 }
380}
381
382#[allow(deprecated)] impl From<dto::ResponseAccount> for Account {
384 fn from(value: dto::ResponseAccount) -> Self {
385 let token_balances = value
387 .token_balances
388 .into_iter()
389 .map(|(token, balance)| {
390 let acct = value.address.clone();
391 (
392 token.clone(),
393 AccountBalance {
394 token,
395 balance,
396 modify_tx: crate::Bytes::zero(32),
397 account: acct,
398 },
399 )
400 })
401 .collect();
402 Account::new(
403 value.chain.into(),
404 value.address,
405 value.title,
406 value.slots,
407 value.native_balance,
408 token_balances,
409 value.code,
410 value.code_hash,
411 value.balance_modify_tx,
412 value.code_modify_tx,
413 value.creation_tx,
414 )
415 }
416}
417
418#[cfg(test)]
419mod test {
420 use std::str::FromStr;
421
422 use super::*;
423
424 fn update_balance_delta() -> AccountDelta {
425 AccountDelta::new(
426 Chain::Ethereum,
427 Bytes::from_str("e688b84b23f322a994A53dbF8E15FA82CDB71127").unwrap(),
428 HashMap::new(),
429 Some(Bytes::from(420u64).lpad(32, 0)),
430 None,
431 ChangeType::Update,
432 )
433 }
434
435 fn update_slots_delta() -> AccountDelta {
436 AccountDelta::new(
437 Chain::Ethereum,
438 Bytes::from_str("e688b84b23f322a994A53dbF8E15FA82CDB71127").unwrap(),
439 slots([(0, 1), (1, 2)]),
440 None,
441 None,
442 ChangeType::Update,
443 )
444 }
445
446 pub fn slots(data: impl IntoIterator<Item = (u64, u64)>) -> HashMap<Bytes, Option<Bytes>> {
449 data.into_iter()
450 .map(|(s, v)| (Bytes::from(s).lpad(32, 0), Some(Bytes::from(v).lpad(32, 0))))
451 .collect()
452 }
453
454 #[test]
455 fn test_merge_account_deltas() {
456 let mut update_left = update_balance_delta();
457 let update_right = update_slots_delta();
458 let mut exp = update_slots_delta();
459 exp.balance = Some(Bytes::from(420u64).lpad(32, 0));
460
461 update_left.merge(update_right).unwrap();
462
463 assert_eq!(update_left, exp);
464 }
465
466 fn creation_delta() -> AccountDelta {
467 AccountDelta::new(
468 Chain::Ethereum,
469 Bytes::from_str("e688b84b23f322a994A53dbF8E15FA82CDB71127").unwrap(),
470 slots([(1, 3), (2, 4)]),
471 Some(Bytes::from(7u64).lpad(32, 0)),
472 Some(Bytes::from("0x6000")),
473 ChangeType::Creation,
474 )
475 }
476
477 #[test]
478 fn test_merge_creation_into_update_is_creation() {
479 let mut delta = update_slots_delta();
480
481 delta.merge(creation_delta()).unwrap();
482
483 assert_eq!(delta.change_type(), ChangeType::Creation);
484 assert_eq!(delta.slots, slots([(0, 1), (1, 3), (2, 4)]));
485 assert_eq!(delta.code(), &Some(Bytes::from("0x6000")));
486 }
487
488 #[test]
489 fn test_merge_update_into_creation_stays_creation() {
490 let mut delta = creation_delta();
491
492 delta
493 .merge(update_slots_delta())
494 .unwrap();
495
496 assert_eq!(delta.change_type(), ChangeType::Creation);
497 assert_eq!(delta.slots, slots([(0, 1), (1, 2), (2, 4)]));
498 }
499
500 #[test]
501 fn test_merge_deletion_into_update_stays_update() {
502 let mut delta = update_slots_delta();
503 let address = delta.address.clone();
504
505 delta
506 .merge(AccountDelta::deleted(&Chain::Ethereum, &address))
507 .unwrap();
508
509 assert_eq!(delta.change_type(), ChangeType::Update);
510 }
511
512 #[test]
513 fn test_merge_account_delta_wrong_address() {
514 let mut update_left = update_balance_delta();
515 let mut update_right = update_slots_delta();
516 update_right.address = Bytes::zero(20);
517 let exp = Err(MergeError::IdMismatch(
518 "AccountDelta".to_string(),
519 format!("{:#020x}", update_left.address),
520 format!("{:#020x}", update_right.address),
521 ));
522
523 let res = update_left.merge(update_right);
524
525 assert_eq!(res, exp);
526 }
527
528 #[test]
529 fn test_account_from_delta_ref_into_account() {
530 let code = vec![0, 0, 0, 0];
531 let code_hash = Bytes::from(keccak256(&code));
532 let tx = Transaction::new(
533 Bytes::zero(32),
534 Bytes::zero(32),
535 Bytes::zero(20),
536 Some(Bytes::zero(20)),
537 10,
538 );
539
540 let delta = AccountDelta::new(
541 Chain::Ethereum,
542 Bytes::from_str("e688b84b23f322a994A53dbF8E15FA82CDB71127").unwrap(),
543 HashMap::new(),
544 Some(Bytes::from(10000u64).lpad(32, 0)),
545 Some(code.clone().into()),
546 ChangeType::Update,
547 );
548
549 let expected = Account::new(
550 Chain::Ethereum,
551 "0xe688b84b23f322a994A53dbF8E15FA82CDB71127"
552 .parse()
553 .unwrap(),
554 "0xe688b84b23f322a994a53dbf8e15fa82cdb71127".into(),
555 HashMap::new(),
556 Bytes::from(10000u64).lpad(32, 0),
557 HashMap::new(),
558 code.into(),
559 code_hash,
560 Bytes::zero(32),
561 Bytes::zero(32),
562 Some(Bytes::zero(32)),
563 );
564
565 assert_eq!(delta.ref_into_account(&tx), expected);
566 }
567}