You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The EIP-8037 conditional NEW_ACCOUNT state-gas charge for the CALL* family is not refunded when the call fails before entering the child frame, contradicting EIP-8037 and creating an asymmetry with CREATE/CREATE2 (which refund correctly on the analogous failures).
NEW_ACCOUNT is charged conditionally in call/callcode/etc. at src/ethereum/forks/amsterdam/vm/instructions/system.py:460-461:
…but it is never credited back on the two pre-frame failure exits:
Insufficient sender balance — system.py:481-485 restores only call_state_gas_reservoir, which is captured after the NEW_ACCOUNT deduction, so the charge (and any portion that spilled into gas_left) is lost.
Neither branch calls credit_state_gas_refund. Compare generic_create, which does refund on its analogous pre-frame failures (system.py:112-119).
Impact: the sender of a value-bearing CALL to a non-existent/empty account is over-charged STATE_BYTES_PER_NEW_ACCOUNT × CPSB when the call fails on insufficient balance or the stack-depth limit, even though no account is created.
Sources
EIP-8037 — account-creation charging for the CALL* family:
If the operation is unsuccessful before entering the call frame (e.g., due to insufficient balance or due to the stack depth), or if the child frame reverts or halts exceptionally, the charged state-gas is refilled in LIFO order and execution_state_gas_used decreases by the same amount.
The third EIP case (child reverts/halts) is effectively unreachable for this charge: a non-alive target has no code, so the child frame STOPs and cannot fail. Only the two pre-frame exits above are affected.
Suggested fix: credit the charge back on both branches, mirroring generic_create, e.g. credit_state_gas_refund(evm, StateGasCosts.NEW_ACCOUNT). Subtlety: the charge happens in the caller while the stack-depth check lives in generic_call, so the refund must either live in each caller's depth handling or generic_call must be told whether the account charge was applied.
Tests: add regression coverage for a value-bearing CALL to an empty account that fails on (a) insufficient sender balance and (b) the stack-depth limit, asserting the NEW_ACCOUNT state-gas is refunded.
Metadata
What was wrong?
The EIP-8037 conditional
NEW_ACCOUNTstate-gas charge for theCALL*family is not refunded when the call fails before entering the child frame, contradicting EIP-8037 and creating an asymmetry withCREATE/CREATE2(which refund correctly on the analogous failures).NEW_ACCOUNTis charged conditionally incall/callcode/etc. atsrc/ethereum/forks/amsterdam/vm/instructions/system.py:460-461:…but it is never credited back on the two pre-frame failure exits:
system.py:481-485restores onlycall_state_gas_reservoir, which is captured after theNEW_ACCOUNTdeduction, so the charge (and any portion that spilled intogas_left) is lost.generic_call,system.py:338-342restoresparams.gas/params.state_gas_reservoironly.Neither branch calls
credit_state_gas_refund. Comparegeneric_create, which does refund on its analogous pre-frame failures (system.py:112-119).Impact: the sender of a value-bearing CALL to a non-existent/empty account is over-charged
STATE_BYTES_PER_NEW_ACCOUNT × CPSBwhen the call fails on insufficient balance or the stack-depth limit, even though no account is created.Sources
EIP-8037 — account-creation charging for the
CALL*family:Update EIP-8037: resolve EIP-7928 conflict and route state-gas refills to origin EIPs#11807 — the PR that clarifies the CALL* (conditional) vs CREATE/CREATE2 (unconditional) charging split, where this requirement is spelled out.
Additional Context
generic_create, e.g.credit_state_gas_refund(evm, StateGasCosts.NEW_ACCOUNT). Subtlety: the charge happens in the caller while the stack-depth check lives ingeneric_call, so the refund must either live in each caller's depth handling orgeneric_callmust be told whether the account charge was applied.NEW_ACCOUNTstate-gas is refunded.