mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-24 08:49:29 +00:00
core/state: improve authorization state gas refund
This commit is contained in:
parent
39fb6df951
commit
ce25997091
5 changed files with 114 additions and 14 deletions
|
|
@ -572,6 +572,30 @@ func (s *stateObject) Code() []byte {
|
||||||
return code
|
return code
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCommittedCode returns the contract code committed at the start of the
|
||||||
|
// current execution, ignoring any intra-execution SetCode modifications.
|
||||||
|
// Used by EIP-7702 authorization application to make refund decisions
|
||||||
|
// relative to the originally-committed delegation, matching the spirit of
|
||||||
|
// GetCommittedState for storage slots.
|
||||||
|
func (s *stateObject) GetCommittedCode() []byte {
|
||||||
|
// The account did not exist at the start of the current execution.
|
||||||
|
if s.origin == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
hash := common.BytesToHash(s.origin.CodeHash)
|
||||||
|
if hash == types.EmptyCodeHash {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
// If the code has not been touched in this execution, the live cache
|
||||||
|
// already holds the committed code.
|
||||||
|
if !s.dirtyCode {
|
||||||
|
return s.Code()
|
||||||
|
}
|
||||||
|
// Code was modified within the current execution. Reach for the on-disk
|
||||||
|
// blob keyed by the origin hash.
|
||||||
|
return s.db.reader.Code(s.address, hash)
|
||||||
|
}
|
||||||
|
|
||||||
// CodeSize returns the size of the contract code associated with this object,
|
// CodeSize returns the size of the contract code associated with this object,
|
||||||
// or zero if none. This method is an almost mirror of Code, but uses a cache
|
// or zero if none. This method is an almost mirror of Code, but uses a cache
|
||||||
// inside the database to avoid loading codes seen recently.
|
// inside the database to avoid loading codes seen recently.
|
||||||
|
|
|
||||||
|
|
@ -397,6 +397,18 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
|
||||||
return common.Hash{}
|
return common.Hash{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetCommittedCode returns the contract code committed at the start of the
|
||||||
|
// current execution, ignoring any in-progress SetCode mutations. Returns
|
||||||
|
// nil when the account had no code (or did not exist) prior to this
|
||||||
|
// execution.
|
||||||
|
func (s *StateDB) GetCommittedCode(addr common.Address) []byte {
|
||||||
|
stateObject := s.getStateObject(addr)
|
||||||
|
if stateObject != nil {
|
||||||
|
return stateObject.GetCommittedCode()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetState retrieves the value associated with the specific key.
|
// GetState retrieves the value associated with the specific key.
|
||||||
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
|
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
|
||||||
stateObject := s.getStateObject(addr)
|
stateObject := s.getStateObject(addr)
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,10 @@ func (s *hookedStateDB) GetCode(addr common.Address) []byte {
|
||||||
return s.inner.GetCode(addr)
|
return s.inner.GetCode(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *hookedStateDB) GetCommittedCode(addr common.Address) []byte {
|
||||||
|
return s.inner.GetCommittedCode(addr)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *hookedStateDB) GetCodeSize(addr common.Address) int {
|
func (s *hookedStateDB) GetCodeSize(addr common.Address) int {
|
||||||
return s.inner.GetCodeSize(addr)
|
return s.inner.GetCodeSize(addr)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -744,12 +744,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)
|
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)
|
||||||
|
|
||||||
// Apply EIP-7702 authorizations.
|
// Apply EIP-7702 authorizations.
|
||||||
if msg.SetCodeAuthorizations != nil {
|
st.applyAuthorizations(rules, msg.SetCodeAuthorizations)
|
||||||
for _, auth := range msg.SetCodeAuthorizations {
|
|
||||||
// Note errors are ignored, we simply skip invalid authorizations here.
|
|
||||||
st.applyAuthorization(rules, &auth)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Perform convenience warming of sender's delegation target. Although the
|
// Perform convenience warming of sender's delegation target. Although the
|
||||||
// sender is already warmed in Prepare(..), it's possible a delegation to
|
// sender is already warmed in Prepare(..), it's possible a delegation to
|
||||||
|
|
@ -902,8 +897,48 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio
|
||||||
return authority, nil
|
return authority, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// applyAuthorizations applies every EIP-7702 code delegation in the tx and,
|
||||||
|
// under EIP-8037, reconciles the per-authority creation budget so that the
|
||||||
|
// total AuthorizationCreationSize state gas charged matches the net change
|
||||||
|
// in on-disk delegation bytes.
|
||||||
|
//
|
||||||
|
// Invalid authorizations are silently skipped (their auth-base intrinsic
|
||||||
|
// state gas remains charged, matching the pre-existing behavior).
|
||||||
|
func (st *stateTransition) applyAuthorizations(rules params.Rules, auths []types.SetCodeAuthorization) {
|
||||||
|
if len(auths) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
// Under EIP-8037 each authority can be billed at most one
|
||||||
|
// AuthorizationCreationSize. applyAuthorization records authorities it
|
||||||
|
// has billed; we reconcile after the loop by refunding any creation that
|
||||||
|
// was billed but whose final delegation state in this tx ended up empty
|
||||||
|
// (e.g., 0→a→0).
|
||||||
|
var billed map[common.Address]struct{}
|
||||||
|
if rules.IsAmsterdam {
|
||||||
|
billed = make(map[common.Address]struct{})
|
||||||
|
}
|
||||||
|
for _, auth := range auths {
|
||||||
|
// Errors are ignored — invalid authorizations are simply skipped.
|
||||||
|
st.applyAuthorization(rules, &auth, billed)
|
||||||
|
}
|
||||||
|
// End-of-loop reconciliation: a billed creation whose authority is no
|
||||||
|
// longer delegated wrote zero net bytes to disk, so the auth-base
|
||||||
|
// intrinsic state gas should not be retained.
|
||||||
|
for authority := range billed {
|
||||||
|
if _, isDelegated := types.ParseDelegation(st.state.GetCode(authority)); !isDelegated {
|
||||||
|
st.gasRemaining.RefundState(params.AuthorizationCreationSize * st.evm.Context.CostPerStateByte)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 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) error {
|
//
|
||||||
|
// authBilledCreations, when non-nil, tracks the set of authorities for which
|
||||||
|
// this tx has been billed one AuthorizationCreationSize charge (the per-
|
||||||
|
// authority "first creation" budget). The caller is expected to do an
|
||||||
|
// end-of-loop pass over this set and refund any entry whose final delegation
|
||||||
|
// state ended up empty.
|
||||||
|
func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.SetCodeAuthorization, authBilledCreations map[common.Address]struct{}) error {
|
||||||
authority, err := st.validateAuthorization(auth)
|
authority, err := st.validateAuthorization(auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -920,14 +955,34 @@ func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.Se
|
||||||
}
|
}
|
||||||
prevDelegation, isDelegated := types.ParseDelegation(st.state.GetCode(authority))
|
prevDelegation, isDelegated := types.ParseDelegation(st.state.GetCode(authority))
|
||||||
if rules.IsAmsterdam {
|
if rules.IsAmsterdam {
|
||||||
// EIP-8037: also refund the auth-base state gas when no new delegation
|
// EIP-8037: refund the auth-base state gas unless this auth is the
|
||||||
// indicator bytes are written. Two cases:
|
// first one in this tx to write delegation bytes to an authority
|
||||||
// - the authority already has a delegation (overwrite in place); or
|
// whose committed code was empty. Refund when ANY of:
|
||||||
// - the auth is no-op (auth.Address == 0).
|
//
|
||||||
// In both cases the 23 delegation bytes are reused, so the auth-base
|
// - the authority was already delegated at the start of the tx
|
||||||
// portion of the intrinsic state gas is refilled.
|
// (the 23 bytes are already accounted for in committed state,
|
||||||
if isDelegated || auth.Address == (common.Address{}) {
|
// and any auth against it just re-writes them);
|
||||||
|
//
|
||||||
|
// - the auth is no-op / clearing (auth.Address == 0) — no bytes
|
||||||
|
// are written in this step at all;
|
||||||
|
//
|
||||||
|
// - we have already billed a creation for this authority in
|
||||||
|
// this tx (per-authority creation budget is 1).
|
||||||
|
//
|
||||||
|
// Modeling it this way mirrors the SSTORE "reset to original"
|
||||||
|
// pattern (EIP-2200 / EIP-3529) and avoids both the undercount in
|
||||||
|
// a→0→b (committed had delegation, second auth missed the refund)
|
||||||
|
// and the overcount in 0→a→0→c (each later auth was previously
|
||||||
|
// billed as a fresh creation). The remaining 0→a→0 case — a
|
||||||
|
// creation is billed and then undone within the same auth list —
|
||||||
|
// is handled by the caller's end-of-loop adjustment over
|
||||||
|
// authBilledCreations.
|
||||||
|
_, committedDelegated := types.ParseDelegation(st.state.GetCommittedCode(authority))
|
||||||
|
_, alreadyBilled := authBilledCreations[authority]
|
||||||
|
if committedDelegated || alreadyBilled || auth.Address == (common.Address{}) {
|
||||||
st.gasRemaining.RefundState(params.AuthorizationCreationSize * st.evm.Context.CostPerStateByte)
|
st.gasRemaining.RefundState(params.AuthorizationCreationSize * st.evm.Context.CostPerStateByte)
|
||||||
|
} else {
|
||||||
|
authBilledCreations[authority] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,11 @@ type StateDB interface {
|
||||||
GetCodeHash(common.Address) common.Hash
|
GetCodeHash(common.Address) common.Hash
|
||||||
GetCode(common.Address) []byte
|
GetCode(common.Address) []byte
|
||||||
|
|
||||||
|
// GetCommittedCode returns the contract code at the start of the current
|
||||||
|
// execution, ignoring any in-progress SetCode mutations. Returns nil when
|
||||||
|
// the account had no code prior to this execution.
|
||||||
|
GetCommittedCode(common.Address) []byte
|
||||||
|
|
||||||
// SetCode sets the new code for the address, and returns the previous code, if any.
|
// SetCode sets the new code for the address, and returns the previous code, if any.
|
||||||
SetCode(common.Address, []byte, tracing.CodeChangeReason) []byte
|
SetCode(common.Address, []byte, tracing.CodeChangeReason) []byte
|
||||||
GetCodeSize(common.Address) int
|
GetCodeSize(common.Address) int
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue