1use crate::{
2 account::LevmAccount,
3 constants::*,
4 errors::{ContextResult, ExceptionalHalt, InternalError, TxValidationError, VMError},
5 gas_cost::{STANDARD_TOKEN_COST, floor_tokens_in_access_list, total_cost_floor_per_token},
6 hooks::hook::Hook,
7 utils::*,
8 vm::VM,
9};
10
11use ethrex_common::{
12 Address, H256, U256,
13 types::{Code, Fork},
14};
15
16pub const MAX_REFUND_QUOTIENT: u64 = 5;
17
18pub struct DefaultHook;
19
20impl Hook for DefaultHook {
21 fn prepare_execution(&mut self, vm: &mut VM<'_>) -> Result<(), VMError> {
30 if vm.env.is_system_call {
35 let intrinsic = vm.get_intrinsic_gas()?;
36 vm.add_intrinsic_gas(&intrinsic)?;
37 transfer_value(vm)?;
38 set_bytecode_and_code_address(vm)?;
39 return Ok(());
40 }
41
42 let sender_address = vm.env.origin;
43 let sender_info = vm.db.get_account(sender_address)?.info.clone();
44
45 let intrinsic = vm.get_intrinsic_gas()?;
49
50 if vm.env.config.fork >= Fork::Prague {
51 validate_min_gas_limit(vm, &intrinsic)?;
52 if vm.env.config.fork >= Fork::Osaka
55 && vm.env.config.fork < Fork::Amsterdam
56 && vm.tx.gas_limit() > POST_OSAKA_GAS_LIMIT_CAP
57 {
58 return Err(VMError::TxValidation(
59 TxValidationError::TxMaxGasLimitExceeded {
60 tx_hash: vm.tx.hash(),
61 tx_gas_limit: vm.tx.gas_limit(),
62 },
63 ));
64 }
65 }
66
67 let gaslimit_price_product = vm
69 .env
70 .gas_price
71 .checked_mul(vm.env.gas_limit.into())
72 .ok_or(TxValidationError::GasLimitPriceProductOverflow)?;
73
74 validate_sender_balance(vm, sender_info.balance)?;
75
76 if let Some(tx_max_fee_per_blob_gas) = vm.env.tx_max_fee_per_blob_gas {
78 validate_max_fee_per_blob_gas(vm, tx_max_fee_per_blob_gas)?;
79 }
80
81 deduct_caller(vm, gaslimit_price_product, sender_address)?;
83
84 validate_sufficient_max_fee_per_gas(vm)?;
86
87 if vm.is_create()? {
89 validate_init_code_size(vm)?;
90 }
91
92 vm.add_intrinsic_gas(&intrinsic)?;
94
95 vm.increment_account_nonce(sender_address)
97 .map_err(|_| TxValidationError::NonceIsMax)?;
98
99 if sender_info.nonce != vm.env.tx_nonce {
101 return Err(TxValidationError::NonceMismatch {
102 expected: sender_info.nonce,
103 actual: vm.env.tx_nonce,
104 }
105 .into());
106 }
107
108 if let (Some(tx_max_priority_fee), Some(tx_max_fee_per_gas)) = (
110 vm.env.tx_max_priority_fee_per_gas,
111 vm.env.tx_max_fee_per_gas,
112 ) && tx_max_priority_fee > tx_max_fee_per_gas
113 {
114 return Err(TxValidationError::PriorityGreaterThanMaxFeePerGas {
115 priority_fee: tx_max_priority_fee,
116 max_fee_per_gas: tx_max_fee_per_gas,
117 }
118 .into());
119 }
120
121 let code = vm.db.get_code(sender_info.code_hash)?;
123 validate_sender(sender_address, code.code())?;
124
125 validate_gas_allowance(vm)?;
127
128 if vm.env.tx_max_fee_per_blob_gas.is_some() {
130 validate_4844_tx(vm)?;
131 }
132
133 if vm.tx.authorization_list().is_some() {
136 validate_type_4_tx(vm)?;
137 }
138
139 transfer_value(vm)?;
140
141 set_bytecode_and_code_address(vm)?;
142
143 Ok(())
144 }
145
146 fn finalize_execution(
152 &mut self,
153 vm: &mut VM<'_>,
154 ctx_result: &mut ContextResult,
155 ) -> Result<(), VMError> {
156 if vm.env.is_system_call {
162 delete_self_destruct_accounts(vm)?;
163 return Ok(());
164 }
165
166 if !ctx_result.is_success() {
167 undo_value_transfer(vm)?;
168 }
169
170 if vm.env.config.fork >= Fork::Amsterdam && ctx_result.is_collision() {
179 let gas_limit = vm.env.gas_limit;
180 let state_refund_signed =
184 i64::try_from(vm.state_refund).map_err(|_| InternalError::Overflow)?;
185 let state_gas: u64 =
186 u64::try_from(vm.state_gas_used.saturating_sub(state_refund_signed).max(0))
187 .map_err(|_| InternalError::Overflow)?;
188 let floor = vm.get_min_gas_used()?;
189 let regular_gas = gas_limit.saturating_sub(vm.state_gas_reservoir);
194 let effective_regular = regular_gas.max(floor);
195 ctx_result.gas_used = effective_regular
196 .checked_add(state_gas)
197 .ok_or(InternalError::Overflow)?;
198 ctx_result.gas_spent = effective_regular;
201 pay_coinbase(vm, effective_regular)?;
202 let gas_to_return = gas_limit
203 .checked_sub(effective_regular)
204 .ok_or(InternalError::Underflow)?;
205 let wei_return_amount = vm
206 .env
207 .gas_price
208 .checked_mul(U256::from(gas_to_return))
209 .ok_or(InternalError::Overflow)?;
210 vm.increase_account_balance(vm.env.origin, wei_return_amount)?;
211 return Ok(());
212 }
213
214 if vm.env.config.fork >= Fork::Amsterdam {
218 ctx_result.gas_used = ctx_result.gas_used.saturating_sub(vm.state_gas_reservoir);
219 }
220
221 let gas_used_pre_refund = ctx_result.gas_used;
223
224 let gas_refunded: u64 = compute_gas_refunded(vm, ctx_result)?;
229 let gas_spent = compute_actual_gas_used(vm, gas_refunded, gas_used_pre_refund)?;
230
231 refund_sender(vm, ctx_result, gas_refunded, gas_spent)?;
232
233 pay_coinbase(vm, gas_spent)?;
234
235 delete_self_destruct_accounts(vm)?;
236
237 Ok(())
238 }
239}
240
241pub fn undo_value_transfer(vm: &mut VM<'_>) -> Result<(), VMError> {
242 if !vm.is_create()? {
244 vm.decrease_account_balance(vm.current_call_frame.to, vm.current_call_frame.msg_value)?;
245 }
246
247 vm.increase_account_balance(vm.env.origin, vm.current_call_frame.msg_value)?;
248
249 Ok(())
250}
251
252pub fn refund_sender(
256 vm: &mut VM<'_>,
257 ctx_result: &mut ContextResult,
258 refunded_gas: u64,
259 gas_spent: u64,
260) -> Result<(), VMError> {
261 vm.substate.refunded_gas = refunded_gas;
262
263 if vm.env.config.fork >= Fork::Amsterdam {
267 let floor = vm.get_min_gas_used()?;
269 let state_refund_signed =
272 i64::try_from(vm.state_refund).map_err(|_| InternalError::Overflow)?;
273 let state_gas: u64 =
274 u64::try_from(vm.state_gas_used.saturating_sub(state_refund_signed).max(0))
275 .map_err(|_| InternalError::Overflow)?;
276 #[expect(clippy::as_conversions, reason = "gas_remaining is >= 0 here")]
280 let gas_remaining = vm.current_call_frame.gas_remaining.max(0) as u64;
281 let raw_consumed = vm.env.gas_limit.saturating_sub(gas_remaining);
282 let regular_gas = raw_consumed
286 .saturating_sub(vm.intrinsic_state_gas)
287 .saturating_sub(vm.state_gas_reservoir_initial)
288 .saturating_sub(vm.state_gas_spill);
289 let effective_regular = regular_gas.max(floor);
290 ctx_result.gas_used = effective_regular
291 .checked_add(state_gas)
292 .ok_or(InternalError::Overflow)?;
293 ctx_result.gas_spent = gas_spent;
295 } else {
296 ctx_result.gas_used = gas_spent;
298 ctx_result.gas_spent = gas_spent;
299 }
300
301 let gas_to_return = vm
303 .env
304 .gas_limit
305 .checked_sub(gas_spent)
306 .ok_or(InternalError::Underflow)?;
307
308 let wei_return_amount = vm
309 .env
310 .gas_price
311 .checked_mul(U256::from(gas_to_return))
312 .ok_or(InternalError::Overflow)?;
313
314 vm.increase_account_balance(vm.env.origin, wei_return_amount)?;
315
316 Ok(())
317}
318
319pub fn compute_gas_refunded(vm: &VM<'_>, ctx_result: &ContextResult) -> Result<u64, VMError> {
321 Ok(vm
322 .substate
323 .refunded_gas
324 .min(ctx_result.gas_used / MAX_REFUND_QUOTIENT))
325}
326
327pub fn compute_actual_gas_used(
329 vm: &mut VM<'_>,
330 refunded_gas: u64,
331 gas_used_without_refunds: u64,
332) -> Result<u64, VMError> {
333 let exec_gas_consumed = gas_used_without_refunds
334 .checked_sub(refunded_gas)
335 .ok_or(InternalError::Underflow)?;
336
337 if vm.env.config.fork >= Fork::Prague {
338 Ok(exec_gas_consumed.max(vm.get_min_gas_used()?))
339 } else {
340 Ok(exec_gas_consumed)
341 }
342}
343
344pub fn pay_coinbase(vm: &mut VM<'_>, gas_to_pay: u64) -> Result<(), VMError> {
345 let priority_fee_per_gas = vm
346 .env
347 .gas_price
348 .checked_sub(vm.env.base_fee_per_gas)
349 .ok_or(InternalError::Underflow)?;
350
351 let coinbase_fee = U256::from(gas_to_pay)
352 .checked_mul(priority_fee_per_gas)
353 .ok_or(InternalError::Overflow)?;
354
355 if !vm.env.gas_price.is_zero()
359 && let Some(recorder) = vm.db.bal_recorder.as_mut()
360 {
361 recorder.record_touched_address(vm.env.coinbase);
362 }
363
364 if !coinbase_fee.is_zero() {
366 vm.increase_account_balance(vm.env.coinbase, coinbase_fee)?;
367 } else if !vm.env.is_system_call {
368 vm.db.get_account(vm.env.coinbase)?;
376 }
377
378 Ok(())
379}
380
381pub fn delete_self_destruct_accounts(vm: &mut VM<'_>) -> Result<(), VMError> {
383 if vm.env.config.fork >= Fork::Amsterdam {
386 let mut addresses_with_balance: Vec<(Address, U256)> = vm
387 .substate
388 .iter_selfdestruct()
389 .filter_map(|addr| {
390 let balance = vm.db.get_account(*addr).ok()?.info.balance;
391 if !balance.is_zero() {
392 Some((*addr, balance))
393 } else {
394 None
395 }
396 })
397 .collect();
398
399 addresses_with_balance.sort_by_key(|(addr, _)| *addr);
401
402 for (addr, balance) in addresses_with_balance {
403 let log = create_burn_log(addr, balance);
404 vm.substate.add_log(log);
405 }
406 }
407
408 for address in vm.substate.iter_selfdestruct() {
410 let account_to_remove = vm.db.get_account(*address)?;
412 vm.current_call_frame
413 .call_frame_backup
414 .backup_account_info(*address, account_to_remove)?;
415
416 let account_to_remove = vm.db.get_account_mut(*address)?;
417 *account_to_remove = LevmAccount::default();
418 account_to_remove.mark_destroyed();
419
420 if let Some(recorder) = vm.db.bal_recorder.as_mut() {
422 recorder.track_selfdestruct(*address);
423 }
424 }
425
426 Ok(())
427}
428
429pub fn validate_min_gas_limit(vm: &mut VM<'_>, intrinsic: &IntrinsicGas) -> Result<(), VMError> {
430 let regular_gas = intrinsic.regular;
432 let state_gas = intrinsic.state;
433 let intrinsic_gas: u64 = regular_gas
434 .checked_add(state_gas)
435 .ok_or(ExceptionalHalt::OutOfGas)?;
436
437 if vm.current_call_frame.gas_limit < intrinsic_gas {
438 return Err(TxValidationError::IntrinsicGasTooLow.into());
439 }
440
441 let fork = vm.env.config.fork;
442
443 let mut tokens_in_calldata: u64 = if fork >= Fork::Amsterdam {
447 let total_bytes: u64 = vm
449 .current_call_frame
450 .calldata
451 .len()
452 .try_into()
453 .map_err(|_| InternalError::TypeConversion)?;
454 total_bytes
455 .checked_mul(STANDARD_TOKEN_COST)
456 .ok_or(InternalError::Overflow)?
457 } else {
458 intrinsic.calldata_cost / STANDARD_TOKEN_COST
461 };
462
463 if fork >= Fork::Amsterdam {
467 let al_floor_tokens = floor_tokens_in_access_list(vm.tx.access_list());
468 tokens_in_calldata = tokens_in_calldata
469 .checked_add(al_floor_tokens)
470 .ok_or(InternalError::Overflow)?;
471 }
472
473 let floor_cost_by_tokens = tokens_in_calldata
476 .checked_mul(total_cost_floor_per_token(fork))
477 .ok_or(InternalError::Overflow)?
478 .checked_add(TX_BASE_COST)
479 .ok_or(InternalError::Overflow)?;
480
481 if vm.env.config.fork >= Fork::Amsterdam
488 && regular_gas.max(floor_cost_by_tokens) > TX_MAX_GAS_LIMIT_AMSTERDAM
489 {
490 return Err(TxValidationError::IntrinsicGasTooLow.into());
491 }
492
493 if vm.current_call_frame.gas_limit < floor_cost_by_tokens {
494 return Err(TxValidationError::IntrinsicGasBelowFloorGasCost.into());
495 }
496
497 Ok(())
498}
499
500pub fn validate_max_fee_per_blob_gas(
501 vm: &mut VM<'_>,
502 tx_max_fee_per_blob_gas: U256,
503) -> Result<(), VMError> {
504 let base_fee_per_blob_gas = vm.env.base_blob_fee_per_gas;
505 if tx_max_fee_per_blob_gas < base_fee_per_blob_gas {
506 return Err(TxValidationError::InsufficientMaxFeePerBlobGas {
507 base_fee_per_blob_gas,
508 tx_max_fee_per_blob_gas,
509 }
510 .into());
511 }
512
513 Ok(())
514}
515
516pub fn validate_init_code_size(vm: &mut VM<'_>) -> Result<(), VMError> {
517 let code_size = vm.current_call_frame.calldata.len();
520 let max_size = if vm.env.config.fork >= Fork::Amsterdam {
521 AMSTERDAM_INIT_CODE_MAX_SIZE
522 } else {
523 INIT_CODE_MAX_SIZE
524 };
525 if code_size > max_size && vm.env.config.fork >= Fork::Shanghai {
526 return Err(TxValidationError::InitcodeSizeExceeded {
527 max_size,
528 actual_size: code_size,
529 }
530 .into());
531 }
532 Ok(())
533}
534
535pub fn validate_sufficient_max_fee_per_gas(vm: &mut VM<'_>) -> Result<(), TxValidationError> {
536 if vm.env.tx_max_fee_per_gas.unwrap_or(vm.env.gas_price) < vm.env.base_fee_per_gas {
537 return Err(TxValidationError::InsufficientMaxFeePerGas);
538 }
539 Ok(())
540}
541
542pub fn validate_4844_tx(vm: &mut VM<'_>) -> Result<(), VMError> {
543 if vm.env.config.fork < Fork::Cancun {
545 return Err(TxValidationError::Type3TxPreFork.into());
546 }
547
548 let blob_hashes = &vm.env.tx_blob_hashes;
549
550 if blob_hashes.is_empty() {
552 return Err(TxValidationError::Type3TxZeroBlobs.into());
553 }
554
555 for blob_hash in blob_hashes {
557 let blob_hash = blob_hash.as_bytes();
558 if blob_hash
559 .first()
560 .is_some_and(|first_byte| !VALID_BLOB_PREFIXES.contains(first_byte))
561 {
562 return Err(TxValidationError::Type3TxInvalidBlobVersionedHash.into());
563 }
564 }
565
566 let max_blob_count = vm
568 .env
569 .config
570 .blob_schedule
571 .max
572 .try_into()
573 .map_err(|_| InternalError::TypeConversion)?;
574 let blob_count = blob_hashes.len();
575 if blob_count > max_blob_count {
576 return Err(TxValidationError::Type3TxBlobCountExceeded {
577 max_blob_count,
578 actual_blob_count: blob_count,
579 }
580 .into());
581 }
582 if vm.env.config.fork >= Fork::Osaka && blob_count > MAX_BLOB_COUNT_TX {
583 return Err(TxValidationError::Type3TxBlobCountExceeded {
584 max_blob_count: MAX_BLOB_COUNT_TX,
585 actual_blob_count: blob_count,
586 }
587 .into());
588 }
589
590 if vm.is_create()? {
598 return Err(TxValidationError::Type3TxContractCreation.into());
599 }
600
601 Ok(())
602}
603
604pub fn validate_type_4_tx(vm: &mut VM<'_>) -> Result<(), VMError> {
605 let Some(auth_list) = vm.tx.authorization_list() else {
606 return Err(InternalError::Custom("Auth list not found".to_string()).into());
608 };
609
610 if vm.env.config.fork < Fork::Prague {
612 return Err(TxValidationError::Type4TxPreFork.into());
613 }
614
615 if vm.is_create()? {
624 return Err(TxValidationError::Type4TxContractCreation.into());
625 }
626
627 if auth_list.is_empty() {
630 return Err(TxValidationError::Type4TxAuthorizationListIsEmpty.into());
631 }
632
633 vm.eip7702_set_access_code()
634}
635
636pub fn validate_sender(sender_address: Address, code: &[u8]) -> Result<(), VMError> {
637 if !code.is_empty() && !code_has_delegation(code)? {
638 return Err(TxValidationError::SenderNotEOA(sender_address).into());
639 }
640 Ok(())
641}
642
643pub fn validate_gas_allowance(vm: &mut VM<'_>) -> Result<(), TxValidationError> {
644 if vm.env.is_system_call {
648 return Ok(());
649 }
650 if vm.env.gas_limit > vm.env.block_gas_limit {
651 return Err(TxValidationError::GasAllowanceExceeded {
652 block_gas_limit: vm.env.block_gas_limit,
653 tx_gas_limit: vm.env.gas_limit,
654 });
655 }
656 Ok(())
657}
658
659pub fn validate_sender_balance(vm: &mut VM<'_>, sender_balance: U256) -> Result<(), VMError> {
660 if vm.env.disable_balance_check {
661 return Ok(());
662 }
663
664 let value = vm.current_call_frame.msg_value;
666
667 let max_blob_gas_cost =
670 get_max_blob_gas_price(&vm.env.tx_blob_hashes, vm.env.tx_max_fee_per_blob_gas)?;
671
672 let gas_fee_for_valid_tx = vm
675 .env
676 .tx_max_fee_per_gas
677 .unwrap_or(vm.env.gas_price)
678 .checked_mul(vm.env.gas_limit.into())
679 .ok_or(TxValidationError::GasLimitPriceProductOverflow)?;
680
681 let balance_for_valid_tx = gas_fee_for_valid_tx
682 .checked_add(value)
683 .ok_or(TxValidationError::InsufficientAccountFunds)?
684 .checked_add(max_blob_gas_cost)
685 .ok_or(TxValidationError::InsufficientAccountFunds)?;
686
687 if sender_balance < balance_for_valid_tx {
688 return Err(TxValidationError::InsufficientAccountFunds.into());
689 }
690
691 Ok(())
692}
693
694pub fn deduct_caller(
695 vm: &mut VM<'_>,
696 gas_limit_price_product: U256,
697 sender_address: Address,
698) -> Result<(), VMError> {
699 if vm.env.disable_balance_check {
700 return Ok(());
701 }
702
703 let value = vm.current_call_frame.msg_value;
705
706 let blob_gas_cost =
707 calculate_blob_gas_cost(&vm.env.tx_blob_hashes, vm.env.base_blob_fee_per_gas)?;
708
709 let up_front_cost = gas_limit_price_product
711 .checked_add(value)
712 .ok_or(TxValidationError::InsufficientAccountFunds)?
713 .checked_add(blob_gas_cost)
714 .ok_or(TxValidationError::InsufficientAccountFunds)?;
715 vm.decrease_account_balance(sender_address, up_front_cost)
721 .map_err(|_| TxValidationError::InsufficientAccountFunds)?;
722
723 Ok(())
724}
725
726pub fn transfer_value(vm: &mut VM<'_>) -> Result<(), VMError> {
728 if !vm.is_create()? {
729 let value = vm.current_call_frame.msg_value;
730 let to = vm.current_call_frame.to;
731
732 vm.increase_account_balance(to, value)?;
733
734 let from = vm.env.origin;
737 if vm.env.config.fork >= Fork::Amsterdam && !value.is_zero() && from != to {
738 let log = create_eth_transfer_log(from, to, value);
739 vm.substate.add_log(log);
740 }
741 }
742 Ok(())
743}
744
745pub fn set_bytecode_and_code_address(vm: &mut VM<'_>) -> Result<(), VMError> {
747 let (bytecode, code_address) = if vm.is_create()? {
749 let calldata = std::mem::take(&mut vm.current_call_frame.calldata);
751 (
752 Code::from_bytecode_unchecked(calldata, H256::zero()),
754 vm.current_call_frame.to,
755 )
756 } else {
757 let to = vm.current_call_frame.to;
759
760 if let Some(recorder) = vm.db.bal_recorder.as_mut() {
762 recorder.record_touched_address(to);
763 }
764
765 let (is_delegation, _eip7702_gas_consumed, code_address, bytecode) =
766 eip7702_get_code(vm.db, &mut vm.substate, to)?;
767
768 if is_delegation && let Some(recorder) = vm.db.bal_recorder.as_mut() {
770 recorder.record_touched_address(code_address);
771 }
772
773 (bytecode, code_address)
774 };
775
776 vm.current_call_frame.code_address = code_address;
778 vm.current_call_frame.set_code(bytecode)?;
779
780 Ok(())
781}