|
| 1 | +//! Utilities for dealing with eth_call and adjacent RPC endpoints. |
| 2 | +
|
| 3 | +use alloy_primitives::U256; |
| 4 | +use revm::Database; |
| 5 | + |
| 6 | +/// Insufficient funds error |
| 7 | +#[derive(Debug, thiserror::Error)] |
| 8 | +#[error("insufficient funds: cost {cost} > balance {balance}")] |
| 9 | +pub struct InsufficientFundsError { |
| 10 | + /// Transaction cost |
| 11 | + pub cost: U256, |
| 12 | + /// Account balance |
| 13 | + pub balance: U256, |
| 14 | +} |
| 15 | + |
| 16 | +/// Error type for call utilities |
| 17 | +#[derive(Debug, thiserror::Error)] |
| 18 | +pub enum CallError<E> { |
| 19 | + /// Database error |
| 20 | + #[error(transparent)] |
| 21 | + Database(E), |
| 22 | + /// Insufficient funds error |
| 23 | + #[error(transparent)] |
| 24 | + InsufficientFunds(#[from] InsufficientFundsError), |
| 25 | +} |
| 26 | + |
| 27 | +/// Calculates the caller gas allowance. |
| 28 | +/// |
| 29 | +/// `allowance = (account.balance - tx.value) / tx.gas_price` |
| 30 | +/// |
| 31 | +/// Returns an error if the caller has insufficient funds. |
| 32 | +/// Caution: This assumes non-zero `env.gas_price`. Otherwise, zero allowance will be returned. |
| 33 | +/// |
| 34 | +/// Note: this takes the mut [Database] trait because the loaded sender can be reused for the |
| 35 | +/// following operation like `eth_call`. |
| 36 | +pub fn caller_gas_allowance<DB, T>(db: &mut DB, env: &T) -> Result<u64, CallError<DB::Error>> |
| 37 | +where |
| 38 | + DB: Database, |
| 39 | + T: revm::context_interface::Transaction, |
| 40 | +{ |
| 41 | + // Get the caller account. |
| 42 | + let caller = db.basic(env.caller()).map_err(CallError::Database)?; |
| 43 | + // Get the caller balance. |
| 44 | + let balance = caller.map(|acc| acc.balance).unwrap_or_default(); |
| 45 | + // Get transaction value. |
| 46 | + let value = env.value(); |
| 47 | + // Subtract transferred value from the caller balance. Return error if the caller has |
| 48 | + // insufficient funds. |
| 49 | + let balance = |
| 50 | + balance.checked_sub(env.value()).ok_or(InsufficientFundsError { cost: value, balance })?; |
| 51 | + |
| 52 | + Ok(balance |
| 53 | + // Calculate the amount of gas the caller can afford with the specified gas price. |
| 54 | + .checked_div(U256::from(env.gas_price())) |
| 55 | + // This will be 0 if gas price is 0. It is fine, because we check it before. |
| 56 | + .unwrap_or_default() |
| 57 | + .saturating_to()) |
| 58 | +} |
0 commit comments