core: get rid of auth delegation state refund

This commit is contained in:
Gary Rong 2026-07-13 09:15:20 +08:00
parent 8c28920c36
commit 46256649a3
3 changed files with 57 additions and 55 deletions

View file

@ -665,9 +665,10 @@ func TestAuthClearRefillBase(t *testing.T) {
} }
} }
// 0->a->0 in one tx: the indicator created by an earlier auth and cleared by a // 0->a->0 in one tx: the indicator charge applies when the delegation is set
// later one writes zero net bytes; the earlier indicator charge is refilled. // and is never credited back when a later auth clears it in the same
func TestAuthClearSameTxDoubleRefill(t *testing.T) { // transaction.
func TestAuthClearSameTxNoRefill(t *testing.T) {
set, authority := signAuth(t, authKeyA, delegate8037, 0) set, authority := signAuth(t, authKeyA, delegate8037, 0)
clr, _ := signAuth(t, authKeyA, common.Address{}, 1) clr, _ := signAuth(t, authKeyA, common.Address{}, 1)
sdb := mkState(senderAlloc(nil)) sdb := mkState(senderAlloc(nil))
@ -676,8 +677,28 @@ func TestAuthClearSameTxDoubleRefill(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
_ = authority _ = authority
if want := newAccountState; gp.cumulativeState != want { if want := authWorstState; gp.cumulativeState != want {
t.Fatalf("state gas = %d, want %d (net-zero delegation)", gp.cumulativeState, want) t.Fatalf("state gas = %d, want %d (indicator charge kept on clear)", gp.cumulativeState, want)
}
}
// 0->a->0->b in one tx: the indicator charge applies at most once per
// authority — re-installing a delegation after an intra-tx clear is free.
func TestAuthSetClearSetChargedOnce(t *testing.T) {
set, _ := signAuth(t, authKeyA, delegate8037, 0)
clr, _ := signAuth(t, authKeyA, common.Address{}, 1)
set2, authority := signAuth(t, authKeyA, common.HexToAddress("0xde1e8a7f"), 2)
sdb := mkState(senderAlloc(nil))
_, gp, err := applyMsg(t, sdb, setCodeTx(0, senderAddr, []types.SetCodeAuthorization{set, clr, set2}))
if err != nil {
t.Fatal(err)
}
// The final delegation is installed and the indicator was paid exactly once.
if _, delegated := types.ParseDelegation(sdb.GetCode(authority)); !delegated {
t.Fatal("final delegation not installed")
}
if want := authWorstState; gp.cumulativeState != want {
t.Fatalf("state gas = %d, want %d (leaf + indicator exactly once)", gp.cumulativeState, want)
} }
} }

View file

@ -44,7 +44,7 @@ func newAuthTestTransition(sdb *state.StateDB) *stateTransition {
func TestAuthRuntimeChargeNetNew(t *testing.T) { func TestAuthRuntimeChargeNetNew(t *testing.T) {
auth, _ := signAuth(t, authKeyA, delegate8037, 0) auth, _ := signAuth(t, authKeyA, delegate8037, 0)
st := newAuthTestTransition(mkState(senderAlloc(nil))) st := newAuthTestTransition(mkState(senderAlloc(nil)))
if err := st.applyAuthorization(rules8037, &auth, map[common.Address]bool{}); err != nil { if err := st.applyAuthorization(rules8037, &auth, map[common.Address]*authTracking{}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if want := params.AccountWriteAmsterdam; st.gasRemaining.UsedRegularGas != want { if want := params.AccountWriteAmsterdam; st.gasRemaining.UsedRegularGas != want {
@ -62,7 +62,7 @@ func TestAuthRuntimeChargeNetNew(t *testing.T) {
func TestAuthRuntimeChargeExistingAccount(t *testing.T) { func TestAuthRuntimeChargeExistingAccount(t *testing.T) {
auth, authority := signAuth(t, authKeyA, delegate8037, 0) auth, authority := signAuth(t, authKeyA, delegate8037, 0)
st := newAuthTestTransition(mkState(senderAlloc(types.GenesisAlloc{authority: {Balance: big.NewInt(1)}}))) st := newAuthTestTransition(mkState(senderAlloc(types.GenesisAlloc{authority: {Balance: big.NewInt(1)}})))
if err := st.applyAuthorization(rules8037, &auth, map[common.Address]bool{}); err != nil { if err := st.applyAuthorization(rules8037, &auth, map[common.Address]*authTracking{}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if want := params.AccountWriteAmsterdam; st.gasRemaining.UsedRegularGas != want { if want := params.AccountWriteAmsterdam; st.gasRemaining.UsedRegularGas != want {
@ -81,7 +81,7 @@ func TestAuthRuntimeChargeWarmAuthority(t *testing.T) {
auth, authority := signAuth(t, authKeyA, delegate8037, 0) auth, authority := signAuth(t, authKeyA, delegate8037, 0)
st := newAuthTestTransition(mkState(senderAlloc(types.GenesisAlloc{authority: {Balance: big.NewInt(1)}}))) st := newAuthTestTransition(mkState(senderAlloc(types.GenesisAlloc{authority: {Balance: big.NewInt(1)}})))
st.state.AddAddressToAccessList(authority) st.state.AddAddressToAccessList(authority)
if err := st.applyAuthorization(rules8037, &auth, map[common.Address]bool{}); err != nil { if err := st.applyAuthorization(rules8037, &auth, map[common.Address]*authTracking{}); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if want := params.AccountWriteAmsterdam; st.gasRemaining.UsedRegularGas != want { if want := params.AccountWriteAmsterdam; st.gasRemaining.UsedRegularGas != want {
@ -99,7 +99,7 @@ func TestAuthRuntimeInvalidNoCharge(t *testing.T) {
ChainID: *uint256.NewInt(999), Address: delegate8037, Nonce: 0, // wrong chain id ChainID: *uint256.NewInt(999), Address: delegate8037, Nonce: 0, // wrong chain id
}) })
st := newAuthTestTransition(mkState(senderAlloc(nil))) st := newAuthTestTransition(mkState(senderAlloc(nil)))
if err := st.applyAuthorization(rules8037, &bad, map[common.Address]bool{}); err == nil { if err := st.applyAuthorization(rules8037, &bad, map[common.Address]*authTracking{}); err == nil {
t.Fatal("expected invalid-authorization error") t.Fatal("expected invalid-authorization error")
} }
if st.gasRemaining.UsedRegularGas != 0 || st.gasRemaining.UsedStateGas != 0 { if st.gasRemaining.UsedRegularGas != 0 || st.gasRemaining.UsedStateGas != 0 {
@ -115,11 +115,11 @@ func TestAuthRuntimeDuplicateAuthorityOnce(t *testing.T) {
a0, _ := signAuth(t, authKeyA, delegate8037, 0) a0, _ := signAuth(t, authKeyA, delegate8037, 0)
a1, _ := signAuth(t, authKeyA, delegate8037, 1) a1, _ := signAuth(t, authKeyA, delegate8037, 1)
st := newAuthTestTransition(mkState(senderAlloc(nil))) st := newAuthTestTransition(mkState(senderAlloc(nil)))
delegates := map[common.Address]bool{} authorities := map[common.Address]*authTracking{}
if err := st.applyAuthorization(rules8037, &a0, delegates); err != nil { if err := st.applyAuthorization(rules8037, &a0, authorities); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if err := st.applyAuthorization(rules8037, &a1, delegates); err != nil { if err := st.applyAuthorization(rules8037, &a1, authorities); err != nil {
t.Fatal(err) t.Fatal(err)
} }
if want := params.AccountWriteAmsterdam; st.gasRemaining.UsedRegularGas != want { if want := params.AccountWriteAmsterdam; st.gasRemaining.UsedRegularGas != want {
@ -136,7 +136,7 @@ func TestAuthRuntimeOutOfGas(t *testing.T) {
auth, authority := signAuth(t, authKeyA, delegate8037, 0) auth, authority := signAuth(t, authKeyA, delegate8037, 0)
st := newAuthTestTransition(mkState(senderAlloc(nil))) st := newAuthTestTransition(mkState(senderAlloc(nil)))
st.gasRemaining = vm.NewGasBudget(10_000, 0) // covers neither leaf nor indicator st.gasRemaining = vm.NewGasBudget(10_000, 0) // covers neither leaf nor indicator
if err := st.applyAuthorization(rules8037, &auth, map[common.Address]bool{}); err != ErrOutOfGasRuntime { if err := st.applyAuthorization(rules8037, &auth, map[common.Address]*authTracking{}); err != ErrOutOfGasRuntime {
t.Fatalf("err = %v, want ErrOutOfGasRuntime", err) t.Fatalf("err = %v, want ErrOutOfGasRuntime", err)
} }
if st.state.GetNonce(authority) != 0 || len(st.state.GetCode(authority)) != 0 { if st.state.GetNonce(authority) != 0 || len(st.state.GetCode(authority)) != 0 {

View file

@ -930,27 +930,11 @@ func (st *stateTransition) chargeCallRecipientEIP2780(value *uint256.Int) bool {
// settleGas finalizes the per-tx gas accounting after EVM execution: // settleGas finalizes the per-tx gas accounting after EVM execution:
// //
// - Snapshots the EIP-8037 block-level 2D figures (tx_regular_gas, // - Snapshots the EIP-8037 block-level 2D figures (tx_regular_gas,
// tx_state_gas) before any refund or floor: // tx_state_gas) before any refund.
//
// tx_gas_used_before_refund = tx.gas - gas_left - state_gas_reservoir
// tx_state_gas = state_gas_used
// tx_regular_gas = tx_gas_used_before_refund - tx_state_gas
//
// - Computes the receipt scalar tx_gas_used by applying the EIP-3529 // - Computes the receipt scalar tx_gas_used by applying the EIP-3529
// refund (capped at tx_gas_used_before_refund/5) and the EIP-7623 // refund and the EIP-7623 calldata floor.
// calldata floor:
//
// tx_gas_used = max(tx_gas_used_before_refund - tx_gas_refund, calldata_floor)
//
// - Charges the block gas pool (2D under Amsterdam, scalar pre-Amsterdam). // - Charges the block gas pool (2D under Amsterdam, scalar pre-Amsterdam).
//
// - Refunds the leftover gas to the sender as ETH. // - Refunds the leftover gas to the sender as ETH.
//
// Returns the receipt-level tx_gas_used and the pre-refund peak (consumed
// by gas-estimation callers via ExecutionResult.MaxUsedGas). UsedStateGas
// should never become negative in the top-most frame, since state-gas
// refunds occur only when state creation is reverted within the same
// transaction and clearing pre-existing state is never refunded.
func (st *stateTransition) settleGas(rules params.Rules, floorDataGas uint64) (gasUsed, peakUsed uint64, err error) { func (st *stateTransition) settleGas(rules params.Rules, floorDataGas uint64) (gasUsed, peakUsed uint64, err error) {
if st.gasRemaining.UsedStateGas < 0 { if st.gasRemaining.UsedStateGas < 0 {
return 0, 0, fmt.Errorf("negative topmost frame state gas usage, %d", st.gasRemaining.UsedStateGas) return 0, 0, fmt.Errorf("negative topmost frame state gas usage, %d", st.gasRemaining.UsedStateGas)
@ -1043,8 +1027,15 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio
return authority, nil return authority, nil
} }
// authTracking tracks the charges already paid for an authority by earlier
// authorizations in the same transaction.
type authTracking struct {
written bool // first-write ACCOUNT_WRITE surcharge paid
authBaseCovered bool // indicator exists at tx start, or paid earlier
}
// applyAuthorization applies an EIP-7702 code delegation to the state. // applyAuthorization applies an EIP-7702 code delegation to the state.
func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.SetCodeAuthorization, delegates map[common.Address]bool) error { func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.SetCodeAuthorization, authorities map[common.Address]*authTracking) error {
authority, err := st.validateAuthorization(auth) authority, err := st.validateAuthorization(auth)
if err != nil { if err != nil {
return err return err
@ -1060,12 +1051,11 @@ func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.Se
// The authority's cold access was already charged unconditionally at the // The authority's cold access was already charged unconditionally at the
// intrinsic phase, so only state-dependent costs remain here. // intrinsic phase, so only state-dependent costs remain here.
var cost vm.GasCosts var cost vm.GasCosts
authBase := params.AuthorizationCreationSize * st.evm.Context.CostPerStateByte
preDelegated, seen := delegates[authority] track := authorities[authority]
if !seen { if track == nil {
preDelegated = curDelegated track = &authTracking{authBaseCovered: curDelegated}
delegates[authority] = preDelegated authorities[authority] = track
} }
// Every valid authorization writes the authority account: the // Every valid authorization writes the authority account: the
// nonce bump, and possibly the delegation indicator. The first // nonce bump, and possibly the delegation indicator. The first
@ -1084,28 +1074,19 @@ func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.Se
// phase. A zero-value transaction pays no TX_VALUE_COST, so a // phase. A zero-value transaction pays no TX_VALUE_COST, so a
// write to tx.to here is still the first paid write. // write to tx.to here is still the first paid write.
hasValue := st.msg.Value != nil && !st.msg.Value.IsZero() hasValue := st.msg.Value != nil && !st.msg.Value.IsZero()
if !seen && authority != st.msg.From && (authority != st.to() || !hasValue) { if !track.written && authority != st.msg.From && (authority != st.to() || !hasValue) {
cost.RegularGas += params.AccountWriteAmsterdam cost.RegularGas += params.AccountWriteAmsterdam
track.written = true
} }
// Durable state growth of the new account // Durable state growth of the new account
if st.state.Empty(authority) { if st.state.Empty(authority) {
cost.StateGas += params.AccountCreationSize * st.evm.Context.CostPerStateByte cost.StateGas += params.AccountCreationSize * st.evm.Context.CostPerStateByte
} }
// Writing the 23-byte delegation indicator into a previously empty // Charge the net-new indicator bytes at most once per authority;
// slot adds net-new state bytes. Overwriting an occupied slot, or one // clearing within the same transaction refunds nothing.
// occupied at transaction start, writes no new bytes. if auth.Address != (common.Address{}) && !track.authBaseCovered {
if auth.Address != (common.Address{}) && !curDelegated && !preDelegated { cost.StateGas += params.AuthorizationCreationSize * st.evm.Context.CostPerStateByte
cost.StateGas += authBase track.authBaseCovered = true
}
// Clearing an indicator that was created by an earlier authorization
// within the same transaction writes zero net bytes; refill the
// earlier state charge as it is no longer justified.
//
// Note that the refund and the charges above can never apply to the
// same authorization. The ordering of the refund and the charge is
// therefore irrelevant.
if auth.Address == (common.Address{}) && curDelegated && !preDelegated {
st.gasRemaining.RefundState(authBase)
} }
if !st.chargeRuntimeGas(cost) { if !st.chargeRuntimeGas(cost) {
return ErrOutOfGasRuntime return ErrOutOfGasRuntime
@ -1132,9 +1113,9 @@ func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.Se
// It reports whether the transaction budget covered all runtime authorization // It reports whether the transaction budget covered all runtime authorization
// charges. // charges.
func (st *stateTransition) applyAuthorizations(rules params.Rules, auths []types.SetCodeAuthorization) bool { func (st *stateTransition) applyAuthorizations(rules params.Rules, auths []types.SetCodeAuthorization) bool {
preDelegated := make(map[common.Address]bool) authorities := make(map[common.Address]*authTracking)
for _, auth := range auths { for _, auth := range auths {
if err := st.applyAuthorization(rules, &auth, preDelegated); err == ErrOutOfGasRuntime { if err := st.applyAuthorization(rules, &auth, authorities); err == ErrOutOfGasRuntime {
return false return false
} }
} }