1use std::collections::HashMap;
2
3use alloy_primitives::B256;
4use anyhow::Ok;
5use anyhow::anyhow;
6use display_json::DebugAsJson;
7use hex_literal::hex;
8use revm::primitives::alloy_primitives;
9use serde_with::serde_as;
10
11use crate::eth::primitives::Account;
12use crate::eth::primitives::Address;
13use crate::eth::primitives::Bytes;
14use crate::eth::primitives::ExecutionAccountChanges;
15use crate::eth::primitives::ExecutionResult;
16use crate::eth::primitives::ExternalReceipt;
17use crate::eth::primitives::Gas;
18use crate::eth::primitives::Log;
19use crate::eth::primitives::Slot;
20use crate::eth::primitives::SlotIndex;
21use crate::eth::primitives::SlotValue;
22use crate::eth::primitives::UnixTime;
23use crate::eth::primitives::Wei;
24use crate::ext::not;
25use crate::log_and_err;
26
27#[serde_as]
28#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
29#[cfg_attr(test, derive(fake::Dummy))]
30pub struct ExecutionChanges {
31 pub accounts: HashMap<Address, ExecutionAccountChanges, hash_hasher::HashBuildHasher>,
32 #[serde_as(as = "Vec<(_, _)>")]
33 pub slots: HashMap<(Address, SlotIndex), SlotValue, hash_hasher::HashBuildHasher>,
34}
35
36impl ExecutionChanges {
37 pub fn insert(&mut self, account: Account, slots: Vec<Slot>) {
38 let address = account.address;
39 match self.accounts.entry(address) {
40 std::collections::hash_map::Entry::Occupied(mut entry) => {
41 let changes = entry.get_mut();
42 changes.apply_modifications(account);
43 }
44 std::collections::hash_map::Entry::Vacant(entry) => {
45 entry.insert(ExecutionAccountChanges::from_changed(account));
46 }
47 }
48
49 for slot in slots {
50 self.slots.insert((address, slot.index), slot.value);
51 }
52 }
53
54 pub fn merge(&mut self, other: ExecutionChanges) {
55 for (address, changes) in other.accounts {
56 match self.accounts.entry(address) {
57 std::collections::hash_map::Entry::Occupied(mut entry) => {
58 let current_changes = entry.get_mut();
59 current_changes.merge(changes);
60 }
61 std::collections::hash_map::Entry::Vacant(entry) => {
62 entry.insert(changes);
63 }
64 }
65 }
66 self.slots.extend(other.slots);
67 }
68}
69
70#[derive(DebugAsJson, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Default)]
72#[cfg_attr(test, derive(fake::Dummy))]
73pub struct EvmExecution {
74 pub block_timestamp: UnixTime, pub result: ExecutionResult,
79
80 pub output: Bytes,
82
83 pub logs: Vec<Log>,
85
86 pub gas_used: Gas,
88
89 pub changes: ExecutionChanges,
91
92 pub deployed_contract_address: Option<Address>,
94}
95
96impl EvmExecution {
97 pub fn from_failed_external_transaction(sender: Account, receipt: &ExternalReceipt, block_timestamp: UnixTime) -> anyhow::Result<Self> {
99 if receipt.is_success() {
100 return log_and_err!("cannot create failed execution for successful transaction");
101 }
102 if not(receipt.inner.logs().is_empty()) {
103 return log_and_err!("failed receipt should not have produced logs");
104 }
105
106 let address = sender.address;
108 let mut sender_changes = ExecutionAccountChanges::from_unchanged(sender);
109 let sender_next_nonce = sender_changes.nonce.next_nonce();
110
111 sender_changes.nonce.apply(sender_next_nonce);
112 let mut changes = ExecutionChanges::default();
113 changes.accounts.insert(address, sender_changes);
114
115 let mut execution = Self {
117 block_timestamp,
118 result: ExecutionResult::new_reverted("reverted externally".into()), output: Bytes::default(), logs: Vec::new(),
121 gas_used: Gas::from(receipt.gas_used),
122 changes,
123 deployed_contract_address: None,
124 };
125 execution.apply_receipt(receipt)?;
126 Ok(execution)
127 }
128
129 pub fn is_success(&self) -> bool {
131 self.result.is_success()
132 }
133
134 pub fn is_failure(&self) -> bool {
136 not(self.is_success())
137 }
138
139 pub fn contract_address(&self) -> Option<Address> {
141 if let Some(contract_address) = &self.deployed_contract_address {
142 return Some(contract_address.to_owned());
143 }
144
145 None
146 }
147
148 pub fn compare_with_receipt(&self, receipt: &ExternalReceipt) -> anyhow::Result<()> {
150 if self.is_success() != receipt.is_success() {
152 return log_and_err!(format!(
153 "transaction status mismatch | hash={} execution={:?} receipt={:?}",
154 receipt.hash(),
155 self.result,
156 receipt.status()
157 ));
158 }
159
160 let receipt_logs = receipt.inner.logs();
161
162 if self.logs.len() != receipt_logs.len() {
164 tracing::trace!(logs = ?self.logs, "execution logs");
165 tracing::trace!(logs = ?receipt_logs, "receipt logs");
166 return log_and_err!(format!(
167 "logs length mismatch | hash={} execution={} receipt={}",
168 receipt.hash(),
169 self.logs.len(),
170 receipt_logs.len()
171 ));
172 }
173
174 for (log_index, (execution_log, receipt_log)) in self.logs.iter().zip(receipt_logs).enumerate() {
176 if execution_log.topics_non_empty().len() != receipt_log.topics().len() {
178 return log_and_err!(format!(
179 "log topics length mismatch | hash={} log_index={} execution={} receipt={}",
180 receipt.hash(),
181 log_index,
182 execution_log.topics_non_empty().len(),
183 receipt_log.topics().len(),
184 ));
185 }
186
187 for (topic_index, (execution_log_topic, receipt_log_topic)) in execution_log.topics_non_empty().iter().zip(receipt_log.topics().iter()).enumerate()
189 {
190 if B256::from(*execution_log_topic) != *receipt_log_topic {
191 return log_and_err!(format!(
192 "log topic content mismatch | hash={} log_index={} topic_index={} execution={} receipt={:#x}",
193 receipt.hash(),
194 log_index,
195 topic_index,
196 execution_log_topic,
197 receipt_log_topic,
198 ));
199 }
200 }
201
202 if execution_log.data.as_ref() != receipt_log.data().data.as_ref() {
204 return log_and_err!(format!(
205 "log data content mismatch | hash={} log_index={} execution={} receipt={:#x}",
206 receipt.hash(),
207 log_index,
208 execution_log.data,
209 receipt_log.data().data,
210 ));
211 }
212 }
213 Ok(())
214 }
215
216 pub fn apply_receipt(&mut self, receipt: &ExternalReceipt) -> anyhow::Result<()> {
222 self.gas_used = Gas::from(receipt.gas_used);
224
225 self.fix_logs_gas_left(receipt);
227
228 let execution_cost = receipt.execution_cost();
230
231 if execution_cost > Wei::ZERO {
232 let sender_address: Address = receipt.0.from.into();
234 let Some(sender_changes) = self.changes.accounts.get_mut(&sender_address) else {
235 return log_and_err!("sender changes not present in execution when applying execution costs");
236 };
237
238 let sender_balance = *sender_changes.balance.value();
240
241 let sender_new_balance = if sender_balance > execution_cost {
242 sender_balance - execution_cost
243 } else {
244 Wei::ZERO
245 };
246 sender_changes.balance.apply(sender_new_balance);
247 }
248
249 Ok(())
250 }
251
252 fn fix_logs_gas_left(&mut self, receipt: &ExternalReceipt) {
265 const ERC20_TRACE_EVENT_HASH: [u8; 32] = hex!("31738ac4a7c9a10ecbbfd3fed5037971ba81b8f6aa4f72a23f5364e9bc76d671");
266 const BALANCE_TRACKER_TRACE_EVENT_HASH: [u8; 32] = hex!("63f1e32b72965e2be75e03024856287aff9e4cdbcec65869c51014fc2c1c95d9");
267
268 const EVENT_HASHES: [&[u8]; 2] = [&ERC20_TRACE_EVENT_HASH, &BALANCE_TRACKER_TRACE_EVENT_HASH];
269
270 let receipt_logs = receipt.inner.logs();
271
272 for (execution_log, receipt_log) in self.logs.iter_mut().zip(receipt_logs) {
273 let execution_log_matches = || execution_log.topic0.is_some_and(|topic| EVENT_HASHES.contains(&topic.as_ref()));
274 let receipt_log_matches = || receipt_log.topics().first().is_some_and(|topic| EVENT_HASHES.contains(&topic.as_ref()));
275
276 let should_overwrite = execution_log_matches() && receipt_log_matches();
278 if !should_overwrite {
279 continue;
280 }
281
282 let (Some(destination), Some(source)) = (execution_log.data.get_mut(0..32), receipt_log.data().data.get(0..32)) else {
283 continue;
284 };
285 destination.copy_from_slice(source);
286 }
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use fake::Fake;
293 use fake::Faker;
294
295 use super::*;
296 use crate::eth::primitives::Nonce;
297
298 #[test]
299 fn test_from_failed_external_transaction() {
300 let sender_address: Address = Faker.fake();
302 let sender = Account {
303 address: sender_address,
304 nonce: Nonce::from(1u64),
305 balance: Wei::from(1000u64),
306 bytecode: None,
307 };
308
309 let mut receipt: ExternalReceipt = Faker.fake();
311 let mut inner_receipt = receipt.0.clone();
312
313 if let alloy_consensus::ReceiptEnvelope::Legacy(ref mut r) = inner_receipt.inner {
315 r.receipt.status = alloy_consensus::Eip658Value::Eip658(false);
316 r.receipt.logs.clear();
317 } else {
318 panic!("expected be legacy!")
319 }
320
321 inner_receipt.from = sender_address.into();
323 receipt.0 = inner_receipt;
324
325 let timestamp = UnixTime::now();
327
328 let execution = EvmExecution::from_failed_external_transaction(sender.clone(), &receipt, timestamp).unwrap();
330
331 assert_eq!(execution.block_timestamp, timestamp);
333 assert!(execution.is_failure());
334 assert_eq!(execution.output, Bytes::default());
335 assert!(execution.logs.is_empty());
336 assert_eq!(execution.gas_used, Gas::from(receipt.gas_used));
337
338 let sender_changes = execution.changes.accounts.get(&sender_address).unwrap();
340
341 let modified_nonce = *sender_changes.nonce.value();
343 assert_eq!(modified_nonce, Nonce::from(2u64));
344
345 if receipt.execution_cost() > Wei::ZERO {
347 let modified_balance = *sender_changes.balance.value();
348 assert!(sender.balance >= modified_balance);
349 }
350 }
351
352 #[test]
353 fn test_compare_with_receipt_success_status_mismatch() {
354 let mut execution: EvmExecution = Faker.fake();
356 execution.result = ExecutionResult::Success;
357
358 let mut receipt: ExternalReceipt = Faker.fake();
360 if let alloy_consensus::ReceiptEnvelope::Legacy(r) = &mut receipt.0.inner {
361 r.receipt.status = alloy_consensus::Eip658Value::Eip658(false);
362 } else {
363 panic!("expected be legacy!")
364 }
365
366 assert!(execution.compare_with_receipt(&receipt).is_err());
368 }
369
370 #[test]
371 fn test_compare_with_receipt_logs_length_mismatch() {
372 let mut execution: EvmExecution = Faker.fake();
374 execution.result = ExecutionResult::Success;
375 execution.logs = vec![Faker.fake(), Faker.fake()]; let mut receipt: ExternalReceipt = Faker.fake();
379 if let alloy_consensus::ReceiptEnvelope::Legacy(r) = &mut receipt.0.inner {
380 r.receipt.status = alloy_consensus::Eip658Value::Eip658(true);
381 r.receipt.logs = vec![alloy_rpc_types_eth::Log::default()]; } else {
383 panic!("expected be legacy!")
384 }
385
386 assert!(execution.compare_with_receipt(&receipt).is_err());
388 }
389
390 #[test]
391 fn test_compare_with_receipt_log_topics_length_mismatch() {
392 let mut log1: Log = Faker.fake();
394 log1.topic0 = Some(Faker.fake());
395 log1.topic1 = Some(Faker.fake());
396 log1.topic2 = None;
397 log1.topic3 = None;
398
399 let mut execution: EvmExecution = Faker.fake();
401 execution.result = ExecutionResult::Success;
402 execution.logs = vec![log1];
403
404 let mut receipt_log = alloy_rpc_types_eth::Log::<alloy_primitives::LogData>::default();
406 let topics = vec![B256::default()];
407 receipt_log.inner.data = alloy_primitives::LogData::new_unchecked(topics, alloy_primitives::Bytes::default());
408 let mut receipt: ExternalReceipt = Faker.fake();
412 if let alloy_consensus::ReceiptEnvelope::Legacy(r) = &mut receipt.0.inner {
413 r.receipt.status = alloy_consensus::Eip658Value::Eip658(true);
414 r.receipt.logs = vec![receipt_log.clone()];
415 } else {
416 panic!("expected be legacy!")
417 }
418
419 assert!(execution.compare_with_receipt(&receipt).is_err());
421 }
422
423 #[test]
424 fn test_compare_with_receipt_topic_content_mismatch() {
425 let topic_value = B256::default();
427 let different_topic = B256::default();
428
429 let mut log1: Log = Faker.fake();
431 log1.topic0 = Some(topic_value.into());
432
433 let mut execution: EvmExecution = Faker.fake();
435 execution.result = ExecutionResult::Success;
436 execution.logs = vec![log1];
437
438 let mut receipt_log = alloy_rpc_types_eth::Log::<alloy_primitives::LogData>::default();
440 let topics = vec![different_topic];
441 receipt_log.inner.data = alloy_primitives::LogData::new_unchecked(topics, alloy_primitives::Bytes::default());
442
443 let mut receipt: ExternalReceipt = Faker.fake();
445 if let alloy_consensus::ReceiptEnvelope::Legacy(r) = &mut receipt.0.inner {
446 r.receipt.status = alloy_consensus::Eip658Value::Eip658(true);
447 r.receipt.logs = vec![receipt_log.clone()];
448 } else {
449 panic!("expected be legacy!")
450 }
451
452 assert!(execution.compare_with_receipt(&receipt).is_err());
454 }
455
456 #[test]
457 fn test_compare_with_receipt_data_content_mismatch() {
458 let mut log1: Log = Faker.fake();
460 log1.topic0 = Some(Faker.fake());
461 log1.data = vec![1, 2, 3, 4].into();
462
463 let mut execution: EvmExecution = Faker.fake();
465 execution.result = ExecutionResult::Success;
466 execution.logs = vec![log1];
467
468 let mut receipt_log = alloy_rpc_types_eth::Log::<alloy_primitives::LogData>::default();
470 let topics = vec![B256::default()];
471 receipt_log.inner.data = alloy_primitives::LogData::new_unchecked(topics, alloy_primitives::Bytes::default());
472 receipt_log.inner.data = alloy_primitives::LogData::new(vec![B256::default()], alloy_primitives::Bytes::from(vec![5, 6, 7, 8])).unwrap();
473
474 let mut receipt: ExternalReceipt = Faker.fake();
476 if let alloy_consensus::ReceiptEnvelope::Legacy(r) = &mut receipt.0.inner {
477 r.receipt.status = alloy_consensus::Eip658Value::Eip658(true);
478 r.receipt.logs = vec![receipt_log.clone()];
479 } else {
480 panic!("expected be legacy!")
481 }
482
483 assert!(execution.compare_with_receipt(&receipt).is_err());
485 }
486
487 #[test]
488 fn test_fix_logs_gas_left() {
489 const ERC20_TRACE_HASH: [u8; 32] = hex!("31738ac4a7c9a10ecbbfd3fed5037971ba81b8f6aa4f72a23f5364e9bc76d671");
491 const BALANCE_TRACKER_TRACE_HASH: [u8; 32] = hex!("63f1e32b72965e2be75e03024856287aff9e4cdbcec65869c51014fc2c1c95d9");
492
493 let mut execution: EvmExecution = Faker.fake();
495 execution.result = ExecutionResult::Success;
496
497 let mut erc20_log: Log = Faker.fake();
499 erc20_log.topic0 = Some(ERC20_TRACE_HASH.into());
500 let execution_gas_left = vec![0u8; 32]; let mut log_data = Vec::with_capacity(execution_gas_left.len() + 32);
502 log_data.extend_from_slice(&execution_gas_left);
503 log_data.extend_from_slice(&[99u8; 32]); erc20_log.data = log_data.into();
505
506 let mut balance_log: Log = Faker.fake();
508 balance_log.topic0 = Some(BALANCE_TRACKER_TRACE_HASH.into());
509 let balance_gas_left = vec![0u8; 32]; balance_log.data = balance_gas_left.into();
511
512 let regular_log: Log = Faker.fake();
514
515 execution.logs = vec![erc20_log, balance_log, regular_log.clone()];
516
517 let receipt_erc20_gas_left = vec![42u8; 32]; let mut erc20_receipt_log = alloy_rpc_types_eth::Log::<alloy_primitives::LogData>::default();
520 let erc20_topics = vec![B256::from_slice(&ERC20_TRACE_HASH)];
521
522 let mut erc20_receipt_data = Vec::with_capacity(receipt_erc20_gas_left.len() + 32);
523 erc20_receipt_data.extend_from_slice(&receipt_erc20_gas_left);
524 erc20_receipt_data.extend_from_slice(&[99u8; 32]); erc20_receipt_log.inner.data = alloy_primitives::LogData::new_unchecked(erc20_topics, alloy_primitives::Bytes::from(erc20_receipt_data));
527
528 let receipt_balance_gas_left = vec![24u8; 32];
530 let mut balance_receipt_log = alloy_rpc_types_eth::Log::<alloy_primitives::LogData>::default();
531 let balance_topics = vec![B256::from_slice(&BALANCE_TRACKER_TRACE_HASH)];
532 balance_receipt_log.inner.data =
533 alloy_primitives::LogData::new_unchecked(balance_topics, alloy_primitives::Bytes::from(receipt_balance_gas_left.clone()));
534
535 let mut regular_receipt_log = alloy_rpc_types_eth::Log::<alloy_primitives::LogData>::default();
537 let regular_topics = Vec::new();
538 regular_receipt_log.inner.data = alloy_primitives::LogData::new_unchecked(regular_topics, alloy_primitives::Bytes::default());
539
540 let mut receipt: ExternalReceipt = Faker.fake();
542 if let alloy_consensus::ReceiptEnvelope::Legacy(r) = &mut receipt.0.inner {
543 r.receipt.status = alloy_consensus::Eip658Value::Eip658(true);
544 r.receipt.logs = vec![erc20_receipt_log.clone(), balance_receipt_log.clone(), regular_receipt_log.clone()];
545 } else {
546 panic!("expected be legacy!")
547 }
548
549 execution.fix_logs_gas_left(&receipt);
551
552 let updated_erc20_data = execution.logs[0].data.as_ref();
554 assert_eq!(&updated_erc20_data[0..32], &receipt_erc20_gas_left[..]);
555 assert_eq!(&updated_erc20_data[32..], &[99u8; 32]);
557
558 assert_eq!(execution.logs[1].data.as_ref(), &receipt_balance_gas_left[..]);
560
561 assert_eq!(execution.logs[2].data, regular_log.data);
563 }
564
565 #[test]
566 fn test_apply_receipt() {
567 let sender_address: Address = Faker.fake();
569 let sender = Account {
570 address: sender_address,
571 nonce: Nonce::from(1u64),
572 balance: Wei::from(1000u64),
573 bytecode: None,
574 };
575
576 let mut execution: EvmExecution = Faker.fake();
578
579 let sender_changes = ExecutionAccountChanges::from_unchanged(sender);
581 let mut accounts = HashMap::with_hasher(hash_hasher::HashBuildHasher::default());
582 accounts.insert(sender_address, sender_changes);
583 execution.changes = ExecutionChanges {
584 accounts,
585 ..Default::default()
586 };
587 execution.gas_used = Gas::from(100u64);
588
589 let mut receipt: ExternalReceipt = Faker.fake();
591 receipt.0.from = sender_address.into();
592 receipt.0.gas_used = 100u64; let gas_price = Wei::from(1u64);
596 receipt.0.effective_gas_price = gas_price.try_into().expect("wei was created with u64 which fits u128 qed.");
597
598 execution.apply_receipt(&receipt).unwrap();
600
601 let sender_changes = execution.changes.accounts.get(&sender_address).unwrap();
603 let modified_balance = *sender_changes.balance.value();
604 assert_eq!(modified_balance, Wei::from(900u64)); }
606}