mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-15 17:30:44 +00:00
core: implement EIP-2780 and EIP-8037 changes (#35318)
Implement the spec changes of EIP-2780 and EIP-8037.
See the spec diffs in
- https://github.com/ethereum/EIPs/pull/11844
- https://github.com/ethereum/EIPs/pull/11891
- https://github.com/ethereum/EIPs/pull/11906
-
a4801f3bb1
---------
Co-authored-by: MariusVanDerWijden <m.vanderwijden@live.de>
This commit is contained in:
parent
abfb2de574
commit
1ef0ffb98c
26 changed files with 1600 additions and 589 deletions
|
|
@ -140,15 +140,15 @@ func Transaction(ctx *cli.Context) error {
|
||||||
value = uint256.NewInt(1)
|
value = uint256.NewInt(1)
|
||||||
}
|
}
|
||||||
rules := chainConfig.Rules(common.Big0, true, 0)
|
rules := chainConfig.Rules(common.Big0, true, 0)
|
||||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), r.Address, tx.To(), value, rules, params.CostPerStateByte)
|
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), r.Address, tx.To(), value, rules)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.Error = err
|
r.Error = err
|
||||||
results = append(results, r)
|
results = append(results, r)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
r.IntrinsicGas = cost.RegularGas
|
r.IntrinsicGas = cost
|
||||||
if tx.Gas() < cost.RegularGas {
|
if tx.Gas() < cost {
|
||||||
r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), cost.RegularGas)
|
r.Error = fmt.Errorf("%w: have %d, want %d", core.ErrIntrinsicGas, tx.Gas(), cost)
|
||||||
results = append(results, r)
|
results = append(results, r)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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, common.Address{}, &toaddr, nil, params.Rules{}, params.CostPerStateByte)
|
cost, _ := IntrinsicGas(data, nil, nil, common.Address{}, &toaddr, nil, params.Rules{})
|
||||||
signer := gen.Signer()
|
signer := gen.Signer()
|
||||||
gasPrice := big.NewInt(0)
|
gasPrice := big.NewInt(0)
|
||||||
if gen.header.BaseFee != nil {
|
if gen.header.BaseFee != nil {
|
||||||
|
|
@ -99,7 +99,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
|
||||||
Nonce: gen.TxNonce(benchRootAddr),
|
Nonce: gen.TxNonce(benchRootAddr),
|
||||||
To: &toaddr,
|
To: &toaddr,
|
||||||
Value: big.NewInt(1),
|
Value: big.NewInt(1),
|
||||||
Gas: cost.RegularGas,
|
Gas: cost,
|
||||||
Data: data,
|
Data: data,
|
||||||
GasPrice: gasPrice,
|
GasPrice: gasPrice,
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -65,12 +65,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, common.Address{}, nil, nil, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
|
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, common.Address{}, nil, nil, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true})
|
||||||
// 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, common.Address{}, nil, nil, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
|
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, common.Address{}, nil, nil, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true})
|
||||||
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
|
||||||
|
|
@ -102,11 +102,11 @@ func TestProcessUBT(t *testing.T) {
|
||||||
|
|
||||||
txCost1 := params.TxGas
|
txCost1 := params.TxGas
|
||||||
txCost2 := params.TxGas
|
txCost2 := params.TxGas
|
||||||
contractCreationCost := intrinsicContractCreationGas.RegularGas +
|
contractCreationCost := intrinsicContractCreationGas +
|
||||||
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation */
|
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation */
|
||||||
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* creation with value */
|
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* creation with value */
|
||||||
739 /* execution costs */
|
739 /* execution costs */
|
||||||
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas.RegularGas +
|
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas +
|
||||||
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (tx) */
|
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (tx) */
|
||||||
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (CREATE at pc=0x20) */
|
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + params.WitnessBranchReadCost + params.WitnessBranchWriteCost + /* creation (CREATE at pc=0x20) */
|
||||||
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash */
|
params.WitnessChunkReadCost + params.WitnessChunkWriteCost + /* write code hash */
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"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"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
@ -37,59 +38,67 @@ func TestEIP2780Intrinsic(t *testing.T) {
|
||||||
name string
|
name string
|
||||||
to *common.Address
|
to *common.Address
|
||||||
value *uint256.Int
|
value *uint256.Int
|
||||||
want vm.GasCosts
|
auths []types.SetCodeAuthorization
|
||||||
|
want uint64
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "self-transfer",
|
name: "self-transfer",
|
||||||
to: &from,
|
to: &from,
|
||||||
value: uint256.NewInt(1),
|
value: uint256.NewInt(1),
|
||||||
want: vm.GasCosts{RegularGas: params.TxBaseCost2780}, // 12,000
|
want: params.TxBaseCost2780, // 12,000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "self-transfer/zero-value",
|
name: "self-transfer/zero-value",
|
||||||
to: &from,
|
to: &from,
|
||||||
value: uint256.NewInt(0),
|
value: uint256.NewInt(0),
|
||||||
want: vm.GasCosts{RegularGas: params.TxBaseCost2780}, // 12,000
|
want: params.TxBaseCost2780, // 12,000
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "zero-value call",
|
name: "zero-value call",
|
||||||
to: &to,
|
to: &to,
|
||||||
value: uint256.NewInt(0),
|
value: uint256.NewInt(0),
|
||||||
// TxBaseCost + ColdAccountAccess = 15,000
|
// TxBaseCost + ColdAccountAccess = 15,000; the recipient touch is
|
||||||
want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780},
|
// charged at the cold rate unconditionally at the intrinsic phase.
|
||||||
|
want: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "value transfer to existing EOA",
|
name: "value transfer to existing EOA",
|
||||||
to: &to,
|
to: &to,
|
||||||
value: uint256.NewInt(1),
|
value: uint256.NewInt(1),
|
||||||
// TxBaseCost + ColdAccountAccess + TxValueCost + TransferLogCost = 21,000
|
// TxBaseCost + ColdAccountAccess + TxValueCost + TransferLogCost = 21,000
|
||||||
want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
|
want: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam +
|
||||||
params.TxValueCost2780 + params.TransferLogCost2780},
|
params.TxValueCost2780 + params.TransferLogCost2780,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "contract creation, value = 0",
|
name: "contract creation, value = 0",
|
||||||
to: nil,
|
to: nil,
|
||||||
value: uint256.NewInt(0),
|
value: uint256.NewInt(0),
|
||||||
// TxBaseCost + CreateAccess = 23,000 regular, plus one account creation in state.
|
// TxBaseCost + CreateAccess = 23,000 regular. The new-account state
|
||||||
want: vm.GasCosts{
|
// charge depends on whether the deployment target exists and is
|
||||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780,
|
// charged at runtime, not intrinsically.
|
||||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
want: params.TxBaseCost2780 + params.CreateAccessAmsterdam,
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "contract creation, value > 0",
|
name: "contract creation, value > 0",
|
||||||
to: nil,
|
to: nil,
|
||||||
value: uint256.NewInt(1),
|
value: uint256.NewInt(1),
|
||||||
// TxBaseCost + CreateAccess + TransferLogCost = 24,756 regular, plus account creation.
|
// TxBaseCost + CreateAccess + TransferLogCost = 24,756 regular.
|
||||||
want: vm.GasCosts{
|
want: params.TxBaseCost2780 + params.CreateAccessAmsterdam + params.TransferLogCost2780,
|
||||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 + params.TransferLogCost2780,
|
},
|
||||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
{
|
||||||
},
|
name: "value transfer with authorizations",
|
||||||
|
to: &to,
|
||||||
|
value: uint256.NewInt(1),
|
||||||
|
auths: make([]types.SetCodeAuthorization, 3),
|
||||||
|
// Each authorization adds the state-independent per-auth base
|
||||||
|
// (cold authority access included).
|
||||||
|
want: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam +
|
||||||
|
params.TxValueCost2780 + params.TransferLogCost2780 + 3*params.RegularPerAuthBaseCost,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
got, err := IntrinsicGas(nil, nil, nil, from, tc.to, tc.value, rules8037, params.CostPerStateByte)
|
got, err := IntrinsicGas(nil, nil, tc.auths, from, tc.to, tc.value, rules8037)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -105,7 +114,7 @@ func TestEIP2780Intrinsic(t *testing.T) {
|
||||||
// (intrinsic + top-level + execution) recorded in the block gas pool.
|
// (intrinsic + top-level + execution) recorded in the block gas pool.
|
||||||
func TestEIP2780Gas(t *testing.T) {
|
func TestEIP2780Gas(t *testing.T) {
|
||||||
const (
|
const (
|
||||||
cold = params.ColdAccountAccess2780
|
cold = params.ColdAccountAccessAmsterdam
|
||||||
base = params.TxBaseCost2780
|
base = params.TxBaseCost2780
|
||||||
valueCst = params.TxValueCost2780 + params.TransferLogCost2780
|
valueCst = params.TxValueCost2780 + params.TransferLogCost2780
|
||||||
)
|
)
|
||||||
|
|
@ -154,9 +163,9 @@ func TestEIP2780Gas(t *testing.T) {
|
||||||
// case 8: ETH transfer creating a new account.
|
// case 8: ETH transfer creating a new account.
|
||||||
{"value/new-account", callTx(0, freshEOA, 1, 300_000, nil), base + cold + valueCst, newAccountState},
|
{"value/new-account", callTx(0, freshEOA, 1, 300_000, nil), base + cold + valueCst, newAccountState},
|
||||||
// case 9: contract-creation transaction, value = 0.
|
// case 9: contract-creation transaction, value = 0.
|
||||||
{"create/zero-value", createTx(0, 300_000, nil), base + params.CreateAccess2780, newAccountState},
|
{"create/zero-value", createTx(0, 300_000, nil), base + params.CreateAccessAmsterdam, newAccountState},
|
||||||
// case 10: contract-creation transaction, value > 0.
|
// case 10: contract-creation transaction, value > 0.
|
||||||
{"create/value", valueCreateTx(1), base + params.CreateAccess2780 + params.TransferLogCost2780, newAccountState},
|
{"create/value", valueCreateTx(1), base + params.CreateAccessAmsterdam + params.TransferLogCost2780, newAccountState},
|
||||||
}
|
}
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
t.Run(tc.name, func(t *testing.T) {
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
|
@ -177,16 +186,191 @@ func TestEIP2780Gas(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestEIP2780NewAccountFunded verifies that a value transfer creating a new
|
// callTxAL builds a signed dynamic-fee call carrying an access list.
|
||||||
// account both materializes and funds the recipient.
|
func callTxAL(nonce uint64, to common.Address, value int64, gas uint64, al types.AccessList) *types.Transaction {
|
||||||
func TestEIP2780NewAccountFunded(t *testing.T) {
|
return types.MustSignNewTx(senderKey, signer8037, &types.DynamicFeeTx{
|
||||||
fresh := common.HexToAddress("0xbeef000000000000000000000000000000000002")
|
ChainID: cfg8037.ChainID, Nonce: nonce, To: &to, Value: big.NewInt(value),
|
||||||
sdb := mkState(senderAlloc(nil))
|
Gas: gas, GasFeeCap: big.NewInt(0), GasTipCap: big.NewInt(0), AccessList: al,
|
||||||
if _, _, err := applyMsg(t, sdb, callTx(0, fresh, 1, 300_000, nil)); err != nil {
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// accessListEntryCost is the total intrinsic cost of one address-only access
|
||||||
|
// list entry: the EIP-8038 per-address charge plus the EIP-7981 data charge.
|
||||||
|
const accessListEntryCost = params.TxAccessListAddressGasAmsterdam +
|
||||||
|
common.AddressLength*params.TxCostFloorPerToken7976*params.TxTokenPerNonZeroByte
|
||||||
|
|
||||||
|
// TestEIP2780WarmRecipientStillChargedCold verifies that a recipient warmed by
|
||||||
|
// the transaction's access list is still charged the recipient at the cold rate.
|
||||||
|
func TestEIP2780WarmRecipientStillChargedCold(t *testing.T) {
|
||||||
|
to := common.HexToAddress("0xe0a0000000000000000000000000000000000009")
|
||||||
|
sdb := mkState(senderAlloc(types.GenesisAlloc{to: {Balance: big.NewInt(1)}}))
|
||||||
|
al := types.AccessList{{Address: to}}
|
||||||
|
res, gp, err := applyMsg(t, sdb, callTxAL(0, to, 0, 100_000, al))
|
||||||
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if !sdb.Exist(fresh) || sdb.GetBalance(fresh).Cmp(uint256.NewInt(1)) != 0 {
|
if res.Err != nil {
|
||||||
t.Fatalf("recipient not funded: exist=%v balance=%v", sdb.Exist(fresh), sdb.GetBalance(fresh))
|
t.Fatalf("execution failed: %v", res.Err)
|
||||||
|
}
|
||||||
|
want := params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam + accessListEntryCost
|
||||||
|
if gp.cumulativeRegular != want {
|
||||||
|
t.Errorf("regular gas = %d, want %d (cold recipient, no access-list discount)", gp.cumulativeRegular, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEIP2780DelegatedWarmTarget verifies that resolving the recipient's
|
||||||
|
// delegation is charged at the warm rate when the target was warmed by the
|
||||||
|
// access list, rather than the flat cold rate.
|
||||||
|
func TestEIP2780DelegatedWarmTarget(t *testing.T) {
|
||||||
|
var (
|
||||||
|
target = common.HexToAddress("0x7a76000000000000000000000000000000000002") // codeless
|
||||||
|
delegated = common.HexToAddress("0xde1e000000000000000000000000000000000002")
|
||||||
|
)
|
||||||
|
sdb := mkState(senderAlloc(types.GenesisAlloc{
|
||||||
|
delegated: {Code: types.AddressToDelegation(target)},
|
||||||
|
}))
|
||||||
|
al := types.AccessList{{Address: target}}
|
||||||
|
res, gp, err := applyMsg(t, sdb, callTxAL(0, delegated, 0, 100_000, al))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if res.Err != nil {
|
||||||
|
t.Fatalf("execution failed: %v", res.Err)
|
||||||
|
}
|
||||||
|
want := params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam + accessListEntryCost + // recipient cold access (intrinsic)
|
||||||
|
params.WarmAccountAccessAmsterdam // warm delegation-target access (runtime)
|
||||||
|
if gp.cumulativeRegular != want {
|
||||||
|
t.Errorf("regular gas = %d, want %d (warm delegation target)", gp.cumulativeRegular, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEIP2780RuntimeOOGRevertsDelegations verifies that running out of gas on
|
||||||
|
// a runtime authorization charge halts the transaction and reverts all state
|
||||||
|
// changes, including the already applied EIP-7702 delegations — while the
|
||||||
|
// sender's nonce increment persists.
|
||||||
|
//
|
||||||
|
// The halt burns the regular dimension in full; the state dimension is
|
||||||
|
// refilled by the revert and the reservoir — if any — is preserved and
|
||||||
|
// returned to the sender rather than burnt.
|
||||||
|
func TestEIP2780RuntimeOOGRevertsDelegations(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
gas uint64
|
||||||
|
numAuths int
|
||||||
|
wantUsed uint64 // = gas − reservoir: all regular burnt, reservoir returned
|
||||||
|
}{
|
||||||
|
// No state reservoir (gas below MaxTxGas). Gas covers the intrinsic
|
||||||
|
// cost (TX_BASE_COST + the cold-inclusive per-authorization base for
|
||||||
|
// a self-call) but not the runtime authorization charges
|
||||||
|
// (ACCOUNT_WRITE + account + indicator bytes): everything is burnt.
|
||||||
|
{"no-reservoir", 30_000, 1, 30_000},
|
||||||
|
|
||||||
|
// A 100,000 state reservoir (gas above MaxTxGas). The 100
|
||||||
|
// authorizations' state charges (~21.9M) overwhelm the reservoir and
|
||||||
|
// the regular budget they spill into. The reservoir is made whole by
|
||||||
|
// the halt-refill and returned to the sender.
|
||||||
|
{"with-reservoir", params.MaxTxGas + 100_000, 100, params.MaxTxGas},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
var (
|
||||||
|
auths = make([]types.SetCodeAuthorization, tc.numAuths)
|
||||||
|
authorities = make([]common.Address, tc.numAuths)
|
||||||
|
)
|
||||||
|
for i := range auths {
|
||||||
|
key, _ := crypto.GenerateKey()
|
||||||
|
auth, err := types.SignSetCode(key, types.SetCodeAuthorization{
|
||||||
|
ChainID: *uint256.MustFromBig(cfg8037.ChainID), Address: delegate8037, Nonce: 0,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("sign auth: %v", err)
|
||||||
|
}
|
||||||
|
auths[i], authorities[i] = auth, crypto.PubkeyToAddress(key.PublicKey)
|
||||||
|
}
|
||||||
|
sdb := mkState(senderAlloc(nil))
|
||||||
|
tx := types.MustSignNewTx(senderKey, signer8037,
|
||||||
|
&types.SetCodeTx{
|
||||||
|
ChainID: uint256.MustFromBig(cfg8037.ChainID),
|
||||||
|
Nonce: 0,
|
||||||
|
To: senderAddr,
|
||||||
|
Value: new(uint256.Int),
|
||||||
|
Gas: tc.gas,
|
||||||
|
GasFeeCap: new(uint256.Int),
|
||||||
|
GasTipCap: new(uint256.Int),
|
||||||
|
AuthList: auths,
|
||||||
|
})
|
||||||
|
res, gp, err := applyMsg(t, sdb, tx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("transaction should remain valid: %v", err)
|
||||||
|
}
|
||||||
|
if res.Err != vm.ErrOutOfGas {
|
||||||
|
t.Fatalf("expected out of gas, got %v", res.Err)
|
||||||
|
}
|
||||||
|
if res.UsedGas != tc.wantUsed {
|
||||||
|
t.Fatalf("used gas = %d, want %d", res.UsedGas, tc.wantUsed)
|
||||||
|
}
|
||||||
|
// The charged state gas was refilled on the halt: the receipt is
|
||||||
|
// all regular, burnt in full, and only the reservoir survives.
|
||||||
|
if gp.cumulativeState != 0 {
|
||||||
|
t.Fatalf("state gas = %d, want 0 (refilled on halt)", gp.cumulativeState)
|
||||||
|
}
|
||||||
|
if gp.cumulativeRegular != tc.wantUsed {
|
||||||
|
t.Fatalf("regular gas = %d, want %d (burnt in full)", gp.cumulativeRegular, tc.wantUsed)
|
||||||
|
}
|
||||||
|
for i, authority := range authorities {
|
||||||
|
if code := sdb.GetCode(authority); len(code) != 0 {
|
||||||
|
t.Fatalf("delegation %d persisted despite runtime OOG: %x", i, code)
|
||||||
|
}
|
||||||
|
if sdb.GetNonce(authority) != 0 {
|
||||||
|
t.Fatalf("authority %d nonce persisted despite runtime OOG", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sdb.GetNonce(senderAddr) != 1 {
|
||||||
|
t.Fatal("sender nonce not consumed")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEIP2780SelfTransferDelegated verifies that a self-transfer incurs no
|
||||||
|
// recipient touch or value charges, while resolving the sender's own
|
||||||
|
// delegation is still paid for.
|
||||||
|
func TestEIP2780SelfTransferDelegated(t *testing.T) {
|
||||||
|
target := common.HexToAddress("0x7a76000000000000000000000000000000000003") // codeless
|
||||||
|
sdb := mkState(types.GenesisAlloc{
|
||||||
|
senderAddr: {Balance: big.NewInt(1e18), Code: types.AddressToDelegation(target)},
|
||||||
|
})
|
||||||
|
res, gp, err := applyMsg(t, sdb, callTx(0, senderAddr, 1, 100_000, nil))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if res.Err != nil {
|
||||||
|
t.Fatalf("execution failed: %v", res.Err)
|
||||||
|
}
|
||||||
|
want := params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam // base + cold delegation target
|
||||||
|
if gp.cumulativeRegular != want {
|
||||||
|
t.Errorf("regular gas = %d, want %d (base + delegation resolution)", gp.cumulativeRegular, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEIP2780CreateInsufficientStateGas verifies that a contract-creation
|
||||||
|
// transaction funded for its intrinsic gas but not the runtime new-account
|
||||||
|
// state charge is included, halts out of gas and consumes the nonce.
|
||||||
|
func TestEIP2780CreateInsufficientStateGas(t *testing.T) {
|
||||||
|
sdb := mkState(senderAlloc(nil))
|
||||||
|
intrinsic := params.TxBaseCost2780 + params.CreateAccessAmsterdam // 23,000
|
||||||
|
res, _, err := applyMsg(t, sdb, createTx(0, intrinsic, nil))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("transaction should remain valid: %v", err)
|
||||||
|
}
|
||||||
|
if res.Err != vm.ErrOutOfGas {
|
||||||
|
t.Fatalf("expected out of gas, got %v", res.Err)
|
||||||
|
}
|
||||||
|
if res.UsedGas != intrinsic {
|
||||||
|
t.Fatalf("used gas = %d, want %d", res.UsedGas, intrinsic)
|
||||||
|
}
|
||||||
|
if sdb.GetNonce(senderAddr) != 1 {
|
||||||
|
t.Fatal("sender nonce not consumed")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -211,4 +395,405 @@ func TestEIP2780InsufficientGasForCallCharge(t *testing.T) {
|
||||||
if sdb.Exist(fresh) {
|
if sdb.Exist(fresh) {
|
||||||
t.Fatal("recipient should not be created when the call charge cannot be paid")
|
t.Fatal("recipient should not be created when the call charge cannot be paid")
|
||||||
}
|
}
|
||||||
|
if sdb.GetNonce(senderAddr) != 1 {
|
||||||
|
t.Fatal("sender nonce not consumed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEIP2780FirstFrameHaltPreservesPreExecution verifies the gas and state
|
||||||
|
// semantics when the top-most frame — message call or creation — halts
|
||||||
|
// exceptionally after the pre-execution phase completed:
|
||||||
|
//
|
||||||
|
// - state changes applied before the frame was entered persist together
|
||||||
|
// with their state-gas charge (the EIP-7702 delegations of a call tx);
|
||||||
|
// - state gas pre-charged for the frame itself is refilled when the halt
|
||||||
|
// voids it (the account-creation charge of a creation tx);
|
||||||
|
// - after the refill the regular dimension is burnt in full, while any
|
||||||
|
// remaining state reservoir is preserved and returned to the sender.
|
||||||
|
func TestEIP2780FirstFrameHaltPreservesPreExecution(t *testing.T) {
|
||||||
|
halting := common.HexToAddress("0xbad0000000000000000000000000000000000002")
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
create bool
|
||||||
|
gas uint64
|
||||||
|
wantUsed uint64 // = gas − preserved reservoir
|
||||||
|
wantRegular uint64
|
||||||
|
wantState uint64
|
||||||
|
}{
|
||||||
|
// Message call carrying one authorization: the delegation and its
|
||||||
|
// state charge (account + indicator) survive the halt.
|
||||||
|
//
|
||||||
|
// Without a reservoir the charge spills from regular gas and everything is
|
||||||
|
// burnt;
|
||||||
|
//
|
||||||
|
// With a reservoir, the reservoir remainder is preserved.
|
||||||
|
{"call/no-reservoir", false, 1_000_000, 1_000_000, 1_000_000 - authWorstState, authWorstState},
|
||||||
|
{"call/with-reservoir", false, params.MaxTxGas + 300_000, params.MaxTxGas + authWorstState, params.MaxTxGas, authWorstState},
|
||||||
|
|
||||||
|
// Creation whose init code halts: no durable account is created, so
|
||||||
|
// the pre-charged account creation is refilled and no state gas
|
||||||
|
// remains.
|
||||||
|
//
|
||||||
|
// Without a reservoir the refill repays spilled regular gas, which the
|
||||||
|
// halt then burns along with the rest;
|
||||||
|
//
|
||||||
|
// With a reservoir, the refill makes the reservoir whole again and it
|
||||||
|
// is preserved.
|
||||||
|
{"create/no-reservoir", true, 1_000_000, 1_000_000, 1_000_000, 0},
|
||||||
|
{"create/with-reservoir", true, params.MaxTxGas + 100_000, params.MaxTxGas, params.MaxTxGas, 0},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
sdb := mkState(senderAlloc(types.GenesisAlloc{
|
||||||
|
halting: {Code: []byte{0xfe}}, // INVALID
|
||||||
|
}))
|
||||||
|
var (
|
||||||
|
tx *types.Transaction
|
||||||
|
authority common.Address
|
||||||
|
)
|
||||||
|
if tc.create {
|
||||||
|
tx = createTx(0, tc.gas, []byte{0xfe}) // init code: INVALID
|
||||||
|
} else {
|
||||||
|
var auth types.SetCodeAuthorization
|
||||||
|
auth, authority = signAuth(t, authKeyA, delegate8037, 0)
|
||||||
|
tx = types.MustSignNewTx(senderKey, signer8037,
|
||||||
|
&types.SetCodeTx{
|
||||||
|
ChainID: uint256.MustFromBig(cfg8037.ChainID),
|
||||||
|
Nonce: 0,
|
||||||
|
To: halting,
|
||||||
|
Value: new(uint256.Int),
|
||||||
|
Gas: tc.gas,
|
||||||
|
GasFeeCap: new(uint256.Int),
|
||||||
|
GasTipCap: new(uint256.Int),
|
||||||
|
AuthList: []types.SetCodeAuthorization{auth},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
res, gp, err := applyMsg(t, sdb, tx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("transaction should remain valid: %v", err)
|
||||||
|
}
|
||||||
|
if res.Err == nil {
|
||||||
|
t.Fatal("expected the frame to halt")
|
||||||
|
}
|
||||||
|
if res.UsedGas != tc.wantUsed {
|
||||||
|
t.Fatalf("used gas = %d, want %d", res.UsedGas, tc.wantUsed)
|
||||||
|
}
|
||||||
|
if gp.cumulativeRegular != tc.wantRegular {
|
||||||
|
t.Fatalf("regular gas = %d, want %d (burnt in full)", gp.cumulativeRegular, tc.wantRegular)
|
||||||
|
}
|
||||||
|
if gp.cumulativeState != tc.wantState {
|
||||||
|
t.Fatalf("state gas = %d, want %d", gp.cumulativeState, tc.wantState)
|
||||||
|
}
|
||||||
|
if tc.create {
|
||||||
|
// The halted creation is fully reverted: no durable account.
|
||||||
|
derived := crypto.CreateAddress(senderAddr, 0)
|
||||||
|
if code := sdb.GetCode(derived); len(code) != 0 {
|
||||||
|
t.Fatalf("created code persisted despite halt: %x", code)
|
||||||
|
}
|
||||||
|
if sdb.GetNonce(derived) != 0 {
|
||||||
|
t.Fatal("created account nonce persisted despite halt")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// The delegation applied before the frame was entered persists.
|
||||||
|
if code := sdb.GetCode(authority); len(code) == 0 {
|
||||||
|
t.Fatal("delegation should persist through an in-frame halt")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if sdb.GetNonce(senderAddr) != 1 {
|
||||||
|
t.Fatal("sender nonce not consumed")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEIP2780CreatePreExecutionOOGPreservesReservoir verifies that when a
|
||||||
|
// creation transaction cannot afford the pre-execution account-creation state
|
||||||
|
// charge (before the init-code frame is entered), the transaction halts with
|
||||||
|
// all regular gas burnt while the state reservoir — never touched, since the
|
||||||
|
// charge is atomic and was not applied — is preserved and returned to the
|
||||||
|
// sender.
|
||||||
|
func TestEIP2780CreatePreExecutionOOGPreservesReservoir(t *testing.T) {
|
||||||
|
// Regular gas left for the pre-execution charge; together with the
|
||||||
|
// reservoir it must not cover the account-creation cost.
|
||||||
|
const (
|
||||||
|
regularLeft = 100_000
|
||||||
|
reservoir = 50_000
|
||||||
|
)
|
||||||
|
// Plain creation intrinsic: TX_BASE_COST + CREATE_ACCESS.
|
||||||
|
plainIntrinsic, err := IntrinsicGas(nil, nil, nil, senderAddr, nil, new(uint256.Int), rules8037)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// For the reservoir case the gas limit must exceed MaxTxGas, which leaves
|
||||||
|
// a huge regular budget by default. A big access list drives the intrinsic
|
||||||
|
// cost close to MaxTxGas, shrinking the regular budget back down to
|
||||||
|
// roughly regularLeft. Storage keys work because their intrinsic charge
|
||||||
|
// exceeds their EIP-7623/7976 floor contribution.
|
||||||
|
al := types.AccessList{{Address: common.HexToAddress("0xa1")}}
|
||||||
|
baseIntrinsic, err := IntrinsicGas(nil, al, nil, senderAddr, nil, new(uint256.Int), rules8037)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
perKey := params.TxAccessListStorageKeyGasAmsterdam + uint64(common.HashLength)*params.TxCostFloorPerToken7976*params.TxTokenPerNonZeroByte
|
||||||
|
|
||||||
|
// Fill the transaction with accessList, drain the gas and make it
|
||||||
|
// insufficient for account-creation cost.
|
||||||
|
al[0].StorageKeys = make([]common.Hash, (params.MaxTxGas-regularLeft-baseIntrinsic)/perKey)
|
||||||
|
alIntrinsic, err := IntrinsicGas(nil, al, nil, senderAddr, nil, new(uint256.Int), rules8037)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if left := params.MaxTxGas - alIntrinsic; left+reservoir >= newAccountState {
|
||||||
|
t.Fatalf("setup: regular %d + reservoir %d must not cover the creation charge %d", left, reservoir, newAccountState)
|
||||||
|
}
|
||||||
|
alCreateTx := types.MustSignNewTx(senderKey, signer8037,
|
||||||
|
&types.DynamicFeeTx{
|
||||||
|
ChainID: cfg8037.ChainID,
|
||||||
|
Nonce: 0,
|
||||||
|
To: nil,
|
||||||
|
Value: big.NewInt(0),
|
||||||
|
Gas: params.MaxTxGas + reservoir,
|
||||||
|
GasFeeCap: big.NewInt(0),
|
||||||
|
GasTipCap: big.NewInt(0),
|
||||||
|
AccessList: al,
|
||||||
|
})
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
tx *types.Transaction
|
||||||
|
wantUsed uint64 // = gas − preserved reservoir
|
||||||
|
}{
|
||||||
|
// Gas below MaxTxGas: no reservoir, the whole limit is burnt.
|
||||||
|
{"no-reservoir", createTx(0, plainIntrinsic+regularLeft, nil), plainIntrinsic + regularLeft},
|
||||||
|
|
||||||
|
// Gas above MaxTxGas: the reservoir survives the halt untouched and
|
||||||
|
// is returned to the sender.
|
||||||
|
{"with-reservoir", alCreateTx, params.MaxTxGas},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
sdb := mkState(senderAlloc(nil))
|
||||||
|
res, gp, err := applyMsg(t, sdb, tc.tx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("transaction should remain valid: %v", err)
|
||||||
|
}
|
||||||
|
if res.Err != vm.ErrOutOfGas {
|
||||||
|
t.Fatalf("expected out of gas, got %v", res.Err)
|
||||||
|
}
|
||||||
|
if res.UsedGas != tc.wantUsed {
|
||||||
|
t.Fatalf("used gas = %d, want %d", res.UsedGas, tc.wantUsed)
|
||||||
|
}
|
||||||
|
if gp.cumulativeRegular != tc.wantUsed {
|
||||||
|
t.Fatalf("regular gas = %d, want %d (burnt in full)", gp.cumulativeRegular, tc.wantUsed)
|
||||||
|
}
|
||||||
|
if gp.cumulativeState != 0 {
|
||||||
|
t.Fatalf("state gas = %d, want 0 (charge never applied)", gp.cumulativeState)
|
||||||
|
}
|
||||||
|
if derived := crypto.CreateAddress(senderAddr, 0); sdb.Exist(derived) {
|
||||||
|
t.Fatal("target account should not be created when the charge cannot be paid")
|
||||||
|
}
|
||||||
|
if sdb.GetNonce(senderAddr) != 1 {
|
||||||
|
t.Fatal("sender nonce not consumed")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEIP2780AuthorityAccountWrite pins the first-write ACCOUNT_WRITE rule for
|
||||||
|
// authorities: the surcharge applies to the first paid write to the account
|
||||||
|
// within the transaction, regardless of whether the account exists, and is
|
||||||
|
// skipped when the write is already paid for: by TX_BASE_COST for the sender,
|
||||||
|
// by TX_VALUE_COST for the recipient of a value-bearing transaction, or by a
|
||||||
|
// preceding valid authorization.
|
||||||
|
func TestEIP2780AuthorityAccountWrite(t *testing.T) {
|
||||||
|
const (
|
||||||
|
base = params.TxBaseCost2780
|
||||||
|
cold = params.ColdAccountAccessAmsterdam
|
||||||
|
aw = params.AccountWriteAmsterdam
|
||||||
|
perAuth = params.RegularPerAuthBaseCost
|
||||||
|
valueCst = params.TxValueCost2780 + params.TransferLogCost2780
|
||||||
|
)
|
||||||
|
existingEOA := common.HexToAddress("0xe0a0000000000000000000000000000000000002")
|
||||||
|
|
||||||
|
auth0, authority := signAuth(t, authKeyA, delegate8037, 0)
|
||||||
|
auth1, _ := signAuth(t, authKeyA, delegate8037, 1)
|
||||||
|
authBadNonce, _ := signAuth(t, authKeyA, delegate8037, 5)
|
||||||
|
|
||||||
|
// Self-sponsored authorization: the sender's nonce is bumped before the
|
||||||
|
// authorization list is processed, hence nonce 1.
|
||||||
|
senderAuth, err := types.SignSetCode(senderKey, types.SetCodeAuthorization{
|
||||||
|
ChainID: *uint256.MustFromBig(cfg8037.ChainID), Address: delegate8037, Nonce: 1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// tx builds a SetCode transaction with an explicit value.
|
||||||
|
tx := func(to common.Address, value uint64, auths ...types.SetCodeAuthorization) *types.Transaction {
|
||||||
|
return types.MustSignNewTx(senderKey, signer8037, &types.SetCodeTx{
|
||||||
|
ChainID: uint256.MustFromBig(cfg8037.ChainID), Nonce: 0, To: to,
|
||||||
|
Value: uint256.NewInt(value), Gas: 1_000_000,
|
||||||
|
GasFeeCap: new(uint256.Int), GasTipCap: new(uint256.Int), AuthList: auths,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fundedAuthority := types.GenesisAlloc{authority: {Balance: big.NewInt(1)}}
|
||||||
|
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
alloc types.GenesisAlloc
|
||||||
|
tx *types.Transaction
|
||||||
|
wantRegular, wantState uint64
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
// Materializing a fresh authority pays the first-write surcharge
|
||||||
|
// alongside the new-account state gas and the indicator bytes.
|
||||||
|
name: "fresh authority",
|
||||||
|
tx: tx(existingEOA, 0, auth0),
|
||||||
|
wantRegular: base + cold + perAuth + aw,
|
||||||
|
wantState: authWorstState,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// An existing authority still pays the surcharge: the nonce and
|
||||||
|
// indicator stores are the first write to the account within the
|
||||||
|
// transaction.
|
||||||
|
name: "existing authority",
|
||||||
|
alloc: fundedAuthority,
|
||||||
|
tx: tx(existingEOA, 0, auth0),
|
||||||
|
wantRegular: base + cold + perAuth + aw,
|
||||||
|
wantState: authBaseState,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Self-sponsored: the sender's account write is prepaid by
|
||||||
|
// TX_BASE_COST, no surcharge.
|
||||||
|
name: "authority is sender",
|
||||||
|
tx: tx(existingEOA, 0, senderAuth),
|
||||||
|
wantRegular: base + cold + perAuth,
|
||||||
|
wantState: authBaseState,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// authority == tx.to with zero value: no TX_VALUE_COST was paid,
|
||||||
|
// so the authorization write is the first paid write and the
|
||||||
|
// surcharge applies. The recipient becomes delegated, adding a
|
||||||
|
// cold delegation-target access at runtime.
|
||||||
|
name: "authority is recipient, zero value",
|
||||||
|
alloc: fundedAuthority,
|
||||||
|
tx: tx(authority, 0, auth0),
|
||||||
|
wantRegular: base + cold + perAuth + aw + cold,
|
||||||
|
wantState: authBaseState,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// authority == tx.to with value: TX_VALUE_COST prepaid the
|
||||||
|
// recipient write, so no surcharge is due.
|
||||||
|
name: "authority is recipient, value",
|
||||||
|
alloc: fundedAuthority,
|
||||||
|
tx: tx(authority, 1, auth0),
|
||||||
|
wantRegular: base + cold + valueCst + perAuth + cold,
|
||||||
|
wantState: authBaseState,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Fresh authority == tx.to with value: the authorization pays the
|
||||||
|
// new-account state gas, and the recipient charge then sees an
|
||||||
|
// existing account, so the leaf is not paid for twice.
|
||||||
|
name: "authority is fresh recipient, value",
|
||||||
|
tx: tx(authority, 1, auth0),
|
||||||
|
wantRegular: base + cold + valueCst + perAuth + cold,
|
||||||
|
wantState: authWorstState,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// The same authority twice: only the first valid authorization
|
||||||
|
// carries the surcharge, the account creation and the indicator.
|
||||||
|
name: "same authority twice",
|
||||||
|
tx: tx(existingEOA, 0, auth0, auth1),
|
||||||
|
wantRegular: base + cold + 2*perAuth + aw,
|
||||||
|
wantState: authWorstState,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// An invalid authorization performs no write and does not count
|
||||||
|
// as the first write; the following valid one pays in full. The
|
||||||
|
// per-auth intrinsic base is still paid for the invalid tuple.
|
||||||
|
name: "invalid then valid",
|
||||||
|
tx: tx(existingEOA, 0, authBadNonce, auth0),
|
||||||
|
wantRegular: base + cold + 2*perAuth + aw,
|
||||||
|
wantState: authWorstState,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
alloc := types.GenesisAlloc{existingEOA: {Balance: big.NewInt(1)}}
|
||||||
|
for addr, acc := range tc.alloc {
|
||||||
|
alloc[addr] = acc
|
||||||
|
}
|
||||||
|
res, gp, err := applyMsg(t, mkState(senderAlloc(alloc)), tc.tx)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("consensus error: %v", err)
|
||||||
|
}
|
||||||
|
if res.Err != nil {
|
||||||
|
t.Fatalf("execution failed: %v", res.Err)
|
||||||
|
}
|
||||||
|
if gp.cumulativeRegular != tc.wantRegular {
|
||||||
|
t.Errorf("regular gas = %d, want %d", gp.cumulativeRegular, tc.wantRegular)
|
||||||
|
}
|
||||||
|
if gp.cumulativeState != tc.wantState {
|
||||||
|
t.Errorf("state gas = %d, want %d", gp.cumulativeState, tc.wantState)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEIP2780DelegationTargetPrewarmed pins the warm rate for delegation
|
||||||
|
// targets that are already in accessed_addresses when the recipient is
|
||||||
|
// loaded.
|
||||||
|
func TestEIP2780DelegationTargetPrewarmed(t *testing.T) {
|
||||||
|
const (
|
||||||
|
base = params.TxBaseCost2780
|
||||||
|
cold = params.ColdAccountAccessAmsterdam
|
||||||
|
warm = params.WarmAccountAccessAmsterdam
|
||||||
|
aw = params.AccountWriteAmsterdam
|
||||||
|
perAuth = params.RegularPerAuthBaseCost
|
||||||
|
)
|
||||||
|
delegatedAcct := common.HexToAddress("0xde1e000000000000000000000000000000000002")
|
||||||
|
|
||||||
|
t.Run("target is sender", func(t *testing.T) {
|
||||||
|
sdb := mkState(senderAlloc(types.GenesisAlloc{
|
||||||
|
delegatedAcct: {Code: types.AddressToDelegation(senderAddr)},
|
||||||
|
}))
|
||||||
|
res, gp, err := applyMsg(t, sdb, callTx(0, delegatedAcct, 0, 100_000, nil))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("consensus error: %v", err)
|
||||||
|
}
|
||||||
|
if res.Err != nil {
|
||||||
|
t.Fatalf("execution failed: %v", res.Err)
|
||||||
|
}
|
||||||
|
if want := base + cold + warm; gp.cumulativeRegular != want {
|
||||||
|
t.Errorf("regular gas = %d, want %d (warm delegation target)", gp.cumulativeRegular, want)
|
||||||
|
}
|
||||||
|
if gp.cumulativeState != 0 {
|
||||||
|
t.Errorf("state gas = %d, want 0", gp.cumulativeState)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("target warmed by authorization", func(t *testing.T) {
|
||||||
|
// A clearing authorization from a fresh authority: it creates the
|
||||||
|
// authority account (nonce bump) and warms it, without installing an
|
||||||
|
// indicator.
|
||||||
|
//
|
||||||
|
// The recipient's pre-existing delegation then resolves to
|
||||||
|
// the freshly warmed, codeless authority at the warm rate.
|
||||||
|
authClear, authority := signAuth(t, authKeyA, common.Address{}, 0)
|
||||||
|
sdb := mkState(senderAlloc(types.GenesisAlloc{
|
||||||
|
delegatedAcct: {Code: types.AddressToDelegation(authority)},
|
||||||
|
}))
|
||||||
|
res, gp, err := applyMsg(t, sdb, setCodeTx(0, delegatedAcct, []types.SetCodeAuthorization{authClear}))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("consensus error: %v", err)
|
||||||
|
}
|
||||||
|
if res.Err != nil {
|
||||||
|
t.Fatalf("execution failed: %v", res.Err)
|
||||||
|
}
|
||||||
|
if want := base + cold + perAuth + aw + warm; gp.cumulativeRegular != want {
|
||||||
|
t.Errorf("regular gas = %d, want %d (auth-warmed delegation target)", gp.cumulativeRegular, want)
|
||||||
|
}
|
||||||
|
if gp.cumulativeState != newAccountState {
|
||||||
|
t.Errorf("state gas = %d, want %d (authority account created)", gp.cumulativeState, newAccountState)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"math/big"
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
|
@ -72,6 +73,40 @@ func mkState(alloc types.GenesisAlloc) *state.StateDB {
|
||||||
return sdb
|
return sdb
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mkCommittedState is mkState with the allocation committed to disk and
|
||||||
|
// reloaded. EIP-161-empty accounts carrying only storage do not survive an
|
||||||
|
// in-memory Finalise; committing without empty-account deletion reproduces
|
||||||
|
// the synthesized prestate an EIP-7610 fixture would load from disk.
|
||||||
|
func mkCommittedState(t *testing.T, alloc types.GenesisAlloc) *state.StateDB {
|
||||||
|
t.Helper()
|
||||||
|
db := state.NewDatabaseForTesting()
|
||||||
|
sdb, _ := state.New(types.EmptyRootHash, db)
|
||||||
|
for addr, acc := range alloc {
|
||||||
|
sdb.CreateAccount(addr)
|
||||||
|
if acc.Balance != nil {
|
||||||
|
sdb.AddBalance(addr, uint256.MustFromBig(acc.Balance), tracing.BalanceChangeUnspecified)
|
||||||
|
}
|
||||||
|
if acc.Nonce != 0 {
|
||||||
|
sdb.SetNonce(addr, acc.Nonce, tracing.NonceChangeGenesis)
|
||||||
|
}
|
||||||
|
if len(acc.Code) != 0 {
|
||||||
|
sdb.SetCode(addr, acc.Code, tracing.CodeChangeUnspecified)
|
||||||
|
}
|
||||||
|
for k, v := range acc.Storage {
|
||||||
|
sdb.SetState(addr, k, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
root, err := sdb.Commit(0, false, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("commit prestate: %v", err)
|
||||||
|
}
|
||||||
|
sdb, err = state.New(root, db)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("reopen prestate: %v", err)
|
||||||
|
}
|
||||||
|
return sdb
|
||||||
|
}
|
||||||
|
|
||||||
// amsterdamCoreEVM builds an Amsterdam EVM over statedb with fees disabled.
|
// amsterdamCoreEVM builds an Amsterdam EVM over statedb with fees disabled.
|
||||||
func amsterdamCoreEVM(sdb *state.StateDB) *vm.EVM {
|
func amsterdamCoreEVM(sdb *state.StateDB) *vm.EVM {
|
||||||
ctx := vm.BlockContext{
|
ctx := vm.BlockContext{
|
||||||
|
|
@ -99,15 +134,24 @@ func applyMsg(t *testing.T, sdb *state.StateDB, tx *types.Transaction) (*Executi
|
||||||
t.Fatalf("to message: %v", err)
|
t.Fatalf("to message: %v", err)
|
||||||
}
|
}
|
||||||
gp := NewGasPool(evm.Context.GasLimit)
|
gp := NewGasPool(evm.Context.GasLimit)
|
||||||
// Drive the stateTransition directly (as ApplyMessage does) so the test can
|
|
||||||
// inspect the final tx-level GasBudget vector via st.gasRemaining.
|
|
||||||
evm.SetTxContext(NewEVMTxContext(msg))
|
evm.SetTxContext(NewEVMTxContext(msg))
|
||||||
st := newStateTransition(evm, msg, gp)
|
st := newStateTransition(evm, msg, gp)
|
||||||
res, err := st.execute()
|
res, err := st.execute()
|
||||||
if err == nil && res != nil {
|
if err == nil && res != nil {
|
||||||
assertPoolSane(t, res, gp)
|
floor, ferr := FloorDataGas(rules8037, msg.From, msg.To, msg.Value, msg.Data, msg.AccessList)
|
||||||
limit := min(msg.GasLimit, params.MaxTxGas)
|
if ferr != nil {
|
||||||
assertBudgetSane(t, vm.NewGasBudget(limit, msg.GasLimit-limit), st.gasRemaining)
|
t.Fatalf("floor data gas: %v", ferr)
|
||||||
|
}
|
||||||
|
assertPoolSane(t, res, gp, floor)
|
||||||
|
|
||||||
|
intrinsic, ierr := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, msg.From, msg.To, msg.Value, rules8037)
|
||||||
|
if ierr != nil {
|
||||||
|
t.Fatalf("intrinsic gas: %v", ierr)
|
||||||
|
}
|
||||||
|
executionGas := msg.GasLimit - intrinsic
|
||||||
|
gasLeft := min(params.MaxTxGas-intrinsic, executionGas)
|
||||||
|
assertBudgetSane(t, vm.NewGasBudget(gasLeft, executionGas-gasLeft), st.gasRemaining)
|
||||||
}
|
}
|
||||||
return res, gp, err
|
return res, gp, err
|
||||||
}
|
}
|
||||||
|
|
@ -136,9 +180,11 @@ func assertBudgetSane(t *testing.T, initial, got vm.GasBudget) {
|
||||||
// assertPoolSane validates the whole 2D block-gas-pool vector after a single tx.
|
// assertPoolSane validates the whole 2D block-gas-pool vector after a single tx.
|
||||||
//
|
//
|
||||||
// receipt: cumulativeUsed == res.UsedGas <= res.MaxUsedGas
|
// receipt: cumulativeUsed == res.UsedGas <= res.MaxUsedGas
|
||||||
// pre-refund: cumulativeRegular + cumulativeState <= res.MaxUsedGas (peak)
|
// regular: cumulativeRegular <= max(res.MaxUsedGas - cumulativeState, floor)
|
||||||
|
// (the calldata floor pads the regular dimension alone, so the
|
||||||
|
// dimension sum may exceed the pre-refund peak when it binds)
|
||||||
// bottleneck: Used() == max(cumulativeRegular, cumulativeState) <= initial
|
// bottleneck: Used() == max(cumulativeRegular, cumulativeState) <= initial
|
||||||
func assertPoolSane(t *testing.T, res *ExecutionResult, gp *GasPool) {
|
func assertPoolSane(t *testing.T, res *ExecutionResult, gp *GasPool, floor uint64) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
if gp.cumulativeUsed != res.UsedGas {
|
if gp.cumulativeUsed != res.UsedGas {
|
||||||
t.Fatalf("receipt scalar = %d, want UsedGas %d", gp.cumulativeUsed, res.UsedGas)
|
t.Fatalf("receipt scalar = %d, want UsedGas %d", gp.cumulativeUsed, res.UsedGas)
|
||||||
|
|
@ -146,8 +192,12 @@ func assertPoolSane(t *testing.T, res *ExecutionResult, gp *GasPool) {
|
||||||
if res.UsedGas > res.MaxUsedGas {
|
if res.UsedGas > res.MaxUsedGas {
|
||||||
t.Fatalf("post-refund gas %d exceeds peak %d", res.UsedGas, res.MaxUsedGas)
|
t.Fatalf("post-refund gas %d exceeds peak %d", res.UsedGas, res.MaxUsedGas)
|
||||||
}
|
}
|
||||||
if sum := gp.cumulativeRegular + gp.cumulativeState; sum > res.MaxUsedGas {
|
if gp.cumulativeState > res.MaxUsedGas {
|
||||||
t.Fatalf("regular+state %d exceeds peak %d", sum, res.MaxUsedGas)
|
t.Fatalf("state %d exceeds peak %d", gp.cumulativeState, res.MaxUsedGas)
|
||||||
|
}
|
||||||
|
if cap := max(res.MaxUsedGas-gp.cumulativeState, floor); gp.cumulativeRegular > cap {
|
||||||
|
t.Fatalf("regular %d exceeds pre-refund cap %d (peak %d, state %d, floor %d)",
|
||||||
|
gp.cumulativeRegular, cap, res.MaxUsedGas, gp.cumulativeState, floor)
|
||||||
}
|
}
|
||||||
if gp.Used() != max(gp.cumulativeRegular, gp.cumulativeState) {
|
if gp.Used() != max(gp.cumulativeRegular, gp.cumulativeState) {
|
||||||
t.Fatalf("block used %d != max(%d,%d)", gp.Used(), gp.cumulativeRegular, gp.cumulativeState)
|
t.Fatalf("block used %d != max(%d,%d)", gp.Used(), gp.cumulativeRegular, gp.cumulativeState)
|
||||||
|
|
@ -185,23 +235,26 @@ func createTx(nonce, gas uint64, initCode []byte) *types.Transaction {
|
||||||
var (
|
var (
|
||||||
deploy3 = []byte{0x60, 0x03, 0x60, 0x00, 0xf3} // init: return 3 bytes of code
|
deploy3 = []byte{0x60, 0x03, 0x60, 0x00, 0xf3} // init: return 3 bytes of code
|
||||||
revertI = []byte{0x60, 0x00, 0x60, 0x00, 0xfd} // init: REVERT
|
revertI = []byte{0x60, 0x00, 0x60, 0x00, 0xfd} // init: REVERT
|
||||||
|
haltI = []byte{0xfe, 0x00, 0x00, 0x00, 0x00} // init: INVALID, exceptional halt
|
||||||
)
|
)
|
||||||
|
|
||||||
// ===================== Top-level create transaction ======================
|
// ===================== Top-level create transaction ======================
|
||||||
|
|
||||||
// A creation tx's intrinsic gas pre-charges one account creation as state gas.
|
// A creation tx's intrinsic gas is state-independent: the new-account state
|
||||||
func TestCreateTxIntrinsicChargesAccountUnconditionally(t *testing.T) {
|
// charge depends on whether the deployment target exists and is charged at
|
||||||
cost, err := IntrinsicGas(nil, nil, nil, common.Address{}, nil, nil, rules8037, params.CostPerStateByte)
|
// runtime (EIP-2780), not intrinsically.
|
||||||
|
func TestCreateTxIntrinsicNoStateGas(t *testing.T) {
|
||||||
|
cost, err := IntrinsicGas(nil, nil, nil, common.Address{}, nil, nil, rules8037)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if cost.StateGas != newAccountState {
|
if want := params.TxBaseCost2780 + params.CreateAccessAmsterdam; cost != want {
|
||||||
t.Fatalf("intrinsic state gas = %d, want %d", cost.StateGas, newAccountState)
|
t.Fatalf("intrinsic gas = %d, want %d", cost, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creating onto a pre-existing (balance-only) address refills the account
|
// Creating onto a pre-existing (balance-only) address incurs no new-account
|
||||||
// portion; only the code deposit is charged as state gas.
|
// runtime charge; only the code deposit is charged as state gas.
|
||||||
func TestCreateTxPreexistingDestRefill(t *testing.T) {
|
func TestCreateTxPreexistingDestRefill(t *testing.T) {
|
||||||
derived := crypto.CreateAddress(senderAddr, 0)
|
derived := crypto.CreateAddress(senderAddr, 0)
|
||||||
sdb := mkState(senderAlloc(types.GenesisAlloc{derived: {Balance: big.NewInt(1)}}))
|
sdb := mkState(senderAlloc(types.GenesisAlloc{derived: {Balance: big.NewInt(1)}}))
|
||||||
|
|
@ -214,7 +267,8 @@ func TestCreateTxPreexistingDestRefill(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A creation tx that reverts refills the account-creation charge.
|
// A creation tx that reverts refills the account-creation charge applied at
|
||||||
|
// runtime.
|
||||||
func TestCreateTxRevertRefill(t *testing.T) {
|
func TestCreateTxRevertRefill(t *testing.T) {
|
||||||
sdb := mkState(senderAlloc(nil))
|
sdb := mkState(senderAlloc(nil))
|
||||||
res, gp, err := applyMsg(t, sdb, createTx(0, 1_000_000, revertI))
|
res, gp, err := applyMsg(t, sdb, createTx(0, 1_000_000, revertI))
|
||||||
|
|
@ -229,7 +283,8 @@ func TestCreateTxRevertRefill(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// An address collision burns gas_left while refilling the account charge.
|
// An address collision burns gas_left. The colliding target exists, so no
|
||||||
|
// new-account state gas is charged at runtime in the first place.
|
||||||
func TestCreateTxCollisionConsumesGasLeft(t *testing.T) {
|
func TestCreateTxCollisionConsumesGasLeft(t *testing.T) {
|
||||||
const gas = 1_000_000
|
const gas = 1_000_000
|
||||||
derived := crypto.CreateAddress(senderAddr, 0)
|
derived := crypto.CreateAddress(senderAddr, 0)
|
||||||
|
|
@ -241,14 +296,185 @@ func TestCreateTxCollisionConsumesGasLeft(t *testing.T) {
|
||||||
if !res.Failed() {
|
if !res.Failed() {
|
||||||
t.Fatal("expected collision failure")
|
t.Fatal("expected collision failure")
|
||||||
}
|
}
|
||||||
|
if gp.cumulativeState != 0 {
|
||||||
|
t.Fatalf("state gas = %d, want 0 (never charged)", gp.cumulativeState)
|
||||||
|
}
|
||||||
|
// All forwarded gas_left is burned: the whole gas limit is consumed as
|
||||||
|
// regular gas.
|
||||||
|
if want := uint64(gas); gp.cumulativeRegular != want {
|
||||||
|
t.Fatalf("regular gas = %d, want %d", gp.cumulativeRegular, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An account can exist yet be EIP-161-empty in the middle of a transaction,
|
||||||
|
// e.g. after being touched as the zero-balance beneficiary of a SELFDESTRUCT.
|
||||||
|
// Deploying onto such an account should charge account-creation cost.
|
||||||
|
func TestCreate2TransientEmptyDestNoRefill(t *testing.T) {
|
||||||
|
var (
|
||||||
|
orchestrator = common.HexToAddress("0xc0de000000000000000000000000000000000002")
|
||||||
|
destructor = common.HexToAddress("0xc0de000000000000000000000000000000000003")
|
||||||
|
target = crypto.CreateAddress2(orchestrator, [32]byte{}, crypto.Keccak256(deploy3))
|
||||||
|
)
|
||||||
|
// destructor: SELFDESTRUCT with zero balance to the future CREATE2 target,
|
||||||
|
// leaving it existing but EIP-161-empty for the rest of the transaction.
|
||||||
|
destructorCode := append(append([]byte{0x73}, target.Bytes()...), 0xff) // PUSH20 target, SELFDESTRUCT
|
||||||
|
|
||||||
|
// orchestrator: CALL destructor (persist the success flag in slot 0),
|
||||||
|
// then CREATE2 deploy3 with salt 0, targeting the touched address.
|
||||||
|
code := []byte{
|
||||||
|
0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, 0x60, 0x00, // ret/arg sizes and offsets, value = 0
|
||||||
|
0x73, // PUSH20 destructor
|
||||||
|
}
|
||||||
|
code = append(code, destructor.Bytes()...)
|
||||||
|
code = append(code,
|
||||||
|
0x62, 0x03, 0x0d, 0x40, // PUSH3 200,000 call gas
|
||||||
|
0xf1, // CALL
|
||||||
|
0x60, 0x00, 0x55, // SSTORE the call result at slot 0
|
||||||
|
0x64, 0x60, 0x03, 0x60, 0x00, 0xf3, // PUSH5 deploy3 init code
|
||||||
|
0x60, 0x00, 0x52, // MSTORE at word 0 (right-aligned, code at offset 27)
|
||||||
|
0x60, 0x00, // salt = 0
|
||||||
|
0x60, 0x05, // size = 5
|
||||||
|
0x60, 0x1b, // offset = 27
|
||||||
|
0x60, 0x00, // endowment = 0
|
||||||
|
0xf5, 0x50, // CREATE2, POP
|
||||||
|
0x00, // STOP
|
||||||
|
)
|
||||||
|
sdb := mkState(senderAlloc(types.GenesisAlloc{
|
||||||
|
orchestrator: {Code: code},
|
||||||
|
destructor: {Code: destructorCode},
|
||||||
|
}))
|
||||||
|
res, gp, err := applyMsg(t, sdb, callTx(0, orchestrator, 0, 2_000_000, nil))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if res.Failed() {
|
||||||
|
t.Fatalf("execution failed: %v", res.Err)
|
||||||
|
}
|
||||||
|
// The inner call must have succeeded, so the target was touched into an
|
||||||
|
// existing-but-empty account before the CREATE2 executed.
|
||||||
|
if flag := sdb.GetState(orchestrator, common.Hash{}); flag != common.BigToHash(big.NewInt(1)) {
|
||||||
|
t.Fatalf("destructor call flag = %v, want 1", flag)
|
||||||
|
}
|
||||||
|
if code := sdb.GetCode(target); len(code) != 3 {
|
||||||
|
t.Fatalf("deployed code length = %d, want 3", len(code))
|
||||||
|
}
|
||||||
|
// State gas: the orchestrator's flag slot, the created contract account
|
||||||
|
// (charged, not refilled) and the 3-byte code deposit.
|
||||||
|
want := newSlotState + newAccountState + uint64(3*params.CostPerStateByte)
|
||||||
|
if gp.cumulativeState != want {
|
||||||
|
t.Fatalf("state gas = %d, want %d (account creation must not be refilled)", gp.cumulativeState, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Storage-only (EIP-7610-shaped) deployment destination ===========
|
||||||
|
//
|
||||||
|
// A destination carrying storage while having zero nonce, zero balance and
|
||||||
|
// empty code is EIP-161-empty, so the account-creation state gas is
|
||||||
|
// pre-charged in the parent frame.
|
||||||
|
|
||||||
|
// create2Orchestrator returns runtime code that CREATE2-deploys the given
|
||||||
|
// 5-byte init code with salt 0 and stores the result address at slot 0.
|
||||||
|
func create2Orchestrator(initCode []byte) []byte {
|
||||||
|
code := append([]byte{0x64}, initCode...) // PUSH5 init code
|
||||||
|
return append(code,
|
||||||
|
0x60, 0x00, 0x52, // MSTORE at word 0 (right-aligned, code at offset 27)
|
||||||
|
0x60, 0x00, // salt = 0
|
||||||
|
0x60, 0x05, // size = 5
|
||||||
|
0x60, 0x1b, // offset = 27
|
||||||
|
0x60, 0x00, // endowment = 0
|
||||||
|
0xf5, // CREATE2
|
||||||
|
0x60, 0x00, 0x55, // SSTORE the result address at slot 0
|
||||||
|
0x00, // STOP
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// storageOnlyAlloc allocates the orchestrator and its CREATE2 target, the
|
||||||
|
// latter carrying a single storage slot while remaining EIP-161-empty.
|
||||||
|
func storageOnlyAlloc(orchestrator common.Address, initCode []byte) (types.GenesisAlloc, common.Address) {
|
||||||
|
target := crypto.CreateAddress2(orchestrator, [32]byte{}, crypto.Keccak256(initCode))
|
||||||
|
return types.GenesisAlloc{
|
||||||
|
orchestrator: {Code: create2Orchestrator(initCode)},
|
||||||
|
target: {Storage: map[common.Hash]common.Hash{{}: common.BigToHash(big.NewInt(1))}},
|
||||||
|
}, target
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deploying onto a storage-only destination pre-charges the account creation.
|
||||||
|
// Under the registry-based EIP-7610 check the creation proceeds, so the
|
||||||
|
// charge is consumed like any other creation.
|
||||||
|
func TestCreate2StorageOnlyDestCharged(t *testing.T) {
|
||||||
|
orchestrator := common.HexToAddress("0xc0de000000000000000000000000000000000004")
|
||||||
|
alloc, target := storageOnlyAlloc(orchestrator, deploy3)
|
||||||
|
sdb := mkCommittedState(t, senderAlloc(alloc))
|
||||||
|
res, gp, err := applyMsg(t, sdb, callTx(0, orchestrator, 0, 1_000_000, nil))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if res.Failed() {
|
||||||
|
t.Fatalf("execution failed: %v", res.Err)
|
||||||
|
}
|
||||||
|
if code := sdb.GetCode(target); len(code) != 3 {
|
||||||
|
t.Fatalf("deployed code length = %d, want 3", len(code))
|
||||||
|
}
|
||||||
|
// The created account (charged, consumed), the orchestrator's result slot
|
||||||
|
// and the 3-byte code deposit.
|
||||||
|
want := newAccountState + newSlotState + uint64(3*params.CostPerStateByte)
|
||||||
|
if gp.cumulativeState != want {
|
||||||
|
t.Fatalf("state gas = %d, want %d", gp.cumulativeState, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the pre-charge succeeds and the create frame then fails, only the create
|
||||||
|
// frame halts: the forwarded regular gas is burnt, the account-creation
|
||||||
|
// charge is refilled, and the parent frame continues.
|
||||||
|
func TestCreate2StorageOnlyDestRefillOnFrameHalt(t *testing.T) {
|
||||||
|
const gas = 1_000_000
|
||||||
|
orchestrator := common.HexToAddress("0xc0de000000000000000000000000000000000005")
|
||||||
|
alloc, target := storageOnlyAlloc(orchestrator, haltI)
|
||||||
|
sdb := mkCommittedState(t, senderAlloc(alloc))
|
||||||
|
res, gp, err := applyMsg(t, sdb, callTx(0, orchestrator, 0, gas, nil))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if res.Failed() {
|
||||||
|
t.Fatalf("parent frame must survive the create-frame halt: %v", res.Err)
|
||||||
|
}
|
||||||
|
// The CREATE2 pushed zero and nothing was deployed.
|
||||||
|
if flag := sdb.GetState(orchestrator, common.Hash{}); flag != (common.Hash{}) {
|
||||||
|
t.Fatalf("create result = %v, want 0", flag)
|
||||||
|
}
|
||||||
|
if code := sdb.GetCode(target); len(code) != 0 {
|
||||||
|
t.Fatalf("deployed code length = %d, want 0", len(code))
|
||||||
|
}
|
||||||
|
// The account-creation charge was refilled in full.
|
||||||
if gp.cumulativeState != 0 {
|
if gp.cumulativeState != 0 {
|
||||||
t.Fatalf("state gas = %d, want 0 (refilled)", gp.cumulativeState)
|
t.Fatalf("state gas = %d, want 0 (refilled)", gp.cumulativeState)
|
||||||
}
|
}
|
||||||
// All forwarded gas_left is burned; only the refilled account charge (which
|
if res.UsedGas > gas-newAccountState {
|
||||||
// had spilled into regular) returns to gas_left. So regular gas consumed is
|
t.Fatalf("used gas = %d, want at most %d (charge not refilled?)", res.UsedGas, gas-newAccountState)
|
||||||
// exactly tx.gas - newAccountState, with no other refund.
|
}
|
||||||
if want := uint64(gas) - newAccountState; gp.cumulativeRegular != want {
|
}
|
||||||
t.Fatalf("regular gas = %d, want %d", gp.cumulativeRegular, want)
|
|
||||||
|
// If the remaining gas cannot cover the account-creation pre-charge, the
|
||||||
|
// parent frame itself halts with out-of-gas instead of the create frame.
|
||||||
|
func TestCreate2StorageOnlyDestPrechargeOOG(t *testing.T) {
|
||||||
|
// Enough for the CREATE2 constant cost, short of the 183,600 pre-charge.
|
||||||
|
const gas = 150_000
|
||||||
|
orchestrator := common.HexToAddress("0xc0de000000000000000000000000000000000006")
|
||||||
|
alloc, _ := storageOnlyAlloc(orchestrator, deploy3)
|
||||||
|
sdb := mkCommittedState(t, senderAlloc(alloc))
|
||||||
|
res, gp, err := applyMsg(t, sdb, callTx(0, orchestrator, 0, gas, nil))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !res.Failed() || !errors.Is(res.Err, vm.ErrOutOfGas) {
|
||||||
|
t.Fatalf("err = %v, want out of gas in the parent frame", res.Err)
|
||||||
|
}
|
||||||
|
if gp.cumulativeState != 0 {
|
||||||
|
t.Fatalf("state gas = %d, want 0 (charge never applied)", gp.cumulativeState)
|
||||||
|
}
|
||||||
|
// The parent is the topmost frame, so its halt burns the whole gas limit.
|
||||||
|
if gp.cumulativeRegular != gas {
|
||||||
|
t.Fatalf("regular gas = %d, want %d", gp.cumulativeRegular, gas)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -300,15 +526,50 @@ func TestValidationIntrinsicRegularCap(t *testing.T) {
|
||||||
for i := range al {
|
for i := range al {
|
||||||
al[i].Address = common.BigToAddress(big.NewInt(int64(i + 1)))
|
al[i].Address = common.BigToAddress(big.NewInt(int64(i + 1)))
|
||||||
}
|
}
|
||||||
tx := types.MustSignNewTx(senderKey, signer8037, &types.DynamicFeeTx{
|
tx := types.MustSignNewTx(senderKey, signer8037,
|
||||||
ChainID: cfg8037.ChainID, Nonce: 0, To: &senderAddr, Value: big.NewInt(0),
|
&types.DynamicFeeTx{
|
||||||
Gas: 25_000_000, GasFeeCap: big.NewInt(0), GasTipCap: big.NewInt(0), AccessList: al,
|
ChainID: cfg8037.ChainID,
|
||||||
})
|
Nonce: 0,
|
||||||
|
To: &senderAddr,
|
||||||
|
Value: big.NewInt(0),
|
||||||
|
Gas: 25_000_000,
|
||||||
|
GasFeeCap: big.NewInt(0),
|
||||||
|
GasTipCap: big.NewInt(0),
|
||||||
|
AccessList: al,
|
||||||
|
})
|
||||||
if _, _, err := applyMsg(t, mkState(senderAlloc(nil)), tx); err == nil {
|
if _, _, err := applyMsg(t, mkState(senderAlloc(nil)), tx); err == nil {
|
||||||
t.Fatal("expected rejection for intrinsic regular over MaxTxGas")
|
t.Fatal("expected rejection for intrinsic regular over MaxTxGas")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The EIP-7623/7976 calldata floor is capped by MaxTxGas even when the gas
|
||||||
|
// limit covers it: a transaction whose floor cost exceeds the cap is rejected
|
||||||
|
// regardless of its (much smaller) intrinsic gas.
|
||||||
|
func TestValidationFloorCostCap(t *testing.T) {
|
||||||
|
// All-zero calldata: the floor charges 64/byte while the intrinsic
|
||||||
|
// charges only 4/byte, so the floor crosses the cap long before the
|
||||||
|
// intrinsic does.
|
||||||
|
data := make([]byte, 300_000) // floor ~19.2M > 16.77M cap, intrinsic ~1.2M
|
||||||
|
floor, err := FloorDataGas(rules8037, senderAddr, &senderAddr, new(uint256.Int), data, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
intrinsic, err := IntrinsicGas(data, nil, nil, senderAddr, &senderAddr, new(uint256.Int), rules8037)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if floor <= params.MaxTxGas || intrinsic > params.MaxTxGas {
|
||||||
|
t.Fatalf("setup: floor %d must exceed cap %d while intrinsic %d stays below",
|
||||||
|
floor, params.MaxTxGas, intrinsic)
|
||||||
|
}
|
||||||
|
// The gas limit covers the floor, so the rejection can only come from
|
||||||
|
// the MaxTxGas cap on the floor cost.
|
||||||
|
tx := callTx(0, senderAddr, 0, floor+1_000_000, data)
|
||||||
|
if _, _, err := applyMsg(t, mkState(senderAlloc(nil)), tx); !errors.Is(err, ErrFloorDataGas) {
|
||||||
|
t.Fatalf("expected ErrFloorDataGas, got %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ========================= Refund and gas used ===========================
|
// ========================= Refund and gas used ===========================
|
||||||
|
|
||||||
// clearSlots deploys a contract that zeroes slots 1..n, each preset to 1.
|
// clearSlots deploys a contract that zeroes slots 1..n, each preset to 1.
|
||||||
|
|
@ -472,18 +733,23 @@ const authKeyA = "02020202020202020202020202020202020202020202020202020020202020
|
||||||
|
|
||||||
var delegate8037 = common.HexToAddress("0xde1e8a7e")
|
var delegate8037 = common.HexToAddress("0xde1e8a7e")
|
||||||
|
|
||||||
// Intrinsic gas pre-charges the worst-case (account + indicator) per auth.
|
// Intrinsic gas charges only the state-independent per-authorization base;
|
||||||
func TestAuthIntrinsicWorstCase(t *testing.T) {
|
// the state-dependent charges are applied at runtime (EIP-2780).
|
||||||
cost, err := IntrinsicGas(nil, nil, []types.SetCodeAuthorization{{}}, common.Address{}, &delegate8037, nil, rules8037, params.CostPerStateByte)
|
func TestAuthIntrinsicBaseOnly(t *testing.T) {
|
||||||
|
cost, err := IntrinsicGas(nil, nil, []types.SetCodeAuthorization{{}}, common.Address{}, &delegate8037, nil, rules8037)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if cost.StateGas != authWorstState {
|
// The recipient touch and the per-authorization authority access (priced
|
||||||
t.Fatalf("intrinsic state gas = %d, want %d", cost.StateGas, authWorstState)
|
// into RegularPerAuthBaseCost) are both charged at the cold rate
|
||||||
|
// unconditionally at the intrinsic phase (EIP-2780).
|
||||||
|
want := params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam + params.RegularPerAuthBaseCost
|
||||||
|
if cost != want {
|
||||||
|
t.Fatalf("intrinsic gas = %d, want %d", cost, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// An invalid authorization refills its entire intrinsic state-gas charge.
|
// An invalid authorization incurs no runtime state-gas charge.
|
||||||
func TestAuthInvalidRefillFull(t *testing.T) {
|
func TestAuthInvalidRefillFull(t *testing.T) {
|
||||||
k, _ := crypto.HexToECDSA(authKeyA)
|
k, _ := crypto.HexToECDSA(authKeyA)
|
||||||
bad, _ := types.SignSetCode(k, types.SetCodeAuthorization{
|
bad, _ := types.SignSetCode(k, types.SetCodeAuthorization{
|
||||||
|
|
@ -499,7 +765,8 @@ func TestAuthInvalidRefillFull(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A pre-existing authority refills the account portion (indicator stands).
|
// A pre-existing authority is not charged for an account leaf; only the
|
||||||
|
// net-new indicator bytes are charged at runtime.
|
||||||
func TestAuthAccountExistsRefill(t *testing.T) {
|
func TestAuthAccountExistsRefill(t *testing.T) {
|
||||||
auth, authority := signAuth(t, authKeyA, delegate8037, 0)
|
auth, authority := signAuth(t, authKeyA, delegate8037, 0)
|
||||||
sdb := mkState(senderAlloc(types.GenesisAlloc{authority: {Balance: big.NewInt(1)}}))
|
sdb := mkState(senderAlloc(types.GenesisAlloc{authority: {Balance: big.NewInt(1)}}))
|
||||||
|
|
@ -508,12 +775,12 @@ func TestAuthAccountExistsRefill(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if gp.cumulativeState != authBaseState {
|
if gp.cumulativeState != authBaseState {
|
||||||
t.Fatalf("state gas = %d, want %d (account refilled)", gp.cumulativeState, authBaseState)
|
t.Fatalf("state gas = %d, want %d (indicator only)", gp.cumulativeState, authBaseState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Setting a delegation on an already-delegated authority refills the indicator
|
// Setting a delegation on an already-delegated authority writes no net-new
|
||||||
// portion (and the account portion, since the authority already exists).
|
// bytes (and no account leaf, since the authority exists): no state charge.
|
||||||
func TestAuthSetOnDelegatedRefillBase(t *testing.T) {
|
func TestAuthSetOnDelegatedRefillBase(t *testing.T) {
|
||||||
auth, authority := signAuth(t, authKeyA, delegate8037, 0)
|
auth, authority := signAuth(t, authKeyA, delegate8037, 0)
|
||||||
pre := types.AddressToDelegation(common.HexToAddress("0xabcd"))
|
pre := types.AddressToDelegation(common.HexToAddress("0xabcd"))
|
||||||
|
|
@ -523,11 +790,12 @@ func TestAuthSetOnDelegatedRefillBase(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if gp.cumulativeState != 0 {
|
if gp.cumulativeState != 0 {
|
||||||
t.Fatalf("state gas = %d, want 0 (account+indicator refilled)", gp.cumulativeState)
|
t.Fatalf("state gas = %d, want 0 (nothing net-new)", gp.cumulativeState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A net-new delegation on a fresh authority keeps the full worst-case charge.
|
// A net-new delegation on a fresh authority is charged the account leaf plus
|
||||||
|
// the indicator bytes at runtime.
|
||||||
func TestAuthSetNetNewNoRefill(t *testing.T) {
|
func TestAuthSetNetNewNoRefill(t *testing.T) {
|
||||||
auth, _ := signAuth(t, authKeyA, delegate8037, 0)
|
auth, _ := signAuth(t, authKeyA, delegate8037, 0)
|
||||||
sdb := mkState(senderAlloc(nil))
|
sdb := mkState(senderAlloc(nil))
|
||||||
|
|
@ -536,11 +804,12 @@ func TestAuthSetNetNewNoRefill(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if gp.cumulativeState != authWorstState {
|
if gp.cumulativeState != authWorstState {
|
||||||
t.Fatalf("state gas = %d, want %d (no refill)", gp.cumulativeState, authWorstState)
|
t.Fatalf("state gas = %d, want %d (leaf + indicator)", gp.cumulativeState, authWorstState)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clearing a delegation writes no indicator, so the indicator portion refills.
|
// Clearing a delegation writes no indicator, so only the (new) account leaf is
|
||||||
|
// charged at runtime.
|
||||||
func TestAuthClearRefillBase(t *testing.T) {
|
func TestAuthClearRefillBase(t *testing.T) {
|
||||||
auth, _ := signAuth(t, authKeyA, common.Address{}, 0) // clear (address ZERO)
|
auth, _ := signAuth(t, authKeyA, common.Address{}, 0) // clear (address ZERO)
|
||||||
sdb := mkState(senderAlloc(nil))
|
sdb := mkState(senderAlloc(nil))
|
||||||
|
|
@ -549,13 +818,14 @@ func TestAuthClearRefillBase(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if want := newAccountState; gp.cumulativeState != want {
|
if want := newAccountState; gp.cumulativeState != want {
|
||||||
t.Fatalf("state gas = %d, want %d (indicator refilled)", gp.cumulativeState, want)
|
t.Fatalf("state gas = %d, want %d (account leaf only)", gp.cumulativeState, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 0->a->0 in one tx: the indicator created by an earlier auth and cleared by a
|
// 0->a->0 in one tx: the indicator charge applies when the delegation is set
|
||||||
// later one writes zero net bytes, so both indicator charges refill.
|
// and is never credited back when a later auth clears it in the same
|
||||||
func TestAuthClearSameTxDoubleRefill(t *testing.T) {
|
// transaction.
|
||||||
|
func TestAuthClearSameTxNoRefill(t *testing.T) {
|
||||||
set, authority := signAuth(t, authKeyA, delegate8037, 0)
|
set, authority := signAuth(t, authKeyA, delegate8037, 0)
|
||||||
clr, _ := signAuth(t, authKeyA, common.Address{}, 1)
|
clr, _ := signAuth(t, authKeyA, common.Address{}, 1)
|
||||||
sdb := mkState(senderAlloc(nil))
|
sdb := mkState(senderAlloc(nil))
|
||||||
|
|
@ -564,8 +834,28 @@ func TestAuthClearSameTxDoubleRefill(t *testing.T) {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
_ = authority
|
_ = authority
|
||||||
if want := newAccountState; gp.cumulativeState != want {
|
if want := authWorstState; gp.cumulativeState != want {
|
||||||
t.Fatalf("state gas = %d, want %d (net-zero delegation)", gp.cumulativeState, want)
|
t.Fatalf("state gas = %d, want %d (indicator charge kept on clear)", gp.cumulativeState, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0->a->0->b in one tx: the indicator charge applies at most once per
|
||||||
|
// authority — re-installing a delegation after an intra-tx clear is free.
|
||||||
|
func TestAuthSetClearSetChargedOnce(t *testing.T) {
|
||||||
|
set, _ := signAuth(t, authKeyA, delegate8037, 0)
|
||||||
|
clr, _ := signAuth(t, authKeyA, common.Address{}, 1)
|
||||||
|
set2, authority := signAuth(t, authKeyA, common.HexToAddress("0xde1e8a7f"), 2)
|
||||||
|
sdb := mkState(senderAlloc(nil))
|
||||||
|
_, gp, err := applyMsg(t, sdb, setCodeTx(0, senderAddr, []types.SetCodeAuthorization{set, clr, set2}))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// The final delegation is installed and the indicator was paid exactly once.
|
||||||
|
if _, delegated := types.ParseDelegation(sdb.GetCode(authority)); !delegated {
|
||||||
|
t.Fatal("final delegation not installed")
|
||||||
|
}
|
||||||
|
if want := authWorstState; gp.cumulativeState != want {
|
||||||
|
t.Fatalf("state gas = %d, want %d (leaf + indicator exactly once)", gp.cumulativeState, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,14 +14,6 @@
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
// EIP-8038 authorization accounting tests. The per-authorization intrinsic gas
|
|
||||||
// pre-charges ACCOUNT_WRITE (regular) on top of REGULAR_PER_AUTH_BASE_COST.
|
|
||||||
// applyAuthorization refunds that ACCOUNT_WRITE to the refund counter in exactly
|
|
||||||
// the cases where no new account leaf is written: an invalid authorization, or
|
|
||||||
// an authority whose account already exists. These white-box tests invoke
|
|
||||||
// applyAuthorization directly and read the raw refund counter, so they observe
|
|
||||||
// the refund before the EIP-3529 cap is applied.
|
|
||||||
|
|
||||||
package core
|
package core
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
|
@ -37,74 +29,117 @@ import (
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
||||||
// newAuthTestTransition builds a minimal stateTransition with a state reservoir,
|
// newAuthTestTransition builds a minimal stateTransition with a runtime gas
|
||||||
// suitable for calling applyAuthorization directly.
|
// budget, suitable for calling applyAuthorization directly.
|
||||||
func newAuthTestTransition(sdb *state.StateDB) *stateTransition {
|
func newAuthTestTransition(sdb *state.StateDB) *stateTransition {
|
||||||
st := newStateTransition(amsterdamCoreEVM(sdb), &Message{}, NewGasPool(30_000_000))
|
st := newStateTransition(amsterdamCoreEVM(sdb), &Message{}, NewGasPool(30_000_000))
|
||||||
st.gasRemaining = vm.NewGasBudget(0, 1_000_000) // reservoir for state-gas refills
|
st.gasRemaining = vm.NewGasBudget(1_000_000, 1_000_000)
|
||||||
return st
|
return st
|
||||||
}
|
}
|
||||||
|
|
||||||
// A net-new delegation on a fresh authority writes a new account leaf, so the
|
// A net-new delegation on a fresh, cold authority is charged ACCOUNT_WRITE in
|
||||||
// intrinsic ACCOUNT_WRITE stands (no refund).
|
// regular gas (the authority's cold access is paid unconditionally at the
|
||||||
func TestAuthAccountWriteNetNewNoRefund(t *testing.T) {
|
// intrinsic phase, not here), plus the account leaf and the indicator bytes in
|
||||||
|
// state gas.
|
||||||
|
func TestAuthRuntimeChargeNetNew(t *testing.T) {
|
||||||
auth, _ := signAuth(t, authKeyA, delegate8037, 0)
|
auth, _ := signAuth(t, authKeyA, delegate8037, 0)
|
||||||
st := newAuthTestTransition(mkState(senderAlloc(nil)))
|
st := newAuthTestTransition(mkState(senderAlloc(nil)))
|
||||||
if err := st.applyAuthorization(rules8037, &auth, map[common.Address]bool{}); err != nil {
|
if err := st.applyAuthorization(rules8037, &auth, map[common.Address]*authTracking{}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if got := st.state.GetRefund(); got != 0 {
|
if want := params.AccountWriteAmsterdam; st.gasRemaining.UsedRegularGas != want {
|
||||||
t.Fatalf("refund = %d, want 0 (net-new account write)", got)
|
t.Fatalf("regular charged = %d, want %d", st.gasRemaining.UsedRegularGas, want)
|
||||||
|
}
|
||||||
|
if want := int64(authWorstState); st.gasRemaining.UsedStateGas != want {
|
||||||
|
t.Fatalf("state charged = %d, want %d", st.gasRemaining.UsedStateGas, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A pre-existing authority writes no new account leaf, so the intrinsic
|
// A pre-existing authority writes no new account leaf, but its first write in
|
||||||
// ACCOUNT_WRITE is refunded.
|
// the transaction still carries ACCOUNT_WRITE; the authority's cold access is
|
||||||
func TestAuthAccountWriteExistsRefund(t *testing.T) {
|
// paid at the intrinsic phase, so only the net-new indicator bytes are charged
|
||||||
|
// as state gas here.
|
||||||
|
func TestAuthRuntimeChargeExistingAccount(t *testing.T) {
|
||||||
auth, authority := signAuth(t, authKeyA, delegate8037, 0)
|
auth, authority := signAuth(t, authKeyA, delegate8037, 0)
|
||||||
st := newAuthTestTransition(mkState(senderAlloc(types.GenesisAlloc{authority: {Balance: big.NewInt(1)}})))
|
st := newAuthTestTransition(mkState(senderAlloc(types.GenesisAlloc{authority: {Balance: big.NewInt(1)}})))
|
||||||
if err := st.applyAuthorization(rules8037, &auth, map[common.Address]bool{}); err != nil {
|
if err := st.applyAuthorization(rules8037, &auth, map[common.Address]*authTracking{}); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if got := st.state.GetRefund(); got != params.AccountWriteAmsterdam {
|
if want := params.AccountWriteAmsterdam; st.gasRemaining.UsedRegularGas != want {
|
||||||
t.Fatalf("refund = %d, want %d (account already exists)", got, params.AccountWriteAmsterdam)
|
t.Fatalf("regular charged = %d, want %d", st.gasRemaining.UsedRegularGas, want)
|
||||||
|
}
|
||||||
|
if want := int64(authBaseState); st.gasRemaining.UsedStateGas != want {
|
||||||
|
t.Fatalf("state charged = %d, want %d", st.gasRemaining.UsedStateGas, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// An invalid authorization is skipped without writing any account leaf, so its
|
// No cold surcharge is ever charged at runtime — the authority access is priced
|
||||||
// intrinsic ACCOUNT_WRITE is refunded.
|
// at the intrinsic phase — so an authority already warmed by the access list or
|
||||||
func TestAuthAccountWriteInvalidRefund(t *testing.T) {
|
// an earlier authorization pays only the first-write surcharge, as it would
|
||||||
|
// whether warm or cold.
|
||||||
|
func TestAuthRuntimeChargeWarmAuthority(t *testing.T) {
|
||||||
|
auth, authority := signAuth(t, authKeyA, delegate8037, 0)
|
||||||
|
st := newAuthTestTransition(mkState(senderAlloc(types.GenesisAlloc{authority: {Balance: big.NewInt(1)}})))
|
||||||
|
st.state.AddAddressToAccessList(authority)
|
||||||
|
if err := st.applyAuthorization(rules8037, &auth, map[common.Address]*authTracking{}); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if want := params.AccountWriteAmsterdam; st.gasRemaining.UsedRegularGas != want {
|
||||||
|
t.Fatalf("regular charged = %d, want %d (warm authority)", st.gasRemaining.UsedRegularGas, want)
|
||||||
|
}
|
||||||
|
if want := int64(authBaseState); st.gasRemaining.UsedStateGas != want {
|
||||||
|
t.Fatalf("state charged = %d, want %d", st.gasRemaining.UsedStateGas, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// An invalid authorization is skipped without any runtime charge.
|
||||||
|
func TestAuthRuntimeInvalidNoCharge(t *testing.T) {
|
||||||
k, _ := crypto.HexToECDSA(authKeyA)
|
k, _ := crypto.HexToECDSA(authKeyA)
|
||||||
bad, _ := types.SignSetCode(k, types.SetCodeAuthorization{
|
bad, _ := types.SignSetCode(k, types.SetCodeAuthorization{
|
||||||
ChainID: *uint256.NewInt(999), Address: delegate8037, Nonce: 0, // wrong chain id
|
ChainID: *uint256.NewInt(999), Address: delegate8037, Nonce: 0, // wrong chain id
|
||||||
})
|
})
|
||||||
st := newAuthTestTransition(mkState(senderAlloc(nil)))
|
st := newAuthTestTransition(mkState(senderAlloc(nil)))
|
||||||
if err := st.applyAuthorization(rules8037, &bad, map[common.Address]bool{}); err == nil {
|
if err := st.applyAuthorization(rules8037, &bad, map[common.Address]*authTracking{}); err == nil {
|
||||||
t.Fatal("expected invalid-authorization error")
|
t.Fatal("expected invalid-authorization error")
|
||||||
}
|
}
|
||||||
if got := st.state.GetRefund(); got != params.AccountWriteAmsterdam {
|
if st.gasRemaining.UsedRegularGas != 0 || st.gasRemaining.UsedStateGas != 0 {
|
||||||
t.Fatalf("refund = %d, want %d (invalid authorization)", got, params.AccountWriteAmsterdam)
|
t.Fatalf("charged = <%d,%d>, want <0,0> (invalid authorization)",
|
||||||
|
st.gasRemaining.UsedRegularGas, st.gasRemaining.UsedStateGas)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// The same authority across two authorizations writes its account leaf only
|
// The same authority across two authorizations is charged once: the first auth
|
||||||
// once: the first auth pays ACCOUNT_WRITE, the second (which now sees the
|
// warms the authority, materializes the account and installs the indicator, so
|
||||||
// account as existing) is refunded.
|
// the second incurs no further charge.
|
||||||
func TestAuthAccountWriteDuplicateOnce(t *testing.T) {
|
func TestAuthRuntimeDuplicateAuthorityOnce(t *testing.T) {
|
||||||
a0, _ := signAuth(t, authKeyA, delegate8037, 0)
|
a0, _ := signAuth(t, authKeyA, delegate8037, 0)
|
||||||
a1, _ := signAuth(t, authKeyA, delegate8037, 1)
|
a1, _ := signAuth(t, authKeyA, delegate8037, 1)
|
||||||
st := newAuthTestTransition(mkState(senderAlloc(nil)))
|
st := newAuthTestTransition(mkState(senderAlloc(nil)))
|
||||||
delegates := map[common.Address]bool{}
|
authorities := map[common.Address]*authTracking{}
|
||||||
if err := st.applyAuthorization(rules8037, &a0, delegates); err != nil {
|
if err := st.applyAuthorization(rules8037, &a0, authorities); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if got := st.state.GetRefund(); got != 0 {
|
if err := st.applyAuthorization(rules8037, &a1, authorities); err != nil {
|
||||||
t.Fatalf("refund after first auth = %d, want 0", got)
|
|
||||||
}
|
|
||||||
if err := st.applyAuthorization(rules8037, &a1, delegates); err != nil {
|
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if got := st.state.GetRefund(); got != params.AccountWriteAmsterdam {
|
if want := params.AccountWriteAmsterdam; st.gasRemaining.UsedRegularGas != want {
|
||||||
t.Fatalf("refund after duplicate auth = %d, want %d", got, params.AccountWriteAmsterdam)
|
t.Fatalf("regular charged = %d, want %d (once)", st.gasRemaining.UsedRegularGas, want)
|
||||||
|
}
|
||||||
|
if want := int64(authWorstState); st.gasRemaining.UsedStateGas != want {
|
||||||
|
t.Fatalf("state charged = %d, want %d (once)", st.gasRemaining.UsedStateGas, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A budget that cannot cover the runtime charge aborts authorization
|
||||||
|
// processing with ErrOutOfGasRuntime, without mutating the authority.
|
||||||
|
func TestAuthRuntimeOutOfGas(t *testing.T) {
|
||||||
|
auth, authority := signAuth(t, authKeyA, delegate8037, 0)
|
||||||
|
st := newAuthTestTransition(mkState(senderAlloc(nil)))
|
||||||
|
st.gasRemaining = vm.NewGasBudget(10_000, 0) // covers neither leaf nor indicator
|
||||||
|
if err := st.applyAuthorization(rules8037, &auth, map[common.Address]*authTracking{}); err != ErrOutOfGasRuntime {
|
||||||
|
t.Fatalf("err = %v, want ErrOutOfGasRuntime", err)
|
||||||
|
}
|
||||||
|
if st.state.GetNonce(authority) != 0 || len(st.state.GetCode(authority)) != 0 {
|
||||||
|
t.Fatal("authority mutated despite out-of-gas runtime charge")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -137,4 +137,9 @@ var (
|
||||||
ErrAuthorizationInvalidSignature = errors.New("EIP-7702 authorization has invalid signature")
|
ErrAuthorizationInvalidSignature = errors.New("EIP-7702 authorization has invalid signature")
|
||||||
ErrAuthorizationDestinationHasCode = errors.New("EIP-7702 authorization destination is a contract")
|
ErrAuthorizationDestinationHasCode = errors.New("EIP-7702 authorization destination is a contract")
|
||||||
ErrAuthorizationNonceMismatch = errors.New("EIP-7702 authorization nonce does not match current account nonce")
|
ErrAuthorizationNonceMismatch = errors.New("EIP-7702 authorization nonce does not match current account nonce")
|
||||||
|
|
||||||
|
// ErrOutOfGasRuntime is returned when the transaction's gas budget cannot
|
||||||
|
// cover an EIP-2780 runtime charge. The transaction remains valid: the top
|
||||||
|
// frame halts out of gas and its state changes are reverted.
|
||||||
|
ErrOutOfGasRuntime = errors.New("out of gas covering EIP-2780 runtime charge")
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,7 @@ import (
|
||||||
"github.com/ethereum/go-ethereum/core/tracing"
|
"github.com/ethereum/go-ethereum/core/tracing"
|
||||||
"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"
|
||||||
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
"github.com/ethereum/go-ethereum/crypto/kzg4844"
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
|
|
@ -68,33 +69,28 @@ 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, from common.Address, to *common.Address, value *uint256.Int, rules params.Rules, costPerStateByte uint64) (vm.GasCosts, error) {
|
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, from common.Address, to *common.Address, value *uint256.Int, rules params.Rules) (uint64, error) {
|
||||||
isContractCreation := to == nil
|
isContractCreation := to == nil
|
||||||
|
|
||||||
// Set the starting gas for the raw transaction
|
// Set the starting gas for the raw transaction
|
||||||
var gas vm.GasCosts
|
var gas uint64
|
||||||
if rules.IsAmsterdam {
|
if rules.IsAmsterdam {
|
||||||
gas.RegularGas = intrinsicBaseGasEIP2780(from, to, value)
|
gas = intrinsicBaseGasEIP2780(from, to, value)
|
||||||
if isContractCreation {
|
|
||||||
// New-account creation is charged as state gas (EIP-8037).
|
|
||||||
gas.StateGas = params.AccountCreationSize * costPerStateByte
|
|
||||||
}
|
|
||||||
} else if isContractCreation && rules.IsHomestead {
|
} else if isContractCreation && rules.IsHomestead {
|
||||||
gas.RegularGas = params.TxGasContractCreation
|
gas = params.TxGasContractCreation
|
||||||
} else {
|
} else {
|
||||||
gas.RegularGas = params.TxGas
|
gas = params.TxGas
|
||||||
}
|
}
|
||||||
// Add gas for authorizations
|
// Add gas for authorizations
|
||||||
if authList != nil {
|
if authList != nil {
|
||||||
if rules.IsAmsterdam {
|
if rules.IsAmsterdam {
|
||||||
gas.RegularGas += uint64(len(authList)) * (params.AccountWriteAmsterdam + params.RegularPerAuthBaseCost)
|
gas += uint64(len(authList)) * params.RegularPerAuthBaseCost
|
||||||
gas.StateGas += uint64(len(authList)) * (params.AuthorizationCreationSize + params.AccountCreationSize) * costPerStateByte
|
|
||||||
} else {
|
} else {
|
||||||
gas.RegularGas += uint64(len(authList)) * params.CallNewAccountGas
|
gas += uint64(len(authList)) * params.CallNewAccountGas
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dataLen := uint64(len(data))
|
|
||||||
// Bump the required gas by the amount of transactional data
|
// Bump the required gas by the amount of transactional data
|
||||||
|
dataLen := uint64(len(data))
|
||||||
if dataLen > 0 {
|
if dataLen > 0 {
|
||||||
// Zero and non-zero bytes are priced differently
|
// Zero and non-zero bytes are priced differently
|
||||||
z := uint64(bytes.Count(data, []byte{0}))
|
z := uint64(bytes.Count(data, []byte{0}))
|
||||||
|
|
@ -105,24 +101,25 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
||||||
if rules.IsIstanbul {
|
if rules.IsIstanbul {
|
||||||
nonZeroGas = params.TxDataNonZeroGasEIP2028
|
nonZeroGas = params.TxDataNonZeroGasEIP2028
|
||||||
}
|
}
|
||||||
if (math.MaxUint64-gas.RegularGas)/nonZeroGas < nz {
|
if (math.MaxUint64-gas)/nonZeroGas < nz {
|
||||||
return vm.GasCosts{}, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
gas.RegularGas += nz * nonZeroGas
|
gas += nz * nonZeroGas
|
||||||
|
|
||||||
if (math.MaxUint64-gas.RegularGas)/params.TxDataZeroGas < z {
|
if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
|
||||||
return vm.GasCosts{}, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
gas.RegularGas += z * params.TxDataZeroGas
|
gas += z * params.TxDataZeroGas
|
||||||
|
|
||||||
if isContractCreation && rules.IsShanghai {
|
if isContractCreation && rules.IsShanghai {
|
||||||
lenWords := toWordSize(dataLen)
|
lenWords := toWordSize(dataLen)
|
||||||
if (math.MaxUint64-gas.RegularGas)/params.InitCodeWordGas < lenWords {
|
if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords {
|
||||||
return vm.GasCosts{}, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
gas.RegularGas += lenWords * params.InitCodeWordGas
|
gas += lenWords * params.InitCodeWordGas
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Add the gas for accessList
|
||||||
if accessList != nil {
|
if accessList != nil {
|
||||||
addresses := uint64(len(accessList))
|
addresses := uint64(len(accessList))
|
||||||
storageKeys := uint64(accessList.StorageKeys())
|
storageKeys := uint64(accessList.StorageKeys())
|
||||||
|
|
@ -134,14 +131,14 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
||||||
addressCost = params.TxAccessListAddressGasAmsterdam
|
addressCost = params.TxAccessListAddressGasAmsterdam
|
||||||
storageKeyCost = params.TxAccessListStorageKeyGasAmsterdam
|
storageKeyCost = params.TxAccessListStorageKeyGasAmsterdam
|
||||||
}
|
}
|
||||||
if (math.MaxUint64-gas.RegularGas)/addressCost < addresses {
|
if (math.MaxUint64-gas)/addressCost < addresses {
|
||||||
return vm.GasCosts{}, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
gas.RegularGas += addresses * addressCost
|
gas += addresses * addressCost
|
||||||
if (math.MaxUint64-gas.RegularGas)/storageKeyCost < storageKeys {
|
if (math.MaxUint64-gas)/storageKeyCost < storageKeys {
|
||||||
return vm.GasCosts{}, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
gas.RegularGas += storageKeys * storageKeyCost
|
gas += storageKeys * storageKeyCost
|
||||||
|
|
||||||
// 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 rules.IsAmsterdam {
|
if rules.IsAmsterdam {
|
||||||
|
|
@ -149,38 +146,41 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
||||||
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.RegularGas)/addressCost < addresses {
|
if (math.MaxUint64-gas)/addressCost < addresses {
|
||||||
return vm.GasCosts{}, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
gas.RegularGas += addresses * addressCost
|
gas += addresses * addressCost
|
||||||
if (math.MaxUint64-gas.RegularGas)/storageKeyCost < storageKeys {
|
if (math.MaxUint64-gas)/storageKeyCost < storageKeys {
|
||||||
return vm.GasCosts{}, ErrGasUintOverflow
|
return 0, ErrGasUintOverflow
|
||||||
}
|
}
|
||||||
gas.RegularGas += storageKeys * storageKeyCost
|
gas += storageKeys * storageKeyCost
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return gas, nil
|
return gas, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// intrinsicBaseGasEIP2780 computes the regular-gas portion of the EIP-2780
|
// intrinsicBaseGasEIP2780 computes the intrinsic base cost of the transaction.
|
||||||
// intrinsic base cost: the per-resource decomposition of the legacy flat 21,000.
|
|
||||||
func intrinsicBaseGasEIP2780(from common.Address, to *common.Address, value *uint256.Int) uint64 {
|
func intrinsicBaseGasEIP2780(from common.Address, to *common.Address, value *uint256.Int) uint64 {
|
||||||
var (
|
var (
|
||||||
isContractCreation = to == nil
|
isContractCreation = to == nil
|
||||||
isSelfTransfer = to != nil && *to == from
|
isSelfTransfer = to != nil && *to == from
|
||||||
hasValue = value != nil && !value.IsZero()
|
hasValue = value != nil && !value.IsZero()
|
||||||
)
|
)
|
||||||
// tx.sender: signature recovery plus the sender account access and write.
|
// tx.sender: signature recovery, the sender account's access and write,
|
||||||
|
// and the inclusion of the transaction in the block (which is transient
|
||||||
|
// and expires with history).
|
||||||
gas := params.TxBaseCost2780
|
gas := params.TxBaseCost2780
|
||||||
|
|
||||||
// tx.to charge.
|
// tx.to charge. Per EIP-2780 the recipient touch is charged at the cold
|
||||||
|
// rate unconditionally at the intrinsic phase, independent of the account's
|
||||||
|
// warm/cold state.
|
||||||
switch {
|
switch {
|
||||||
case isSelfTransfer:
|
case isSelfTransfer:
|
||||||
// The recipient account is already accessed and written as the sender.
|
// The recipient account is already accessed and written as the sender.
|
||||||
case isContractCreation:
|
case isContractCreation:
|
||||||
gas += params.CreateAccess2780
|
gas += params.CreateAccessAmsterdam
|
||||||
default:
|
default:
|
||||||
gas += params.ColdAccountAccess2780
|
gas += params.ColdAccountAccessAmsterdam
|
||||||
}
|
}
|
||||||
|
|
||||||
// tx.value charge.
|
// tx.value charge.
|
||||||
|
|
@ -242,10 +242,12 @@ func FloorDataGas(rules params.Rules, from common.Address, to *common.Address, v
|
||||||
tokenCost = params.TxCostFloorPerToken
|
tokenCost = params.TxCostFloorPerToken
|
||||||
}
|
}
|
||||||
|
|
||||||
// The floor is anchored to the transaction base cost.
|
// The floor is anchored to the transaction base cost. Under EIP-2780 that
|
||||||
|
// base is the per-resource decomposition (the same one used by the intrinsic
|
||||||
|
// gas), so the floor never undercuts the transaction's own base.
|
||||||
floorBase := params.TxGas
|
floorBase := params.TxGas
|
||||||
if rules.IsAmsterdam {
|
if rules.IsAmsterdam {
|
||||||
floorBase = params.TxBaseCost2780
|
floorBase = intrinsicBaseGasEIP2780(from, to, value)
|
||||||
}
|
}
|
||||||
// Check for overflow
|
// Check for overflow
|
||||||
if (math.MaxUint64-floorBase)/tokenCost < tokens {
|
if (math.MaxUint64-floorBase)/tokenCost < tokens {
|
||||||
|
|
@ -260,7 +262,6 @@ func toWordSize(size uint64) uint64 {
|
||||||
if size > math.MaxUint64-31 {
|
if size > math.MaxUint64-31 {
|
||||||
return math.MaxUint64/32 + 1
|
return math.MaxUint64/32 + 1
|
||||||
}
|
}
|
||||||
|
|
||||||
return (size + 31) / 32
|
return (size + 31) / 32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -423,24 +424,16 @@ 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
|
// buyGas pre-pays gas from the sender's balance.
|
||||||
// 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
|
// The balance requirement is the worst-case ETH the tx may need to lock
|
||||||
// up: `msg.GasLimit × max(msg.GasPrice, msg.GasFeeCap) + msg.Value`,
|
// up: `msg.GasLimit × max(msg.GasPrice, msg.GasFeeCap) + msg.Value`,
|
||||||
// plus `blobGas × msg.BlobGasFeeCap` under Cancun. Insufficient balance
|
// plus `blobGas × msg.BlobGasFeeCap` under Cancun. Insufficient balance
|
||||||
// returns ErrInsufficientFunds. After the check, the sender is actually
|
// returns ErrInsufficientFunds.
|
||||||
// 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
|
// After the check, the sender is actually debited `msg.GasLimit × msg.GasPrice`
|
||||||
// for tx-end accounting) and `gasRemaining` (live running balance):
|
// (plus `blobGas × blobBaseFee` under Cancun), the cap-vs-tip differential
|
||||||
//
|
// is settled at tx end.
|
||||||
// - 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)
|
||||||
|
|
@ -493,54 +486,63 @@ 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)
|
||||||
}
|
}
|
||||||
isAmsterdam := st.evm.ChainConfig().IsAmsterdam(st.evm.Context.BlockNumber, st.evm.Context.Time)
|
|
||||||
|
|
||||||
// Reserve the gas budget in the block gas pool
|
|
||||||
var err error
|
|
||||||
if isAmsterdam {
|
|
||||||
err = st.gp.CheckGasAmsterdam(min(st.msg.GasLimit, params.MaxTxGas), st.msg.GasLimit)
|
|
||||||
} else {
|
|
||||||
err = st.gp.CheckGasLegacy(st.msg.GasLimit)
|
|
||||||
}
|
|
||||||
if 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 isAmsterdam {
|
|
||||||
limit = min(st.msg.GasLimit, params.MaxTxGas)
|
|
||||||
}
|
|
||||||
st.gasRemaining = vm.NewGasBudget(limit, st.msg.GasLimit-limit)
|
|
||||||
|
|
||||||
if st.evm.Config.Tracer.HasGasHook() {
|
|
||||||
st.evm.Config.Tracer.EmitGasChange(tracing.Gas{}, st.gasRemaining.AsTracing(), tracing.GasChangeTxInitialBalance)
|
|
||||||
}
|
|
||||||
// Deduct the gas cost from the sender's balance
|
// Deduct the gas cost from the sender's balance
|
||||||
st.state.SubBalance(st.msg.From, mgval, tracing.BalanceDecreaseGasBuy)
|
st.state.SubBalance(st.msg.From, mgval, tracing.BalanceDecreaseGasBuy)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// initRuntimeGasBudget initializes the transaction's running gas budget with the
|
||||||
|
// gas remaining after the intrinsic cost has been deducted.
|
||||||
|
//
|
||||||
|
// After Amsterdam (EIP-8037) the intrinsic cost counts towards the EIP-7825
|
||||||
|
// regular-gas cap:
|
||||||
|
//
|
||||||
|
// execution_gas = tx.gas - intrinsic_gas
|
||||||
|
// regular_gas_budget = TX_MAX_GAS_LIMIT - intrinsic_gas
|
||||||
|
// gas_left = min(regular_gas_budget, execution_gas)
|
||||||
|
// state_gas_reservoir = execution_gas - gas_left
|
||||||
|
func (st *stateTransition) initRuntimeGasBudget(rules params.Rules, intrinsicGas uint64) {
|
||||||
|
executionGas := st.msg.GasLimit - intrinsicGas
|
||||||
|
gasLeft := executionGas
|
||||||
|
if rules.IsAmsterdam {
|
||||||
|
gasLeft = min(params.MaxTxGas-intrinsicGas, executionGas)
|
||||||
|
}
|
||||||
|
st.gasRemaining = vm.NewGasBudget(gasLeft, executionGas-gasLeft)
|
||||||
|
|
||||||
|
if st.evm.Config.Tracer.HasGasHook() {
|
||||||
|
st.evm.Config.Tracer.EmitGasChange(tracing.Gas{Regular: st.msg.GasLimit}, st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// preCheck performs all pre-execution validation that does not require
|
// 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.
|
// the EVM to run, then ends by calling buyGas to lock ether for prepay.
|
||||||
// It returns a consensus error if any of the following fail:
|
// It returns a consensus error if any of the following fail:
|
||||||
//
|
//
|
||||||
// - Sender nonce matches state and is not at 2^64-1 (EIP-2681).
|
// - 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
|
// - EIP-7825 per-tx gas-limit cap on Osaka chains pre-Amsterdam.
|
||||||
// it is enforced there via the two-dimensional budget in buyGas).
|
//
|
||||||
// - EIP-3607 sender-is-EOA, allowing accounts whose only code is an
|
// - EIP-3607 sender-is-EOA, allowing accounts whose only code is an
|
||||||
// EIP-7702 delegation designator.
|
// EIP-7702 delegation designator.
|
||||||
|
//
|
||||||
// - EIP-1559 fee-cap, tip-cap and base-fee constraints (London+).
|
// - EIP-1559 fee-cap, tip-cap and base-fee constraints (London+).
|
||||||
|
//
|
||||||
// - Blob-tx structural checks: non-nil `To`, non-empty hash list,
|
// - Blob-tx structural checks: non-nil `To`, non-empty hash list,
|
||||||
// valid KZG versioned hashes, count below `BlobTxMaxBlobs` (Osaka+).
|
// valid KZG versioned hashes, count below `BlobTxMaxBlobs` (Osaka+).
|
||||||
|
//
|
||||||
// - Blob fee-cap not below the current blob base fee (Cancun+).
|
// - Blob fee-cap not below the current blob base fee (Cancun+).
|
||||||
|
//
|
||||||
// - EIP-7702 set-code-tx shape: non-nil `To` and non-empty
|
// - EIP-7702 set-code-tx shape: non-nil `To` and non-empty
|
||||||
// authorization list.
|
// authorization list.
|
||||||
//
|
//
|
||||||
|
// - EIP-3860 init code size cap on create transactions (Shanghai+,
|
||||||
|
// with the raised Amsterdam cap).
|
||||||
|
//
|
||||||
|
// - Insufficient block gas budget for including the transaction.
|
||||||
|
//
|
||||||
// The SkipNonceChecks / SkipTransactionChecks / NoBaseFee flags bypass
|
// The SkipNonceChecks / SkipTransactionChecks / NoBaseFee flags bypass
|
||||||
// subsets of these checks for simulation paths (eth_call, eth_estimateGas).
|
// subsets of these checks for simulation paths (eth_call, eth_estimateGas).
|
||||||
func (st *stateTransition) preCheck() error {
|
func (st *stateTransition) preCheck(rules params.Rules) error {
|
||||||
// Only check transactions that are not fake
|
// Only check transactions that are not fake
|
||||||
msg := st.msg
|
msg := st.msg
|
||||||
if !msg.SkipNonceChecks {
|
if !msg.SkipNonceChecks {
|
||||||
|
|
@ -557,13 +559,9 @@ func (st *stateTransition) preCheck() error {
|
||||||
msg.From.Hex(), stNonce)
|
msg.From.Hex(), stNonce)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var (
|
|
||||||
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 !isAmsterdam && isOsaka && msg.GasLimit > params.MaxTxGas {
|
if !rules.IsAmsterdam && rules.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
|
||||||
|
|
@ -574,7 +572,7 @@ func (st *stateTransition) preCheck() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Make sure that transaction gasFeeCap is greater than the baseFee (post london)
|
// Make sure that transaction gasFeeCap is greater than the baseFee (post london)
|
||||||
if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
|
if rules.IsLondon {
|
||||||
// Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call)
|
// Skip the checks if gas fields are zero and baseFee was explicitly disabled (eth_call)
|
||||||
skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0
|
skipCheck := st.evm.Config.NoBaseFee && msg.GasFeeCap.BitLen() == 0 && msg.GasTipCap.BitLen() == 0
|
||||||
if !skipCheck {
|
if !skipCheck {
|
||||||
|
|
@ -601,7 +599,7 @@ func (st *stateTransition) preCheck() error {
|
||||||
if len(msg.BlobHashes) == 0 {
|
if len(msg.BlobHashes) == 0 {
|
||||||
return ErrMissingBlobHashes
|
return ErrMissingBlobHashes
|
||||||
}
|
}
|
||||||
if isOsaka && len(msg.BlobHashes) > params.BlobTxMaxBlobs {
|
if rules.IsOsaka && len(msg.BlobHashes) > params.BlobTxMaxBlobs {
|
||||||
return ErrTooManyBlobs
|
return ErrTooManyBlobs
|
||||||
}
|
}
|
||||||
for i, hash := range msg.BlobHashes {
|
for i, hash := range msg.BlobHashes {
|
||||||
|
|
@ -611,7 +609,7 @@ func (st *stateTransition) preCheck() error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check that the user is paying at least the current blob fee
|
// Check that the user is paying at least the current blob fee
|
||||||
if st.evm.ChainConfig().IsCancun(st.evm.Context.BlockNumber, st.evm.Context.Time) {
|
if rules.IsCancun {
|
||||||
if st.blobGasUsed() > 0 {
|
if st.blobGasUsed() > 0 {
|
||||||
// Skip the checks if gas fields are zero and blobBaseFee was explicitly disabled (eth_call)
|
// Skip the checks if gas fields are zero and blobBaseFee was explicitly disabled (eth_call)
|
||||||
skipCheck := st.evm.Config.NoBaseFee && msg.BlobGasFeeCap.BitLen() == 0
|
skipCheck := st.evm.Config.NoBaseFee && msg.BlobGasFeeCap.BitLen() == 0
|
||||||
|
|
@ -634,6 +632,22 @@ func (st *stateTransition) preCheck() error {
|
||||||
return fmt.Errorf("%w (sender %v)", ErrEmptyAuthList, msg.From)
|
return fmt.Errorf("%w (sender %v)", ErrEmptyAuthList, msg.From)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Check whether the init code size has been exceeded (EIP-3860).
|
||||||
|
if msg.To == nil {
|
||||||
|
if err := vm.CheckMaxInitCodeSize(&rules, uint64(len(msg.Data))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Reserve the gas budget in the block gas pool
|
||||||
|
var err error
|
||||||
|
if rules.IsAmsterdam {
|
||||||
|
err = st.gp.CheckGasAmsterdam(min(st.msg.GasLimit, params.MaxTxGas), st.msg.GasLimit)
|
||||||
|
} else {
|
||||||
|
err = st.gp.CheckGasLegacy(st.msg.GasLimit)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return st.buyGas()
|
return st.buyGas()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -649,32 +663,25 @@ func (st *stateTransition) preCheck() error {
|
||||||
// If a consensus error is encountered, it is returned directly with a
|
// If a consensus error is encountered, it is returned directly with a
|
||||||
// nil EVM execution result.
|
// nil EVM execution result.
|
||||||
func (st *stateTransition) execute() (*ExecutionResult, error) {
|
func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
// Validate the message and pre-pay gas.
|
|
||||||
if err := st.preCheck(); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.
|
|
||||||
var (
|
var (
|
||||||
msg = st.msg
|
msg = st.msg
|
||||||
rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil, st.evm.Context.Time)
|
rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil, st.evm.Context.Time)
|
||||||
contractCreation = msg.To == nil
|
contractCreation = msg.To == nil
|
||||||
floorDataGas uint64
|
floorDataGas uint64
|
||||||
)
|
)
|
||||||
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, msg.From, msg.To, msg.Value, rules, st.evm.Context.CostPerStateByte)
|
// Validate the message and pre-pay gas.
|
||||||
|
if err := st.preCheck(rules); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// Calculate the intrinsic gas of this transaction and make sure the gas limit
|
||||||
|
// is sufficient to cover that.
|
||||||
|
intrinsicGas, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, msg.From, msg.To, msg.Value, rules)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
prior, sufficient := st.gasRemaining.Charge(cost)
|
if msg.GasLimit < intrinsicGas {
|
||||||
if !sufficient {
|
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, msg.GasLimit, intrinsicGas)
|
||||||
return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gasRemaining.RegularGas, cost.RegularGas)
|
|
||||||
}
|
}
|
||||||
if st.evm.Config.Tracer.HasGasHook() {
|
|
||||||
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Validate the EIP-7623 calldata floor against the gas limit. The floor inflates
|
// 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.
|
// the total gas usage at tx end, so the gas limit must be sufficient to cover that.
|
||||||
if rules.IsPrague {
|
if rules.IsPrague {
|
||||||
|
|
@ -687,13 +694,15 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
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", ErrFloorDataGas, msg.GasLimit, floorDataGas)
|
||||||
}
|
}
|
||||||
// In Amsterdam, the transaction gas limit is allowed to exceed
|
}
|
||||||
// params.MaxTxGas, but the calldata floor cost is capped by it.
|
// In Amsterdam, the transaction gas limit is allowed to exceed
|
||||||
if rules.IsAmsterdam && max(cost.RegularGas, floorDataGas) > params.MaxTxGas {
|
// params.MaxTxGas, but the intrinsic cost and calldata floor
|
||||||
return nil, fmt.Errorf("%w: regular intrisic cost %v, floor: %v", ErrFloorDataGas, cost.RegularGas, floorDataGas)
|
// cost is still capped by it.
|
||||||
}
|
if rules.IsAmsterdam && max(intrinsicGas, floorDataGas) > params.MaxTxGas {
|
||||||
|
return nil, fmt.Errorf("%w: intrinsic cost %v, floor: %v", ErrFloorDataGas, intrinsicGas, floorDataGas)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// EIP-4762 setup
|
||||||
if rules.IsEIP4762 {
|
if rules.IsEIP4762 {
|
||||||
st.evm.AccessEvents.AddTxOrigin(msg.From)
|
st.evm.AccessEvents.AddTxOrigin(msg.From)
|
||||||
|
|
||||||
|
|
@ -718,56 +727,18 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
// - enable block-level accessList construction (EIP-7928)
|
// - enable block-level accessList construction (EIP-7928)
|
||||||
st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList)
|
st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList)
|
||||||
|
|
||||||
|
// Initialize the running gas budget with the post-intrinsic remainder.
|
||||||
|
st.initRuntimeGasBudget(rules, intrinsicGas)
|
||||||
|
|
||||||
// Execute the top-most frame
|
// Execute the top-most frame
|
||||||
var (
|
var (
|
||||||
ret []byte
|
ret []byte
|
||||||
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
|
vmerr error // vm errors do not effect consensus
|
||||||
result vm.GasBudget
|
|
||||||
)
|
)
|
||||||
if contractCreation {
|
if contractCreation {
|
||||||
// Check whether the init code size has been exceeded.
|
ret, vmerr = st.executeCreate(rules, value)
|
||||||
if err := vm.CheckMaxInitCodeSize(&rules, uint64(len(msg.Data))); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
// Execute the transaction's creation.
|
|
||||||
var creation bool
|
|
||||||
ret, _, result, creation, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining.ForwardAll(), value)
|
|
||||||
st.gasRemaining.Absorb(result)
|
|
||||||
|
|
||||||
// If the contract creation failed, or the destination was pre-existing,
|
|
||||||
// refund the account-creation state gas pre-charged in IntrinsicGas.
|
|
||||||
if rules.IsAmsterdam && !creation {
|
|
||||||
st.gasRemaining.RefundStateToReservoir(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Increment the nonce for the next transaction.
|
ret, vmerr = st.executeCall(rules, value)
|
||||||
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)
|
|
||||||
|
|
||||||
// Apply EIP-7702 authorizations.
|
|
||||||
st.applyAuthorizations(rules, msg.SetCodeAuthorizations)
|
|
||||||
|
|
||||||
// Perform convenience warming of sender's delegation target. Although the
|
|
||||||
// sender is already warmed in Prepare(..), it's possible a delegation to
|
|
||||||
// the account was deployed during this transaction. To handle correctly,
|
|
||||||
// simply wait until the final state of delegations is determined before
|
|
||||||
// performing the resolution and warming.
|
|
||||||
if addr, ok := types.ParseDelegation(st.state.GetCode(*msg.To)); ok {
|
|
||||||
st.state.AddAddressToAccessList(addr)
|
|
||||||
// Record in BAL
|
|
||||||
if rules.IsAmsterdam {
|
|
||||||
st.state.GetCode(addr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// EIP-2780: charge the transaction's top-level recipient costs. If the
|
|
||||||
// budget cannot cover the charge, the top frame halts out of gas.
|
|
||||||
if rules.IsAmsterdam && !st.chargeCallRecipientEIP2780(value) {
|
|
||||||
vmerr = vm.ErrOutOfGas
|
|
||||||
st.gasRemaining = st.gasRemaining.ExitHalt()
|
|
||||||
} else {
|
|
||||||
// Execute the transaction's call.
|
|
||||||
ret, result, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining.ForwardAll(), value)
|
|
||||||
st.gasRemaining.Absorb(result)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Settle down the gas usage and refund the ETH back if any remaining
|
// Settle down the gas usage and refund the ETH back if any remaining
|
||||||
|
|
@ -808,43 +779,150 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// chargeCallRecipientEIP2780 applies the EIP-2780 transaction top-level gas costs for
|
// executeCreate runs the top-level frame of a contract-creation transaction
|
||||||
// a message-call transaction, charged before any opcode executes:
|
// and returns the EVM return data and the frame-level execution error.
|
||||||
//
|
func (st *stateTransition) executeCreate(rules params.Rules, value *uint256.Int) ([]byte, error) {
|
||||||
// - if the recipient is EIP-161 non-existent and the transaction carries value,
|
msg := st.msg
|
||||||
// charge for account creation.
|
|
||||||
//
|
var chargedCreation bool
|
||||||
// - if the recipient is an EIP-7702 delegated account, resolving the delegation
|
if rules.IsAmsterdam {
|
||||||
// loads the target's code, charged an additional cold account access in
|
addr := crypto.CreateAddress(msg.From, st.state.GetNonce(msg.From))
|
||||||
// regular gas.
|
if st.state.Empty(addr) {
|
||||||
func (st *stateTransition) chargeCallRecipientEIP2780(value *uint256.Int) bool {
|
if !st.chargeRuntimeGas(vm.GasCosts{StateGas: params.AccountCreationSize * st.evm.Context.CostPerStateByte}) {
|
||||||
var (
|
// The nonce increment normally performed inside evm.Create
|
||||||
cost vm.GasCosts
|
// must still happen for the included transaction.
|
||||||
to = *st.msg.To
|
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeContractCreator)
|
||||||
)
|
st.gasRemaining = st.gasRemaining.ExitHalt()
|
||||||
// This runs in the topmost frame before any bytecode executes, so unlike the
|
return nil, vm.ErrOutOfGas
|
||||||
// execution-level checks which must use StateDB.Empty because SELFDESTRUCT can
|
}
|
||||||
// leave a transient EIP-161-empty account, no empty account can exist here, and
|
chargedCreation = true
|
||||||
// !Exist is equivalent to Empty.
|
}
|
||||||
if !value.IsZero() && !st.state.Exist(to) {
|
|
||||||
cost.StateGas += params.AccountCreationSize * st.evm.Context.CostPerStateByte
|
|
||||||
}
|
}
|
||||||
if _, ok := types.ParseDelegation(st.state.GetCode(to)); ok {
|
// The first frame is entered with the gas remaining after the runtime
|
||||||
// EIP-2780: The tx.sender, tx.to, and (where applicable) delegation-target
|
// charges.
|
||||||
// charges above are always at the cold rate.
|
ret, _, result, vmerr := st.evm.Create(msg.From, msg.Data, st.gasRemaining.ForwardAll(), value)
|
||||||
//
|
st.gasRemaining.Absorb(result)
|
||||||
// The delegation-target is already warmed before, no double warming here.
|
|
||||||
cost.RegularGas += params.ColdAccountAccess2780
|
// If the contract creation failed (e.g. the initcode reverted or halted),
|
||||||
|
// refill the account-creation state gas charged at runtime.
|
||||||
|
if rules.IsAmsterdam && chargedCreation && vmerr != nil {
|
||||||
|
st.gasRemaining.RefundState(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
|
||||||
}
|
}
|
||||||
if cost == (vm.GasCosts{}) {
|
// If the top-most frame halted, drain the leftover regular gas rather
|
||||||
return true
|
// than returning it to the sender. The frame exit itself already burned
|
||||||
|
// its gas left, but the refill above repays the regular gas the charge
|
||||||
|
// originally borrowed, and on a halt that repayment must be burned as
|
||||||
|
// well. The state dimension is left untouched.
|
||||||
|
if rules.IsAmsterdam && vmerr != nil && vmerr != vm.ErrExecutionReverted {
|
||||||
|
st.gasRemaining.DrainRegular()
|
||||||
}
|
}
|
||||||
|
return ret, vmerr
|
||||||
|
}
|
||||||
|
|
||||||
|
// executeCall runs the top-level frame of a message-call transaction and
|
||||||
|
// returns the EVM return data and the frame-level execution error.
|
||||||
|
func (st *stateTransition) executeCall(rules params.Rules, value *uint256.Int) ([]byte, error) {
|
||||||
|
msg := st.msg
|
||||||
|
|
||||||
|
// Increment the nonce for the next transaction.
|
||||||
|
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)
|
||||||
|
|
||||||
|
if rules.IsAmsterdam {
|
||||||
|
snapshot := st.state.Snapshot()
|
||||||
|
if !st.applyAuthorizations(rules, st.msg.SetCodeAuthorizations) {
|
||||||
|
st.state.RevertToSnapshot(snapshot)
|
||||||
|
st.gasRemaining = st.gasRemaining.ExitHalt()
|
||||||
|
return nil, vm.ErrOutOfGas
|
||||||
|
}
|
||||||
|
if !st.chargeCallRecipientEIP2780(value) {
|
||||||
|
st.state.RevertToSnapshot(snapshot)
|
||||||
|
st.gasRemaining = st.gasRemaining.ExitHalt()
|
||||||
|
return nil, vm.ErrOutOfGas
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Apply EIP-7702 authorizations.
|
||||||
|
st.applyAuthorizations(rules, msg.SetCodeAuthorizations)
|
||||||
|
|
||||||
|
// Perform convenience warming of sender's delegation target. Although the
|
||||||
|
// sender is already warmed in Prepare(..), it's possible a delegation to
|
||||||
|
// the account was deployed during this transaction. To handle correctly,
|
||||||
|
// simply wait until the final state of delegations is determined before
|
||||||
|
// performing the resolution and warming.
|
||||||
|
if addr, ok := types.ParseDelegation(st.state.GetCode(*msg.To)); ok {
|
||||||
|
st.state.AddAddressToAccessList(addr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ret, result, vmerr := st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining.ForwardAll(), value)
|
||||||
|
st.gasRemaining.Absorb(result)
|
||||||
|
|
||||||
|
// If the call frame reverts or halts exceptionally, the charged state-gas
|
||||||
|
// is refilled back to the state reservoir in Amsterdam.
|
||||||
|
if rules.IsAmsterdam && vmerr != nil && !value.IsZero() && st.evm.StateDB.Empty(st.to()) {
|
||||||
|
st.gasRemaining.RefundState(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
|
||||||
|
}
|
||||||
|
// If the top-most frame halted, drain the leftover regular gas rather
|
||||||
|
// than returning it to the sender. The frame exit itself already burned
|
||||||
|
// its gas left, but the refill above repays the regular gas the charge
|
||||||
|
// originally borrowed, and on a halt that repayment must be burned as
|
||||||
|
// well.
|
||||||
|
if rules.IsAmsterdam && vmerr != nil && vmerr != vm.ErrExecutionReverted {
|
||||||
|
st.gasRemaining.DrainRegular()
|
||||||
|
}
|
||||||
|
return ret, vmerr
|
||||||
|
}
|
||||||
|
|
||||||
|
// chargeRuntimeGas deducts an EIP-2780 runtime charge from the transaction's
|
||||||
|
// gas budget and reports whether the budget covered it.
|
||||||
|
func (st *stateTransition) chargeRuntimeGas(cost vm.GasCosts) bool {
|
||||||
prior, ok := st.gasRemaining.Charge(cost)
|
prior, ok := st.gasRemaining.Charge(cost)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
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.GasChangeTxRuntimeGas)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// chargeCallRecipientEIP2780 applies the EIP-2780 runtime charges for the
|
||||||
|
// top-level recipient of a message-call transaction, before the first frame is
|
||||||
|
// entered:
|
||||||
|
//
|
||||||
|
// - if the recipient is EIP-161 empty and the transaction carries value,
|
||||||
|
// the durable state growth of the new account;
|
||||||
|
//
|
||||||
|
// - if the recipient is an EIP-7702 delegated account, resolving the
|
||||||
|
// delegation loads the target's code: a cold account access, or a warm
|
||||||
|
// access if the target is already warm.
|
||||||
|
//
|
||||||
|
// Each charge is deducted before the state access it prices is performed:
|
||||||
|
// under EIP-7928 every account load is recorded in the block access list, so
|
||||||
|
// an access the budget cannot cover must not happen at all.
|
||||||
|
func (st *stateTransition) chargeCallRecipientEIP2780(value *uint256.Int) bool {
|
||||||
|
to := *st.msg.To
|
||||||
|
|
||||||
|
// This runs in the topmost frame before any bytecode executes, non-existence
|
||||||
|
// is equivalent with EIP-161-empty, as no preceding operation can leave a
|
||||||
|
// transient EIP-161-empty account (such as zero-value transfer).
|
||||||
|
if !value.IsZero() && st.state.Empty(to) {
|
||||||
|
if !st.chargeRuntimeGas(vm.GasCosts{StateGas: params.AccountCreationSize * st.evm.Context.CostPerStateByte}) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if target, delegated := types.ParseDelegation(st.state.GetCode(to)); delegated {
|
||||||
|
// Pay the delegation-target access before the target is warmed and
|
||||||
|
// its code resolved (loaded).
|
||||||
|
cost := vm.GasCosts{RegularGas: params.ColdAccountAccessAmsterdam}
|
||||||
|
if st.state.AddressInAccessList(target) {
|
||||||
|
cost.RegularGas = params.WarmAccountAccessAmsterdam
|
||||||
|
}
|
||||||
|
if !st.chargeRuntimeGas(cost) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
st.state.AddAddressToAccessList(target)
|
||||||
|
|
||||||
|
// Record the delegation in the block level accessList explicitly
|
||||||
|
st.state.GetCode(target)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
@ -852,27 +930,11 @@ func (st *stateTransition) chargeCallRecipientEIP2780(value *uint256.Int) bool {
|
||||||
// settleGas finalizes the per-tx gas accounting after EVM execution:
|
// settleGas finalizes the per-tx gas accounting after EVM execution:
|
||||||
//
|
//
|
||||||
// - Snapshots the EIP-8037 block-level 2D figures (tx_regular_gas,
|
// - Snapshots the EIP-8037 block-level 2D figures (tx_regular_gas,
|
||||||
// tx_state_gas) before any refund or floor:
|
// tx_state_gas) before any refund.
|
||||||
//
|
|
||||||
// tx_gas_used_before_refund = tx.gas - gas_left - state_gas_reservoir
|
|
||||||
// tx_state_gas = state_gas_used
|
|
||||||
// tx_regular_gas = tx_gas_used_before_refund - tx_state_gas
|
|
||||||
//
|
|
||||||
// - Computes the receipt scalar tx_gas_used by applying the EIP-3529
|
// - Computes the receipt scalar tx_gas_used by applying the EIP-3529
|
||||||
// refund (capped at tx_gas_used_before_refund/5) and the EIP-7623
|
// refund and the EIP-7623 calldata floor.
|
||||||
// calldata floor:
|
|
||||||
//
|
|
||||||
// tx_gas_used = max(tx_gas_used_before_refund - tx_gas_refund, calldata_floor)
|
|
||||||
//
|
|
||||||
// - Charges the block gas pool (2D under Amsterdam, scalar pre-Amsterdam).
|
// - Charges the block gas pool (2D under Amsterdam, scalar pre-Amsterdam).
|
||||||
//
|
|
||||||
// - Refunds the leftover gas to the sender as ETH.
|
// - Refunds the leftover gas to the sender as ETH.
|
||||||
//
|
|
||||||
// Returns the receipt-level tx_gas_used and the pre-refund peak (consumed
|
|
||||||
// by gas-estimation callers via ExecutionResult.MaxUsedGas). UsedStateGas
|
|
||||||
// should never become negative in the top-most frame, since state-gas
|
|
||||||
// refunds occur only when state creation is reverted within the same
|
|
||||||
// transaction and clearing pre-existing state is never refunded.
|
|
||||||
func (st *stateTransition) settleGas(rules params.Rules, floorDataGas uint64) (gasUsed, peakUsed uint64, err error) {
|
func (st *stateTransition) settleGas(rules params.Rules, floorDataGas uint64) (gasUsed, peakUsed uint64, err error) {
|
||||||
if st.gasRemaining.UsedStateGas < 0 {
|
if st.gasRemaining.UsedStateGas < 0 {
|
||||||
return 0, 0, fmt.Errorf("negative topmost frame state gas usage, %d", st.gasRemaining.UsedStateGas)
|
return 0, 0, fmt.Errorf("negative topmost frame state gas usage, %d", st.gasRemaining.UsedStateGas)
|
||||||
|
|
@ -881,7 +943,7 @@ func (st *stateTransition) settleGas(rules params.Rules, floorDataGas uint64) (g
|
||||||
|
|
||||||
// EIP-8037:
|
// EIP-8037:
|
||||||
// tx_gas_used_before_refund = tx.gas - tx_output.gas_left - tx_output.state_gas_reservoir
|
// tx_gas_used_before_refund = tx.gas - tx_output.gas_left - tx_output.state_gas_reservoir
|
||||||
// tx_state_gas = intrinsic_state_gas + tx_output.execution_state_gas_used
|
// tx_state_gas = tx_output.execution_state_gas_used
|
||||||
// tx_regular_gas = max(tx_gas_used_before_refund - tx_state_gas, calldata_floor_gas_cost)
|
// tx_regular_gas = max(tx_gas_used_before_refund - tx_state_gas, calldata_floor_gas_cost)
|
||||||
gasLeft := st.gasRemaining.RegularGas + st.gasRemaining.StateGas
|
gasLeft := st.gasRemaining.RegularGas + st.gasRemaining.StateGas
|
||||||
gasUsedBeforeRefund := st.msg.GasLimit - gasLeft
|
gasUsedBeforeRefund := st.msg.GasLimit - gasLeft
|
||||||
|
|
@ -911,6 +973,7 @@ func (st *stateTransition) settleGas(rules params.Rules, floorDataGas uint64) (g
|
||||||
peakUsed = max(peakUsed, floorDataGas)
|
peakUsed = max(peakUsed, floorDataGas)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Settle down the final gas consumption in the block-level pool
|
||||||
if rules.IsAmsterdam {
|
if rules.IsAmsterdam {
|
||||||
if err = st.gp.ChargeGasAmsterdam(txRegularGas, txStateGas, gasUsed); err != nil {
|
if err = st.gp.ChargeGasAmsterdam(txRegularGas, txStateGas, gasUsed); err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
|
|
@ -921,7 +984,7 @@ func (st *stateTransition) settleGas(rules params.Rules, floorDataGas uint64) (g
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Refund leftover gas to the sender as ETH.
|
// Refund leftover gas to the sender
|
||||||
if gasLeft > 0 {
|
if gasLeft > 0 {
|
||||||
refund := new(uint256.Int).Mul(uint256.NewInt(gasLeft), st.msg.GasPrice)
|
refund := new(uint256.Int).Mul(uint256.NewInt(gasLeft), st.msg.GasPrice)
|
||||||
st.state.AddBalance(st.msg.From, refund, tracing.BalanceIncreaseGasReturn)
|
st.state.AddBalance(st.msg.From, refund, tracing.BalanceIncreaseGasReturn)
|
||||||
|
|
@ -964,51 +1027,71 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio
|
||||||
return authority, nil
|
return authority, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyAuthorization applies an EIP-7702 code delegation to the state and,
|
// authTracking tracks the charges already paid for an authority by earlier
|
||||||
// adjust the pre-charged intrinsic cost accordingly.
|
// authorizations in the same transaction.
|
||||||
func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.SetCodeAuthorization, delegates map[common.Address]bool) error {
|
type authTracking struct {
|
||||||
|
written bool // first-write ACCOUNT_WRITE surcharge paid
|
||||||
|
authBaseCovered bool // indicator exists at tx start, or paid earlier
|
||||||
|
}
|
||||||
|
|
||||||
|
// applyAuthorization applies an EIP-7702 code delegation to the state.
|
||||||
|
func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.SetCodeAuthorization, authorities map[common.Address]*authTracking) error {
|
||||||
authority, err := st.validateAuthorization(auth)
|
authority, err := st.validateAuthorization(auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if rules.IsAmsterdam {
|
|
||||||
st.gasRemaining.RefundStateToReservoir((params.AccountCreationSize + params.AuthorizationCreationSize) * st.evm.Context.CostPerStateByte)
|
|
||||||
st.state.AddRefund(params.AccountWriteAmsterdam)
|
|
||||||
}
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
prevDelegation, curDelegated := types.ParseDelegation(st.state.GetCode(authority))
|
oldDelegation, curDelegated := types.ParseDelegation(st.state.GetCode(authority))
|
||||||
|
|
||||||
if !rules.IsAmsterdam {
|
if !rules.IsAmsterdam {
|
||||||
if st.state.Exist(authority) {
|
if st.state.Exist(authority) {
|
||||||
st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas)
|
st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if st.state.Exist(authority) {
|
// EIP-2780: charge the state-dependent authorization costs at runtime.
|
||||||
st.gasRemaining.RefundStateToReservoir(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
|
// The authority's cold access was already charged unconditionally at the
|
||||||
st.state.AddRefund(params.AccountWriteAmsterdam)
|
// intrinsic phase, so only state-dependent costs remain here.
|
||||||
}
|
var cost vm.GasCosts
|
||||||
authBase := params.AuthorizationCreationSize * st.evm.Context.CostPerStateByte
|
|
||||||
|
|
||||||
preDelegated, ok := delegates[authority]
|
track := authorities[authority]
|
||||||
if !ok {
|
if track == nil {
|
||||||
preDelegated = curDelegated
|
track = &authTracking{authBaseCovered: curDelegated}
|
||||||
delegates[authority] = preDelegated
|
authorities[authority] = track
|
||||||
}
|
}
|
||||||
if auth.Address == (common.Address{}) {
|
// Every valid authorization writes the authority account: the
|
||||||
// Clearing writes no indicator, refill this auth's state charge.
|
// nonce bump, and possibly the delegation indicator. The first
|
||||||
st.gasRemaining.RefundStateToReservoir(authBase)
|
// write to an account within the transaction carries the
|
||||||
|
// first-write surcharge. At this point the accounts whose write
|
||||||
// The indicator was created by an earlier auth within the same
|
// has already been paid for are:
|
||||||
// transaction, refill the state charge as it's no longer justified.
|
//
|
||||||
if curDelegated && !preDelegated {
|
// - the sender: TX_BASE_COST prices its account write, and the
|
||||||
st.gasRemaining.RefundStateToReservoir(authBase)
|
// gas prepayment and nonce bump have already happened;
|
||||||
}
|
//
|
||||||
} else if curDelegated || preDelegated {
|
// - authorities written by preceding valid authorizations in
|
||||||
// The 23-byte slot is already occupied, overwriting it writes no
|
// this list, which carried the surcharge themselves;
|
||||||
// new bytes, refill the state charge.
|
//
|
||||||
st.gasRemaining.RefundStateToReservoir(authBase)
|
// - tx.to, but only when the transaction carries value:
|
||||||
|
// TX_VALUE_COST prepaid the recipient write at the intrinsic
|
||||||
|
// phase. A zero-value transaction pays no TX_VALUE_COST, so a
|
||||||
|
// write to tx.to here is still the first paid write.
|
||||||
|
hasValue := st.msg.Value != nil && !st.msg.Value.IsZero()
|
||||||
|
if !track.written && authority != st.msg.From && (authority != st.to() || !hasValue) {
|
||||||
|
cost.RegularGas += params.AccountWriteAmsterdam
|
||||||
|
track.written = true
|
||||||
|
}
|
||||||
|
// Durable state growth of the new account
|
||||||
|
if st.state.Empty(authority) {
|
||||||
|
cost.StateGas += params.AccountCreationSize * st.evm.Context.CostPerStateByte
|
||||||
|
}
|
||||||
|
// Charge the net-new indicator bytes at most once per authority;
|
||||||
|
// clearing within the same transaction refunds nothing.
|
||||||
|
if auth.Address != (common.Address{}) && !track.authBaseCovered {
|
||||||
|
cost.StateGas += params.AuthorizationCreationSize * st.evm.Context.CostPerStateByte
|
||||||
|
track.authBaseCovered = true
|
||||||
|
}
|
||||||
|
if !st.chargeRuntimeGas(cost) {
|
||||||
|
return ErrOutOfGasRuntime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
||||||
|
|
||||||
|
|
@ -1020,18 +1103,23 @@ func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.Se
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
// Install delegation to auth.Address if the delegation changed
|
// Install delegation to auth.Address if the delegation changed
|
||||||
if !curDelegated || auth.Address != prevDelegation {
|
if !curDelegated || auth.Address != oldDelegation {
|
||||||
st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization)
|
st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyAuthorizations applies an EIP-7702 code delegation to the state.
|
// applyAuthorizations applies the EIP-7702 code delegations to the state.
|
||||||
func (st *stateTransition) applyAuthorizations(rules params.Rules, auths []types.SetCodeAuthorization) {
|
// It reports whether the transaction budget covered all runtime authorization
|
||||||
preDelegated := make(map[common.Address]bool)
|
// charges.
|
||||||
|
func (st *stateTransition) applyAuthorizations(rules params.Rules, auths []types.SetCodeAuthorization) bool {
|
||||||
|
authorities := make(map[common.Address]*authTracking)
|
||||||
for _, auth := range auths {
|
for _, auth := range auths {
|
||||||
st.applyAuthorization(rules, &auth, preDelegated)
|
if err := st.applyAuthorization(rules, &auth, authorities); err == ErrOutOfGasRuntime {
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// calcRefund computes the EIP-3529 refund cap against tx_gas_used_before_refund.
|
// calcRefund computes the EIP-3529 refund cap against tx_gas_used_before_refund.
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ import (
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"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/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
)
|
)
|
||||||
|
|
@ -158,50 +157,50 @@ func TestIntrinsicGas(t *testing.T) {
|
||||||
isEIP3860 bool
|
isEIP3860 bool
|
||||||
isAmsterdam bool
|
isAmsterdam bool
|
||||||
value *uint256.Int
|
value *uint256.Int
|
||||||
want vm.GasCosts
|
want uint64
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "frontier/empty-call",
|
name: "frontier/empty-call",
|
||||||
want: vm.GasCosts{RegularGas: params.TxGas},
|
want: 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: vm.GasCosts{RegularGas: params.TxGas},
|
want: params.TxGas,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "homestead/contract-creation",
|
name: "homestead/contract-creation",
|
||||||
creation: true,
|
creation: true,
|
||||||
isHomestead: true,
|
isHomestead: true,
|
||||||
want: vm.GasCosts{RegularGas: params.TxGasContractCreation},
|
want: 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: vm.GasCosts{RegularGas: params.TxGas + 100*params.TxDataNonZeroGasFrontier},
|
want: 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: vm.GasCosts{RegularGas: params.TxGas + 100*params.TxDataNonZeroGasEIP2028},
|
want: 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: vm.GasCosts{RegularGas: params.TxGas + 100*params.TxDataZeroGas},
|
want: 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: vm.GasCosts{RegularGas: params.TxGas + 50*params.TxDataZeroGas + 50*params.TxDataNonZeroGasEIP2028},
|
want: params.TxGas + 50*params.TxDataZeroGas + 50*params.TxDataNonZeroGasEIP2028,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "shanghai/init-code-word-gas",
|
name: "shanghai/init-code-word-gas",
|
||||||
|
|
@ -211,7 +210,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: vm.GasCosts{RegularGas: params.TxGasContractCreation + 64*params.TxDataZeroGas + 2*params.InitCodeWordGas},
|
want: params.TxGasContractCreation + 64*params.TxDataZeroGas + 2*params.InitCodeWordGas,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "shanghai/init-code-non-multiple-of-32",
|
name: "shanghai/init-code-non-multiple-of-32",
|
||||||
|
|
@ -220,7 +219,7 @@ func TestIntrinsicGas(t *testing.T) {
|
||||||
isHomestead: true,
|
isHomestead: true,
|
||||||
isEIP2028: true,
|
isEIP2028: true,
|
||||||
isEIP3860: true,
|
isEIP3860: true,
|
||||||
want: vm.GasCosts{RegularGas: params.TxGasContractCreation + 33*params.TxDataZeroGas + 2*params.InitCodeWordGas},
|
want: params.TxGasContractCreation + 33*params.TxDataZeroGas + 2*params.InitCodeWordGas,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "berlin/access-list",
|
name: "berlin/access-list",
|
||||||
|
|
@ -230,7 +229,7 @@ func TestIntrinsicGas(t *testing.T) {
|
||||||
},
|
},
|
||||||
isEIP2028: true,
|
isEIP2028: true,
|
||||||
// 2 addrs * 2400 + 3 keys * 1900
|
// 2 addrs * 2400 + 3 keys * 1900
|
||||||
want: vm.GasCosts{RegularGas: params.TxGas + 2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas},
|
want: params.TxGas + 2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "amsterdam/access-list-extra-cost",
|
name: "amsterdam/access-list-extra-cost",
|
||||||
|
|
@ -241,10 +240,12 @@ func TestIntrinsicGas(t *testing.T) {
|
||||||
isEIP2028: true,
|
isEIP2028: true,
|
||||||
isAmsterdam: true,
|
isAmsterdam: true,
|
||||||
// EIP-2780: zero-value call base is TxBaseCost + ColdAccountAccess
|
// EIP-2780: zero-value call base is TxBaseCost + ColdAccountAccess
|
||||||
// (15,000). Plus base access-list charge + EIP-7981 extra.
|
// (15,000); the recipient touch is charged at the cold rate
|
||||||
want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
|
// unconditionally at the intrinsic phase. Plus base access-list
|
||||||
|
// charge + EIP-7981 extra.
|
||||||
|
want: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam +
|
||||||
2*params.TxAccessListAddressGasAmsterdam + 3*params.TxAccessListStorageKeyGasAmsterdam +
|
2*params.TxAccessListAddressGasAmsterdam + 3*params.TxAccessListStorageKeyGasAmsterdam +
|
||||||
2*amsterdamAddressCost + 3*amsterdamStorageKeyCost},
|
2*amsterdamAddressCost + 3*amsterdamStorageKeyCost,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "prague/auth-list",
|
name: "prague/auth-list",
|
||||||
|
|
@ -255,7 +256,7 @@ func TestIntrinsicGas(t *testing.T) {
|
||||||
},
|
},
|
||||||
isEIP2028: true,
|
isEIP2028: true,
|
||||||
// 3 auths * 25000 (pre-Amsterdam: CallNewAccountGas per auth tuple)
|
// 3 auths * 25000 (pre-Amsterdam: CallNewAccountGas per auth tuple)
|
||||||
want: vm.GasCosts{RegularGas: params.TxGas + 3*params.CallNewAccountGas},
|
want: params.TxGas + 3*params.CallNewAccountGas,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "amsterdam/contract-creation-empty",
|
name: "amsterdam/contract-creation-empty",
|
||||||
|
|
@ -263,12 +264,9 @@ func TestIntrinsicGas(t *testing.T) {
|
||||||
isHomestead: true,
|
isHomestead: true,
|
||||||
isEIP2028: true,
|
isEIP2028: true,
|
||||||
isAmsterdam: true,
|
isAmsterdam: true,
|
||||||
// EIP-2780: creation regular gas is TxBaseCost + CreateAccess (23,000),
|
// EIP-2780: creation regular gas is TxBaseCost + CreateAccess (23,000);
|
||||||
// and account-creation cost is charged as state gas.
|
// the new-account state charge is applied at runtime.
|
||||||
want: vm.GasCosts{
|
want: params.TxBaseCost2780 + params.CreateAccessAmsterdam,
|
||||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780,
|
|
||||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "amsterdam/contract-creation-init-code",
|
name: "amsterdam/contract-creation-init-code",
|
||||||
|
|
@ -278,11 +276,8 @@ func TestIntrinsicGas(t *testing.T) {
|
||||||
isEIP2028: true,
|
isEIP2028: true,
|
||||||
isEIP3860: true, // Shanghai gates init-code word gas
|
isEIP3860: true, // Shanghai gates init-code word gas
|
||||||
isAmsterdam: true,
|
isAmsterdam: true,
|
||||||
want: vm.GasCosts{
|
want: params.TxBaseCost2780 + params.CreateAccessAmsterdam +
|
||||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 +
|
64*params.TxDataZeroGas + 2*params.InitCodeWordGas,
|
||||||
64*params.TxDataZeroGas + 2*params.InitCodeWordGas,
|
|
||||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "amsterdam/contract-creation-with-access-list",
|
name: "amsterdam/contract-creation-with-access-list",
|
||||||
|
|
@ -295,13 +290,10 @@ func TestIntrinsicGas(t *testing.T) {
|
||||||
isEIP2028: true,
|
isEIP2028: true,
|
||||||
isEIP3860: true,
|
isEIP3860: true,
|
||||||
isAmsterdam: true,
|
isAmsterdam: true,
|
||||||
want: vm.GasCosts{
|
want: params.TxBaseCost2780 + params.CreateAccessAmsterdam +
|
||||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 +
|
32*params.TxDataNonZeroGasEIP2028 + 1*params.InitCodeWordGas +
|
||||||
32*params.TxDataNonZeroGasEIP2028 + 1*params.InitCodeWordGas +
|
1*params.TxAccessListAddressGasAmsterdam + 1*params.TxAccessListStorageKeyGasAmsterdam +
|
||||||
1*params.TxAccessListAddressGasAmsterdam + 1*params.TxAccessListStorageKeyGasAmsterdam +
|
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost,
|
||||||
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost,
|
|
||||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "amsterdam/combined",
|
name: "amsterdam/combined",
|
||||||
|
|
@ -314,18 +306,15 @@ func TestIntrinsicGas(t *testing.T) {
|
||||||
},
|
},
|
||||||
isEIP2028: true,
|
isEIP2028: true,
|
||||||
isAmsterdam: true,
|
isAmsterdam: true,
|
||||||
// EIP-8037 splits the auth-tuple charge into regular + state gas, with
|
// EIP-2780: the recipient touch and the per-authorization authority
|
||||||
// the values finalized by EIP-8038:
|
// access (priced into RegularPerAuthBaseCost) are both charged at the
|
||||||
// regular: ACCOUNT_WRITE (8,000) + REGULAR_PER_AUTH_BASE_COST (7,500) per auth
|
// cold rate unconditionally at the intrinsic phase; the account leaf
|
||||||
// state: (AuthorizationCreationSize + AccountCreationSize) * CostPerStateByte per auth
|
// and indicator bytes are charged at runtime.
|
||||||
want: vm.GasCosts{
|
want: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam +
|
||||||
RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
|
100*params.TxDataNonZeroGasEIP2028 +
|
||||||
100*params.TxDataNonZeroGasEIP2028 +
|
1*params.TxAccessListAddressGasAmsterdam + 1*params.TxAccessListStorageKeyGasAmsterdam +
|
||||||
1*params.TxAccessListAddressGasAmsterdam + 1*params.TxAccessListStorageKeyGasAmsterdam +
|
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost +
|
||||||
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost +
|
1*params.RegularPerAuthBaseCost,
|
||||||
1*(params.AccountWriteAmsterdam+params.RegularPerAuthBaseCost),
|
|
||||||
StateGas: 1 * (params.AuthorizationCreationSize + params.AccountCreationSize) * params.CostPerStateByte,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "amsterdam/value-transfer-call",
|
name: "amsterdam/value-transfer-call",
|
||||||
|
|
@ -333,8 +322,8 @@ func TestIntrinsicGas(t *testing.T) {
|
||||||
isAmsterdam: true,
|
isAmsterdam: true,
|
||||||
value: uint256.NewInt(1),
|
value: uint256.NewInt(1),
|
||||||
// EIP-2780: TxBaseCost + ColdAccountAccess + TransferLogCost + TxValueCost = 21,000.
|
// EIP-2780: TxBaseCost + ColdAccountAccess + TransferLogCost + TxValueCost = 21,000.
|
||||||
want: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
|
want: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam +
|
||||||
params.TransferLogCost2780 + params.TxValueCost2780},
|
params.TransferLogCost2780 + params.TxValueCost2780,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "amsterdam/value-bearing-contract-creation",
|
name: "amsterdam/value-bearing-contract-creation",
|
||||||
|
|
@ -343,11 +332,9 @@ func TestIntrinsicGas(t *testing.T) {
|
||||||
isEIP2028: true,
|
isEIP2028: true,
|
||||||
isAmsterdam: true,
|
isAmsterdam: true,
|
||||||
value: uint256.NewInt(1),
|
value: uint256.NewInt(1),
|
||||||
// EIP-2780: TxBaseCost + CreateAccess + TransferLogCost = 24,756, plus account-creation state gas.
|
// EIP-2780: TxBaseCost + CreateAccess + TransferLogCost = 24,756;
|
||||||
want: vm.GasCosts{
|
// the new-account state charge is applied at runtime.
|
||||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 + params.TransferLogCost2780,
|
want: params.TxBaseCost2780 + params.CreateAccessAmsterdam + params.TransferLogCost2780,
|
||||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
|
|
@ -363,7 +350,7 @@ func TestIntrinsicGas(t *testing.T) {
|
||||||
to = &addr1
|
to = &addr1
|
||||||
}
|
}
|
||||||
got, err := IntrinsicGas(tt.data, tt.accessList, tt.authList,
|
got, err := IntrinsicGas(tt.data, tt.accessList, tt.authList,
|
||||||
common.Address{}, to, tt.value, rules, params.CostPerStateByte)
|
common.Address{}, to, tt.value, rules)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,21 +29,23 @@ func _() {
|
||||||
_ = x[GasChangeWitnessContractCollisionCheck-18]
|
_ = x[GasChangeWitnessContractCollisionCheck-18]
|
||||||
_ = x[GasChangeTxDataFloor-19]
|
_ = x[GasChangeTxDataFloor-19]
|
||||||
_ = x[GasChangeRefundAccountCreation-20]
|
_ = x[GasChangeRefundAccountCreation-20]
|
||||||
|
_ = x[GasChangeTxRuntimeGas-21]
|
||||||
|
_ = x[GasChangeAccountCreation-22]
|
||||||
_ = x[GasChangeIgnored-255]
|
_ = x[GasChangeIgnored-255]
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_GasChangeReason_name_0 = "UnspecifiedTxInitialBalanceTxIntrinsicGasTxRefundsTxLeftOverReturnedCallInitialBalanceCallLeftOverReturnedCallLeftOverRefundedCallContractCreationCallContractCreation2CallCodeStorageCallOpCodeCallPrecompiledContractCallStorageColdAccessCallFailedExecutionWitnessContractInitWitnessContractCreationWitnessCodeChunkWitnessContractCollisionCheckTxDataFloorRefundAccountCreation"
|
_GasChangeReason_name_0 = "UnspecifiedTxInitialBalanceTxIntrinsicGasTxRefundsTxLeftOverReturnedCallInitialBalanceCallLeftOverReturnedCallLeftOverRefundedCallContractCreationCallContractCreation2CallCodeStorageCallOpCodeCallPrecompiledContractCallStorageColdAccessCallFailedExecutionWitnessContractInitWitnessContractCreationWitnessCodeChunkWitnessContractCollisionCheckTxDataFloorRefundAccountCreationTxRuntimeGasAccountCreation"
|
||||||
_GasChangeReason_name_1 = "Ignored"
|
_GasChangeReason_name_1 = "Ignored"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
_GasChangeReason_index_0 = [...]uint16{0, 11, 27, 41, 50, 68, 86, 106, 126, 146, 167, 182, 192, 215, 236, 255, 274, 297, 313, 342, 353, 374}
|
_GasChangeReason_index_0 = [...]uint16{0, 11, 27, 41, 50, 68, 86, 106, 126, 146, 167, 182, 192, 215, 236, 255, 274, 297, 313, 342, 353, 374, 386, 401}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (i GasChangeReason) String() string {
|
func (i GasChangeReason) String() string {
|
||||||
switch {
|
switch {
|
||||||
case i <= 20:
|
case i <= 22:
|
||||||
return _GasChangeReason_name_0[_GasChangeReason_index_0[i]:_GasChangeReason_index_0[i+1]]
|
return _GasChangeReason_name_0[_GasChangeReason_index_0[i]:_GasChangeReason_index_0[i+1]]
|
||||||
case i == 255:
|
case i == 255:
|
||||||
return _GasChangeReason_name_1
|
return _GasChangeReason_name_1
|
||||||
|
|
|
||||||
|
|
@ -476,6 +476,15 @@ const (
|
||||||
// pre-charged account-creation cost when no account is created.
|
// pre-charged account-creation cost when no account is created.
|
||||||
GasChangeRefundAccountCreation GasChangeReason = 20
|
GasChangeRefundAccountCreation GasChangeReason = 20
|
||||||
|
|
||||||
|
// GasChangeTxRuntimeGas is the amount of gas charged for the state-dependent
|
||||||
|
// costs of the transaction per EIP-2780.
|
||||||
|
GasChangeTxRuntimeGas GasChangeReason = 21
|
||||||
|
|
||||||
|
// GasChangeAccountCreation represents the conditional account-creation
|
||||||
|
// state cost charged in the creating frame when a CREATE/CREATE2 is about
|
||||||
|
// to create a new account (EIP-8037).
|
||||||
|
GasChangeAccountCreation GasChangeReason = 22
|
||||||
|
|
||||||
// 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
|
||||||
|
|
|
||||||
|
|
@ -132,12 +132,12 @@ 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(), from, tx.To(), value, rules, params.CostPerStateByte)
|
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), from, tx.To(), value, rules)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if tx.Gas() < intrGas.RegularGas {
|
if tx.Gas() < intrGas {
|
||||||
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), intrGas.RegularGas)
|
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrIntrinsicGas, tx.Gas(), intrGas)
|
||||||
}
|
}
|
||||||
// Ensure the transaction can cover floor data gas.
|
// Ensure the transaction can cover floor data gas.
|
||||||
if rules.IsPrague {
|
if rules.IsPrague {
|
||||||
|
|
@ -152,8 +152,8 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
||||||
}
|
}
|
||||||
// In Amsterdam, the transaction gas limit is allowed to exceed
|
// In Amsterdam, the transaction gas limit is allowed to exceed
|
||||||
// params.MaxTxGas, but the calldata floor cost is capped by it.
|
// params.MaxTxGas, but the calldata floor cost is capped by it.
|
||||||
if rules.IsAmsterdam && max(intrGas.RegularGas, floorDataGas) > params.MaxTxGas {
|
if rules.IsAmsterdam && max(intrGas, floorDataGas) > params.MaxTxGas {
|
||||||
return fmt.Errorf("%w: regular intrisic cost %v, floor: %v", core.ErrFloorDataGas, intrGas.RegularGas, floorDataGas)
|
return fmt.Errorf("%w: intrinsic cost %v, floor: %v", core.ErrFloorDataGas, intrGas, 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
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,6 @@ func CheckMaxInitCodeSize(rules *params.Rules, size uint64) error {
|
||||||
return fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, size, params.MaxInitCodeSize)
|
return fmt.Errorf("%w: code size %v limit %v", ErrMaxInitCodeSizeExceeded, size, params.MaxInitCodeSize)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -473,20 +473,57 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
|
||||||
return ret, exitGas, err
|
return ret, exitGas, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// create creates a new contract using code as deployment code.
|
// createFramePreCheck the precondition before executing the contract deployment,
|
||||||
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, creation bool, err error) {
|
// halts the create frame if fails with any check below.
|
||||||
// Depth check execution. Fail if we're trying to execute above the
|
func (evm *EVM) createFramePreCheck(caller common.Address, value *uint256.Int) error {
|
||||||
// limit.
|
|
||||||
var nonce uint64
|
|
||||||
if evm.depth > int(params.CallCreateDepth) {
|
if evm.depth > int(params.CallCreateDepth) {
|
||||||
err = ErrDepth
|
return ErrDepth
|
||||||
} else if !evm.Context.CanTransfer(evm.StateDB, caller, value) {
|
}
|
||||||
err = ErrInsufficientBalance
|
if !evm.Context.CanTransfer(evm.StateDB, caller, value) {
|
||||||
} else {
|
return ErrInsufficientBalance
|
||||||
nonce = evm.StateDB.GetNonce(caller)
|
}
|
||||||
if nonce+1 < nonce {
|
nonce := evm.StateDB.GetNonce(caller)
|
||||||
err = ErrNonceUintOverflow
|
if nonce+1 < nonce {
|
||||||
}
|
return ErrNonceUintOverflow
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// chargeAccountCreation runs the create-frame precheck and charges the
|
||||||
|
// account-creation state gas since Amsterdam, before the 63/64ths split.
|
||||||
|
//
|
||||||
|
// The charge only applies if the destination is empty, skipping pre-funded
|
||||||
|
// deployment destinations. Note, a destination colliding on storage alone
|
||||||
|
// (zero nonce, zero balance, empty code) is still empty and is charged.
|
||||||
|
//
|
||||||
|
// If halt is true, the caller must terminate with the returned error:
|
||||||
|
// - a failed precheck halts the create frame only and parent frame continues,
|
||||||
|
// - an insufficient charge halts the parent frame with ErrOutOfGas.
|
||||||
|
func (evm *EVM) chargeAccountCreation(scope *ScopeContext, contractAddr common.Address, value *uint256.Int) (charged, halt bool, err error) {
|
||||||
|
if !evm.chainRules.IsAmsterdam {
|
||||||
|
return false, false, nil
|
||||||
|
}
|
||||||
|
if err := evm.createFramePreCheck(scope.Contract.Address(), value); err != nil {
|
||||||
|
scope.Stack.get().Clear()
|
||||||
|
evm.returnData = nil
|
||||||
|
return false, true, nil
|
||||||
|
}
|
||||||
|
if !evm.StateDB.Empty(contractAddr) {
|
||||||
|
return false, false, nil
|
||||||
|
}
|
||||||
|
cost := params.AccountCreationSize * evm.Context.CostPerStateByte
|
||||||
|
if !scope.Contract.chargeState(cost, evm.Config.Tracer, tracing.GasChangeAccountCreation) {
|
||||||
|
return false, true, ErrOutOfGas
|
||||||
|
}
|
||||||
|
return true, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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, result GasBudget, err error) {
|
||||||
|
// Since Amsterdam, the precheck has been folded into the parent frame
|
||||||
|
// due to account-creation determination, so skip the duplicate check here.
|
||||||
|
if !evm.chainRules.IsAmsterdam {
|
||||||
|
err = evm.createFramePreCheck(caller, value)
|
||||||
}
|
}
|
||||||
if evm.Config.Tracer != nil {
|
if evm.Config.Tracer != nil {
|
||||||
evm.captureBegin(evm.depth, typ, caller, address, code, gas, value.ToBig())
|
evm.captureBegin(evm.depth, typ, caller, address, code, gas, value.ToBig())
|
||||||
|
|
@ -495,17 +532,17 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
||||||
}(gas)
|
}(gas)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, common.Address{}, gas, false, err
|
return nil, common.Address{}, gas, err
|
||||||
}
|
}
|
||||||
// Increment the caller's nonce after passing all validations
|
// Increment the caller's nonce after passing all validations
|
||||||
evm.StateDB.SetNonce(caller, nonce+1, tracing.NonceChangeContractCreator)
|
evm.StateDB.SetNonce(caller, evm.StateDB.GetNonce(caller)+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 {
|
||||||
return nil, common.Address{}, gas.ExitHalt(), false, ErrOutOfGas
|
return nil, common.Address{}, gas.ExitHalt(), 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)
|
||||||
|
|
@ -532,7 +569,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
||||||
}
|
}
|
||||||
// EIP-8037 collision rule: the state reservoir is fully preserved on
|
// EIP-8037 collision rule: the state reservoir is fully preserved on
|
||||||
// address collision while regular gas is burnt.
|
// address collision while regular gas is burnt.
|
||||||
return nil, common.Address{}, halt, false, ErrContractAddressCollision
|
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
|
||||||
|
|
@ -540,7 +577,6 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
||||||
snapshot := evm.StateDB.Snapshot()
|
snapshot := evm.StateDB.Snapshot()
|
||||||
if !evm.StateDB.Exist(address) {
|
if !evm.StateDB.Exist(address) {
|
||||||
evm.StateDB.CreateAccount(address)
|
evm.StateDB.CreateAccount(address)
|
||||||
creation = true
|
|
||||||
}
|
}
|
||||||
// CreateContract means that regardless of whether the account previously existed
|
// CreateContract means that regardless of whether the account previously existed
|
||||||
// in the state trie or not, it _now_ becomes created as a _contract_ account.
|
// in the state trie or not, it _now_ becomes created as a _contract_ account.
|
||||||
|
|
@ -555,7 +591,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 {
|
||||||
return nil, common.Address{}, gas.ExitHalt(), false, ErrOutOfGas
|
return nil, common.Address{}, gas.ExitHalt(), ErrOutOfGas
|
||||||
}
|
}
|
||||||
prior, _ := gas.Charge(GasCosts{RegularGas: consumed})
|
prior, _ := gas.Charge(GasCosts{RegularGas: consumed})
|
||||||
if evm.Config.Tracer.HasGasHook() {
|
if evm.Config.Tracer.HasGasHook() {
|
||||||
|
|
@ -586,11 +622,11 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
||||||
evm.Config.Tracer.EmitGasChange(contract.Gas.AsTracing(), exit.AsTracing(), tracing.GasChangeCallFailedExecution)
|
evm.Config.Tracer.EmitGasChange(contract.Gas.AsTracing(), exit.AsTracing(), tracing.GasChangeCallFailedExecution)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret, address, exit, false, err
|
return ret, address, exit, err
|
||||||
}
|
}
|
||||||
// Either success, or pre-Homestead ErrCodeStoreOutOfGas (gas preserved).
|
// Either success, or pre-Homestead ErrCodeStoreOutOfGas (gas preserved).
|
||||||
// Both packaged as a success-form GasBudget.
|
// Both packaged as a success-form GasBudget.
|
||||||
return ret, address, contract.Gas.ExitSuccess(), creation, err
|
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
|
||||||
|
|
@ -646,7 +682,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, result GasBudget, creation bool, 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)
|
||||||
}
|
}
|
||||||
|
|
@ -655,7 +691,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, result GasBudget, creation bool, 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)
|
||||||
|
|
|
||||||
|
|
@ -560,12 +560,10 @@ func gasCreateEip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
|
||||||
words := (size + 31) / 32
|
words := (size + 31) / 32
|
||||||
wordGas := params.InitCodeWordGas * words
|
wordGas := params.InitCodeWordGas * words
|
||||||
|
|
||||||
// Unconditionally pre-charge the account creation and refunds if the creation
|
// The account-creation state gas is not part of the opcode cost: it is
|
||||||
// doesn't happen after the create-frame.
|
// charged conditionally at the destination access, in the creating frame,
|
||||||
return GasCosts{
|
// right before the 63/64ths split (see opCreate).
|
||||||
RegularGas: gas + wordGas,
|
return GasCosts{RegularGas: gas + wordGas}, nil
|
||||||
StateGas: params.AccountCreationSize * evm.Context.CostPerStateByte,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func gasCreate2Eip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
func gasCreate2Eip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||||
|
|
@ -590,12 +588,10 @@ func gasCreate2Eip8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory,
|
||||||
// (for address hashing).
|
// (for address hashing).
|
||||||
wordGas := (params.InitCodeWordGas + params.Keccak256WordGas) * words
|
wordGas := (params.InitCodeWordGas + params.Keccak256WordGas) * words
|
||||||
|
|
||||||
// Unconditionally pre-charge the account creation and refunds if the creation
|
// The account-creation state gas is not part of the opcode cost: it is
|
||||||
// doesn't happen after the create-frame.
|
// charged conditionally at the destination access, in the creating frame,
|
||||||
return GasCosts{
|
// right before the 63/64ths split (see opCreate2).
|
||||||
RegularGas: gas + wordGas,
|
return GasCosts{RegularGas: gas + wordGas}, nil
|
||||||
StateGas: params.AccountCreationSize * evm.Context.CostPerStateByte,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// regularGasCall8038 is the intrinsic regular-gas calculator for CALL in
|
// regularGasCall8038 is the intrinsic regular-gas calculator for CALL in
|
||||||
|
|
@ -635,19 +631,13 @@ func stateGasCall8037(evm *EVM, contract *Contract, stack *Stack) (uint64, error
|
||||||
transfersValue = !stack.back(2).IsZero()
|
transfersValue = !stack.back(2).IsZero()
|
||||||
address = common.Address(stack.back(1).Bytes20())
|
address = common.Address(stack.back(1).Bytes20())
|
||||||
)
|
)
|
||||||
// TODO(rjl, marius), can EIP8037 implicitly means the EIP158 is also activated?
|
// Important: use StateDB.Empty instead of !StateDB.Exist. An account may exist
|
||||||
// It's technically possible to skip the EIP158 but very unlikely in practice.
|
// in the current state yet still be considered non-existent by EIP-161 if its
|
||||||
if evm.chainRules.IsEIP158 {
|
// nonce, balance, and code are all zero. Such accounts can appear temporarily
|
||||||
// Important: use StateDB.Empty instead of !StateDB.Exist. An account may exist
|
// during execution (e.g. via SELFDESTRUCT) and are removed at tx end.
|
||||||
// in the current state yet still be considered non-existent by EIP-161 if its
|
//
|
||||||
// nonce, balance, and code are all zero. Such accounts can appear temporarily
|
// Funding such an account makes it permanent state growth and must be charged.
|
||||||
// during execution (e.g. via SELFDESTRUCT) and are removed at tx end.
|
if transfersValue && evm.StateDB.Empty(address) {
|
||||||
//
|
|
||||||
// Funding such an account makes it permanent state growth and must be charged.
|
|
||||||
if transfersValue && evm.StateDB.Empty(address) {
|
|
||||||
gas += params.AccountCreationSize * evm.Context.CostPerStateByte
|
|
||||||
}
|
|
||||||
} else if !evm.StateDB.Exist(address) {
|
|
||||||
gas += params.AccountCreationSize * evm.Context.CostPerStateByte
|
gas += params.AccountCreationSize * evm.Context.CostPerStateByte
|
||||||
}
|
}
|
||||||
return gas, nil
|
return gas, nil
|
||||||
|
|
@ -698,22 +688,26 @@ func gasSStore8037And8038(evm *EVM, contract *Contract, stack *Stack, mem *Memor
|
||||||
var (
|
var (
|
||||||
y, x = stack.back(1), stack.peek()
|
y, x = stack.back(1), stack.peek()
|
||||||
slot = common.Hash(x.Bytes32())
|
slot = common.Hash(x.Bytes32())
|
||||||
value = common.Hash(y.Bytes32())
|
|
||||||
stateSet = params.StorageCreationSize * evm.Context.CostPerStateByte
|
stateSet = params.StorageCreationSize * evm.Context.CostPerStateByte
|
||||||
)
|
)
|
||||||
// Check slot presence in the access list
|
// Check slot presence in the access list
|
||||||
access := params.WarmStorageReadCostEIP2929
|
access := params.WarmStorageAccessAmsterdam
|
||||||
if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
|
_, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot)
|
||||||
|
if !slotPresent {
|
||||||
access = params.ColdStorageAccessAmsterdam
|
access = params.ColdStorageAccessAmsterdam
|
||||||
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
|
|
||||||
}
|
}
|
||||||
// Check access cost affordability before reading slot
|
// Check access cost affordability before reading slot
|
||||||
if contract.Gas.RegularGas < access {
|
if contract.Gas.RegularGas < access {
|
||||||
return GasCosts{}, errors.New("not enough gas for slot access")
|
return GasCosts{}, errors.New("not enough gas for slot access")
|
||||||
}
|
}
|
||||||
|
if !slotPresent {
|
||||||
|
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
|
||||||
|
}
|
||||||
// Read the slot value for gas cost measurement
|
// Read the slot value for gas cost measurement
|
||||||
current, original := evm.StateDB.GetStateAndCommittedState(contract.Address(), slot)
|
var (
|
||||||
|
value = common.Hash(y.Bytes32())
|
||||||
|
current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), slot)
|
||||||
|
)
|
||||||
if current == value { // noop (1)
|
if current == value { // noop (1)
|
||||||
return GasCosts{RegularGas: access}, nil
|
return GasCosts{RegularGas: access}, nil
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,18 +48,6 @@ func (g GasCosts) String() string {
|
||||||
// - UsedRegularGas / UsedStateGas: per-frame accumulators tracking gross
|
// - UsedRegularGas / UsedStateGas: per-frame accumulators tracking gross
|
||||||
// consumption. UsedStateGas is signed so it can be decremented by inline
|
// consumption. UsedStateGas is signed so it can be decremented by inline
|
||||||
// state-gas refunds (e.g., SSTORE 0->A->0).
|
// 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: ExitSuccess / ExitRevert / ExitHalt 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 // remaining regular-gas balance (or leftover for caller to absorb)
|
RegularGas uint64 // remaining regular-gas balance (or leftover for caller to absorb)
|
||||||
StateGas uint64 // remaining state-gas reservoir (or leftover for caller to absorb)
|
StateGas uint64 // remaining state-gas reservoir (or leftover for caller to absorb)
|
||||||
|
|
@ -78,9 +66,7 @@ func NewGasBudget(regular, state uint64) GasBudget {
|
||||||
return GasBudget{RegularGas: regular, StateGas: state}
|
return GasBudget{RegularGas: regular, StateGas: state}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Used returns the total scalar gas consumed relative to an initial budget
|
// 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).
|
|
||||||
func (g GasBudget) Used(initial GasBudget) uint64 {
|
func (g GasBudget) Used(initial GasBudget) uint64 {
|
||||||
return (initial.RegularGas + initial.StateGas) - (g.RegularGas + g.StateGas)
|
return (initial.RegularGas + initial.StateGas) - (g.RegularGas + g.StateGas)
|
||||||
}
|
}
|
||||||
|
|
@ -91,16 +77,16 @@ func (g GasBudget) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Charge deducts a combined regular+state cost from the running balance and
|
// Charge deducts a combined regular+state cost from the running balance and
|
||||||
// updates the usage accumulators. State-gas in excess of the reservoir spills
|
// updates the usage accumulators.
|
||||||
// into regular_gas.
|
|
||||||
func (g *GasBudget) Charge(cost GasCosts) (GasBudget, bool) {
|
func (g *GasBudget) Charge(cost GasCosts) (GasBudget, bool) {
|
||||||
prior := *g
|
prior := *g
|
||||||
ok := g.charge(cost)
|
ok := g.charge(cost)
|
||||||
return prior, ok
|
return prior, ok
|
||||||
}
|
}
|
||||||
|
|
||||||
// chargeRegularOnly deducts a regular-only cost.
|
// ChargeRegularOnly deducts a regular-only cost. It's always preferred for
|
||||||
func (g *GasBudget) chargeRegularOnly(r uint64) bool {
|
// performance consideration if the opcode doesn't have any state cost.
|
||||||
|
func (g *GasBudget) ChargeRegularOnly(r uint64) bool {
|
||||||
if g.RegularGas < r {
|
if g.RegularGas < r {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
@ -110,9 +96,7 @@ func (g *GasBudget) chargeRegularOnly(r uint64) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// CanAfford reports whether the running budget can cover the given cost vector
|
// CanAfford reports whether the running budget can cover the given cost vector
|
||||||
// without going out of gas. The regular cost must fit in the regular balance,
|
// without going out of gas.
|
||||||
// and any state gas in excess of the reservoir must be coverable by the
|
|
||||||
// remaining regular gas (the spillover), mirroring charge without mutating.
|
|
||||||
func (g GasBudget) CanAfford(cost GasCosts) bool {
|
func (g GasBudget) CanAfford(cost GasCosts) bool {
|
||||||
if g.RegularGas < cost.RegularGas {
|
if g.RegularGas < cost.RegularGas {
|
||||||
return false
|
return false
|
||||||
|
|
@ -162,8 +146,7 @@ func (g *GasBudget) ChargeRegular(r uint64) (GasBudget, bool) {
|
||||||
return g.Charge(GasCosts{RegularGas: r})
|
return g.Charge(GasCosts{RegularGas: r})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ChargeState is a convenience that deducts a state-only cost (spills to
|
// ChargeState is a convenience that deducts a state-only cost.
|
||||||
// regular when the reservoir is exhausted). Returns false on OOG.
|
|
||||||
func (g *GasBudget) ChargeState(s uint64) (GasBudget, bool) {
|
func (g *GasBudget) ChargeState(s uint64) (GasBudget, bool) {
|
||||||
return g.Charge(GasCosts{StateGas: s})
|
return g.Charge(GasCosts{StateGas: s})
|
||||||
}
|
}
|
||||||
|
|
@ -174,56 +157,24 @@ func (g *GasBudget) IsZero() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RefundState applies an inline state-gas refund (e.g., SSTORE 0->A->0).
|
// RefundState applies an inline state-gas refund (e.g., SSTORE 0->A->0).
|
||||||
//
|
|
||||||
// Per EIP-8037, the refund repays the regular gas previously borrowed for
|
|
||||||
// state-gas spillover (tracked by Spilled) before crediting the
|
|
||||||
// reservoir: it is returned to RegularGas up to the outstanding borrowed
|
|
||||||
// amount, and only the remainder tops up StateGas.
|
|
||||||
//
|
|
||||||
// The signed usage counter is decremented by the full refund regardless of the
|
|
||||||
// split, preserving the per-frame invariant:
|
|
||||||
//
|
|
||||||
// StateGas + UsedStateGas == initialStateGas + Spilled
|
|
||||||
//
|
|
||||||
// which the revert and halt paths rely on for the correct gross refund.
|
|
||||||
func (g *GasBudget) RefundState(s uint64) {
|
func (g *GasBudget) RefundState(s uint64) {
|
||||||
repay := min(s, g.Spilled)
|
repay := min(s, g.Spilled)
|
||||||
g.RegularGas += repay
|
g.RegularGas += repay
|
||||||
g.Spilled -= repay
|
g.Spilled -= repay
|
||||||
|
|
||||||
// Whatever is left tops up the reservoir.
|
|
||||||
g.StateGas += s - repay
|
g.StateGas += s - repay
|
||||||
g.UsedStateGas -= int64(s)
|
g.UsedStateGas -= int64(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RefundStateToReservoir credits a state-gas refund directly to the
|
// DrainRegular burns the remaining regular-gas.
|
||||||
// reservoir, without repaying spilled regular gas first.
|
func (g *GasBudget) DrainRegular() {
|
||||||
//
|
g.UsedRegularGas += g.RegularGas
|
||||||
// Per the spec's set_delegation, authorization refunds (and the post-create
|
g.RegularGas = 0
|
||||||
// new-account refund) are added to message.state_gas_reservoir directly, in
|
|
||||||
// contrast to the LIFO inline refunds handled by RefundState. The usage
|
|
||||||
// counter is decremented by the full amount, matching the spec's
|
|
||||||
// tx_state_gas = intrinsic_state + state_gas_used - state_refund and
|
|
||||||
// preserving the per-frame invariant:
|
|
||||||
//
|
|
||||||
// StateGas + UsedStateGas == initialStateGas + Spilled
|
|
||||||
func (g *GasBudget) RefundStateToReservoir(s uint64) {
|
|
||||||
g.StateGas += s
|
|
||||||
g.UsedStateGas -= int64(s)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Forward drains `regular` regular gas and the entire state reservoir from
|
// Forward drains `regular` regular gas and the entire state reservoir from
|
||||||
// the parent's running budget and returns the initial GasBudget for a child
|
// the parent's running budget and returns the initial GasBudget for a child
|
||||||
// frame. The parent's UsedRegularGas is bumped by the forwarded amount so
|
// frame. The parent's UsedRegularGas is bumped by the forwarded amount so
|
||||||
// that the absorb-on-return path correctly reclaims the unused portion.
|
// that the absorb-on-return path correctly reclaims the unused portion.
|
||||||
//
|
|
||||||
// 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 {
|
func (g *GasBudget) Forward(regular uint64) GasBudget {
|
||||||
g.RegularGas -= regular
|
g.RegularGas -= regular
|
||||||
g.UsedRegularGas += regular
|
g.UsedRegularGas += regular
|
||||||
|
|
@ -249,19 +200,15 @@ func (g *GasBudget) ForwardAll() GasBudget {
|
||||||
// absorb to update its own state.
|
// absorb to update its own state.
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// ExitSuccess produces the leftover form for a successful frame. Inline
|
// ExitSuccess produces the leftover form for a successful frame.
|
||||||
// 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 {
|
func (g GasBudget) ExitSuccess() GasBudget {
|
||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExitRevert produces the leftover for a REVERT exit. The frame's state
|
// ExitRevert produces the leftover for a REVERT exit. The frame's state
|
||||||
// changes are discarded, so all state gas it charged is refilled to its origin
|
// changes are discarded, so all state gas it charged is refilled with LIFO
|
||||||
// (EIP-8037): up to Spilled is returned to RegularGas (the regular
|
// mechanism: up to Spilled is returned to RegularGas (the regular gas it
|
||||||
// gas it borrowed), and the remainder restores the reservoir. Because the
|
// borrowed), and the remainder restores the reservoir.
|
||||||
// borrowed regular gas is repaid first, the reservoir is made whole back to its
|
|
||||||
// start-of-frame value.
|
|
||||||
func (g GasBudget) ExitRevert() GasBudget {
|
func (g GasBudget) ExitRevert() GasBudget {
|
||||||
reservoir := int64(g.StateGas) + g.UsedStateGas - int64(g.Spilled)
|
reservoir := int64(g.StateGas) + g.UsedStateGas - int64(g.Spilled)
|
||||||
if reservoir < 0 {
|
if reservoir < 0 {
|
||||||
|
|
@ -280,10 +227,10 @@ func (g GasBudget) ExitRevert() GasBudget {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ExitHalt produces the leftover for an exceptional halt. As with a revert, the
|
// ExitHalt produces the leftover for an exceptional halt. As with a revert, the
|
||||||
// frame's state changes are rolled back and its state gas is refilled to origin
|
// frame's state changes are rolled back and its state gas is refilled with LIFO
|
||||||
// (EIP-8037); the difference is that the frame's gas_left is consumed rather
|
// mechanism. The difference is that the frame's regular gas is consumed rather
|
||||||
// than returned. The portion refilled to RegularGas is therefore burned along
|
// than returned. The portion refilled to RegularGas is therefore burned along
|
||||||
// with the rest of gas_left, leaving only the reservoir portion to survive,
|
// with the rest of regular gas, leaving only the reservoir portion to survive,
|
||||||
// which equals the reservoir's value at the start of the frame.
|
// which equals the reservoir's value at the start of the frame.
|
||||||
func (g GasBudget) ExitHalt() GasBudget {
|
func (g GasBudget) ExitHalt() GasBudget {
|
||||||
reservoir := int64(g.StateGas) + g.UsedStateGas - int64(g.Spilled)
|
reservoir := int64(g.StateGas) + g.UsedStateGas - int64(g.Spilled)
|
||||||
|
|
|
||||||
|
|
@ -635,7 +635,12 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
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())
|
||||||
|
contractAddr = crypto.CreateAddress(scope.Contract.Address(), evm.StateDB.GetNonce(scope.Contract.Address()))
|
||||||
)
|
)
|
||||||
|
creationCharged, halt, err := evm.chargeAccountCreation(scope, contractAddr, &value)
|
||||||
|
if halt {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
// Apply EIP-150 to the regular gas left after the state charge.
|
// Apply EIP-150 to the regular gas left after the state charge.
|
||||||
forward := scope.Contract.Gas.RegularGas
|
forward := scope.Contract.Gas.RegularGas
|
||||||
if evm.chainRules.IsEIP150 {
|
if evm.chainRules.IsEIP150 {
|
||||||
|
|
@ -646,7 +651,8 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
stackvalue := size
|
stackvalue := size
|
||||||
|
|
||||||
child := scope.Contract.forwardGas(forward, evm.Config.Tracer, tracing.GasChangeCallContractCreation)
|
child := scope.Contract.forwardGas(forward, evm.Config.Tracer, tracing.GasChangeCallContractCreation)
|
||||||
res, addr, result, creation, suberr := evm.Create(scope.Contract.Address(), input, child, &value)
|
res, addr, result, suberr := evm.create(scope.Contract.Address(), input, child, &value, contractAddr, CREATE)
|
||||||
|
|
||||||
// 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
|
||||||
|
|
@ -663,8 +669,11 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
// Refund the leftover gas back to current frame
|
// Refund the leftover gas back to current frame
|
||||||
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||||
|
|
||||||
// Refund the state gas of account-creation if creation doesn't happen
|
// Refill the account-creation charge if the create frame failed (reverted,
|
||||||
if evm.GetRules().IsAmsterdam && !creation {
|
// halted exceptionally, or collided); a successful creation consumes it.
|
||||||
|
// This rule is only applied since the Amsterdam, therefore all non-nil vm
|
||||||
|
// error can be interpreted as deployment failure.
|
||||||
|
if creationCharged && suberr != nil {
|
||||||
scope.Contract.refundState(params.AccountCreationSize*evm.Context.CostPerStateByte, evm.Config.Tracer, tracing.GasChangeRefundAccountCreation)
|
scope.Contract.refundState(params.AccountCreationSize*evm.Context.CostPerStateByte, evm.Config.Tracer, tracing.GasChangeRefundAccountCreation)
|
||||||
}
|
}
|
||||||
if suberr == ErrExecutionReverted {
|
if suberr == ErrExecutionReverted {
|
||||||
|
|
@ -681,7 +690,13 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
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())
|
||||||
|
inithash = crypto.Keccak256Hash(input)
|
||||||
|
contractAddr = crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), inithash[:])
|
||||||
)
|
)
|
||||||
|
creationCharged, halt, err := evm.chargeAccountCreation(scope, contractAddr, &endowment)
|
||||||
|
if halt {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
// Apply EIP-150 to the regular gas left after the state charge.
|
// Apply EIP-150 to the regular gas left after the state charge.
|
||||||
forward := scope.Contract.Gas.RegularGas
|
forward := scope.Contract.Gas.RegularGas
|
||||||
forward -= forward / 64
|
forward -= forward / 64
|
||||||
|
|
@ -689,7 +704,7 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
// reuse size int for stackvalue
|
// reuse size int for stackvalue
|
||||||
stackvalue := size
|
stackvalue := size
|
||||||
child := scope.Contract.forwardGas(forward, evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
|
child := scope.Contract.forwardGas(forward, evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
|
||||||
res, addr, result, creation, suberr := evm.Create2(scope.Contract.Address(), input, child, &endowment, &salt)
|
res, addr, result, suberr := evm.create(scope.Contract.Address(), input, child, &endowment, contractAddr, CREATE2)
|
||||||
// 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()
|
||||||
|
|
@ -701,8 +716,11 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||||
// Refund the leftover gas back to current frame
|
// Refund the leftover gas back to current frame
|
||||||
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
scope.Contract.refundGas(result, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||||
|
|
||||||
// Refund the state gas of account-creation if creation doesn't happen
|
// Refill the account-creation charge if the create frame failed (reverted,
|
||||||
if evm.GetRules().IsAmsterdam && !creation {
|
// halted exceptionally, or collided); a successful creation consumes it.
|
||||||
|
// This rule is only applied since the Amsterdam, therefore all non-nil vm
|
||||||
|
// error can be interpreted as deployment failure.
|
||||||
|
if creationCharged && suberr != nil {
|
||||||
scope.Contract.refundState(params.AccountCreationSize*evm.Context.CostPerStateByte, evm.Config.Tracer, tracing.GasChangeRefundAccountCreation)
|
scope.Contract.refundState(params.AccountCreationSize*evm.Context.CostPerStateByte, evm.Config.Tracer, tracing.GasChangeRefundAccountCreation)
|
||||||
}
|
}
|
||||||
if suberr == ErrExecutionReverted {
|
if suberr == ErrExecutionReverted {
|
||||||
|
|
|
||||||
|
|
@ -77,9 +77,11 @@ type StateDB interface {
|
||||||
|
|
||||||
AddressInAccessList(addr common.Address) bool
|
AddressInAccessList(addr common.Address) bool
|
||||||
SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool)
|
SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool)
|
||||||
|
|
||||||
// AddAddressToAccessList adds the given address to the access list. This operation is safe to perform
|
// AddAddressToAccessList adds the given address to the access list. This operation is safe to perform
|
||||||
// even if the feature/fork is not active yet
|
// even if the feature/fork is not active yet
|
||||||
AddAddressToAccessList(addr common.Address)
|
AddAddressToAccessList(addr common.Address)
|
||||||
|
|
||||||
// AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform
|
// AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform
|
||||||
// even if the feature/fork is not active yet
|
// even if the feature/fork is not active yet
|
||||||
AddSlotToAccessList(addr common.Address, slot common.Hash)
|
AddSlotToAccessList(addr common.Address, slot common.Hash)
|
||||||
|
|
|
||||||
|
|
@ -192,7 +192,7 @@ 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.chargeRegularOnly(cost) {
|
if !contract.Gas.ChargeRegularOnly(cost) {
|
||||||
return nil, ErrOutOfGas
|
return nil, ErrOutOfGas
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -223,7 +223,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
|
||||||
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
|
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
|
||||||
}
|
}
|
||||||
if dynamicCost.StateGas == 0 {
|
if dynamicCost.StateGas == 0 {
|
||||||
if !contract.Gas.chargeRegularOnly(dynamicCost.RegularGas) {
|
if !contract.Gas.ChargeRegularOnly(dynamicCost.RegularGas) {
|
||||||
return nil, ErrOutOfGas
|
return nil, ErrOutOfGas
|
||||||
}
|
}
|
||||||
} else if !contract.Gas.charge(dynamicCost) {
|
} else if !contract.Gas.charge(dynamicCost) {
|
||||||
|
|
|
||||||
|
|
@ -493,7 +493,7 @@ func makeCallVariantGasCallEIP8037(regularFunc regularGasFunc, stateGasFunc stat
|
||||||
// EIP-7702 delegation check.
|
// EIP-7702 delegation check.
|
||||||
if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok {
|
if target, ok := types.ParseDelegation(evm.StateDB.GetCode(addr)); ok {
|
||||||
if evm.StateDB.AddressInAccessList(target) {
|
if evm.StateDB.AddressInAccessList(target) {
|
||||||
eip7702Cost = params.WarmStorageReadCostEIP2929
|
eip7702Cost = params.WarmAccountAccessAmsterdam
|
||||||
} else {
|
} else {
|
||||||
evm.StateDB.AddAddressToAccessList(target)
|
evm.StateDB.AddAddressToAccessList(target)
|
||||||
eip7702Cost = coldCost
|
eip7702Cost = coldCost
|
||||||
|
|
|
||||||
|
|
@ -187,7 +187,7 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
|
||||||
limit = min(cfg.GasLimit, params.MaxTxGas)
|
limit = min(cfg.GasLimit, params.MaxTxGas)
|
||||||
}
|
}
|
||||||
// Call the code with the given configuration.
|
// Call the code with the given configuration.
|
||||||
code, address, result, _, err := vmenv.Create(
|
code, address, result, err := vmenv.Create(
|
||||||
cfg.Origin,
|
cfg.Origin,
|
||||||
input,
|
input,
|
||||||
vm.NewGasBudget(limit, cfg.GasLimit-limit),
|
vm.NewGasBudget(limit, cfg.GasLimit-limit),
|
||||||
|
|
|
||||||
|
|
@ -31,8 +31,6 @@ const (
|
||||||
MaxTxGas uint64 = 1 << 24 // Maximum transaction gas limit after eip-7825 (16,777,216).
|
MaxTxGas uint64 = 1 << 24 // Maximum transaction gas limit after eip-7825 (16,777,216).
|
||||||
|
|
||||||
MaximumExtraDataSize uint64 = 32 // Maximum size extra data may be after Genesis.
|
MaximumExtraDataSize uint64 = 32 // Maximum size extra data may be after Genesis.
|
||||||
ExpByteGas uint64 = 10 // Times ceil(log256(exponent)) for the EXP instruction.
|
|
||||||
SloadGas uint64 = 50 //
|
|
||||||
CallValueTransferGas uint64 = 9000 // Paid for CALL when the value transfer is non-zero.
|
CallValueTransferGas uint64 = 9000 // Paid for CALL when the value transfer is non-zero.
|
||||||
CallNewAccountGas uint64 = 25000 // Paid for CALL when the destination address didn't exist prior.
|
CallNewAccountGas uint64 = 25000 // Paid for CALL when the destination address didn't exist prior.
|
||||||
TxGas uint64 = 21000 // Per transaction not creating a contract. NOTE: Not payable on data of calls between transactions.
|
TxGas uint64 = 21000 // Per transaction not creating a contract. NOTE: Not payable on data of calls between transactions.
|
||||||
|
|
@ -75,8 +73,7 @@ const (
|
||||||
// Which becomes: 5000 - 2100 + 1900 = 4800
|
// Which becomes: 5000 - 2100 + 1900 = 4800
|
||||||
SstoreClearsScheduleRefundEIP3529 uint64 = SstoreResetGasEIP2200 - ColdSloadCostEIP2929 + TxAccessListStorageKeyGas
|
SstoreClearsScheduleRefundEIP3529 uint64 = SstoreResetGasEIP2200 - ColdSloadCostEIP2929 + TxAccessListStorageKeyGas
|
||||||
|
|
||||||
JumpdestGas uint64 = 1 // Once per JUMPDEST operation.
|
JumpdestGas uint64 = 1 // Once per JUMPDEST operation.
|
||||||
EpochDuration uint64 = 30000 // Duration between proof-of-work epochs.
|
|
||||||
|
|
||||||
CreateDataGas uint64 = 200 //
|
CreateDataGas uint64 = 200 //
|
||||||
CallCreateDepth uint64 = 1024 // Maximum depth of call/create stack.
|
CallCreateDepth uint64 = 1024 // Maximum depth of call/create stack.
|
||||||
|
|
@ -84,7 +81,6 @@ const (
|
||||||
LogGas uint64 = 375 // Per LOG* operation.
|
LogGas uint64 = 375 // Per LOG* operation.
|
||||||
CopyGas uint64 = 3 // Multiplied by the number of 32-byte words that are copied (round up) for any *COPY operation and added.
|
CopyGas uint64 = 3 // Multiplied by the number of 32-byte words that are copied (round up) for any *COPY operation and added.
|
||||||
StackLimit uint64 = 1024 // Maximum size of VM stack allowed.
|
StackLimit uint64 = 1024 // Maximum size of VM stack allowed.
|
||||||
TierStepGas uint64 = 0 // Once per operation, for a selection of them.
|
|
||||||
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
|
||||||
|
|
@ -101,20 +97,27 @@ const (
|
||||||
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
|
||||||
|
|
||||||
RegularPerAuthBaseCost uint64 = 7816 // As defined by EIP-8037 and EIP-8038
|
// RegularPerAuthBaseCost is the state-independent per-authorization floor,
|
||||||
|
// defined in EIP-8037 as the sum of:
|
||||||
|
//
|
||||||
|
// - Calldata cost for the authorization tuple
|
||||||
|
// - ECDSA recovery of the authority address
|
||||||
|
// - Cold authority access (COLD_ACCOUNT_ACCESS)
|
||||||
|
// - Warm writes to the authority account
|
||||||
|
RegularPerAuthBaseCost uint64 = 7816
|
||||||
|
|
||||||
// EIP-2780: resource-based intrinsic transaction gas.
|
// EIP-2780: resource-based intrinsic transaction gas.
|
||||||
TxBaseCost2780 uint64 = 12000
|
TxBaseCost2780 uint64 = 12000
|
||||||
ColdAccountAccess2780 uint64 = 3000
|
TxValueCost2780 uint64 = 4244
|
||||||
CreateAccess2780 uint64 = 11000
|
TransferLogCost2780 uint64 = 1756
|
||||||
TxValueCost2780 uint64 = 4244
|
|
||||||
TransferLogCost2780 uint64 = 1756
|
|
||||||
|
|
||||||
// EIP-8038: state-access gas cost update (Amsterdam).
|
// EIP-8038: state-access gas cost update (Amsterdam).
|
||||||
ColdAccountAccessAmsterdam uint64 = 3000 // COLD_ACCOUNT_ACCESS: cold touch of an account
|
ColdAccountAccessAmsterdam uint64 = 3000 // COLD_ACCOUNT_ACCESS: cold touch of an account
|
||||||
|
WarmAccountAccessAmsterdam uint64 = 100 // WARM_ACCESS: warm touch of an account
|
||||||
AccountWriteAmsterdam uint64 = 8000 // ACCOUNT_WRITE: surcharge for first-time write to an account
|
AccountWriteAmsterdam uint64 = 8000 // ACCOUNT_WRITE: surcharge for first-time write to an account
|
||||||
CallValueTransferAmsterdam uint64 = 10300 // CALL_VALUE = ACCOUNT_WRITE + CallStipend (2300)
|
CallValueTransferAmsterdam uint64 = 10300 // CALL_VALUE = ACCOUNT_WRITE + CallStipend (2300)
|
||||||
ColdStorageAccessAmsterdam uint64 = 3000 // COLD_STORAGE_ACCESS: cold touch of a storage slot
|
ColdStorageAccessAmsterdam uint64 = 3000 // COLD_STORAGE_ACCESS: cold touch of a storage slot
|
||||||
|
WarmStorageAccessAmsterdam uint64 = 100 // WARM_STORAGE_ACCESS: warm touch of a storage slot
|
||||||
StorageWriteAmsterdam uint64 = 10000 // STORAGE_WRITE: surcharge for first-time write to a storage slot
|
StorageWriteAmsterdam uint64 = 10000 // STORAGE_WRITE: surcharge for first-time write to a storage slot
|
||||||
StorageClearRefundAmsterdam uint64 = 12480 // STORAGE_CLEAR_REFUND: refund for clearing a storage slot
|
StorageClearRefundAmsterdam uint64 = 12480 // STORAGE_CLEAR_REFUND: refund for clearing a storage slot
|
||||||
CreateAccessAmsterdam uint64 = 11000 // CREATE_ACCESS = ACCOUNT_WRITE + COLD_STORAGE_ACCESS
|
CreateAccessAmsterdam uint64 = 11000 // CREATE_ACCESS = ACCOUNT_WRITE + COLD_STORAGE_ACCESS
|
||||||
|
|
|
||||||
|
|
@ -94,7 +94,6 @@ func TestExecutionSpecBlocktests(t *testing.T) {
|
||||||
// Broken tests
|
// Broken tests
|
||||||
bt.skipLoad(`.*eip7610_create_collision/initcollision/.*`)
|
bt.skipLoad(`.*eip7610_create_collision/initcollision/.*`)
|
||||||
bt.skipLoad(`.*eip7610_create_collision/revert_in_create/.*`)
|
bt.skipLoad(`.*eip7610_create_collision/revert_in_create/.*`)
|
||||||
bt.skipLoad(`.*stRandom2/random_statetest642/.*`)
|
|
||||||
|
|
||||||
bt.walk(t, executionSpecBlockchainTestDir, func(t *testing.T, name string, test *BlockTest) {
|
bt.walk(t, executionSpecBlockchainTestDir, func(t *testing.T, name string, test *BlockTest) {
|
||||||
execBlockTest(t, bt, test)
|
execBlockTest(t, bt, test)
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ func (s stTransaction) MarshalJSON() ([]byte, error) {
|
||||||
GasPrice *math.HexOrDecimal256 `json:"gasPrice"`
|
GasPrice *math.HexOrDecimal256 `json:"gasPrice"`
|
||||||
MaxFeePerGas *math.HexOrDecimal256 `json:"maxFeePerGas"`
|
MaxFeePerGas *math.HexOrDecimal256 `json:"maxFeePerGas"`
|
||||||
MaxPriorityFeePerGas *math.HexOrDecimal256 `json:"maxPriorityFeePerGas"`
|
MaxPriorityFeePerGas *math.HexOrDecimal256 `json:"maxPriorityFeePerGas"`
|
||||||
Nonce math.HexOrDecimal64 `json:"nonce"`
|
Nonce *math.HexOrDecimal256 `json:"nonce"`
|
||||||
To string `json:"to"`
|
To string `json:"to"`
|
||||||
Data []string `json:"data"`
|
Data []string `json:"data"`
|
||||||
AccessLists []*types.AccessList `json:"accessLists,omitempty"`
|
AccessLists []*types.AccessList `json:"accessLists,omitempty"`
|
||||||
|
|
@ -36,7 +36,7 @@ func (s stTransaction) MarshalJSON() ([]byte, error) {
|
||||||
enc.GasPrice = (*math.HexOrDecimal256)(s.GasPrice)
|
enc.GasPrice = (*math.HexOrDecimal256)(s.GasPrice)
|
||||||
enc.MaxFeePerGas = (*math.HexOrDecimal256)(s.MaxFeePerGas)
|
enc.MaxFeePerGas = (*math.HexOrDecimal256)(s.MaxFeePerGas)
|
||||||
enc.MaxPriorityFeePerGas = (*math.HexOrDecimal256)(s.MaxPriorityFeePerGas)
|
enc.MaxPriorityFeePerGas = (*math.HexOrDecimal256)(s.MaxPriorityFeePerGas)
|
||||||
enc.Nonce = math.HexOrDecimal64(s.Nonce)
|
enc.Nonce = (*math.HexOrDecimal256)(s.Nonce)
|
||||||
enc.To = s.To
|
enc.To = s.To
|
||||||
enc.Data = s.Data
|
enc.Data = s.Data
|
||||||
enc.AccessLists = s.AccessLists
|
enc.AccessLists = s.AccessLists
|
||||||
|
|
@ -61,7 +61,7 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error {
|
||||||
GasPrice *math.HexOrDecimal256 `json:"gasPrice"`
|
GasPrice *math.HexOrDecimal256 `json:"gasPrice"`
|
||||||
MaxFeePerGas *math.HexOrDecimal256 `json:"maxFeePerGas"`
|
MaxFeePerGas *math.HexOrDecimal256 `json:"maxFeePerGas"`
|
||||||
MaxPriorityFeePerGas *math.HexOrDecimal256 `json:"maxPriorityFeePerGas"`
|
MaxPriorityFeePerGas *math.HexOrDecimal256 `json:"maxPriorityFeePerGas"`
|
||||||
Nonce *math.HexOrDecimal64 `json:"nonce"`
|
Nonce *math.HexOrDecimal256 `json:"nonce"`
|
||||||
To *string `json:"to"`
|
To *string `json:"to"`
|
||||||
Data []string `json:"data"`
|
Data []string `json:"data"`
|
||||||
AccessLists []*types.AccessList `json:"accessLists,omitempty"`
|
AccessLists []*types.AccessList `json:"accessLists,omitempty"`
|
||||||
|
|
@ -87,7 +87,7 @@ func (s *stTransaction) UnmarshalJSON(input []byte) error {
|
||||||
s.MaxPriorityFeePerGas = (*big.Int)(dec.MaxPriorityFeePerGas)
|
s.MaxPriorityFeePerGas = (*big.Int)(dec.MaxPriorityFeePerGas)
|
||||||
}
|
}
|
||||||
if dec.Nonce != nil {
|
if dec.Nonce != nil {
|
||||||
s.Nonce = uint64(*dec.Nonce)
|
s.Nonce = (*big.Int)(dec.Nonce)
|
||||||
}
|
}
|
||||||
if dec.To != nil {
|
if dec.To != nil {
|
||||||
s.To = *dec.To
|
s.To = *dec.To
|
||||||
|
|
|
||||||
|
|
@ -116,7 +116,7 @@ type stTransaction struct {
|
||||||
GasPrice *big.Int `json:"gasPrice"`
|
GasPrice *big.Int `json:"gasPrice"`
|
||||||
MaxFeePerGas *big.Int `json:"maxFeePerGas"`
|
MaxFeePerGas *big.Int `json:"maxFeePerGas"`
|
||||||
MaxPriorityFeePerGas *big.Int `json:"maxPriorityFeePerGas"`
|
MaxPriorityFeePerGas *big.Int `json:"maxPriorityFeePerGas"`
|
||||||
Nonce uint64 `json:"nonce"`
|
Nonce *big.Int `json:"nonce"`
|
||||||
To string `json:"to"`
|
To string `json:"to"`
|
||||||
Data []string `json:"data"`
|
Data []string `json:"data"`
|
||||||
AccessLists []*types.AccessList `json:"accessLists,omitempty"`
|
AccessLists []*types.AccessList `json:"accessLists,omitempty"`
|
||||||
|
|
@ -133,7 +133,7 @@ type stTransactionMarshaling struct {
|
||||||
GasPrice *math.HexOrDecimal256
|
GasPrice *math.HexOrDecimal256
|
||||||
MaxFeePerGas *math.HexOrDecimal256
|
MaxFeePerGas *math.HexOrDecimal256
|
||||||
MaxPriorityFeePerGas *math.HexOrDecimal256
|
MaxPriorityFeePerGas *math.HexOrDecimal256
|
||||||
Nonce math.HexOrDecimal64
|
Nonce *math.HexOrDecimal256
|
||||||
GasLimit []math.HexOrDecimal64
|
GasLimit []math.HexOrDecimal64
|
||||||
PrivateKey hexutil.Bytes
|
PrivateKey hexutil.Bytes
|
||||||
BlobGasFeeCap *math.HexOrDecimal256
|
BlobGasFeeCap *math.HexOrDecimal256
|
||||||
|
|
@ -392,6 +392,16 @@ func (t *StateTest) genesis(config *params.ChainConfig) *core.Genesis {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Message, error) {
|
func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Message, error) {
|
||||||
|
// The nonce is parsed as an arbitrary-precision integer so that fixtures
|
||||||
|
// probing the EIP-2681 limit can be loaded; such a transaction can never
|
||||||
|
// be RLP-decoded and must be rejected here.
|
||||||
|
var nonce uint64
|
||||||
|
if tx.Nonce != nil {
|
||||||
|
if !tx.Nonce.IsUint64() {
|
||||||
|
return nil, fmt.Errorf("nonce %v exceeds 2^64-1 (EIP-2681)", tx.Nonce)
|
||||||
|
}
|
||||||
|
nonce = tx.Nonce.Uint64()
|
||||||
|
}
|
||||||
var from common.Address
|
var from common.Address
|
||||||
// If 'sender' field is present, use that
|
// If 'sender' field is present, use that
|
||||||
if tx.Sender != nil {
|
if tx.Sender != nil {
|
||||||
|
|
@ -481,7 +491,7 @@ func (tx *stTransaction) toMessage(ps stPostState, baseFee *big.Int) (*core.Mess
|
||||||
msg := &core.Message{
|
msg := &core.Message{
|
||||||
From: from,
|
From: from,
|
||||||
To: to,
|
To: to,
|
||||||
Nonce: tx.Nonce,
|
Nonce: nonce,
|
||||||
Value: uint256.MustFromBig(value),
|
Value: uint256.MustFromBig(value),
|
||||||
GasLimit: gasLimit,
|
GasLimit: gasLimit,
|
||||||
GasPrice: uint256.MustFromBig(gasPrice),
|
GasPrice: uint256.MustFromBig(gasPrice),
|
||||||
|
|
|
||||||
|
|
@ -86,11 +86,11 @@ func (tt *TransactionTest) Run() error {
|
||||||
if overflow {
|
if overflow {
|
||||||
return sender, hash, 0, errors.New("value exceeds 256 bits")
|
return sender, hash, 0, errors.New("value exceeds 256 bits")
|
||||||
}
|
}
|
||||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), sender, tx.To(), value, rules, params.CostPerStateByte)
|
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), sender, tx.To(), value, rules)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
requiredGas = cost.RegularGas
|
requiredGas = cost
|
||||||
if requiredGas > tx.Gas() {
|
if requiredGas > tx.Gas() {
|
||||||
return sender, hash, 0, fmt.Errorf("insufficient gas ( %d < %d )", tx.Gas(), requiredGas)
|
return sender, hash, 0, fmt.Errorf("insufficient gas ( %d < %d )", tx.Gas(), requiredGas)
|
||||||
}
|
}
|
||||||
|
|
@ -125,6 +125,8 @@ func (tt *TransactionTest) Run() error {
|
||||||
{"Shanghai", true},
|
{"Shanghai", true},
|
||||||
{"Cancun", true},
|
{"Cancun", true},
|
||||||
{"Prague", true},
|
{"Prague", true},
|
||||||
|
{"Osaka", true},
|
||||||
|
{"Amsterdam", true},
|
||||||
} {
|
} {
|
||||||
expected := tt.Result[testcase.name]
|
expected := tt.Result[testcase.name]
|
||||||
if expected == nil {
|
if expected == nil {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue