example_erc20_gas/
handler.rs

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