This commit is contained in:
Marius van der Wijden 2026-05-21 19:01:16 +00:00 committed by GitHub
commit 3d555a4896
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
43 changed files with 1565 additions and 449 deletions

View file

@ -10,7 +10,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types/bal"
) )
var _ = (*executableDataMarshaling)(nil) var _ = (*executableDataMarshaling)(nil)
@ -36,7 +35,7 @@ func (e ExecutableData) MarshalJSON() ([]byte, error) {
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"` SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"`
BlockAccessList *bal.BlockAccessList `json:"blockAccessList,omitempty"` BlockAccessList hexutil.Bytes `json:"blockAccessList,omitempty"`
} }
var enc ExecutableData var enc ExecutableData
enc.ParentHash = e.ParentHash enc.ParentHash = e.ParentHash
@ -87,7 +86,7 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"` BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed"`
ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"` ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas"`
SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"` SlotNumber *hexutil.Uint64 `json:"slotNumber,omitempty"`
BlockAccessList *bal.BlockAccessList `json:"blockAccessList,omitempty"` BlockAccessList *hexutil.Bytes `json:"blockAccessList,omitempty"`
} }
var dec ExecutableData var dec ExecutableData
if err := json.Unmarshal(input, &dec); err != nil { if err := json.Unmarshal(input, &dec); err != nil {
@ -165,7 +164,7 @@ func (e *ExecutableData) UnmarshalJSON(input []byte) error {
e.SlotNumber = (*uint64)(dec.SlotNumber) e.SlotNumber = (*uint64)(dec.SlotNumber)
} }
if dec.BlockAccessList != nil { if dec.BlockAccessList != nil {
e.BlockAccessList = dec.BlockAccessList e.BlockAccessList = *dec.BlockAccessList
} }
return nil return nil
} }

View file

@ -17,6 +17,7 @@
package engine package engine
import ( import (
"bytes"
"fmt" "fmt"
"math/big" "math/big"
"slices" "slices"
@ -24,8 +25,9 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil" "github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/types/bal" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
) )
@ -101,7 +103,7 @@ type ExecutableData struct {
BlobGasUsed *uint64 `json:"blobGasUsed"` BlobGasUsed *uint64 `json:"blobGasUsed"`
ExcessBlobGas *uint64 `json:"excessBlobGas"` ExcessBlobGas *uint64 `json:"excessBlobGas"`
SlotNumber *uint64 `json:"slotNumber,omitempty"` SlotNumber *uint64 `json:"slotNumber,omitempty"`
BlockAccessList *bal.BlockAccessList `json:"blockAccessList,omitempty"` BlockAccessList hexutil.Bytes `json:"blockAccessList,omitempty"`
} }
// JSON type overrides for executableData. // JSON type overrides for executableData.
@ -314,13 +316,14 @@ func ExecutableDataToBlockNoHash(data ExecutableData, versionedHashes []common.H
} }
// If Amsterdam is enabled, data.BlockAccessList is always non-nil, // If Amsterdam is enabled, data.BlockAccessList is always non-nil,
// even for empty blocks with no state transitions. // even for empty blocks with no state transitions. The wire format is
// the RLP-encoded access list; the header hash is keccak256(rlp).
// //
// If Amsterdam is not enabled yet, blockAccessListHash is expected // If Amsterdam is not enabled yet, blockAccessListHash is expected
// to be nil. // to be nil.
var blockAccessListHash *common.Hash var blockAccessListHash *common.Hash
if data.BlockAccessList != nil { if data.BlockAccessList != nil {
hash := data.BlockAccessList.Hash() hash := crypto.Keccak256Hash(data.BlockAccessList)
blockAccessListHash = &hash blockAccessListHash = &hash
} }
header := &types.Header{ header := &types.Header{
@ -372,7 +375,14 @@ func BlockToExecutableData(block *types.Block, fees *big.Int, sidecars []*types.
BlobGasUsed: block.BlobGasUsed(), BlobGasUsed: block.BlobGasUsed(),
ExcessBlobGas: block.ExcessBlobGas(), ExcessBlobGas: block.ExcessBlobGas(),
SlotNumber: block.SlotNumber(), SlotNumber: block.SlotNumber(),
BlockAccessList: block.AccessList(), }
// Per Engine API spec (Amsterdam): blockAccessList is the RLP-encoded
// access list, serialized as a hex string. Encode it to bytes here.
if al := block.AccessList(); al != nil {
var buf bytes.Buffer
if err := rlp.Encode(&buf, al); err == nil {
data.BlockAccessList = buf.Bytes()
}
} }
// Add blobs. // Add blobs.

View file

@ -5,6 +5,11 @@
# https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0 # https://github.com/ethereum/execution-spec-tests/releases/download/v5.1.0
a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_develop.tar.gz a3192784375acec7eaec492799d5c5d0c47a2909a3cc40178898e4ecd20cc416 fixtures_develop.tar.gz
# version:spec-tests-bal v7.2.0
# https://github.com/ethereum/execution-specs/releases
# https://github.com/ethereum/execution-specs/releases/download/tests-bal%40v7.2.0
fc1d9ae174cdd5db789068839999e6f83666cc79f7dac36e973d7616d9a2e2cf fixtures_bal.tar.gz
# version:golang 1.25.10 # version:golang 1.25.10
# https://go.dev/dl/ # https://go.dev/dl/
20cf04a92e5af99748e341bc8996fa28090c9ac98765fa115ec5ddf41d7af41d go1.25.10.src.tar.gz 20cf04a92e5af99748e341bc8996fa28090c9ac98765fa115ec5ddf41d7af41d go1.25.10.src.tar.gz

View file

@ -164,6 +164,9 @@ var (
// This is where the tests should be unpacked. // This is where the tests should be unpacked.
executionSpecTestsDir = "tests/spec-tests" executionSpecTestsDir = "tests/spec-tests"
// This is where the bal-specific release of the tests should be unpacked.
executionSpecTestsBALDir = "tests/spec-tests-bal"
) )
var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin")) var GOBIN, _ = filepath.Abs(filepath.Join("build", "bin"))
@ -402,6 +405,7 @@ func doTest(cmdline []string) {
// Get test fixtures. // Get test fixtures.
if !*short { if !*short {
downloadSpecTestFixtures(csdb, *cachedir) downloadSpecTestFixtures(csdb, *cachedir)
downloadBALSpecTestFixtures(csdb, *cachedir)
} }
// Configure the toolchain. // Configure the toolchain.
@ -467,6 +471,20 @@ func downloadSpecTestFixtures(csdb *download.ChecksumDB, cachedir string) string
return filepath.Join(cachedir, base) return filepath.Join(cachedir, base)
} }
// downloadBALSpecTestFixtures downloads and extracts the bal-specific execution-spec-tests fixtures.
func downloadBALSpecTestFixtures(csdb *download.ChecksumDB, cachedir string) string {
ext := ".tar.gz"
base := "fixtures_bal"
archivePath := filepath.Join(cachedir, base+ext)
if err := csdb.DownloadFileFromKnownURL(archivePath); err != nil {
log.Fatal(err)
}
if err := build.ExtractArchive(archivePath, executionSpecTestsBALDir); err != nil {
log.Fatal(err)
}
return filepath.Join(cachedir, base)
}
// doCheckGenerate ensures that re-generating generated files does not cause // doCheckGenerate ensures that re-generating generated files does not cause
// any mutations in the source file tree. // any mutations in the source file tree.
func doCheckGenerate() { func doCheckGenerate() {

View file

@ -133,7 +133,7 @@ func Transaction(ctx *cli.Context) error {
} }
// Check intrinsic gas // Check intrinsic gas
rules := chainConfig.Rules(common.Big0, true, 0) rules := chainConfig.Rules(common.Big0, true, 0)
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam) cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte)
if err != nil { if err != nil {
r.Error = err r.Error = err
results = append(results, r) results = append(results, r)

View file

@ -95,6 +95,7 @@ var (
utils.BinTrieGroupDepthFlag, utils.BinTrieGroupDepthFlag,
utils.LightKDFFlag, utils.LightKDFFlag,
utils.EthRequiredBlocksFlag, utils.EthRequiredBlocksFlag,
utils.BALExecutionModeFlag,
utils.LegacyWhitelistFlag, // deprecated utils.LegacyWhitelistFlag, // deprecated
utils.CacheFlag, utils.CacheFlag,
utils.CacheDatabaseFlag, utils.CacheDatabaseFlag,

View file

@ -243,6 +243,11 @@ var (
Usage: "Comma separated block number-to-hash mappings to require for peering (<number>=<hash>)", Usage: "Comma separated block number-to-hash mappings to require for peering (<number>=<hash>)",
Category: flags.EthCategory, Category: flags.EthCategory,
} }
BALExecutionModeFlag = &cli.StringFlag{
Name: "bal.executionmode",
Usage: "EIP-7928 block-access-list execution mode (no-op placeholder)",
Category: flags.EthCategory,
}
BloomFilterSizeFlag = &cli.Uint64Flag{ BloomFilterSizeFlag = &cli.Uint64Flag{
Name: "bloomfilter.size", Name: "bloomfilter.size",
Usage: "Megabytes of memory allocated to bloom-filter for pruning", Usage: "Megabytes of memory allocated to bloom-filter for pruning",

View file

@ -89,7 +89,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
data := make([]byte, nbytes) data := make([]byte, nbytes)
return func(i int, gen *BlockGen) { return func(i int, gen *BlockGen) {
toaddr := common.Address{} toaddr := common.Address{}
cost, _ := IntrinsicGas(data, nil, nil, false, false, false, false, false) cost, _ := IntrinsicGas(data, nil, nil, false, params.Rules{}, params.CostPerStateByte)
signer := gen.Signer() signer := gen.Signer()
gasPrice := big.NewInt(0) gasPrice := big.NewInt(0)
if gen.header.BaseFee != nil { if gen.header.BaseFee != nil {

View file

@ -64,12 +64,12 @@ var (
func TestProcessUBT(t *testing.T) { func TestProcessUBT(t *testing.T) {
var ( var (
code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`) code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`)
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, true, true, true, false) intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
// A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness // A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness
// will not contain that copied data. // will not contain that copied data.
// Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985 // Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985
codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`) codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`)
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, true, true, true, false) intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
signer = types.LatestSigner(testUBTChainConfig) signer = types.LatestSigner(testUBTChainConfig)
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291") testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain
@ -201,7 +201,7 @@ func TestProcessParentBlockHash(t *testing.T) {
if isUBT { if isUBT {
chainConfig = testUBTChainConfig chainConfig = testUBTChainConfig
} }
vmContext := NewEVMBlockContext(header, nil, new(common.Address)) vmContext := NewEVMBlockContext(header, &BlockChain{chainConfig: chainConfig}, new(common.Address))
evm := vm.NewEVM(vmContext, statedb, chainConfig, vm.Config{}) evm := vm.NewEVM(vmContext, statedb, chainConfig, vm.Config{})
ProcessParentBlockHash(header.ParentHash, evm, bal.NewConstructionBlockAccessList()) ProcessParentBlockHash(header.ParentHash, evm, bal.NewConstructionBlockAccessList())
} }

View file

@ -2301,6 +2301,12 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // The time spent on block validation stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // The time spent on block validation
stats.CrossValidation = xvtime // The time spent on stateless cross validation stats.CrossValidation = xvtime // The time spent on stateless cross validation
// Attach the computed block access list so it gets persisted alongside the
// block. The validator has already verified the hash matches the header.
if res.Bal != nil && block.AccessList() == nil {
block = block.WithAccessListUnsafe(res.Bal.ToEncodingObj())
}
// Write the block to the chain and get the status. // Write the block to the chain and get the status.
var status WriteStatus var status WriteStatus
if config.WriteState { if config.WriteState {

View file

@ -68,18 +68,19 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
} }
return vm.BlockContext{ return vm.BlockContext{
CanTransfer: CanTransfer, CanTransfer: CanTransfer,
Transfer: Transfer, Transfer: Transfer,
GetHash: GetHashFn(header, chain), GetHash: GetHashFn(header, chain),
Coinbase: beneficiary, Coinbase: beneficiary,
BlockNumber: new(big.Int).Set(header.Number), BlockNumber: new(big.Int).Set(header.Number),
Time: header.Time, Time: header.Time,
Difficulty: new(big.Int).Set(header.Difficulty), Difficulty: new(big.Int).Set(header.Difficulty),
BaseFee: baseFee, BaseFee: baseFee,
BlobBaseFee: blobBaseFee, BlobBaseFee: blobBaseFee,
GasLimit: header.GasLimit, GasLimit: header.GasLimit,
Random: random, Random: random,
SlotNum: slotNum, SlotNum: slotNum,
CostPerStateByte: params.CostPerStateByte,
} }
} }

View file

@ -27,6 +27,11 @@ type GasPool struct {
remaining uint64 remaining uint64
initial uint64 initial uint64
cumulativeUsed uint64 cumulativeUsed uint64
// After 8037 Block gas used is
// max(cumulativeRegular, cumulativeState).
cumulativeRegular uint64
cumulativeState uint64
} }
// NewGasPool initializes the gasPool with the given amount. // NewGasPool initializes the gasPool with the given amount.
@ -37,9 +42,9 @@ func NewGasPool(amount uint64) *GasPool {
} }
} }
// SubGas deducts the given amount from the pool if enough gas is // CheckGasLegacy deducts the given amount from the pool if enough gas is
// available and returns an error otherwise. // available and returns an error otherwise.
func (gp *GasPool) SubGas(amount uint64) error { func (gp *GasPool) CheckGasLegacy(amount uint64) error {
if gp.remaining < amount { if gp.remaining < amount {
return ErrGasLimitReached return ErrGasLimitReached
} }
@ -47,41 +52,70 @@ func (gp *GasPool) SubGas(amount uint64) error {
return nil return nil
} }
// ReturnGas adds the refunded gas back to the pool and updates // CheckGasAmsterdam performs the EIP-8037 per-tx 2D block-inclusion check:
// the worst-case regular contribution must fit in the regular dimension and
// the worst-case state contribution must fit in the state dimension
func (gp *GasPool) CheckGasAmsterdam(regularReservation, stateReservation uint64) error {
if gp.initial-gp.cumulativeRegular < regularReservation {
return ErrGasLimitReached
}
if gp.initial-gp.cumulativeState < stateReservation {
return ErrGasLimitReached
}
return nil
}
// ChargeGasLegacy adds the refunded gas back to the pool and updates
// the cumulative gas usage accordingly. // the cumulative gas usage accordingly.
func (gp *GasPool) ReturnGas(returned uint64, gasUsed uint64) error { func (gp *GasPool) ChargeGasLegacy(returned uint64, gasUsed uint64) error {
if gp.remaining > math.MaxUint64-returned { if gp.remaining > math.MaxUint64-returned {
return fmt.Errorf("%w: remaining: %d, returned: %d", ErrGasLimitOverflow, gp.remaining, returned) return fmt.Errorf("%w: remaining: %d, returned: %d", ErrGasLimitOverflow, gp.remaining, returned)
} }
// The returned gas calculation differs across forks. // returned = purchased - remaining (refund included)
//
// - Pre-Amsterdam:
// returned = purchased - remaining (refund included)
//
// - Post-Amsterdam:
// returned = purchased - gasUsed (refund excluded)
gp.remaining += returned gp.remaining += returned
// gasUsed = max(txGasUsed - gasRefund, calldataFloorGasCost) // gasUsed = max(txGasUsed - gasRefund, calldataFloorGasCost)
// regardless of Amsterdam is activated or not.
gp.cumulativeUsed += gasUsed gp.cumulativeUsed += gasUsed
return nil return nil
} }
// ChargeGasAmsterdam calculates the new remaining gas in the pool after the
// execution of a message. Previously we subtracted and re-added gas to the
// gaspool. After Amsterdam we only check if we can include the transaction
// and charge the gaspool at the end.
func (gp *GasPool) ChargeGasAmsterdam(txRegular, txState, receiptGasUsed uint64) error {
gp.cumulativeRegular += txRegular
gp.cumulativeState += txState
gp.cumulativeUsed += receiptGasUsed
blockUsed := max(gp.cumulativeRegular, gp.cumulativeState)
if gp.initial < blockUsed {
return fmt.Errorf("%w: block gas overflow: initial %d, used %d (regular: %d, state: %d)",
ErrGasLimitReached, gp.initial, blockUsed, gp.cumulativeRegular, gp.cumulativeState)
}
// For tx inclusion, we only check if the regular dimension fits.
gp.remaining = gp.initial - gp.cumulativeRegular
return nil
}
// Gas returns the amount of gas remaining in the pool. // Gas returns the amount of gas remaining in the pool.
func (gp *GasPool) Gas() uint64 { func (gp *GasPool) Gas() uint64 {
return gp.remaining return gp.remaining
} }
// CumulativeUsed returns the amount of cumulative consumed gas (refunded included). // CumulativeUsed returns the cumulative gas consumed for receipt tracking.
func (gp *GasPool) CumulativeUsed() uint64 { func (gp *GasPool) CumulativeUsed() uint64 {
return gp.cumulativeUsed return gp.cumulativeUsed
} }
// Used returns the amount of consumed gas. // Used returns the amount of consumed gas.
func (gp *GasPool) Used() uint64 { func (gp *GasPool) Used() uint64 {
if gp.cumulativeRegular > 0 || gp.cumulativeState > 0 {
// After 8037 we return max(sum_regular, sum_state)
return max(gp.cumulativeRegular, gp.cumulativeState)
}
if gp.initial < gp.remaining { if gp.initial < gp.remaining {
panic("gas used underflow") panic(fmt.Sprintf("gas used underflow: %v %v", gp.initial, gp.remaining))
} }
return gp.initial - gp.remaining return gp.initial - gp.remaining
} }
@ -89,9 +123,11 @@ func (gp *GasPool) Used() uint64 {
// Snapshot returns the deep-copied object as the snapshot. // Snapshot returns the deep-copied object as the snapshot.
func (gp *GasPool) Snapshot() *GasPool { func (gp *GasPool) Snapshot() *GasPool {
return &GasPool{ return &GasPool{
initial: gp.initial, initial: gp.initial,
remaining: gp.remaining, remaining: gp.remaining,
cumulativeUsed: gp.cumulativeUsed, cumulativeUsed: gp.cumulativeUsed,
cumulativeRegular: gp.cumulativeRegular,
cumulativeState: gp.cumulativeState,
} }
} }
@ -100,6 +136,8 @@ func (gp *GasPool) Set(other *GasPool) {
gp.initial = other.initial gp.initial = other.initial
gp.remaining = other.remaining gp.remaining = other.remaining
gp.cumulativeUsed = other.cumulativeUsed gp.cumulativeUsed = other.cumulativeUsed
gp.cumulativeRegular = other.cumulativeRegular
gp.cumulativeState = other.cumulativeState
} }
func (gp *GasPool) String() string { func (gp *GasPool) String() string {

View file

@ -572,6 +572,30 @@ func (s *stateObject) Code() []byte {
return code return code
} }
// GetCommittedCode returns the contract code committed at the start of the
// current execution, ignoring any intra-execution SetCode modifications.
// Used by EIP-7702 authorization application to make refund decisions
// relative to the originally-committed delegation, matching the spirit of
// GetCommittedState for storage slots.
func (s *stateObject) GetCommittedCode() []byte {
// The account did not exist at the start of the current execution.
if s.origin == nil {
return nil
}
hash := common.BytesToHash(s.origin.CodeHash)
if hash == types.EmptyCodeHash {
return nil
}
// If the code has not been touched in this execution, the live cache
// already holds the committed code.
if !s.dirtyCode {
return s.Code()
}
// Code was modified within the current execution. Reach for the on-disk
// blob keyed by the origin hash.
return s.db.reader.Code(s.address, hash)
}
// CodeSize returns the size of the contract code associated with this object, // CodeSize returns the size of the contract code associated with this object,
// or zero if none. This method is an almost mirror of Code, but uses a cache // or zero if none. This method is an almost mirror of Code, but uses a cache
// inside the database to avoid loading codes seen recently. // inside the database to avoid loading codes seen recently.

View file

@ -397,6 +397,18 @@ func (s *StateDB) GetCodeHash(addr common.Address) common.Hash {
return common.Hash{} return common.Hash{}
} }
// GetCommittedCode returns the contract code committed at the start of the
// current execution, ignoring any in-progress SetCode mutations. Returns
// nil when the account had no code (or did not exist) prior to this
// execution.
func (s *StateDB) GetCommittedCode(addr common.Address) []byte {
stateObject := s.getStateObject(addr)
if stateObject != nil {
return stateObject.GetCommittedCode()
}
return nil
}
// GetState retrieves the value associated with the specific key. // GetState retrieves the value associated with the specific key.
func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash { func (s *StateDB) GetState(addr common.Address, hash common.Hash) common.Hash {
stateObject := s.getStateObject(addr) stateObject := s.getStateObject(addr)

View file

@ -75,6 +75,10 @@ func (s *hookedStateDB) GetCode(addr common.Address) []byte {
return s.inner.GetCode(addr) return s.inner.GetCode(addr)
} }
func (s *hookedStateDB) GetCommittedCode(addr common.Address) []byte {
return s.inner.GetCommittedCode(addr)
}
func (s *hookedStateDB) GetCodeSize(addr common.Address) int { func (s *hookedStateDB) GetCodeSize(addr common.Address) int {
return s.inner.GetCodeSize(addr) return s.inner.GetCodeSize(addr)
} }

View file

@ -277,9 +277,15 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM, blockAccessList
defer tracer.OnSystemCallEnd() defer tracer.OnSystemCallEnd()
} }
} }
// SYSTEM_MAX_SSTORES_PER_CALL = 16 is the upper bound on the number of
// new storage slots a single system call is expected to write.
//
// This value matches MAX_WITHDRAWAL_REQUESTS_PER_BLOCK (EIP-7002), the
// largest per-block bound across the existing system contracts.
stateBudget := params.SystemMaxSStoresPerCall * evm.Context.CostPerStateByte * params.StorageCreationSize
msg := &Message{ msg := &Message{
From: params.SystemAddress, From: params.SystemAddress,
GasLimit: 30_000_000, GasLimit: 30_000_000 + stateBudget,
GasPrice: uint256.NewInt(0), GasPrice: uint256.NewInt(0),
GasFeeCap: uint256.NewInt(0), GasFeeCap: uint256.NewInt(0),
GasTipCap: uint256.NewInt(0), GasTipCap: uint256.NewInt(0),
@ -290,7 +296,7 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM, blockAccessList
evm.StateDB.Prepare(evm.GetRules(), common.Address{}, common.Address{}, nil, nil, nil) evm.StateDB.Prepare(evm.GetRules(), common.Address{}, common.Address{}, nil, nil, nil)
evm.StateDB.SetTxContext(common.Hash{}, 0, 0) evm.StateDB.SetTxContext(common.Hash{}, 0, 0)
evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress) evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress)
_, _, _ = evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560) _, _, _ = evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000, stateBudget), common.U2560)
if evm.StateDB.AccessEvents() != nil { if evm.StateDB.AccessEvents() != nil {
evm.StateDB.AccessEvents().Merge(evm.AccessEvents) evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
} }
@ -306,9 +312,15 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM, blockAccessList *
defer tracer.OnSystemCallEnd() defer tracer.OnSystemCallEnd()
} }
} }
// SYSTEM_MAX_SSTORES_PER_CALL = 16 is the upper bound on the number of
// new storage slots a single system call is expected to write.
//
// This value matches MAX_WITHDRAWAL_REQUESTS_PER_BLOCK (EIP-7002), the
// largest per-block bound across the existing system contracts.
stateBudget := params.SystemMaxSStoresPerCall * evm.Context.CostPerStateByte * params.StorageCreationSize
msg := &Message{ msg := &Message{
From: params.SystemAddress, From: params.SystemAddress,
GasLimit: 30_000_000, GasLimit: 30_000_000 + stateBudget,
GasPrice: uint256.NewInt(0), GasPrice: uint256.NewInt(0),
GasFeeCap: uint256.NewInt(0), GasFeeCap: uint256.NewInt(0),
GasTipCap: uint256.NewInt(0), GasTipCap: uint256.NewInt(0),
@ -319,7 +331,7 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM, blockAccessList *
evm.StateDB.Prepare(evm.GetRules(), common.Address{}, common.Address{}, nil, nil, nil) evm.StateDB.Prepare(evm.GetRules(), common.Address{}, common.Address{}, nil, nil, nil)
evm.StateDB.SetTxContext(common.Hash{}, 0, 0) evm.StateDB.SetTxContext(common.Hash{}, 0, 0)
evm.StateDB.AddAddressToAccessList(params.HistoryStorageAddress) evm.StateDB.AddAddressToAccessList(params.HistoryStorageAddress)
_, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560) _, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000, stateBudget), common.U2560)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -348,9 +360,15 @@ func processRequestsSystemCall(requests *[][]byte, rules params.Rules, evm *vm.E
defer tracer.OnSystemCallEnd() defer tracer.OnSystemCallEnd()
} }
} }
// SYSTEM_MAX_SSTORES_PER_CALL = 16 is the upper bound on the number of
// new storage slots a single system call is expected to write.
//
// This value matches MAX_WITHDRAWAL_REQUESTS_PER_BLOCK (EIP-7002), the
// largest per-block bound across the existing system contracts.
stateBudget := params.SystemMaxSStoresPerCall * evm.Context.CostPerStateByte * params.StorageCreationSize
msg := &Message{ msg := &Message{
From: params.SystemAddress, From: params.SystemAddress,
GasLimit: 30_000_000, GasLimit: 30_000_000 + stateBudget,
GasPrice: uint256.NewInt(0), GasPrice: uint256.NewInt(0),
GasFeeCap: uint256.NewInt(0), GasFeeCap: uint256.NewInt(0),
GasTipCap: uint256.NewInt(0), GasTipCap: uint256.NewInt(0),
@ -360,7 +378,7 @@ func processRequestsSystemCall(requests *[][]byte, rules params.Rules, evm *vm.E
evm.StateDB.Prepare(rules, common.Address{}, common.Address{}, nil, nil, nil) evm.StateDB.Prepare(rules, common.Address{}, common.Address{}, nil, nil, nil)
evm.StateDB.SetTxContext(common.Hash{}, 0, blockAccessIndex) evm.StateDB.SetTxContext(common.Hash{}, 0, blockAccessIndex)
evm.StateDB.AddAddressToAccessList(addr) evm.StateDB.AddAddressToAccessList(addr)
ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560) ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000, stateBudget), common.U2560)
if evm.StateDB.AccessEvents() != nil { if evm.StateDB.AccessEvents() != nil {
evm.StateDB.AccessEvents().Merge(evm.AccessEvents) evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
} }

View file

@ -27,6 +27,7 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto/kzg4844" "github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -68,13 +69,27 @@ func (result *ExecutionResult) Revert() []byte {
} }
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data. // IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation, isHomestead, isEIP2028, isEIP3860, isAmsterdam bool) (vm.GasCosts, error) { func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation bool, rules params.Rules, costPerStateByte uint64) (vm.GasCosts, error) {
// Set the starting gas for the raw transaction // Set the starting gas for the raw transaction
var gas uint64 var gas vm.GasCosts
if isContractCreation && isHomestead { if isContractCreation && rules.IsHomestead {
gas = params.TxGasContractCreation if rules.IsAmsterdam {
gas.RegularGas = params.TxGas + params.CreateGasAmsterdam
gas.StateGas = params.AccountCreationSize * costPerStateByte
} else {
gas.RegularGas = params.TxGasContractCreation
}
} else { } else {
gas = params.TxGas gas.RegularGas = params.TxGas
}
// Add gas for authorizations
if authList != nil {
if rules.IsAmsterdam {
gas.RegularGas += uint64(len(authList)) * params.TxAuthTupleRegularGas
gas.StateGas += uint64(len(authList)) * (params.AuthorizationCreationSize + params.AccountCreationSize) * costPerStateByte
} else {
gas.RegularGas += uint64(len(authList)) * params.CallNewAccountGas
}
} }
dataLen := uint64(len(data)) dataLen := uint64(len(data))
// Bump the required gas by the amount of transactional data // Bump the required gas by the amount of transactional data
@ -85,59 +100,56 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
// Make sure we don't exceed uint64 for all data combinations // Make sure we don't exceed uint64 for all data combinations
nonZeroGas := params.TxDataNonZeroGasFrontier nonZeroGas := params.TxDataNonZeroGasFrontier
if isEIP2028 { if rules.IsIstanbul {
nonZeroGas = params.TxDataNonZeroGasEIP2028 nonZeroGas = params.TxDataNonZeroGasEIP2028
} }
if (math.MaxUint64-gas)/nonZeroGas < nz { if (math.MaxUint64-gas.RegularGas)/nonZeroGas < nz {
return vm.GasCosts{}, ErrGasUintOverflow return vm.GasCosts{}, ErrGasUintOverflow
} }
gas += nz * nonZeroGas gas.RegularGas += nz * nonZeroGas
if (math.MaxUint64-gas)/params.TxDataZeroGas < z { if (math.MaxUint64-gas.RegularGas)/params.TxDataZeroGas < z {
return vm.GasCosts{}, ErrGasUintOverflow return vm.GasCosts{}, ErrGasUintOverflow
} }
gas += z * params.TxDataZeroGas gas.RegularGas += z * params.TxDataZeroGas
if isContractCreation && isEIP3860 { if isContractCreation && rules.IsShanghai {
lenWords := toWordSize(dataLen) lenWords := toWordSize(dataLen)
if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords { if (math.MaxUint64-gas.RegularGas)/params.InitCodeWordGas < lenWords {
return vm.GasCosts{}, ErrGasUintOverflow return vm.GasCosts{}, ErrGasUintOverflow
} }
gas += lenWords * params.InitCodeWordGas gas.RegularGas += lenWords * params.InitCodeWordGas
} }
} }
if accessList != nil { if accessList != nil {
addresses := uint64(len(accessList)) addresses := uint64(len(accessList))
storageKeys := uint64(accessList.StorageKeys()) storageKeys := uint64(accessList.StorageKeys())
if (math.MaxUint64-gas)/params.TxAccessListAddressGas < addresses { if (math.MaxUint64-gas.RegularGas)/params.TxAccessListAddressGas < addresses {
return vm.GasCosts{}, ErrGasUintOverflow return vm.GasCosts{}, ErrGasUintOverflow
} }
gas += addresses * params.TxAccessListAddressGas gas.RegularGas += addresses * params.TxAccessListAddressGas
if (math.MaxUint64-gas)/params.TxAccessListStorageKeyGas < storageKeys { if (math.MaxUint64-gas.RegularGas)/params.TxAccessListStorageKeyGas < storageKeys {
return vm.GasCosts{}, ErrGasUintOverflow return vm.GasCosts{}, ErrGasUintOverflow
} }
gas += storageKeys * params.TxAccessListStorageKeyGas gas.RegularGas += storageKeys * params.TxAccessListStorageKeyGas
// EIP-7981: access list data is charged in addition to the base charge. // EIP-7981: access list data is charged in addition to the base charge.
if isAmsterdam { if rules.IsAmsterdam {
const ( const (
addressCost = common.AddressLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte addressCost = common.AddressLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
storageKeyCost = common.HashLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte storageKeyCost = common.HashLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
) )
if (math.MaxUint64-gas)/addressCost < addresses { if (math.MaxUint64-gas.RegularGas)/addressCost < addresses {
return vm.GasCosts{}, ErrGasUintOverflow return vm.GasCosts{}, ErrGasUintOverflow
} }
gas += addresses * addressCost gas.RegularGas += addresses * addressCost
if (math.MaxUint64-gas)/storageKeyCost < storageKeys { if (math.MaxUint64-gas.RegularGas)/storageKeyCost < storageKeys {
return vm.GasCosts{}, ErrGasUintOverflow return vm.GasCosts{}, ErrGasUintOverflow
} }
gas += storageKeys * storageKeyCost gas.RegularGas += storageKeys * storageKeyCost
} }
} }
if authList != nil { return gas, nil
gas += uint64(len(authList)) * params.CallNewAccountGas
}
return vm.GasCosts{RegularGas: gas}, nil
} }
// FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623). // FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623).
@ -364,6 +376,24 @@ func (st *stateTransition) to() common.Address {
return *st.msg.To return *st.msg.To
} }
// buyGas pre-pays gas from the sender's balance and initializes the
// transaction's gas budget. It is invoked at the tail of preCheck.
//
// The balance requirement is the worst-case ETH the tx may need to lock
// up: `msg.GasLimit × max(msg.GasPrice, msg.GasFeeCap) + msg.Value`,
// plus `blobGas × msg.BlobGasFeeCap` under Cancun. Insufficient balance
// returns ErrInsufficientFunds. After the check, the sender is actually
// debited `msg.GasLimit × msg.GasPrice` (plus `blobGas × blobBaseFee`
// under Cancun), the cap-vs-tip differential is settled at tx end.
//
// The gas budget is seeded into both `initialBudget` (frozen snapshot
// for tx-end accounting) and `gasRemaining` (live running balance):
//
// - Pre-Amsterdam: one-dimensional regular budget equal to
// `msg.GasLimit`; the state-gas reservoir is zero.
// - Amsterdam+ (EIP-8037): two-dimensional budget. Regular gas is
// capped at `MaxTxGas` (EIP-7825, 16_777_216); any excess from
// `msg.GasLimit` above that cap becomes the state-gas reservoir.
func (st *stateTransition) buyGas() error { func (st *stateTransition) buyGas() error {
mgval := new(uint256.Int).SetUint64(st.msg.GasLimit) mgval := new(uint256.Int).SetUint64(st.msg.GasLimit)
_, overflow := mgval.MulOverflow(mgval, st.msg.GasPrice) _, overflow := mgval.MulOverflow(mgval, st.msg.GasPrice)
@ -416,22 +446,42 @@ func (st *stateTransition) buyGas() error {
if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 { if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 {
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want) return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
} }
if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
return err // After Amsterdam we limit the regular gas to 16M, the data gas to the transaction limit
limit := st.msg.GasLimit
if st.evm.ChainConfig().IsAmsterdam(st.evm.Context.BlockNumber, st.evm.Context.Time) {
limit = min(st.msg.GasLimit, params.MaxTxGas)
} }
st.initialBudget = vm.NewGasBudget(limit, st.msg.GasLimit-limit)
st.gasRemaining = st.initialBudget.Copy()
if st.evm.Config.Tracer.HasGasHook() { if st.evm.Config.Tracer.HasGasHook() {
empty := vm.GasBudget{} st.evm.Config.Tracer.EmitGasChange(tracing.Gas{}, st.gasRemaining.AsTracing(), tracing.GasChangeTxInitialBalance)
initial := vm.NewGasBudget(st.msg.GasLimit)
st.evm.Config.Tracer.EmitGasChange(empty.AsTracing(), initial.AsTracing(), tracing.GasChangeTxInitialBalance)
} }
st.gasRemaining = vm.NewGasBudget(st.msg.GasLimit) // Deduct the gas cost from the sender's balance
st.initialBudget = st.gasRemaining.Copy()
st.state.SubBalance(st.msg.From, mgval, tracing.BalanceDecreaseGasBuy) st.state.SubBalance(st.msg.From, mgval, tracing.BalanceDecreaseGasBuy)
return nil return nil
} }
// preCheck performs all pre-execution validation that does not require
// the EVM to run, then ends by calling buyGas to lock in the gas budget.
// It returns a consensus error if any of the following fail:
//
// - Sender nonce matches state and is not at 2^64-1 (EIP-2681).
// - EIP-7825 per-tx gas-limit cap on Osaka chains pre-Amsterdam
// (the cap also bounds the regular dimension after Amsterdam, but
// it is enforced there via the two-dimensional budget in buyGas).
// - EIP-3607 sender-is-EOA, allowing accounts whose only code is an
// EIP-7702 delegation designator.
// - EIP-1559 fee-cap, tip-cap and base-fee constraints (London+).
// - Blob-tx structural checks: non-nil `To`, non-empty hash list,
// valid KZG versioned hashes, count below `BlobTxMaxBlobs` (Osaka+).
// - Blob fee-cap not below the current blob base fee (Cancun+).
// - EIP-7702 set-code-tx shape: non-nil `To` and non-empty
// authorization list.
//
// The SkipNonceChecks / SkipTransactionChecks / NoBaseFee flags bypass
// subsets of these checks for simulation paths (eth_call, eth_estimateGas).
func (st *stateTransition) preCheck() error { func (st *stateTransition) preCheck() error {
// Only check transactions that are not fake // Only check transactions that are not fake
msg := st.msg msg := st.msg
@ -449,11 +499,13 @@ func (st *stateTransition) preCheck() error {
msg.From.Hex(), stNonce) msg.From.Hex(), stNonce)
} }
} }
isOsaka := st.evm.ChainConfig().IsOsaka(st.evm.Context.BlockNumber, st.evm.Context.Time) var (
isAmsterdam := st.evm.ChainConfig().IsAmsterdam(st.evm.Context.BlockNumber, st.evm.Context.Time) isOsaka = st.evm.ChainConfig().IsOsaka(st.evm.Context.BlockNumber, st.evm.Context.Time)
isAmsterdam = st.evm.ChainConfig().IsAmsterdam(st.evm.Context.BlockNumber, st.evm.Context.Time)
)
if !msg.SkipTransactionChecks { if !msg.SkipTransactionChecks {
// Verify tx gas limit does not exceed EIP-7825 cap. // Verify tx gas limit does not exceed EIP-7825 cap.
if isOsaka && !isAmsterdam && msg.GasLimit > params.MaxTxGas { if !isAmsterdam && isOsaka && msg.GasLimit > params.MaxTxGas {
return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit) return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit)
} }
// Make sure the sender is an EOA // Make sure the sender is an EOA
@ -527,28 +579,72 @@ func (st *stateTransition) preCheck() error {
return st.buyGas() return st.buyGas()
} }
// execute will transition the state by applying the current message and // reserveBlockGasBudget checks if the remaining gas budget in the block pool is
// returning the evm execution result with following fields. // sufficient for including this transaction.
// func (st *stateTransition) reserveBlockGasBudget(rules params.Rules, gasLimit uint64, intrinsicCost vm.GasCosts) error {
// - used gas: total gas used (including gas being refunded) var err error
// - returndata: the returned data from evm if rules.IsAmsterdam {
// - concrete execution error: various EVM errors which abort the execution, e.g. // EIP-8037 per-tx 2D block-inclusion check. For each dimension,
// ErrOutOfGas, ErrExecutionReverted // the worst-case contribution is tx.gas minus the other
// // dimension's intrinsic (capped at MaxTxGas for the regular
// However if any consensus issue encountered, return the error directly with // dimension).
// nil evm execution result. regularReservation := gasLimit
func (st *stateTransition) execute() (*ExecutionResult, error) { if regularReservation > intrinsicCost.StateGas {
// First check this message satisfies all consensus rules before regularReservation -= intrinsicCost.StateGas
// applying the message. The rules include these clauses } else {
// regularReservation = 0
// 1. the nonce of the message caller is correct }
// 2. caller has enough balance to cover transaction fee(gaslimit * gasprice) regularReservation = min(regularReservation, params.MaxTxGas)
// 3. the amount of gas required is available in the block
// 4. the purchased gas is enough to cover intrinsic usage
// 5. there is no overflow when calculating intrinsic gas
// 6. caller has enough balance to cover asset transfer for **topmost** call
// Check clauses 1-3, buy gas if everything is correct stateReservation := gasLimit
if stateReservation > intrinsicCost.RegularGas {
stateReservation -= intrinsicCost.RegularGas
} else {
stateReservation = 0
}
err = st.gp.CheckGasAmsterdam(regularReservation, stateReservation)
} else {
err = st.gp.CheckGasLegacy(gasLimit)
}
return err
}
// execute transitions the state by applying the current message and
// returns the EVM execution result with the following fields:
//
// - used gas: total gas used, including gas refunded
// - peak used gas: maximum gas used before applying refunds
// - returndata: data returned by the EVM
// - execution error: EVM-level errors that abort execution, such as
// ErrOutOfGas or ErrExecutionReverted
//
// If a consensus error is encountered, it is returned directly with a
// nil EVM execution result.
func (st *stateTransition) execute() (*ExecutionResult, error) {
// The state-transition pipeline below runs in stages. Each stage may
// abort with a consensus error before the EVM is invoked:
//
// 1. preCheck: nonce, fee-cap, blob and EIP-7702 structural
// checks; ends by calling buyGas to debit the
// sender and seed the two-dimensional gas budget
// (EIP-8037).
// 2. Intrinsic: charges the intrinsic regular + state cost from
// the running budget with overflow detection.
// 3. Block pool: per-dimension inclusion reservation against the
// block gas pool (two-dimensional after Amsterdam,
// EIP-8037).
// 4. Floor pre: EIP-7623 calldata floor must fit in the gas allowance.
// 5. Top-call: run the top-most call, ensuring sender can cover
// the value transfer of the top call frame; init-code
// size respects the cap.
//
// After the EVM has run, the result path applies EIP-8037 state-gas
// refunds, the EIP-3529 regular-refund cap, and the EIP-7623 scalar
// floor (`tx_gas_used = max(tx_gas_used_after_refund, floor)`),
// returns leftover gas to the sender, settles the block pool and
// pays the coinbase tip.
// Stage 1: validate the message and pre-pay gas.
if err := st.preCheck(); err != nil { if err := st.preCheck(); err != nil {
return nil, err return nil, err
} }
@ -559,8 +655,11 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
contractCreation = msg.To == nil contractCreation = msg.To == nil
floorDataGas uint64 floorDataGas uint64
) )
// Check clauses 4-5, subtract intrinsic gas if everything is correct
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam) // Stage 2: charge intrinsic gas (with overflow detection inside
// IntrinsicGas). Under Amsterdam the cost is two-dimensional and
// Charge debits both regular and state in one step.
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules, st.evm.Context.CostPerStateByte)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -571,14 +670,33 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
if st.evm.Config.Tracer.HasGasHook() { if st.evm.Config.Tracer.HasGasHook() {
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas) st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas)
} }
// Gas limit suffices for the floor data cost (EIP-7623)
// Stage 3: reserve this tx's share of the block gas pool. Under
// Amsterdam this is a two-dimensional per-tx inclusion check; before
// Amsterdam it is a single scalar subtraction.
if err := st.reserveBlockGasBudget(rules, msg.GasLimit, cost); err != nil {
return nil, err
}
// Stage 4: 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 { if rules.IsPrague {
floorDataGas, err = FloorDataGas(rules, msg.Data, msg.AccessList) floorDataGas, err = FloorDataGas(rules, msg.Data, msg.AccessList)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Make sure the transaction has sufficient gas allowance to
// pay the floor cost.
if msg.GasLimit < floorDataGas { if msg.GasLimit < floorDataGas {
return nil, fmt.Errorf("%w: have %d, want %d", ErrFloorDataGas, msg.GasLimit, floorDataGas) return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, msg.GasLimit, floorDataGas)
}
// In Amsterdam, the transaction gas limit is allowed to exceed
// params.MaxTxGas, but the calldata floor cost is capped by it.
if rules.IsAmsterdam {
if max(cost.RegularGas, floorDataGas) > params.MaxTxGas {
return nil, fmt.Errorf("%w: regular intrisic cost %v, floor: %v", ErrFloorDataGas, cost.RegularGas, floorDataGas)
}
} }
} }
@ -590,7 +708,8 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
} }
} }
// Check clause 6 // Stage 5: top-call affordability, the sender must still be able
// to cover the value transfer of the top frame after gas pre-pay.
value := msg.Value value := msg.Value
if value == nil { if value == nil {
value = new(uint256.Int) value = new(uint256.Int)
@ -617,18 +736,15 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
vmerr error // vm errors do not effect consensus and are therefore not assigned to err vmerr error // vm errors do not effect consensus and are therefore not assigned to err
) )
if contractCreation { if contractCreation {
ret, _, st.gasRemaining, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining, value) var result vm.GasBudget
ret, _, result, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining.ForwardAll(), value)
st.gasRemaining.Absorb(result)
} else { } else {
// Increment the nonce for the next transaction. // Increment the nonce for the next transaction.
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall) st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)
// Apply EIP-7702 authorizations. // Apply EIP-7702 authorizations.
if msg.SetCodeAuthorizations != nil { st.applyAuthorizations(rules, msg.SetCodeAuthorizations)
for _, auth := range msg.SetCodeAuthorizations {
// Note errors are ignored, we simply skip invalid authorizations here.
st.applyAuthorization(&auth)
}
}
// Perform convenience warming of sender's delegation target. Although the // Perform convenience warming of sender's delegation target. Although the
// sender is already warmed in Prepare(..), it's possible a delegation to // sender is already warmed in Prepare(..), it's possible a delegation to
@ -638,43 +754,81 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
if addr, ok := types.ParseDelegation(st.state.GetCode(*msg.To)); ok { if addr, ok := types.ParseDelegation(st.state.GetCode(*msg.To)); ok {
st.state.AddAddressToAccessList(addr) st.state.AddAddressToAccessList(addr)
} }
// Execute the transaction's call. // Execute the transaction's call.
ret, st.gasRemaining, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining, value) var result vm.GasBudget
ret, result, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining.ForwardAll(), value)
st.gasRemaining.Absorb(result)
}
// If this was a failed contract creation, refund the account creation costs.
if rules.IsAmsterdam {
if vmerr != nil && contractCreation {
refund := params.AccountCreationSize * st.evm.Context.CostPerStateByte
st.gasRemaining.RefundState(refund)
}
} }
// Record the gas used excluding gas refunds. This value represents the actual // Record the gas used excluding gas refunds. This value represents the actual
// gas allowance required to complete execution. // gas allowance required to complete execution.
peakGasUsed := st.gasUsed() peakGasUsed := st.gasUsed()
peakRegular := st.gasRemaining.UsedRegularGas
// Compute refund counter, capped to a refund quotient. // Compute refund counter, capped to a refund quotient.
st.gasRemaining.Refund(st.calcRefund()) st.gasRemaining.RefundRegular(st.calcRefund())
if rules.IsPrague { if rules.IsPrague {
// After EIP-7623: Data-heavy transactions pay the floor gas. // EIP-7623 floor: tx_gas_used_after_refund = max(used, calldata_floor).
// Drain the leftover gas budget — regular first, then state — to bring
// gasUsed up to the floor. State must be drained too because a failed
// contract-creation top-level refund (line ~770) can move otherwise-spent
// gas back into the state reservoir, leaving RegularGas too small to
// satisfy the floor on its own.
if used := st.gasUsed(); used < floorDataGas { if used := st.gasUsed(); used < floorDataGas {
prior, _ := st.gasRemaining.Charge(vm.GasCosts{RegularGas: floorDataGas - used}) prior := st.gasRemaining
need := floorDataGas - used
if take := min(need, st.gasRemaining.RegularGas); take > 0 {
st.gasRemaining.RegularGas -= take
st.gasRemaining.UsedRegularGas += take
need -= take
}
if take := min(need, st.gasRemaining.StateGas); take > 0 {
st.gasRemaining.StateGas -= take
st.gasRemaining.UsedStateGas += int64(take)
need -= take
}
if need > 0 {
return nil, fmt.Errorf("insufficient gas for floor cost, remaining: %d", need)
}
if st.evm.Config.Tracer.HasGasHook() { if st.evm.Config.Tracer.HasGasHook() {
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxDataFloor) st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxDataFloor)
} }
} }
if peakGasUsed < floorDataGas { peakGasUsed = max(peakGasUsed, floorDataGas)
peakGasUsed = floorDataGas peakRegular = max(peakRegular, floorDataGas)
}
} }
// Return gas to the user
st.returnGas()
// Return gas to the gas pool returned := st.returnGas()
if rules.IsAmsterdam { if rules.IsAmsterdam {
// Refund is excluded for returning // EIP-8037: 2D gas accounting for Amsterdam.
err = st.gp.ReturnGas(st.initialBudget.RegularGas-peakGasUsed, st.gasUsed()) // st.gasRemaining.UsedRegularGas / UsedStateGas already include both
// the intrinsic charge (from st.gasRemaining.Charge(cost) above) and
// the per-frame exec contributions absorbed from evm.Call / evm.Create.
//
// UsedStateGas should never become negative in the top-most frame, since
// state gas refunds only occur when state creation is reverted within the
// same transaction, while clearing pre-existing state is never refunded.
var txState uint64
if st.gasRemaining.UsedStateGas >= 0 {
txState = uint64(st.gasRemaining.UsedStateGas)
} else {
log.Error("Negative top-most frame state gas usage", "amount", st.gasRemaining.UsedStateGas)
}
if err := st.gp.ChargeGasAmsterdam(peakRegular, txState, st.gasUsed()); err != nil {
return nil, err
}
} else { } else {
// Refund is included for returning if err = st.gp.ChargeGasLegacy(returned, st.gasUsed()); err != nil {
err = st.gp.ReturnGas(st.gasRemaining.RegularGas, st.gasUsed()) return nil, err
} }
if err != nil {
return nil, err
} }
effectiveTip := msg.GasPrice effectiveTip := msg.GasPrice
if rules.IsLondon { if rules.IsLondon {
@ -743,35 +897,114 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio
return authority, nil return authority, nil
} }
// applyAuthorizations applies every EIP-7702 code delegation in the tx and,
// under EIP-8037, reconciles the per-authority creation budget so that the
// total AuthorizationCreationSize state gas charged matches the net change
// in on-disk delegation bytes.
//
// Invalid authorizations are silently skipped (their auth-base intrinsic
// state gas remains charged, matching the pre-existing behavior).
func (st *stateTransition) applyAuthorizations(rules params.Rules, auths []types.SetCodeAuthorization) {
if len(auths) == 0 {
return
}
// Under EIP-8037 each authority can be billed at most one
// AuthorizationCreationSize. applyAuthorization records authorities it
// has billed; we reconcile after the loop by refunding any creation that
// was billed but whose final delegation state in this tx ended up empty
// (e.g., 0→a→0).
var billed map[common.Address]struct{}
if rules.IsAmsterdam {
billed = make(map[common.Address]struct{})
}
for _, auth := range auths {
// Errors are ignored — invalid authorizations are simply skipped.
st.applyAuthorization(rules, &auth, billed)
}
// End-of-loop reconciliation: a billed creation whose authority is no
// longer delegated wrote zero net bytes to disk, so the auth-base
// intrinsic state gas should not be retained.
for authority := range billed {
if _, isDelegated := types.ParseDelegation(st.state.GetCode(authority)); !isDelegated {
st.gasRemaining.RefundState(params.AuthorizationCreationSize * st.evm.Context.CostPerStateByte)
}
}
}
// applyAuthorization applies an EIP-7702 code delegation to the state. // applyAuthorization applies an EIP-7702 code delegation to the state.
func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization) error { //
// authBilledCreations, when non-nil, tracks the set of authorities for which
// this tx has been billed one AuthorizationCreationSize charge (the per-
// authority "first creation" budget). The caller is expected to do an
// end-of-loop pass over this set and refund any entry whose final delegation
// state ended up empty.
func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.SetCodeAuthorization, authBilledCreations map[common.Address]struct{}) error {
authority, err := st.validateAuthorization(auth) authority, err := st.validateAuthorization(auth)
if err != nil { if err != nil {
return err return err
} }
// If the account already exists in state, refund the new account cost // If the account already exists in state, refund the new account cost
// charged in the intrinsic calculation. // charged in the intrinsic calculation.
if st.state.Exist(authority) { if st.state.Exist(authority) {
st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas) if rules.IsAmsterdam {
// EIP-8037: refund account creation state gas to the reservoir.
st.gasRemaining.RefundState(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
} else {
st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas)
}
}
prevDelegation, isDelegated := types.ParseDelegation(st.state.GetCode(authority))
if rules.IsAmsterdam {
// EIP-8037: refund the auth-base state gas unless this auth is the
// first one in this tx to write delegation bytes to an authority
// whose committed code was empty. Refund when ANY of:
//
// - the authority was already delegated at the start of the tx
// (the 23 bytes are already accounted for in committed state,
// and any auth against it just re-writes them);
//
// - the auth is no-op / clearing (auth.Address == 0) — no bytes
// are written in this step at all;
//
// - we have already billed a creation for this authority in
// this tx (per-authority creation budget is 1).
//
// Modeling it this way mirrors the SSTORE "reset to original"
// pattern (EIP-2200 / EIP-3529) and avoids both the undercount in
// a→0→b (committed had delegation, second auth missed the refund)
// and the overcount in 0→a→0→c (each later auth was previously
// billed as a fresh creation). The remaining 0→a→0 case — a
// creation is billed and then undone within the same auth list —
// is handled by the caller's end-of-loop adjustment over
// authBilledCreations.
_, committedDelegated := types.ParseDelegation(st.state.GetCommittedCode(authority))
_, alreadyBilled := authBilledCreations[authority]
if committedDelegated || alreadyBilled || auth.Address == (common.Address{}) {
st.gasRemaining.RefundState(params.AuthorizationCreationSize * st.evm.Context.CostPerStateByte)
} else {
authBilledCreations[authority] = struct{}{}
}
} }
// Update nonce and account code. // Update nonce and account code.
st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization) st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization)
// Delegation to zero address means clear.
if auth.Address == (common.Address{}) { if auth.Address == (common.Address{}) {
// Delegation to zero address means clear. if isDelegated {
st.state.SetCode(authority, nil, tracing.CodeChangeAuthorizationClear) st.state.SetCode(authority, nil, tracing.CodeChangeAuthorizationClear)
}
return nil return nil
} }
// Install delegation to auth.Address if the delegation changed
// Otherwise install delegation to auth.Address. if !isDelegated || auth.Address != prevDelegation {
st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization) st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization)
}
return nil return nil
} }
// calcRefund computes refund counter, capped to a refund quotient. // calcRefund computes refund counter, capped to a refund quotient.
func (st *stateTransition) calcRefund() vm.GasBudget { func (st *stateTransition) calcRefund() uint64 {
var refund uint64 var refund uint64
if !st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) { if !st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
// Before EIP-3529: refunds were capped to gasUsed / 2 // Before EIP-3529: refunds were capped to gasUsed / 2
@ -789,21 +1022,20 @@ func (st *stateTransition) calcRefund() vm.GasBudget {
st.evm.Config.Tracer.EmitGasChange(st.gasRemaining.AsTracing(), after.AsTracing(), tracing.GasChangeTxRefunds) st.evm.Config.Tracer.EmitGasChange(st.gasRemaining.AsTracing(), after.AsTracing(), tracing.GasChangeTxRefunds)
} }
return vm.NewGasBudget(refund) return refund
} }
// returnGas returns ETH for remaining gas, // returnGas returns ETH for remaining gas, exchanged at the original rate.
// exchanged at the original rate. func (st *stateTransition) returnGas() uint64 {
func (st *stateTransition) returnGas() { gas := st.gasRemaining.RegularGas + st.gasRemaining.StateGas
remaining := uint256.NewInt(st.gasRemaining.RegularGas) remaining := uint256.NewInt(gas)
remaining.Mul(remaining, st.msg.GasPrice) remaining.Mul(remaining, st.msg.GasPrice)
st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn) st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
if st.gasRemaining.RegularGas > 0 && st.evm.Config.Tracer.HasGasHook() { if !st.gasRemaining.IsZero() && st.evm.Config.Tracer.HasGasHook() {
after := st.gasRemaining st.evm.Config.Tracer.EmitGasChange(st.gasRemaining.AsTracing(), tracing.Gas{}, tracing.GasChangeTxLeftOverReturned)
after.RegularGas = 0
st.evm.Config.Tracer.EmitGasChange(st.gasRemaining.AsTracing(), after.AsTracing(), tracing.GasChangeTxLeftOverReturned)
} }
return gas
} }
// gasUsed returns the amount of gas used up by the state transition. // gasUsed returns the amount of gas used up by the state transition.

View file

@ -155,50 +155,50 @@ func TestIntrinsicGas(t *testing.T) {
isEIP2028 bool isEIP2028 bool
isEIP3860 bool isEIP3860 bool
isAmsterdam bool isAmsterdam bool
want uint64 want vm.GasCosts
}{ }{
{ {
name: "frontier/empty-call", name: "frontier/empty-call",
want: params.TxGas, want: vm.GasCosts{RegularGas: params.TxGas},
}, },
{ {
name: "frontier/contract-creation-pre-homestead", name: "frontier/contract-creation-pre-homestead",
creation: true, creation: true,
isHomestead: false, isHomestead: false,
// pre-homestead, contract creation still uses TxGas // pre-homestead, contract creation still uses TxGas
want: params.TxGas, want: vm.GasCosts{RegularGas: params.TxGas},
}, },
{ {
name: "homestead/contract-creation", name: "homestead/contract-creation",
creation: true, creation: true,
isHomestead: true, isHomestead: true,
want: params.TxGasContractCreation, want: vm.GasCosts{RegularGas: params.TxGasContractCreation},
}, },
{ {
name: "frontier/non-zero-data", name: "frontier/non-zero-data",
data: bytes.Repeat([]byte{0xff}, 100), data: bytes.Repeat([]byte{0xff}, 100),
// 100 nz bytes * 68 (frontier) // 100 nz bytes * 68 (frontier)
want: params.TxGas + 100*params.TxDataNonZeroGasFrontier, want: vm.GasCosts{RegularGas: params.TxGas + 100*params.TxDataNonZeroGasFrontier},
}, },
{ {
name: "istanbul/non-zero-data", name: "istanbul/non-zero-data",
data: bytes.Repeat([]byte{0xff}, 100), data: bytes.Repeat([]byte{0xff}, 100),
isEIP2028: true, isEIP2028: true,
// 100 nz bytes * 16 (post-EIP2028) // 100 nz bytes * 16 (post-EIP2028)
want: params.TxGas + 100*params.TxDataNonZeroGasEIP2028, want: vm.GasCosts{RegularGas: params.TxGas + 100*params.TxDataNonZeroGasEIP2028},
}, },
{ {
name: "istanbul/zero-data", name: "istanbul/zero-data",
data: bytes.Repeat([]byte{0x00}, 100), data: bytes.Repeat([]byte{0x00}, 100),
isEIP2028: true, isEIP2028: true,
// 100 zero bytes * 4 // 100 zero bytes * 4
want: params.TxGas + 100*params.TxDataZeroGas, want: vm.GasCosts{RegularGas: params.TxGas + 100*params.TxDataZeroGas},
}, },
{ {
name: "istanbul/mixed-data", name: "istanbul/mixed-data",
data: append(bytes.Repeat([]byte{0x00}, 50), bytes.Repeat([]byte{0xff}, 50)...), data: append(bytes.Repeat([]byte{0x00}, 50), bytes.Repeat([]byte{0xff}, 50)...),
isEIP2028: true, isEIP2028: true,
want: params.TxGas + 50*params.TxDataZeroGas + 50*params.TxDataNonZeroGasEIP2028, want: vm.GasCosts{RegularGas: params.TxGas + 50*params.TxDataZeroGas + 50*params.TxDataNonZeroGasEIP2028},
}, },
{ {
name: "shanghai/init-code-word-gas", name: "shanghai/init-code-word-gas",
@ -208,7 +208,7 @@ func TestIntrinsicGas(t *testing.T) {
isEIP2028: true, isEIP2028: true,
isEIP3860: true, isEIP3860: true,
// TxGasContractCreation + 64 zero bytes * 4 + 2 words * 2 // TxGasContractCreation + 64 zero bytes * 4 + 2 words * 2
want: params.TxGasContractCreation + 64*params.TxDataZeroGas + 2*params.InitCodeWordGas, want: vm.GasCosts{RegularGas: params.TxGasContractCreation + 64*params.TxDataZeroGas + 2*params.InitCodeWordGas},
}, },
{ {
name: "shanghai/init-code-non-multiple-of-32", name: "shanghai/init-code-non-multiple-of-32",
@ -217,7 +217,7 @@ func TestIntrinsicGas(t *testing.T) {
isHomestead: true, isHomestead: true,
isEIP2028: true, isEIP2028: true,
isEIP3860: true, isEIP3860: true,
want: params.TxGasContractCreation + 33*params.TxDataZeroGas + 2*params.InitCodeWordGas, want: vm.GasCosts{RegularGas: params.TxGasContractCreation + 33*params.TxDataZeroGas + 2*params.InitCodeWordGas},
}, },
{ {
name: "berlin/access-list", name: "berlin/access-list",
@ -227,7 +227,7 @@ func TestIntrinsicGas(t *testing.T) {
}, },
isEIP2028: true, isEIP2028: true,
// 2 addrs * 2400 + 3 keys * 1900 // 2 addrs * 2400 + 3 keys * 1900
want: params.TxGas + 2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas, want: vm.GasCosts{RegularGas: params.TxGas + 2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas},
}, },
{ {
name: "amsterdam/access-list-extra-cost", name: "amsterdam/access-list-extra-cost",
@ -238,9 +238,9 @@ func TestIntrinsicGas(t *testing.T) {
isEIP2028: true, isEIP2028: true,
isAmsterdam: true, isAmsterdam: true,
// base access-list charge + EIP-7981 extra // base access-list charge + EIP-7981 extra
want: params.TxGas + want: vm.GasCosts{RegularGas: params.TxGas +
2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas + 2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas +
2*amsterdamAddressCost + 3*amsterdamStorageKeyCost, 2*amsterdamAddressCost + 3*amsterdamStorageKeyCost},
}, },
{ {
name: "prague/auth-list", name: "prague/auth-list",
@ -250,8 +250,54 @@ func TestIntrinsicGas(t *testing.T) {
{Address: addr1}, {Address: addr1},
}, },
isEIP2028: true, isEIP2028: true,
// 3 auths * 25000 // 3 auths * 25000 (pre-Amsterdam: CallNewAccountGas per auth tuple)
want: params.TxGas + 3*params.CallNewAccountGas, want: vm.GasCosts{RegularGas: params.TxGas + 3*params.CallNewAccountGas},
},
{
name: "amsterdam/contract-creation-empty",
creation: true,
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.
want: vm.GasCosts{
RegularGas: params.TxGas + params.CreateGasAmsterdam,
StateGas: params.AccountCreationSize * params.CostPerStateByte,
},
},
{
name: "amsterdam/contract-creation-init-code",
data: bytes.Repeat([]byte{0x00}, 64), // 2 words of init code
creation: true,
isHomestead: true,
isEIP2028: true,
isEIP3860: true, // Shanghai gates init-code word gas
isAmsterdam: true,
want: vm.GasCosts{
RegularGas: params.TxGas + params.CreateGasAmsterdam +
64*params.TxDataZeroGas + 2*params.InitCodeWordGas,
StateGas: params.AccountCreationSize * params.CostPerStateByte,
},
},
{
name: "amsterdam/contract-creation-with-access-list",
data: bytes.Repeat([]byte{0xff}, 32), // 1 word of non-zero init code
accessList: types.AccessList{
{Address: addr1, StorageKeys: []common.Hash{key1}},
},
creation: true,
isHomestead: true,
isEIP2028: true,
isEIP3860: true,
isAmsterdam: true,
want: vm.GasCosts{
RegularGas: params.TxGas + params.CreateGasAmsterdam +
32*params.TxDataNonZeroGasEIP2028 + 1*params.InitCodeWordGas +
1*params.TxAccessListAddressGas + 1*params.TxAccessListStorageKeyGas +
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost,
StateGas: params.AccountCreationSize * params.CostPerStateByte,
},
}, },
{ {
name: "amsterdam/combined", name: "amsterdam/combined",
@ -264,23 +310,34 @@ func TestIntrinsicGas(t *testing.T) {
}, },
isEIP2028: true, isEIP2028: true,
isAmsterdam: true, isAmsterdam: true,
want: params.TxGas + // EIP-8037 splits the auth-tuple charge into regular + state gas:
100*params.TxDataNonZeroGasEIP2028 + // regular: TxAuthTupleRegularGas (7500) per auth
1*params.TxAccessListAddressGas + 1*params.TxAccessListStorageKeyGas + // state: (AuthorizationCreationSize + AccountCreationSize) * CostPerStateByte per auth
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost + want: vm.GasCosts{
1*params.CallNewAccountGas, RegularGas: params.TxGas +
100*params.TxDataNonZeroGasEIP2028 +
1*params.TxAccessListAddressGas + 1*params.TxAccessListStorageKeyGas +
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost +
1*params.TxAuthTupleRegularGas,
StateGas: 1 * (params.AuthorizationCreationSize + params.AccountCreationSize) * params.CostPerStateByte,
},
}, },
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
rules := params.Rules{
IsHomestead: tt.isHomestead,
IsIstanbul: tt.isEIP2028,
IsShanghai: tt.isEIP3860,
IsAmsterdam: tt.isAmsterdam,
}
got, err := IntrinsicGas(tt.data, tt.accessList, tt.authList, got, err := IntrinsicGas(tt.data, tt.accessList, tt.authList,
tt.creation, tt.isHomestead, tt.isEIP2028, tt.isEIP3860, tt.isAmsterdam) tt.creation, rules, params.CostPerStateByte)
if err != nil { if err != nil {
t.Fatalf("unexpected error: %v", err) t.Fatalf("unexpected error: %v", err)
} }
want := vm.GasCosts{RegularGas: tt.want} if got != tt.want {
if got != want { t.Fatalf("gas mismatch: got %+v, want %+v", got, tt.want)
t.Fatalf("gas mismatch: got %+v, want %+v", got, want)
} }
}) })
} }

View file

@ -472,6 +472,10 @@ const (
// transaction data. This change will always be a negative change. // transaction data. This change will always be a negative change.
GasChangeTxDataFloor GasChangeReason = 19 GasChangeTxDataFloor GasChangeReason = 19
// GasChangeStateGasRefund represents the amount of pre-charged state gas
// refunded back to the state reservoir.
GasChangeStateGasRefund GasChangeReason = 20
// GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as // GasChangeIgnored is a special value that can be used to indicate that the gas change should be ignored as
// it will be "manually" tracked by a direct emit of the gas change event. // it will be "manually" tracked by a direct emit of the gas change event.
GasChangeIgnored GasChangeReason = 0xFF GasChangeIgnored GasChangeReason = 0xFF

View file

@ -125,7 +125,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
} }
// Ensure the transaction has more gas than the bare minimum needed to cover // Ensure the transaction has more gas than the bare minimum needed to cover
// the transaction metadata // the transaction metadata
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam) intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte)
if err != nil { if err != nil {
return err return err
} }
@ -138,9 +138,18 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
if err != nil { if err != nil {
return err return err
} }
// Make sure the transaction has sufficient gas allowance to
// pay the floor cost.
if tx.Gas() < floorDataGas { if tx.Gas() < floorDataGas {
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrFloorDataGas, tx.Gas(), floorDataGas) return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrFloorDataGas, tx.Gas(), floorDataGas)
} }
// In Amsterdam, the transaction gas limit is allowed to exceed
// params.MaxTxGas, but the calldata floor cost is capped by it.
if rules.IsAmsterdam {
if max(intrGas.RegularGas, floorDataGas) > params.MaxTxGas {
return fmt.Errorf("%w: regular intrisic cost %v, floor: %v", core.ErrFloorDataGas, intrGas.RegularGas, floorDataGas)
}
}
} }
// Ensure the gasprice is high enough to cover the requirement of the calling pool // Ensure the gasprice is high enough to cover the requirement of the calling pool
if tx.GasTipCapIntCmp(opts.MinTip) < 0 { if tx.GasTipCapIntCmp(opts.MinTip) < 0 {

View file

@ -42,6 +42,8 @@ type Contract struct {
IsDeployment bool IsDeployment bool
IsSystemCall bool IsSystemCall bool
// Gas carries the unified gas state for this frame: running balance,
// reservoir, and per-frame usage accumulators. See GasBudget.
Gas GasBudget Gas GasBudget
value *uint256.Int value *uint256.Int
} }
@ -113,7 +115,6 @@ func (c *Contract) GetOp(n uint64) OpCode {
if n < uint64(len(c.Code)) { if n < uint64(len(c.Code)) {
return OpCode(c.Code[n]) return OpCode(c.Code[n])
} }
return STOP return STOP
} }
@ -125,9 +126,10 @@ func (c *Contract) Caller() common.Address {
return c.caller return c.caller
} }
// UseGas attempts the use gas and subtracts it and returns true on success // chargeRegular deducts regular gas only, with tracer integration.
func (c *Contract) UseGas(cost GasCosts, logger *tracing.Hooks, reason tracing.GasChangeReason) (ok bool) { // Returns false on OOG. Delegates the arithmetic to GasBudget.ChargeRegular.
prior, ok := c.Gas.Charge(cost) func (c *Contract) chargeRegular(r uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) bool {
prior, ok := c.Gas.ChargeRegular(r)
if !ok { if !ok {
return false return false
} }
@ -137,15 +139,55 @@ func (c *Contract) UseGas(cost GasCosts, logger *tracing.Hooks, reason tracing.G
return true return true
} }
// RefundGas refunds the leftover gas budget back to the contract. // chargeState deducts state gas (spilling into regular when the reservoir is
func (c *Contract) RefundGas(refund GasBudget, logger *tracing.Hooks, reason tracing.GasChangeReason) { // exhausted), with tracer integration. Returns false on OOG.
prior, changed := c.Gas.Refund(refund) func (c *Contract) chargeState(s uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) bool {
if !changed { prior, ok := c.Gas.ChargeState(s)
return if !ok {
return false
} }
if logger.HasGasHook() && reason != tracing.GasChangeIgnored { if logger.HasGasHook() && reason != tracing.GasChangeIgnored {
logger.EmitGasChange(prior.AsTracing(), c.Gas.AsTracing(), reason) logger.EmitGasChange(prior.AsTracing(), c.Gas.AsTracing(), reason)
} }
return true
}
// refundState refunds the pre-charged state gas back to state reservoir.
func (c *Contract) refundState(s uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) {
prior := c.Gas
c.Gas.RefundState(s)
if s != 0 && logger.HasGasHook() && reason != tracing.GasChangeIgnored {
logger.EmitGasChange(prior.AsTracing(), c.Gas.AsTracing(), reason)
}
}
// refundGas absorbs a sub-call's leftover GasBudget into this contract's gas
// state. Thin wrapper around GasBudget.Absorb with tracer integration.
func (c *Contract) refundGas(child GasBudget, logger *tracing.Hooks, reason tracing.GasChangeReason) {
prior := c.Gas
c.Gas.Absorb(child)
if logger.HasGasHook() && reason != tracing.GasChangeIgnored {
logger.EmitGasChange(prior.AsTracing(), c.Gas.AsTracing(), reason)
}
}
// forwardGas drains `regular` regular gas and the entire state reservoir
// from this contract's running budget and returns the initial GasBudget for
// a child frame. The caller's UsedRegularGas is bumped by the forwarded
// amount so that the absorb-on-return path correctly reclaims the unused
// portion. Thin wrapper around GasBudget.Forward with tracer integration.
//
// Caller must ensure `regular` is no larger than the running balance (the
// opcode's dynamic gas table is expected to validate that before invoking
// the opcode handler).
func (c *Contract) forwardGas(regular uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) GasBudget {
prior := c.Gas
child := c.Gas.Forward(regular)
if logger.HasGasHook() && reason != tracing.GasChangeIgnored {
logger.EmitGasChange(prior.AsTracing(), c.Gas.AsTracing(), reason)
}
return child
} }
// Address returns the contracts address // Address returns the contracts address

View file

@ -264,9 +264,8 @@ func ActivePrecompiles(rules params.Rules) []common.Address {
// - any error that occurred // - any error that occurred
func RunPrecompiledContract(stateDB StateDB, p PrecompiledContract, address common.Address, input []byte, gas GasBudget, logger *tracing.Hooks, rules params.Rules) (ret []byte, remaining GasBudget, err error) { func RunPrecompiledContract(stateDB StateDB, p PrecompiledContract, address common.Address, input []byte, gas GasBudget, logger *tracing.Hooks, rules params.Rules) (ret []byte, remaining GasBudget, err error) {
gasCost := p.RequiredGas(input) gasCost := p.RequiredGas(input)
prior, ok := gas.Charge(GasCosts{RegularGas: gasCost}) prior, ok := gas.ChargeRegular(gasCost)
if !ok { if !ok {
gas.Exhaust()
return nil, gas, ErrOutOfGas return nil, gas, ErrOutOfGas
} }
if logger.HasGasHook() { if logger.HasGasHook() {

View file

@ -37,7 +37,7 @@ func FuzzPrecompiledContracts(f *testing.F) {
return return
} }
inWant := string(input) inWant := string(input)
RunPrecompiledContract(nil, p, a, input, NewGasBudget(gas), nil, params.Rules{}) RunPrecompiledContract(nil, p, a, input, NewGasBudget(gas, 0), nil, params.Rules{})
if inHave := string(input); inWant != inHave { if inHave := string(input); inWant != inHave {
t.Errorf("Precompiled %v modified input data", a) t.Errorf("Precompiled %v modified input data", a)
} }

View file

@ -100,7 +100,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
in := common.Hex2Bytes(test.Input) in := common.Hex2Bytes(test.Input)
gas := p.RequiredGas(in) gas := p.RequiredGas(in)
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
if res, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas), nil, params.Rules{}); err != nil { if res, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas, 0), nil, params.Rules{}); err != nil {
t.Error(err) t.Error(err)
} else if common.Bytes2Hex(res) != test.Expected { } else if common.Bytes2Hex(res) != test.Expected {
t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res)) t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res))
@ -122,7 +122,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
gas := test.Gas - 1 gas := test.Gas - 1
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) { t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
_, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas), nil, params.Rules{}) _, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas, 0), nil, params.Rules{})
if err.Error() != "out of gas" { if err.Error() != "out of gas" {
t.Errorf("Expected error [out of gas], got [%v]", err) t.Errorf("Expected error [out of gas], got [%v]", err)
} }
@ -139,7 +139,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing
in := common.Hex2Bytes(test.Input) in := common.Hex2Bytes(test.Input)
gas := p.RequiredGas(in) gas := p.RequiredGas(in)
t.Run(test.Name, func(t *testing.T) { t.Run(test.Name, func(t *testing.T) {
_, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas), nil, params.Rules{}) _, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas, 0), nil, params.Rules{})
if err.Error() != test.ExpectedError { if err.Error() != test.ExpectedError {
t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err) t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err)
} }
@ -170,7 +170,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
start := time.Now() start := time.Now()
for bench.Loop() { for bench.Loop() {
copy(data, in) copy(data, in)
res, _, err = RunPrecompiledContract(nil, p, common.HexToAddress(addr), data, NewGasBudget(reqGas), nil, params.Rules{}) res, _, err = RunPrecompiledContract(nil, p, common.HexToAddress(addr), data, NewGasBudget(reqGas, 0), nil, params.Rules{})
} }
elapsed := uint64(time.Since(start)) elapsed := uint64(time.Since(start))
if elapsed < 1 { if elapsed < 1 {

View file

@ -43,6 +43,7 @@ var activators = map[int]func(*JumpTable){
7939: enable7939, 7939: enable7939,
8024: enable8024, 8024: enable8024,
7843: enable7843, 7843: enable7843,
8037: enable8037,
} }
// EnableEIP enables the given EIP on the config. // EnableEIP enables the given EIP on the config.
@ -377,7 +378,7 @@ func opExtCodeCopyEIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, er
code := evm.StateDB.GetCode(addr) code := evm.StateDB.GetCode(addr)
paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64())
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(code)), false, scope.Contract.Gas.RegularGas) consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(code)), false, scope.Contract.Gas.RegularGas)
scope.Contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeUnspecified) scope.Contract.chargeRegular(consumed, evm.Config.Tracer, tracing.GasChangeUnspecified)
if consumed < wanted { if consumed < wanted {
return nil, ErrOutOfGas return nil, ErrOutOfGas
} }
@ -403,7 +404,7 @@ func opPush1EIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// advanced past this boundary. // advanced past this boundary.
contractAddr := scope.Contract.Address() contractAddr := scope.Contract.Address()
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas.RegularGas) consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas.RegularGas)
scope.Contract.UseGas(GasCosts{RegularGas: wanted}, evm.Config.Tracer, tracing.GasChangeUnspecified) scope.Contract.chargeRegular(wanted, evm.Config.Tracer, tracing.GasChangeUnspecified)
if consumed < wanted { if consumed < wanted {
return nil, ErrOutOfGas return nil, ErrOutOfGas
} }
@ -430,7 +431,7 @@ func makePushEIP4762(size uint64, pushByteSize int) executionFunc {
if !scope.Contract.IsDeployment && !scope.Contract.IsSystemCall { if !scope.Contract.IsDeployment && !scope.Contract.IsSystemCall {
contractAddr := scope.Contract.Address() contractAddr := scope.Contract.Address()
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(contractAddr, uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas.RegularGas) consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(contractAddr, uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas.RegularGas)
scope.Contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeUnspecified) scope.Contract.chargeRegular(consumed, evm.Config.Tracer, tracing.GasChangeUnspecified)
if consumed < wanted { if consumed < wanted {
return nil, ErrOutOfGas return nil, ErrOutOfGas
} }
@ -590,3 +591,14 @@ func enable7843(jt *JumpTable) {
maxStack: maxStack(0, 1), maxStack: maxStack(0, 1),
} }
} }
// enable8037 enables the multidimensional-metering as specified in EIP-8037.
func enable8037(jt *JumpTable) {
jt[CREATE].constantGas = params.CreateGasAmsterdam
jt[CREATE].dynamicGas = gasCreateEip8037
jt[CREATE2].constantGas = params.CreateGasAmsterdam
jt[CREATE2].dynamicGas = gasCreate2Eip8037
jt[CALL].dynamicGas = gasCallEIP8037
jt[SELFDESTRUCT].dynamicGas = gasSelfdestruct8037
jt[SSTORE].dynamicGas = gasSStore8037
}

View file

@ -67,6 +67,8 @@ type BlockContext struct {
BlobBaseFee *big.Int // Provides information for BLOBBASEFEE (0 if vm runs with NoBaseFee flag and 0 blob gas price) BlobBaseFee *big.Int // Provides information for BLOBBASEFEE (0 if vm runs with NoBaseFee flag and 0 blob gas price)
Random *common.Hash // Provides information for PREVRANDAO Random *common.Hash // Provides information for PREVRANDAO
SlotNum uint64 // Provides information for SLOTNUM SlotNum uint64 // Provides information for SLOTNUM
CostPerStateByte uint64 // CostPerByte for new state after EIP-8037
} }
// TxContext provides the EVM with information about a transaction. // TxContext provides the EVM with information about a transaction.
@ -245,23 +247,29 @@ func isSystemCall(caller common.Address) bool {
// parameters. It also handles any necessary value transfer required and takse // parameters. It also handles any necessary value transfer required and takse
// the necessary steps to create accounts and reverses the state in case of an // the necessary steps to create accounts and reverses the state in case of an
// execution error or failed value transfer. // execution error or failed value transfer.
func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, leftOverGas GasBudget, err error) { func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, result GasBudget, err error) {
// Capture the tracer start/end events in debug mode // Capture the tracer start/end events in debug mode
if evm.Config.Tracer != nil { if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, CALL, caller, addr, input, gas.RegularGas, value.ToBig()) evm.captureBegin(evm.depth, CALL, caller, addr, input, gas, value.ToBig())
defer func(startGas uint64) { defer func(startGas GasBudget) {
evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err) evm.captureEnd(evm.depth, startGas, result, ret, err)
}(gas.RegularGas) }(gas)
} }
// Fail if we're trying to execute above the call depth limit // Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas.Preserved(), ErrDepth
} }
syscall := isSystemCall(caller) syscall := isSystemCall(caller)
// EIP-7928: per the Amsterdam spec, delegation resolution happens before
// the value-transfer check, so the delegated-to must appear in the BAL
// even when the call later reverts with ErrInsufficientBalance. Touch the
// target's code here (a no-op for non-delegated accounts) to record it.
evm.resolveCode(addr)
// Fail if we're trying to transfer more than the available balance. // Fail if we're trying to transfer more than the available balance.
if !syscall && !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller, value) { if !syscall && !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller, value) {
return nil, gas, ErrInsufficientBalance return nil, gas.Preserved(), ErrInsufficientBalance
} }
snapshot := evm.StateDB.Snapshot() snapshot := evm.StateDB.Snapshot()
p, isPrecompile := evm.precompile(addr) p, isPrecompile := evm.precompile(addr)
@ -275,16 +283,15 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
// hash leaf to the access list, then account creation will proceed unimpaired. // hash leaf to the access list, then account creation will proceed unimpaired.
// Thus, only pay for the creation of the code hash leaf here. // Thus, only pay for the creation of the code hash leaf here.
wgas := evm.AccessEvents.CodeHashGas(addr, true, gas.RegularGas, false) wgas := evm.AccessEvents.CodeHashGas(addr, true, gas.RegularGas, false)
if _, ok := gas.Charge(GasCosts{RegularGas: wgas}); !ok { if _, ok := gas.ChargeRegular(wgas); !ok {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
gas.Exhaust() return nil, gas.ExitHalt(), ErrOutOfGas
return nil, gas, ErrOutOfGas
} }
} }
if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() { if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() {
// Calling a non-existing account, don't do anything. // Calling a non-existing account, don't do anything.
return nil, gas, nil return nil, gas.Preserved(), nil
} }
evm.StateDB.CreateAccount(addr) evm.StateDB.CreateAccount(addr)
} }
@ -311,22 +318,20 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
gas = contract.Gas gas = contract.Gas
} }
} }
// When an error was returned by the EVM or when setting the creation code
// above we revert to the snapshot and consume any gas remaining. Additionally, // Calculate the remaining gas at the end of frame
// when we're in homestead this also counts for code storage gas errors. exitGas := gas.Exit(err)
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
// Drain the leftover regular gas if unexceptional halt occurs
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
if evm.Config.Tracer.HasGasHook() { if evm.Config.Tracer.HasGasHook() {
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution) evm.Config.Tracer.EmitGasChange(gas.AsTracing(), exitGas.AsTracing(), tracing.GasChangeCallFailedExecution)
} }
gas.Exhaust()
} }
// TODO: consider clearing up unused snapshots:
//} else {
// evm.StateDB.DiscardSnapshot(snapshot)
} }
return ret, gas, err return ret, exitGas, err
} }
// CallCode executes the contract associated with the addr with the given input // CallCode executes the contract associated with the addr with the given input
@ -336,24 +341,27 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
// //
// CallCode differs from Call in the sense that it executes the given address' // CallCode differs from Call in the sense that it executes the given address'
// code with the caller as context. // code with the caller as context.
func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, leftOverGas GasBudget, err error) { func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, result GasBudget, err error) {
// Invoke tracer hooks that signal entering/exiting a call frame // Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil { if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, CALLCODE, caller, addr, input, gas.RegularGas, value.ToBig()) evm.captureBegin(evm.depth, CALLCODE, caller, addr, input, gas, value.ToBig())
defer func(startGas uint64) { defer func(startGas GasBudget) {
evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err) evm.captureEnd(evm.depth, startGas, result, ret, err)
}(gas.RegularGas) }(gas)
} }
// Fail if we're trying to execute above the call depth limit // Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas.Preserved(), ErrDepth
} }
// EIP-7928: per the Amsterdam spec, delegation resolution happens before
// the value-transfer check, so the delegated-to must appear in the BAL
// even when the call later reverts with ErrInsufficientBalance.
evm.resolveCode(addr)
// Fail if we're trying to transfer more than the available balance // Fail if we're trying to transfer more than the available balance
// Note although it's noop to transfer X ether to caller itself. But
// if caller doesn't have enough balance, it would be an error to allow
// over-charging itself. So the check here is necessary.
if !evm.Context.CanTransfer(evm.StateDB, caller, value) { if !evm.Context.CanTransfer(evm.StateDB, caller, value) {
return nil, gas, ErrInsufficientBalance return nil, gas.Preserved(), ErrInsufficientBalance
} }
var snapshot = evm.StateDB.Snapshot() var snapshot = evm.StateDB.Snapshot()
@ -368,16 +376,20 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
ret, err = evm.Run(contract, input, false) ret, err = evm.Run(contract, input, false)
gas = contract.Gas gas = contract.Gas
} }
// Calculate the remaining gas at the end of frame
exitGas := gas.Exit(err)
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
// Drain the leftover regular gas if unexceptional halt occurs
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
if evm.Config.Tracer.HasGasHook() { if evm.Config.Tracer.HasGasHook() {
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution) evm.Config.Tracer.EmitGasChange(gas.AsTracing(), exitGas.AsTracing(), tracing.GasChangeCallFailedExecution)
} }
gas.Exhaust()
} }
} }
return ret, gas, err return ret, exitGas, err
} }
// DelegateCall executes the contract associated with the addr with the given input // DelegateCall executes the contract associated with the addr with the given input
@ -385,18 +397,18 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
// //
// DelegateCall differs from CallCode in the sense that it executes the given address' // DelegateCall differs from CallCode in the sense that it executes the given address'
// code with the caller as context and the caller is set to the caller of the caller. // code with the caller as context and the caller is set to the caller of the caller.
func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, leftOverGas GasBudget, err error) { func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, result GasBudget, err error) {
// Invoke tracer hooks that signal entering/exiting a call frame // Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil { if evm.Config.Tracer != nil {
// DELEGATECALL inherits value from parent call // DELEGATECALL inherits value from parent call
evm.captureBegin(evm.depth, DELEGATECALL, caller, addr, input, gas.RegularGas, value.ToBig()) evm.captureBegin(evm.depth, DELEGATECALL, caller, addr, input, gas, value.ToBig())
defer func(startGas uint64) { defer func(startGas GasBudget) {
evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err) evm.captureEnd(evm.depth, startGas, result, ret, err)
}(gas.RegularGas) }(gas)
} }
// Fail if we're trying to execute above the call depth limit // Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas.Preserved(), ErrDepth
} }
var snapshot = evm.StateDB.Snapshot() var snapshot = evm.StateDB.Snapshot()
@ -404,41 +416,42 @@ func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address,
if p, isPrecompile := evm.precompile(addr); isPrecompile { if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(evm.StateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules) ret, gas, err = RunPrecompiledContract(evm.StateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules)
} else { } else {
// Initialise a new contract and make initialise the delegate values
//
// Note: The value refers to the original value from the parent call.
contract := NewContract(originCaller, caller, value, gas, evm.jumpDests) contract := NewContract(originCaller, caller, value, gas, evm.jumpDests)
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
ret, err = evm.Run(contract, input, false) ret, err = evm.Run(contract, input, false)
gas = contract.Gas gas = contract.Gas
} }
// Calculate the remaining gas at the end of frame
exitGas := gas.Exit(err)
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
// Drain the leftover regular gas if unexceptional halt occurs
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
if evm.Config.Tracer.HasGasHook() { if evm.Config.Tracer.HasGasHook() {
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution) evm.Config.Tracer.EmitGasChange(gas.AsTracing(), exitGas.AsTracing(), tracing.GasChangeCallFailedExecution)
} }
gas.Exhaust()
} }
} }
return ret, gas, err return ret, exitGas, err
} }
// StaticCall executes the contract associated with the addr with the given input // StaticCall executes the contract associated with the addr with the given input
// as parameters while disallowing any modifications to the state during the call. // as parameters while disallowing any modifications to the state during the call.
// Opcodes that attempt to perform such modifications will result in exceptions // Opcodes that attempt to perform such modifications will result in exceptions
// instead of performing the modifications. // instead of performing the modifications.
func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []byte, gas GasBudget) (ret []byte, leftOverGas GasBudget, err error) { func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []byte, gas GasBudget) (ret []byte, result GasBudget, err error) {
// Invoke tracer hooks that signal entering/exiting a call frame // Invoke tracer hooks that signal entering/exiting a call frame
if evm.Config.Tracer != nil { if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, STATICCALL, caller, addr, input, gas.RegularGas, nil) evm.captureBegin(evm.depth, STATICCALL, caller, addr, input, gas, nil)
defer func(startGas uint64) { defer func(startGas GasBudget) {
evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err) evm.captureEnd(evm.depth, startGas, result, ret, err)
}(gas.RegularGas) }(gas)
} }
// Fail if we're trying to execute above the call depth limit // Fail if we're trying to execute above the call depth limit
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, gas, ErrDepth return nil, gas.Preserved(), ErrDepth
} }
// We take a snapshot here. This is a bit counter-intuitive, and could probably be skipped. // We take a snapshot here. This is a bit counter-intuitive, and could probably be skipped.
// However, even a staticcall is considered a 'touch'. On mainnet, static calls were introduced // However, even a staticcall is considered a 'touch'. On mainnet, static calls were introduced
@ -456,58 +469,59 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
if p, isPrecompile := evm.precompile(addr); isPrecompile { if p, isPrecompile := evm.precompile(addr); isPrecompile {
ret, gas, err = RunPrecompiledContract(evm.StateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules) ret, gas, err = RunPrecompiledContract(evm.StateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules)
} else { } else {
// Initialise a new contract and set the code that is to be used by the EVM.
// The contract is a scoped environment for this execution context only.
contract := NewContract(caller, addr, new(uint256.Int), gas, evm.jumpDests) contract := NewContract(caller, addr, new(uint256.Int), gas, evm.jumpDests)
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr)) contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
// When an error was returned by the EVM or when setting the creation code
// above we revert to the snapshot and consume any gas remaining. Additionally
// when we're in Homestead this also counts for code storage gas errors.
ret, err = evm.Run(contract, input, true) ret, err = evm.Run(contract, input, true)
gas = contract.Gas gas = contract.Gas
} }
// Calculate the remaining gas at the end of frame
exitGas := gas.Exit(err)
if err != nil { if err != nil {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
if evm.Config.Tracer.HasGasHook() { if evm.Config.Tracer.HasGasHook() {
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution) evm.Config.Tracer.EmitGasChange(gas.AsTracing(), exitGas.AsTracing(), tracing.GasChangeCallFailedExecution)
} }
gas.Exhaust()
} }
} }
return ret, gas, err return ret, exitGas, err
} }
// create creates a new contract using code as deployment code. // create creates a new contract using code as deployment code.
func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas GasBudget, err error) { func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, result GasBudget, err error) {
if evm.Config.Tracer != nil {
evm.captureBegin(evm.depth, typ, caller, address, code, gas.RegularGas, value.ToBig())
defer func(startGas uint64) {
evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err)
}(gas.RegularGas)
}
// Depth check execution. Fail if we're trying to execute above the // Depth check execution. Fail if we're trying to execute above the
// limit. // limit.
var nonce uint64
if evm.depth > int(params.CallCreateDepth) { if evm.depth > int(params.CallCreateDepth) {
return nil, common.Address{}, gas, ErrDepth err = ErrDepth
} else if !evm.Context.CanTransfer(evm.StateDB, caller, value) {
err = ErrInsufficientBalance
} else {
nonce = evm.StateDB.GetNonce(caller)
if nonce+1 < nonce {
err = ErrNonceUintOverflow
}
} }
if !evm.Context.CanTransfer(evm.StateDB, caller, value) { if err == nil {
return nil, common.Address{}, gas, ErrInsufficientBalance evm.StateDB.SetNonce(caller, nonce+1, tracing.NonceChangeContractCreator)
} }
nonce := evm.StateDB.GetNonce(caller) if evm.Config.Tracer != nil {
if nonce+1 < nonce { evm.captureBegin(evm.depth, typ, caller, address, code, gas, value.ToBig())
return nil, common.Address{}, gas, ErrNonceUintOverflow defer func(startGas GasBudget) {
evm.captureEnd(evm.depth, startGas, result, ret, err)
}(gas)
}
if err != nil {
return nil, common.Address{}, gas.Preserved(), err
} }
evm.StateDB.SetNonce(caller, nonce+1, tracing.NonceChangeContractCreator)
// Charge the contract creation init gas in verkle mode // Charge the contract creation init gas in verkle mode
if evm.chainRules.IsEIP4762 { if evm.chainRules.IsEIP4762 {
statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address, gas.RegularGas) statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address, gas.RegularGas)
prior, ok := gas.Charge(GasCosts{RegularGas: statelessGas}) prior, ok := gas.Charge(GasCosts{RegularGas: statelessGas})
if !ok { if !ok {
gas.Exhaust() return nil, common.Address{}, gas.ExitHalt(), ErrOutOfGas
return nil, common.Address{}, gas, ErrOutOfGas
} }
if evm.Config.Tracer.HasGasHook() { if evm.Config.Tracer.HasGasHook() {
evm.Config.Tracer.EmitGasChange(prior.AsTracing(), gas.AsTracing(), tracing.GasChangeWitnessContractCollisionCheck) evm.Config.Tracer.EmitGasChange(prior.AsTracing(), gas.AsTracing(), tracing.GasChangeWitnessContractCollisionCheck)
@ -528,11 +542,13 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
if evm.StateDB.GetNonce(address) != 0 || if evm.StateDB.GetNonce(address) != 0 ||
(contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code (contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code
isEIP7610RejectedAccount(evm.ChainConfig().ChainID, address, evm.chainRules.IsEIP158) { isEIP7610RejectedAccount(evm.ChainConfig().ChainID, address, evm.chainRules.IsEIP158) {
halt := gas.ExitHalt()
if evm.Config.Tracer.HasGasHook() { if evm.Config.Tracer.HasGasHook() {
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution) evm.Config.Tracer.EmitGasChange(gas.AsTracing(), halt.AsTracing(), tracing.GasChangeCallFailedExecution)
} }
gas.Exhaust() // EIP-8037 collision rule: the state reservoir is fully preserved on
return nil, common.Address{}, gas, ErrContractAddressCollision // address collision while regular gas is burnt.
return nil, common.Address{}, halt, ErrContractAddressCollision
} }
// Create a new account on the state only if the object was not present. // Create a new account on the state only if the object was not present.
// It might be possible the contract code is deployed to a pre-existent // It might be possible the contract code is deployed to a pre-existent
@ -554,8 +570,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
if evm.chainRules.IsEIP4762 { if evm.chainRules.IsEIP4762 {
consumed, wanted := evm.AccessEvents.ContractCreateInitGas(address, gas.RegularGas) consumed, wanted := evm.AccessEvents.ContractCreateInitGas(address, gas.RegularGas)
if consumed < wanted { if consumed < wanted {
gas.Exhaust() return nil, common.Address{}, gas.ExitHalt(), ErrOutOfGas
return nil, common.Address{}, gas, ErrOutOfGas
} }
prior, _ := gas.Charge(GasCosts{RegularGas: consumed}) prior, _ := gas.Charge(GasCosts{RegularGas: consumed})
if evm.Config.Tracer.HasGasHook() { if evm.Config.Tracer.HasGasHook() {
@ -574,13 +589,23 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
contract.IsDeployment = true contract.IsDeployment = true
ret, err = evm.initNewContract(contract, address) ret, err = evm.initNewContract(contract, address)
// Special case: ErrCodeStoreOutOfGas pre-Homestead does NOT roll back
// state and gas is preserved (i.e., treated as success).
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) { if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
evm.StateDB.RevertToSnapshot(snapshot) evm.StateDB.RevertToSnapshot(snapshot)
exit := contract.Gas.Exit(err)
if err != ErrExecutionReverted { if err != ErrExecutionReverted {
contract.UseGas(GasCosts{RegularGas: contract.Gas.RegularGas}, evm.Config.Tracer, tracing.GasChangeCallFailedExecution) if evm.Config.Tracer.HasGasHook() {
evm.Config.Tracer.EmitGasChange(contract.Gas.AsTracing(), exit.AsTracing(), tracing.GasChangeCallFailedExecution)
}
} }
return ret, address, exit, err
} }
return ret, address, contract.Gas, err // Either success, or pre-Homestead ErrCodeStoreOutOfGas (gas preserved).
// Both packaged as a success-form GasBudget.
return ret, address, contract.Gas.ExitSuccess(), err
} }
// initNewContract runs a new contract's creation code, performs checks on the // initNewContract runs a new contract's creation code, performs checks on the
@ -591,27 +616,43 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
return ret, err return ret, err
} }
// Check whether the max code size has been exceeded, assign err if the case. // Check prefix before gas calculation.
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
return ret, err
}
// Reject code starting with 0xEF if EIP-3541 is enabled. // Reject code starting with 0xEF if EIP-3541 is enabled.
if len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon { if len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon {
return ret, ErrInvalidCode return ret, ErrInvalidCode
} }
if evm.chainRules.IsEIP4762 {
if !evm.chainRules.IsEIP4762 { consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas.RegularGas)
createDataGas := uint64(len(ret)) * params.CreateDataGas contract.chargeRegular(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
if !contract.UseGas(GasCosts{RegularGas: createDataGas}, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) { if len(ret) > 0 && (consumed < wanted) {
return ret, ErrCodeStoreOutOfGas
}
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
return ret, err
}
} else if evm.chainRules.IsAmsterdam {
// Check max code size BEFORE charging gas so over-max code
// does not consume state gas (which would inflate tx_state).
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
return ret, err
}
// Charge regular gas (hash cost) before state gas (code-deposit cost).
regularCost := toWordSize(uint64(len(ret))) * params.Keccak256WordGas
if !contract.chargeRegular(regularCost, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
return ret, ErrCodeStoreOutOfGas
}
stateCost := uint64(len(ret)) * evm.Context.CostPerStateByte
if !contract.chargeState(stateCost, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
return ret, ErrCodeStoreOutOfGas return ret, ErrCodeStoreOutOfGas
} }
} else { } else {
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas.RegularGas) createDataCost := uint64(len(ret)) * params.CreateDataGas
contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) if !contract.chargeRegular(createDataCost, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
if len(ret) > 0 && (consumed < wanted) {
return ret, ErrCodeStoreOutOfGas return ret, ErrCodeStoreOutOfGas
} }
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
return ret, err
}
} }
if len(ret) > 0 { if len(ret) > 0 {
@ -621,7 +662,7 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
} }
// Create creates a new contract using code as deployment code. // Create creates a new contract using code as deployment code.
func (evm *EVM) Create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas GasBudget, err error) { func (evm *EVM) Create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int) (ret []byte, contractAddr common.Address, result GasBudget, err error) {
contractAddr = crypto.CreateAddress(caller, evm.StateDB.GetNonce(caller)) contractAddr = crypto.CreateAddress(caller, evm.StateDB.GetNonce(caller))
return evm.create(caller, code, gas, value, contractAddr, CREATE) return evm.create(caller, code, gas, value, contractAddr, CREATE)
} }
@ -630,7 +671,7 @@ func (evm *EVM) Create(caller common.Address, code []byte, gas GasBudget, value
// //
// The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:] // The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:]
// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at. // instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
func (evm *EVM) Create2(caller common.Address, code []byte, gas GasBudget, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas GasBudget, err error) { func (evm *EVM) Create2(caller common.Address, code []byte, gas GasBudget, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, result GasBudget, err error) {
inithash := crypto.Keccak256Hash(code) inithash := crypto.Keccak256Hash(code)
contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:]) contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:])
return evm.create(caller, code, gas, endowment, contractAddr, CREATE2) return evm.create(caller, code, gas, endowment, contractAddr, CREATE2)
@ -668,22 +709,20 @@ func (evm *EVM) resolveCodeHash(addr common.Address) common.Hash {
// ChainConfig returns the environment's chain configuration // ChainConfig returns the environment's chain configuration
func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig } func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }
func (evm *EVM) captureBegin(depth int, typ OpCode, from common.Address, to common.Address, input []byte, startGas uint64, value *big.Int) { func (evm *EVM) captureBegin(depth int, typ OpCode, from common.Address, to common.Address, input []byte, startGas GasBudget, value *big.Int) {
tracer := evm.Config.Tracer tracer := evm.Config.Tracer
if tracer.OnEnter != nil { if tracer.OnEnter != nil {
tracer.OnEnter(depth, byte(typ), from, to, input, startGas, value) tracer.OnEnter(depth, byte(typ), from, to, input, startGas.RegularGas, value)
} }
if tracer.HasGasHook() { if tracer.HasGasHook() {
initial := NewGasBudget(startGas) tracer.EmitGasChange(tracing.Gas{}, startGas.AsTracing(), tracing.GasChangeCallInitialBalance)
tracer.EmitGasChange(tracing.Gas{}, initial.AsTracing(), tracing.GasChangeCallInitialBalance)
} }
} }
func (evm *EVM) captureEnd(depth int, startGas uint64, leftOverGas uint64, ret []byte, err error) { func (evm *EVM) captureEnd(depth int, startGas GasBudget, leftOverGas GasBudget, ret []byte, err error) {
tracer := evm.Config.Tracer tracer := evm.Config.Tracer
if leftOverGas != 0 && tracer.HasGasHook() { if !leftOverGas.IsZero() && tracer.HasGasHook() {
leftover := NewGasBudget(leftOverGas) tracer.EmitGasChange(leftOverGas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallLeftOverReturned)
tracer.EmitGasChange(leftover.AsTracing(), tracing.Gas{}, tracing.GasChangeCallLeftOverReturned)
} }
var reverted bool var reverted bool
if err != nil { if err != nil {
@ -693,7 +732,7 @@ func (evm *EVM) captureEnd(depth int, startGas uint64, leftOverGas uint64, ret [
reverted = false reverted = false
} }
if tracer.OnExit != nil { if tracer.OnExit != nil {
tracer.OnExit(depth, ret, startGas-leftOverGas, VMErrorFromErr(err), reverted) tracer.OnExit(depth, ret, startGas.RegularGas-leftOverGas.RegularGas, VMErrorFromErr(err), reverted)
} }
} }

View file

@ -291,10 +291,19 @@ var (
gasMLoad = pureMemoryGascost gasMLoad = pureMemoryGascost
gasMStore8 = pureMemoryGascost gasMStore8 = pureMemoryGascost
gasMStore = pureMemoryGascost gasMStore = pureMemoryGascost
gasCreate = pureMemoryGascost
) )
func gasCreate(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
if evm.readOnly {
return GasCosts{}, ErrWriteProtection
}
return pureMemoryGascost(evm, contract, stack, mem, memorySize)
}
func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
if evm.readOnly {
return GasCosts{}, ErrWriteProtection
}
gas, err := memoryGasCost(mem, memorySize) gas, err := memoryGasCost(mem, memorySize)
if err != nil { if err != nil {
return GasCosts{}, err return GasCosts{}, err
@ -313,6 +322,9 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS
} }
func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
if evm.readOnly {
return GasCosts{}, ErrWriteProtection
}
gas, err := memoryGasCost(mem, memorySize) gas, err := memoryGasCost(mem, memorySize)
if err != nil { if err != nil {
return GasCosts{}, err return GasCosts{}, err
@ -331,7 +343,11 @@ func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
} }
return GasCosts{RegularGas: gas}, nil return GasCosts{RegularGas: gas}, nil
} }
func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
if evm.readOnly {
return GasCosts{}, ErrWriteProtection
}
gas, err := memoryGasCost(mem, memorySize) gas, err := memoryGasCost(mem, memorySize)
if err != nil { if err != nil {
return GasCosts{}, err return GasCosts{}, err
@ -384,17 +400,17 @@ var (
gasStaticCall = makeCallVariantGasCost(gasStaticCallIntrinsic) gasStaticCall = makeCallVariantGasCost(gasStaticCallIntrinsic)
) )
func makeCallVariantGasCost(intrinsicFunc gasFunc) gasFunc { func makeCallVariantGasCost(intrinsicFunc intrinsicGasFunc) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
intrinsic, err := intrinsicFunc(evm, contract, stack, mem, memorySize) intrinsic, err := intrinsicFunc(evm, contract, stack, mem, memorySize)
if err != nil { if err != nil {
return GasCosts{}, err return GasCosts{}, err
} }
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsic.RegularGas, stack.back(0)) evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsic, stack.back(0))
if err != nil { if err != nil {
return GasCosts{}, err return GasCosts{}, err
} }
gas, overflow := math.SafeAdd(intrinsic.RegularGas, evm.callGasTemp) gas, overflow := math.SafeAdd(intrinsic, evm.callGasTemp)
if overflow { if overflow {
return GasCosts{}, ErrGasUintOverflow return GasCosts{}, ErrGasUintOverflow
} }
@ -402,19 +418,19 @@ func makeCallVariantGasCost(intrinsicFunc gasFunc) gasFunc {
} }
} }
func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
var ( var (
gas uint64 gas uint64
transfersValue = !stack.back(2).IsZero() transfersValue = !stack.back(2).IsZero()
address = common.Address(stack.back(1).Bytes20()) address = common.Address(stack.back(1).Bytes20())
) )
if evm.readOnly && transfersValue { if evm.readOnly && transfersValue {
return GasCosts{}, ErrWriteProtection return 0, ErrWriteProtection
} }
// Stateless check // Stateless check
memoryGas, err := memoryGasCost(mem, memorySize) memoryGas, err := memoryGasCost(mem, memorySize)
if err != nil { if err != nil {
return GasCosts{}, err return 0, err
} }
var transferGas uint64 var transferGas uint64
if transfersValue && !evm.chainRules.IsEIP4762 { if transfersValue && !evm.chainRules.IsEIP4762 {
@ -422,12 +438,12 @@ func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
} }
var overflow bool var overflow bool
if gas, overflow = math.SafeAdd(memoryGas, transferGas); overflow { if gas, overflow = math.SafeAdd(memoryGas, transferGas); overflow {
return GasCosts{}, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
// Terminate the gas measurement if the leftover gas is not sufficient, // Terminate the gas measurement if the leftover gas is not sufficient,
// it can effectively prevent accessing the states in the following steps. // it can effectively prevent accessing the states in the following steps.
if contract.Gas.RegularGas < gas { if contract.Gas.RegularGas < gas {
return GasCosts{}, ErrOutOfGas return 0, ErrOutOfGas
} }
// Stateful check // Stateful check
var stateGas uint64 var stateGas uint64
@ -439,56 +455,87 @@ func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
stateGas += params.CallNewAccountGas stateGas += params.CallNewAccountGas
} }
if gas, overflow = math.SafeAdd(gas, stateGas); overflow { if gas, overflow = math.SafeAdd(gas, stateGas); overflow {
return GasCosts{}, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
return GasCosts{RegularGas: gas}, nil return gas, nil
} }
func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { // 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) {
var (
gas uint64
transfersValue = !stack.back(2).IsZero()
)
if evm.readOnly && transfersValue {
return 0, ErrWriteProtection
}
memoryGas, err := memoryGasCost(mem, memorySize) memoryGas, err := memoryGasCost(mem, memorySize)
if err != nil { if err != nil {
return GasCosts{}, err return 0, err
}
var transferGas uint64
if transfersValue && !evm.chainRules.IsEIP4762 {
transferGas = params.CallValueTransferGas
}
var overflow bool
if gas, overflow = math.SafeAdd(memoryGas, transferGas); overflow {
return 0, ErrGasUintOverflow
}
return gas, nil
}
func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
memoryGas, err := memoryGasCost(mem, memorySize)
if err != nil {
return 0, err
} }
var ( var (
gas uint64 gas uint64
overflow bool overflow bool
transfersValue = !stack.back(2).IsZero()
) )
if stack.back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 { if transfersValue {
gas += params.CallValueTransferGas if !evm.chainRules.IsEIP4762 {
gas += params.CallValueTransferGas
}
} }
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
return GasCosts{}, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
return GasCosts{RegularGas: gas}, nil return gas, nil
} }
func gasDelegateCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { func gasDelegateCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas, err := memoryGasCost(mem, memorySize) gas, err := memoryGasCost(mem, memorySize)
if err != nil { if err != nil {
return GasCosts{}, err return 0, err
} }
return GasCosts{RegularGas: gas}, nil return gas, nil
} }
func gasStaticCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { func gasStaticCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
gas, err := memoryGasCost(mem, memorySize) gas, err := memoryGasCost(mem, memorySize)
if err != nil { if err != nil {
return GasCosts{}, err return 0, err
} }
return GasCosts{RegularGas: gas}, nil return gas, nil
} }
func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
if evm.readOnly { if evm.readOnly {
return GasCosts{}, ErrWriteProtection return GasCosts{}, ErrWriteProtection
} }
var gas uint64 var gas uint64
// EIP150 homestead gas reprice fork: // EIP150 homestead gas reprice fork:
if evm.chainRules.IsEIP150 { if evm.chainRules.IsEIP150 {
gas = params.SelfdestructGasEIP150 gas = params.SelfdestructGasEIP150
var address = common.Address(stack.back(0).Bytes20()) if gas > contract.Gas.RegularGas {
return GasCosts{RegularGas: gas}, nil
}
var address = common.Address(stack.back(0).Bytes20())
if evm.chainRules.IsEIP158 { if evm.chainRules.IsEIP158 {
// if empty and transfers value // if empty and transfers value
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
@ -504,3 +551,174 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
} }
return GasCosts{RegularGas: gas}, nil return GasCosts{RegularGas: gas}, nil
} }
func gasCreateEip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
if evm.readOnly {
return GasCosts{}, ErrWriteProtection
}
gas, err := memoryGasCost(mem, memorySize)
if err != nil {
return GasCosts{}, err
}
size, overflow := stack.back(2).Uint64WithOverflow()
if overflow {
return GasCosts{}, ErrGasUintOverflow
}
if err := CheckMaxInitCodeSize(&evm.chainRules, size); err != nil {
return GasCosts{}, err
}
// Since size <= MaxInitCodeSizeAmsterdam, these multiplications cannot overflow
words := (size + 31) / 32
wordGas := params.InitCodeWordGas * words
stateGas := params.AccountCreationSize * evm.Context.CostPerStateByte
return GasCosts{
RegularGas: gas + wordGas,
StateGas: stateGas,
}, nil
}
func gasCreate2Eip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
if evm.readOnly {
return GasCosts{}, ErrWriteProtection
}
gas, err := memoryGasCost(mem, memorySize)
if err != nil {
return GasCosts{}, err
}
size, overflow := stack.back(2).Uint64WithOverflow()
if overflow {
return GasCosts{}, ErrGasUintOverflow
}
if err := CheckMaxInitCodeSize(&evm.chainRules, size); err != nil {
return GasCosts{}, err
}
// Since size <= MaxInitCodeSizeAmsterdam, these multiplications cannot overflow
words := (size + 31) / 32
// CREATE2 charges both InitCodeWordGas (EIP-3860) and Keccak256WordGas
// (for address hashing).
wordGas := (params.InitCodeWordGas + params.Keccak256WordGas) * words
stateGas := params.AccountCreationSize * evm.Context.CostPerStateByte
return GasCosts{
RegularGas: gas + wordGas,
StateGas: stateGas,
}, nil
}
// stateGasCall8037 is the stateful gas calculator for CALL in Amsterdam (EIP-8037).
// It only returns the state-dependent gas (account creation as state gas).
// Memory gas, transfer gas, and callGas are handled by gasCallStateless and
// makeCallVariantGasCall.
func stateGasCall8037(evm *EVM, contract *Contract, stack *Stack) (uint64, error) {
var (
gas uint64
transfersValue = !stack.back(2).IsZero()
address = common.Address(stack.back(1).Bytes20())
)
// TODO(rjl, marius), can EIP8037 implicitly means the EIP158 is also activated?
// It's technically possible to skip the EIP158 but very unlikely in practice.
if evm.chainRules.IsEIP158 {
if transfersValue && evm.StateDB.Empty(address) {
gas += params.AccountCreationSize * evm.Context.CostPerStateByte
}
} else if !evm.StateDB.Exist(address) {
gas += params.AccountCreationSize * evm.Context.CostPerStateByte
}
return gas, nil
}
func gasSelfdestruct8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
if evm.readOnly {
return GasCosts{}, ErrWriteProtection
}
var (
gas GasCosts
address = common.Address(stack.peek().Bytes20())
)
if !evm.StateDB.AddressInAccessList(address) {
// If the caller cannot afford the cost, this change will be rolled back
evm.StateDB.AddAddressToAccessList(address)
gas.RegularGas = params.ColdAccountAccessCostEIP2929
}
// Check we have enough regular gas before we add the address to the BAL
if contract.Gas.RegularGas < gas.RegularGas {
return gas, nil
}
// if empty and transfers value
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
gas.StateGas += params.AccountCreationSize * evm.Context.CostPerStateByte
}
return gas, nil
}
func gasSStore8037(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 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
)
// Check slot presence in the access list
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
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
}
value := common.Hash(y.Bytes32())
if current == value { // noop (1)
// EIP 2200 original clause:
// return params.SloadGasEIP2200, nil
return GasCosts{RegularGas: cost.RegularGas + params.WarmStorageReadCostEIP2929}, nil // SLOAD_GAS
}
if original == current {
if original == (common.Hash{}) { // create slot (2.1.1)
return GasCosts{
RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929,
StateGas: params.StorageCreationSize * evm.Context.CostPerStateByte,
}, nil
}
if value == (common.Hash{}) { // delete slot (2.1.2b)
evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP3529)
}
// 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)
}
if original != (common.Hash{}) {
if current == (common.Hash{}) { // recreate slot (2.2.1.1)
evm.StateDB.SubRefund(params.SstoreClearsScheduleRefundEIP3529)
} else if value == (common.Hash{}) { // delete slot (2.2.1.2)
evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP3529)
}
}
if original == value {
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)
}
}
// EIP-2200 original clause:
//return params.SloadGasEIP2200, nil // dirty update (2.2)
return GasCosts{RegularGas: cost.RegularGas + params.WarmStorageReadCostEIP2929}, nil // dirty update (2.2)
}

View file

@ -97,12 +97,12 @@ func TestEIP2200(t *testing.T) {
Transfer: func(StateDB, common.Address, common.Address, *uint256.Int, *params.Rules) {}, Transfer: func(StateDB, common.Address, common.Address, *uint256.Int, *params.Rules) {},
} }
evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}}) evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}})
initialGas := NewGasBudget(tt.gaspool) initialGas := NewGasBudget(tt.gaspool, 0)
_, leftOver, err := evm.Call(common.Address{}, address, nil, initialGas.Copy(), new(uint256.Int)) _, result, err := evm.Call(common.Address{}, address, nil, initialGas.Copy(), new(uint256.Int))
if !errors.Is(err, tt.failure) { if !errors.Is(err, tt.failure) {
t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure) t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure)
} }
if used := leftOver.Used(initialGas); used != tt.used { if used := result.Used(initialGas); used != tt.used {
t.Errorf("test %d: gas used mismatch: have %v, want %v", i, used, tt.used) t.Errorf("test %d: gas used mismatch: have %v, want %v", i, used, tt.used)
} }
if refund := evm.StateDB.GetRefund(); refund != tt.refund { if refund := evm.StateDB.GetRefund(); refund != tt.refund {
@ -157,12 +157,12 @@ func TestCreateGas(t *testing.T) {
} }
evm := NewEVM(vmctx, statedb, chainConfig, config) evm := NewEVM(vmctx, statedb, chainConfig, config)
initialGas := NewGasBudget(uint64(testGas)) initialGas := NewGasBudget(uint64(testGas), 0)
ret, leftOver, err := evm.Call(common.Address{}, address, nil, initialGas.Copy(), new(uint256.Int)) ret, result, err := evm.Call(common.Address{}, address, nil, initialGas.Copy(), new(uint256.Int))
if err != nil { if err != nil {
return false return false
} }
gasUsed = leftOver.Used(initialGas) gasUsed = result.Used(initialGas)
if len(ret) != 32 { if len(ret) != 32 {
t.Fatalf("test %d: expected 32 bytes returned, have %d", i, len(ret)) t.Fatalf("test %d: expected 32 bytes returned, have %d", i, len(ret))
} }

View file

@ -22,9 +22,8 @@ import (
"github.com/ethereum/go-ethereum/core/tracing" "github.com/ethereum/go-ethereum/core/tracing"
) )
// GasCosts denotes a vector of gas costs in the // GasCosts denotes a vector of gas costs in the multidimensional metering
// multidimensional metering paradigm. It represents the cost // paradigm. It represents the cost charged by an individual operation.
// charged by an individual operation.
type GasCosts struct { type GasCosts struct {
RegularGas uint64 RegularGas uint64
StateGas uint64 StateGas uint64
@ -40,67 +39,274 @@ func (g GasCosts) String() string {
return fmt.Sprintf("<%v,%v>", g.RegularGas, g.StateGas) return fmt.Sprintf("<%v,%v>", g.RegularGas, g.StateGas)
} }
// GasBudget denotes a vector of remaining gas allowances available // GasBudget is the unified gas-state structure used throughout the EVM.
// for EVM execution in the multidimensional metering paradigm. // It carries two pairs of fields:
// Unlike GasCosts which represents the price of an operation, //
// GasBudget tracks how much gas is left to spend. // - RegularGas / StateGas: the running balance during execution, or the
// leftover balance the caller must absorb after a sub-call.
// - UsedRegularGas / UsedStateGas: per-frame accumulators tracking gross
// consumption. UsedStateGas is signed so it can be decremented by inline
// state-gas refunds (e.g., SSTORE 0->A->0).
//
// The same struct serves three roles:
//
// - During execution: Charge / ChargeRegular / ChargeState / RefundState
// and RefundRegular mutate the running balance and the usage accumulators
// in lockstep.
//
// - At frame exit: Preserved / ExitSuccess / ExitRevert / ExitHalt /
// Exit produce a new GasBudget in "leftover" form that packages
// the result for the caller.
//
// - At absorption: the caller's Absorb method merges the child's leftover
// budget into its own running budget.
type GasBudget struct { type GasBudget struct {
RegularGas uint64 // The leftover gas for execution and state gas usage RegularGas uint64 // remaining regular-gas balance (or leftover for caller to absorb)
StateGas uint64 // The state gas reservoir StateGas uint64 // remaining state-gas reservoir (or leftover for caller to absorb)
UsedRegularGas uint64 // gross regular gas consumed in this frame
UsedStateGas int64 // signed net state-gas consumed in this frame
} }
// NewGasBudget creates a GasBudget with the given initial regular gas allowance. // NewGasBudget initializes a fresh GasBudget for execution / forwarding,
func NewGasBudget(gas uint64) GasBudget { // with both usage accumulators set to zero.
return GasBudget{RegularGas: gas} func NewGasBudget(regular, state uint64) GasBudget {
return GasBudget{RegularGas: regular, StateGas: state}
} }
// Used returns the amount of regular gas consumed so far. // Used returns the total scalar gas consumed relative to an initial budget
// (= (initial.regular + initial.state) (current.regular + current.state)).
// This is the payment scalar (EIP-8037's tx_gas_used_before_refund).
//
// TODO(rjl493456442) the total used gas can be calculated via g.UsedRegularGas
// and g.UsedStateGas.
func (g GasBudget) Used(initial GasBudget) uint64 { func (g GasBudget) Used(initial GasBudget) uint64 {
return initial.RegularGas - g.RegularGas return (initial.RegularGas + initial.StateGas) - (g.RegularGas + g.StateGas)
} }
// Exhaust sets all remaining gas to zero, preserving the initial amount // Copy returns a deep copy of the budget.
// for usage tracking. func (g GasBudget) Copy() GasBudget {
func (g *GasBudget) Exhaust() { return g
g.RegularGas = 0
g.StateGas = 0
} }
func (g *GasBudget) Copy() GasBudget { // String returns a visual representation of the budget.
return GasBudget{RegularGas: g.RegularGas, StateGas: g.StateGas}
}
// String returns a visual representation of the gas budget vector.
func (g GasBudget) String() string { func (g GasBudget) String() string {
return fmt.Sprintf("<%v,%v>", g.RegularGas, g.StateGas) return fmt.Sprintf("<%v,%v,used=<%v,%v>>", g.RegularGas, g.StateGas, g.UsedRegularGas, g.UsedStateGas)
} }
// CanAfford reports whether the budget has sufficient gas to cover the cost. // CanAfford reports whether the running balance can cover the given cost.
// State-gas charges that exceed the reservoir spill into regular gas.
func (g GasBudget) CanAfford(cost GasCosts) bool { func (g GasBudget) CanAfford(cost GasCosts) bool {
return g.RegularGas >= cost.RegularGas if g.RegularGas < cost.RegularGas {
return false
}
if cost.StateGas > g.StateGas {
spillover := cost.StateGas - g.StateGas
if spillover > g.RegularGas-cost.RegularGas {
return false
}
}
return true
} }
// Charge deducts the given gas cost from the budget. It returns the // Charge deducts a combined regular+state cost from the running balance and
// pre-charge budget and false if the budget does not have sufficient // updates the usage accumulators. State-gas in excess of the reservoir spills
// gas to cover the cost. // into regular_gas.
func (g *GasBudget) Charge(cost GasCosts) (GasBudget, bool) { func (g *GasBudget) Charge(cost GasCosts) (GasBudget, bool) {
prior := *g prior := *g
if g.RegularGas < cost.RegularGas { if !g.CanAfford(cost) {
return prior, false return prior, false
} }
// Charge regular gas
g.RegularGas -= cost.RegularGas g.RegularGas -= cost.RegularGas
return prior, true g.UsedRegularGas += cost.RegularGas
}
// Refund adds the given gas budget back. It returns the pre-refund budget // Charge state gas
// and whether the budget was actually changed. if cost.StateGas > g.StateGas {
func (g *GasBudget) Refund(other GasBudget) (GasBudget, bool) { spillover := cost.StateGas - g.StateGas
prior := *g g.StateGas = 0
g.RegularGas += other.RegularGas g.RegularGas -= spillover
return prior, g.RegularGas != prior.RegularGas } else {
g.StateGas -= cost.StateGas
}
g.UsedStateGas += int64(cost.StateGas)
return prior, true
} }
// AsTracing converts the GasBudget into the tracing-facing Gas vector. // AsTracing converts the GasBudget into the tracing-facing Gas vector.
func (g GasBudget) AsTracing() tracing.Gas { func (g GasBudget) AsTracing() tracing.Gas {
return tracing.Gas{Regular: g.RegularGas, State: g.StateGas} return tracing.Gas{Regular: g.RegularGas, State: g.StateGas}
} }
// ChargeRegular is a convenience that deducts a regular-only cost.
func (g *GasBudget) ChargeRegular(r uint64) (GasBudget, bool) {
return g.Charge(GasCosts{RegularGas: r})
}
// ChargeState is a convenience that deducts a state-only cost (spills to
// regular when the reservoir is exhausted). Returns false on OOG.
func (g *GasBudget) ChargeState(s uint64) (GasBudget, bool) {
return g.Charge(GasCosts{StateGas: s})
}
// IsZero returns an indicator if the gas budget has been exhausted.
func (g *GasBudget) IsZero() bool {
return g.RegularGas == 0 && g.StateGas == 0
}
// RefundState applies an inline state-gas refund (e.g., SSTORE 0->A->0).
// The reservoir is credited and the signed usage counter is decremented
// in lockstep, preserving the per-frame invariant:
//
// StateGas + UsedStateGas == initialStateGas + spillover_so_far
//
// which the revert path relies on for the correct gross refund.
func (g *GasBudget) RefundState(s uint64) {
g.StateGas += s
g.UsedStateGas -= int64(s)
}
// RefundRegular applies an inline regular-gas refund.
func (g *GasBudget) RefundRegular(s uint64) {
g.RegularGas += s
g.UsedRegularGas -= 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.
//
// Used by frame boundaries where the regular forward has NOT been pre-
// deducted: tx-level dispatch (state_transition) and CREATE / CREATE2. The
// CALL family pre-deducts the forward via the dynamic gas table for tracer-
// reporting reasons and therefore constructs its child budget directly.
//
// Caller must ensure `regular` does not exceed the running balance and
// apply any EIP-150 1/64 retention before calling Forward.
func (g *GasBudget) Forward(regular uint64) GasBudget {
g.RegularGas -= regular
child := GasBudget{
RegularGas: regular,
StateGas: g.StateGas,
}
g.StateGas = 0
return child
}
// ForwardAll forwards the parent's full remaining budget (both regular and
// state) to a child frame. Equivalent to Forward(g.RegularGas) — used at
// the tx boundary where there is no 1/64 retention.
func (g *GasBudget) ForwardAll() GasBudget {
return g.Forward(g.RegularGas)
}
// ============================================================================
// Exit-form constructors. These take a post-execution running budget and
// produce a new GasBudget in "leftover form" — the value the caller should
// absorb to update its own state.
// ============================================================================
// Preserved produces a leftover form with the running balance preserved and
// usage zeroed. Use this for pre-execution validation failures (depth,
// balance, EIP-158 zero-value-to-nonexistent) where no execution actually
// occurred.
func (g GasBudget) Preserved() GasBudget {
return GasBudget{
RegularGas: g.RegularGas,
StateGas: g.StateGas,
// UsedRegularGas / UsedStateGas stay at zero.
}
}
// ExitSuccess produces the leftover form for a successful frame. Inline
// state-gas refunds have already been folded into StateGas / UsedStateGas
// during execution; the running budget IS the exit budget on success.
func (g GasBudget) ExitSuccess() GasBudget {
return g
}
// ExitRevert produces the leftover form for a REVERT exit. Per EIP-8037,
// the gross state-gas charged in the failing subtree is refunded to the
// caller's reservoir via the signed-counter formula
//
// leftover.StateGas = StateGas + UsedStateGas
//
// and UsedStateGas is zeroed because the state effect is routed through
// StateGas. UsedRegularGas is unchanged.
func (g GasBudget) ExitRevert() GasBudget {
reservoir := int64(g.StateGas) + g.UsedStateGas
if reservoir < 0 {
// Defensive: invariant guarantees non-negativity. Clamping prevents
// a hypothetical bug from sign-extending to a huge uint64.
reservoir = 0
}
return GasBudget{
RegularGas: g.RegularGas,
StateGas: uint64(reservoir),
UsedRegularGas: g.UsedRegularGas,
UsedStateGas: 0,
}
}
// ExitHalt produces the leftover form for an exceptional halt. Remaining
// regular gas is burned into UsedRegularGas; the state dimension follows the
// same revert-style formula as ExitRevert because the spec routes both halt
// and revert through incorporate_child_on_error:
//
// parent.state_gas_left = parent + child.state_gas_used + child.state_gas_left
//
// which in our model means returning `StateGas + UsedStateGas` to the parent
// and zeroing the per-frame counter.
func (g GasBudget) ExitHalt() GasBudget {
reservoir := int64(g.StateGas) + g.UsedStateGas
if reservoir < 0 {
reservoir = 0
}
return GasBudget{
RegularGas: 0,
StateGas: uint64(reservoir),
UsedRegularGas: g.UsedRegularGas + g.RegularGas,
UsedStateGas: 0,
}
}
// Exit dispatches on err to the appropriate exit-form constructor
// for the post-evm.Run path:
//
// - err == nil → ExitSuccess
// - err == ErrExecutionReverted → ExitRevert
// - any other err → ExitHalt
//
// Soft validation failures (occurring BEFORE evm.Run) should call Preserved
// directly instead of going through this dispatcher.
func (g GasBudget) Exit(err error) GasBudget {
switch {
case err == nil:
return g.ExitSuccess()
case err == ErrExecutionReverted:
return g.ExitRevert()
default:
return g.ExitHalt()
}
}
// Absorb merges a sub-call's leftover GasBudget into this (caller's) running
// budget.
//
// - RegularGas: the child's leftover regular gas flows back to the caller.
// - UsedRegularGas: the child's own gross regular-gas consumption is added
// to the caller's accumulator. Combined with the caller having NOT
// pre-bumped UsedRegularGas by the forwarded amount, this matches the
// spec's escrow_subcall_regular_gas + incorporate_child_* pattern: only
// opcode charges count towards regular_gas_used, never state-gas
// spillover or escrowed forwards.
// - StateGas: the reservoir is overwritten by the child's leftover (spec's
// incorporate_child_on_success / on_error formula for state_gas_left).
// - UsedStateGas: the child's signed net state-gas usage is added to the
// caller's accumulator (spec's incorporate child gas).
func (g *GasBudget) Absorb(child GasBudget) {
g.RegularGas += child.RegularGas
g.UsedRegularGas += child.UsedRegularGas
g.StateGas = child.StateGas
g.UsedStateGas += child.UsedStateGas
}

View file

@ -647,25 +647,21 @@ func opSwap16(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
} }
func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
if evm.readOnly {
return nil, ErrWriteProtection
}
var ( var (
value = scope.Stack.pop() value = scope.Stack.pop()
offset, size = scope.Stack.pop(), scope.Stack.pop() offset, size = scope.Stack.pop(), scope.Stack.pop()
input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64()) input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64())
gas = scope.Contract.Gas.RegularGas forward = scope.Contract.Gas.RegularGas
) )
if evm.chainRules.IsEIP150 { if evm.chainRules.IsEIP150 {
gas -= gas / 64 forward -= forward / 64
} }
// reuse size int for stackvalue // reuse size int for stackvalue
stackvalue := size stackvalue := size
scope.Contract.UseGas(GasCosts{RegularGas: gas}, evm.Config.Tracer, tracing.GasChangeCallContractCreation) child := scope.Contract.forwardGas(forward, evm.Config.Tracer, tracing.GasChangeCallContractCreation)
res, addr, result, suberr := evm.Create(scope.Contract.Address(), input, child, &value)
res, addr, returnGas, suberr := evm.Create(scope.Contract.Address(), input, NewGasBudget(gas), &value)
// Push item on the stack based on the returned error. If the ruleset is // Push item on the stack based on the returned error. If the ruleset is
// homestead we must check for CodeStoreOutOfGasError (homestead only // homestead we must check for CodeStoreOutOfGasError (homestead only
// rule) and treat as an error, if the ruleset is frontier we must // rule) and treat as an error, if the ruleset is frontier we must
@ -679,8 +675,10 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
} }
scope.Stack.push(&stackvalue) scope.Stack.push(&stackvalue)
scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
if evm.chainRules.IsAmsterdam && suberr != nil {
scope.Contract.Gas.RefundState(params.AccountCreationSize * evm.Context.CostPerStateByte)
}
if suberr == ErrExecutionReverted { if suberr == ErrExecutionReverted {
evm.returnData = res // set REVERT data to return data buffer evm.returnData = res // set REVERT data to return data buffer
return res, nil return res, nil
@ -690,24 +688,21 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
} }
func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) { func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
if evm.readOnly {
return nil, ErrWriteProtection
}
var ( var (
endowment = scope.Stack.pop() endowment = scope.Stack.pop()
offset, size = scope.Stack.pop(), scope.Stack.pop() offset, size = scope.Stack.pop(), scope.Stack.pop()
salt = scope.Stack.pop() salt = scope.Stack.pop()
input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64()) input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64())
gas = scope.Contract.Gas.RegularGas forward = scope.Contract.Gas.RegularGas
) )
// Apply EIP150 // Apply EIP150
gas -= gas / 64 forward -= forward / 64
scope.Contract.UseGas(GasCosts{RegularGas: gas}, evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
// reuse size int for stackvalue // reuse size int for stackvalue
stackvalue := size stackvalue := size
res, addr, returnGas, suberr := evm.Create2(scope.Contract.Address(), input, NewGasBudget(gas), child := scope.Contract.forwardGas(forward, evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
&endowment, &salt) res, addr, result, suberr := evm.Create2(scope.Contract.Address(), input, child, &endowment, &salt)
// Push item on the stack based on the returned error. // Push item on the stack based on the returned error.
if suberr != nil { if suberr != nil {
stackvalue.Clear() stackvalue.Clear()
@ -715,8 +710,13 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
stackvalue.SetBytes(addr.Bytes()) stackvalue.SetBytes(addr.Bytes())
} }
scope.Stack.push(&stackvalue) scope.Stack.push(&stackvalue)
scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
// If the creation frame reverts or halts exceptionally, the charged state-gas
// is refilled back to the state reservoir in Amsterdam.
if evm.chainRules.IsAmsterdam && suberr != nil {
scope.Contract.refundState(params.AccountCreationSize*evm.Context.CostPerStateByte, evm.Config.Tracer, tracing.GasChangeStateGasRefund)
}
if suberr == ErrExecutionReverted { if suberr == ErrExecutionReverted {
evm.returnData = res // set REVERT data to return data buffer evm.returnData = res // set REVERT data to return data buffer
return res, nil return res, nil
@ -743,7 +743,18 @@ func opCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
if !value.IsZero() { if !value.IsZero() {
gas += params.CallStipend gas += params.CallStipend
} }
ret, returnGas, err := evm.Call(scope.Contract.Address(), toAddr, args, NewGasBudget(gas), &value) // Escrow pattern (spec: escrow_subcall_regular_gas): undo the full
// forwarded gas — including the value-transfer stipend — from
// UsedRegularGas. The stipend is a free loan to the child and must
// not count toward the caller's regular gas usage; the child's own
// opcode charges are re-added via Absorb on return.
scope.Contract.Gas.UsedRegularGas -= gas
// Regular gas for the forward was already pre-deducted by the dynamic
// gas table (see makeCallVariantGasCallEIP*); only the state reservoir
// needs to be handed off to the child here.
childBudget := NewGasBudget(gas, scope.Contract.Gas.StateGas)
ret, result, err := evm.Call(scope.Contract.Address(), toAddr, args, childBudget, &value)
if err != nil { if err != nil {
temp.Clear() temp.Clear()
@ -751,11 +762,16 @@ func opCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
temp.SetOne() temp.SetOne()
} }
stack.push(&temp) stack.push(&temp)
if err == nil || err == ErrExecutionReverted { if err == nil || err == ErrExecutionReverted {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
} }
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) // EIP-8037: CALL does not refund the new-account state_gas on
// ErrDepth/ErrInsufficientBalance — the spec only reclaims the forwarded
// reservoir; the state_gas charged for new account creation stays in
// state_gas_used (see Amsterdam spec call() / generic_call()).
evm.returnData = ret evm.returnData = ret
return ret, nil return ret, nil
@ -776,8 +792,12 @@ func opCallCode(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
if !value.IsZero() { if !value.IsZero() {
gas += params.CallStipend gas += params.CallStipend
} }
// Escrow pattern: undo the full forwarded gas (including stipend) from
// UsedRegularGas; the child's own opcode charges come back via Absorb.
scope.Contract.Gas.UsedRegularGas -= gas
ret, returnGas, err := evm.CallCode(scope.Contract.Address(), toAddr, args, NewGasBudget(gas), &value) childBudget := NewGasBudget(gas, scope.Contract.Gas.StateGas)
ret, result, err := evm.CallCode(scope.Contract.Address(), toAddr, args, childBudget, &value)
if err != nil { if err != nil {
temp.Clear() temp.Clear()
} else { } else {
@ -788,7 +808,7 @@ func opCallCode(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
} }
scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
evm.returnData = ret evm.returnData = ret
return ret, nil return ret, nil
@ -806,7 +826,10 @@ func opDelegateCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// Get arguments from the memory. // Get arguments from the memory.
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64()) args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
ret, returnGas, err := evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, NewGasBudget(gas), scope.Contract.value) // Undo the forwarded gas from UsedRegularGas to prevent double-charging.
scope.Contract.Gas.UsedRegularGas -= gas
childBudget := NewGasBudget(gas, scope.Contract.Gas.StateGas)
ret, result, err := evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, childBudget, scope.Contract.value)
if err != nil { if err != nil {
temp.Clear() temp.Clear()
} else { } else {
@ -816,8 +839,7 @@ func opDelegateCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
if err == nil || err == ErrExecutionReverted { if err == nil || err == ErrExecutionReverted {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
} }
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
evm.returnData = ret evm.returnData = ret
return ret, nil return ret, nil
@ -835,7 +857,10 @@ func opStaticCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
// Get arguments from the memory. // Get arguments from the memory.
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64()) args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
ret, returnGas, err := evm.StaticCall(scope.Contract.Address(), toAddr, args, NewGasBudget(gas)) // Undo the forwarded gas from UsedRegularGas to prevent double-charging.
scope.Contract.Gas.UsedRegularGas -= gas
childBudget := NewGasBudget(gas, scope.Contract.Gas.StateGas)
ret, result, err := evm.StaticCall(scope.Contract.Address(), toAddr, args, childBudget)
if err != nil { if err != nil {
temp.Clear() temp.Clear()
} else { } else {
@ -846,7 +871,7 @@ func opStaticCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret) scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
} }
scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded) scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
evm.returnData = ret evm.returnData = ret
return ret, nil return ret, nil

View file

@ -42,6 +42,11 @@ type StateDB interface {
GetCodeHash(common.Address) common.Hash GetCodeHash(common.Address) common.Hash
GetCode(common.Address) []byte GetCode(common.Address) []byte
// GetCommittedCode returns the contract code at the start of the current
// execution, ignoring any in-progress SetCode mutations. Returns nil when
// the account had no code prior to this execution.
GetCommittedCode(common.Address) []byte
// SetCode sets the new code for the address, and returns the previous code, if any. // SetCode sets the new code for the address, and returns the previous code, if any.
SetCode(common.Address, []byte, tracing.CodeChangeReason) []byte SetCode(common.Address, []byte, tracing.CodeChangeReason) []byte
GetCodeSize(common.Address) int GetCodeSize(common.Address) int

View file

@ -174,7 +174,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
// associated costs. // associated costs.
contractAddr := contract.Address() contractAddr := contract.Address()
consumed, wanted := evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas.RegularGas) consumed, wanted := evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas.RegularGas)
contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) contract.chargeRegular(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
if consumed < wanted { if consumed < wanted {
return nil, ErrOutOfGas return nil, ErrOutOfGas
} }
@ -192,10 +192,8 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack} return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
} }
// for tracing: this gas consumption event is emitted below in the debug section. // for tracing: this gas consumption event is emitted below in the debug section.
if contract.Gas.RegularGas < cost { if !contract.chargeRegular(cost, nil, tracing.GasChangeIgnored) {
return nil, ErrOutOfGas return nil, ErrOutOfGas
} else {
contract.Gas.RegularGas -= cost
} }
// All ops with a dynamic memory usage also has a dynamic gas cost. // All ops with a dynamic memory usage also has a dynamic gas cost.
@ -224,11 +222,13 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
if err != nil { if err != nil {
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err) return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
} }
// for tracing: this gas consumption event is emitted below in the debug section. // EIP-8037: charge regular gas before state gas. The state charge
if contract.Gas.RegularGas < dynamicCost.RegularGas { // is a no-op when dynamicCost.StateGas == 0 (e.g., pre-Amsterdam).
if !contract.chargeRegular(dynamicCost.RegularGas, nil, tracing.GasChangeIgnored) {
return nil, ErrOutOfGas
}
if !contract.chargeState(dynamicCost.StateGas, nil, tracing.GasChangeIgnored) {
return nil, ErrOutOfGas return nil, ErrOutOfGas
} else {
contract.Gas.RegularGas -= dynamicCost.RegularGas
} }
} }

View file

@ -55,7 +55,7 @@ func TestLoopInterrupt(t *testing.T) {
timeout := make(chan bool) timeout := make(chan bool)
go func(evm *EVM) { go func(evm *EVM) {
_, _, err := evm.Call(common.Address{}, address, nil, NewGasBudget(math.MaxUint64), new(uint256.Int)) _, _, err := evm.Call(common.Address{}, address, nil, NewGasBudget(math.MaxUint64, 0), new(uint256.Int))
errChannel <- err errChannel <- err
}(evm) }(evm)
@ -85,7 +85,7 @@ func BenchmarkInterpreter(b *testing.B) {
value = uint256.NewInt(0) value = uint256.NewInt(0)
stack = newStackForTesting() stack = newStackForTesting()
mem = NewMemory() mem = NewMemory()
contract = NewContract(common.Address{}, common.Address{}, value, NewGasBudget(startGas), nil) contract = NewContract(common.Address{}, common.Address{}, value, NewGasBudget(startGas, 0), nil)
) )
stack.push(uint256.NewInt(123)) stack.push(uint256.NewInt(123))
stack.push(uint256.NewInt(123)) stack.push(uint256.NewInt(123))

View file

@ -23,10 +23,14 @@ import (
) )
type ( type (
executionFunc func(pc *uint64, evm *EVM, callContext *ScopeContext) ([]byte, error) executionFunc func(pc *uint64, evm *EVM, callContext *ScopeContext) ([]byte, error)
gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (GasCosts, error) // last parameter is the requested memory size as a uint64 gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (GasCosts, error) // last parameter is the requested memory size as a uint64
intrinsicGasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64
// memorySizeFunc returns the required size, and whether the operation overflowed a uint64 // memorySizeFunc returns the required size, and whether the operation overflowed a uint64
memorySizeFunc func(*Stack) (size uint64, overflow bool) memorySizeFunc func(*Stack) (size uint64, overflow bool)
regularGasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error)
stateGasFunc func(*EVM, *Contract, *Stack) (uint64, error)
) )
type operation struct { type operation struct {
@ -97,6 +101,7 @@ func newAmsterdamInstructionSet() JumpTable {
instructionSet := newOsakaInstructionSet() instructionSet := newOsakaInstructionSet()
enable7843(&instructionSet) // EIP-7843 (SLOTNUM opcode) enable7843(&instructionSet) // EIP-7843 (SLOTNUM opcode)
enable8024(&instructionSet) // EIP-8024 (Backward compatible SWAPN, DUPN, EXCHANGE) enable8024(&instructionSet) // EIP-8024 (Backward compatible SWAPN, DUPN, EXCHANGE)
enable8037(&instructionSet) // EIP-8037 (State creation gas cost increase)
return validate(instructionSet) return validate(instructionSet)
} }

View file

@ -168,7 +168,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) g
evm.StateDB.AddAddressToAccessList(addr) evm.StateDB.AddAddressToAccessList(addr)
// Charge the remaining difference here already, to correctly calculate available // Charge the remaining difference here already, to correctly calculate available
// gas for call // gas for call
if !contract.UseGas(GasCosts{RegularGas: coldCost}, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { if !contract.chargeRegular(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
return GasCosts{}, ErrOutOfGas return GasCosts{}, ErrOutOfGas
} }
} }
@ -245,6 +245,10 @@ func makeSelfdestructGasFn(refundsEnabled bool) gasFunc {
return GasCosts{}, ErrOutOfGas return GasCosts{}, ErrOutOfGas
} }
} }
if gas > contract.Gas.RegularGas {
return GasCosts{RegularGas: gas}, nil
}
// if empty and transfers value // if empty and transfers value
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 { if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
gas += params.CreateBySelfdestructGas gas += params.CreateBySelfdestructGas
@ -262,6 +266,8 @@ var (
gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCallIntrinsic) gasDelegateCallEIP7702 = makeCallVariantGasCallEIP7702(gasDelegateCallIntrinsic)
gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCallIntrinsic) gasStaticCallEIP7702 = makeCallVariantGasCallEIP7702(gasStaticCallIntrinsic)
gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCodeIntrinsic) gasCallCodeEIP7702 = makeCallVariantGasCallEIP7702(gasCallCodeIntrinsic)
innerGasCallEIP8037 = makeCallVariantGasCallEIP8037(regularGasCall8037, stateGasCall8037)
) )
func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
@ -276,7 +282,15 @@ func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
return innerGasCallEIP7702(evm, contract, stack, mem, memorySize) return innerGasCallEIP7702(evm, contract, stack, mem, memorySize)
} }
func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc { func gasCallEIP8037(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)
}
func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) { return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
var ( var (
eip2929Cost uint64 eip2929Cost uint64
@ -295,7 +309,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
// Charge the remaining difference here already, to correctly calculate // Charge the remaining difference here already, to correctly calculate
// available gas for call // available gas for call
if !contract.UseGas(GasCosts{RegularGas: eip2929Cost}, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { if !contract.chargeRegular(eip2929Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
return GasCosts{}, ErrOutOfGas return GasCosts{}, ErrOutOfGas
} }
} }
@ -312,7 +326,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
// Terminate the gas measurement if the leftover gas is not sufficient, // Terminate the gas measurement if the leftover gas is not sufficient,
// it can effectively prevent accessing the states in the following steps. // it can effectively prevent accessing the states in the following steps.
// It's an essential safeguard before any stateful check. // It's an essential safeguard before any stateful check.
if contract.Gas.RegularGas < intrinsicCost.RegularGas { if contract.Gas.RegularGas < intrinsicCost {
return GasCosts{}, ErrOutOfGas return GasCosts{}, ErrOutOfGas
} }
@ -324,13 +338,13 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
evm.StateDB.AddAddressToAccessList(target) evm.StateDB.AddAddressToAccessList(target)
eip7702Cost = params.ColdAccountAccessCostEIP2929 eip7702Cost = params.ColdAccountAccessCostEIP2929
} }
if !contract.UseGas(GasCosts{RegularGas: eip7702Cost}, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) { if !contract.chargeRegular(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
return GasCosts{}, ErrOutOfGas return GasCosts{}, ErrOutOfGas
} }
} }
// Calculate the gas budget for the nested call. The costs defined by // Calculate the gas budget for the nested call. The costs defined by
// EIP-2929 and EIP-7702 have already been applied. // EIP-2929 and EIP-7702 have already been applied.
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsicCost.RegularGas, stack.back(0)) evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsicCost, stack.back(0))
if err != nil { if err != nil {
return GasCosts{}, err return GasCosts{}, err
} }
@ -339,6 +353,10 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
// part of the dynamic gas. This will ensure it is correctly reported to // part of the dynamic gas. This will ensure it is correctly reported to
// tracers. // tracers.
contract.Gas.RegularGas += eip2929Cost + eip7702Cost contract.Gas.RegularGas += eip2929Cost + eip7702Cost
// 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
// Aggregate the gas costs from all components, including EIP-2929, EIP-7702, // Aggregate the gas costs from all components, including EIP-2929, EIP-7702,
// the CALL opcode itself, and the cost incurred by nested calls. // the CALL opcode itself, and the cost incurred by nested calls.
@ -349,7 +367,93 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
if totalCost, overflow = math.SafeAdd(eip2929Cost, eip7702Cost); overflow { if totalCost, overflow = math.SafeAdd(eip2929Cost, eip7702Cost); overflow {
return GasCosts{}, ErrGasUintOverflow return GasCosts{}, ErrGasUintOverflow
} }
if totalCost, overflow = math.SafeAdd(totalCost, intrinsicCost.RegularGas); overflow { if totalCost, overflow = math.SafeAdd(totalCost, intrinsicCost); overflow {
return GasCosts{}, ErrGasUintOverflow
}
if totalCost, overflow = math.SafeAdd(totalCost, evm.callGasTemp); overflow {
return GasCosts{}, ErrGasUintOverflow
}
return GasCosts{RegularGas: totalCost}, nil
}
}
// makeCallVariantGasCallEIP8037 creates a call gas function for Amsterdam (EIP-8037).
// 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 {
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
var (
eip2929Cost uint64
eip7702Cost uint64
addr = common.Address(stack.back(1).Bytes20())
)
// EIP-2929 cold access check.
if !evm.StateDB.AddressInAccessList(addr) {
evm.StateDB.AddAddressToAccessList(addr)
eip2929Cost = params.ColdAccountAccessCostEIP2929 - params.WarmStorageReadCostEIP2929
if !contract.chargeRegular(eip2929Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
return GasCosts{}, ErrOutOfGas
}
}
// Compute regular cost (memory + transfer, no new account creation).
regularCost, err := regularFunc(evm, contract, stack, mem, memorySize)
if err != nil {
return GasCosts{}, err
}
// Charge intrinsic cost directly (regular gas). This must happen
// BEFORE state gas to prevent reservoir inflation, and also serves
// as the OOG guard before stateful operations.
if !contract.chargeRegular(regularCost, evm.Config.Tracer, tracing.GasChangeCallOpCode) {
return GasCosts{}, ErrOutOfGas
}
// EIP-7702 delegation check.
if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok {
if evm.StateDB.AddressInAccessList(target) {
eip7702Cost = params.WarmStorageReadCostEIP2929
} else {
evm.StateDB.AddAddressToAccessList(target)
eip7702Cost = params.ColdAccountAccessCostEIP2929
}
if !contract.chargeRegular(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
return GasCosts{}, ErrOutOfGas
}
}
// Compute and charge state gas (new account creation) AFTER regular gas.
stateGas, err := stateGasFunc(evm, contract, stack)
if err != nil {
return GasCosts{}, err
}
if stateGas > 0 {
if _, ok := contract.Gas.ChargeState(stateGas); !ok {
return GasCosts{}, ErrOutOfGas
}
}
// Calculate the gas budget for the nested call (63/64 rule).
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, 0, stack.back(0))
if err != nil {
return GasCosts{}, err
}
// Temporarily undo direct regular charges for tracer reporting.
// The interpreter will charge the returned totalCost.
contract.Gas.RegularGas += eip2929Cost + eip7702Cost + regularCost
contract.Gas.UsedRegularGas -= eip2929Cost + eip7702Cost + regularCost
// Aggregate total cost.
var (
overflow bool
totalCost uint64
)
if totalCost, overflow = math.SafeAdd(eip2929Cost, eip7702Cost); overflow {
return GasCosts{}, ErrGasUintOverflow
}
if totalCost, overflow = math.SafeAdd(totalCost, regularCost); overflow {
return GasCosts{}, ErrGasUintOverflow return GasCosts{}, ErrGasUintOverflow
} }
if totalCost, overflow = math.SafeAdd(totalCost, evm.callGasTemp); overflow { if totalCost, overflow = math.SafeAdd(totalCost, evm.callGasTemp); overflow {

View file

@ -144,15 +144,15 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
// set the receiver's (the executing contract) code for execution. // set the receiver's (the executing contract) code for execution.
cfg.State.SetCode(address, code, tracing.CodeChangeUnspecified) cfg.State.SetCode(address, code, tracing.CodeChangeUnspecified)
// Call the code with the given configuration. // Call the code with the given configuration.
ret, leftOverGas, err := vmenv.Call( ret, result, err := vmenv.Call(
cfg.Origin, cfg.Origin,
common.BytesToAddress([]byte("contract")), common.BytesToAddress([]byte("contract")),
input, input,
vm.NewGasBudget(cfg.GasLimit), vm.NewGasBudget(cfg.GasLimit, 0),
uint256.MustFromBig(cfg.Value), uint256.MustFromBig(cfg.Value),
) )
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil { if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil {
cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - leftOverGas.RegularGas}, err) cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - result.RegularGas}, err)
} }
return ret, cfg.State, err return ret, cfg.State, err
} }
@ -179,16 +179,16 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
// - reset transient storage(eip 1153) // - reset transient storage(eip 1153)
cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil) cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil)
// Call the code with the given configuration. // Call the code with the given configuration.
code, address, leftOverGas, err := vmenv.Create( code, address, result, err := vmenv.Create(
cfg.Origin, cfg.Origin,
input, input,
vm.NewGasBudget(cfg.GasLimit), vm.NewGasBudget(cfg.GasLimit, 0),
uint256.MustFromBig(cfg.Value), uint256.MustFromBig(cfg.Value),
) )
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil { if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil {
cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - leftOverGas.RegularGas}, err) cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - result.RegularGas}, err)
} }
return code, address, leftOverGas.RegularGas, err return code, address, result.RegularGas, err
} }
// Call executes the code given by the contract's address. It will return the // Call executes the code given by the contract's address. It will return the
@ -213,15 +213,15 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er
statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil) statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil)
// Call the code with the given configuration. // Call the code with the given configuration.
ret, leftOverGas, err := vmenv.Call( ret, result, err := vmenv.Call(
cfg.Origin, cfg.Origin,
address, address,
input, input,
vm.NewGasBudget(cfg.GasLimit), vm.NewGasBudget(cfg.GasLimit, 0),
uint256.MustFromBig(cfg.Value), uint256.MustFromBig(cfg.Value),
) )
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil { if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil {
cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - leftOverGas.RegularGas}, err) cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - result.RegularGas}, err)
} }
return ret, leftOverGas.RegularGas, err return ret, result.RegularGas, err
} }

View file

@ -55,7 +55,7 @@ func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCo
gasLimit uint64 = 31000 gasLimit uint64 = 31000
startGas uint64 = 10000 startGas uint64 = 10000
value = uint256.NewInt(0) value = uint256.NewInt(0)
contract = vm.NewContract(common.Address{}, common.Address{}, value, vm.NewGasBudget(startGas), nil) contract = vm.NewContract(common.Address{}, common.Address{}, value, vm.NewGasBudget(startGas, 0), nil)
) )
evm.SetTxContext(vmctx.txCtx) evm.SetTxContext(vmctx.txCtx)
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0} contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0}

View file

@ -47,7 +47,7 @@ func TestStoreCapture(t *testing.T) {
var ( var (
logger = NewStructLogger(nil) logger = NewStructLogger(nil)
evm = vm.NewEVM(vm.BlockContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: logger.Hooks()}) evm = vm.NewEVM(vm.BlockContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: logger.Hooks()})
contract = vm.NewContract(common.Address{}, common.Address{}, new(uint256.Int), vm.NewGasBudget(100000), nil) contract = vm.NewContract(common.Address{}, common.Address{}, new(uint256.Int), vm.NewGasBudget(100000, 0), nil)
) )
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)} contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)}
var index common.Hash var index common.Hash

View file

@ -88,6 +88,7 @@ const (
LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas. LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas.
CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction. CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction.
Create2Gas uint64 = 32000 // Once per CREATE2 operation Create2Gas uint64 = 32000 // Once per CREATE2 operation
CreateGasAmsterdam uint64 = 9000 // Regular gas portion of CREATE in Amsterdam (EIP-8037); state gas is charged separately.
CreateNGasEip4762 uint64 = 1000 // Once per CREATEn operations post-verkle CreateNGasEip4762 uint64 = 1000 // Once per CREATEn operations post-verkle
SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation. SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation.
MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL. MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL.
@ -100,6 +101,7 @@ const (
TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list TxAccessListAddressGas uint64 = 2400 // Per address specified in EIP 2930 access list
TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list TxAccessListStorageKeyGas uint64 = 1900 // Per storage key specified in EIP 2930 access list
TxAuthTupleGas uint64 = 12500 // Per auth tuple code specified in EIP-7702 TxAuthTupleGas uint64 = 12500 // Per auth tuple code specified in EIP-7702
TxAuthTupleRegularGas uint64 = 7500 // Per auth tuple regular gas specified in EIP-8037
// These have been changed during the course of the chain // These have been changed during the course of the chain
CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction. CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction.
@ -196,6 +198,12 @@ const (
// the bound has a small safety margin for system-contract accesses that // the bound has a small safety margin for system-contract accesses that
// don't consume block gas. // don't consume block gas.
BALItemCost uint64 = 2000 BALItemCost uint64 = 2000
AccountCreationSize = 120
StorageCreationSize = 64
AuthorizationCreationSize = 23
CostPerStateByte = 1530
SystemMaxSStoresPerCall = 16
) )
// Bls12381G1MultiExpDiscountTable is the gas discount table for BLS12-381 G1 multi exponentiation operation // Bls12381G1MultiExpDiscountTable is the gas discount table for BLS12-381 G1 multi exponentiation operation

View file

@ -82,8 +82,17 @@ func TestBlockchain(t *testing.T) {
// TestExecutionSpecBlocktests runs the test fixtures from execution-spec-tests. // TestExecutionSpecBlocktests runs the test fixtures from execution-spec-tests.
func TestExecutionSpecBlocktests(t *testing.T) { func TestExecutionSpecBlocktests(t *testing.T) {
if !common.FileExist(executionSpecBlockchainTestDir) { testExecutionSpecBlocktests(t, executionSpecBlockchainTestDir)
t.Skipf("directory %s does not exist", executionSpecBlockchainTestDir) }
// TestExecutionSpecBlocktestsBAL runs the BAL release test fixtures from execution-spec-tests.
func TestExecutionSpecBlocktestsBAL(t *testing.T) {
testExecutionSpecBlocktests(t, executionSpecBALBlockchainTestDir)
}
func testExecutionSpecBlocktests(t *testing.T, testDir string) {
if !common.FileExist(testDir) {
t.Skipf("directory %s does not exist", testDir)
} }
bt := new(testMatcher) bt := new(testMatcher)
@ -97,7 +106,7 @@ func TestExecutionSpecBlocktests(t *testing.T) {
bt.skipLoad(`dynamicAccountOverwriteEmpty_Paris`) bt.skipLoad(`dynamicAccountOverwriteEmpty_Paris`)
bt.skipLoad(`create2collisionStorageParis`) bt.skipLoad(`create2collisionStorageParis`)
bt.walk(t, executionSpecBlockchainTestDir, func(t *testing.T, name string, test *BlockTest) { bt.walk(t, testDir, func(t *testing.T, name string, test *BlockTest) {
execBlockTest(t, bt, test) execBlockTest(t, bt, test)
}) })
} }

View file

@ -41,9 +41,10 @@ var (
transactionTestDir = filepath.Join(baseDir, "TransactionTests") transactionTestDir = filepath.Join(baseDir, "TransactionTests")
rlpTestDir = filepath.Join(baseDir, "RLPTests") rlpTestDir = filepath.Join(baseDir, "RLPTests")
difficultyTestDir = filepath.Join(baseDir, "BasicTests") difficultyTestDir = filepath.Join(baseDir, "BasicTests")
executionSpecBlockchainTestDir = filepath.Join(".", "spec-tests", "fixtures", "blockchain_tests") executionSpecBlockchainTestDir = filepath.Join(".", "spec-tests", "fixtures", "blockchain_tests")
executionSpecStateTestDir = filepath.Join(".", "spec-tests", "fixtures", "state_tests") executionSpecBALBlockchainTestDir = filepath.Join(".", "spec-tests-bal", "fixtures", "blockchain_tests")
executionSpecTransactionTestDir = filepath.Join(".", "spec-tests", "fixtures", "transaction_tests") executionSpecStateTestDir = filepath.Join(".", "spec-tests", "fixtures", "state_tests")
executionSpecTransactionTestDir = filepath.Join(".", "spec-tests", "fixtures", "transaction_tests")
benchmarksDir = filepath.Join(".", "evm-benchmarks", "benchmarks") benchmarksDir = filepath.Join(".", "evm-benchmarks", "benchmarks")
) )

View file

@ -325,10 +325,10 @@ func runBenchmark(b *testing.B, t *StateTest) {
b.StartTimer() b.StartTimer()
start := time.Now() start := time.Now()
initialGas := vm.NewGasBudget(msg.GasLimit) initialGas := vm.NewGasBudget(msg.GasLimit, 0)
// Execute the message. // Execute the message.
_, leftOverGas, err := evm.Call(sender.Address(), *msg.To, msg.Data, initialGas.Copy(), msg.Value) _, result, err := evm.Call(sender.Address(), *msg.To, msg.Data, initialGas.Copy(), msg.Value)
if err != nil { if err != nil {
b.Error(err) b.Error(err)
return return
@ -337,7 +337,7 @@ func runBenchmark(b *testing.B, t *StateTest) {
b.StopTimer() b.StopTimer()
elapsed += uint64(time.Since(start)) elapsed += uint64(time.Since(start))
refund += state.StateDB.GetRefund() refund += state.StateDB.GetRefund()
gasUsed += leftOverGas.Used(initialGas) gasUsed += result.Used(initialGas)
state.StateDB.RevertToSnapshot(snapshot) state.StateDB.RevertToSnapshot(snapshot)
} }

View file

@ -80,8 +80,8 @@ func (tt *TransactionTest) Run() error {
if err != nil { if err != nil {
return return
} }
// Intrinsic gas // Intrinsic cost
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam) cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte)
if err != nil { if err != nil {
return return
} }