1use std::collections::{BTreeMap, BTreeSet};
2
3use serde::Serialize;
4
5use crate::primitives::{Address, B256, Bytes, Delta, EMPTY_CODE_HASH, U256};
6use crate::trace::{AccountState, PrestateDiff, TxTrace};
7
8#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
9pub struct Change<T> {
10 pub from: T,
11 pub to: T,
12}
13
14impl<T: PartialEq> Change<T> {
15 fn new(from: T, to: T) -> Option<Self> {
16 (from != to).then_some(Self { from, to })
17 }
18}
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize)]
21#[serde(rename_all = "lowercase")]
22pub enum AccountStatus {
23 Created,
25 Destroyed,
27 Modified,
28}
29
30#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
31pub struct AccountDiff {
32 pub status: AccountStatus,
33 #[serde(skip_serializing_if = "Option::is_none")]
34 pub balance: Option<Change<U256>>,
35 #[serde(skip_serializing_if = "Option::is_none")]
36 pub nonce: Option<Change<u64>>,
37 #[serde(skip_serializing_if = "Option::is_none")]
38 pub code: Option<Change<Bytes>>,
39 #[serde(skip_serializing_if = "Option::is_none")]
40 pub code_hash: Option<Change<B256>>,
41 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
42 pub storage: BTreeMap<B256, Change<B256>>,
43}
44
45impl AccountDiff {
46 fn empty(status: AccountStatus) -> Self {
47 Self { status, balance: None, nonce: None, code: None, code_hash: None, storage: BTreeMap::new() }
48 }
49
50 pub fn has_changes(&self) -> bool {
51 self.balance.is_some()
52 || self.nonce.is_some()
53 || self.code.is_some()
54 || self.code_hash.is_some()
55 || !self.storage.is_empty()
56 }
57
58 pub fn balance_delta(&self) -> Option<Delta> {
59 self.balance.map(|c| Delta::between(c.from, c.to))
60 }
61}
62
63#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
65#[serde(transparent)]
66pub struct StateDiff {
67 pub accounts: BTreeMap<Address, AccountDiff>,
68}
69
70#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
71pub struct Summary {
72 pub accounts: usize,
73 pub created: usize,
74 pub destroyed: usize,
75 pub balance_changes: usize,
76 pub nonce_changes: usize,
77 pub code_changes: usize,
78 pub storage_changes: usize,
79 pub total_credited: U256,
81 pub total_debited: U256,
83}
84
85fn balance_of(a: Option<&AccountState>) -> U256 {
86 a.and_then(|a| a.balance).unwrap_or_default()
87}
88
89fn nonce_of(a: Option<&AccountState>) -> u64 {
90 a.and_then(|a| a.nonce).unwrap_or(0)
91}
92
93fn code_of(a: Option<&AccountState>) -> Bytes {
94 a.and_then(|a| a.code.clone()).unwrap_or_default()
95}
96
97fn code_hash_of(a: Option<&AccountState>) -> B256 {
98 a.and_then(|a| a.code_hash).unwrap_or(EMPTY_CODE_HASH)
99}
100
101impl StateDiff {
102 pub fn from_prestate(trace: &PrestateDiff) -> Self {
111 let addresses: BTreeSet<&Address> = trace.pre.keys().chain(trace.post.keys()).collect();
112 let mut accounts = BTreeMap::new();
113 for addr in addresses {
114 let pre = trace.pre.get(addr);
115 let post = trace.post.get(addr);
116 let diff = match (pre, post) {
117 (Some(pre), None) => destroyed(pre),
118 (None, Some(post)) => modified(AccountStatus::Created, None, post),
119 (Some(pre), Some(post)) => modified(AccountStatus::Modified, Some(pre), post),
120 (None, None) => unreachable!("address comes from pre or post"),
121 };
122 if diff.has_changes() {
123 accounts.insert(*addr, diff);
124 }
125 }
126 Self { accounts }
127 }
128
129 pub fn compose<'a>(diffs: impl IntoIterator<Item = &'a StateDiff>) -> Self {
131 let mut out = StateDiff::default();
132 for diff in diffs {
133 out = out.then(diff);
134 }
135 out
136 }
137
138 pub fn then(&self, next: &StateDiff) -> StateDiff {
140 let mut accounts = self.accounts.clone();
141 for (addr, later) in &next.accounts {
142 match accounts.remove(addr) {
143 None => {
144 accounts.insert(*addr, later.clone());
145 }
146 Some(earlier) => {
147 let merged = merge(earlier, later);
148 if merged.has_changes() {
149 accounts.insert(*addr, merged);
150 }
151 }
152 }
153 }
154 StateDiff { accounts }
155 }
156
157 pub fn is_empty(&self) -> bool {
158 self.accounts.is_empty()
159 }
160
161 pub fn summary(&self) -> Summary {
162 let mut s = Summary { accounts: self.accounts.len(), ..Summary::default() };
163 for acct in self.accounts.values() {
164 match acct.status {
165 AccountStatus::Created => s.created += 1,
166 AccountStatus::Destroyed => s.destroyed += 1,
167 AccountStatus::Modified => {}
168 }
169 if let Some(delta) = acct.balance_delta() {
170 s.balance_changes += 1;
171 let total = if delta.negative { &mut s.total_debited } else { &mut s.total_credited };
172 *total = total.checked_add(delta.magnitude).unwrap_or(U256::MAX);
173 }
174 s.nonce_changes += usize::from(acct.nonce.is_some());
175 s.code_changes += usize::from(acct.code.is_some() || acct.code_hash.is_some());
176 s.storage_changes += acct.storage.len();
177 }
178 s
179 }
180}
181
182fn destroyed(pre: &AccountState) -> AccountDiff {
183 let mut d = AccountDiff::empty(AccountStatus::Destroyed);
184 d.balance = Change::new(balance_of(Some(pre)), U256::ZERO);
185 d.nonce = Change::new(nonce_of(Some(pre)), 0);
186 d.code = pre.code.clone().and_then(|c| Change::new(c, Bytes::default()));
187 d.code_hash = pre.code_hash.and_then(|h| Change::new(h, EMPTY_CODE_HASH));
188 for (slot, value) in pre.storage.iter().flatten() {
189 if let Some(c) = Change::new(*value, B256::ZERO) {
190 d.storage.insert(*slot, c);
191 }
192 }
193 d
194}
195
196fn modified(status: AccountStatus, pre: Option<&AccountState>, post: &AccountState) -> AccountDiff {
197 let mut d = AccountDiff::empty(status);
198 d.balance = post.balance.and_then(|to| Change::new(balance_of(pre), to));
199 d.nonce = post.nonce.and_then(|to| Change::new(nonce_of(pre), to));
200 d.code = post.code.clone().and_then(|to| Change::new(code_of(pre), to));
201 d.code_hash = post.code_hash.and_then(|to| Change::new(code_hash_of(pre), to));
202
203 let empty = BTreeMap::new();
204 let pre_slots = pre.and_then(|p| p.storage.as_ref()).unwrap_or(&empty);
205 let post_slots = post.storage.as_ref().unwrap_or(&empty);
206 for slot in pre_slots.keys().chain(post_slots.keys()) {
207 let from = pre_slots.get(slot).copied().unwrap_or(B256::ZERO);
208 let to = post_slots.get(slot).copied().unwrap_or(B256::ZERO);
209 if let Some(c) = Change::new(from, to) {
210 d.storage.insert(*slot, c);
211 }
212 }
213 d
214}
215
216fn merge_field<T: Clone + PartialEq>(a: &Option<Change<T>>, b: &Option<Change<T>>) -> Option<Change<T>> {
217 match (a, b) {
218 (None, None) => None,
219 (Some(x), None) => Some(x.clone()),
220 (None, Some(y)) => Some(y.clone()),
221 (Some(x), Some(y)) => Change::new(x.from.clone(), y.to.clone()),
222 }
223}
224
225fn merge(earlier: AccountDiff, later: &AccountDiff) -> AccountDiff {
226 use AccountStatus::*;
227 let status = match (earlier.status, later.status) {
228 (Created, Destroyed) => Created,
229 (_, Destroyed) => Destroyed,
230 (Created, _) => Created,
231 (Destroyed, _) => Modified,
232 (Modified, s) => s,
233 };
234 let mut storage = earlier.storage;
235 for (slot, change) in &later.storage {
236 match storage.remove(slot) {
237 None => {
238 storage.insert(*slot, *change);
239 }
240 Some(first) => {
241 if let Some(c) = Change::new(first.from, change.to) {
242 storage.insert(*slot, c);
243 }
244 }
245 }
246 }
247 AccountDiff {
248 status,
249 balance: merge_field(&earlier.balance, &later.balance),
250 nonce: merge_field(&earlier.nonce, &later.nonce),
251 code: merge_field(&earlier.code, &later.code),
252 code_hash: merge_field(&earlier.code_hash, &later.code_hash),
253 storage,
254 }
255}
256
257#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
259pub struct TxDiff {
260 #[serde(skip_serializing_if = "Option::is_none")]
261 pub tx_hash: Option<B256>,
262 pub diff: StateDiff,
263}
264
265pub fn diffs_from_traces(traces: &[TxTrace]) -> Vec<TxDiff> {
266 traces.iter().map(|t| TxDiff { tx_hash: t.tx_hash, diff: StateDiff::from_prestate(&t.diff) }).collect()
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272 use crate::trace::parse_traces_str;
273
274 fn addr(n: u8) -> Address {
275 Address([n; 20])
276 }
277
278 fn word(n: u64) -> B256 {
279 let mut w = [0u8; 32];
280 w[24..].copy_from_slice(&n.to_be_bytes());
281 B256(w)
282 }
283
284 fn diff(json: &str) -> StateDiff {
285 StateDiff::from_prestate(&parse_traces_str(json).unwrap()[0].diff)
286 }
287
288 const A: &str = "0x0101010101010101010101010101010101010101";
289
290 #[test]
291 fn transfer_to_new_account() {
292 let d = diff(&format!(
293 r#"{{"pre":{{"{A}":{{"balance":"0x64","nonce":3}}}},
294 "post":{{"{A}":{{"balance":"0x50","nonce":4}},"0x0202020202020202020202020202020202020202":{{"balance":"0xa"}}}}}}"#
295 ));
296 let sender = &d.accounts[&addr(1)];
297 assert_eq!(sender.status, AccountStatus::Modified);
298 assert_eq!(sender.balance, Some(Change { from: U256::from_u128(100), to: U256::from_u128(80) }));
299 assert_eq!(sender.nonce, Some(Change { from: 3, to: 4 }));
300 let receiver = &d.accounts[&addr(2)];
301 assert_eq!(receiver.status, AccountStatus::Created);
302 assert_eq!(receiver.balance, Some(Change { from: U256::ZERO, to: U256::from_u128(10) }));
303 let s = d.summary();
304 assert_eq!((s.accounts, s.created, s.balance_changes), (2, 1, 2));
305 assert_eq!(s.total_credited, U256::from_u128(10));
306 assert_eq!(s.total_debited, U256::from_u128(20));
307 }
308
309 #[test]
310 fn storage_set_cleared_and_unchanged_fields() {
311 let d = diff(&format!(
312 r#"{{"pre":{{"{A}":{{"balance":"0x1","storage":{{"0x1":"0x7","0x2":"0x9"}}}}}},
313 "post":{{"{A}":{{"storage":{{"0x2":"0xa","0x3":"0x5"}}}}}}}}"#
314 ));
315 let a = &d.accounts[&addr(1)];
316 assert_eq!(a.balance, None, "balance missing from post is unchanged");
317 assert_eq!(a.storage[&word(1)], Change { from: word(7), to: word(0) }, "cleared slot");
318 assert_eq!(a.storage[&word(2)], Change { from: word(9), to: word(10) });
319 assert_eq!(a.storage[&word(3)], Change { from: word(0), to: word(5) }, "new slot");
320 }
321
322 #[test]
323 fn contract_creation_and_destruction() {
324 let created = diff(&format!(
325 r#"{{"pre":{{}},"post":{{"{A}":{{"nonce":1,"code":"0x6000","storage":{{"0x0":"0x1"}}}}}}}}"#
326 ));
327 let c = &created.accounts[&addr(1)];
328 assert_eq!(c.status, AccountStatus::Created);
329 assert_eq!(c.code.as_ref().unwrap().to.0, vec![0x60, 0x00]);
330 assert_eq!(c.nonce, Some(Change { from: 0, to: 1 }));
331
332 let destroyed = diff(&format!(
333 r#"{{"pre":{{"{A}":{{"balance":"0x5","nonce":1,"code":"0x6000","storage":{{"0x0":"0x1","0x1":"0x0"}}}}}},"post":{{}}}}"#
334 ));
335 let d = &destroyed.accounts[&addr(1)];
336 assert_eq!(d.status, AccountStatus::Destroyed);
337 assert_eq!(d.balance.unwrap().to, U256::ZERO);
338 assert_eq!(d.code.as_ref().unwrap().to, Bytes::default());
339 assert_eq!(d.storage.len(), 1, "zero-valued slots are not changes");
340 }
341
342 #[test]
343 fn composition_nets_out_changes() {
344 let first = diff(&format!(
345 r#"{{"pre":{{"{A}":{{"balance":"0x10","storage":{{"0x1":"0x1"}}}}}},"post":{{"{A}":{{"balance":"0x20","storage":{{"0x1":"0x2"}}}}}}}}"#
346 ));
347 let second = diff(&format!(
348 r#"{{"pre":{{"{A}":{{"balance":"0x20","storage":{{"0x1":"0x2"}}}}}},"post":{{"{A}":{{"balance":"0x10","storage":{{"0x1":"0x3"}}}}}}}}"#
349 ));
350 let net = StateDiff::compose([&first, &second]);
351 let a = &net.accounts[&addr(1)];
352 assert_eq!(a.balance, None, "balance returned to its starting value");
353 assert_eq!(a.storage[&word(1)], Change { from: word(1), to: word(3) });
354
355 let back = diff(&format!(
356 r#"{{"pre":{{"{A}":{{"balance":"0x10","storage":{{"0x1":"0x3"}}}}}},"post":{{"{A}":{{"storage":{{"0x1":"0x1"}}}}}}}}"#
357 ));
358 assert!(StateDiff::compose([&first, &second, &back]).is_empty());
359 }
360
361 #[test]
362 fn created_then_destroyed_vanishes() {
363 let created = diff(&format!(r#"{{"pre":{{}},"post":{{"{A}":{{"balance":"0x5"}}}}}}"#));
364 let destroyed = diff(&format!(r#"{{"pre":{{"{A}":{{"balance":"0x5"}}}},"post":{{}}}}"#));
365 assert!(StateDiff::compose([&created, &destroyed]).is_empty());
366 let recreated = StateDiff::compose([&destroyed, &created]);
367 assert!(recreated.is_empty(), "destroyed then recreated with the same balance is no net change");
368 }
369}