reth_rpc_eth_types/
revm_utils.rs

1//! utilities for working with revm
2
3use alloy_primitives::{keccak256, 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    database::{CacheDB, State},
12    state::{Account, AccountStatus, Bytecode, EvmStorageSlot},
13    Database, DatabaseCommit,
14};
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        // we need to set both the bytecode and the codehash
292        info.code_hash = keccak256(&code);
293        info.code = Some(
294            Bytecode::new_raw_checked(code)
295                .map_err(|err| EthApiError::InvalidBytecode(err.to_string()))?,
296        );
297    }
298    if let Some(balance) = account_override.balance {
299        info.balance = balance;
300    }
301
302    // Create a new account marked as touched
303    let mut acc =
304        revm::state::Account { info, status: AccountStatus::Touched, storage: HashMap::default() };
305
306    let storage_diff = match (account_override.state, account_override.state_diff) {
307        (Some(_), Some(_)) => return Err(EthApiError::BothStateAndStateDiffInOverride(account)),
308        (None, None) => None,
309        // If we need to override the entire state, we firstly mark account as destroyed to clear
310        // its storage, and then we mark it is "NewlyCreated" to make sure that old storage won't be
311        // used.
312        (Some(state), None) => {
313            // Destroy the account to ensure that its storage is cleared
314            db.commit(HashMap::from_iter([(
315                account,
316                Account {
317                    status: AccountStatus::SelfDestructed | AccountStatus::Touched,
318                    ..Default::default()
319                },
320            )]));
321            // Mark the account as created to ensure that old storage is not read
322            acc.mark_created();
323            Some(state)
324        }
325        (None, Some(state)) => Some(state),
326    };
327
328    if let Some(state) = storage_diff {
329        for (slot, value) in state {
330            acc.storage.insert(
331                slot.into(),
332                EvmStorageSlot {
333                    // we use inverted value here to ensure that storage is treated as changed
334                    original_value: (!value).into(),
335                    present_value: value.into(),
336                    is_cold: false,
337                },
338            );
339        }
340    }
341
342    db.commit(HashMap::from_iter([(account, acc)]));
343
344    Ok(())
345}
346
347#[cfg(test)]
348mod tests {
349    use super::*;
350    use alloy_consensus::constants::GWEI_TO_WEI;
351    use alloy_primitives::{address, bytes};
352    use reth_revm::db::EmptyDB;
353
354    #[test]
355    fn test_ensure_0_fallback() {
356        let CallFees { gas_price, .. } =
357            CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO))
358                .unwrap();
359        assert!(gas_price.is_zero());
360    }
361
362    #[test]
363    fn test_ensure_max_fee_0_exception() {
364        let CallFees { gas_price, .. } =
365            CallFees::ensure_fees(None, Some(U256::ZERO), None, U256::from(99), None, None, None)
366                .unwrap();
367        assert!(gas_price.is_zero());
368    }
369
370    #[test]
371    fn test_blob_fees() {
372        let CallFees { gas_price, max_fee_per_blob_gas, .. } =
373            CallFees::ensure_fees(None, None, None, U256::from(99), None, None, Some(U256::ZERO))
374                .unwrap();
375        assert!(gas_price.is_zero());
376        assert_eq!(max_fee_per_blob_gas, None);
377
378        let CallFees { gas_price, max_fee_per_blob_gas, .. } = CallFees::ensure_fees(
379            None,
380            None,
381            None,
382            U256::from(99),
383            Some(&[B256::from(U256::ZERO)]),
384            None,
385            Some(U256::from(99)),
386        )
387        .unwrap();
388        assert!(gas_price.is_zero());
389        assert_eq!(max_fee_per_blob_gas, Some(U256::from(99)));
390    }
391
392    #[test]
393    fn test_eip_1559_fees() {
394        let CallFees { gas_price, .. } = CallFees::ensure_fees(
395            None,
396            Some(U256::from(25 * GWEI_TO_WEI)),
397            Some(U256::from(15 * GWEI_TO_WEI)),
398            U256::from(15 * GWEI_TO_WEI),
399            None,
400            None,
401            Some(U256::ZERO),
402        )
403        .unwrap();
404        assert_eq!(gas_price, U256::from(25 * GWEI_TO_WEI));
405
406        let CallFees { gas_price, .. } = CallFees::ensure_fees(
407            None,
408            Some(U256::from(25 * GWEI_TO_WEI)),
409            Some(U256::from(5 * GWEI_TO_WEI)),
410            U256::from(15 * GWEI_TO_WEI),
411            None,
412            None,
413            Some(U256::ZERO),
414        )
415        .unwrap();
416        assert_eq!(gas_price, U256::from(20 * GWEI_TO_WEI));
417
418        let CallFees { gas_price, .. } = CallFees::ensure_fees(
419            None,
420            Some(U256::from(30 * GWEI_TO_WEI)),
421            Some(U256::from(30 * GWEI_TO_WEI)),
422            U256::from(15 * GWEI_TO_WEI),
423            None,
424            None,
425            Some(U256::ZERO),
426        )
427        .unwrap();
428        assert_eq!(gas_price, U256::from(30 * GWEI_TO_WEI));
429
430        let call_fees = CallFees::ensure_fees(
431            None,
432            Some(U256::from(30 * GWEI_TO_WEI)),
433            Some(U256::from(31 * GWEI_TO_WEI)),
434            U256::from(15 * GWEI_TO_WEI),
435            None,
436            None,
437            Some(U256::ZERO),
438        );
439        assert!(call_fees.is_err());
440
441        let call_fees = CallFees::ensure_fees(
442            None,
443            Some(U256::from(5 * GWEI_TO_WEI)),
444            Some(U256::from(GWEI_TO_WEI)),
445            U256::from(15 * GWEI_TO_WEI),
446            None,
447            None,
448            Some(U256::ZERO),
449        );
450        assert!(call_fees.is_err());
451
452        let call_fees = CallFees::ensure_fees(
453            None,
454            Some(U256::MAX),
455            Some(U256::MAX),
456            U256::from(5 * GWEI_TO_WEI),
457            None,
458            None,
459            Some(U256::ZERO),
460        );
461        assert!(call_fees.is_err());
462    }
463
464    #[test]
465    fn state_override_state() {
466        let code = bytes!(
467        "0x63d0e30db05f525f5f6004601c3473c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25af15f5260205ff3"
468    );
469        let to = address!("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599");
470
471        let mut db = State::builder().with_database(CacheDB::new(EmptyDB::new())).build();
472
473        let acc_override = AccountOverride::default().with_code(code.clone());
474        apply_account_override(to, acc_override, &mut db).unwrap();
475
476        let account = db.basic(to).unwrap().unwrap();
477        assert!(account.code.is_some());
478        assert_eq!(account.code_hash, keccak256(&code));
479    }
480
481    #[test]
482    fn state_override_cache_db() {
483        let code = bytes!(
484        "0x63d0e30db05f525f5f6004601c3473c02aaa39b223fe8d0a0e5c4f27ead9083c756cc25af15f5260205ff3"
485    );
486        let to = address!("0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599");
487
488        let mut db = CacheDB::new(EmptyDB::new());
489
490        let acc_override = AccountOverride::default().with_code(code.clone());
491        apply_account_override(to, acc_override, &mut db).unwrap();
492
493        let account = db.basic(to).unwrap().unwrap();
494        assert!(account.code.is_some());
495        assert_eq!(account.code_hash, keccak256(&code));
496    }
497}