Skip to main content

example_erc20_gas/
handler.rs

1use revm::{
2    context::{journaled_state::account::JournaledAccountTr, Cfg},
3    context_interface::{result::HaltReason, Block, ContextTr, JournalTr, Transaction},
4    handler::{
5        pre_execution::{calculate_caller_fee, validate_account_nonce_and_code_with_components},
6        EvmTr, EvmTrError, FrameResult, FrameTr, Handler,
7    },
8    interpreter::{interpreter_action::FrameInit, InitialAndFloorGas},
9    primitives::{hardfork::SpecId, U256},
10    state::EvmState,
11};
12
13use crate::{erc_address_storage, TOKEN};
14
15/// Custom handler that implements ERC20 token gas payment.
16/// Instead of paying gas in ETH, transactions pay gas using ERC20 tokens.
17/// The tokens are transferred from the transaction sender to a treasury address.
18#[derive(Debug)]
19pub struct Erc20MainnetHandler<EVM, ERROR, FRAME> {
20    _phantom: core::marker::PhantomData<(EVM, ERROR, FRAME)>,
21}
22
23impl<CTX, ERROR, FRAME> Erc20MainnetHandler<CTX, ERROR, FRAME> {
24    /// Creates a new ERC20 gas payment handler
25    pub const fn new() -> Self {
26        Self {
27            _phantom: core::marker::PhantomData,
28        }
29    }
30}
31
32impl<EVM, ERROR, FRAME> Default for Erc20MainnetHandler<EVM, ERROR, FRAME> {
33    fn default() -> Self {
34        Self::new()
35    }
36}
37
38impl<EVM, ERROR, FRAME> Handler for Erc20MainnetHandler<EVM, ERROR, FRAME>
39where
40    EVM: EvmTr<Context: ContextTr<Journal: JournalTr<State = EvmState>>, Frame = FRAME>,
41    FRAME: FrameTr<FrameResult = FrameResult, FrameInit = FrameInit>,
42    ERROR: EvmTrError<EVM>,
43{
44    type Evm = EVM;
45    type Error = ERROR;
46    type HaltReason = HaltReason;
47
48    fn validate_against_state_and_deduct_caller(
49        &self,
50        evm: &mut Self::Evm,
51        _init_and_floor_gas: &mut InitialAndFloorGas,
52    ) -> Result<(), ERROR> {
53        let (block, tx, cfg, journal, _, _) = evm.ctx_mut().all_mut();
54
55        // load TOKEN contract
56        journal.load_account_mut(TOKEN)?.touch();
57
58        // Load caller's account.
59        let mut caller_account = journal.load_account_with_code_mut(tx.caller())?;
60
61        validate_account_nonce_and_code_with_components(&caller_account.account().info, tx, cfg)?;
62
63        // make changes to the account. Account balance stays the same
64        caller_account.touch();
65        if tx.kind().is_call() {
66            caller_account.bump_nonce();
67        }
68
69        let account_balance_slot = erc_address_storage(tx.caller());
70
71        drop(caller_account); // Drop caller_account to avoid borrow checker issues.
72
73        // load account balance
74        let account_balance = journal.sload(TOKEN, account_balance_slot)?.data;
75
76        let new_balance = calculate_caller_fee(account_balance, tx, block, cfg)?;
77
78        // store deducted balance.
79        journal.sstore(TOKEN, account_balance_slot, new_balance)?;
80
81        Ok(())
82    }
83
84    fn reimburse_caller(
85        &self,
86        evm: &mut Self::Evm,
87        exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
88    ) -> Result<(), Self::Error> {
89        let context = evm.ctx();
90        let basefee = context.block().basefee() as u128;
91        let caller = context.tx().caller();
92        let effective_gas_price = context.tx().effective_gas_price(basefee);
93        let gas = exec_result.gas();
94
95        let reimbursement =
96            effective_gas_price.saturating_mul((gas.remaining() + gas.refunded() as u64) as u128);
97
98        let account_balance_slot = erc_address_storage(caller);
99
100        // load account balance
101        let account_balance = context
102            .journal_mut()
103            .sload(TOKEN, account_balance_slot)?
104            .data;
105
106        // reimburse caller
107        context.journal_mut().sstore(
108            TOKEN,
109            account_balance_slot,
110            account_balance + U256::from(reimbursement),
111        )?;
112
113        Ok(())
114    }
115
116    fn reward_beneficiary(
117        &self,
118        evm: &mut Self::Evm,
119        exec_result: &mut <<Self::Evm as EvmTr>::Frame as FrameTr>::FrameResult,
120    ) -> Result<(), Self::Error> {
121        let context = evm.ctx();
122        let tx = context.tx();
123        let beneficiary = context.block().beneficiary();
124        let basefee = context.block().basefee() as u128;
125        let effective_gas_price = tx.effective_gas_price(basefee);
126        let gas = exec_result.gas();
127
128        let coinbase_gas_price = if context.cfg().spec().into().is_enabled_in(SpecId::LONDON) {
129            effective_gas_price.saturating_sub(basefee)
130        } else {
131            effective_gas_price
132        };
133
134        let reward = coinbase_gas_price.saturating_mul(gas.used() as u128);
135
136        let beneficiary_slot = erc_address_storage(beneficiary);
137        // load account balance
138        let journal = context.journal_mut();
139        let beneficiary_balance = journal.sload(TOKEN, beneficiary_slot)?.data;
140        // reimburse caller
141        journal.sstore(
142            TOKEN,
143            beneficiary_slot,
144            beneficiary_balance + U256::from(reward),
145        )?;
146
147        Ok(())
148    }
149}