1use std::collections::HashMap;
2
3use deepsize::DeepSizeOf;
4use serde::{Deserialize, Serialize};
5use tracing::warn;
6
7use crate::{
8 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
347#[cfg(test)]
348mod test {
349 use std::str::FromStr;
350
351 use super::*;
352
353 fn update_balance_delta() -> AccountDelta {
354 AccountDelta::new(
355 Chain::Ethereum,
356 Bytes::from_str("e688b84b23f322a994A53dbF8E15FA82CDB71127").unwrap(),
357 HashMap::new(),
358 Some(Bytes::from(420u64).lpad(32, 0)),
359 None,
360 ChangeType::Update,
361 )
362 }
363
364 fn update_slots_delta() -> AccountDelta {
365 AccountDelta::new(
366 Chain::Ethereum,
367 Bytes::from_str("e688b84b23f322a994A53dbF8E15FA82CDB71127").unwrap(),
368 slots([(0, 1), (1, 2)]),
369 None,
370 None,
371 ChangeType::Update,
372 )
373 }
374
375 pub fn slots(data: impl IntoIterator<Item = (u64, u64)>) -> HashMap<Bytes, Option<Bytes>> {
378 data.into_iter()
379 .map(|(s, v)| (Bytes::from(s).lpad(32, 0), Some(Bytes::from(v).lpad(32, 0))))
380 .collect()
381 }
382
383 #[test]
384 fn test_merge_account_deltas() {
385 let mut update_left = update_balance_delta();
386 let update_right = update_slots_delta();
387 let mut exp = update_slots_delta();
388 exp.balance = Some(Bytes::from(420u64).lpad(32, 0));
389
390 update_left.merge(update_right).unwrap();
391
392 assert_eq!(update_left, exp);
393 }
394
395 #[test]
396 fn test_merge_account_delta_wrong_address() {
397 let mut update_left = update_balance_delta();
398 let mut update_right = update_slots_delta();
399 update_right.address = Bytes::zero(20);
400 let exp = Err(MergeError::IdMismatch(
401 "AccountDelta".to_string(),
402 format!("{:#020x}", update_left.address),
403 format!("{:#020x}", update_right.address),
404 ));
405
406 let res = update_left.merge(update_right);
407
408 assert_eq!(res, exp);
409 }
410
411 #[test]
412 fn test_account_from_delta_ref_into_account() {
413 let code = vec![0, 0, 0, 0];
414 let code_hash = Bytes::from(keccak256(&code));
415 let tx = Transaction::new(
416 Bytes::zero(32),
417 Bytes::zero(32),
418 Bytes::zero(20),
419 Some(Bytes::zero(20)),
420 10,
421 );
422
423 let delta = AccountDelta::new(
424 Chain::Ethereum,
425 Bytes::from_str("e688b84b23f322a994A53dbF8E15FA82CDB71127").unwrap(),
426 HashMap::new(),
427 Some(Bytes::from(10000u64).lpad(32, 0)),
428 Some(code.clone().into()),
429 ChangeType::Update,
430 );
431
432 let expected = Account::new(
433 Chain::Ethereum,
434 "0xe688b84b23f322a994A53dbF8E15FA82CDB71127"
435 .parse()
436 .unwrap(),
437 "0xe688b84b23f322a994a53dbf8e15fa82cdb71127".into(),
438 HashMap::new(),
439 Bytes::from(10000u64).lpad(32, 0),
440 HashMap::new(),
441 code.into(),
442 code_hash,
443 Bytes::zero(32),
444 Bytes::zero(32),
445 Some(Bytes::zero(32)),
446 );
447
448 assert_eq!(delta.ref_into_account(&tx), expected);
449 }
450}