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.
Description
In Ethereum and
morph-geth, callers (wallets, dApps) routinely invokeeth_estimateGaswithout specifyinggasPriceormaxFeePerGas, 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 (wherefeeCap == 0) skips account balance capping, allowing unfunded accounts to estimate gas. However, inmorph-reth, callingeth_estimateGasfor an unfunded account without specifyinggasPriceunconditionally fails with"insufficient funds for l1 fee".Root Cause
In
crates/rpc/src/eth/call.rs:105-121:if l1_fee >= availableis checked unconditionally beforegas_allowance_from_balance. If an account hasbalance = 0(orbalance < l1_fee),availableis 0 whilel1_feeis positive, causing the check to reject the call withInsufficientFundsForL1Fee.In contrast, in
morph-geth(internal/ethapi/api.go:1409):morph-gethguards the entire balance and L1 fee check withif feeCap.BitLen() != 0. When nogasPrice/maxFeePerGasis provided,feeCapis 0, so geth skips the balance recap and proceeds with gas estimation.Similarly, upstream reth's
caller_gas_allowancereturnsu64::MAXimmediately whentx_env.gas_price() == 0.Expected Behavior
When
tx_env.gas_price() == 0(unpriced estimation),caller_gas_allowanceshould not reject onl1_fee >= available, matchingmorph-gethand upstream reth.