diff --git a/core/eip2780_test.go b/core/eip2780_test.go index 26b6b174be..d9545923ff 100644 --- a/core/eip2780_test.go +++ b/core/eip2780_test.go @@ -17,7 +17,6 @@ package core import ( - "errors" "math/big" "testing" @@ -192,15 +191,22 @@ func TestEIP2780NewAccountFunded(t *testing.T) { } // TestEIP2780InsufficientGasForCallCharge verifies that a value transfer -// creating a new account is rejected when the gas limit only covers the 21,000 -// intrinsic base and not the additional new-account state gas charged before the -// call executes. +// creating a new account, whose gas limit only covers the 21,000 intrinsic base +// and not the additional new-account state gas charged before the call executes, +// halts out of gas. The transaction stays valid (no consensus error) but +// execution fails and the recipient is not created. func TestEIP2780InsufficientGasForCallCharge(t *testing.T) { fresh := common.HexToAddress("0xbeef000000000000000000000000000000000003") sdb := mkState(senderAlloc(nil)) - _, _, err := applyMsg(t, sdb, callTx(0, fresh, 1, 21_000, nil)) - if !errors.Is(err, ErrEIP2780CallRecipientCharge) { - t.Fatalf("expected ErrEIP2780CallRecipientCharge, got %v", err) + res, _, err := applyMsg(t, sdb, callTx(0, fresh, 1, 21_000, nil)) + if err != nil { + t.Fatalf("transaction should remain valid: %v", err) + } + if res.Err != vm.ErrOutOfGas { + t.Fatalf("expected out of gas, got %v", res.Err) + } + if res.UsedGas != 21_000 { + t.Fatalf("expected used gas, got %v", res.UsedGas) } if sdb.Exist(fresh) { t.Fatal("recipient should not be created when the call charge cannot be paid") diff --git a/core/error.go b/core/error.go index 4b5bf5471b..26b007f9d9 100644 --- a/core/error.go +++ b/core/error.go @@ -81,10 +81,6 @@ var ( // than required for the data floor cost. ErrFloorDataGas = errors.New("insufficient gas for floor data gas cost") - // ErrEIP2780CallRecipientCharge is returned if the transaction doesn't have - // sufficient gas to cover the cost of EIP-2780 call charge. - ErrEIP2780CallRecipientCharge = errors.New("insufficient gas for EIP-2780 call recipient charge") - // ErrTxTypeNotSupported is returned if a transaction is not supported in the // current network configuration. ErrTxTypeNotSupported = types.ErrTxTypeNotSupported diff --git a/core/state_transition.go b/core/state_transition.go index 6386b74af2..3b04c63014 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -746,13 +746,16 @@ func (st *stateTransition) execute() (*ExecutionResult, error) { if addr, ok := types.ParseDelegation(st.state.GetCode(*msg.To)); ok { st.state.AddAddressToAccessList(addr) } - // EIP-2780: charge the transaction's top-level recipient costs. + // EIP-2780: charge the transaction's top-level recipient costs. If the + // budget cannot cover the charge, the top frame halts out of gas. if rules.IsAmsterdam && !st.chargeCallRecipientEIP2780(value) { - return nil, fmt.Errorf("%w: address %v", ErrEIP2780CallRecipientCharge, msg.To.Hex()) + vmerr = vm.ErrOutOfGas + st.gasRemaining = st.gasRemaining.ExitHalt() + } else { + // Execute the transaction's call. + ret, result, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining.ForwardAll(), value) + st.gasRemaining.Absorb(result) } - // Execute the transaction's call. - ret, result, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining.ForwardAll(), value) - st.gasRemaining.Absorb(result) } // EIP-8037 (fork.py:1086): on any transaction error, the state gas