example_erc20_gas/
handler.rs

1use core::cmp::Ordering;
2use revm::{
3    context::{Cfg, JournalOutput},
4    context_interface::{
5        result::{HaltReason, InvalidTransaction},
6        Block, ContextTr, JournalTr, Transaction, TransactionType,
7    },
8    handler::{EvmTr, EvmTrError, Frame, FrameResult, Handler},
9    interpreter::FrameInput,
10    primitives::{hardfork::SpecId, U256},
11};
12
13use crate::{erc_address_storage, token_operation, TOKEN, TREASURY};
14
15pub struct Erc20MainetHandler<EVM, ERROR, FRAME> {
16    _phantom: core::marker::PhantomData<(EVM, ERROR, FRAME)>,
17}
18
19impl<CTX, ERROR, FRAME> Erc20MainetHandler<CTX, ERROR, FRAME> {
20    pub fn new() -> Self {
21        Self {
22            _phantom: core::marker::PhantomData,
23        }
24    }
25}
26
27impl<EVM, ERROR, FRAME> Default for Erc20MainetHandler<EVM, ERROR, FRAME> {
28    fn default() -> Self {
29        Self::new()
30    }
31}
32
33impl<EVM, ERROR, FRAME> Handler for Erc20MainetHandler<EVM, ERROR, FRAME>
34where
35    EVM: EvmTr<Context: ContextTr<Journal: JournalTr<FinalOutput = JournalOutput>>>,
36    FRAME: Frame<Evm = EVM, Error = ERROR, FrameResult = FrameResult, FrameInit = FrameInput>,
37    ERROR: EvmTrError<EVM>,
38{
39    type Evm = EVM;
40    type Error = ERROR;
41    type Frame = FRAME;
42    type HaltReason = HaltReason;
43
44    fn validate_tx_against_state(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
45        let context = evm.ctx();
46        let caller = context.tx().caller();
47        let caller_nonce = context.journal().load_account(caller)?.data.info.nonce;
48        let _ = context.journal().load_account(TOKEN)?.data.clone();
49
50        if !context.cfg().is_nonce_check_disabled() {
51            let tx_nonce = context.tx().nonce();
52            let state_nonce = caller_nonce;
53            match tx_nonce.cmp(&state_nonce) {
54                Ordering::Less => {
55                    return Err(ERROR::from(InvalidTransaction::NonceTooLow {
56                        tx: tx_nonce,
57                        state: state_nonce,
58                    }))
59                }
60                Ordering::Greater => {
61                    return Err(ERROR::from(InvalidTransaction::NonceTooHigh {
62                        tx: tx_nonce,
63                        state: state_nonce,
64                    }))
65                }
66                _ => (),
67            }
68        }
69
70        let mut balance_check = U256::from(context.tx().gas_limit())
71            .checked_mul(U256::from(context.tx().max_fee_per_gas()))
72            .and_then(|gas_cost| gas_cost.checked_add(context.tx().value()))
73            .ok_or(InvalidTransaction::OverflowPaymentInTransaction)?;
74
75        if context.tx().tx_type() == TransactionType::Eip4844 {
76            let tx = context.tx();
77            let data_fee = tx.calc_max_data_fee();
78            balance_check = balance_check
79                .checked_add(data_fee)
80                .ok_or(InvalidTransaction::OverflowPaymentInTransaction)?;
81        }
82
83        let account_balance_slot = erc_address_storage(caller);
84        let account_balance = context
85            .journal()
86            .sload(TOKEN, account_balance_slot)
87            .map(|v| v.data)
88            .unwrap_or_default();
89
90        if account_balance < balance_check && !context.cfg().is_balance_check_disabled() {
91            return Err(InvalidTransaction::LackOfFundForMaxFee {
92                fee: Box::new(balance_check),
93                balance: Box::new(account_balance),
94            }
95            .into());
96        };
97
98        Ok(())
99    }
100
101    fn deduct_caller(&self, evm: &mut Self::Evm) -> Result<(), Self::Error> {
102        let context = evm.ctx();
103        // load and touch token account
104        let _ = context.journal().load_account(TOKEN)?.data;
105        context.journal().touch_account(TOKEN);
106
107        let basefee = context.block().basefee() as u128;
108        let blob_price = context.block().blob_gasprice().unwrap_or_default();
109        let effective_gas_price = context.tx().effective_gas_price(basefee);
110
111        let mut gas_cost = (context.tx().gas_limit() as u128).saturating_mul(effective_gas_price);
112
113        if context.tx().tx_type() == TransactionType::Eip4844 {
114            let blob_gas = context.tx().total_blob_gas() as u128;
115            gas_cost = gas_cost.saturating_add(blob_price.saturating_mul(blob_gas));
116        }
117
118        let caller = context.tx().caller();
119        println!("Deduct caller: {:?} for amount: {gas_cost:?}", caller);
120        token_operation::<EVM::Context, ERROR>(context, caller, TREASURY, U256::from(gas_cost))?;
121
122        Ok(())
123    }
124
125    fn reimburse_caller(
126        &self,
127        evm: &mut Self::Evm,
128        exec_result: &mut <Self::Frame as Frame>::FrameResult,
129    ) -> Result<(), Self::Error> {
130        let context = evm.ctx();
131        let basefee = context.block().basefee() as u128;
132        let caller = context.tx().caller();
133        let effective_gas_price = context.tx().effective_gas_price(basefee);
134        let gas = exec_result.gas();
135
136        let reimbursement =
137            effective_gas_price.saturating_mul((gas.remaining() + gas.refunded() as u64) as u128);
138        token_operation::<EVM::Context, ERROR>(
139            context,
140            TREASURY,
141            caller,
142            U256::from(reimbursement),
143        )?;
144
145        Ok(())
146    }
147
148    fn reward_beneficiary(
149        &self,
150        evm: &mut Self::Evm,
151        exec_result: &mut <Self::Frame as Frame>::FrameResult,
152    ) -> Result<(), Self::Error> {
153        let context = evm.ctx();
154        let tx = context.tx();
155        let beneficiary = context.block().beneficiary();
156        let basefee = context.block().basefee() as u128;
157        let effective_gas_price = tx.effective_gas_price(basefee);
158        let gas = exec_result.gas();
159
160        let coinbase_gas_price = if context.cfg().spec().into().is_enabled_in(SpecId::LONDON) {
161            effective_gas_price.saturating_sub(basefee)
162        } else {
163            effective_gas_price
164        };
165
166        let reward =
167            coinbase_gas_price.saturating_mul((gas.spent() - gas.refunded() as u64) as u128);
168        token_operation::<EVM::Context, ERROR>(context, TREASURY, beneficiary, U256::from(reward))?;
169
170        Ok(())
171    }
172}