mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-19 11:20:45 +00:00
Merge remote-tracking branch 'origin' into bs/cell-blobpool/sparse-v2
This commit is contained in:
commit
a56771bd92
127 changed files with 2571 additions and 464 deletions
|
|
@ -59,6 +59,11 @@ var (
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *{{.Type}}) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// New{{.Type}} creates a new instance of {{.Type}}.
|
||||
func New{{.Type}}() *{{.Type}} {
|
||||
parsed, err := {{.Type}}MetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ type CallbackParam struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *CallbackParam) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewCallbackParam creates a new instance of CallbackParam.
|
||||
func NewCallbackParam() *CallbackParam {
|
||||
parsed, err := CallbackParamMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ type Crowdsale struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Crowdsale) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewCrowdsale creates a new instance of Crowdsale.
|
||||
func NewCrowdsale() *Crowdsale {
|
||||
parsed, err := CrowdsaleMetaData.ParseABI()
|
||||
|
|
|
|||
5
accounts/abi/abigen/testdata/v2/dao.go.txt
vendored
5
accounts/abi/abigen/testdata/v2/dao.go.txt
vendored
|
|
@ -36,6 +36,11 @@ type DAO struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *DAO) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewDAO creates a new instance of DAO.
|
||||
func NewDAO() *DAO {
|
||||
parsed, err := DAOMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ type DeeplyNestedArray struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *DeeplyNestedArray) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewDeeplyNestedArray creates a new instance of DeeplyNestedArray.
|
||||
func NewDeeplyNestedArray() *DeeplyNestedArray {
|
||||
parsed, err := DeeplyNestedArrayMetaData.ParseABI()
|
||||
|
|
|
|||
5
accounts/abi/abigen/testdata/v2/empty.go.txt
vendored
5
accounts/abi/abigen/testdata/v2/empty.go.txt
vendored
|
|
@ -36,6 +36,11 @@ type Empty struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Empty) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewEmpty creates a new instance of Empty.
|
||||
func NewEmpty() *Empty {
|
||||
parsed, err := EmptyMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -35,6 +35,11 @@ type EventChecker struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *EventChecker) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewEventChecker creates a new instance of EventChecker.
|
||||
func NewEventChecker() *EventChecker {
|
||||
parsed, err := EventCheckerMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ type Getter struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Getter) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewGetter creates a new instance of Getter.
|
||||
func NewGetter() *Getter {
|
||||
parsed, err := GetterMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ type IdentifierCollision struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *IdentifierCollision) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewIdentifierCollision creates a new instance of IdentifierCollision.
|
||||
func NewIdentifierCollision() *IdentifierCollision {
|
||||
parsed, err := IdentifierCollisionMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -35,6 +35,11 @@ type InputChecker struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *InputChecker) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewInputChecker creates a new instance of InputChecker.
|
||||
func NewInputChecker() *InputChecker {
|
||||
parsed, err := InputCheckerMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ type Interactor struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Interactor) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewInteractor creates a new instance of Interactor.
|
||||
func NewInteractor() *Interactor {
|
||||
parsed, err := InteractorMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -42,6 +42,11 @@ type NameConflict struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *NameConflict) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewNameConflict creates a new instance of NameConflict.
|
||||
func NewNameConflict() *NameConflict {
|
||||
parsed, err := NameConflictMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ type NumericMethodName struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *NumericMethodName) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewNumericMethodName creates a new instance of NumericMethodName.
|
||||
func NewNumericMethodName() *NumericMethodName {
|
||||
parsed, err := NumericMethodNameMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -35,6 +35,11 @@ type OutputChecker struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *OutputChecker) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewOutputChecker creates a new instance of OutputChecker.
|
||||
func NewOutputChecker() *OutputChecker {
|
||||
parsed, err := OutputCheckerMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ type Overload struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Overload) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewOverload creates a new instance of Overload.
|
||||
func NewOverload() *Overload {
|
||||
parsed, err := OverloadMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ type RangeKeyword struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *RangeKeyword) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewRangeKeyword creates a new instance of RangeKeyword.
|
||||
func NewRangeKeyword() *RangeKeyword {
|
||||
parsed, err := RangeKeywordMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ type Slicer struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Slicer) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewSlicer creates a new instance of Slicer.
|
||||
func NewSlicer() *Slicer {
|
||||
parsed, err := SlicerMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -41,6 +41,11 @@ type Structs struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Structs) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewStructs creates a new instance of Structs.
|
||||
func NewStructs() *Structs {
|
||||
parsed, err := StructsMetaData.ParseABI()
|
||||
|
|
|
|||
5
accounts/abi/abigen/testdata/v2/token.go.txt
vendored
5
accounts/abi/abigen/testdata/v2/token.go.txt
vendored
|
|
@ -36,6 +36,11 @@ type Token struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Token) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewToken creates a new instance of Token.
|
||||
func NewToken() *Token {
|
||||
parsed, err := TokenMetaData.ParseABI()
|
||||
|
|
|
|||
5
accounts/abi/abigen/testdata/v2/tuple.go.txt
vendored
5
accounts/abi/abigen/testdata/v2/tuple.go.txt
vendored
|
|
@ -61,6 +61,11 @@ type Tuple struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Tuple) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewTuple creates a new instance of Tuple.
|
||||
func NewTuple() *Tuple {
|
||||
parsed, err := TupleMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ type Tupler struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Tupler) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewTupler creates a new instance of Tupler.
|
||||
func NewTupler() *Tupler {
|
||||
parsed, err := TuplerMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ type Underscorer struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *Underscorer) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewUnderscorer creates a new instance of Underscorer.
|
||||
func NewUnderscorer() *Underscorer {
|
||||
parsed, err := UnderscorerMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -43,6 +43,11 @@ type DB struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *DB) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewDB creates a new instance of DB.
|
||||
func NewDB() *DB {
|
||||
parsed, err := DBMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ type C struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *C) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewC creates a new instance of C.
|
||||
func NewC() *C {
|
||||
parsed, err := CMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -40,6 +40,11 @@ type C1 struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *C1) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewC1 creates a new instance of C1.
|
||||
func NewC1() *C1 {
|
||||
parsed, err := C1MetaData.ParseABI()
|
||||
|
|
@ -118,6 +123,11 @@ type C2 struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *C2) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewC2 creates a new instance of C2.
|
||||
func NewC2() *C2 {
|
||||
parsed, err := C2MetaData.ParseABI()
|
||||
|
|
@ -192,6 +202,11 @@ type L1 struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *L1) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewL1 creates a new instance of L1.
|
||||
func NewL1() *L1 {
|
||||
parsed, err := L1MetaData.ParseABI()
|
||||
|
|
@ -257,6 +272,11 @@ type L2 struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *L2) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewL2 creates a new instance of L2.
|
||||
func NewL2() *L2 {
|
||||
parsed, err := L2MetaData.ParseABI()
|
||||
|
|
@ -322,6 +342,11 @@ type L2b struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *L2b) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewL2b creates a new instance of L2b.
|
||||
func NewL2b() *L2b {
|
||||
parsed, err := L2bMetaData.ParseABI()
|
||||
|
|
@ -384,6 +409,11 @@ type L3 struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *L3) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewL3 creates a new instance of L3.
|
||||
func NewL3() *L3 {
|
||||
parsed, err := L3MetaData.ParseABI()
|
||||
|
|
@ -450,6 +480,11 @@ type L4 struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *L4) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewL4 creates a new instance of L4.
|
||||
func NewL4() *L4 {
|
||||
parsed, err := L4MetaData.ParseABI()
|
||||
|
|
@ -515,6 +550,11 @@ type L4b struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *L4b) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewL4b creates a new instance of L4b.
|
||||
func NewL4b() *L4b {
|
||||
parsed, err := L4bMetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ type C struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *C) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewC creates a new instance of C.
|
||||
func NewC() *C {
|
||||
parsed, err := CMetaData.ParseABI()
|
||||
|
|
@ -173,6 +178,11 @@ type C2 struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *C2) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewC2 creates a new instance of C2.
|
||||
func NewC2() *C2 {
|
||||
parsed, err := C2MetaData.ParseABI()
|
||||
|
|
|
|||
|
|
@ -36,6 +36,11 @@ type MyContract struct {
|
|||
abi abi.ABI
|
||||
}
|
||||
|
||||
// GetABI returns the ABI associated with this contract binding.
|
||||
func (c *MyContract) GetABI() abi.ABI {
|
||||
return c.abi
|
||||
}
|
||||
|
||||
// NewMyContract creates a new instance of MyContract.
|
||||
func NewMyContract() *MyContract {
|
||||
parsed, err := MyContractMetaData.ParseABI()
|
||||
|
|
|
|||
1
accounts/external/backend.go
vendored
1
accounts/external/backend.go
vendored
|
|
@ -237,6 +237,7 @@ func (api *ExternalSigner) SignTx(account accounts.Account, tx *types.Transactio
|
|||
}
|
||||
if tx.Type() == types.BlobTxType {
|
||||
args.BlobHashes = tx.BlobHashes()
|
||||
args.BlobFeeCap = (*hexutil.Big)(tx.BlobGasFeeCap())
|
||||
sidecar := tx.BlobTxSidecar()
|
||||
if sidecar == nil {
|
||||
return nil, errors.New("blobs must be present for signing")
|
||||
|
|
|
|||
|
|
@ -230,7 +230,7 @@ func toISO8601(t time.Time) string {
|
|||
if name == "UTC" {
|
||||
tz = "Z"
|
||||
} else {
|
||||
tz = fmt.Sprintf("%03d00", offset/3600)
|
||||
tz = fmt.Sprintf("%+03d00", offset/3600)
|
||||
}
|
||||
return fmt.Sprintf("%04d-%02d-%02dT%02d-%02d-%02d.%09d%s",
|
||||
t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute(), t.Second(), t.Nanosecond(), tz)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ package accounts
|
|||
|
||||
import (
|
||||
"reflect"
|
||||
"slices"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
|
|
@ -254,13 +255,12 @@ func merge(slice []Wallet, wallets ...Wallet) []Wallet {
|
|||
// drop is the counterpart of merge, which looks up wallets from within the sorted
|
||||
// cache and removes the ones specified.
|
||||
func drop(slice []Wallet, wallets ...Wallet) []Wallet {
|
||||
for _, wallet := range wallets {
|
||||
n := sort.Search(len(slice), func(i int) bool { return slice[i].URL().Cmp(wallet.URL()) >= 0 })
|
||||
if n == len(slice) {
|
||||
// Wallet not found, may happen during startup
|
||||
continue
|
||||
}
|
||||
slice = append(slice[:n], slice[n+1:]...)
|
||||
remove := make(map[URL]struct{}, len(wallets))
|
||||
for _, w := range wallets {
|
||||
remove[w.URL()] = struct{}{}
|
||||
}
|
||||
return slice
|
||||
return slices.DeleteFunc(slice, func(w Wallet) bool {
|
||||
_, ok := remove[w.URL()]
|
||||
return ok
|
||||
})
|
||||
}
|
||||
|
|
|
|||
47
accounts/manager_test.go
Normal file
47
accounts/manager_test.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package accounts
|
||||
|
||||
import "testing"
|
||||
|
||||
type managerTestWallet struct {
|
||||
Wallet
|
||||
url URL
|
||||
}
|
||||
|
||||
func (w managerTestWallet) URL() URL {
|
||||
return w.url
|
||||
}
|
||||
|
||||
func TestDropMissingWallet(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
wallets := []Wallet{
|
||||
managerTestWallet{url: URL{Scheme: "test", Path: "a"}},
|
||||
managerTestWallet{url: URL{Scheme: "test", Path: "c"}},
|
||||
}
|
||||
dropped := drop(wallets, managerTestWallet{url: URL{Scheme: "test", Path: "b"}})
|
||||
|
||||
if len(dropped) != len(wallets) {
|
||||
t.Fatalf("drop removed wallet for missing URL: got %d wallets, want %d", len(dropped), len(wallets))
|
||||
}
|
||||
for i := range dropped {
|
||||
if got, want := dropped[i].URL(), wallets[i].URL(); got != want {
|
||||
t.Fatalf("wallet %d mismatch: got %v, want %v", i, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -126,7 +126,7 @@ var (
|
|||
}
|
||||
extAddrFlag = &cli.StringFlag{
|
||||
Name: "extaddr",
|
||||
Usage: "UDP endpoint announced in ENR. You can provide a bare IP address or IP:port as the value of this flag.",
|
||||
Usage: "UDP endpoint announced in ENR. You can provide a bare IP address or IP:port as the value of this flag. Provide a comma-separated pair to announce both an IPv4 and an IPv6 endpoint.",
|
||||
}
|
||||
crawlTimeoutFlag = &cli.DurationFlag{
|
||||
Name: "timeout",
|
||||
|
|
@ -344,36 +344,60 @@ func parseExtAddr(spec string) (ip net.IP, port int, ok bool) {
|
|||
|
||||
func listen(ctx *cli.Context, ln *enode.LocalNode) *net.UDPConn {
|
||||
addr := ctx.String(listenAddrFlag.Name)
|
||||
extAddr := ctx.String(extAddrFlag.Name)
|
||||
var (
|
||||
socket net.PacketConn
|
||||
err error
|
||||
)
|
||||
if addr == "" {
|
||||
addr = "0.0.0.0:0"
|
||||
// Dual-stack socket, falling back to IPv4-only where IPv6 is unavailable.
|
||||
if socket, err = net.ListenPacket("udp", "[::]:0"); err != nil {
|
||||
socket, err = net.ListenPacket("udp", "0.0.0.0:0")
|
||||
}
|
||||
} else {
|
||||
socket, err = net.ListenPacket("udp", addr)
|
||||
}
|
||||
socket, err := net.ListenPacket("udp4", addr)
|
||||
if err != nil {
|
||||
exit(err)
|
||||
}
|
||||
|
||||
// Configure UDP endpoint in ENR from listener address.
|
||||
// Configure the ENR endpoint from the listener address, but only without an
|
||||
// explicit -extaddr: otherwise we'd announce a fallback IP for an address
|
||||
// family the user didn't specify (e.g. loopback IPv4 on an IPv6-only node).
|
||||
usocket := socket.(*net.UDPConn)
|
||||
uaddr := socket.LocalAddr().(*net.UDPAddr)
|
||||
if uaddr.IP.IsUnspecified() {
|
||||
ln.SetFallbackIP(net.IP{127, 0, 0, 1})
|
||||
} else {
|
||||
ln.SetFallbackIP(uaddr.IP)
|
||||
if extAddr == "" {
|
||||
if uaddr.IP.IsUnspecified() {
|
||||
ln.SetFallbackIP(net.IP{127, 0, 0, 1})
|
||||
} else {
|
||||
ln.SetFallbackIP(uaddr.IP)
|
||||
}
|
||||
}
|
||||
ln.SetFallbackUDP(uaddr.Port)
|
||||
|
||||
// If an ENR endpoint is set explicitly on the command-line, override
|
||||
// the information from the listening address. Note this is careful not
|
||||
// to set the UDP port if the external address doesn't have it.
|
||||
extAddr := ctx.String(extAddrFlag.Name)
|
||||
// Override with explicit -extaddr address(es). A static IP is set per family,
|
||||
// and all specs share one UDP port because the node has a single socket.
|
||||
if extAddr != "" {
|
||||
ip, port, ok := parseExtAddr(extAddr)
|
||||
if !ok {
|
||||
exit(fmt.Errorf("-%s: invalid external address %q", extAddrFlag.Name, extAddr))
|
||||
var extPort int
|
||||
for spec := range strings.SplitSeq(extAddr, ",") {
|
||||
spec = strings.TrimSpace(spec)
|
||||
if spec == "" {
|
||||
continue
|
||||
}
|
||||
ip, port, ok := parseExtAddr(spec)
|
||||
if !ok {
|
||||
exit(fmt.Errorf("-%s: invalid external address %q", extAddrFlag.Name, spec))
|
||||
}
|
||||
ln.SetStaticIP(ip)
|
||||
if port != 0 {
|
||||
if extPort != 0 && port != extPort {
|
||||
exit(fmt.Errorf("-%s: all addresses must announce the same UDP port, got %d and %d", extAddrFlag.Name, extPort, port))
|
||||
}
|
||||
extPort = port
|
||||
}
|
||||
}
|
||||
ln.SetStaticIP(ip)
|
||||
if port != 0 {
|
||||
ln.SetFallbackUDP(port)
|
||||
if extPort != 0 {
|
||||
ln.SetFallbackUDP(extPort)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -406,6 +430,7 @@ type discv4API struct {
|
|||
|
||||
func (api *discv4API) LookupRandom(n int) (ns []*enode.Node) {
|
||||
it := api.host.RandomNodes()
|
||||
defer it.Close()
|
||||
for len(ns) < n && it.Next() {
|
||||
ns = append(ns, it.Node())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ var (
|
|||
Action: discv5Crawl,
|
||||
Flags: slices.Concat(discoveryNodeFlags, []cli.Flag{
|
||||
crawlTimeoutFlag,
|
||||
crawlParallelismFlag,
|
||||
}),
|
||||
}
|
||||
discv5TestCommand = &cli.Command{
|
||||
|
|
|
|||
|
|
@ -335,7 +335,7 @@ func readAccounts(file string) (map[common.Address]*senderInfo, error) {
|
|||
for addr, acc := range keys {
|
||||
pk, err := crypto.HexToECDSA(common.Bytes2Hex(acc.Key))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to read private key for %s: %v", err, addr)
|
||||
return nil, fmt.Errorf("unable to read private key for %s: %v", addr, err)
|
||||
}
|
||||
accounts[addr] = &senderInfo{Key: pk, Nonce: 0}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -425,7 +425,7 @@ func (s *Suite) TestGetBlockBodies(t *utesting.T) {
|
|||
t.Fatalf("error reading block bodies msg: %v", err)
|
||||
}
|
||||
if got, want := resp.RequestId, req.RequestId; got != want {
|
||||
t.Fatalf("unexpected request id in respond", got, want)
|
||||
t.Fatalf("unexpected request id in response: got %d, want %d", got, want)
|
||||
}
|
||||
if resp.List.Len() != len(req.GetBlockBodiesRequest) {
|
||||
t.Fatalf("wrong bodies in response: expected %d bodies, got %d", len(req.GetBlockBodiesRequest), resp.List.Len())
|
||||
|
|
@ -469,7 +469,7 @@ func (s *Suite) TestGetReceipts(t *utesting.T) {
|
|||
t.Fatalf("error reading block receipts msg: %v", err)
|
||||
}
|
||||
if got, want := resp.RequestId, req.RequestId; got != want {
|
||||
t.Fatalf("unexpected request id in respond", got, want)
|
||||
t.Fatalf("unexpected request id in response: got %d, want %d", got, want)
|
||||
}
|
||||
if resp.List.Len() != len(req.GetReceiptsRequest) {
|
||||
t.Fatalf("wrong receipts in response: expected %d receipts, got %d", len(req.GetReceiptsRequest), resp.List.Len())
|
||||
|
|
@ -490,7 +490,7 @@ func (s *Suite) TestGetReceipts(t *utesting.T) {
|
|||
t.Fatalf("error reading block receipts msg: %v", err)
|
||||
}
|
||||
if got, want := resp.RequestId, req.RequestId; got != want {
|
||||
t.Fatalf("unexpected request id in respond", got, want)
|
||||
t.Fatalf("unexpected request id in response: got %d, want %d", got, want)
|
||||
}
|
||||
if resp.List.Len() != len(req.GetReceiptsRequest) {
|
||||
t.Fatalf("wrong receipts in response: expected %d receipts, got %d", len(req.GetReceiptsRequest), resp.List.Len())
|
||||
|
|
|
|||
|
|
@ -186,7 +186,7 @@ func (s *Suite) TestHandshakeResend(t *utesting.T) {
|
|||
t.Fatalf("wrong nonce %x in WHOAREYOU (want %x)", resp.Nonce[:], challenge1.Nonce[:])
|
||||
}
|
||||
if !bytes.Equal(resp.ChallengeData, challenge1.ChallengeData) {
|
||||
t.Fatalf("wrong ChallengeData in resent WHOAREYOU (want %x)", resp.ChallengeData, challenge1.ChallengeData)
|
||||
t.Fatalf("wrong ChallengeData in resent WHOAREYOU: got %x, want %x", resp.ChallengeData, challenge1.ChallengeData)
|
||||
}
|
||||
resp.Node = conn.remote
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ func blockTestCmd(ctx *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
// During fuzzing, we report the result after every block
|
||||
if !ctx.IsSet(FuzzFlag.Name) {
|
||||
if !ctx.Bool(FuzzFlag.Name) {
|
||||
report(ctx, results)
|
||||
}
|
||||
}
|
||||
|
|
@ -101,7 +101,7 @@ func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) {
|
|||
tracer := tracerFromFlags(ctx)
|
||||
|
||||
// Suppress INFO logs during fuzzing
|
||||
if ctx.IsSet(FuzzFlag.Name) {
|
||||
if ctx.Bool(FuzzFlag.Name) {
|
||||
log.SetDefault(log.NewLogger(log.DiscardHandler()))
|
||||
}
|
||||
|
||||
|
|
@ -140,7 +140,7 @@ func runBlockTest(ctx *cli.Context, fname string) ([]testResult, error) {
|
|||
}
|
||||
|
||||
// When fuzzing, write results after every block
|
||||
if ctx.IsSet(FuzzFlag.Name) {
|
||||
if ctx.Bool(FuzzFlag.Name) {
|
||||
report(ctx, []testResult{*result})
|
||||
}
|
||||
results = append(results, *result)
|
||||
|
|
|
|||
|
|
@ -242,6 +242,13 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
chainConfig.DAOForkBlock.Cmp(new(big.Int).SetUint64(pre.Env.Number)) == 0 {
|
||||
misc.ApplyDAOHardFork(statedb)
|
||||
}
|
||||
// EIP-7997: insert the deterministic deployment factory at the Amsterdam
|
||||
// activation block via an irregular state transition.
|
||||
if pre.Env.Number > 0 &&
|
||||
chainConfig.IsAmsterdam(new(big.Int).SetUint64(pre.Env.Number), pre.Env.Timestamp) &&
|
||||
!chainConfig.IsAmsterdam(new(big.Int).SetUint64(pre.Env.Number-1), pre.Env.ParentTimestamp) {
|
||||
misc.ApplyEIP7997(statedb)
|
||||
}
|
||||
evm := vm.NewEVM(vmContext, statedb, chainConfig, vmConfig)
|
||||
if beaconRoot := pre.Env.ParentBeaconBlockRoot; beaconRoot != nil {
|
||||
core.ProcessBeaconBlockRoot(*beaconRoot, evm, blockAccessList)
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/ethereum/go-ethereum/tests"
|
||||
"github.com/holiman/uint256"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
|
|
@ -132,8 +133,14 @@ func Transaction(ctx *cli.Context) error {
|
|||
r.Address = sender
|
||||
}
|
||||
// Check intrinsic gas
|
||||
value, overflow := uint256.FromBig(tx.Value())
|
||||
if overflow {
|
||||
// A 256-bit overflow is reported by the field validation below; use a
|
||||
// non-zero placeholder so intrinsic gas is still computed and reported.
|
||||
value = uint256.NewInt(1)
|
||||
}
|
||||
rules := chainConfig.Rules(common.Big0, true, 0)
|
||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte)
|
||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), r.Address, tx.To(), value, rules, params.CostPerStateByte)
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
results = append(results, r)
|
||||
|
|
@ -147,7 +154,7 @@ func Transaction(ctx *cli.Context) error {
|
|||
}
|
||||
// For Prague txs, validate the floor data gas.
|
||||
if rules.IsPrague {
|
||||
floorDataGas, err := core.FloorDataGas(rules, tx.Data(), tx.AccessList())
|
||||
floorDataGas, err := core.FloorDataGas(rules, r.Address, tx.To(), value, tx.Data(), tx.AccessList())
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
results = append(results, r)
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ var (
|
|||
Flags: slices.Concat([]cli.Flag{
|
||||
utils.CachePreimagesFlag,
|
||||
utils.OverrideOsaka,
|
||||
utils.OverrideAmsterdam,
|
||||
utils.OverrideBPO1,
|
||||
utils.OverrideBPO2,
|
||||
utils.OverrideUBT,
|
||||
|
|
@ -287,6 +288,10 @@ func initGenesis(ctx *cli.Context) error {
|
|||
v := ctx.Uint64(utils.OverrideOsaka.Name)
|
||||
overrides.OverrideOsaka = &v
|
||||
}
|
||||
if ctx.IsSet(utils.OverrideAmsterdam.Name) {
|
||||
v := ctx.Uint64(utils.OverrideAmsterdam.Name)
|
||||
overrides.OverrideAmsterdam = &v
|
||||
}
|
||||
if ctx.IsSet(utils.OverrideBPO1.Name) {
|
||||
v := ctx.Uint64(utils.OverrideBPO1.Name)
|
||||
overrides.OverrideBPO1 = &v
|
||||
|
|
@ -792,8 +797,8 @@ func downloadEra(ctx *cli.Context) error {
|
|||
var network = "mainnet"
|
||||
if utils.IsNetworkPreset(ctx) {
|
||||
switch {
|
||||
case ctx.IsSet(utils.MainnetFlag.Name):
|
||||
case ctx.IsSet(utils.SepoliaFlag.Name):
|
||||
case ctx.Bool(utils.MainnetFlag.Name):
|
||||
case ctx.Bool(utils.SepoliaFlag.Name):
|
||||
network = "sepolia"
|
||||
default:
|
||||
return errors.New("unsupported network, no known era1 checksums")
|
||||
|
|
@ -820,7 +825,7 @@ func downloadEra(ctx *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
switch {
|
||||
case ctx.IsSet(eraAllFlag.Name):
|
||||
case ctx.Bool(eraAllFlag.Name):
|
||||
return l.DownloadAll(dir)
|
||||
|
||||
case ctx.IsSet(eraBlockFlag.Name):
|
||||
|
|
|
|||
|
|
@ -228,6 +228,10 @@ func makeFullNode(ctx *cli.Context) *node.Node {
|
|||
v := ctx.Uint64(utils.OverrideOsaka.Name)
|
||||
cfg.Eth.OverrideOsaka = &v
|
||||
}
|
||||
if ctx.IsSet(utils.OverrideAmsterdam.Name) {
|
||||
v := ctx.Uint64(utils.OverrideAmsterdam.Name)
|
||||
cfg.Eth.OverrideAmsterdam = &v
|
||||
}
|
||||
if ctx.IsSet(utils.OverrideBPO1.Name) {
|
||||
v := ctx.Uint64(utils.OverrideBPO1.Name)
|
||||
cfg.Eth.OverrideBPO1 = &v
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ var (
|
|||
utils.USBFlag,
|
||||
utils.SmartCardDaemonPathFlag,
|
||||
utils.OverrideOsaka,
|
||||
utils.OverrideAmsterdam,
|
||||
utils.OverrideBPO1,
|
||||
utils.OverrideBPO2,
|
||||
utils.OverrideUBT,
|
||||
|
|
@ -177,6 +178,7 @@ var (
|
|||
utils.AllowUnprotectedTxs,
|
||||
utils.BatchRequestLimit,
|
||||
utils.BatchResponseMaxSize,
|
||||
utils.HTTPBodyLimitFlag,
|
||||
utils.RPCTxSyncDefaultTimeoutFlag,
|
||||
utils.RPCTxSyncMaxTimeoutFlag,
|
||||
utils.RPCGlobalRangeLimitFlag,
|
||||
|
|
@ -290,13 +292,13 @@ func main() {
|
|||
func prepare(ctx *cli.Context) {
|
||||
// If we're running a known preset, log it for convenience.
|
||||
switch {
|
||||
case ctx.IsSet(utils.SepoliaFlag.Name):
|
||||
case ctx.Bool(utils.SepoliaFlag.Name):
|
||||
log.Info("Starting Geth on Sepolia testnet...")
|
||||
|
||||
case ctx.IsSet(utils.HoleskyFlag.Name):
|
||||
case ctx.Bool(utils.HoleskyFlag.Name):
|
||||
log.Info("Starting Geth on Holesky testnet...")
|
||||
|
||||
case ctx.IsSet(utils.HoodiFlag.Name):
|
||||
case ctx.Bool(utils.HoodiFlag.Name):
|
||||
log.Info("Starting Geth on Hoodi testnet...")
|
||||
|
||||
case !ctx.IsSet(utils.NetworkIdFlag.Name):
|
||||
|
|
|
|||
|
|
@ -1054,6 +1054,7 @@ func listEIP7610EligibleAccounts(ctx *cli.Context) error {
|
|||
log.Error("Failed to get account iterator", "err", err)
|
||||
return err
|
||||
}
|
||||
defer iter.Release()
|
||||
var (
|
||||
start = time.Now()
|
||||
accounts []common.Address
|
||||
|
|
|
|||
|
|
@ -255,6 +255,11 @@ var (
|
|||
Usage: "Manually specify the Osaka fork timestamp, overriding the bundled setting",
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
OverrideAmsterdam = &cli.Uint64Flag{
|
||||
Name: "override.amsterdam",
|
||||
Usage: "Manually specify the Amsterdam fork timestamp, overriding the bundled setting",
|
||||
Category: flags.EthCategory,
|
||||
}
|
||||
OverrideBPO1 = &cli.Uint64Flag{
|
||||
Name: "override.bpo1",
|
||||
Usage: "Manually specify the bpo1 fork timestamp, overriding the bundled setting",
|
||||
|
|
@ -863,6 +868,12 @@ var (
|
|||
Value: node.DefaultConfig.BatchResponseMaxSize,
|
||||
Category: flags.APICategory,
|
||||
}
|
||||
HTTPBodyLimitFlag = &cli.IntFlag{
|
||||
Name: "rpc.http-body-limit",
|
||||
Usage: "Maximum size (in megabytes) of an HTTP request body",
|
||||
Value: node.DefaultConfig.HTTPBodyLimit / (1024 * 1024),
|
||||
Category: flags.APICategory,
|
||||
}
|
||||
|
||||
// Network Settings
|
||||
MaxPeersFlag = &cli.IntFlag{
|
||||
|
|
@ -1363,6 +1374,10 @@ func setHTTP(ctx *cli.Context, cfg *node.Config) {
|
|||
if ctx.IsSet(BatchResponseMaxSize.Name) {
|
||||
cfg.BatchResponseMaxSize = ctx.Int(BatchResponseMaxSize.Name)
|
||||
}
|
||||
|
||||
if ctx.IsSet(HTTPBodyLimitFlag.Name) {
|
||||
cfg.HTTPBodyLimit = ctx.Int(HTTPBodyLimitFlag.Name) * 1024 * 1024
|
||||
}
|
||||
}
|
||||
|
||||
// setGraphQL creates the GraphQL listener interface string from the set
|
||||
|
|
@ -1910,7 +1925,7 @@ func SetEthConfig(ctx *cli.Context, stack *node.Node, cfg *ethconfig.Config) {
|
|||
if ctx.IsSet(RPCGlobalTxFeeCapFlag.Name) {
|
||||
cfg.RPCTxFeeCap = ctx.Float64(RPCGlobalTxFeeCapFlag.Name)
|
||||
}
|
||||
if ctx.IsSet(NoDiscoverFlag.Name) {
|
||||
if ctx.Bool(NoDiscoverFlag.Name) {
|
||||
cfg.EthDiscoveryURLs, cfg.SnapDiscoveryURLs = []string{}, []string{}
|
||||
} else if ctx.IsSet(DNSDiscoveryFlag.Name) {
|
||||
urls := ctx.String(DNSDiscoveryFlag.Name)
|
||||
|
|
@ -2289,7 +2304,7 @@ func SplitTagsFlag(tagsFlag string) map[string]string {
|
|||
|
||||
for _, t := range tags {
|
||||
if t != "" {
|
||||
kv := strings.Split(t, "=")
|
||||
kv := strings.SplitN(t, "=", 2)
|
||||
|
||||
if len(kv) == 2 {
|
||||
tagsMap[kv[0]] = kv[1]
|
||||
|
|
@ -2348,7 +2363,7 @@ func tryMakeReadOnlyDatabase(ctx *cli.Context, stack *node.Node) ethdb.Database
|
|||
func IsNetworkPreset(ctx *cli.Context) bool {
|
||||
for _, flag := range NetworkFlags {
|
||||
bFlag, _ := flag.(*cli.BoolFlag)
|
||||
if ctx.IsSet(bFlag.Name) {
|
||||
if ctx.Bool(bFlag.Name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,8 +18,11 @@
|
|||
package utils
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func Test_SplitTagsFlag(t *testing.T) {
|
||||
|
|
@ -52,7 +55,9 @@ func Test_SplitTagsFlag(t *testing.T) {
|
|||
{
|
||||
"garbage",
|
||||
"smth=smthelse=123",
|
||||
map[string]string{},
|
||||
map[string]string{
|
||||
"smth": "smthelse=123",
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
|
@ -64,3 +69,52 @@ func Test_SplitTagsFlag(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsNetworkPresetUsesFlagValue(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
args []string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "unset",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "enabled",
|
||||
args: []string{"--sepolia"},
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "explicit false",
|
||||
args: []string{"--sepolia=false"},
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := newTestContext(t, tt.args, NetworkFlags...)
|
||||
if got := IsNetworkPreset(ctx); got != tt.want {
|
||||
t.Fatalf("IsNetworkPreset() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func newTestContext(t *testing.T, args []string, flags ...cli.Flag) *cli.Context {
|
||||
t.Helper()
|
||||
|
||||
set := flag.NewFlagSet("test", flag.ContinueOnError)
|
||||
for _, f := range flags {
|
||||
if err := f.Apply(set); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := set.Parse(args); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return cli.NewContext(nil, set, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -122,7 +122,7 @@ func validateHistoryPruneErr(err error, blockNum uint64, historyPruneBlock *uint
|
|||
|
||||
func testConfigFromCLI(ctx *cli.Context) (cfg testConfig) {
|
||||
flags.CheckExclusive(ctx, testMainnetFlag, testSepoliaFlag)
|
||||
if (ctx.IsSet(testMainnetFlag.Name) || ctx.IsSet(testSepoliaFlag.Name)) && ctx.IsSet(filterQueryFileFlag.Name) {
|
||||
if (ctx.Bool(testMainnetFlag.Name) || ctx.Bool(testSepoliaFlag.Name)) && ctx.IsSet(filterQueryFileFlag.Name) {
|
||||
exit(filterQueryFileFlag.Name + " cannot be used with " + testMainnetFlag.Name + " or " + testSepoliaFlag.Name)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -73,8 +73,6 @@ func latestBlobConfig(cfg *params.ChainConfig, time uint64) (BlobConfig, error)
|
|||
bc = s.BPO2
|
||||
case cfg.IsBPO1(london, time) && s.BPO1 != nil:
|
||||
bc = s.BPO1
|
||||
case cfg.IsOsaka(london, time) && s.Osaka != nil:
|
||||
bc = s.Osaka
|
||||
case cfg.IsPrague(london, time) && s.Prague != nil:
|
||||
bc = s.Prague
|
||||
case cfg.IsCancun(london, time) && s.Cancun != nil:
|
||||
|
|
|
|||
|
|
@ -120,7 +120,6 @@ func TestCalcBlobFeePostOsaka(t *testing.T) {
|
|||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||
Cancun: params.DefaultCancunBlobConfig,
|
||||
Prague: params.DefaultPragueBlobConfig,
|
||||
Osaka: params.DefaultOsakaBlobConfig,
|
||||
BPO1: ¶ms.BlobConfig{
|
||||
Target: 9,
|
||||
Max: 14,
|
||||
|
|
@ -191,7 +190,7 @@ func TestFakeExponential(t *testing.T) {
|
|||
func TestCalcExcessBlobGasEIP7918(t *testing.T) {
|
||||
var (
|
||||
cfg = params.MergedTestChainConfig
|
||||
targetBlobs = cfg.BlobScheduleConfig.Osaka.Target
|
||||
targetBlobs = cfg.BlobScheduleConfig.Prague.Target
|
||||
blobGasTarget = uint64(targetBlobs) * params.BlobTxBlobGasPerBlob
|
||||
)
|
||||
|
||||
|
|
|
|||
47
consensus/misc/eip7997.go
Normal file
47
consensus/misc/eip7997.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package misc
|
||||
|
||||
import (
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// ApplyEIP7997 inserts the deterministic deployment factory into the state as an
|
||||
// irregular state transition, as specified by EIP-7997. The factory is a keyless
|
||||
// CREATE2 factory that, once present at the canonical address on every EVM chain,
|
||||
// allows contracts to be deployed at identical addresses across chains.
|
||||
func ApplyEIP7997(statedb vm.StateDB) {
|
||||
// The account must hold the canonical factory runtime code. If its code hash
|
||||
// already matches, the chain satisfies EIP-7997 and nothing needs to change.
|
||||
wantHash := crypto.Keccak256Hash(params.DeterministicFactoryCode)
|
||||
if statedb.GetCodeHash(params.DeterministicFactoryAddress) == wantHash {
|
||||
return
|
||||
}
|
||||
if !statedb.Exist(params.DeterministicFactoryAddress) {
|
||||
statedb.CreateAccount(params.DeterministicFactoryAddress)
|
||||
}
|
||||
statedb.CreateContract(params.DeterministicFactoryAddress)
|
||||
statedb.SetCode(params.DeterministicFactoryAddress, params.DeterministicFactoryCode, tracing.CodeChangeUnspecified)
|
||||
|
||||
// Preserve a pre-existing nonce; only bump the default zero nonce to 1.
|
||||
if statedb.GetNonce(params.DeterministicFactoryAddress) == 0 {
|
||||
statedb.SetNonce(params.DeterministicFactoryAddress, 1, tracing.NonceChangeNewContract)
|
||||
}
|
||||
}
|
||||
126
core/bal_test.go
126
core/bal_test.go
|
|
@ -42,9 +42,6 @@ import (
|
|||
func balChainConfig() *params.ChainConfig {
|
||||
cfg := *params.MergedTestChainConfig
|
||||
cfg.AmsterdamTime = new(uint64)
|
||||
blob := *cfg.BlobScheduleConfig
|
||||
blob.Amsterdam = blob.Osaka
|
||||
cfg.BlobScheduleConfig = &blob
|
||||
return &cfg
|
||||
}
|
||||
|
||||
|
|
@ -518,6 +515,73 @@ func TestBALStaticCallTargetIncluded(t *testing.T) {
|
|||
assertEmpty(t, assertPresent(t, b, target))
|
||||
}
|
||||
|
||||
// makeValueCaller emits a single value-transferring CALL-family op (CALL 0xf1
|
||||
// or CALLCODE 0xf2) against `target` with value=1, then STOPs. Used together
|
||||
// with a zero-balance caller to make the value transfer fail CanTransfer.
|
||||
func makeValueCaller(op byte, target common.Address) []byte {
|
||||
code := []byte{
|
||||
0x60, 0x00, // retSize
|
||||
0x60, 0x00, // retOff
|
||||
0x60, 0x00, // argsSize
|
||||
0x60, 0x00, // argsOff
|
||||
0x60, 0x01, // value = 1
|
||||
0x73, // PUSH20 target
|
||||
}
|
||||
code = append(code, target.Bytes()...)
|
||||
return append(code, 0x5a, op, 0x50, 0x00) // GAS, op, POP, STOP
|
||||
}
|
||||
|
||||
// TestBALCallToDelegatedTargetBalanceFail asserts the EIP-7928 rule revised in
|
||||
// ethereum/EIPs#11838: when a CALL targets an EIP-7702 delegated account and the
|
||||
// delegated address passes its access_cost gas check, the delegated
|
||||
// (implementation) address MUST appear in the BAL even when the call then fails
|
||||
// its sender-balance check, because the delegation is resolved before that
|
||||
// check. CALL routes through the EIP-8037 gas path.
|
||||
func TestBALCallToDelegatedTargetBalanceFail(t *testing.T) {
|
||||
delegated := common.HexToAddress("0xde1e9a7ed") // EOA carrying a 7702 designator
|
||||
impl := common.HexToAddress("0x111111") // delegation target (implementation)
|
||||
caller := common.HexToAddress("0xca11") // zero-balance contract issuing the CALL
|
||||
|
||||
env := newBALTestEnv(types.GenesisAlloc{
|
||||
caller: {Code: makeValueCaller(0xf1 /* CALL */, delegated), Balance: common.Big0},
|
||||
delegated: {Code: types.AddressToDelegation(impl), Balance: common.Big0},
|
||||
impl: {Code: []byte{0x00}, Balance: common.Big0}, // STOP
|
||||
})
|
||||
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
assertPresent(t, b, caller)
|
||||
assertPresent(t, b, delegated)
|
||||
// The call failed its sender-balance check, so the implementation never
|
||||
// executed: it is recorded with an empty change set, but it MUST be present.
|
||||
assertEmpty(t, assertPresent(t, b, impl))
|
||||
}
|
||||
|
||||
// TestBALCallCodeToDelegatedTargetBalanceFail is the CALLCODE analogue of
|
||||
// TestBALCallToDelegatedTargetBalanceFail, exercising the EIP-7702 gas path
|
||||
// (CALLCODE/STATICCALL/DELEGATECALL) rather than the EIP-8037 one.
|
||||
func TestBALCallCodeToDelegatedTargetBalanceFail(t *testing.T) {
|
||||
delegated := common.HexToAddress("0xde1e9a7ed")
|
||||
impl := common.HexToAddress("0x111111")
|
||||
caller := common.HexToAddress("0xca11")
|
||||
|
||||
env := newBALTestEnv(types.GenesisAlloc{
|
||||
caller: {Code: makeValueCaller(0xf2 /* CALLCODE */, delegated), Balance: common.Big0},
|
||||
delegated: {Code: types.AddressToDelegation(impl), Balance: common.Big0},
|
||||
impl: {Code: []byte{0x00}, Balance: common.Big0}, // STOP
|
||||
})
|
||||
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
assertPresent(t, b, caller)
|
||||
assertPresent(t, b, delegated)
|
||||
assertEmpty(t, assertPresent(t, b, impl))
|
||||
}
|
||||
|
||||
// ============================== Revert behaviour ==============================
|
||||
|
||||
// TestBALRevertedTxStillIncluded: a tx whose top-level call REVERTs still
|
||||
|
|
@ -946,6 +1010,62 @@ func TestBALSelfDestructPreExistingContract(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestBALSelfDestructToSelfKeepsBalance: under EIP-8246 a freshly created
|
||||
// contract that self-destructs to itself keeps its balance (it is not burnt and
|
||||
// the account is not removed). The surviving balance-only account must therefore
|
||||
// be recorded in the BAL with its preserved balance.
|
||||
func TestBALSelfDestructToSelfKeepsBalance(t *testing.T) {
|
||||
env := newBALTestEnv(nil)
|
||||
// Init code: ADDRESS SELFDESTRUCT — the contract self-destructs to itself
|
||||
// during its own creation transaction (satisfying EIP-6780's same-tx rule).
|
||||
// ADDRESS (0x30) ; SELFDESTRUCT (0xff)
|
||||
init := []byte{0x30, 0xff}
|
||||
|
||||
b, receipts := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, nil, big.NewInt(100), 1_000_000, 0, init))
|
||||
})
|
||||
|
||||
created := receipts[0].ContractAddress
|
||||
cc := assertPresent(t, b, created)
|
||||
// EIP-8246: balance preserved (not burnt), account survives -> the BAL must
|
||||
// record the created address with its retained balance.
|
||||
if len(cc.BalanceChanges) != 1 || cc.BalanceChanges[0].PostBalance.Uint64() != 100 {
|
||||
t.Fatalf("self-destruct-to-self must preserve balance 100 in the BAL: %+v", cc.BalanceChanges)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBALSelfDestructToSelfPrefundedUnchanged: a pre-funded address onto which a
|
||||
// contract is deployed and which self-destructs to itself in the same
|
||||
// transaction. Under EIP-8246 the account survives with its balance unchanged,
|
||||
// so the BAL must list it only as an access (no balance/nonce/code change).
|
||||
func TestBALSelfDestructToSelfPrefundedUnchanged(t *testing.T) {
|
||||
// The contract address created by the sender's nonce-0 transaction; it is
|
||||
// pre-funded in genesis (balance only: nonce 0, no code, no storage), which
|
||||
// EIP-7610 permits as a deployment target.
|
||||
key, _ := crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
created := crypto.CreateAddress(crypto.PubkeyToAddress(key.PublicKey), 0)
|
||||
|
||||
env := newBALTestEnv(types.GenesisAlloc{
|
||||
created: {Balance: big.NewInt(77)},
|
||||
})
|
||||
// Init code: ADDRESS SELFDESTRUCT, deployed with zero value so the balance is
|
||||
// untouched (stays at the pre-funded 77).
|
||||
init := []byte{0x30, 0xff}
|
||||
|
||||
b, receipts := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, nil, big.NewInt(0), 1_000_000, 0, init))
|
||||
})
|
||||
|
||||
if receipts[0].ContractAddress != created {
|
||||
t.Fatalf("unexpected created address: have %x want %x", receipts[0].ContractAddress, created)
|
||||
}
|
||||
aa := assertPresent(t, b, created)
|
||||
// EIP-8246: balance preserved and equal to the pre-transaction value, so no
|
||||
// balance change; nonce and code end where they started (0 / empty). The
|
||||
// account is only read, with an empty change set.
|
||||
assertEmpty(t, aa)
|
||||
}
|
||||
|
||||
// ============================== Mid-tx balance round-trip ==============================
|
||||
|
||||
// TestBALMidTxBalanceRoundTrip: when an address's balance changes during a
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
|
|||
data := make([]byte, nbytes)
|
||||
return func(i int, gen *BlockGen) {
|
||||
toaddr := common.Address{}
|
||||
cost, _ := IntrinsicGas(data, nil, nil, false, params.Rules{}, params.CostPerStateByte)
|
||||
cost, _ := IntrinsicGas(data, nil, nil, common.Address{}, &toaddr, nil, params.Rules{}, params.CostPerStateByte)
|
||||
signer := gen.Signer()
|
||||
gasPrice := big.NewInt(0)
|
||||
if gen.header.BaseFee != nil {
|
||||
|
|
|
|||
|
|
@ -55,8 +55,9 @@ var (
|
|||
UBTTime: u64(0),
|
||||
TerminalTotalDifficulty: common.Big0,
|
||||
EnableUBTAtGenesis: true,
|
||||
// UBT inherits its blob schedule; nothing to declare here.
|
||||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||
UBT: params.DefaultPragueBlobConfig,
|
||||
Prague: params.DefaultPragueBlobConfig,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
|
@ -64,12 +65,12 @@ var (
|
|||
func TestProcessUBT(t *testing.T) {
|
||||
var (
|
||||
code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`)
|
||||
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
|
||||
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, common.Address{}, nil, nil, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
|
||||
// A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness
|
||||
// will not contain that copied data.
|
||||
// Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985
|
||||
codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`)
|
||||
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
|
||||
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, common.Address{}, nil, nil, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
|
||||
signer = types.LatestSigner(testUBTChainConfig)
|
||||
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain
|
||||
|
|
|
|||
|
|
@ -385,6 +385,11 @@ func GenerateChain(config *params.ChainConfig, parent *types.Block, engine conse
|
|||
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(b.header.Number) == 0 {
|
||||
misc.ApplyDAOHardFork(statedb)
|
||||
}
|
||||
// EIP-7997: insert the deterministic deployment factory at the Amsterdam
|
||||
// activation block via an irregular state transition.
|
||||
if config.IsAmsterdam(b.header.Number, b.header.Time) && !config.IsAmsterdam(parent.Number(), parent.Time()) {
|
||||
misc.ApplyEIP7997(statedb)
|
||||
}
|
||||
|
||||
if config.IsPrague(b.header.Number, b.header.Time) || config.IsUBT(b.header.Number, b.header.Time) {
|
||||
// EIP-2935
|
||||
|
|
|
|||
214
core/eip2780_test.go
Normal file
214
core/eip2780_test.go
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// TestEIP2780Intrinsic checks the intrinsic-gas decomposition.
|
||||
func TestEIP2780Intrinsic(t *testing.T) {
|
||||
var (
|
||||
from = common.HexToAddress("0x1111111111111111111111111111111111111111")
|
||||
to = common.HexToAddress("0x2222222222222222222222222222222222222222")
|
||||
)
|
||||
cases := []struct {
|
||||
name string
|
||||
to *common.Address
|
||||
value *uint256.Int
|
||||
want vm.GasCosts
|
||||
}{
|
||||
{
|
||||
name: "self-transfer",
|
||||
to: &from,
|
||||
value: uint256.NewInt(1),
|
||||
want: vm.GasCosts{RegularGas: params.TxBaseCost2780}, // 12,000
|
||||
},
|
||||
{
|
||||
name: "self-transfer/zero-value",
|
||||
to: &from,
|
||||
value: uint256.NewInt(0),
|
||||
want: vm.GasCosts{RegularGas: params.TxBaseCost2780}, // 12,000
|
||||
},
|
||||
{
|
||||
name: "zero-value call",
|
||||
to: &to,
|
||||
value: uint256.NewInt(0),
|
||||
// TxBaseCost + ColdAccountAccess = 15,000
|
||||
want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780},
|
||||
},
|
||||
{
|
||||
name: "value transfer to existing EOA",
|
||||
to: &to,
|
||||
value: uint256.NewInt(1),
|
||||
// TxBaseCost + ColdAccountAccess + TxValueCost + TransferLogCost = 21,000
|
||||
want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
|
||||
params.TxValueCost2780 + params.TransferLogCost2780},
|
||||
},
|
||||
{
|
||||
name: "contract creation, value = 0",
|
||||
to: nil,
|
||||
value: uint256.NewInt(0),
|
||||
// TxBaseCost + CreateAccess = 23,000 regular, plus one account creation in state.
|
||||
want: vm.GasCosts{
|
||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780,
|
||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "contract creation, value > 0",
|
||||
to: nil,
|
||||
value: uint256.NewInt(1),
|
||||
// TxBaseCost + CreateAccess + TransferLogCost = 24,756 regular, plus account creation.
|
||||
want: vm.GasCosts{
|
||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 + params.TransferLogCost2780,
|
||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
got, err := IntrinsicGas(nil, nil, nil, from, tc.to, tc.value, rules8037, params.CostPerStateByte)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got != tc.want {
|
||||
t.Fatalf("gas mismatch: got %+v, want %+v", got, tc.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestEIP2780Gas checks every "Transaction reference case" in
|
||||
// the EIP-2780 specification end-to-end, asserting the two-dimensional charge
|
||||
// (intrinsic + top-level + execution) recorded in the block gas pool.
|
||||
func TestEIP2780Gas(t *testing.T) {
|
||||
const (
|
||||
cold = params.ColdAccountAccess2780
|
||||
base = params.TxBaseCost2780
|
||||
valueCst = params.TxValueCost2780 + params.TransferLogCost2780
|
||||
)
|
||||
var (
|
||||
existingEOA = common.HexToAddress("0xe0a0000000000000000000000000000000000001")
|
||||
stopContract = common.HexToAddress("0xc0de000000000000000000000000000000000001")
|
||||
delegated = common.HexToAddress("0xde1e000000000000000000000000000000000001")
|
||||
emptyTarget = common.HexToAddress("0x7a76000000000000000000000000000000000001") // never allocated
|
||||
freshEOA = common.HexToAddress("0xbeef000000000000000000000000000000000001") // never allocated
|
||||
)
|
||||
// Shared world: a funded EOA, a STOP contract and an account delegated to a
|
||||
// non-existent (codeless) target. The delegation target is intentionally
|
||||
// absent so resolving it executes no code.
|
||||
base7702 := types.GenesisAlloc{
|
||||
existingEOA: {Balance: big.NewInt(1)},
|
||||
stopContract: {Code: []byte{0x00}}, // STOP
|
||||
delegated: {Code: types.AddressToDelegation(emptyTarget)},
|
||||
}
|
||||
// valueCreateTx builds a contract-creation transaction carrying value.
|
||||
valueCreateTx := func(value int64) *types.Transaction {
|
||||
return types.MustSignNewTx(senderKey, signer8037, &types.DynamicFeeTx{
|
||||
ChainID: cfg8037.ChainID, Nonce: 0, To: nil, Value: big.NewInt(value),
|
||||
Gas: 300_000, GasFeeCap: big.NewInt(0), GasTipCap: big.NewInt(0),
|
||||
})
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
tx *types.Transaction
|
||||
wantRegular, wantState uint64
|
||||
}{
|
||||
// case 1: ETH transfer to self.
|
||||
{"self-transfer", callTx(0, senderAddr, 1, 100_000, nil), base, 0},
|
||||
// case 2: no-transfer to an existing EOA.
|
||||
{"zero-value/eoa", callTx(0, existingEOA, 0, 100_000, nil), base + cold, 0},
|
||||
// case 3: no-transfer to a contract.
|
||||
{"zero-value/contract", callTx(0, stopContract, 0, 100_000, nil), base + cold, 0},
|
||||
// case 4: ETH transfer to an existing EOA.
|
||||
{"value/eoa", callTx(0, existingEOA, 1, 100_000, nil), base + cold + valueCst, 0},
|
||||
// case 5: ETH transfer to a contract.
|
||||
{"value/contract", callTx(0, stopContract, 1, 100_000, nil), base + cold + valueCst, 0},
|
||||
// case 6: no-transfer to a 7702-delegated account.
|
||||
{"zero-value/delegated", callTx(0, delegated, 0, 100_000, nil), base + 2*cold, 0},
|
||||
// case 7: ETH transfer to a 7702-delegated account (no new-account charge).
|
||||
{"value/delegated", callTx(0, delegated, 1, 100_000, nil), base + 2*cold + valueCst, 0},
|
||||
// case 8: ETH transfer creating a new account.
|
||||
{"value/new-account", callTx(0, freshEOA, 1, 300_000, nil), base + cold + valueCst, newAccountState},
|
||||
// case 9: contract-creation transaction, value = 0.
|
||||
{"create/zero-value", createTx(0, 300_000, nil), base + params.CreateAccess2780, newAccountState},
|
||||
// case 10: contract-creation transaction, value > 0.
|
||||
{"create/value", valueCreateTx(1), base + params.CreateAccess2780 + params.TransferLogCost2780, newAccountState},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
res, gp, err := applyMsg(t, mkState(senderAlloc(base7702)), tc.tx)
|
||||
if err != nil {
|
||||
t.Fatalf("consensus error: %v", err)
|
||||
}
|
||||
if res.Err != nil {
|
||||
t.Fatalf("execution failed: %v", res.Err)
|
||||
}
|
||||
if gp.cumulativeRegular != tc.wantRegular {
|
||||
t.Errorf("regular gas = %d, want %d", gp.cumulativeRegular, tc.wantRegular)
|
||||
}
|
||||
if gp.cumulativeState != tc.wantState {
|
||||
t.Errorf("state gas = %d, want %d", gp.cumulativeState, tc.wantState)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestEIP2780NewAccountFunded verifies that a value transfer creating a new
|
||||
// account both materializes and funds the recipient.
|
||||
func TestEIP2780NewAccountFunded(t *testing.T) {
|
||||
fresh := common.HexToAddress("0xbeef000000000000000000000000000000000002")
|
||||
sdb := mkState(senderAlloc(nil))
|
||||
if _, _, err := applyMsg(t, sdb, callTx(0, fresh, 1, 300_000, nil)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !sdb.Exist(fresh) || sdb.GetBalance(fresh).Cmp(uint256.NewInt(1)) != 0 {
|
||||
t.Fatalf("recipient not funded: exist=%v balance=%v", sdb.Exist(fresh), sdb.GetBalance(fresh))
|
||||
}
|
||||
}
|
||||
|
||||
// TestEIP2780InsufficientGasForCallCharge verifies that a value transfer
|
||||
// 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))
|
||||
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")
|
||||
}
|
||||
}
|
||||
113
core/eip7997_test.go
Normal file
113
core/eip7997_test.go
Normal file
|
|
@ -0,0 +1,113 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Tests for EIP-7997: the deterministic deployment factory inserted as an
|
||||
// irregular state transition at the Amsterdam activation block.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/misc"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// TestApplyEIP7997 verifies the irregular state transition seeds the factory
|
||||
// account with the canonical code and nonce.
|
||||
func TestApplyEIP7997(t *testing.T) {
|
||||
sdb := mkState(nil)
|
||||
misc.ApplyEIP7997(sdb)
|
||||
|
||||
if got := sdb.GetCode(params.DeterministicFactoryAddress); !bytes.Equal(got, params.DeterministicFactoryCode) {
|
||||
t.Fatalf("factory code mismatch:\n got %x\nwant %x", got, params.DeterministicFactoryCode)
|
||||
}
|
||||
if got := sdb.GetNonce(params.DeterministicFactoryAddress); got != 1 {
|
||||
t.Fatalf("factory nonce = %d, want %d", got, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// TestApplyEIP7997Existing checks that a chain which already hosts the factory
|
||||
// (for example via its keyless creation transaction) is left untouched, so the
|
||||
// transition never rewrites an existing nonce.
|
||||
func TestApplyEIP7997Existing(t *testing.T) {
|
||||
sdb := mkState(types.GenesisAlloc{
|
||||
params.DeterministicFactoryAddress: {Code: params.DeterministicFactoryCode, Nonce: 5},
|
||||
})
|
||||
misc.ApplyEIP7997(sdb)
|
||||
|
||||
if got := sdb.GetNonce(params.DeterministicFactoryAddress); got != 5 {
|
||||
t.Fatalf("existing factory nonce overwritten: got %d, want 5", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestApplyEIP7997WrongCode checks that an account occupying the factory address
|
||||
// with the wrong code is force-overwritten with the canonical runtime code, while
|
||||
// a pre-existing non-zero nonce is preserved.
|
||||
func TestApplyEIP7997WrongCode(t *testing.T) {
|
||||
sdb := mkState(types.GenesisAlloc{
|
||||
params.DeterministicFactoryAddress: {Code: []byte{0x60, 0x00}, Nonce: 7},
|
||||
})
|
||||
misc.ApplyEIP7997(sdb)
|
||||
|
||||
if got := sdb.GetCode(params.DeterministicFactoryAddress); !bytes.Equal(got, params.DeterministicFactoryCode) {
|
||||
t.Fatalf("factory code not overwritten:\n got %x\nwant %x", got, params.DeterministicFactoryCode)
|
||||
}
|
||||
if got := sdb.GetNonce(params.DeterministicFactoryAddress); got != 7 {
|
||||
t.Fatalf("factory nonce = %d, want %d (existing nonce must be preserved)", got, 7)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEIP7997FactoryDeploys exercises the inserted factory bytecode: calling it
|
||||
// with a salt followed by init code must CREATE2-deploy the contract at the
|
||||
// canonical deterministic address and return that address (20 bytes, unpadded).
|
||||
func TestEIP7997FactoryDeploys(t *testing.T) {
|
||||
sdb := mkState(nil)
|
||||
misc.ApplyEIP7997(sdb)
|
||||
|
||||
var (
|
||||
caller = common.Address{0xca}
|
||||
salt [32]byte
|
||||
// initcode returning the single-byte runtime 0xfe:
|
||||
// PUSH1 0xfe PUSH1 0x00 MSTORE8 PUSH1 0x01 PUSH1 0x00 RETURN
|
||||
initcode = common.FromHex("60fe60005360016000f3")
|
||||
)
|
||||
salt[31] = 0x42
|
||||
|
||||
input := append(append([]byte{}, salt[:]...), initcode...)
|
||||
|
||||
ret, _, err := amsterdamCoreEVM(sdb).Call(caller, params.DeterministicFactoryAddress, input, vm.NewGasBudget(10_000_000, 0), new(uint256.Int))
|
||||
if err != nil {
|
||||
t.Fatalf("factory call failed: %v", err)
|
||||
}
|
||||
|
||||
want := crypto.CreateAddress2(params.DeterministicFactoryAddress, salt, crypto.Keccak256(initcode))
|
||||
if len(ret) != 20 {
|
||||
t.Fatalf("factory returned %d bytes, want 20", len(ret))
|
||||
}
|
||||
if got := common.BytesToAddress(ret); got != want {
|
||||
t.Fatalf("factory returned address %x, want %x", got, want)
|
||||
}
|
||||
if code := sdb.GetCode(want); !bytes.Equal(code, []byte{0xfe}) {
|
||||
t.Fatalf("deployed runtime code = %x, want fe", code)
|
||||
}
|
||||
}
|
||||
|
|
@ -191,7 +191,7 @@ var (
|
|||
|
||||
// A creation tx's intrinsic gas pre-charges one account creation as state gas.
|
||||
func TestCreateTxIntrinsicChargesAccountUnconditionally(t *testing.T) {
|
||||
cost, err := IntrinsicGas(nil, nil, nil, true, rules8037, params.CostPerStateByte)
|
||||
cost, err := IntrinsicGas(nil, nil, nil, common.Address{}, nil, nil, rules8037, params.CostPerStateByte)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -325,7 +325,7 @@ func clearSlots(addr common.Address, n int) (types.GenesisAlloc, []byte) {
|
|||
// tx_gas_used_before_refund (peak) exceeds the post-refund gas used.
|
||||
func TestGasUsedBeforeRefund(t *testing.T) {
|
||||
c := common.HexToAddress("0xc1ea0")
|
||||
alloc, _ := clearSlots(c, 1)
|
||||
alloc, _ := clearSlots(c, 4)
|
||||
res, _, err := applyMsg(t, mkState(senderAlloc(alloc)), callTx(0, c, 0, 1_000_000, nil))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -351,8 +351,8 @@ func TestRefundCappedAt20Percent(t *testing.T) {
|
|||
// The EIP-7623 calldata floor is applied after the refund.
|
||||
func TestRefundCalldataFloorAfterRefund(t *testing.T) {
|
||||
data := make([]byte, 1000) // all-zero calldata: floor dominates a bare call
|
||||
floor, _ := FloorDataGas(rules8037, data, nil)
|
||||
to := common.HexToAddress("0xeeee")
|
||||
floor, _ := FloorDataGas(rules8037, senderAddr, &to, new(uint256.Int), data, nil)
|
||||
res, _, err := applyMsg(t, mkState(senderAlloc(nil)), callTx(0, to, 0, 1_000_000, data))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -367,7 +367,7 @@ func TestRefundFloorNegatesRefund(t *testing.T) {
|
|||
c := common.HexToAddress("0xc1ea1")
|
||||
alloc, _ := clearSlots(c, 1)
|
||||
data := make([]byte, 1000)
|
||||
floor, _ := FloorDataGas(rules8037, data, nil)
|
||||
floor, _ := FloorDataGas(rules8037, senderAddr, &c, new(uint256.Int), data, nil)
|
||||
res, _, err := applyMsg(t, mkState(senderAlloc(alloc)), callTx(0, c, 0, 1_000_000, data))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
|
@ -474,7 +474,7 @@ var delegate8037 = common.HexToAddress("0xde1e8a7e")
|
|||
|
||||
// Intrinsic gas pre-charges the worst-case (account + indicator) per auth.
|
||||
func TestAuthIntrinsicWorstCase(t *testing.T) {
|
||||
cost, err := IntrinsicGas(nil, nil, []types.SetCodeAuthorization{{}}, false, rules8037, params.CostPerStateByte)
|
||||
cost, err := IntrinsicGas(nil, nil, []types.SetCodeAuthorization{{}}, common.Address{}, &delegate8037, nil, rules8037, params.CostPerStateByte)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
|
|||
110
core/eip8038_test.go
Normal file
110
core/eip8038_test.go
Normal file
|
|
@ -0,0 +1,110 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// EIP-8038 authorization accounting tests. The per-authorization intrinsic gas
|
||||
// pre-charges ACCOUNT_WRITE (regular) on top of REGULAR_PER_AUTH_BASE_COST.
|
||||
// applyAuthorization refunds that ACCOUNT_WRITE to the refund counter in exactly
|
||||
// the cases where no new account leaf is written: an invalid authorization, or
|
||||
// an authority whose account already exists. These white-box tests invoke
|
||||
// applyAuthorization directly and read the raw refund counter, so they observe
|
||||
// the refund before the EIP-3529 cap is applied.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// newAuthTestTransition builds a minimal stateTransition with a state reservoir,
|
||||
// suitable for calling applyAuthorization directly.
|
||||
func newAuthTestTransition(sdb *state.StateDB) *stateTransition {
|
||||
st := newStateTransition(amsterdamCoreEVM(sdb), &Message{}, NewGasPool(30_000_000))
|
||||
st.gasRemaining = vm.NewGasBudget(0, 1_000_000) // reservoir for state-gas refills
|
||||
return st
|
||||
}
|
||||
|
||||
// A net-new delegation on a fresh authority writes a new account leaf, so the
|
||||
// intrinsic ACCOUNT_WRITE stands (no refund).
|
||||
func TestAuthAccountWriteNetNewNoRefund(t *testing.T) {
|
||||
auth, _ := signAuth(t, authKeyA, delegate8037, 0)
|
||||
st := newAuthTestTransition(mkState(senderAlloc(nil)))
|
||||
if err := st.applyAuthorization(rules8037, &auth, map[common.Address]bool{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := st.state.GetRefund(); got != 0 {
|
||||
t.Fatalf("refund = %d, want 0 (net-new account write)", got)
|
||||
}
|
||||
}
|
||||
|
||||
// A pre-existing authority writes no new account leaf, so the intrinsic
|
||||
// ACCOUNT_WRITE is refunded.
|
||||
func TestAuthAccountWriteExistsRefund(t *testing.T) {
|
||||
auth, authority := signAuth(t, authKeyA, delegate8037, 0)
|
||||
st := newAuthTestTransition(mkState(senderAlloc(types.GenesisAlloc{authority: {Balance: big.NewInt(1)}})))
|
||||
if err := st.applyAuthorization(rules8037, &auth, map[common.Address]bool{}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := st.state.GetRefund(); got != params.AccountWriteAmsterdam {
|
||||
t.Fatalf("refund = %d, want %d (account already exists)", got, params.AccountWriteAmsterdam)
|
||||
}
|
||||
}
|
||||
|
||||
// An invalid authorization is skipped without writing any account leaf, so its
|
||||
// intrinsic ACCOUNT_WRITE is refunded.
|
||||
func TestAuthAccountWriteInvalidRefund(t *testing.T) {
|
||||
k, _ := crypto.HexToECDSA(authKeyA)
|
||||
bad, _ := types.SignSetCode(k, types.SetCodeAuthorization{
|
||||
ChainID: *uint256.NewInt(999), Address: delegate8037, Nonce: 0, // wrong chain id
|
||||
})
|
||||
st := newAuthTestTransition(mkState(senderAlloc(nil)))
|
||||
if err := st.applyAuthorization(rules8037, &bad, map[common.Address]bool{}); err == nil {
|
||||
t.Fatal("expected invalid-authorization error")
|
||||
}
|
||||
if got := st.state.GetRefund(); got != params.AccountWriteAmsterdam {
|
||||
t.Fatalf("refund = %d, want %d (invalid authorization)", got, params.AccountWriteAmsterdam)
|
||||
}
|
||||
}
|
||||
|
||||
// The same authority across two authorizations writes its account leaf only
|
||||
// once: the first auth pays ACCOUNT_WRITE, the second (which now sees the
|
||||
// account as existing) is refunded.
|
||||
func TestAuthAccountWriteDuplicateOnce(t *testing.T) {
|
||||
a0, _ := signAuth(t, authKeyA, delegate8037, 0)
|
||||
a1, _ := signAuth(t, authKeyA, delegate8037, 1)
|
||||
st := newAuthTestTransition(mkState(senderAlloc(nil)))
|
||||
delegates := map[common.Address]bool{}
|
||||
if err := st.applyAuthorization(rules8037, &a0, delegates); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := st.state.GetRefund(); got != 0 {
|
||||
t.Fatalf("refund after first auth = %d, want 0", got)
|
||||
}
|
||||
if err := st.applyAuthorization(rules8037, &a1, delegates); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := st.state.GetRefund(); got != params.AccountWriteAmsterdam {
|
||||
t.Fatalf("refund after duplicate auth = %d, want %d", got, params.AccountWriteAmsterdam)
|
||||
}
|
||||
}
|
||||
102
core/eip8246_test.go
Normal file
102
core/eip8246_test.go
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/consensus/beacon"
|
||||
"github.com/ethereum/go-ethereum/consensus/ethash"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
// TestEIP8246SelfdestructNoBurn verifies that, once EIP-8246 is active
|
||||
// (Amsterdam), a contract that is created and self-destructs to itself within
|
||||
// the same transaction keeps its balance instead of burning it: the account
|
||||
// survives as a balance-only account (no code, zero nonce, balance preserved).
|
||||
//
|
||||
// https://eips.ethereum.org/EIPS/eip-8246
|
||||
func TestEIP8246SelfdestructNoBurn(t *testing.T) {
|
||||
var (
|
||||
key1, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
addr1 = crypto.PubkeyToAddress(key1.PublicKey)
|
||||
config = *params.MergedTestChainConfig
|
||||
signer = types.LatestSigner(&config)
|
||||
engine = beacon.New(ethash.NewFaker())
|
||||
value = big.NewInt(1_000_000)
|
||||
// Init code: ADDRESS (0x30) ; SELFDESTRUCT (0xff). The created contract
|
||||
// self-destructs to itself during its own creation transaction.
|
||||
initcode = common.FromHex("30ff")
|
||||
)
|
||||
// TODO: drop this hacky Amsterdam config initialization once the final
|
||||
// Amsterdam config is available (mirrors TestEthTransferLogs).
|
||||
config.AmsterdamTime = new(uint64)
|
||||
|
||||
gspec := &Genesis{
|
||||
Config: &config,
|
||||
Alloc: types.GenesisAlloc{
|
||||
addr1: {Balance: newGwei(1_000_000_000)},
|
||||
},
|
||||
}
|
||||
// The contract created by addr1's first (nonce 0) transaction.
|
||||
created := crypto.CreateAddress(addr1, 0)
|
||||
|
||||
db, blocks, _ := GenerateChainWithGenesis(gspec, engine, 1, func(i int, b *BlockGen) {
|
||||
tx := types.MustSignNewTx(key1, signer, &types.DynamicFeeTx{
|
||||
ChainID: gspec.Config.ChainID,
|
||||
Nonce: 0,
|
||||
To: nil, // contract creation
|
||||
Gas: 1_000_000,
|
||||
GasFeeCap: newGwei(5),
|
||||
GasTipCap: newGwei(5),
|
||||
Value: value,
|
||||
Data: initcode,
|
||||
})
|
||||
b.AddTx(tx)
|
||||
})
|
||||
|
||||
chain, err := NewBlockChain(db, gspec, engine, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create chain: %v", err)
|
||||
}
|
||||
defer chain.Stop()
|
||||
// Read the post-state of the generated block directly. InsertChain is avoided
|
||||
// on purpose: it would additionally verify the EIP-7928 block access list,
|
||||
// which the chain-generation harness on this branch does not yet populate
|
||||
// consistently — an orthogonal concern to the EIP-8246 state semantics under
|
||||
// test here.
|
||||
state, err := chain.StateAt(blocks[0].Header())
|
||||
if err != nil {
|
||||
t.Fatalf("failed to obtain block state: %v", err)
|
||||
}
|
||||
// EIP-8246: the self-destructed, freshly-created contract keeps its balance
|
||||
// rather than burning it, so the account survives.
|
||||
if got := state.GetBalance(created); got.ToBig().Cmp(value) != 0 {
|
||||
t.Errorf("created account balance = %v, want %v (EIP-8246: balance must be preserved, not burned)", got, value)
|
||||
}
|
||||
// It survives as a balance-only account: nonce reset to 0 and no code.
|
||||
if got := state.GetNonce(created); got != 0 {
|
||||
t.Errorf("created account nonce = %d, want 0", got)
|
||||
}
|
||||
if got := state.GetCodeSize(created); got != 0 {
|
||||
t.Errorf("created account code size = %d, want 0 (code must be cleared)", got)
|
||||
}
|
||||
}
|
||||
|
|
@ -66,11 +66,6 @@ var (
|
|||
// have enough funds for transfer(topmost call only).
|
||||
ErrInsufficientFundsForTransfer = errors.New("insufficient funds for transfer")
|
||||
|
||||
// ErrInsufficientBalanceWitness is returned if the transaction sender has enough
|
||||
// funds to cover the transfer, but not enough to pay for witness access/modification
|
||||
// costs for the transaction
|
||||
ErrInsufficientBalanceWitness = errors.New("insufficient funds to cover witness access costs for transaction")
|
||||
|
||||
// ErrInsufficientFunds is returned if the total cost of executing a transaction
|
||||
// is higher than the balance of the user's account.
|
||||
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")
|
||||
|
|
|
|||
|
|
@ -78,11 +78,7 @@ func testEthTransferLogs(t *testing.T, value uint64) {
|
|||
engine = beacon.New(ethash.NewFaker())
|
||||
)
|
||||
|
||||
//TODO remove this hacky config initialization when final Amsterdam config is available
|
||||
config.AmsterdamTime = new(uint64)
|
||||
blobConfig := *config.BlobScheduleConfig
|
||||
blobConfig.Amsterdam = blobConfig.Osaka
|
||||
config.BlobScheduleConfig = &blobConfig
|
||||
|
||||
gspec := &Genesis{
|
||||
Config: &config,
|
||||
|
|
|
|||
|
|
@ -277,10 +277,11 @@ func (e *GenesisMismatchError) Error() string {
|
|||
|
||||
// ChainOverrides contains the changes to chain config.
|
||||
type ChainOverrides struct {
|
||||
OverrideOsaka *uint64
|
||||
OverrideBPO1 *uint64
|
||||
OverrideBPO2 *uint64
|
||||
OverrideUBT *uint64
|
||||
OverrideOsaka *uint64
|
||||
OverrideAmsterdam *uint64
|
||||
OverrideBPO1 *uint64
|
||||
OverrideBPO2 *uint64
|
||||
OverrideUBT *uint64
|
||||
}
|
||||
|
||||
// apply applies the chain overrides on the supplied chain config.
|
||||
|
|
@ -291,6 +292,9 @@ func (o *ChainOverrides) apply(cfg *params.ChainConfig) error {
|
|||
if o.OverrideOsaka != nil {
|
||||
cfg.OsakaTime = o.OverrideOsaka
|
||||
}
|
||||
if o.OverrideAmsterdam != nil {
|
||||
cfg.AmsterdamTime = o.OverrideAmsterdam
|
||||
}
|
||||
if o.OverrideBPO1 != nil {
|
||||
cfg.BPO1Time = o.OverrideBPO1
|
||||
}
|
||||
|
|
@ -721,6 +725,8 @@ func DeveloperGenesisBlock(gasLimit uint64, faucet *common.Address) *Genesis {
|
|||
// EIP-8282 - Builder Execution Requests
|
||||
params.BuilderDepositAddress: {Nonce: 1, Code: params.BuilderDepositCode, Balance: common.Big0},
|
||||
params.BuilderExitAddress: {Nonce: 1, Code: params.BuilderExitCode, Balance: common.Big0},
|
||||
// EIP-7997 - Deterministic deployment factory
|
||||
params.DeterministicFactoryAddress: {Nonce: 1, Code: params.DeterministicFactoryCode, Balance: common.Big0},
|
||||
},
|
||||
}
|
||||
if faucet != nil {
|
||||
|
|
|
|||
|
|
@ -293,8 +293,6 @@ func TestBinaryGenesisCommit(t *testing.T) {
|
|||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||
Cancun: params.DefaultCancunBlobConfig,
|
||||
Prague: params.DefaultPragueBlobConfig,
|
||||
Osaka: params.DefaultOsakaBlobConfig,
|
||||
UBT: params.DefaultPragueBlobConfig,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -112,7 +112,7 @@ func decodeV2(file *os.File) *freezerTableMeta {
|
|||
return nil
|
||||
}
|
||||
if o.Offset > math.MaxInt64 {
|
||||
log.Error("Invalid flushOffset %d in freezer metadata", o.Offset, "file", file.Name())
|
||||
log.Error("Invalid flushOffset %d in freezer metadata", "offset", o.Offset, "file", file.Name())
|
||||
return nil
|
||||
}
|
||||
return &freezerTableMeta{
|
||||
|
|
|
|||
|
|
@ -611,9 +611,18 @@ func (t *freezerTable) truncateHead(items uint64) error {
|
|||
if existing <= items {
|
||||
return nil
|
||||
}
|
||||
if items < t.itemHidden.Load() {
|
||||
|
||||
hidden := t.itemHidden.Load()
|
||||
|
||||
if items < hidden {
|
||||
if existing == hidden {
|
||||
// Empty table means that it is newly added. Its tail would be
|
||||
// at the head, so we have to align the table down to the new head.
|
||||
return t.resetTo(items)
|
||||
}
|
||||
return errors.New("truncation below tail")
|
||||
}
|
||||
|
||||
// We need to truncate, save the old size for metrics tracking
|
||||
oldSize, err := t.sizeNolock()
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -516,7 +516,7 @@ func TestFreezerCloseSync(t *testing.T) {
|
|||
if err := f.SyncAncient(); err == nil {
|
||||
t.Fatalf("want error, have nil")
|
||||
} else if have, want := err.Error(), "[closed closed]"; have != want {
|
||||
t.Fatalf("want %v, have %v", have, want)
|
||||
t.Fatalf("want %v, have %v", want, have)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -107,12 +107,18 @@ type Trie interface {
|
|||
// in the trie with provided address.
|
||||
UpdateAccount(address common.Address, account *types.StateAccount, codeLen int) error
|
||||
|
||||
// UpdateAccountBatch attempts to update a list of accounts in the batch manner.
|
||||
UpdateAccountBatch(addresses []common.Address, accounts []*types.StateAccount, codeLengths []int) error
|
||||
|
||||
// UpdateStorage associates key with value in the trie. If value has length zero,
|
||||
// any existing value is deleted from the trie. The value bytes must not be modified
|
||||
// by the caller while they are stored in the trie. If a node was not found in the
|
||||
// database, a trie.MissingNodeError is returned.
|
||||
UpdateStorage(addr common.Address, key, value []byte) error
|
||||
|
||||
// UpdateStorageBatch attempts to update a list of storages in the batch manner.
|
||||
UpdateStorageBatch(_ common.Address, keys [][]byte, values [][]byte) error
|
||||
|
||||
// DeleteAccount abstracts an account deletion from the trie.
|
||||
DeleteAccount(address common.Address) error
|
||||
|
||||
|
|
|
|||
|
|
@ -614,16 +614,17 @@ func (s *StateDB) getStateObject(addr common.Address) *stateObject {
|
|||
}
|
||||
s.AccountReads += time.Since(start)
|
||||
|
||||
// Short circuit if the account is not found
|
||||
if acct == nil {
|
||||
return nil
|
||||
}
|
||||
// Schedule the resolved account for prefetching if it's enabled.
|
||||
// Schedule the account path for prefetching if it's enabled. Even if the
|
||||
// account is absent, the trie path proves its non-existence for witnesses.
|
||||
if s.prefetcher != nil {
|
||||
if err = s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, []common.Address{addr}, nil, true); err != nil {
|
||||
log.Error("Failed to prefetch account", "addr", addr, "err", err)
|
||||
}
|
||||
}
|
||||
// Short circuit if the account is not found
|
||||
if acct == nil {
|
||||
return nil
|
||||
}
|
||||
// Insert into the live set
|
||||
obj := newObject(s, addr, acct)
|
||||
s.setStateObject(obj)
|
||||
|
|
@ -764,50 +765,15 @@ func (s *StateDB) GetRefund() uint64 {
|
|||
return s.refund
|
||||
}
|
||||
|
||||
type removedAccountWithBalance struct {
|
||||
address common.Address
|
||||
balance *uint256.Int
|
||||
}
|
||||
|
||||
// LogsForBurnAccounts returns the eth burn logs for accounts scheduled for
|
||||
// removal which still have positive balance. The purpose of this function is
|
||||
// to handle a corner case of EIP-7708 where a self-destructed account might
|
||||
// still receive funds between sending/burning its previous balance and actual
|
||||
// removal. In this case the burning of these remaining balances still need to
|
||||
// be logged.
|
||||
// Specification EIP-7708: https://eips.ethereum.org/EIPS/eip-7708
|
||||
//
|
||||
// This function should only be invoked at the transaction boundary, specifically
|
||||
// before the Finalise.
|
||||
func (s *StateDB) LogsForBurnAccounts() []*types.Log {
|
||||
var list []removedAccountWithBalance
|
||||
for addr := range s.journal.mutations {
|
||||
if obj, exist := s.stateObjects[addr]; exist && obj.selfDestructed && !obj.Balance().IsZero() {
|
||||
list = append(list, removedAccountWithBalance{
|
||||
address: obj.address,
|
||||
balance: obj.Balance(),
|
||||
})
|
||||
}
|
||||
}
|
||||
if list == nil {
|
||||
return nil
|
||||
}
|
||||
sort.Slice(list, func(i, j int) bool {
|
||||
return list[i].address.Cmp(list[j].address) < 0
|
||||
})
|
||||
logs := make([]*types.Log, len(list))
|
||||
for i, acct := range list {
|
||||
logs[i] = types.EthBurnLog(acct.address, acct.balance)
|
||||
}
|
||||
return logs
|
||||
}
|
||||
|
||||
// Finalise finalises the state by removing the destructed objects and clears
|
||||
// the journal as well as the refunds. Finalise, however, will not push any updates
|
||||
// into the tries just yet. Only IntermediateRoot or Commit will do that.
|
||||
func (s *StateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlockAccessList {
|
||||
if s.stateAccessList != nil {
|
||||
return s.finaliseAmsterdam(deleteEmptyObjects)
|
||||
}
|
||||
addressesToPrefetch := make([]common.Address, 0, len(s.journal.mutations))
|
||||
for addr, state := range s.journal.mutations {
|
||||
for addr := range s.journal.mutations {
|
||||
obj, exist := s.stateObjects[addr]
|
||||
if !exist {
|
||||
// RIPEMD160 (0x03) gets an extra dirty marker for a historical
|
||||
|
|
@ -831,46 +797,103 @@ func (s *StateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlockAccess
|
|||
if _, ok := s.stateObjectsDestruct[obj.address]; !ok {
|
||||
s.stateObjectsDestruct[obj.address] = obj
|
||||
}
|
||||
// Aggregate the account mutation into the block-level accessList
|
||||
// if Amsterdam has been activated.
|
||||
if s.stateAccessList != nil {
|
||||
// Notably, if the account is deleted during the transaction,
|
||||
// its pre-transaction nonce, code, and storage must be empty.
|
||||
//
|
||||
// EIP-6780 restricts self-destruct to contracts deployed within
|
||||
// the same transaction, while EIP-7610 rejects deployments to
|
||||
// destinations with non-empty storage, non-zero nonce and non-empty
|
||||
// code.
|
||||
//
|
||||
// Therefore, when an account is deleted, its pre-transaction nonce
|
||||
// code and storage is guaranteed to be empty, leaving nothing to
|
||||
// clean up here.
|
||||
balance := uint256.NewInt(0)
|
||||
if state.balanceSet && balance.Cmp(state.balance) != 0 {
|
||||
s.stateAccessList.BalanceChange(s.blockAccessIndex, addr, balance)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Aggregate the account mutation into the block-level accessList
|
||||
// if Amsterdam has been activated.
|
||||
if s.stateAccessList != nil {
|
||||
balance := obj.Balance()
|
||||
if state.balanceSet && balance.Cmp(state.balance) != 0 {
|
||||
s.stateAccessList.BalanceChange(s.blockAccessIndex, addr, balance)
|
||||
}
|
||||
nonce := obj.Nonce()
|
||||
if state.nonceSet && nonce != state.nonce {
|
||||
s.stateAccessList.NonceChange(addr, s.blockAccessIndex, nonce)
|
||||
}
|
||||
if state.codeSet {
|
||||
if code := obj.Code(); !bytes.Equal(code, state.code) {
|
||||
s.stateAccessList.CodeChange(addr, s.blockAccessIndex, code)
|
||||
}
|
||||
}
|
||||
}
|
||||
obj.finalise()
|
||||
s.markUpdate(addr)
|
||||
}
|
||||
addressesToPrefetch = append(addressesToPrefetch, addr) // Copy needed for closure
|
||||
}
|
||||
if s.prefetcher != nil && len(addressesToPrefetch) > 0 {
|
||||
if err := s.prefetcher.prefetch(common.Hash{}, s.originalRoot, common.Address{}, addressesToPrefetch, nil, false); err != nil {
|
||||
log.Error("Failed to prefetch addresses", "addresses", len(addressesToPrefetch), "err", err)
|
||||
}
|
||||
}
|
||||
// Invalidate journal because reverting across transactions is not allowed.
|
||||
s.clearJournalAndRefund()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *StateDB) recordAccessListChanges(addr common.Address, state *journalMutationState) {
|
||||
var (
|
||||
balance = uint256.NewInt(0)
|
||||
nonce uint64
|
||||
)
|
||||
obj := s.stateObjects[addr] // nil when the account was removed
|
||||
if obj != nil {
|
||||
balance, nonce = obj.Balance(), obj.Nonce()
|
||||
}
|
||||
if state.balanceSet && balance.Cmp(state.balance) != 0 {
|
||||
s.stateAccessList.BalanceChange(s.blockAccessIndex, addr, balance)
|
||||
}
|
||||
if state.nonceSet && nonce != state.nonce {
|
||||
s.stateAccessList.NonceChange(addr, s.blockAccessIndex, nonce)
|
||||
}
|
||||
if state.codeSet {
|
||||
var code []byte
|
||||
if obj != nil {
|
||||
code = obj.Code()
|
||||
}
|
||||
if !bytes.Equal(code, state.code) {
|
||||
s.stateAccessList.CodeChange(addr, s.blockAccessIndex, code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// finaliseAmsterdam is the Amsterdam-and-later variant of Finalise.
|
||||
func (s *StateDB) finaliseAmsterdam(deleteEmptyObjects bool) *bal.ConstructionBlockAccessList {
|
||||
addressesToPrefetch := make([]common.Address, 0, len(s.journal.mutations))
|
||||
for addr, state := range s.journal.mutations {
|
||||
obj, exist := s.stateObjects[addr]
|
||||
if !exist {
|
||||
// RIPEMD160 (0x03) gets an extra dirty marker for a historical
|
||||
// mainnet consensus exception (at block 1714175, in tx
|
||||
// 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2)
|
||||
// around empty-account touch/revert handling.
|
||||
//
|
||||
// That marker survives journal revert, so the account may remain in
|
||||
// s.journal.mutations even though its state object was rolled
|
||||
// back and no longer exists. In that case there is nothing to
|
||||
// finalise or delete, so ignore it here.
|
||||
continue
|
||||
}
|
||||
switch {
|
||||
case obj.selfDestructed:
|
||||
// EIP-8264: accounts marked for self-destruction, instead of
|
||||
// being deleted, are modified as follows:
|
||||
// - nonce is reset to 0,
|
||||
// - balance is unchanged,
|
||||
// - code is cleared,
|
||||
// - all storage is cleared
|
||||
if !obj.Balance().IsZero() {
|
||||
o := newObject(s, obj.address, obj.origin)
|
||||
o.setBalance(new(uint256.Int).Set(obj.Balance()))
|
||||
s.setStateObject(o)
|
||||
s.markUpdate(addr)
|
||||
} else {
|
||||
delete(s.stateObjects, obj.address)
|
||||
s.markDelete(addr)
|
||||
if _, ok := s.stateObjectsDestruct[obj.address]; !ok {
|
||||
s.stateObjectsDestruct[obj.address] = obj
|
||||
}
|
||||
}
|
||||
|
||||
case deleteEmptyObjects && obj.empty():
|
||||
// EIP-161: a touched, empty account is removed.
|
||||
delete(s.stateObjects, obj.address)
|
||||
s.markDelete(addr)
|
||||
if _, ok := s.stateObjectsDestruct[obj.address]; !ok {
|
||||
s.stateObjectsDestruct[obj.address] = obj
|
||||
}
|
||||
|
||||
default:
|
||||
obj.finalise()
|
||||
s.markUpdate(addr)
|
||||
}
|
||||
// Aggregate the resulting account metadata change
|
||||
// into the block-level access list.
|
||||
s.recordAccessListChanges(addr, state)
|
||||
|
||||
// At this point, also ship the address off to the precacher. The precacher
|
||||
// will start loading tries, and when the change is eventually committed,
|
||||
// the commit-phase will be a lot faster
|
||||
|
|
|
|||
|
|
@ -230,10 +230,6 @@ func (s *hookedStateDB) AddLog(log *types.Log) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) LogsForBurnAccounts() []*types.Log {
|
||||
return s.inner.LogsForBurnAccounts()
|
||||
}
|
||||
|
||||
func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlockAccessList {
|
||||
if s.hooks.OnBalanceChange == nil && s.hooks.OnNonceChangeV2 == nil && s.hooks.OnNonceChange == nil && s.hooks.OnCodeChangeV2 == nil && s.hooks.OnCodeChange == nil {
|
||||
// Short circuit if no relevant hooks are set.
|
||||
|
|
@ -256,18 +252,24 @@ func (s *hookedStateDB) Finalise(deleteEmptyObjects bool) *bal.ConstructionBlock
|
|||
return bytes.Compare(selfDestructedAddrs[i][:], selfDestructedAddrs[j][:]) < 0
|
||||
})
|
||||
|
||||
// EIP-8246 (Amsterdam) removes the SELFDESTRUCT burn: a self-destructed
|
||||
// account that retains a non-zero balance is preserved as a balance-only
|
||||
// account rather than removed, so its balance is no longer burnt.
|
||||
burnsBalance := s.inner.stateAccessList == nil
|
||||
|
||||
for _, addr := range selfDestructedAddrs {
|
||||
obj := s.inner.stateObjects[addr]
|
||||
// Bingo: state object was self-destructed, call relevant hooks.
|
||||
|
||||
// If ether was sent to account post-selfdestruct, record as burnt.
|
||||
if s.hooks.OnBalanceChange != nil {
|
||||
if burnsBalance && s.hooks.OnBalanceChange != nil {
|
||||
if bal := obj.Balance(); bal.Sign() != 0 {
|
||||
s.hooks.OnBalanceChange(addr, bal.ToBig(), new(big.Int), tracing.BalanceDecreaseSelfdestructBurn)
|
||||
}
|
||||
}
|
||||
|
||||
// Nonce is set to reset on self-destruct.
|
||||
//
|
||||
// TODO(rjl) shall we emit the nonce change if the pre-tx nonce was zero?
|
||||
if s.hooks.OnNonceChangeV2 != nil {
|
||||
s.hooks.OnNonceChangeV2(addr, obj.Nonce(), 0, tracing.NonceChangeSelfdestruct)
|
||||
} else if s.hooks.OnNonceChange != nil {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/core/state/snapshot"
|
||||
"github.com/ethereum/go-ethereum/core/stateless"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
|
|
@ -998,6 +999,45 @@ func TestDeleteCreateRevert(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestWitnessIncludesAbsentAccountReads(t *testing.T) {
|
||||
db := NewDatabaseForTesting()
|
||||
state, _ := New(types.EmptyRootHash, db)
|
||||
for i := byte(0); i < 3; i++ {
|
||||
addr := common.Address{i + 1}
|
||||
state.SetBalance(addr, uint256.NewInt(uint64(i+1)), tracing.BalanceChangeUnspecified)
|
||||
}
|
||||
root, err := state.Commit(0, false, false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to commit initial state: %v", err)
|
||||
}
|
||||
state, err = New(root, db)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to reopen state: %v", err)
|
||||
}
|
||||
|
||||
witness := &stateless.Witness{
|
||||
Codes: make(map[string]struct{}),
|
||||
State: make(map[string]struct{}),
|
||||
}
|
||||
state.StartPrefetcher("test", witness)
|
||||
missing := common.HexToAddress("0x017655eac00c837122cabbbc0dd604a196906648")
|
||||
if balance := state.GetBalance(missing); balance.Sign() != 0 {
|
||||
t.Fatalf("unexpected balance for absent account: %v", balance)
|
||||
}
|
||||
if err := state.Error(); err != nil {
|
||||
t.Fatalf("unexpected state error after read: %v", err)
|
||||
}
|
||||
if got := state.IntermediateRoot(false); got != root {
|
||||
t.Fatalf("unexpected root after read-only access: have %x want %x", got, root)
|
||||
}
|
||||
if err := state.Error(); err != nil {
|
||||
t.Fatalf("unexpected state error after root calculation: %v", err)
|
||||
}
|
||||
if len(witness.State) == 0 {
|
||||
t.Fatal("missing witness nodes for absent account read")
|
||||
}
|
||||
}
|
||||
|
||||
// TestMissingTrieNodes tests that if the StateDB fails to load parts of the trie,
|
||||
// the Commit operation fails with an error
|
||||
// If we are missing trie nodes, we should not continue writing to the trie
|
||||
|
|
|
|||
|
|
@ -81,6 +81,10 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
|
|||
if config.DAOForkSupport && config.DAOForkBlock != nil && config.DAOForkBlock.Cmp(block.Number()) == 0 {
|
||||
misc.ApplyDAOHardFork(tracingStateDB)
|
||||
}
|
||||
parent := p.chain.GetHeader(block.ParentHash(), block.NumberU64()-1)
|
||||
if parent == nil {
|
||||
return nil, fmt.Errorf("missing parent %#x", block.ParentHash())
|
||||
}
|
||||
var (
|
||||
context = NewEVMBlockContext(header, p.chain, nil)
|
||||
signer = types.MakeSigner(config, header.Number, header.Time)
|
||||
|
|
@ -88,12 +92,12 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
|
|||
blockAccessList = bal.NewConstructionBlockAccessList()
|
||||
)
|
||||
defer evm.Release()
|
||||
|
||||
if jumpDestCache != nil {
|
||||
evm.SetJumpDestCache(jumpDestCache)
|
||||
}
|
||||
|
||||
// Run the pre-execution system calls
|
||||
blockAccessList.Merge(PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), config, evm, block.Number(), block.Time()))
|
||||
blockAccessList.Merge(PreExecution(ctx, block.BeaconRoot(), parent, config, evm, block.Number(), block.Time()))
|
||||
|
||||
// Iterate over and process the individual transactions
|
||||
for i, tx := range block.Transactions() {
|
||||
|
|
@ -137,14 +141,20 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
|
|||
}, nil
|
||||
}
|
||||
|
||||
// PreExecution processes pre-execution system calls.
|
||||
func PreExecution(ctx context.Context, beaconRoot *common.Hash, parent common.Hash, config *params.ChainConfig, evm *vm.EVM, number *big.Int, time uint64) *bal.ConstructionBlockAccessList {
|
||||
// PreExecution processes pre-execution state changes and system calls.
|
||||
func PreExecution(ctx context.Context, beaconRoot *common.Hash, parent *types.Header, config *params.ChainConfig, evm *vm.EVM, number *big.Int, time uint64) *bal.ConstructionBlockAccessList {
|
||||
_, _, spanEnd := telemetry.StartSpan(ctx, "core.preExecution")
|
||||
defer spanEnd(nil)
|
||||
|
||||
var blockAccessList *bal.ConstructionBlockAccessList
|
||||
if config.IsAmsterdam(number, time) {
|
||||
blockAccessList = bal.NewConstructionBlockAccessList()
|
||||
|
||||
// EIP-7997: insert the deterministic deployment factory at the Amsterdam
|
||||
// activation block via an irregular state transition.
|
||||
if !config.IsAmsterdam(parent.Number, parent.Time) {
|
||||
misc.ApplyEIP7997(evm.StateDB)
|
||||
}
|
||||
}
|
||||
// EIP-4788
|
||||
if beaconRoot != nil {
|
||||
|
|
@ -152,7 +162,7 @@ func PreExecution(ctx context.Context, beaconRoot *common.Hash, parent common.Ha
|
|||
}
|
||||
// EIP-2935
|
||||
if config.IsPrague(number, time) || config.IsUBT(number, time) {
|
||||
ProcessParentBlockHash(parent, evm, blockAccessList)
|
||||
ProcessParentBlockHash(parent.Hash(), evm, blockAccessList)
|
||||
}
|
||||
return blockAccessList
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,23 +68,26 @@ func (result *ExecutionResult) Revert() []byte {
|
|||
}
|
||||
|
||||
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
|
||||
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation bool, rules params.Rules, costPerStateByte uint64) (vm.GasCosts, error) {
|
||||
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, from common.Address, to *common.Address, value *uint256.Int, rules params.Rules, costPerStateByte uint64) (vm.GasCosts, error) {
|
||||
isContractCreation := to == nil
|
||||
|
||||
// Set the starting gas for the raw transaction
|
||||
var gas vm.GasCosts
|
||||
if isContractCreation && rules.IsHomestead {
|
||||
if rules.IsAmsterdam {
|
||||
gas.RegularGas = params.TxGas + params.CreateGasAmsterdam
|
||||
if rules.IsAmsterdam {
|
||||
gas.RegularGas = intrinsicBaseGasEIP2780(from, to, value)
|
||||
if isContractCreation {
|
||||
// New-account creation is charged as state gas (EIP-8037).
|
||||
gas.StateGas = params.AccountCreationSize * costPerStateByte
|
||||
} else {
|
||||
gas.RegularGas = params.TxGasContractCreation
|
||||
}
|
||||
} else if isContractCreation && rules.IsHomestead {
|
||||
gas.RegularGas = params.TxGasContractCreation
|
||||
} else {
|
||||
gas.RegularGas = params.TxGas
|
||||
}
|
||||
// Add gas for authorizations
|
||||
if authList != nil {
|
||||
if rules.IsAmsterdam {
|
||||
gas.RegularGas += uint64(len(authList)) * params.TxAuthTupleRegularGas
|
||||
gas.RegularGas += uint64(len(authList)) * (params.AccountWriteAmsterdam + params.RegularPerAuthBaseCost)
|
||||
gas.StateGas += uint64(len(authList)) * (params.AuthorizationCreationSize + params.AccountCreationSize) * costPerStateByte
|
||||
} else {
|
||||
gas.RegularGas += uint64(len(authList)) * params.CallNewAccountGas
|
||||
|
|
@ -123,14 +126,22 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
|||
if accessList != nil {
|
||||
addresses := uint64(len(accessList))
|
||||
storageKeys := uint64(accessList.StorageKeys())
|
||||
if (math.MaxUint64-gas.RegularGas)/params.TxAccessListAddressGas < addresses {
|
||||
|
||||
// Amsterdam re-prices the per-entry access-list cost
|
||||
addressCost := params.TxAccessListAddressGas
|
||||
storageKeyCost := params.TxAccessListStorageKeyGas
|
||||
if rules.IsAmsterdam {
|
||||
addressCost = params.TxAccessListAddressGasAmsterdam
|
||||
storageKeyCost = params.TxAccessListStorageKeyGasAmsterdam
|
||||
}
|
||||
if (math.MaxUint64-gas.RegularGas)/addressCost < addresses {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas.RegularGas += addresses * params.TxAccessListAddressGas
|
||||
if (math.MaxUint64-gas.RegularGas)/params.TxAccessListStorageKeyGas < storageKeys {
|
||||
gas.RegularGas += addresses * addressCost
|
||||
if (math.MaxUint64-gas.RegularGas)/storageKeyCost < storageKeys {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas.RegularGas += storageKeys * params.TxAccessListStorageKeyGas
|
||||
gas.RegularGas += storageKeys * storageKeyCost
|
||||
|
||||
// EIP-7981: access list data is charged in addition to the base charge.
|
||||
if rules.IsAmsterdam {
|
||||
|
|
@ -151,8 +162,41 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
|||
return gas, nil
|
||||
}
|
||||
|
||||
// intrinsicBaseGasEIP2780 computes the regular-gas portion of the EIP-2780
|
||||
// intrinsic base cost: the per-resource decomposition of the legacy flat 21,000.
|
||||
func intrinsicBaseGasEIP2780(from common.Address, to *common.Address, value *uint256.Int) uint64 {
|
||||
var (
|
||||
isContractCreation = to == nil
|
||||
isSelfTransfer = to != nil && *to == from
|
||||
hasValue = value != nil && !value.IsZero()
|
||||
)
|
||||
// tx.sender: signature recovery plus the sender account access and write.
|
||||
gas := params.TxBaseCost2780
|
||||
|
||||
// tx.to charge.
|
||||
switch {
|
||||
case isSelfTransfer:
|
||||
// The recipient account is already accessed and written as the sender.
|
||||
case isContractCreation:
|
||||
gas += params.CreateAccess2780
|
||||
default:
|
||||
gas += params.ColdAccountAccess2780
|
||||
}
|
||||
|
||||
// tx.value charge.
|
||||
switch {
|
||||
case !hasValue || isSelfTransfer:
|
||||
// No transfer log and no recipient balance write.
|
||||
case isContractCreation:
|
||||
gas += params.TransferLogCost2780
|
||||
default:
|
||||
gas += params.TransferLogCost2780 + params.TxValueCost2780
|
||||
}
|
||||
return gas
|
||||
}
|
||||
|
||||
// FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623).
|
||||
func FloorDataGas(rules params.Rules, data []byte, accessList types.AccessList) (uint64, error) {
|
||||
func FloorDataGas(rules params.Rules, from common.Address, to *common.Address, value *uint256.Int, data []byte, accessList types.AccessList) (uint64, error) {
|
||||
var (
|
||||
tokens uint64
|
||||
tokenCost uint64
|
||||
|
|
@ -198,12 +242,17 @@ func FloorDataGas(rules params.Rules, data []byte, accessList types.AccessList)
|
|||
tokenCost = params.TxCostFloorPerToken
|
||||
}
|
||||
|
||||
// The floor is anchored to the transaction base cost.
|
||||
floorBase := params.TxGas
|
||||
if rules.IsAmsterdam {
|
||||
floorBase = params.TxBaseCost2780
|
||||
}
|
||||
// Check for overflow
|
||||
if (math.MaxUint64-params.TxGas)/tokenCost < tokens {
|
||||
if (math.MaxUint64-floorBase)/tokenCost < tokens {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
// Minimum gas required for a transaction based on its data tokens (EIP-7623).
|
||||
return params.TxGas + tokens*tokenCost, nil
|
||||
return floorBase + tokens*tokenCost, nil
|
||||
}
|
||||
|
||||
// toWordSize returns the ceiled word size required for init code payment calculation.
|
||||
|
|
@ -614,7 +663,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
contractCreation = msg.To == nil
|
||||
floorDataGas uint64
|
||||
)
|
||||
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules, st.evm.Context.CostPerStateByte)
|
||||
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, msg.From, msg.To, msg.Value, rules, st.evm.Context.CostPerStateByte)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -629,7 +678,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
// Validate the EIP-7623 calldata floor against the gas limit. The floor inflates
|
||||
// the total gas usage at tx end, so the gas limit must be sufficient to cover that.
|
||||
if rules.IsPrague {
|
||||
floorDataGas, err = FloorDataGas(rules, msg.Data, msg.AccessList)
|
||||
floorDataGas, err = FloorDataGas(rules, msg.From, msg.To, msg.Value, msg.Data, msg.AccessList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -688,7 +737,7 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
// If the contract creation failed, or the destination was pre-existing,
|
||||
// refund the account-creation state gas pre-charged in IntrinsicGas.
|
||||
if rules.IsAmsterdam && !creation {
|
||||
st.gasRemaining.RefundState(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
|
||||
st.gasRemaining.RefundStateToReservoir(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
|
||||
}
|
||||
} else {
|
||||
// Increment the nonce for the next transaction.
|
||||
|
|
@ -704,10 +753,21 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
// performing the resolution and warming.
|
||||
if addr, ok := types.ParseDelegation(st.state.GetCode(*msg.To)); ok {
|
||||
st.state.AddAddressToAccessList(addr)
|
||||
// Record in BAL
|
||||
if rules.IsAmsterdam {
|
||||
st.state.GetCode(addr)
|
||||
}
|
||||
}
|
||||
// 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) {
|
||||
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)
|
||||
}
|
||||
|
||||
// Settle down the gas usage and refund the ETH back if any remaining
|
||||
|
|
@ -740,12 +800,6 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// EIP-7708: Emit the ETH-burn logs
|
||||
if rules.IsAmsterdam {
|
||||
for _, log := range st.evm.StateDB.LogsForBurnAccounts() {
|
||||
st.evm.StateDB.AddLog(log)
|
||||
}
|
||||
}
|
||||
return &ExecutionResult{
|
||||
UsedGas: gasUsed,
|
||||
MaxUsedGas: peakUsed,
|
||||
|
|
@ -754,6 +808,47 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
// chargeCallRecipientEIP2780 applies the EIP-2780 transaction top-level gas costs for
|
||||
// a message-call transaction, charged before any opcode executes:
|
||||
//
|
||||
// - if the recipient is EIP-161 non-existent and the transaction carries value,
|
||||
// charge for account creation.
|
||||
//
|
||||
// - if the recipient is an EIP-7702 delegated account, resolving the delegation
|
||||
// loads the target's code, charged an additional cold account access in
|
||||
// regular gas.
|
||||
func (st *stateTransition) chargeCallRecipientEIP2780(value *uint256.Int) bool {
|
||||
var (
|
||||
cost vm.GasCosts
|
||||
to = *st.msg.To
|
||||
)
|
||||
// This runs in the topmost frame before any bytecode executes, so unlike the
|
||||
// execution-level checks which must use StateDB.Empty because SELFDESTRUCT can
|
||||
// leave a transient EIP-161-empty account, no empty account can exist here, and
|
||||
// !Exist is equivalent to Empty.
|
||||
if !value.IsZero() && !st.state.Exist(to) {
|
||||
cost.StateGas += params.AccountCreationSize * st.evm.Context.CostPerStateByte
|
||||
}
|
||||
if _, ok := types.ParseDelegation(st.state.GetCode(to)); ok {
|
||||
// EIP-2780: The tx.sender, tx.to, and (where applicable) delegation-target
|
||||
// charges above are always at the cold rate.
|
||||
//
|
||||
// The delegation-target is already warmed before, no double warming here.
|
||||
cost.RegularGas += params.ColdAccountAccess2780
|
||||
}
|
||||
if cost == (vm.GasCosts{}) {
|
||||
return true
|
||||
}
|
||||
prior, ok := st.gasRemaining.Charge(cost)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if st.evm.Config.Tracer.HasGasHook() {
|
||||
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// settleGas finalizes the per-tx gas accounting after EVM execution:
|
||||
//
|
||||
// - Snapshots the EIP-8037 block-level 2D figures (tx_regular_gas,
|
||||
|
|
@ -870,20 +965,13 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio
|
|||
}
|
||||
|
||||
// applyAuthorization applies an EIP-7702 code delegation to the state and,
|
||||
// under EIP-8037, reconciles the per-authorization intrinsic state-gas pre-
|
||||
// charge so that, per authority:
|
||||
//
|
||||
// - the account portion (AccountCreationSize × CPSB) is charged at most
|
||||
// once, and only when the account did not exist before the tx
|
||||
//
|
||||
// - the delegation-indicator portion (AuthorizationCreationSize × CPSB) is
|
||||
// charged at most once, and only when the authority ends the tx delegated
|
||||
// having started it undelegated.
|
||||
// adjust the pre-charged intrinsic cost accordingly.
|
||||
func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.SetCodeAuthorization, delegates map[common.Address]bool) error {
|
||||
authority, err := st.validateAuthorization(auth)
|
||||
if err != nil {
|
||||
if rules.IsAmsterdam {
|
||||
st.gasRemaining.RefundState((params.AccountCreationSize + params.AuthorizationCreationSize) * st.evm.Context.CostPerStateByte)
|
||||
st.gasRemaining.RefundStateToReservoir((params.AccountCreationSize + params.AuthorizationCreationSize) * st.evm.Context.CostPerStateByte)
|
||||
st.state.AddRefund(params.AccountWriteAmsterdam)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -895,7 +983,8 @@ func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.Se
|
|||
}
|
||||
} else {
|
||||
if st.state.Exist(authority) {
|
||||
st.gasRemaining.RefundState(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
|
||||
st.gasRemaining.RefundStateToReservoir(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
|
||||
st.state.AddRefund(params.AccountWriteAmsterdam)
|
||||
}
|
||||
authBase := params.AuthorizationCreationSize * st.evm.Context.CostPerStateByte
|
||||
|
||||
|
|
@ -906,17 +995,17 @@ func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.Se
|
|||
}
|
||||
if auth.Address == (common.Address{}) {
|
||||
// Clearing writes no indicator, refill this auth's state charge.
|
||||
st.gasRemaining.RefundState(authBase)
|
||||
st.gasRemaining.RefundStateToReservoir(authBase)
|
||||
|
||||
// The indicator was created by an earlier auth within the same
|
||||
// transaction, refill the state charge as it's no longer justified.
|
||||
if curDelegated && !preDelegated {
|
||||
st.gasRemaining.RefundState(authBase)
|
||||
st.gasRemaining.RefundStateToReservoir(authBase)
|
||||
}
|
||||
} else if curDelegated || preDelegated {
|
||||
// The 23-byte slot is already occupied, overwriting it writes no
|
||||
// new bytes, refill the state charge.
|
||||
st.gasRemaining.RefundState(authBase)
|
||||
st.gasRemaining.RefundStateToReservoir(authBase)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
func TestFloorDataGas(t *testing.T) {
|
||||
|
|
@ -73,21 +74,22 @@ func TestFloorDataGas(t *testing.T) {
|
|||
{
|
||||
name: "amsterdam/empty",
|
||||
amsterdam: true,
|
||||
want: params.TxGas,
|
||||
// EIP-2780 anchors the floor to the reduced base cost.
|
||||
want: params.TxBaseCost2780,
|
||||
},
|
||||
{
|
||||
name: "amsterdam/data-only",
|
||||
amsterdam: true,
|
||||
data: bytes.Repeat([]byte{0x00}, 1024),
|
||||
// post-amsterdam: every byte = 4 tokens regardless of value
|
||||
want: params.TxGas + 1024*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976,
|
||||
want: params.TxBaseCost2780 + 1024*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976,
|
||||
},
|
||||
{
|
||||
name: "amsterdam/data-non-zero",
|
||||
amsterdam: true,
|
||||
data: bytes.Repeat([]byte{0xff}, 1024),
|
||||
// same as zero data post-amsterdam
|
||||
want: params.TxGas + 1024*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976,
|
||||
want: params.TxBaseCost2780 + 1024*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976,
|
||||
},
|
||||
{
|
||||
name: "amsterdam/access-list-addresses-only",
|
||||
|
|
@ -97,7 +99,7 @@ func TestFloorDataGas(t *testing.T) {
|
|||
{Address: addr2},
|
||||
},
|
||||
// 2 * 20 bytes * 4 tokens/byte * 16 cost/token
|
||||
want: params.TxGas + 2*common.AddressLength*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976,
|
||||
want: params.TxBaseCost2780 + 2*common.AddressLength*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976,
|
||||
},
|
||||
{
|
||||
name: "amsterdam/access-list-with-storage-keys",
|
||||
|
|
@ -106,7 +108,7 @@ func TestFloorDataGas(t *testing.T) {
|
|||
{Address: addr1, StorageKeys: []common.Hash{key1, key2}},
|
||||
},
|
||||
// 1 addr * 20 * 4 + 2 keys * 32 * 4 = 80 + 256 = 336 tokens * 16
|
||||
want: params.TxGas + (1*common.AddressLength+2*common.HashLength)*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976,
|
||||
want: params.TxBaseCost2780 + (1*common.AddressLength+2*common.HashLength)*params.TxTokenPerNonZeroByte*params.TxCostFloorPerToken7976,
|
||||
},
|
||||
{
|
||||
name: "amsterdam/mixed",
|
||||
|
|
@ -117,13 +119,13 @@ func TestFloorDataGas(t *testing.T) {
|
|||
{Address: addr2, StorageKeys: []common.Hash{key1, key2}},
|
||||
},
|
||||
// data: 100*4 = 400; addrs: 2*20*4 = 160; keys: 3*32*4 = 384; total = 944 * 16
|
||||
want: params.TxGas + (100*params.TxTokenPerNonZeroByte+2*common.AddressLength*params.TxTokenPerNonZeroByte+3*common.HashLength*params.TxTokenPerNonZeroByte)*params.TxCostFloorPerToken7976,
|
||||
want: params.TxBaseCost2780 + (100*params.TxTokenPerNonZeroByte+2*common.AddressLength*params.TxTokenPerNonZeroByte+3*common.HashLength*params.TxTokenPerNonZeroByte)*params.TxCostFloorPerToken7976,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rules := params.Rules{IsAmsterdam: tt.amsterdam}
|
||||
got, err := FloorDataGas(rules, tt.data, tt.accessList)
|
||||
got, err := FloorDataGas(rules, addr1, &addr1, new(uint256.Int), tt.data, tt.accessList)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -155,6 +157,7 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
isEIP2028 bool
|
||||
isEIP3860 bool
|
||||
isAmsterdam bool
|
||||
value *uint256.Int
|
||||
want vm.GasCosts
|
||||
}{
|
||||
{
|
||||
|
|
@ -237,9 +240,10 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
},
|
||||
isEIP2028: true,
|
||||
isAmsterdam: true,
|
||||
// base access-list charge + EIP-7981 extra
|
||||
want: vm.GasCosts{RegularGas: params.TxGas +
|
||||
2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas +
|
||||
// EIP-2780: zero-value call base is TxBaseCost + ColdAccountAccess
|
||||
// (15,000). Plus base access-list charge + EIP-7981 extra.
|
||||
want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
|
||||
2*params.TxAccessListAddressGasAmsterdam + 3*params.TxAccessListStorageKeyGasAmsterdam +
|
||||
2*amsterdamAddressCost + 3*amsterdamStorageKeyCost},
|
||||
},
|
||||
{
|
||||
|
|
@ -259,10 +263,10 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
isHomestead: true,
|
||||
isEIP2028: true,
|
||||
isAmsterdam: true,
|
||||
// EIP-8037: creation regular gas is TxGas + CreateGasAmsterdam (not TxGasContractCreation),
|
||||
// and account-creation cost is moved to state gas.
|
||||
// EIP-2780: creation regular gas is TxBaseCost + CreateAccess (23,000),
|
||||
// and account-creation cost is charged as state gas.
|
||||
want: vm.GasCosts{
|
||||
RegularGas: params.TxGas + params.CreateGasAmsterdam,
|
||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780,
|
||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
||||
},
|
||||
},
|
||||
|
|
@ -275,7 +279,7 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
isEIP3860: true, // Shanghai gates init-code word gas
|
||||
isAmsterdam: true,
|
||||
want: vm.GasCosts{
|
||||
RegularGas: params.TxGas + params.CreateGasAmsterdam +
|
||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 +
|
||||
64*params.TxDataZeroGas + 2*params.InitCodeWordGas,
|
||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
||||
},
|
||||
|
|
@ -292,9 +296,9 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
isEIP3860: true,
|
||||
isAmsterdam: true,
|
||||
want: vm.GasCosts{
|
||||
RegularGas: params.TxGas + params.CreateGasAmsterdam +
|
||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 +
|
||||
32*params.TxDataNonZeroGasEIP2028 + 1*params.InitCodeWordGas +
|
||||
1*params.TxAccessListAddressGas + 1*params.TxAccessListStorageKeyGas +
|
||||
1*params.TxAccessListAddressGasAmsterdam + 1*params.TxAccessListStorageKeyGasAmsterdam +
|
||||
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost,
|
||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
||||
},
|
||||
|
|
@ -310,18 +314,41 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
},
|
||||
isEIP2028: true,
|
||||
isAmsterdam: true,
|
||||
// EIP-8037 splits the auth-tuple charge into regular + state gas:
|
||||
// regular: TxAuthTupleRegularGas (7500) per auth
|
||||
// EIP-8037 splits the auth-tuple charge into regular + state gas, with
|
||||
// the values finalized by EIP-8038:
|
||||
// regular: ACCOUNT_WRITE (8,000) + REGULAR_PER_AUTH_BASE_COST (7,500) per auth
|
||||
// state: (AuthorizationCreationSize + AccountCreationSize) * CostPerStateByte per auth
|
||||
want: vm.GasCosts{
|
||||
RegularGas: params.TxGas +
|
||||
RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
|
||||
100*params.TxDataNonZeroGasEIP2028 +
|
||||
1*params.TxAccessListAddressGas + 1*params.TxAccessListStorageKeyGas +
|
||||
1*params.TxAccessListAddressGasAmsterdam + 1*params.TxAccessListStorageKeyGasAmsterdam +
|
||||
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost +
|
||||
1*params.TxAuthTupleRegularGas,
|
||||
1*(params.AccountWriteAmsterdam+params.RegularPerAuthBaseCost),
|
||||
StateGas: 1 * (params.AuthorizationCreationSize + params.AccountCreationSize) * params.CostPerStateByte,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "amsterdam/value-transfer-call",
|
||||
isEIP2028: true,
|
||||
isAmsterdam: true,
|
||||
value: uint256.NewInt(1),
|
||||
// EIP-2780: TxBaseCost + ColdAccountAccess + TransferLogCost + TxValueCost = 21,000.
|
||||
want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
|
||||
params.TransferLogCost2780 + params.TxValueCost2780},
|
||||
},
|
||||
{
|
||||
name: "amsterdam/value-bearing-contract-creation",
|
||||
creation: true,
|
||||
isHomestead: true,
|
||||
isEIP2028: true,
|
||||
isAmsterdam: true,
|
||||
value: uint256.NewInt(1),
|
||||
// EIP-2780: TxBaseCost + CreateAccess + TransferLogCost = 24,756, plus account-creation state gas.
|
||||
want: vm.GasCosts{
|
||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 + params.TransferLogCost2780,
|
||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
@ -331,8 +358,12 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
IsShanghai: tt.isEIP3860,
|
||||
IsAmsterdam: tt.isAmsterdam,
|
||||
}
|
||||
var to *common.Address
|
||||
if !tt.creation {
|
||||
to = &addr1
|
||||
}
|
||||
got, err := IntrinsicGas(tt.data, tt.accessList, tt.authList,
|
||||
tt.creation, rules, params.CostPerStateByte)
|
||||
common.Address{}, to, tt.value, rules, params.CostPerStateByte)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1235,11 +1235,6 @@ func TestBillyMigration(t *testing.T) {
|
|||
Max: maxBlobs,
|
||||
UpdateFraction: params.DefaultCancunBlobConfig.UpdateFraction,
|
||||
},
|
||||
Osaka: ¶ms.BlobConfig{
|
||||
Target: maxBlobs / 2,
|
||||
Max: maxBlobs,
|
||||
UpdateFraction: params.DefaultCancunBlobConfig.UpdateFraction,
|
||||
},
|
||||
},
|
||||
}
|
||||
chain := &testBlockChain{
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ func newTestCache(t *testing.T, txConfig []txSpec) *testCache {
|
|||
CancunTime: &cancunTime,
|
||||
OsakaTime: &cancunTime,
|
||||
BlobScheduleConfig: ¶ms.BlobScheduleConfig{
|
||||
Osaka: ¶ms.BlobConfig{
|
||||
Cancun: ¶ms.BlobConfig{
|
||||
Target: 1,
|
||||
Max: 1,
|
||||
UpdateFraction: params.DefaultCancunBlobConfig.UpdateFraction,
|
||||
|
|
|
|||
|
|
@ -107,7 +107,8 @@ func (l *limbo) Close() error {
|
|||
func (l *limbo) parseBlob(id uint64, data []byte) error {
|
||||
item := new(limboBlob)
|
||||
if err := rlp.DecodeBytes(data, item); err != nil {
|
||||
if isLegacyLimboBlob(data) {
|
||||
// This entry may have been stored with the legacy limboBlob type
|
||||
if isLegacy(data) {
|
||||
return errLegacyTx
|
||||
}
|
||||
// This path is impossible unless the disk data representation changes
|
||||
|
|
@ -133,9 +134,9 @@ func (l *limbo) parseBlob(id uint64, data []byte) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// isLegacyLimboBlob returns true if the data is encoded in legacy limboBlob type.
|
||||
// It checks whether the first byte of third element is blobTxType.
|
||||
func isLegacyLimboBlob(data []byte) bool {
|
||||
// isLegacy reports whether data is a limbo entry with the legacy limboBlob
|
||||
// {TxHash, Block, Tx *types.Transaction} type.
|
||||
func isLegacy(data []byte) bool {
|
||||
elems, err := rlp.SplitListValues(data)
|
||||
if err != nil || len(elems) < 3 {
|
||||
return false
|
||||
|
|
|
|||
69
core/txpool/blobpool/limbo_test.go
Normal file
69
core/txpool/blobpool/limbo_test.go
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
// Copyright 2025 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package blobpool
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
"github.com/holiman/billy"
|
||||
)
|
||||
|
||||
// TestLimboLegacyMigration checks that a limbo entry in the legacy limboBlob
|
||||
// layout is flagged for conversion by newLimbo instead of being dropped, so the
|
||||
// conversion queue can migrate it to the blobTxForPool layout.
|
||||
func TestLimboLegacyMigration(t *testing.T) {
|
||||
key, _ := crypto.GenerateKey()
|
||||
tx := makeMultiBlobTx(0, 10, 100, 100, 2, 0, key)
|
||||
|
||||
dir := t.TempDir()
|
||||
|
||||
// Write a single entry using the legacy on-disk layout.
|
||||
store, err := billy.Open(billy.Options{Path: dir}, newSlotterEIP7594(params.BlobTxMaxBlobs), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open store: %v", err)
|
||||
}
|
||||
legacy := struct {
|
||||
TxHash common.Hash
|
||||
Block uint64
|
||||
Tx *types.Transaction
|
||||
}{tx.Hash(), 42, tx}
|
||||
data, err := rlp.EncodeToBytes(&legacy)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to encode legacy entry: %v", err)
|
||||
}
|
||||
id, err := store.Put(data)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to store legacy entry: %v", err)
|
||||
}
|
||||
store.Close()
|
||||
|
||||
// Open the limbo, which should flag the legacy entry for conversion.
|
||||
l, convert, err := newLimbo(new(params.ChainConfig), dir)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to open limbo: %v", err)
|
||||
}
|
||||
defer l.Close()
|
||||
|
||||
if len(convert) != 1 || convert[0] != id {
|
||||
t.Fatalf("legacy entry not flagged for conversion: got %v, want [%d]", convert, id)
|
||||
}
|
||||
}
|
||||
|
|
@ -119,10 +119,11 @@ func (journal *journal) load(add func([]*types.Transaction) []error) error {
|
|||
|
||||
func (journal *journal) setupWriter() error {
|
||||
if journal.writer != nil {
|
||||
if err := journal.writer.Close(); err != nil {
|
||||
err := journal.writer.Close()
|
||||
journal.writer = nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
journal.writer = nil
|
||||
}
|
||||
|
||||
// Re-open the journal file for appending
|
||||
|
|
@ -152,10 +153,11 @@ func (journal *journal) insert(tx *types.Transaction) error {
|
|||
func (journal *journal) rotate(all map[common.Address]types.Transactions) error {
|
||||
// Close the current journal (if any is open)
|
||||
if journal.writer != nil {
|
||||
if err := journal.writer.Close(); err != nil {
|
||||
err := journal.writer.Close()
|
||||
journal.writer = nil
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
journal.writer = nil
|
||||
}
|
||||
// Generate a new journal with the contents of the current pool
|
||||
replacement, err := os.OpenFile(journal.path+".new", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -113,16 +114,22 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
return core.ErrTipAboveFeeCap
|
||||
}
|
||||
// Make sure the transaction is signed properly
|
||||
if _, err := types.Sender(signer, tx); err != nil {
|
||||
from, err := types.Sender(signer, tx)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %v", ErrInvalidSender, err)
|
||||
}
|
||||
// Limit nonce to 2^64-1 per EIP-2681
|
||||
if tx.Nonce()+1 < tx.Nonce() {
|
||||
return core.ErrNonceMax
|
||||
}
|
||||
// Sanity check for extremely large numbers (supported by RLP or RPC)
|
||||
value, overflow := uint256.FromBig(tx.Value())
|
||||
if overflow {
|
||||
return core.ErrInsufficientFunds
|
||||
}
|
||||
// Ensure the transaction has more gas than the bare minimum needed to cover
|
||||
// the transaction metadata
|
||||
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte)
|
||||
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), from, tx.To(), value, rules, params.CostPerStateByte)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -131,7 +138,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
}
|
||||
// Ensure the transaction can cover floor data gas.
|
||||
if rules.IsPrague {
|
||||
floorDataGas, err := core.FloorDataGas(rules, tx.Data(), tx.AccessList())
|
||||
floorDataGas, err := core.FloorDataGas(rules, from, tx.To(), value, tx.Data(), tx.AccessList())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ func (e *BlockAccessList) DecodeRLP(s *rlp.Stream) error {
|
|||
// according to the spec or any code changes are contained which exceed protocol
|
||||
// max code size.
|
||||
func (e *BlockAccessList) Validate(blockGasLimit uint64, blockTxCount int) error {
|
||||
if !slices.IsSortedFunc(*e, func(a, b AccountAccess) int {
|
||||
if !isStrictlySortedFunc(*e, func(a, b AccountAccess) int {
|
||||
return bytes.Compare(a.Address[:], b.Address[:])
|
||||
}) {
|
||||
return errors.New("block access list accounts not in lexicographic order")
|
||||
|
|
|
|||
|
|
@ -110,9 +110,9 @@ func CreateBloom(receipt *Receipt) Bloom {
|
|||
buf [6]byte
|
||||
)
|
||||
for _, log := range receipt.Logs {
|
||||
bin.AddWithBuffer(log.Address.Bytes(), &buf)
|
||||
for _, b := range log.Topics {
|
||||
bin.AddWithBuffer(b[:], &buf)
|
||||
bin.AddWithBuffer(log.Address[:], &buf)
|
||||
for k := range log.Topics {
|
||||
bin.AddWithBuffer(log.Topics[k][:], &buf)
|
||||
}
|
||||
}
|
||||
return bin
|
||||
|
|
|
|||
|
|
@ -79,17 +79,3 @@ func EthTransferLog(from, to common.Address, amount *uint256.Int) *Log {
|
|||
Data: amount32[:],
|
||||
}
|
||||
}
|
||||
|
||||
// EthBurnLog creates an ETH burn log according to EIP-7708.
|
||||
// Specification: https://eips.ethereum.org/EIPS/eip-7708
|
||||
func EthBurnLog(from common.Address, amount *uint256.Int) *Log {
|
||||
amount32 := amount.Bytes32()
|
||||
return &Log{
|
||||
Address: params.SystemAddress,
|
||||
Topics: []common.Hash{
|
||||
params.EthBurnLogEvent,
|
||||
common.BytesToHash(from.Bytes()),
|
||||
},
|
||||
Data: amount32[:],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,9 +45,6 @@ var (
|
|||
func amsterdam8037Config() *params.ChainConfig {
|
||||
cfg := *params.MergedTestChainConfig
|
||||
cfg.AmsterdamTime = new(uint64)
|
||||
blob := *cfg.BlobScheduleConfig
|
||||
blob.Amsterdam = blob.Osaka
|
||||
cfg.BlobScheduleConfig = &blob
|
||||
return &cfg
|
||||
}
|
||||
|
||||
|
|
@ -193,16 +190,25 @@ func TestSStoreChargedAtOpcodeEnd(t *testing.T) {
|
|||
}
|
||||
|
||||
// The SSTORE reentrancy sentry checks gas_left only; the reservoir is excluded.
|
||||
// Uses a noop write (1->1->1) so the sentry is the sole gate.
|
||||
// Uses a noop write (1->1->1): the two PUSH1s cost 6, leaving gas_left at the
|
||||
// sentry (2300) for a 2306 budget. Under EIP-8038 the cold-slot access that
|
||||
// follows a cleared sentry costs COLD_STORAGE_ACCESS (3000).
|
||||
func TestSStoreStipendExcludesReservoir(t *testing.T) {
|
||||
// regular at the sentry, huge reservoir: must still fail.
|
||||
// regular at the sentry, huge reservoir: must still fail, proving the
|
||||
// reservoir does not count toward the sentry.
|
||||
if _, _, err := run8037(t, sstore(0, 1), NewGasBudget(2306, math.MaxUint64/2), new(uint256.Int), setSlot(0, 1)); err == nil {
|
||||
t.Fatal("expected sentry failure with regular gas at the limit")
|
||||
}
|
||||
// one more regular gas clears the sentry.
|
||||
if _, _, err := run8037(t, sstore(0, 1), NewGasBudget(2307, math.MaxUint64/2), new(uint256.Int), setSlot(0, 1)); err != nil {
|
||||
// Enough regular gas to clear the sentry and pay the cold-slot access
|
||||
// (6 for the PUSH1s + COLD_STORAGE_ACCESS) succeeds with a huge reservoir.
|
||||
regular := 6 + params.ColdStorageAccessAmsterdam
|
||||
if _, _, err := run8037(t, sstore(0, 1), NewGasBudget(regular, math.MaxUint64/2), new(uint256.Int), setSlot(0, 1)); err != nil {
|
||||
t.Fatalf("unexpected failure above sentry: %v", err)
|
||||
}
|
||||
// One gas short of the cold-slot access still fails (now on OOG, not sentry).
|
||||
if _, _, err := run8037(t, sstore(0, 1), NewGasBudget(regular-1, math.MaxUint64/2), new(uint256.Int), setSlot(0, 1)); err == nil {
|
||||
t.Fatal("expected OOG when regular gas cannot cover cold-slot access")
|
||||
}
|
||||
}
|
||||
|
||||
// ---- CALL / CREATE bytecode helpers ----
|
||||
|
|
|
|||
254
core/vm/eip8038_test.go
Normal file
254
core/vm/eip8038_test.go
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
// Copyright 2026 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Opcode-level tests for EIP-8038 (state-access gas cost update). They reuse the
|
||||
// Amsterdam harness from eip8037_test.go and assert the re-priced regular-gas,
|
||||
// state-gas and refund-counter accounting.
|
||||
|
||||
package vm
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// run8038 executes code at a contract address under the Amsterdam ruleset and
|
||||
// returns the resulting budget together with the transaction's refund counter.
|
||||
func run8038(t *testing.T, code []byte, gas GasBudget, value *uint256.Int, setup func(*state.StateDB, common.Address)) (GasBudget, uint64, error) {
|
||||
t.Helper()
|
||||
self := common.BytesToAddress([]byte("self"))
|
||||
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting())
|
||||
statedb.CreateAccount(self)
|
||||
statedb.SetCode(self, code, tracing.CodeChangeUnspecified)
|
||||
if setup != nil {
|
||||
setup(statedb, self)
|
||||
}
|
||||
statedb.Finalise(true)
|
||||
_, result, err := amsterdam8037EVM(statedb).Call(common.Address{}, self, nil, gas, value)
|
||||
return result, statedb.GetRefund(), err
|
||||
}
|
||||
|
||||
// TestEIP8038SStore exercises SSTORE under Amsterdam (EIP-8037 + EIP-8038),
|
||||
// asserting the two-dimensional charge (regular + state gas) and the net refund
|
||||
// counter. It covers single stores in isolation (the EIP-8038 cases-table rows,
|
||||
// cold access), the warm-access variants, the dirty-slot refund reversals and
|
||||
// multi-store round trips.
|
||||
//
|
||||
// Each sstore() is "PUSH1 val; PUSH1 slot; SSTORE", so the non-SSTORE overhead is
|
||||
// 6 gas (two PUSH1) per store. The first store to a slot is cold and the rest are
|
||||
// warm, so the access component is COLD_STORAGE_ACCESS + (n-1) * WARM_ACCESS for n
|
||||
// stores. STORAGE_WRITE is charged once per "first change" (current == original).
|
||||
// GAS_STORAGE_SET is state gas, charged when a slot is created from zero and
|
||||
// refilled to the reservoir when that creation is undone within the same tx.
|
||||
func TestEIP8038SStore(t *testing.T) {
|
||||
const (
|
||||
push = uint64(6) // two PUSH1 per SSTORE
|
||||
cold = params.ColdStorageAccessAmsterdam
|
||||
warm = params.WarmStorageReadCostEIP2929
|
||||
write = params.StorageWriteAmsterdam
|
||||
clear = params.StorageClearRefundAmsterdam
|
||||
)
|
||||
set := uint64(params.StorageCreationSize * params.CostPerStateByte) // GAS_STORAGE_SET
|
||||
|
||||
// access(n) is the access-only regular cost for n stores: cold first, warm rest.
|
||||
access := func(n uint64) uint64 { return cold + (n-1)*warm }
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
orig byte // committed (pre-tx) value; 0 means a fresh slot
|
||||
vals []byte // values written to slot 0, in order
|
||||
wantReg uint64
|
||||
wantState int64
|
||||
wantRfnd uint64
|
||||
}{
|
||||
// Single store, cold access (EIP-8038 cases table, Cold rows + noop).
|
||||
{"noop (1->1)", 1, []byte{1}, push + cold, 0, 0},
|
||||
{"create (0->1)", 0, []byte{1}, push + cold + write, int64(set), 0},
|
||||
{"first change (1->2)", 1, []byte{2}, push + cold + write, 0, 0},
|
||||
{"clear (1->0)", 1, []byte{0}, push + cold + write, 0, clear},
|
||||
// Two stores, warm access on the second (Warm rows of the cases table).
|
||||
{"create warm (0->0->1)", 0, []byte{0, 1}, 2*push + access(2) + write, int64(set), 0},
|
||||
{"first change warm (1->1->2)", 1, []byte{1, 2}, 2*push + access(2) + write, 0, 0},
|
||||
{"clear warm (1->1->0)", 1, []byte{1, 0}, 2*push + access(2) + write, 0, clear},
|
||||
{"dirty modified again (1->2->3)", 1, []byte{2, 3}, 2*push + access(2) + write, 0, 0},
|
||||
// Two stores, refund reversals when a slot returns toward its original.
|
||||
{"reset to zero (0->1->0)", 0, []byte{1, 0}, 2*push + access(2) + write, 0, write},
|
||||
{"reset to original (1->2->1)", 1, []byte{2, 1}, 2*push + access(2) + write, 0, write},
|
||||
{"cleared then restored (1->0->1)", 1, []byte{0, 1}, 2*push + access(2) + write, 0, write},
|
||||
{"cleared then new value (1->0->2)", 1, []byte{0, 2}, 2*push + access(2) + write, 0, 0},
|
||||
// Three stores, round trips (note the state-gas refill on the 0-> path).
|
||||
{"0->1->0->1", 0, []byte{1, 0, 1}, 3*push + access(3) + 2*write, int64(set), write},
|
||||
{"1->0->1->0", 1, []byte{0, 1, 0}, 3*push + access(3) + 2*write, 0, clear + write},
|
||||
}
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var code []byte
|
||||
for _, v := range tc.vals {
|
||||
code = append(code, sstore(0, v)...)
|
||||
}
|
||||
var setup func(*state.StateDB, common.Address)
|
||||
if tc.orig != 0 {
|
||||
setup = setSlot(0, tc.orig)
|
||||
}
|
||||
res, refund, err := run8038(t, code, hugeBudget(), new(uint256.Int), setup)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if res.UsedRegularGas != tc.wantReg {
|
||||
t.Errorf("regular gas = %d, want %d", res.UsedRegularGas, tc.wantReg)
|
||||
}
|
||||
if res.UsedStateGas != tc.wantState {
|
||||
t.Errorf("state gas = %d, want %d", res.UsedStateGas, tc.wantState)
|
||||
}
|
||||
if refund != tc.wantRfnd {
|
||||
t.Errorf("refund = %d, want %d", refund, tc.wantRfnd)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestEIP8038SLoad checks the re-priced SLOAD access costs (cold 3000, warm 100).
|
||||
func TestEIP8038SLoad(t *testing.T) {
|
||||
push := uint64(3) // PUSH1 slot
|
||||
// PUSH1 0x00; SLOAD
|
||||
cold := []byte{0x60, 0x00, 0x54}
|
||||
res, _, err := run8038(t, cold, hugeBudget(), new(uint256.Int), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want := push + params.ColdStorageAccessAmsterdam; res.UsedRegularGas != want {
|
||||
t.Fatalf("cold SLOAD = %d, want %d", res.UsedRegularGas, want)
|
||||
}
|
||||
// PUSH1 0x00; SLOAD; PUSH1 0x00; SLOAD -> second access is warm.
|
||||
warm := []byte{0x60, 0x00, 0x54, 0x60, 0x00, 0x54}
|
||||
res, _, err = run8038(t, warm, hugeBudget(), new(uint256.Int), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
want := 2*push + params.ColdStorageAccessAmsterdam + params.WarmStorageReadCostEIP2929
|
||||
if res.UsedRegularGas != want {
|
||||
t.Fatalf("cold+warm SLOAD = %d, want %d", res.UsedRegularGas, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEIP8038AccountAccess checks the re-priced cold-account access for the
|
||||
// account-reading opcodes and the extra WARM_ACCESS surcharge for EXTCODESIZE
|
||||
// and EXTCODECOPY (their second database read).
|
||||
func TestEIP8038AccountAccess(t *testing.T) {
|
||||
push20 := uint64(3)
|
||||
addr := common.BytesToAddress([]byte("some-cold-account"))
|
||||
|
||||
// pushAddr emits PUSH20 <addr>.
|
||||
pushAddr := func() []byte { return append([]byte{0x73}, addr.Bytes()...) }
|
||||
|
||||
cold := params.ColdAccountAccessAmsterdam
|
||||
warm := params.WarmStorageReadCostEIP2929
|
||||
|
||||
t.Run("BALANCE", func(t *testing.T) {
|
||||
code := append(pushAddr(), 0x31) // BALANCE
|
||||
res, _, err := run8038(t, code, hugeBudget(), new(uint256.Int), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want := push20 + cold; res.UsedRegularGas != want {
|
||||
t.Fatalf("cold BALANCE = %d, want %d", res.UsedRegularGas, want)
|
||||
}
|
||||
})
|
||||
t.Run("EXTCODEHASH", func(t *testing.T) {
|
||||
code := append(pushAddr(), 0x3f) // EXTCODEHASH
|
||||
res, _, err := run8038(t, code, hugeBudget(), new(uint256.Int), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want := push20 + cold; res.UsedRegularGas != want {
|
||||
t.Fatalf("cold EXTCODEHASH = %d, want %d", res.UsedRegularGas, want)
|
||||
}
|
||||
})
|
||||
t.Run("EXTCODESIZE adds WARM_ACCESS", func(t *testing.T) {
|
||||
code := append(pushAddr(), 0x3b) // EXTCODESIZE
|
||||
res, _, err := run8038(t, code, hugeBudget(), new(uint256.Int), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if want := push20 + cold + warm; res.UsedRegularGas != want {
|
||||
t.Fatalf("cold EXTCODESIZE = %d, want %d", res.UsedRegularGas, want)
|
||||
}
|
||||
})
|
||||
t.Run("EXTCODECOPY adds WARM_ACCESS", func(t *testing.T) {
|
||||
// PUSH1 0 (length); PUSH1 0 (codeOffset); PUSH1 0 (destOffset); PUSH20 addr; EXTCODECOPY
|
||||
code := []byte{0x60, 0x00, 0x60, 0x00, 0x60, 0x00}
|
||||
code = append(code, pushAddr()...)
|
||||
code = append(code, 0x3c) // EXTCODECOPY
|
||||
res, _, err := run8038(t, code, hugeBudget(), new(uint256.Int), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// three PUSH1 + one PUSH20 = 12 gas, zero-length copy => no memory/copy gas.
|
||||
if want := uint64(12) + cold + warm; res.UsedRegularGas != want {
|
||||
t.Fatalf("cold EXTCODECOPY = %d, want %d", res.UsedRegularGas, want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestEIP8038SelfdestructAccountWrite checks that SELFDESTRUCT sending a positive
|
||||
// balance to an empty account is charged the cold access, an additional
|
||||
// ACCOUNT_WRITE (regular) and GAS_NEW_ACCOUNT (state).
|
||||
func TestEIP8038SelfdestructAccountWrite(t *testing.T) {
|
||||
beneficiary := common.BytesToAddress([]byte("fresh-beneficiary"))
|
||||
// PUSH20 beneficiary; SELFDESTRUCT
|
||||
code := append([]byte{0x73}, beneficiary.Bytes()...)
|
||||
code = append(code, 0xff)
|
||||
|
||||
// Fund the contract so it sends a positive balance on self-destruct.
|
||||
fundSelf := func(db *state.StateDB, self common.Address) {
|
||||
db.AddBalance(self, uint256.NewInt(1), tracing.BalanceChangeUnspecified)
|
||||
}
|
||||
res, _, err := run8038(t, code, hugeBudget(), new(uint256.Int), fundSelf)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
const push20 = uint64(3)
|
||||
wantReg := push20 + params.SelfdestructGasEIP150 + params.ColdAccountAccessAmsterdam + params.AccountWriteAmsterdam
|
||||
if res.UsedRegularGas != wantReg {
|
||||
t.Fatalf("regular gas = %d, want %d", res.UsedRegularGas, wantReg)
|
||||
}
|
||||
if want := int64(params.AccountCreationSize * params.CostPerStateByte); res.UsedStateGas != want {
|
||||
t.Fatalf("state gas = %d, want %d", res.UsedStateGas, want)
|
||||
}
|
||||
}
|
||||
|
||||
// TestEIP8038SStoreAccessGuard covers the affordability check that bails out
|
||||
// before the slot is read once the gas left cannot cover the slot's access cost.
|
||||
// The two PUSH1s cost 6, so a 2506 budget leaves 2500 at the SSTORE: above the
|
||||
// reentrancy sentry (2300) yet below COLD_STORAGE_ACCESS (3000). The guard must
|
||||
// fire, distinguishable from the sentry/charge OOG by its "slot access" message.
|
||||
func TestEIP8038SStoreAccessGuard(t *testing.T) {
|
||||
budget := NewGasBudget(6+params.SstoreSentryGasEIP2200+200, 0)
|
||||
_, _, err := run8038(t, sstore(0, 1), budget, new(uint256.Int), nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected failure: gas left cannot cover cold-slot access")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "not enough gas for slot access") {
|
||||
t.Fatalf("got %q, want the slot-access guard error", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -43,7 +43,8 @@ var activators = map[int]func(*JumpTable){
|
|||
7939: enable7939,
|
||||
8024: enable8024,
|
||||
7843: enable7843,
|
||||
8037: enable8037,
|
||||
8037: enable8037And8038,
|
||||
8038: enable8037And8038,
|
||||
}
|
||||
|
||||
// EnableEIP enables the given EIP on the config.
|
||||
|
|
@ -584,13 +585,30 @@ func enable7843(jt *JumpTable) {
|
|||
}
|
||||
}
|
||||
|
||||
// enable8037 enables the multidimensional-metering as specified in EIP-8037.
|
||||
func enable8037(jt *JumpTable) {
|
||||
jt[CREATE].constantGas = params.CreateGasAmsterdam
|
||||
// enable8037And8038 enables EIP-8037 (multidimensional state-gas metering)
|
||||
// together with EIP-8038 (state-access gas cost update).
|
||||
func enable8037And8038(jt *JumpTable) {
|
||||
jt[CREATE].constantGas = params.CreateAccessAmsterdam
|
||||
jt[CREATE].dynamicGas = gasCreateEip8037
|
||||
jt[CREATE2].constantGas = params.CreateGasAmsterdam
|
||||
jt[CREATE2].constantGas = params.CreateAccessAmsterdam
|
||||
jt[CREATE2].dynamicGas = gasCreate2Eip8037
|
||||
jt[CALL].dynamicGas = gasCallEIP8037
|
||||
jt[SELFDESTRUCT].dynamicGas = gasSelfdestruct8037
|
||||
jt[SSTORE].dynamicGas = gasSStore8037
|
||||
|
||||
// Storage-access opcodes
|
||||
jt[SLOAD].dynamicGas = gasSLoad8038
|
||||
jt[SSTORE].dynamicGas = gasSStore8037And8038
|
||||
|
||||
// Account-access opcodes
|
||||
jt[BALANCE].dynamicGas = gasEip8038AccountCheck
|
||||
jt[EXTCODEHASH].dynamicGas = gasEip8038AccountCheck
|
||||
jt[EXTCODESIZE].dynamicGas = gasExtCodeSize8038
|
||||
jt[EXTCODECOPY].dynamicGas = gasExtCodeCopy8038
|
||||
|
||||
// Call family
|
||||
jt[CALL].dynamicGas = gasCall8038
|
||||
jt[CALLCODE].dynamicGas = gasCallCode8038
|
||||
jt[STATICCALL].dynamicGas = gasStaticCall8038
|
||||
jt[DELEGATECALL].dynamicGas = gasDelegateCall8038
|
||||
|
||||
// SELFDESTRUCT
|
||||
jt[SELFDESTRUCT].dynamicGas = gasSelfdestruct8037And8038
|
||||
}
|
||||
|
|
|
|||
|
|
@ -478,6 +478,28 @@ func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memor
|
|||
return gas, nil
|
||||
}
|
||||
|
||||
// gasCallCodeIntrinsic8038 mirrors gasCallCodeIntrinsic but charges the
|
||||
// re-priced CALL_VALUE (ACCOUNT_WRITE + CALL_STIPEND) on value transfers per
|
||||
// EIP-8038. CALLCODE executes in the caller's context, so it never creates a
|
||||
// new account and has no state-gas component.
|
||||
func gasCallCodeIntrinsic8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
memoryGas, err := memoryGasCost(mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var (
|
||||
gas uint64
|
||||
overflow bool
|
||||
)
|
||||
if stack.back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 {
|
||||
gas += params.CallValueTransferAmsterdam
|
||||
}
|
||||
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasDelegateCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
gas, err := memoryGasCost(mem, memorySize)
|
||||
if err != nil {
|
||||
|
|
@ -576,10 +598,11 @@ func gasCreate2Eip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
|
|||
}, nil
|
||||
}
|
||||
|
||||
// regularGasCall8037 is the intrinsic gas calculator for CALL in Amsterdam.
|
||||
// It computes memory expansion + value transfer gas but excludes new account
|
||||
// creation, which is handled as state gas by the wrapper.
|
||||
func regularGasCall8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
// regularGasCall8038 is the intrinsic regular-gas calculator for CALL in
|
||||
// Amsterdam. It computes memory expansion plus the re-priced CALL_VALUE
|
||||
// (ACCOUNT_WRITE + CALL_STIPEND) on value transfers, but excludes new account
|
||||
// creation, which is handled as state gas by stateGasCall8037.
|
||||
func regularGasCall8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
var (
|
||||
gas uint64
|
||||
transfersValue = !stack.back(2).IsZero()
|
||||
|
|
@ -593,7 +616,7 @@ func regularGasCall8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
|
|||
}
|
||||
var transferGas uint64
|
||||
if transfersValue && !evm.chainRules.IsEIP4762 {
|
||||
transferGas = params.CallValueTransferGas
|
||||
transferGas = params.CallValueTransferAmsterdam
|
||||
}
|
||||
var overflow bool
|
||||
if gas, overflow = math.SafeAdd(memoryGas, transferGas); overflow {
|
||||
|
|
@ -630,7 +653,9 @@ func stateGasCall8037(evm *EVM, contract *Contract, stack *Stack) (uint64, error
|
|||
return gas, nil
|
||||
}
|
||||
|
||||
func gasSelfdestruct8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
// gasSelfdestruct8037And8038 implements the SELFDESTRUCT gas charging under
|
||||
// the EIP8037 and EIP-8038.
|
||||
func gasSelfdestruct8037And8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
if evm.readOnly {
|
||||
return GasCosts{}, ErrWriteProtection
|
||||
}
|
||||
|
|
@ -639,11 +664,11 @@ func gasSelfdestruct8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory
|
|||
address = common.Address(stack.peek().Bytes20())
|
||||
)
|
||||
if !evm.StateDB.AddressInAccessList(address) {
|
||||
// If the caller cannot afford the cost, this change will be rolled back
|
||||
// If the caller cannot afford the cost, this change will be rolled back.
|
||||
evm.StateDB.AddAddressToAccessList(address)
|
||||
gas.RegularGas = params.ColdAccountAccessCostEIP2929
|
||||
gas.RegularGas = params.ColdAccountAccessAmsterdam
|
||||
}
|
||||
// Check we have enough regular gas before we add the address to the BAL
|
||||
// Check we have enough regular gas before we add the address to the BAL.
|
||||
if contract.Gas.RegularGas < gas.RegularGas {
|
||||
return gas, ErrOutOfGas
|
||||
}
|
||||
|
|
@ -654,79 +679,68 @@ func gasSelfdestruct8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory
|
|||
//
|
||||
// Funding such an account makes it permanent state growth and must be charged.
|
||||
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
|
||||
gas.RegularGas += params.AccountWriteAmsterdam
|
||||
gas.StateGas += params.AccountCreationSize * evm.Context.CostPerStateByte
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasSStore8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
// gasSStore8037And8038 implements the SSTORE gas charging under EIP-8037 and
|
||||
// EIP-8038.
|
||||
func gasSStore8037And8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
if evm.readOnly {
|
||||
return GasCosts{}, ErrWriteProtection
|
||||
}
|
||||
// If we fail the minimum gas availability invariant, fail (0)
|
||||
// If we fail the minimum gas availability invariant, fail (0).
|
||||
if contract.Gas.RegularGas <= params.SstoreSentryGasEIP2200 {
|
||||
return GasCosts{}, errors.New("not enough gas for reentrancy sentry")
|
||||
}
|
||||
// Gas sentry honoured, do the actual gas calculation based on the stored value
|
||||
var (
|
||||
y, x = stack.back(1), stack.peek()
|
||||
slot = common.Hash(x.Bytes32())
|
||||
current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), slot)
|
||||
cost GasCosts
|
||||
y, x = stack.back(1), stack.peek()
|
||||
slot = common.Hash(x.Bytes32())
|
||||
value = common.Hash(y.Bytes32())
|
||||
stateSet = params.StorageCreationSize * evm.Context.CostPerStateByte
|
||||
)
|
||||
// Check slot presence in the access list
|
||||
access := params.WarmStorageReadCostEIP2929
|
||||
if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
|
||||
cost = GasCosts{RegularGas: params.ColdSloadCostEIP2929}
|
||||
// If the caller cannot afford the cost, this change will be rolled back
|
||||
access = params.ColdStorageAccessAmsterdam
|
||||
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
|
||||
}
|
||||
value := common.Hash(y.Bytes32())
|
||||
// Check access cost affordability before reading slot
|
||||
if contract.Gas.RegularGas < access {
|
||||
return GasCosts{}, errors.New("not enough gas for slot access")
|
||||
}
|
||||
// Read the slot value for gas cost measurement
|
||||
current, original := evm.StateDB.GetStateAndCommittedState(contract.Address(), slot)
|
||||
|
||||
if current == value { // noop (1)
|
||||
// EIP 2200 original clause:
|
||||
// return params.SloadGasEIP2200, nil
|
||||
return GasCosts{RegularGas: cost.RegularGas + params.WarmStorageReadCostEIP2929}, nil // SLOAD_GAS
|
||||
return GasCosts{RegularGas: access}, nil
|
||||
}
|
||||
if original == current {
|
||||
if original == current { // first change of the slot (2.1)
|
||||
if original == (common.Hash{}) { // create slot (2.1.1)
|
||||
return GasCosts{
|
||||
RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929,
|
||||
StateGas: params.StorageCreationSize * evm.Context.CostPerStateByte,
|
||||
RegularGas: access + params.StorageWriteAmsterdam,
|
||||
StateGas: stateSet,
|
||||
}, nil
|
||||
}
|
||||
if value == (common.Hash{}) { // delete slot (2.1.2b)
|
||||
evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP3529)
|
||||
evm.StateDB.AddRefund(params.StorageClearRefundAmsterdam)
|
||||
}
|
||||
// EIP-2200 original clause:
|
||||
// return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2)
|
||||
return GasCosts{RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929}, nil // write existing slot (2.1.2)
|
||||
return GasCosts{RegularGas: access + params.StorageWriteAmsterdam}, nil // write existing slot (2.1.2)
|
||||
}
|
||||
if original != (common.Hash{}) {
|
||||
if current == (common.Hash{}) { // recreate slot (2.2.1.1)
|
||||
evm.StateDB.SubRefund(params.SstoreClearsScheduleRefundEIP3529)
|
||||
evm.StateDB.SubRefund(params.StorageClearRefundAmsterdam)
|
||||
} else if value == (common.Hash{}) { // delete slot (2.2.1.2)
|
||||
evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP3529)
|
||||
evm.StateDB.AddRefund(params.StorageClearRefundAmsterdam)
|
||||
}
|
||||
}
|
||||
if original == value {
|
||||
if original == value { // reset to original value (2.2.2)
|
||||
if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1)
|
||||
// EIP-8037 point (2): refund state gas directly to the reservoir
|
||||
// at the SSTORE restoration point (0→x→0 in same tx); not to the
|
||||
// refund counter, which is capped at gas_used/5.
|
||||
contract.Gas.RefundState(params.StorageCreationSize * evm.Context.CostPerStateByte)
|
||||
|
||||
// Regular portion of the refund still goes through the refund counter.
|
||||
evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929)
|
||||
} else { // reset to original existing slot (2.2.2.2)
|
||||
// EIP 2200 Original clause:
|
||||
// evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200)
|
||||
// - SSTORE_RESET_GAS redefined as (5000 - COLD_SLOAD_COST)
|
||||
// - SLOAD_GAS redefined as WARM_STORAGE_READ_COST
|
||||
// Final: (5000 - COLD_SLOAD_COST) - WARM_STORAGE_READ_COST
|
||||
evm.StateDB.AddRefund((params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929) - params.WarmStorageReadCostEIP2929)
|
||||
contract.Gas.RefundState(stateSet)
|
||||
}
|
||||
evm.StateDB.AddRefund(params.StorageWriteAmsterdam)
|
||||
}
|
||||
// EIP-2200 original clause:
|
||||
//return params.SloadGasEIP2200, nil // dirty update (2.2)
|
||||
return GasCosts{RegularGas: cost.RegularGas + params.WarmStorageReadCostEIP2929}, nil // dirty update (2.2)
|
||||
return GasCosts{RegularGas: access}, nil // dirty update (2.2)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -196,6 +196,22 @@ func (g *GasBudget) RefundState(s uint64) {
|
|||
g.UsedStateGas -= int64(s)
|
||||
}
|
||||
|
||||
// RefundStateToReservoir credits a state-gas refund directly to the
|
||||
// reservoir, without repaying spilled regular gas first.
|
||||
//
|
||||
// Per the spec's set_delegation, authorization refunds (and the post-create
|
||||
// new-account refund) are added to message.state_gas_reservoir directly, in
|
||||
// contrast to the LIFO inline refunds handled by RefundState. The usage
|
||||
// counter is decremented by the full amount, matching the spec's
|
||||
// tx_state_gas = intrinsic_state + state_gas_used - state_refund and
|
||||
// preserving the per-frame invariant:
|
||||
//
|
||||
// StateGas + UsedStateGas == initialStateGas + Spilled
|
||||
func (g *GasBudget) RefundStateToReservoir(s uint64) {
|
||||
g.StateGas += s
|
||||
g.UsedStateGas -= int64(s)
|
||||
}
|
||||
|
||||
// Forward drains `regular` regular gas and the entire state reservoir from
|
||||
// the parent's running budget and returns the initial GasBudget for a child
|
||||
// frame. The parent's UsedRegularGas is bumped by the forwarded amount so
|
||||
|
|
|
|||
|
|
@ -928,8 +928,15 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro
|
|||
if newContract {
|
||||
if this != beneficiary { // Skip no-op transfer when self-destructing to self.
|
||||
evm.StateDB.AddBalance(beneficiary, balance, tracing.BalanceIncreaseSelfdestruct)
|
||||
evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct)
|
||||
} else if !evm.chainRules.IsAmsterdam {
|
||||
// Self-destructing to self burns the balance prior to EIP-8246.
|
||||
// EIP-8246 (Amsterdam) removes this burn: the balance is left
|
||||
// untouched and the account is preserved as a balance-only account
|
||||
// at transaction finalization (unless its balance is zero, in which
|
||||
// case EIP-161 deletes it).
|
||||
evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct)
|
||||
}
|
||||
evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct)
|
||||
evm.StateDB.SelfDestruct(this)
|
||||
}
|
||||
|
||||
|
|
@ -938,12 +945,10 @@ func opSelfdestruct6780(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, erro
|
|||
evm.StateDB.SubBalance(this, balance, tracing.BalanceDecreaseSelfdestruct)
|
||||
evm.StateDB.AddBalance(beneficiary, balance, tracing.BalanceIncreaseSelfdestruct)
|
||||
}
|
||||
if evm.chainRules.IsAmsterdam && !balance.IsZero() {
|
||||
if this != beneficiary {
|
||||
evm.StateDB.AddLog(types.EthTransferLog(this, beneficiary, balance))
|
||||
} else if newContract {
|
||||
evm.StateDB.AddLog(types.EthBurnLog(this, balance))
|
||||
}
|
||||
// EIP-7708: emit a transfer log for the moved balance. EIP-8246 removes the
|
||||
// SELFDESTRUCT burn entirely, so there is no longer a burn to log.
|
||||
if evm.chainRules.IsAmsterdam && !balance.IsZero() && this != beneficiary {
|
||||
evm.StateDB.AddLog(types.EthTransferLog(this, beneficiary, balance))
|
||||
}
|
||||
|
||||
if tracer := evm.Config.Tracer; tracer != nil {
|
||||
|
|
|
|||
|
|
@ -90,7 +90,6 @@ type StateDB interface {
|
|||
Snapshot() int
|
||||
|
||||
AddLog(*types.Log)
|
||||
LogsForBurnAccounts() []*types.Log
|
||||
AddPreimage(common.Hash, []byte)
|
||||
|
||||
Witness() *stateless.Witness
|
||||
|
|
|
|||
|
|
@ -99,9 +99,9 @@ func newVerkleInstructionSet() JumpTable {
|
|||
|
||||
func newAmsterdamInstructionSet() JumpTable {
|
||||
instructionSet := newOsakaInstructionSet()
|
||||
enable7843(&instructionSet) // EIP-7843 (SLOTNUM opcode)
|
||||
enable8024(&instructionSet) // EIP-8024 (Backward compatible SWAPN, DUPN, EXCHANGE)
|
||||
enable8037(&instructionSet) // EIP-8037 (State creation gas cost increase)
|
||||
enable7843(&instructionSet) // EIP-7843 (SLOTNUM opcode)
|
||||
enable8024(&instructionSet) // EIP-8024 (Backward compatible SWAPN, DUPN, EXCHANGE)
|
||||
enable8037And8038(&instructionSet) // EIP-8037 (state-gas metering) + EIP-8038 (state-access repricing)
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -101,16 +101,25 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
|
|||
func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
loc := stack.peek()
|
||||
slot := common.Hash(loc.Bytes32())
|
||||
// Check slot presence in the access list
|
||||
if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
|
||||
// If the caller cannot afford the cost, this change will be rolled back
|
||||
// If he does afford it, we can skip checking the same thing later on, during execution
|
||||
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
|
||||
return GasCosts{RegularGas: params.ColdSloadCostEIP2929}, nil
|
||||
}
|
||||
return GasCosts{RegularGas: params.WarmStorageReadCostEIP2929}, nil
|
||||
}
|
||||
|
||||
// gasSLoad8038 mirrors gasSLoadEIP2929 but uses the EIP-8038 COLD_STORAGE_ACCESS
|
||||
// for a cold slot.
|
||||
func gasSLoad8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
loc := stack.peek()
|
||||
slot := common.Hash(loc.Bytes32())
|
||||
if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
|
||||
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
|
||||
return GasCosts{RegularGas: params.ColdStorageAccessAmsterdam}, nil
|
||||
}
|
||||
return GasCosts{RegularGas: params.WarmStorageReadCostEIP2929}, nil
|
||||
}
|
||||
|
||||
// gasExtCodeCopyEIP2929 implements extcodecopy according to EIP-2929
|
||||
// EIP spec:
|
||||
// > If the target is not in accessed_addresses,
|
||||
|
|
@ -137,6 +146,34 @@ func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memo
|
|||
return GasCosts{RegularGas: gas}, nil
|
||||
}
|
||||
|
||||
// gasExtCodeCopy8038 mirrors gasExtCodeCopyEIP2929 but uses the EIP-8038
|
||||
// COLD_ACCOUNT_ACCESS and adds an extra WARM_ACCESS for the second
|
||||
// database read EXTCODECOPY performs.
|
||||
func gasExtCodeCopy8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
// memory expansion first (dynamic part of pre-2929 implementation)
|
||||
gasCost, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize)
|
||||
if err != nil {
|
||||
return GasCosts{}, err
|
||||
}
|
||||
gas := gasCost.RegularGas
|
||||
addr := common.Address(stack.peek().Bytes20())
|
||||
// Check slot presence in the access list
|
||||
if !evm.StateDB.AddressInAccessList(addr) {
|
||||
evm.StateDB.AddAddressToAccessList(addr)
|
||||
var overflow bool
|
||||
// We charge (cold-warm), since 'warm' is already charged as constantGas
|
||||
if gas, overflow = math.SafeAdd(gas, params.ColdAccountAccessAmsterdam-params.WarmStorageReadCostEIP2929); overflow {
|
||||
return GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
}
|
||||
// Additional WARM_ACCESS for the second database read (contract code).
|
||||
var overflow bool
|
||||
if gas, overflow = math.SafeAdd(gas, params.WarmStorageReadCostEIP2929); overflow {
|
||||
return GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
return GasCosts{RegularGas: gas}, nil
|
||||
}
|
||||
|
||||
// gasEip2929AccountCheck checks whether the first stack item (as address) is present in the access list.
|
||||
// If it is, this method returns '0', otherwise 'cold-warm' gas, presuming that the opcode using it
|
||||
// is also using 'warm' as constant factor.
|
||||
|
|
@ -156,6 +193,32 @@ func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Mem
|
|||
return GasCosts{}, nil
|
||||
}
|
||||
|
||||
// gasEip8038AccountCheck mirrors gasEip2929AccountCheck but uses the EIP-8038
|
||||
// COLD_ACCOUNT_ACCESS. Used by BALANCE and EXTCODEHASH.
|
||||
func gasEip8038AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
addr := common.Address(stack.peek().Bytes20())
|
||||
// Check slot presence in the access list
|
||||
if !evm.StateDB.AddressInAccessList(addr) {
|
||||
// If the caller cannot afford the cost, this change will be rolled back
|
||||
evm.StateDB.AddAddressToAccessList(addr)
|
||||
// The warm storage read cost is already charged as constantGas
|
||||
return GasCosts{RegularGas: params.ColdAccountAccessAmsterdam - params.WarmStorageReadCostEIP2929}, nil
|
||||
}
|
||||
return GasCosts{}, nil
|
||||
}
|
||||
|
||||
// gasExtCodeSize8038 prices EXTCODESIZE under EIP-8038: the gasEip8038AccountCheck
|
||||
// surcharge plus an additional WARM_ACCESS for the second database read (code size).
|
||||
func gasExtCodeSize8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
cost, err := gasEip8038AccountCheck(evm, contract, stack, mem, memorySize)
|
||||
if err != nil {
|
||||
return GasCosts{}, err
|
||||
}
|
||||
// Additional WARM_ACCESS for the second database read (contract size).
|
||||
cost.RegularGas += params.WarmStorageReadCostEIP2929
|
||||
return cost, nil
|
||||
}
|
||||
|
||||
func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) gasFunc {
|
||||
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
addr := common.Address(stack.back(addressPosition).Bytes20())
|
||||
|
|
@ -257,12 +320,19 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
|
|||
return gasFunc
|
||||
}
|
||||
|
||||
// recordDelegationAccess records the EIP-7702 delegated target in the block
|
||||
// access list (EIP-7928).
|
||||
func recordDelegationAccess(evm *EVM, target common.Address) {
|
||||
if evm.chainRules.IsAmsterdam {
|
||||
evm.StateDB.GetCode(target)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCallIntrinsic)
|
||||
gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCallIntrinsic)
|
||||
gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCallIntrinsic)
|
||||
gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCodeIntrinsic)
|
||||
innerGasCallEIP8037 = makeCallVariantGasCallEIP8037(regularGasCall8037, stateGasCall8037)
|
||||
innerGasCallEIP7702 = makeCallVariantGasCallEIP7702(gasCallIntrinsic, params.ColdAccountAccessCostEIP2929)
|
||||
gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCallIntrinsic, params.ColdAccountAccessCostEIP2929)
|
||||
gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCallIntrinsic, params.ColdAccountAccessCostEIP2929)
|
||||
gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCodeIntrinsic, params.ColdAccountAccessCostEIP2929)
|
||||
)
|
||||
|
||||
func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
|
|
@ -277,15 +347,24 @@ func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
|
|||
return innerGasCallEIP7702(evm, contract, stack, mem, memorySize)
|
||||
}
|
||||
|
||||
func gasCallEIP8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
var (
|
||||
innerGasCall8038 = makeCallVariantGasCallEIP8037(regularGasCall8038, stateGasCall8037, params.ColdAccountAccessAmsterdam)
|
||||
gasCallCode8038 = makeCallVariantGasCallEIP7702(gasCallCodeIntrinsic8038, params.ColdAccountAccessAmsterdam)
|
||||
gasDelegateCall8038 = makeCallVariantGasCallEIP7702(gasDelegateCallIntrinsic, params.ColdAccountAccessAmsterdam)
|
||||
gasStaticCall8038 = makeCallVariantGasCallEIP7702(gasStaticCallIntrinsic, params.ColdAccountAccessAmsterdam)
|
||||
)
|
||||
|
||||
// gasCall8038 prices CALL for Amsterdam, guarding against value transfers in a
|
||||
// read-only context before delegating to the state-gas-aware wrapper.
|
||||
func gasCall8038(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
transfersValue := !stack.back(2).IsZero()
|
||||
if evm.readOnly && transfersValue {
|
||||
return GasCosts{}, ErrWriteProtection
|
||||
}
|
||||
return innerGasCallEIP8037(evm, contract, stack, mem, memorySize)
|
||||
return innerGasCall8038(evm, contract, stack, mem, memorySize)
|
||||
}
|
||||
|
||||
func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
|
||||
func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc, coldCost uint64) gasFunc {
|
||||
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
var (
|
||||
eip2929Cost uint64
|
||||
|
|
@ -300,7 +379,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
|
|||
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form
|
||||
// of a constant cost, so the cost to charge for cold access, if any,
|
||||
// is Cold - Warm
|
||||
eip2929Cost = params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
|
||||
eip2929Cost = coldCost - params.WarmStorageReadCostEIP2929
|
||||
|
||||
// Charge the remaining difference here already, to correctly calculate
|
||||
// available gas for call
|
||||
|
|
@ -321,7 +400,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
|
|||
// Terminate the gas measurement if the leftover gas is not sufficient,
|
||||
// it can effectively prevent accessing the states in the following steps.
|
||||
// It's an essential safeguard before any stateful check.
|
||||
if contract.Gas.RegularGas < intrinsicCost {
|
||||
if !contract.chargeRegular(intrinsicCost, evm.Config.Tracer, tracing.GasChangeIgnored) {
|
||||
return GasCosts{}, ErrOutOfGas
|
||||
}
|
||||
|
||||
|
|
@ -331,15 +410,19 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
|
|||
eip7702Cost = params.WarmStorageReadCostEIP2929
|
||||
} else {
|
||||
evm.StateDB.AddAddressToAccessList(target)
|
||||
eip7702Cost = params.ColdAccountAccessCostEIP2929
|
||||
eip7702Cost = coldCost
|
||||
}
|
||||
if !contract.chargeRegular(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||
return GasCosts{}, ErrOutOfGas
|
||||
}
|
||||
// The delegated address has passed its gas check; record it in the
|
||||
// block access list now, before the call's sender-balance and
|
||||
// call-stack-depth checks.
|
||||
recordDelegationAccess(evm, target)
|
||||
}
|
||||
// Calculate the gas budget for the nested call. The costs defined by
|
||||
// EIP-2929 and EIP-7702 have already been applied.
|
||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsicCost, stack.back(0))
|
||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, 0, stack.back(0))
|
||||
if err != nil {
|
||||
return GasCosts{}, err
|
||||
}
|
||||
|
|
@ -347,11 +430,13 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
|
|||
// adding it to the return, it will be charged outside of this function, as
|
||||
// part of the dynamic gas. This will ensure it is correctly reported to
|
||||
// tracers.
|
||||
contract.Gas.RegularGas += eip2929Cost + eip7702Cost
|
||||
contract.Gas.RegularGas += eip2929Cost + eip7702Cost + intrinsicCost
|
||||
|
||||
// Undo the RegularGasUsed increments from the direct UseGas charges,
|
||||
// since this gas will be re-charged via the returned cost.
|
||||
contract.Gas.UsedRegularGas -= eip2929Cost
|
||||
contract.Gas.UsedRegularGas -= eip7702Cost
|
||||
contract.Gas.UsedRegularGas -= intrinsicCost
|
||||
|
||||
// Aggregate the gas costs from all components, including EIP-2929, EIP-7702,
|
||||
// the CALL opcode itself, and the cost incurred by nested calls.
|
||||
|
|
@ -376,7 +461,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
|
|||
// It extends the EIP-7702 pattern with state gas handling and GasUsed tracking.
|
||||
// intrinsicFunc computes the regular gas (memory + transfer, no new account creation).
|
||||
// stateGasFunc computes the state gas (new account creation as state gas).
|
||||
func makeCallVariantGasCallEIP8037(regularFunc regularGasFunc, stateGasFunc stateGasFunc) gasFunc {
|
||||
func makeCallVariantGasCallEIP8037(regularFunc regularGasFunc, stateGasFunc stateGasFunc, coldCost uint64) gasFunc {
|
||||
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
var (
|
||||
eip2929Cost uint64
|
||||
|
|
@ -386,7 +471,7 @@ func makeCallVariantGasCallEIP8037(regularFunc regularGasFunc, stateGasFunc stat
|
|||
// EIP-2929 cold access check.
|
||||
if !evm.StateDB.AddressInAccessList(addr) {
|
||||
evm.StateDB.AddAddressToAccessList(addr)
|
||||
eip2929Cost = params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
|
||||
eip2929Cost = coldCost - params.WarmStorageReadCostEIP2929
|
||||
if !contract.chargeRegular(eip2929Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||
return GasCosts{}, ErrOutOfGas
|
||||
}
|
||||
|
|
@ -411,11 +496,15 @@ func makeCallVariantGasCallEIP8037(regularFunc regularGasFunc, stateGasFunc stat
|
|||
eip7702Cost = params.WarmStorageReadCostEIP2929
|
||||
} else {
|
||||
evm.StateDB.AddAddressToAccessList(target)
|
||||
eip7702Cost = params.ColdAccountAccessCostEIP2929
|
||||
eip7702Cost = coldCost
|
||||
}
|
||||
if !contract.chargeRegular(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||
return GasCosts{}, ErrOutOfGas
|
||||
}
|
||||
// The delegated address has passed its gas check; record it in the
|
||||
// block access list now, before the call's sender-balance and
|
||||
// call-stack-depth checks.
|
||||
recordDelegationAccess(evm, target)
|
||||
}
|
||||
|
||||
// Compute and charge state gas (new account creation) AFTER regular gas.
|
||||
|
|
|
|||
|
|
@ -281,6 +281,9 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
|
|||
if config.OverrideOsaka != nil {
|
||||
overrides.OverrideOsaka = config.OverrideOsaka
|
||||
}
|
||||
if config.OverrideAmsterdam != nil {
|
||||
overrides.OverrideAmsterdam = config.OverrideAmsterdam
|
||||
}
|
||||
if config.OverrideBPO1 != nil {
|
||||
overrides.OverrideBPO1 = config.OverrideBPO1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -197,7 +197,6 @@ func newBenchmarkBlobEnv(b *testing.B, blobCount int, version byte, fork benchFo
|
|||
config.BlobScheduleConfig = ¶ms.BlobScheduleConfig{
|
||||
Cancun: ¶ms.BlobConfig{Target: 6, Max: 128, UpdateFraction: 3338477},
|
||||
Prague: ¶ms.BlobConfig{Target: 6, Max: 128, UpdateFraction: 5007716},
|
||||
Osaka: ¶ms.BlobConfig{Target: 6, Max: 128, UpdateFraction: 5007716},
|
||||
}
|
||||
// Configure fork times based on requested fork
|
||||
switch fork {
|
||||
|
|
|
|||
|
|
@ -206,6 +206,9 @@ type Config struct {
|
|||
// OverrideOsaka (TODO: remove after the fork)
|
||||
OverrideOsaka *uint64 `toml:",omitempty"`
|
||||
|
||||
// OverrideAmsterdam (TODO: remove after the fork)
|
||||
OverrideAmsterdam *uint64 `toml:",omitempty"`
|
||||
|
||||
// OverrideBPO1 (TODO: remove after the fork)
|
||||
OverrideBPO1 *uint64 `toml:",omitempty"`
|
||||
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
|||
RPCEVMTimeout time.Duration
|
||||
RPCTxFeeCap float64
|
||||
OverrideOsaka *uint64 `toml:",omitempty"`
|
||||
OverrideAmsterdam *uint64 `toml:",omitempty"`
|
||||
OverrideBPO1 *uint64 `toml:",omitempty"`
|
||||
OverrideBPO2 *uint64 `toml:",omitempty"`
|
||||
OverrideUBT *uint64 `toml:",omitempty"`
|
||||
|
|
@ -119,6 +120,7 @@ func (c Config) MarshalTOML() (interface{}, error) {
|
|||
enc.RPCEVMTimeout = c.RPCEVMTimeout
|
||||
enc.RPCTxFeeCap = c.RPCTxFeeCap
|
||||
enc.OverrideOsaka = c.OverrideOsaka
|
||||
enc.OverrideAmsterdam = c.OverrideAmsterdam
|
||||
enc.OverrideBPO1 = c.OverrideBPO1
|
||||
enc.OverrideBPO2 = c.OverrideBPO2
|
||||
enc.OverrideUBT = c.OverrideUBT
|
||||
|
|
@ -178,6 +180,7 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
|||
RPCEVMTimeout *time.Duration
|
||||
RPCTxFeeCap *float64
|
||||
OverrideOsaka *uint64 `toml:",omitempty"`
|
||||
OverrideAmsterdam *uint64 `toml:",omitempty"`
|
||||
OverrideBPO1 *uint64 `toml:",omitempty"`
|
||||
OverrideBPO2 *uint64 `toml:",omitempty"`
|
||||
OverrideUBT *uint64 `toml:",omitempty"`
|
||||
|
|
@ -330,6 +333,9 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
|
|||
if dec.OverrideOsaka != nil {
|
||||
c.OverrideOsaka = dec.OverrideOsaka
|
||||
}
|
||||
if dec.OverrideAmsterdam != nil {
|
||||
c.OverrideAmsterdam = dec.OverrideAmsterdam
|
||||
}
|
||||
if dec.OverrideBPO1 != nil {
|
||||
c.OverrideBPO1 = dec.OverrideBPO1
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ func (h *ethHandler) Handle(peer *eth.Peer, packet eth.Packet) error {
|
|||
// handleTransactions marks all given transactions as known to the peer
|
||||
// and performs basic validations.
|
||||
func handleTransactions(peer *eth.Peer, list []*types.Transaction, directBroadcast bool) error {
|
||||
seen := make(map[common.Hash]struct{})
|
||||
seen := make(map[common.Hash]struct{}, len(list))
|
||||
for _, tx := range list {
|
||||
if tx.Type() == types.BlobTxType {
|
||||
if directBroadcast {
|
||||
|
|
|
|||
|
|
@ -745,7 +745,7 @@ func testGetBlockAccessLists(t *testing.T, protocol uint) {
|
|||
|
||||
var (
|
||||
hashes []common.Hash
|
||||
expect rlp.RawList[RawBlockAccessList]
|
||||
expect rlp.RawList[rlp.RawValue]
|
||||
)
|
||||
for i := uint64(0); i <= backend.chain.CurrentBlock().Number.Uint64(); i++ {
|
||||
block := backend.chain.GetBlockByNumber(i)
|
||||
|
|
@ -774,6 +774,42 @@ func testGetBlockAccessLists(t *testing.T, protocol uint) {
|
|||
}
|
||||
}
|
||||
|
||||
// TestBlockAccessListsUnavailableDecode checks that a BlockAccessLists response
|
||||
// containing the EIP-8159 unavailability marker (RLP empty string).
|
||||
func TestBlockAccessListsUnavailableDecode(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
balRaw := makeTestBAL(t, common.Address{0x11})
|
||||
|
||||
// Assemble a response the way the serving side does, with the middle
|
||||
// entry signaled as unavailable.
|
||||
var list rlp.RawList[rlp.RawValue]
|
||||
list.AppendRaw(balRaw)
|
||||
list.AppendRaw(rlp.EmptyString)
|
||||
list.AppendRaw(balRaw)
|
||||
|
||||
enc, err := rlp.EncodeToBytes(&BlockAccessListPacket{RequestId: 42, List: list})
|
||||
if err != nil {
|
||||
t.Fatalf("failed to encode packet: %v", err)
|
||||
}
|
||||
var packet BlockAccessListPacket
|
||||
if err := rlp.DecodeBytes(enc, &packet); err != nil {
|
||||
t.Fatalf("failed to decode packet: %v", err)
|
||||
}
|
||||
bals, err := packet.List.Items()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to decode BAL entries: %v", err)
|
||||
}
|
||||
if len(bals) != 3 {
|
||||
t.Fatalf("wrong entry count: got %d, want 3", len(bals))
|
||||
}
|
||||
for i, want := range [][]byte{balRaw, rlp.EmptyString, balRaw} {
|
||||
if !bytes.Equal(bals[i], want) {
|
||||
t.Errorf("entry %d mismatch: got %x, want %x", i, bals[i], want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type decoder struct {
|
||||
msg []byte
|
||||
}
|
||||
|
|
|
|||
|
|
@ -774,10 +774,10 @@ func handleGetBlockAccessLists(backend Backend, msg Decoder, peer *Peer) error {
|
|||
|
||||
// serviceGetBlockAccessListsQuery assembles the response to a BAL query.
|
||||
// Unavailable BALs are returned as empty list entries.
|
||||
func serviceGetBlockAccessListsQuery(chain *core.BlockChain, query GetBlockAccessListsRequest) rlp.RawList[RawBlockAccessList] {
|
||||
func serviceGetBlockAccessListsQuery(chain *core.BlockChain, query GetBlockAccessListsRequest) rlp.RawList[rlp.RawValue] {
|
||||
var (
|
||||
bytes int
|
||||
bals rlp.RawList[RawBlockAccessList]
|
||||
bals rlp.RawList[rlp.RawValue]
|
||||
)
|
||||
for _, hash := range query {
|
||||
if bytes >= softResponseLimit || bals.Len() >= maxBALsServe {
|
||||
|
|
@ -815,7 +815,12 @@ func handleBlockAccessLists(backend Backend, msg Decoder, peer *Peer) error {
|
|||
metadata := func() interface{} {
|
||||
hashes := make([]common.Hash, len(bals))
|
||||
for i := range bals {
|
||||
hashes[i] = crypto.Keccak256Hash(bals[i].Bytes())
|
||||
// Unavailable BALs (signaled by the empty string) are marked
|
||||
// with the zero hash
|
||||
if bytes.Equal(bals[i], rlp.EmptyString) {
|
||||
continue
|
||||
}
|
||||
hashes[i] = crypto.Keccak256Hash(bals[i])
|
||||
}
|
||||
return hashes
|
||||
}
|
||||
|
|
|
|||
|
|
@ -296,7 +296,7 @@ func (p *Peer) ReplyReceiptsRLP70(id uint64, receipts rlp.RawList[*ReceiptList],
|
|||
}
|
||||
|
||||
// ReplyBlockAccessLists is the response to GetBlockAccessLists (EIP-8159).
|
||||
func (p *Peer) ReplyBlockAccessLists(id uint64, list rlp.RawList[RawBlockAccessList]) error {
|
||||
func (p *Peer) ReplyBlockAccessLists(id uint64, list rlp.RawList[rlp.RawValue]) error {
|
||||
return p2p.Send(p.rw, BlockAccessListsMsg, &BlockAccessListPacket{
|
||||
RequestId: id,
|
||||
List: list,
|
||||
|
|
|
|||
|
|
@ -24,7 +24,6 @@ import (
|
|||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/forkid"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/core/types/bal"
|
||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
|
@ -337,15 +336,14 @@ type GetBlockAccessListsPacket struct {
|
|||
GetBlockAccessListsRequest
|
||||
}
|
||||
|
||||
type RawBlockAccessList struct {
|
||||
rlp.RawList[bal.AccountAccess]
|
||||
}
|
||||
|
||||
type BlockAccessListResponse []RawBlockAccessList
|
||||
// BlockAccessListResponse holds one raw entry per requested hash. Entries are
|
||||
// kept as raw values because, per EIP-8159, the RLP empty string signals an
|
||||
// unavailable BAL (an empty list is itself a valid BAL).
|
||||
type BlockAccessListResponse []rlp.RawValue
|
||||
|
||||
type BlockAccessListPacket struct {
|
||||
RequestId uint64
|
||||
List rlp.RawList[RawBlockAccessList]
|
||||
List rlp.RawList[rlp.RawValue]
|
||||
}
|
||||
|
||||
func (*StatusPacket) Name() string { return "Status" }
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ func (eth *Ethereum) stateAtTransaction(ctx context.Context, block *types.Block,
|
|||
defer evm.Release()
|
||||
|
||||
// Run pre-execution system calls
|
||||
core.PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), eth.blockchain.Config(), evm, block.Number(), block.Time())
|
||||
core.PreExecution(ctx, block.BeaconRoot(), parent.Header(), eth.blockchain.Config(), evm, block.Number(), block.Time())
|
||||
|
||||
if txIndex == 0 && len(block.Transactions()) == 0 {
|
||||
return nil, context, statedb, release, nil
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue