mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-19 11:20:45 +00:00
core: re-implement eip-2780
This commit is contained in:
parent
e7314c8a13
commit
e8d192ad6b
13 changed files with 864 additions and 268 deletions
|
|
@ -37,6 +37,7 @@ func TestEIP2780Intrinsic(t *testing.T) {
|
||||||
name string
|
name string
|
||||||
to *common.Address
|
to *common.Address
|
||||||
value *uint256.Int
|
value *uint256.Int
|
||||||
|
auths []types.SetCodeAuthorization
|
||||||
want vm.GasCosts
|
want vm.GasCosts
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
|
|
@ -55,41 +56,52 @@ func TestEIP2780Intrinsic(t *testing.T) {
|
||||||
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: vm.GasCosts{RegularGas: 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: vm.GasCosts{RegularGas: 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
|
||||||
|
// charge depends on whether the deployment target exists and is
|
||||||
|
// charged at runtime, not intrinsically.
|
||||||
want: vm.GasCosts{
|
want: vm.GasCosts{
|
||||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780,
|
RegularGas: params.TxBaseCost2780 + params.CreateAccessAmsterdam,
|
||||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
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: vm.GasCosts{
|
||||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 + params.TransferLogCost2780,
|
RegularGas: params.TxBaseCost2780 + params.CreateAccessAmsterdam + 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: vm.GasCosts{RegularGas: 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, params.CostPerStateByte)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -105,7 +117,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 +166,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) {
|
||||||
|
|
@ -190,6 +202,180 @@ func TestEIP2780NewAccountFunded(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// callTxAL builds a signed dynamic-fee call carrying an access list.
|
||||||
|
func callTxAL(nonce uint64, to common.Address, value int64, gas uint64, al types.AccessList) *types.Transaction {
|
||||||
|
return types.MustSignNewTx(senderKey, signer8037, &types.DynamicFeeTx{
|
||||||
|
ChainID: cfg8037.ChainID, Nonce: nonce, To: &to, Value: big.NewInt(value),
|
||||||
|
Gas: gas, GasFeeCap: big.NewInt(0), GasTipCap: big.NewInt(0), AccessList: al,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 touch at the
|
||||||
|
// cold rate: per EIP-2780 that touch is priced unconditionally at the intrinsic
|
||||||
|
// phase, so an access-list entry does not discount it. The total is the
|
||||||
|
// intrinsic cold recipient charge plus the access-list entry itself, with no
|
||||||
|
// separate runtime charge.
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if res.Err != nil {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEIP2780RecipientColdInIntrinsic exercises the validity boundary created
|
||||||
|
// by charging the recipient touch at the cold rate unconditionally in the
|
||||||
|
// intrinsic phase: a zero-value call funded one gas below the cold-inclusive
|
||||||
|
// intrinsic (TX_BASE_COST + COLD_ACCOUNT_ACCESS) is rejected as intrinsic-gas
|
||||||
|
// too low, while funding exactly that amount is valid and included with no
|
||||||
|
// further runtime charge.
|
||||||
|
func TestEIP2780RecipientColdInIntrinsic(t *testing.T) {
|
||||||
|
to := common.HexToAddress("0xe0a000000000000000000000000000000000000a")
|
||||||
|
intrinsic := params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam // 15,000
|
||||||
|
|
||||||
|
// One gas short of the intrinsic cost: the transaction is invalid.
|
||||||
|
sdb := mkState(senderAlloc(types.GenesisAlloc{to: {Balance: big.NewInt(1)}}))
|
||||||
|
if _, _, err := applyMsg(t, sdb, callTx(0, to, 0, intrinsic-1, nil)); err == nil {
|
||||||
|
t.Fatal("expected intrinsic-gas-too-low error, got nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Funded for exactly the intrinsic cost: valid, included, and fully
|
||||||
|
// consumed with no runtime cold surcharge to halt on.
|
||||||
|
sdb = mkState(senderAlloc(types.GenesisAlloc{to: {Balance: big.NewInt(1)}}))
|
||||||
|
res, _, err := applyMsg(t, sdb, callTx(0, to, 0, intrinsic, nil))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("transaction should be valid: %v", err)
|
||||||
|
}
|
||||||
|
if res.Err != nil {
|
||||||
|
t.Fatalf("unexpected execution error: %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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEIP2780RuntimeOOGRevertsDelegations verifies that running out of gas on
|
||||||
|
// a runtime authorization charge halts the (still valid) transaction and
|
||||||
|
// reverts all state changes, including the already applied EIP-7702
|
||||||
|
// delegations — while the sender's nonce increment persists.
|
||||||
|
func TestEIP2780RuntimeOOGRevertsDelegations(t *testing.T) {
|
||||||
|
auth, authority := signAuth(t, authKeyA, delegate8037, 0)
|
||||||
|
sdb := mkState(senderAlloc(nil))
|
||||||
|
// 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).
|
||||||
|
tx := types.MustSignNewTx(senderKey, signer8037, &types.SetCodeTx{
|
||||||
|
ChainID: uint256.MustFromBig(cfg8037.ChainID), Nonce: 0, To: senderAddr,
|
||||||
|
Value: new(uint256.Int), Gas: 30_000, GasFeeCap: new(uint256.Int),
|
||||||
|
GasTipCap: new(uint256.Int), AuthList: []types.SetCodeAuthorization{auth},
|
||||||
|
})
|
||||||
|
res, _, 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 != 30_000 {
|
||||||
|
t.Fatalf("used gas = %d, want all 30000 burnt", res.UsedGas)
|
||||||
|
}
|
||||||
|
if code := sdb.GetCode(authority); len(code) != 0 {
|
||||||
|
t.Fatalf("delegation persisted despite runtime OOG: %x", code)
|
||||||
|
}
|
||||||
|
if sdb.GetNonce(authority) != 0 {
|
||||||
|
t.Fatal("authority nonce persisted despite runtime OOG")
|
||||||
|
}
|
||||||
|
if sdb.GetNonce(senderAddr) != 1 {
|
||||||
|
t.Fatal("sender nonce not consumed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestEIP2780SelfTransferDelegated verifies that a self-transfer incurs no
|
||||||
|
// recipient touch or value charges (the account is warm and existent as the
|
||||||
|
// sender), 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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestEIP2780InsufficientGasForCallCharge verifies that a value transfer
|
// TestEIP2780InsufficientGasForCallCharge verifies that a value transfer
|
||||||
// creating a new account, whose gas limit only covers the 21,000 intrinsic base
|
// creating a new account, whose gas limit only covers the 21,000 intrinsic base
|
||||||
// and not the additional new-account state gas charged before the call executes,
|
// and not the additional new-account state gas charged before the call executes,
|
||||||
|
|
@ -212,3 +398,243 @@ func TestEIP2780InsufficientGasForCallCharge(t *testing.T) {
|
||||||
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")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestEIP2780HaltKeepsAuthStateGas verifies that when the top-most frame halts
|
||||||
|
// exceptionally, the EIP-7702 delegations applied before the frame was persist.
|
||||||
|
func TestEIP2780HaltKeepsAuthStateGas(t *testing.T) {
|
||||||
|
halting := common.HexToAddress("0xbad0000000000000000000000000000000000001")
|
||||||
|
sdb := mkState(senderAlloc(types.GenesisAlloc{
|
||||||
|
halting: {Code: []byte{0xfe}}, // INVALID
|
||||||
|
}))
|
||||||
|
auth, authority := signAuth(t, authKeyA, delegate8037, 0)
|
||||||
|
|
||||||
|
// A gas limit above MaxTxGas so the state reservoir covers the runtime
|
||||||
|
// authorization state charges (218,790) without spilling into regular gas.
|
||||||
|
gasLimit := params.MaxTxGas + 300_000
|
||||||
|
tx := types.MustSignNewTx(senderKey, signer8037, &types.SetCodeTx{
|
||||||
|
ChainID: uint256.MustFromBig(cfg8037.ChainID), Nonce: 0, To: halting,
|
||||||
|
Value: new(uint256.Int), Gas: gasLimit, 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 code := sdb.GetCode(authority); len(code) == 0 {
|
||||||
|
t.Fatal("delegation should persist through an in-frame halt")
|
||||||
|
}
|
||||||
|
// The regular dimension is burned in full by the halt; the state dimension
|
||||||
|
// keeps the delegation's durable growth: a new account leaf plus the
|
||||||
|
// 23-byte indicator.
|
||||||
|
if gp.cumulativeRegular != params.MaxTxGas {
|
||||||
|
t.Errorf("regular gas = %d, want %d", gp.cumulativeRegular, params.MaxTxGas)
|
||||||
|
}
|
||||||
|
if gp.cumulativeState != authWorstState {
|
||||||
|
t.Errorf("state gas = %d, want %d (delegation state growth persisted)", gp.cumulativeState, authWorstState)
|
||||||
|
}
|
||||||
|
if want := params.MaxTxGas + authWorstState; res.UsedGas != want {
|
||||||
|
t.Errorf("used gas = %d, want %d", res.UsedGas, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -189,19 +189,24 @@ var (
|
||||||
|
|
||||||
// ===================== 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
|
||||||
|
// runtime (EIP-2780), not intrinsically.
|
||||||
|
func TestCreateTxIntrinsicNoStateGas(t *testing.T) {
|
||||||
cost, err := IntrinsicGas(nil, nil, nil, common.Address{}, nil, nil, rules8037, params.CostPerStateByte)
|
cost, err := IntrinsicGas(nil, nil, nil, common.Address{}, nil, nil, rules8037, params.CostPerStateByte)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if cost.StateGas != newAccountState {
|
if cost.StateGas != 0 {
|
||||||
t.Fatalf("intrinsic state gas = %d, want %d", cost.StateGas, newAccountState)
|
t.Fatalf("intrinsic state gas = %d, want 0", cost.StateGas)
|
||||||
|
}
|
||||||
|
if want := params.TxBaseCost2780 + params.CreateAccessAmsterdam; cost.RegularGas != want {
|
||||||
|
t.Fatalf("intrinsic regular gas = %d, want %d", cost.RegularGas, 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 +219,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 +235,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)
|
||||||
|
|
@ -242,12 +249,11 @@ func TestCreateTxCollisionConsumesGasLeft(t *testing.T) {
|
||||||
t.Fatal("expected collision failure")
|
t.Fatal("expected collision failure")
|
||||||
}
|
}
|
||||||
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 (never charged)", gp.cumulativeState)
|
||||||
}
|
}
|
||||||
// All forwarded gas_left is burned; only the refilled account charge (which
|
// All forwarded gas_left is burned: the whole gas limit is consumed as
|
||||||
// had spilled into regular) returns to gas_left. So regular gas consumed is
|
// regular gas.
|
||||||
// exactly tx.gas - newAccountState, with no other refund.
|
if want := uint64(gas); gp.cumulativeRegular != want {
|
||||||
if want := uint64(gas) - newAccountState; gp.cumulativeRegular != want {
|
|
||||||
t.Fatalf("regular gas = %d, want %d", gp.cumulativeRegular, want)
|
t.Fatalf("regular gas = %d, want %d", gp.cumulativeRegular, want)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -472,18 +478,26 @@ 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).
|
||||||
|
func TestAuthIntrinsicBaseOnly(t *testing.T) {
|
||||||
cost, err := IntrinsicGas(nil, nil, []types.SetCodeAuthorization{{}}, common.Address{}, &delegate8037, nil, rules8037, params.CostPerStateByte)
|
cost, err := IntrinsicGas(nil, nil, []types.SetCodeAuthorization{{}}, common.Address{}, &delegate8037, nil, rules8037, params.CostPerStateByte)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if cost.StateGas != authWorstState {
|
if cost.StateGas != 0 {
|
||||||
t.Fatalf("intrinsic state gas = %d, want %d", cost.StateGas, authWorstState)
|
t.Fatalf("intrinsic state gas = %d, want 0", cost.StateGas)
|
||||||
|
}
|
||||||
|
// The recipient touch and the per-authorization authority access (priced
|
||||||
|
// 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.RegularGas != want {
|
||||||
|
t.Fatalf("intrinsic regular gas = %d, want %d", cost.RegularGas, 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 +513,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 +523,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 +538,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 +552,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,12 +566,12 @@ 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 created by an earlier auth and cleared by a
|
||||||
// later one writes zero net bytes, so both indicator charges refill.
|
// later one writes zero net bytes; the earlier indicator charge is refilled.
|
||||||
func TestAuthClearSameTxDoubleRefill(t *testing.T) {
|
func TestAuthClearSameTxDoubleRefill(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)
|
||||||
|
|
|
||||||
|
|
@ -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,43 +29,71 @@ 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]bool{}); 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]bool{}); 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]bool{}); 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
|
||||||
|
|
@ -82,15 +102,16 @@ func TestAuthAccountWriteInvalidRefund(t *testing.T) {
|
||||||
if err := st.applyAuthorization(rules8037, &bad, map[common.Address]bool{}); err == nil {
|
if err := st.applyAuthorization(rules8037, &bad, map[common.Address]bool{}); 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)))
|
||||||
|
|
@ -98,13 +119,27 @@ func TestAuthAccountWriteDuplicateOnce(t *testing.T) {
|
||||||
if err := st.applyAuthorization(rules8037, &a0, delegates); err != nil {
|
if err := st.applyAuthorization(rules8037, &a0, delegates); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
if got := st.state.GetRefund(); got != 0 {
|
|
||||||
t.Fatalf("refund after first auth = %d, want 0", got)
|
|
||||||
}
|
|
||||||
if err := st.applyAuthorization(rules8037, &a1, delegates); err != nil {
|
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]bool{}); 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"
|
||||||
|
|
@ -75,10 +76,6 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
||||||
var gas vm.GasCosts
|
var gas vm.GasCosts
|
||||||
if rules.IsAmsterdam {
|
if rules.IsAmsterdam {
|
||||||
gas.RegularGas = intrinsicBaseGasEIP2780(from, to, value)
|
gas.RegularGas = 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.RegularGas = params.TxGasContractCreation
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -87,14 +84,13 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
||||||
// 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.RegularGas += 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.RegularGas += 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}))
|
||||||
|
|
@ -123,6 +119,7 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
||||||
gas.RegularGas += lenWords * params.InitCodeWordGas
|
gas.RegularGas += 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())
|
||||||
|
|
@ -170,17 +167,21 @@ func intrinsicBaseGasEIP2780(from common.Address, to *common.Address, value *uin
|
||||||
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 +243,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 +263,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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -537,10 +539,12 @@ func (st *stateTransition) buyGas() error {
|
||||||
// - 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).
|
||||||
//
|
//
|
||||||
// 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 +561,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 +574,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 +601,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 +611,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 +634,12 @@ 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
|
||||||
|
}
|
||||||
|
}
|
||||||
return st.buyGas()
|
return st.buyGas()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -649,20 +655,20 @@ 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
|
||||||
)
|
)
|
||||||
|
// Validate the message and pre-pay gas.
|
||||||
|
if err := st.preCheck(rules); 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.
|
||||||
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, msg.From, msg.To, msg.Value, rules, st.evm.Context.CostPerStateByte)
|
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, msg.From, msg.To, msg.Value, rules, st.evm.Context.CostPerStateByte)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -720,54 +726,13 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||||
|
|
||||||
// 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 +773,155 @@ 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.Exist(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, creation, 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),
|
||||||
|
// refill the account-creation state gas charged at runtime.
|
||||||
|
if rules.IsAmsterdam && chargedCreation && !creation {
|
||||||
|
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, as the first frame is
|
||||||
|
// entered:
|
||||||
|
//
|
||||||
|
// - the recipient touch was already charged at the cold rate unconditionally
|
||||||
|
// at the intrinsic phase (EIP-2780) and the account is warm from
|
||||||
|
// statedb.Prepare (EIP-2929), so no access charge or warming is due here;
|
||||||
|
//
|
||||||
|
// - if the recipient is EIP-161 non-existent 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, so unlike the
|
||||||
|
// execution-level checks which must use StateDB.Empty because SELFDESTRUCT can
|
||||||
|
// leave a transient EIP-161-empty account, no empty account can exist here, and
|
||||||
|
// !Exist is equivalent to Empty.
|
||||||
|
if !value.IsZero() && !st.state.Exist(to) {
|
||||||
|
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) on frame entry.
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
@ -964,48 +1041,76 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio
|
||||||
return authority, nil
|
return authority, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// applyAuthorization applies an EIP-7702 code delegation to the state and,
|
// applyAuthorization applies an EIP-7702 code delegation to the state.
|
||||||
// adjust the pre-charged intrinsic cost accordingly.
|
|
||||||
func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.SetCodeAuthorization, delegates map[common.Address]bool) error {
|
func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.SetCodeAuthorization, delegates map[common.Address]bool) 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
|
authBase := params.AuthorizationCreationSize * st.evm.Context.CostPerStateByte
|
||||||
|
|
||||||
preDelegated, ok := delegates[authority]
|
preDelegated, seen := delegates[authority]
|
||||||
if !ok {
|
if !seen {
|
||||||
preDelegated = curDelegated
|
preDelegated = curDelegated
|
||||||
delegates[authority] = preDelegated
|
delegates[authority] = preDelegated
|
||||||
}
|
}
|
||||||
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 !seen && authority != st.msg.From && (authority != st.to() || !hasValue) {
|
||||||
|
cost.RegularGas += params.AccountWriteAmsterdam
|
||||||
|
}
|
||||||
|
// Durable state growth of the new account
|
||||||
|
if !st.state.Exist(authority) {
|
||||||
|
cost.StateGas += params.AccountCreationSize * st.evm.Context.CostPerStateByte
|
||||||
|
}
|
||||||
|
// Writing the 23-byte delegation indicator into a previously empty
|
||||||
|
// slot adds net-new state bytes. Overwriting an occupied slot, or one
|
||||||
|
// occupied at transaction start, writes no new bytes.
|
||||||
|
if auth.Address != (common.Address{}) && !curDelegated && !preDelegated {
|
||||||
|
cost.StateGas += authBase
|
||||||
|
}
|
||||||
|
// Clearing an indicator that was created by an earlier authorization
|
||||||
|
// within the same transaction writes zero net bytes; refill the
|
||||||
|
// earlier state charge as it is no longer justified.
|
||||||
|
//
|
||||||
|
// Note that the refund and the charges above can never apply to the
|
||||||
|
// same authorization. The refund requires the indicator to have been
|
||||||
|
// created by a preceding authorization in this transaction, in which
|
||||||
|
// case the authority already exists, has already been written, and
|
||||||
|
// its indicator slot was empty at transaction start, so none of the
|
||||||
|
// charges is due. The ordering of the refund and the charge is
|
||||||
|
// therefore irrelevant.
|
||||||
|
if auth.Address == (common.Address{}) && curDelegated && !preDelegated {
|
||||||
|
st.gasRemaining.RefundState(authBase)
|
||||||
|
}
|
||||||
|
if !st.chargeRuntimeGas(cost) {
|
||||||
|
return ErrOutOfGasRuntime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1020,18 +1125,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
|
||||||
|
// charges.
|
||||||
|
func (st *stateTransition) applyAuthorizations(rules params.Rules, auths []types.SetCodeAuthorization) bool {
|
||||||
preDelegated := make(map[common.Address]bool)
|
preDelegated := make(map[common.Address]bool)
|
||||||
for _, auth := range auths {
|
for _, auth := range auths {
|
||||||
st.applyAuthorization(rules, &auth, preDelegated)
|
if err := st.applyAuthorization(rules, &auth, preDelegated); 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.
|
||||||
|
|
|
||||||
|
|
@ -241,8 +241,10 @@ 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: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam +
|
||||||
2*params.TxAccessListAddressGasAmsterdam + 3*params.TxAccessListStorageKeyGasAmsterdam +
|
2*params.TxAccessListAddressGasAmsterdam + 3*params.TxAccessListStorageKeyGasAmsterdam +
|
||||||
2*amsterdamAddressCost + 3*amsterdamStorageKeyCost},
|
2*amsterdamAddressCost + 3*amsterdamStorageKeyCost},
|
||||||
},
|
},
|
||||||
|
|
@ -263,11 +265,10 @@ 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: vm.GasCosts{
|
||||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780,
|
RegularGas: params.TxBaseCost2780 + params.CreateAccessAmsterdam,
|
||||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -279,9 +280,8 @@ func TestIntrinsicGas(t *testing.T) {
|
||||||
isEIP3860: true, // Shanghai gates init-code word gas
|
isEIP3860: true, // Shanghai gates init-code word gas
|
||||||
isAmsterdam: true,
|
isAmsterdam: true,
|
||||||
want: vm.GasCosts{
|
want: vm.GasCosts{
|
||||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 +
|
RegularGas: params.TxBaseCost2780 + params.CreateAccessAmsterdam +
|
||||||
64*params.TxDataZeroGas + 2*params.InitCodeWordGas,
|
64*params.TxDataZeroGas + 2*params.InitCodeWordGas,
|
||||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -296,11 +296,10 @@ func TestIntrinsicGas(t *testing.T) {
|
||||||
isEIP3860: true,
|
isEIP3860: true,
|
||||||
isAmsterdam: true,
|
isAmsterdam: true,
|
||||||
want: vm.GasCosts{
|
want: vm.GasCosts{
|
||||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 +
|
RegularGas: params.TxBaseCost2780 + params.CreateAccessAmsterdam +
|
||||||
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,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -314,17 +313,16 @@ 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: vm.GasCosts{
|
||||||
RegularGas: params.TxBaseCost2780 + params.ColdAccountAccess2780 +
|
RegularGas: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam +
|
||||||
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.AccountWriteAmsterdam+params.RegularPerAuthBaseCost),
|
1*params.RegularPerAuthBaseCost,
|
||||||
StateGas: 1 * (params.AuthorizationCreationSize + params.AccountCreationSize) * params.CostPerStateByte,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -333,7 +331,7 @@ 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: vm.GasCosts{RegularGas: params.TxBaseCost2780 + params.ColdAccountAccessAmsterdam +
|
||||||
params.TransferLogCost2780 + params.TxValueCost2780},
|
params.TransferLogCost2780 + params.TxValueCost2780},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
@ -343,10 +341,10 @@ 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;
|
||||||
|
// the new-account state charge is applied at runtime.
|
||||||
want: vm.GasCosts{
|
want: vm.GasCosts{
|
||||||
RegularGas: params.TxBaseCost2780 + params.CreateAccess2780 + params.TransferLogCost2780,
|
RegularGas: params.TxBaseCost2780 + params.CreateAccessAmsterdam + params.TransferLogCost2780,
|
||||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,21 +29,22 @@ func _() {
|
||||||
_ = x[GasChangeWitnessContractCollisionCheck-18]
|
_ = x[GasChangeWitnessContractCollisionCheck-18]
|
||||||
_ = x[GasChangeTxDataFloor-19]
|
_ = x[GasChangeTxDataFloor-19]
|
||||||
_ = x[GasChangeRefundAccountCreation-20]
|
_ = x[GasChangeRefundAccountCreation-20]
|
||||||
|
_ = x[GasChangeTxRuntimeGas-21]
|
||||||
_ = x[GasChangeIgnored-255]
|
_ = x[GasChangeIgnored-255]
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_GasChangeReason_name_0 = "UnspecifiedTxInitialBalanceTxIntrinsicGasTxRefundsTxLeftOverReturnedCallInitialBalanceCallLeftOverReturnedCallLeftOverRefundedCallContractCreationCallContractCreation2CallCodeStorageCallOpCodeCallPrecompiledContractCallStorageColdAccessCallFailedExecutionWitnessContractInitWitnessContractCreationWitnessCodeChunkWitnessContractCollisionCheckTxDataFloorRefundAccountCreation"
|
_GasChangeReason_name_0 = "UnspecifiedTxInitialBalanceTxIntrinsicGasTxRefundsTxLeftOverReturnedCallInitialBalanceCallLeftOverReturnedCallLeftOverRefundedCallContractCreationCallContractCreation2CallCodeStorageCallOpCodeCallPrecompiledContractCallStorageColdAccessCallFailedExecutionWitnessContractInitWitnessContractCreationWitnessCodeChunkWitnessContractCollisionCheckTxDataFloorRefundAccountCreationTxRuntimeGas"
|
||||||
_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}
|
||||||
)
|
)
|
||||||
|
|
||||||
func (i GasChangeReason) String() string {
|
func (i GasChangeReason) String() string {
|
||||||
switch {
|
switch {
|
||||||
case i <= 20:
|
case i <= 21:
|
||||||
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,10 @@ 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
|
||||||
|
|
||||||
// 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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -698,22 +698,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
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -196,20 +196,10 @@ func (g *GasBudget) RefundState(s uint64) {
|
||||||
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
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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,29 @@ 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 (per EIP-7904)
|
||||||
|
// - 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. The access primitives
|
||||||
TxBaseCost2780 uint64 = 12000
|
// it references (COLD_ACCOUNT_ACCESS, WARM_ACCESS, CREATE_ACCESS) are the
|
||||||
ColdAccountAccess2780 uint64 = 3000
|
// EIP-8038 parameters defined below.
|
||||||
CreateAccess2780 uint64 = 11000
|
TxBaseCost2780 uint64 = 12000
|
||||||
TxValueCost2780 uint64 = 4244
|
TxValueCost2780 uint64 = 4244
|
||||||
TransferLogCost2780 uint64 = 1756
|
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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue