Skip to content

fix(rpc): eth_estimateGas rejects unfunded accounts with 'insufficient funds for l1 fee' when gasPrice is zero or omitted #183

Description

@panos-xyz

Description

In Ethereum and morph-geth, callers (wallets, dApps) routinely invoke eth_estimateGas without specifying gasPrice or maxFeePerGas, often from a new or unfunded account (balance = 0), to determine how much gas a transaction or contract interaction will consume.

In morph-geth, unmetered gas estimation (where feeCap == 0) skips account balance capping, allowing unfunded accounts to estimate gas. However, in morph-reth, calling eth_estimateGas for an unfunded account without specifying gasPrice unconditionally fails with "insufficient funds for l1 fee".

Root Cause

In crates/rpc/src/eth/call.rs:105-121:

let balance = db
    .basic(tx_env.caller())
    .map_err(|e| MorphEthApiError::Eth(e.into()))?
    .map(|acc| acc.balance)
    .unwrap_or_default();
let available = balance
    .checked_sub(tx_env.value())
    .ok_or(MorphEthApiError::InsufficientFundsForTransfer)?;

// Reject at `l1_fee >= available` so the RPC surfaces the real
// reason rather than a downstream "gas required exceeds allowance 0".
if l1_fee >= available {
    return Err(MorphEthApiError::InsufficientFundsForL1Fee);
}
Ok(gas_allowance_from_balance(
    available - l1_fee,
    tx_env.gas_price(),
))

if l1_fee >= available is checked unconditionally before gas_allowance_from_balance. If an account has balance = 0 (or balance < l1_fee), available is 0 while l1_fee is positive, causing the check to reject the call with InsufficientFundsForL1Fee.

In contrast, in morph-geth (internal/ethapi/api.go:1409):

if feeCap.BitLen() != 0 {
    ...
    if l1DataFee.Cmp(available) >= 0 {
        return 0, errors.New("insufficient funds for l1 fee")
    }
    allowance := new(big.Int).Div(available, feeCap)
    ...
}

morph-geth guards the entire balance and L1 fee check with if feeCap.BitLen() != 0. When no gasPrice / maxFeePerGas is provided, feeCap is 0, so geth skips the balance recap and proceeds with gas estimation.

Similarly, upstream reth's caller_gas_allowance returns u64::MAX immediately when tx_env.gas_price() == 0.

Expected Behavior

When tx_env.gas_price() == 0 (unpriced estimation), caller_gas_allowance should not reject on l1_fee >= available, matching morph-geth and upstream reth.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions