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> {
237 if self.address != other.address {
238 return Err(MergeError::IdMismatch(
239 "AccountDelta".to_string(),
240 format!("{:#020x}", self.address),
241 format!("{:#020x}", other.address),
242 ));
243 }
244
245 self.slots.extend(other.slots);
246
247 if let Some(balance) = other.balance {
248 self.balance = Some(balance)
249 }
250 self.code = other.code.or(self.code.take());
251
252 if self.code.is_none() && matches!(self.change, ChangeType::Creation) {
253 warn!(address=?self.address, "AccountDelta without code marked as creation after merge!")
254 }
255
256 Ok(())
257 }
258
259 pub fn is_update(&self) -> bool {
260 self.change == ChangeType::Update
261 }
262
263 pub fn is_creation(&self) -> bool {
264 self.change == ChangeType::Creation
265 }
266
267 pub fn change_type(&self) -> ChangeType {
268 self.change
269 }
270
271 pub fn code(&self) -> &Option<Code> {
272 &self.code
273 }
274
275 pub fn set_code(&mut self, code: Bytes) {
276 self.code = Some(code)
277 }
278}
279
280impl From<Account> for AccountDelta {
281 fn from(value: Account) -> Self {
282 Self::new(
283 value.chain,
284 value.address,
285 value
286 .slots
287 .into_iter()
288 .map(|(k, v)| (k, Some(v)))
289 .collect(),
290 Some(value.native_balance),
291 Some(value.code),
292 ChangeType::Creation,
293 )
294 }
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, DeepSizeOf)]
298pub struct AccountBalance {
299 pub account: Address,
300 pub token: Address,
301 pub balance: Balance,
302 pub modify_tx: TxHash,
303}
304
305impl AccountBalance {
306 pub fn new(account: Address, token: Address, balance: Balance, modify_tx: TxHash) -> Self {
307 Self { account, token, balance, modify_tx }
308 }
309}
310
311#[derive(Debug, PartialEq, Clone, DeepSizeOf)]
312pub struct ContractStorageChange {
313 pub value: Bytes,
314 pub previous: Bytes,
315}
316
317impl ContractStorageChange {
318 pub fn new(value: impl Into<Bytes>, previous: impl Into<Bytes>) -> Self {
319 Self { value: value.into(), previous: previous.into() }
320 }
321
322 pub fn initial(value: impl Into<Bytes>) -> Self {
323 Self { value: value.into(), previous: Bytes::default() }
324 }
325}
326
327#[derive(Debug, PartialEq, Default, Clone, DeepSizeOf)]
328pub struct ContractChanges {
329 pub account: Address,
330 pub slots: HashMap<StoreKey, ContractStorageChange>,
331 pub native_balance: Option<Balance>,
332}
333
334impl ContractChanges {
335 pub fn new(
336 account: Address,
337 slots: HashMap<StoreKey, ContractStorageChange>,
338 native_balance: Option<Balance>,
339 ) -> Self {
340 Self { account, slots, native_balance }
341 }
342}
343
344pub type AccountToContractChanges = HashMap<Address, ContractChanges>;
346
347impl From<dto::AccountBalance> for AccountBalance {
348 fn from(value: dto::AccountBalance) -> Self {
349 Self {
350 token: value.token,
351 balance: value.balance,
352 modify_tx: value.modify_tx,
353 account: value.account,
354 }
355 }
356}
357
358impl From<dto::AccountUpdate> for AccountDelta {
359 fn from(value: dto::AccountUpdate) -> Self {
360 AccountDelta::new(
363 value.chain.into(),
364 value.address,
365 value
366 .slots
367 .into_iter()
368 .map(|(k, v)| (k, Some(v)))
369 .collect(),
370 value.balance,
371 value.code,
372 value.change.into(),
373 )
374 }
375}
376
377#[allow(deprecated)] impl From<dto::ResponseAccount> for Account {
379 fn from(value: dto::ResponseAccount) -> Self {
380 let token_balances = value
382 .token_balances
383 .into_iter()
384 .map(|(token, balance)| {
385 let acct = value.address.clone();
386 (
387 token.clone(),
388 AccountBalance {
389 token,
390 balance,
391 modify_tx: crate::Bytes::zero(32),
392 account: acct,
393 },
394 )
395 })
396 .collect();
397 Account::new(
398 value.chain.into(),
399 value.address,
400 value.title,
401 value.slots,
402 value.native_balance,
403 token_balances,
404 value.code,
405 value.code_hash,
406 value.balance_modify_tx,
407 value.code_modify_tx,
408 value.creation_tx,
409 )
410 }
411}
412
413#[cfg(test)]
414mod test {
415 use std::str::FromStr;
416
417 use super::*;
418
419 fn update_balance_delta() -> AccountDelta {
420 AccountDelta::new(
421 Chain::Ethereum,
422 Bytes::from_str("e688b84b23f322a994A53dbF8E15FA82CDB71127").unwrap(),
423 HashMap::new(),
424 Some(Bytes::from(420u64).lpad(32, 0)),
425 None,
426 ChangeType::Update,
427 )
428 }
429
430 fn update_slots_delta() -> AccountDelta {
431 AccountDelta::new(
432 Chain::Ethereum,
433 Bytes::from_str("e688b84b23f322a994A53dbF8E15FA82CDB71127").unwrap(),
434 slots([(0, 1), (1, 2)]),
435 None,
436 None,
437 ChangeType::Update,
438 )
439 }
440
441 pub fn slots(data: impl IntoIterator<Item = (u64, u64)>) -> HashMap<Bytes, Option<Bytes>> {
444 data.into_iter()
445 .map(|(s, v)| (Bytes::from(s).lpad(32, 0), Some(Bytes::from(v).lpad(32, 0))))
446 .collect()
447 }
448
449 #[test]
450 fn test_merge_account_deltas() {
451 let mut update_left = update_balance_delta();
452 let update_right = update_slots_delta();
453 let mut exp = update_slots_delta();
454 exp.balance = Some(Bytes::from(420u64).lpad(32, 0));
455
456 update_left.merge(update_right).unwrap();
457
458 assert_eq!(update_left, exp);
459 }
460
461 #[test]
462 fn test_merge_account_delta_wrong_address() {
463 let mut update_left = update_balance_delta();
464 let mut update_right = update_slots_delta();
465 update_right.address = Bytes::zero(20);
466 let exp = Err(MergeError::IdMismatch(
467 "AccountDelta".to_string(),
468 format!("{:#020x}", update_left.address),
469 format!("{:#020x}", update_right.address),
470 ));
471
472 let res = update_left.merge(update_right);
473
474 assert_eq!(res, exp);
475 }
476
477 #[test]
478 fn test_account_from_delta_ref_into_account() {
479 let code = vec![0, 0, 0, 0];
480 let code_hash = Bytes::from(keccak256(&code));
481 let tx = Transaction::new(
482 Bytes::zero(32),
483 Bytes::zero(32),
484 Bytes::zero(20),
485 Some(Bytes::zero(20)),
486 10,
487 );
488
489 let delta = AccountDelta::new(
490 Chain::Ethereum,
491 Bytes::from_str("e688b84b23f322a994A53dbF8E15FA82CDB71127").unwrap(),
492 HashMap::new(),
493 Some(Bytes::from(10000u64).lpad(32, 0)),
494 Some(code.clone().into()),
495 ChangeType::Update,
496 );
497
498 let expected = Account::new(
499 Chain::Ethereum,
500 "0xe688b84b23f322a994A53dbF8E15FA82CDB71127"
501 .parse()
502 .unwrap(),
503 "0xe688b84b23f322a994a53dbf8e15fa82cdb71127".into(),
504 HashMap::new(),
505 Bytes::from(10000u64).lpad(32, 0),
506 HashMap::new(),
507 code.into(),
508 code_hash,
509 Bytes::zero(32),
510 Bytes::zero(32),
511 Some(Bytes::zero(32)),
512 );
513
514 assert_eq!(delta.ref_into_account(&tx), expected);
515 }
516}