reth_rpc_eth_types/
revm_utils.rs

1//! utilities for working with revm
2
3use alloy_primitives::{Address, B256, U256};
4use alloy_rpc_types_eth::{
5    state::{AccountOverride, StateOverride},
6    BlockOverrides,
7};
8use reth_evm::TransactionEnv;
9use revm::{
10    context::BlockEnv,
11    state::{Account, AccountStatus, Bytecode, EvmStorageSlot},
12    Database, DatabaseCommit,
13};
14use revm_database::{CacheDB, State};
15use std::{
16    cmp::min,
17    collections::{BTreeMap, HashMap},
18};
19
20use super::{EthApiError, EthResult, RpcInvalidTransactionError};
21
22/// Calculates the caller gas allowance.
23///
24/// `allowance = (account.balance - tx.value) / tx.gas_price`
25///
26/// Returns an error if the caller has insufficient funds.
27/// Caution: This assumes non-zero `env.gas_price`. Otherwise, zero allowance will be returned.
28///
29/// Note: this takes the mut [Database] trait because the loaded sender can be reused for the
30/// following operation like `eth_call`.
31pub fn caller_gas_allowance<DB>(db: &mut DB, env: &impl TransactionEnv) -> EthResult<u64>
32where
33    DB: Database,
34    EthApiError: From<<DB as Database>::Error>,
35{
36    // Get the caller account.
37    let caller = db.basic(env.caller())?;
38    // Get the caller balance.
39    let balance = caller.map(|acc| acc.balance).unwrap_or_default();
40    // Get transaction value.
41    let value = env.value();
42    // Subtract transferred value from the caller balance. Return error if the caller has
43    // insufficient funds.
44    let balance = balance
45        .checked_sub(env.value())
46        .ok_or_else(|| RpcInvalidTransactionError::InsufficientFunds { cost: value, balance })?;
47
48    Ok(balance
49        // Calculate the amount of gas the caller can afford with the specified gas price.
50        .checked_div(U256::from(env.gas_price()))
51        // This will be 0 if gas price is 0. It is fine, because we check it before.
52        .unwrap_or_default()
53        .saturating_to())
54}
55
56/// Helper type for representing the fees of a `TransactionRequest`
57#[derive(Debug)]
58pub struct CallFees {
59    /// EIP-1559 priority fee
60    pub max_priority_fee_per_gas: Option<U256>,
61    /// Unified gas price setting
62    ///
63    /// Will be the configured `basefee` if unset in the request
64    ///
65    /// `gasPrice` for legacy,
66    /// `maxFeePerGas` for EIP-1559
67    pub gas_price: U256,
68    /// Max Fee per Blob gas for EIP-4844 transactions
69    pub max_fee_per_blob_gas: Option<U256>,
70}
71
72// === impl CallFees ===
73
74impl CallFees {
75    /// Ensures the fields of a `TransactionRequest` are not conflicting.
76    ///
77    /// # EIP-4844 transactions
78    ///
79    /// Blob transactions have an additional fee parameter `maxFeePerBlobGas`.
80    /// If the `maxFeePerBlobGas` or `blobVersionedHashes` are set we treat it as an EIP-4844
81    /// transaction.
82    ///
83    /// Note: Due to the `Default` impl of [`BlockEnv`] (Some(0)) this assumes the `block_blob_fee`
84    /// is always `Some`
85    ///
86    /// ## Notable design decisions
87    ///
88    /// For compatibility reasons, this contains several exceptions when fee values are validated:
89    /// - If both `maxFeePerGas` and `maxPriorityFeePerGas` are set to `0` they are treated as
90    ///   missing values, bypassing fee checks wrt. `baseFeePerGas`.
91    ///
92    /// This mirrors geth's behaviour when transaction requests are executed: <https://github.com/ethereum/go-ethereum/blob/380688c636a654becc8f114438c2a5d93d2db032/core/state_transition.go#L306-L306>
93    pub fn ensure_fees(
94        call_gas_price: Option<U256>,
95        call_max_fee: Option<U256>,
96        call_priority_fee: Option<U256>,
97        block_base_fee: U256,
98        blob_versioned_hashes: Option<&[B256]>,
99        max_fee_per_blob_gas: Option<U256>,
100        block_blob_fee: Option<U256>,
101    ) -> EthResult<Self> {
102        /// Get the effective gas price of a transaction as specfified in EIP-1559 with relevant
103        /// checks.
104        fn get_effective_gas_price(
105            max_fee_per_gas: Option<U256>,
106            max_priority_fee_per_gas: Option<U256>,
107            block_base_fee: U256,
108        ) -> EthResult<U256> {
109            match max_fee_per_gas {
110                Some(max_fee) => {
111                    let max_priority_fee_per_gas = max_priority_fee_per_gas.unwrap_or(U256::ZERO);
112
113                    // only enforce the fee cap if provided input is not zero
114                    if !(max_fee.is_zero() && max_priority_fee_per_gas.is_zero()) &&
115                        max_fee < block_base_fee
116                    {
117                        // `base_fee_per_gas` is greater than the `max_fee_per_gas`
118                        return Err(RpcInvalidTransactionError::FeeCapTooLow.into())
119                    }
120                    if max_fee < max_priority_fee_per_gas {
121                        return Err(
122                            // `max_priority_fee_per_gas` is greater than the `max_fee_per_gas`
123                            RpcInvalidTransactionError::TipAboveFeeCap.into(),
124                        )
125                    }
126                    // ref <https://github.com/ethereum/go-ethereum/blob/0dd173a727dd2d2409b8e401b22e85d20c25b71f/internal/ethapi/transaction_args.go#L446-L446>
127                    Ok(min(
128                        max_fee,
129                        block_base_fee.checked_add(max_priority_fee_per_gas).ok_or_else(|| {
130                            EthApiError::from(RpcInvalidTransactionError::TipVeryHigh)
131                        })?,
132                    ))
133                }
134                None => Ok(block_base_fee
135                    .checked_add(max_priority_fee_per_gas.unwrap_or(U256::ZERO))
136                    .ok_or(EthApiError::from(RpcInvalidTransactionError::TipVeryHigh))?),
137            }
138        }
139
140        let has_blob_hashes =
141            blob_versioned_hashes.as_ref().map(|blobs| !blobs.is_empty()).unwrap_or(false);
142
143        match (call_gas_price, call_max_fee, call_priority_fee, max_fee_per_blob_gas) {
144            (gas_price, None, None, None) => {
145                // either legacy transaction or no fee fields are specified
146                // when no fields are specified, set gas price to zero
147                let gas_price = gas_price.unwrap_or(U256::ZERO);
148                Ok(Self {
149                    gas_price,
150                    max_priority_fee_per_gas: None,
151                    max_fee_per_blob_gas: has_blob_hashes.then_some(block_blob_fee).flatten(),
152                })
153            }
154            (None, max_fee_per_gas, max_priority_fee_per_gas, None) => {
155                // request for eip-1559 transaction
156                let effective_gas_price = get_effective_gas_price(
157                    max_fee_per_gas,
158                    max_priority_fee_per_gas,
159                    block_base_fee,
160                )?;
161                let max_fee_per_blob_gas = has_blob_hashes.then_some(block_blob_fee).flatten();
162
163                Ok(Self {
164                    gas_price: effective_gas_price,
165                    max_priority_fee_per_gas,
166                    max_fee_per_blob_gas,
167                })
168            }
169            (None, max_fee_per_gas, max_priority_fee_per_gas, Some(max_fee_per_blob_gas)) => {
170                // request for eip-4844 transaction
171                let effective_gas_price = get_effective_gas_price(
172                    max_fee_per_gas,
173                    max_priority_fee_per_gas,
174                    block_base_fee,
175                )?;
176                // Ensure blob_hashes are present
177                if !has_blob_hashes {
178                    // Blob transaction but no blob hashes
179                    return Err(RpcInvalidTransactionError::BlobTransactionMissingBlobHashes.into())
180                }
181
182                Ok(Self {
183                    gas_price: effective_gas_price,
184                    max_priority_fee_per_gas,
185                    max_fee_per_blob_gas: Some(max_fee_per_blob_gas),
186                })
187            }
188            _ => {
189                // this fallback covers incompatible combinations of fields
190                Err(EthApiError::ConflictingFeeFieldsInRequest)
191            }
192        }
193    }
194}
195
196/// Helper trait implemented for databases that support overriding block hashes.
197///
198/// Used for applying [`BlockOverrides::block_hash`]
199pub trait OverrideBlockHashes {
200    /// Overrides the given block hashes.
201    fn override_block_hashes(&mut self, block_hashes: BTreeMap<u64, B256>);
202}
203
204impl<DB> OverrideBlockHashes for CacheDB<DB> {
205    fn override_block_hashes(&mut self, block_hashes: BTreeMap<u64, B256>) {
206        self.cache
207            .block_hashes
208            .extend(block_hashes.into_iter().map(|(num, hash)| (U256::from(num), hash)))
209    }
210}
211
212impl<DB> OverrideBlockHashes for State<DB> {
213    fn override_block_hashes(&mut self, block_hashes: BTreeMap<u64, B256>) {
214        self.block_hashes.extend(block_hashes);
215    }
216}
217
218/// Applies the given block overrides to the env and updates overridden block hashes in the db.
219pub fn apply_block_overrides(
220    overrides: BlockOverrides,
221    db: &mut impl OverrideBlockHashes,
222    env: &mut BlockEnv,
223) {
224    let BlockOverrides {
225        number,
226        difficulty,
227        time,
228        gas_limit,
229        coinbase,
230        random,
231        base_fee,
232        block_hash,
233    } = overrides;
234
235    if let Some(block_hashes) = block_hash {
236        // override block hashes
237        db.override_block_hashes(block_hashes);
238    }
239
240    if let Some(number) = number {
241        env.number = number.saturating_to();
242    }
243    if let Some(difficulty) = difficulty {
244        env.difficulty = difficulty;
245    }
246    if let Some(time) = time {
247        env.timestamp = time;
248    }
249    if let Some(gas_limit) = gas_limit {
250        env.gas_limit = gas_limit;
251    }
252    if let Some(coinbase) = coinbase {
253        env.beneficiary = coinbase;
254    }
255    if let Some(random) = random {
256        env.prevrandao = Some(random);
257    }
258    if let Some(base_fee) = base_fee {
259        env.basefee = base_fee.saturating_to();
260    }
261}
262
263/// Applies the given state overrides (a set of [`AccountOverride`]) to the [`CacheDB`].
264pub fn apply_state_overrides<DB>(overrides: StateOverride, db: &mut DB) -> EthResult<()>
265where
266    DB: Database + DatabaseCommit,
267    EthApiError: From<DB::Error>,
268{
269    for (account, account_overrides) in overrides {
270        apply_account_override(account, account_overrides, db)?;
271    }
272    Ok(())
273}
274
275/// Applies a single [`AccountOverride`] to the [`CacheDB`].
276fn apply_account_override<DB>(
277    account: Address,
278    account_override: AccountOverride,
279    db: &mut DB,
280) -> EthResult<()>
281where
282    DB: Database + DatabaseCommit,
283    EthApiError: From<DB::Error>,
284{
285    let mut info = db.basic(account)?.unwrap_or_default();
286
287    if let Some(nonce) = account_override.nonce {
288        info.nonce = nonce;
289    }
290    if let Some(code) = account_override.code {
291        info.code = Some(
292            Bytecode::new_raw_checked(code)
293                .map_err(|err| EthApiError::InvalidBytecode(err.to_string()))?,
294        );
295    }
296    if let Some(balance) = account_override.balance {
297        info.balance = balance;
298    }
299
300    // Create a new account marked as touched
301    let mut acc =
302        revm::state::Account { info, status: AccountStatus::Touched, storage: HashMap::default() };
303
304    let storage_diff = match (account_override.state, account_override.state_diff) {
305        (Some(_), Some(_)) => return Err(EthApiError::BothStateAndStateDiffInOverride(account)),
306        (None, None) => None,
307        // If we need to override the entire state, we firstly mark account as destroyed to clear
308        // its storage, and then we mark it is "NewlyCreated" to make sure that old storage won't be
309        // used.
310        (Some(state), None) => {
311            // Destroy the account to ensure that its storage is cleared
312            db.commit(HashMap::from_iter([(
313                account,
314                Account {
315                    status: AccountStatus::SelfDestructed | AccountStatus::Touched,
316                    ..Default::default()
317                },
318            )]));
319            // Mark the account as created to ensure that old storage is not read
320            acc.mark_created();
321            Some(state)
322        }
323        (None, Some(state)) => Some(state),
324    };
325
326    if let Some(state) = storage_diff {
327        for (slot, value) in state {
328            acc.storage.insert(
329                slot.into(),
330                EvmStorageSlot {
331                    // we use inverted value here to ensure that storage is treated as changed
332                    original_value: (!value).into(),
333                    present_value: value.into(),
334                    is_cold: false,
335                },
336            );
337        }
338    }
339
340    db.commit(HashMap::from_iter([(account, acc)]));
341
342    Ok(())
343}
344
345#[cfg(test)]
346mod tests {
347    use super::*;
348    use alloy_consensus::constants::GWEI_TO_WEI;
349
350    #[test]
351    fn test_ensure_0_fallback() {
352        let CallFees { gas_price, .. } =
353            CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO))
354                .unwrap();
355        assert!(gas_price.is_zero());
356    }
357
358    #[test]
359    fn test_ensure_max_fee_0_exception() {
360        let CallFees { gas_price, .. } =
361            CallFees::ensure_fees(None, Some(U256::ZERO), None, U256::from(99), None, None, None)
362                .unwrap();
363        assert!(gas_price.is_zero());
364    }
365
366    #[test]
367    fn test_blob_fees() {
368        let CallFees { gas_price, max_fee_per_blob_gas, .. } =
369            CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO))
370                .unwrap();
371        assert!(gas_price.is_zero());
372        assert_eq!(max_fee_per_blob_gas, None);
373
374        let CallFees { gas_price, max_fee_per_blob_gas, .. } = CallFees::ensure_fees(
375            None,
376            None,
377            None,
378            U256::from(99),
379            Some(&[B256::from(U256::ZERO)]),
380            None,
381            Some(U256::from(99)),
382        )
383        .unwrap();
384        assert!(gas_price.is_zero());
385        assert_eq!(max_fee_per_blob_gas, Some(U256::from(99)));
386    }
387
388    #[test]
389    fn test_eip_1559_fees() {
390        let CallFees { gas_price, .. } = CallFees::ensure_fees(
391            None,
392            Some(U256::from(25 * GWEI_TO_WEI)),
393            Some(U256::from(15 * GWEI_TO_WEI)),
394            U256::from(15 * GWEI_TO_WEI),
395            None,
396            None,
397            Some(U256::ZERO),
398        )
399        .unwrap();
400        assert_eq!(gas_price, U256::from(25 * GWEI_TO_WEI));
401
402        let CallFees { gas_price, .. } = CallFees::ensure_fees(
403            None,
404            Some(U256::from(25 * GWEI_TO_WEI)),
405            Some(U256::from(5 * GWEI_TO_WEI)),
406            U256::from(15 * GWEI_TO_WEI),
407            None,
408            None,
409            Some(U256::ZERO),
410        )
411        .unwrap();
412        assert_eq!(gas_price, U256::from(20 * GWEI_TO_WEI));
413
414        let CallFees { gas_price, .. } = CallFees::ensure_fees(
415            None,
416            Some(U256::from(30 * GWEI_TO_WEI)),
417            Some(U256::from(30 * GWEI_TO_WEI)),
418            U256::from(15 * GWEI_TO_WEI),
419            None,
420            None,
421            Some(U256::ZERO),
422        )
423        .unwrap();
424        assert_eq!(gas_price, U256::from(30 * GWEI_TO_WEI));
425
426        let call_fees = CallFees::ensure_fees(
427            None,
428            Some(U256::from(30 * GWEI_TO_WEI)),
429            Some(U256::from(31 * GWEI_TO_WEI)),
430            U256::from(15 * GWEI_TO_WEI),
431            None,
432            None,
433            Some(U256::ZERO),
434        );
435        assert!(call_fees.is_err());
436
437        let call_fees = CallFees::ensure_fees(
438            None,
439            Some(U256::from(5 * GWEI_TO_WEI)),
440            Some(U256::from(GWEI_TO_WEI)),
441            U256::from(15 * GWEI_TO_WEI),
442            None,
443            None,
444            Some(U256::ZERO),
445        );
446        assert!(call_fees.is_err());
447
448        let call_fees = CallFees::ensure_fees(
449            None,
450            Some(U256::MAX),
451            Some(U256::MAX),
452            U256::from(5 * GWEI_TO_WEI),
453            None,
454            None,
455            Some(U256::ZERO),
456        );
457        assert!(call_fees.is_err());
458    }
459}