mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 13:21:37 +00:00
core: implement EIP-8037, state creation gas cost increase (#33601)
Implements https://eips.ethereum.org/EIPS/eip-8037 mainly done in order to judge the complexity of the EIP and to act as a jumping off point, since the eip will likely change. --------- Co-authored-by: Gary Rong <garyrong0905@gmail.com>
This commit is contained in:
parent
d93dda49c8
commit
9059157eba
36 changed files with 1257 additions and 547 deletions
|
|
@ -183,14 +183,15 @@ func (pre *Prestate) Apply(vmConfig vm.Config, chainConfig *params.ChainConfig,
|
|||
blockAccessList = bal.NewConstructionBlockAccessList()
|
||||
)
|
||||
vmContext := vm.BlockContext{
|
||||
CanTransfer: core.CanTransfer,
|
||||
Transfer: core.Transfer,
|
||||
Coinbase: pre.Env.Coinbase,
|
||||
BlockNumber: new(big.Int).SetUint64(pre.Env.Number),
|
||||
Time: pre.Env.Timestamp,
|
||||
Difficulty: pre.Env.Difficulty,
|
||||
GasLimit: pre.Env.GasLimit,
|
||||
GetHash: getHash,
|
||||
CanTransfer: core.CanTransfer,
|
||||
Transfer: core.Transfer,
|
||||
Coinbase: pre.Env.Coinbase,
|
||||
BlockNumber: new(big.Int).SetUint64(pre.Env.Number),
|
||||
Time: pre.Env.Timestamp,
|
||||
Difficulty: pre.Env.Difficulty,
|
||||
GasLimit: pre.Env.GasLimit,
|
||||
GetHash: getHash,
|
||||
CostPerStateByte: params.CostPerStateByte,
|
||||
}
|
||||
if pre.Env.SlotNumber != nil {
|
||||
vmContext.SlotNum = *pre.Env.SlotNumber
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ func Transaction(ctx *cli.Context) error {
|
|||
}
|
||||
// Check intrinsic gas
|
||||
rules := chainConfig.Rules(common.Big0, true, 0)
|
||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam)
|
||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte)
|
||||
if err != nil {
|
||||
r.Error = err
|
||||
results = append(results, r)
|
||||
|
|
|
|||
|
|
@ -153,6 +153,14 @@ func assertEmpty(t *testing.T, aa *bal.AccountAccess) {
|
|||
}
|
||||
}
|
||||
|
||||
// txGasNewAccount covers the base tx cost plus the EIP-8037 account-creation
|
||||
// state-gas charge (STATE_BYTES_PER_NEW_ACCOUNT × CPSB ≈ 183,600) that is
|
||||
// incurred when value is transferred to a non-existent account under Amsterdam.
|
||||
// params.TxGas (21,000) alone is insufficient: the transfer would run out of
|
||||
// gas, the credit would revert, and the recipient would never get a balance
|
||||
// change recorded in the BAL.
|
||||
const txGasNewAccount = 250_000
|
||||
|
||||
// --- tx builders ---
|
||||
|
||||
func (e *balTestEnv) tx(nonce uint64, to *common.Address, value *big.Int, gas uint64, tipGwei int64, data []byte) *types.Transaction {
|
||||
|
|
@ -177,7 +185,7 @@ func TestBALTxSenderAndRecipient(t *testing.T) {
|
|||
env := newBALTestEnv(nil)
|
||||
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &to, big.NewInt(1000), params.TxGas, 0, nil))
|
||||
g.AddTx(env.tx(0, &to, big.NewInt(1000), txGasNewAccount, 0, nil))
|
||||
})
|
||||
|
||||
sender := assertPresent(t, b, env.from)
|
||||
|
|
@ -260,7 +268,7 @@ func TestBALSystemAddressIncludedWhenTouched(t *testing.T) {
|
|||
env := newBALTestEnv(nil)
|
||||
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &sys, big.NewInt(1000), params.TxGas, 0, nil))
|
||||
g.AddTx(env.tx(0, &sys, big.NewInt(1000), txGasNewAccount, 0, nil))
|
||||
})
|
||||
|
||||
aa := assertPresent(t, b, sys)
|
||||
|
|
@ -283,7 +291,7 @@ func TestBALPrecompileInvokedFromContractIncluded(t *testing.T) {
|
|||
|
||||
env := newBALTestEnv(types.GenesisAlloc{caller: {Code: code, Balance: common.Big0}})
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 100_000, 0, nil))
|
||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
aa := assertPresent(t, b, identity)
|
||||
|
|
@ -315,7 +323,7 @@ func TestBALPrecompileValueTransferRecordsBalance(t *testing.T) {
|
|||
env := newBALTestEnv(nil)
|
||||
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &identity, big.NewInt(5), 50_000, 0, nil))
|
||||
g.AddTx(env.tx(0, &identity, big.NewInt(5), txGasNewAccount, 0, nil))
|
||||
})
|
||||
|
||||
aa := assertPresent(t, b, identity)
|
||||
|
|
@ -334,7 +342,7 @@ func TestBALBalanceProbeOnNonExistent(t *testing.T) {
|
|||
|
||||
env := newBALTestEnv(types.GenesisAlloc{caller: {Code: code, Balance: common.Big0}})
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 100_000, 0, nil))
|
||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
assertEmpty(t, assertPresent(t, b, probe))
|
||||
|
|
@ -350,7 +358,7 @@ func TestBALExtCodeSizeProbeOnNonExistent(t *testing.T) {
|
|||
|
||||
env := newBALTestEnv(types.GenesisAlloc{caller: {Code: code, Balance: common.Big0}})
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 100_000, 0, nil))
|
||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
assertEmpty(t, assertPresent(t, b, probe))
|
||||
|
|
@ -366,7 +374,7 @@ func TestBALExtCodeHashProbeOnNonExistent(t *testing.T) {
|
|||
|
||||
env := newBALTestEnv(types.GenesisAlloc{caller: {Code: code, Balance: common.Big0}})
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 100_000, 0, nil))
|
||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
assertEmpty(t, assertPresent(t, b, probe))
|
||||
|
|
@ -385,7 +393,7 @@ func TestBALExtCodeCopyProbeOnNonExistent(t *testing.T) {
|
|||
|
||||
env := newBALTestEnv(types.GenesisAlloc{caller: {Code: code, Balance: common.Big0}})
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 100_000, 0, nil))
|
||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
assertEmpty(t, assertPresent(t, b, probe))
|
||||
|
|
@ -447,7 +455,7 @@ func TestBALCallTargetWithEmptyChangeSet(t *testing.T) {
|
|||
})
|
||||
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &target, big.NewInt(0), 100_000, 0, nil))
|
||||
g.AddTx(env.tx(0, &target, big.NewInt(0), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
assertEmpty(t, assertPresent(t, b, target))
|
||||
|
|
@ -465,7 +473,7 @@ func TestBALCallCodeTargetIncluded(t *testing.T) {
|
|||
})
|
||||
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 200_000, 0, nil))
|
||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
assertPresent(t, b, caller)
|
||||
|
|
@ -483,7 +491,7 @@ func TestBALDelegateCallTargetIncluded(t *testing.T) {
|
|||
})
|
||||
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 200_000, 0, nil))
|
||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
assertPresent(t, b, caller)
|
||||
|
|
@ -501,7 +509,7 @@ func TestBALStaticCallTargetIncluded(t *testing.T) {
|
|||
})
|
||||
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 200_000, 0, nil))
|
||||
g.AddTx(env.tx(0, &caller, big.NewInt(0), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
assertPresent(t, b, caller)
|
||||
|
|
@ -519,7 +527,7 @@ func TestBALRevertedTxStillIncluded(t *testing.T) {
|
|||
env := newBALTestEnv(types.GenesisAlloc{reverter: {Code: revertCode, Balance: common.Big0}})
|
||||
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &reverter, big.NewInt(0), 100_000, 0, nil))
|
||||
g.AddTx(env.tx(0, &reverter, big.NewInt(0), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
assertEmpty(t, assertPresent(t, b, reverter))
|
||||
|
|
@ -533,7 +541,7 @@ func TestBALSenderRecordedOnRevert(t *testing.T) {
|
|||
env := newBALTestEnv(types.GenesisAlloc{reverter: {Code: revertCode, Balance: common.Big0}})
|
||||
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &reverter, big.NewInt(0), 100_000, 0, nil))
|
||||
g.AddTx(env.tx(0, &reverter, big.NewInt(0), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
sender := assertPresent(t, b, env.from)
|
||||
|
|
@ -557,7 +565,7 @@ func TestBALStorageWriteRecorded(t *testing.T) {
|
|||
env := newBALTestEnv(types.GenesisAlloc{contract: {Code: code, Balance: common.Big0}})
|
||||
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &contract, big.NewInt(0), 100_000, 0, nil))
|
||||
g.AddTx(env.tx(0, &contract, big.NewInt(0), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
aa := assertPresent(t, b, contract)
|
||||
|
|
@ -578,7 +586,7 @@ func TestBALStorageSloadOnly(t *testing.T) {
|
|||
env := newBALTestEnv(types.GenesisAlloc{contract: {Code: code, Balance: common.Big0}})
|
||||
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &contract, big.NewInt(0), 100_000, 0, nil))
|
||||
g.AddTx(env.tx(0, &contract, big.NewInt(0), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
aa := assertPresent(t, b, contract)
|
||||
|
|
@ -604,7 +612,7 @@ func TestBALStorageReadThenWriteOnlyInWrites(t *testing.T) {
|
|||
env := newBALTestEnv(types.GenesisAlloc{contract: {Code: code, Balance: common.Big0}})
|
||||
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &contract, big.NewInt(0), 100_000, 0, nil))
|
||||
g.AddTx(env.tx(0, &contract, big.NewInt(0), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
aa := assertPresent(t, b, contract)
|
||||
|
|
@ -632,7 +640,7 @@ func TestBALNoOpSSTOREDemotesToRead(t *testing.T) {
|
|||
})
|
||||
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &contract, big.NewInt(0), 100_000, 0, nil))
|
||||
g.AddTx(env.tx(0, &contract, big.NewInt(0), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
aa := assertPresent(t, b, contract)
|
||||
|
|
@ -660,7 +668,7 @@ func TestBALStorageWriteZeroIsAWrite(t *testing.T) {
|
|||
})
|
||||
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &contract, big.NewInt(0), 100_000, 0, nil))
|
||||
g.AddTx(env.tx(0, &contract, big.NewInt(0), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
aa := assertPresent(t, b, contract)
|
||||
|
|
@ -687,7 +695,7 @@ func TestBALCreateDeploysCode(t *testing.T) {
|
|||
init := []byte{0x60, 0x00, 0x60, 0x00, 0x53, 0x60, 0x01, 0x60, 0x00, 0xf3}
|
||||
|
||||
b, receipts := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, nil, big.NewInt(7), 200_000, 0, init))
|
||||
g.AddTx(env.tx(0, nil, big.NewInt(7), 1_000_000, 0, init))
|
||||
})
|
||||
|
||||
created := receipts[0].ContractAddress
|
||||
|
|
@ -711,7 +719,7 @@ func TestBALCreateEmptyRuntimeNoCodeEntry(t *testing.T) {
|
|||
init := []byte{0x60, 0x00, 0x60, 0x00, 0xf3}
|
||||
|
||||
b, receipts := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, nil, big.NewInt(0), 200_000, 0, init))
|
||||
g.AddTx(env.tx(0, nil, big.NewInt(0), 1_000_000, 0, init))
|
||||
})
|
||||
|
||||
created := receipts[0].ContractAddress
|
||||
|
|
@ -732,7 +740,7 @@ func TestBALCreateInitRevertEmptyChangeSet(t *testing.T) {
|
|||
init := []byte{0x60, 0x00, 0x60, 0x00, 0xfd}
|
||||
|
||||
b, receipts := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, nil, big.NewInt(0), 200_000, 0, init))
|
||||
g.AddTx(env.tx(0, nil, big.NewInt(0), 1_000_000, 0, init))
|
||||
})
|
||||
|
||||
created := receipts[0].ContractAddress
|
||||
|
|
@ -743,11 +751,13 @@ func TestBALCreateInitRevertEmptyChangeSet(t *testing.T) {
|
|||
// the deployed address in the BAL with an empty change set.
|
||||
func TestBALCreateInitOOGEmptyChangeSet(t *testing.T) {
|
||||
env := newBALTestEnv(nil)
|
||||
// Infinite loop: JUMPDEST PUSH1 0 JUMP — burns gas until OOG.
|
||||
// Infinite loop: JUMPDEST PUSH1 0 JUMP — burns gas until OOG. The
|
||||
// gas budget must cover EIP-8037 intrinsic state gas (account creation)
|
||||
// so the tx is accepted; OOG must happen inside the init code.
|
||||
init := []byte{0x5b, 0x60, 0x00, 0x56}
|
||||
|
||||
b, receipts := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, nil, big.NewInt(0), 60_000, 0, init))
|
||||
g.AddTx(env.tx(0, nil, big.NewInt(0), 220_000, 0, init))
|
||||
})
|
||||
|
||||
created := receipts[0].ContractAddress
|
||||
|
|
@ -771,7 +781,7 @@ func TestBALCreateAddressCollisionStillIncluded(t *testing.T) {
|
|||
// Init code doesn't matter — execution never starts.
|
||||
init := []byte{0x60, 0x00, 0x60, 0x00, 0xf3}
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, nil, big.NewInt(0), 200_000, 0, init))
|
||||
g.AddTx(env.tx(0, nil, big.NewInt(0), 1_000_000, 0, init))
|
||||
})
|
||||
|
||||
aa := assertPresent(t, b, collide)
|
||||
|
|
@ -799,7 +809,7 @@ func TestBALInEVMCreatePreAccessAbortDestinationExcluded(t *testing.T) {
|
|||
})
|
||||
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &factory, big.NewInt(0), 200_000, 0, nil))
|
||||
g.AddTx(env.tx(0, &factory, big.NewInt(0), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
// The address that WOULD have been deployed had the create succeeded.
|
||||
|
|
@ -842,7 +852,7 @@ func TestBALInEVMCreateDeploysContract(t *testing.T) {
|
|||
env := newBALTestEnv(types.GenesisAlloc{factory: {Code: code, Balance: common.Big0, Nonce: 1}})
|
||||
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &factory, big.NewInt(0), 300_000, 0, nil))
|
||||
g.AddTx(env.tx(0, &factory, big.NewInt(0), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
// Deployed address depends on the factory's nonce at the moment of CREATE,
|
||||
|
|
@ -870,7 +880,7 @@ func TestBALSelfDestructBeneficiaryWithZeroBalance(t *testing.T) {
|
|||
init = append(init, 0xff)
|
||||
|
||||
b, receipts := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, nil, big.NewInt(0), 200_000, 0, init))
|
||||
g.AddTx(env.tx(0, nil, big.NewInt(0), 1_000_000, 0, init))
|
||||
})
|
||||
|
||||
created := receipts[0].ContractAddress
|
||||
|
|
@ -896,7 +906,7 @@ func TestBALSelfDestructBeneficiaryWithValueTransfer(t *testing.T) {
|
|||
init = append(init, 0xff)
|
||||
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, nil, big.NewInt(100), 200_000, 0, init))
|
||||
g.AddTx(env.tx(0, nil, big.NewInt(100), 1_000_000, 0, init))
|
||||
})
|
||||
|
||||
ben := assertPresent(t, b, beneficiary)
|
||||
|
|
@ -921,7 +931,7 @@ func TestBALSelfDestructPreExistingContract(t *testing.T) {
|
|||
})
|
||||
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &suicidal, big.NewInt(0), 200_000, 0, nil))
|
||||
g.AddTx(env.tx(0, &suicidal, big.NewInt(0), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
aa := assertPresent(t, b, suicidal)
|
||||
|
|
@ -966,7 +976,7 @@ func TestBALMidTxBalanceRoundTrip(t *testing.T) {
|
|||
env := newBALTestEnv(types.GenesisAlloc{bouncer: {Code: code, Balance: common.Big0}})
|
||||
|
||||
b, _ := env.run(t, func(g *BlockGen) {
|
||||
g.AddTx(env.tx(0, &bouncer, big.NewInt(1234), 200_000, 0, nil))
|
||||
g.AddTx(env.tx(0, &bouncer, big.NewInt(1234), 1_000_000, 0, nil))
|
||||
})
|
||||
|
||||
aa := assertPresent(t, b, bouncer)
|
||||
|
|
@ -1072,7 +1082,7 @@ func TestBALAuthorityIncludedOnSetCodeTx(t *testing.T) {
|
|||
Nonce: 0,
|
||||
To: env.from,
|
||||
Value: new(uint256.Int),
|
||||
Gas: 200_000,
|
||||
Gas: 1_000_000,
|
||||
GasFeeCap: uint256.NewInt(uint64(newGwei(10).Int64())),
|
||||
GasTipCap: new(uint256.Int),
|
||||
AuthList: []types.SetCodeAuthorization{auth},
|
||||
|
|
@ -1112,7 +1122,7 @@ func TestBALDelegationTargetNotIncludedOnAuthOnly(t *testing.T) {
|
|||
Nonce: 0,
|
||||
To: env.from, // tx.to is an EOA with no code: delegate is never called
|
||||
Value: new(uint256.Int),
|
||||
Gas: 200_000,
|
||||
Gas: 1_000_000,
|
||||
GasFeeCap: uint256.NewInt(uint64(newGwei(10).Int64())),
|
||||
GasTipCap: new(uint256.Int),
|
||||
AuthList: []types.SetCodeAuthorization{auth},
|
||||
|
|
@ -1131,7 +1141,7 @@ func (e *balTestEnv) newSetCodeTx(t *testing.T, nonce uint64, to common.Address,
|
|||
Nonce: nonce,
|
||||
To: to,
|
||||
Value: new(uint256.Int),
|
||||
Gas: 400_000,
|
||||
Gas: 1_000_000,
|
||||
GasFeeCap: uint256.NewInt(uint64(newGwei(10).Int64())),
|
||||
GasTipCap: new(uint256.Int),
|
||||
AuthList: auths,
|
||||
|
|
|
|||
|
|
@ -89,7 +89,7 @@ func genValueTx(nbytes int) func(int, *BlockGen) {
|
|||
data := make([]byte, nbytes)
|
||||
return func(i int, gen *BlockGen) {
|
||||
toaddr := common.Address{}
|
||||
cost, _ := IntrinsicGas(data, nil, nil, false, false, false, false, false)
|
||||
cost, _ := IntrinsicGas(data, nil, nil, false, params.Rules{}, params.CostPerStateByte)
|
||||
signer := gen.Signer()
|
||||
gasPrice := big.NewInt(0)
|
||||
if gen.header.BaseFee != nil {
|
||||
|
|
|
|||
|
|
@ -64,12 +64,12 @@ var (
|
|||
func TestProcessUBT(t *testing.T) {
|
||||
var (
|
||||
code = common.FromHex(`6060604052600a8060106000396000f360606040526008565b00`)
|
||||
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, true, true, true, false)
|
||||
intrinsicContractCreationGas, _ = IntrinsicGas(code, nil, nil, true, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
|
||||
// A contract creation that calls EXTCODECOPY in the constructor. Used to ensure that the witness
|
||||
// will not contain that copied data.
|
||||
// Source: https://gist.github.com/gballet/a23db1e1cb4ed105616b5920feb75985
|
||||
codeWithExtCodeCopy = common.FromHex(`0x60806040526040516100109061017b565b604051809103906000f08015801561002c573d6000803e3d6000fd5b506000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561007857600080fd5b5060008067ffffffffffffffff8111156100955761009461024a565b5b6040519080825280601f01601f1916602001820160405280156100c75781602001600182028036833780820191505090505b50905060008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1690506020600083833c81610101906101e3565b60405161010d90610187565b61011791906101a3565b604051809103906000f080158015610133573d6000803e3d6000fd5b50600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550505061029b565b60d58061046783390190565b6102068061053c83390190565b61019d816101d9565b82525050565b60006020820190506101b86000830184610194565b92915050565b6000819050602082019050919050565b600081519050919050565b6000819050919050565b60006101ee826101ce565b826101f8846101be565b905061020381610279565b925060208210156102435761023e7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8360200360080261028e565b831692505b5050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600061028582516101d9565b80915050919050565b600082821b905092915050565b6101bd806102aa6000396000f3fe608060405234801561001057600080fd5b506004361061002b5760003560e01c8063f566852414610030575b600080fd5b61003861004e565b6040516100459190610146565b60405180910390f35b6000600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166381ca91d36040518163ffffffff1660e01b815260040160206040518083038186803b1580156100b857600080fd5b505afa1580156100cc573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906100f0919061010a565b905090565b60008151905061010481610170565b92915050565b6000602082840312156101205761011f61016b565b5b600061012e848285016100f5565b91505092915050565b61014081610161565b82525050565b600060208201905061015b6000830184610137565b92915050565b6000819050919050565b600080fd5b61017981610161565b811461018457600080fd5b5056fea2646970667358221220a6a0e11af79f176f9c421b7b12f441356b25f6489b83d38cc828a701720b41f164736f6c63430008070033608060405234801561001057600080fd5b5060b68061001f6000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c8063ab5ed15014602d575b600080fd5b60336047565b604051603e9190605d565b60405180910390f35b60006001905090565b6057816076565b82525050565b6000602082019050607060008301846050565b92915050565b600081905091905056fea26469706673582212203a14eb0d5cd07c277d3e24912f110ddda3e553245a99afc4eeefb2fbae5327aa64736f6c63430008070033608060405234801561001057600080fd5b5060405161020638038061020683398181016040528101906100329190610063565b60018160001c6100429190610090565b60008190555050610145565b60008151905061005d8161012e565b92915050565b60006020828403121561007957610078610129565b5b60006100878482850161004e565b91505092915050565b600061009b826100f0565b91506100a6836100f0565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156100db576100da6100fa565b5b828201905092915050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b600080fd5b610137816100e6565b811461014257600080fd5b50565b60b3806101536000396000f3fe6080604052348015600f57600080fd5b506004361060285760003560e01c806381ca91d314602d575b600080fd5b60336047565b604051603e9190605a565b60405180910390f35b60005481565b6054816073565b82525050565b6000602082019050606d6000830184604d565b92915050565b600081905091905056fea26469706673582212209bff7098a2f526de1ad499866f27d6d0d6f17b74a413036d6063ca6a0998ca4264736f6c63430008070033`)
|
||||
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, true, true, true, false)
|
||||
intrinsicCodeWithExtCodeCopyGas, _ = IntrinsicGas(codeWithExtCodeCopy, nil, nil, true, params.Rules{IsHomestead: true, IsIstanbul: true, IsShanghai: true}, 0)
|
||||
signer = types.LatestSigner(testUBTChainConfig)
|
||||
testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
|
||||
bcdb = rawdb.NewMemoryDatabase() // Database for the blockchain
|
||||
|
|
@ -201,7 +201,7 @@ func TestProcessParentBlockHash(t *testing.T) {
|
|||
if isUBT {
|
||||
chainConfig = testUBTChainConfig
|
||||
}
|
||||
vmContext := NewEVMBlockContext(header, nil, new(common.Address))
|
||||
vmContext := NewEVMBlockContext(header, &BlockChain{chainConfig: chainConfig}, new(common.Address))
|
||||
evm := vm.NewEVM(vmContext, statedb, chainConfig, vm.Config{})
|
||||
ProcessParentBlockHash(header.ParentHash, evm, bal.NewConstructionBlockAccessList())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2316,6 +2316,14 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
|
|||
stats.Validation = vtime - (statedb.AccountHashes + statedb.AccountUpdates + statedb.StorageUpdates) // The time spent on block validation
|
||||
stats.CrossValidation = xvtime // The time spent on stateless cross validation
|
||||
|
||||
// Attach the computed block access list so it gets persisted alongside the
|
||||
// block. The validator has already verified the hash matches the header.
|
||||
// BAL is only meaningful from Amsterdam onward; skip pre-Amsterdam blocks
|
||||
// to avoid persisting and serving empty BALs over the network.
|
||||
if res.Bal != nil && block.AccessList() == nil && bc.chainConfig.IsAmsterdam(block.Number(), block.Time()) {
|
||||
block = block.WithAccessListUnsafe(res.Bal.ToEncodingObj())
|
||||
}
|
||||
|
||||
// Write the block to the chain and get the status.
|
||||
var status WriteStatus
|
||||
if config.WriteState {
|
||||
|
|
|
|||
25
core/evm.go
25
core/evm.go
|
|
@ -68,18 +68,19 @@ func NewEVMBlockContext(header *types.Header, chain ChainContext, author *common
|
|||
}
|
||||
|
||||
return vm.BlockContext{
|
||||
CanTransfer: CanTransfer,
|
||||
Transfer: Transfer,
|
||||
GetHash: GetHashFn(header, chain),
|
||||
Coinbase: beneficiary,
|
||||
BlockNumber: new(big.Int).Set(header.Number),
|
||||
Time: header.Time,
|
||||
Difficulty: new(big.Int).Set(header.Difficulty),
|
||||
BaseFee: baseFee,
|
||||
BlobBaseFee: blobBaseFee,
|
||||
GasLimit: header.GasLimit,
|
||||
Random: random,
|
||||
SlotNum: slotNum,
|
||||
CanTransfer: CanTransfer,
|
||||
Transfer: Transfer,
|
||||
GetHash: GetHashFn(header, chain),
|
||||
Coinbase: beneficiary,
|
||||
BlockNumber: new(big.Int).Set(header.Number),
|
||||
Time: header.Time,
|
||||
Difficulty: new(big.Int).Set(header.Difficulty),
|
||||
BaseFee: baseFee,
|
||||
BlobBaseFee: blobBaseFee,
|
||||
GasLimit: header.GasLimit,
|
||||
Random: random,
|
||||
SlotNum: slotNum,
|
||||
CostPerStateByte: params.CostPerStateByte,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@ type GasPool struct {
|
|||
remaining uint64
|
||||
initial uint64
|
||||
cumulativeUsed uint64
|
||||
|
||||
// After 8037 Block gas used is max(cumulativeRegular, cumulativeState).
|
||||
cumulativeRegular uint64
|
||||
cumulativeState uint64
|
||||
}
|
||||
|
||||
// NewGasPool initializes the gasPool with the given amount.
|
||||
|
|
@ -37,9 +41,9 @@ func NewGasPool(amount uint64) *GasPool {
|
|||
}
|
||||
}
|
||||
|
||||
// SubGas deducts the given amount from the pool if enough gas is
|
||||
// CheckGasLegacy deducts the given amount from the pool if enough gas is
|
||||
// available and returns an error otherwise.
|
||||
func (gp *GasPool) SubGas(amount uint64) error {
|
||||
func (gp *GasPool) CheckGasLegacy(amount uint64) error {
|
||||
if gp.remaining < amount {
|
||||
return ErrGasLimitReached
|
||||
}
|
||||
|
|
@ -47,41 +51,73 @@ func (gp *GasPool) SubGas(amount uint64) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ReturnGas adds the refunded gas back to the pool and updates
|
||||
// CheckGasAmsterdam performs the EIP-8037 per-tx 2D block-inclusion check:
|
||||
// the worst-case regular contribution must fit in the regular dimension and
|
||||
// the worst-case state contribution must fit in the state dimension
|
||||
func (gp *GasPool) CheckGasAmsterdam(regularReservation, stateReservation uint64) error {
|
||||
if gp.initial-gp.cumulativeRegular < regularReservation {
|
||||
return ErrGasLimitReached
|
||||
}
|
||||
if gp.initial-gp.cumulativeState < stateReservation {
|
||||
return ErrGasLimitReached
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChargeGasLegacy adds the refunded gas back to the pool and updates
|
||||
// the cumulative gas usage accordingly.
|
||||
func (gp *GasPool) ReturnGas(returned uint64, gasUsed uint64) error {
|
||||
func (gp *GasPool) ChargeGasLegacy(returned uint64, gasUsed uint64) error {
|
||||
if gp.remaining > math.MaxUint64-returned {
|
||||
return fmt.Errorf("%w: remaining: %d, returned: %d", ErrGasLimitOverflow, gp.remaining, returned)
|
||||
}
|
||||
// The returned gas calculation differs across forks.
|
||||
//
|
||||
// - Pre-Amsterdam:
|
||||
// returned = purchased - remaining (refund included)
|
||||
//
|
||||
// - Post-Amsterdam:
|
||||
// returned = purchased - gasUsed (refund excluded)
|
||||
// returned = purchased - remaining (refund included)
|
||||
gp.remaining += returned
|
||||
|
||||
// gasUsed = max(txGasUsed - gasRefund, calldataFloorGasCost)
|
||||
// regardless of Amsterdam is activated or not.
|
||||
gp.cumulativeUsed += gasUsed
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChargeGasAmsterdam calculates the new remaining gas in the pool after the
|
||||
// execution of a message. Previously we subtracted and re-added gas to the
|
||||
// gaspool. After Amsterdam we only check if we can include the transaction
|
||||
// and charge the gaspool at the end.
|
||||
func (gp *GasPool) ChargeGasAmsterdam(txRegular, txState, receiptGasUsed uint64) error {
|
||||
cumulativeRegular := gp.cumulativeRegular + txRegular
|
||||
cumulativeState := gp.cumulativeState + txState
|
||||
blockUsed := max(cumulativeRegular, cumulativeState)
|
||||
if gp.initial < blockUsed {
|
||||
return fmt.Errorf("%w: block gas overflow: initial %d, used %d (regular: %d, state: %d)",
|
||||
ErrGasLimitReached, gp.initial, blockUsed, cumulativeRegular, cumulativeState)
|
||||
}
|
||||
gp.cumulativeRegular = cumulativeRegular
|
||||
gp.cumulativeState = cumulativeState
|
||||
gp.cumulativeUsed += receiptGasUsed
|
||||
// TODO(rjl, marius), the semantics of this counter is slightly different
|
||||
// in the context of Amsterdam, the API Gas() should be reworked.
|
||||
gp.remaining = gp.initial - gp.cumulativeRegular
|
||||
return nil
|
||||
}
|
||||
|
||||
// Gas returns the amount of gas remaining in the pool.
|
||||
func (gp *GasPool) Gas() uint64 {
|
||||
return gp.remaining
|
||||
}
|
||||
|
||||
// CumulativeUsed returns the amount of cumulative consumed gas (refunded included).
|
||||
// CumulativeUsed returns the cumulative gas consumed for receipt tracking.
|
||||
func (gp *GasPool) CumulativeUsed() uint64 {
|
||||
return gp.cumulativeUsed
|
||||
}
|
||||
|
||||
// Used returns the amount of consumed gas.
|
||||
func (gp *GasPool) Used() uint64 {
|
||||
// After 8037, return max(sum_regular, sum_state)
|
||||
if gp.cumulativeRegular > 0 || gp.cumulativeState > 0 {
|
||||
return max(gp.cumulativeRegular, gp.cumulativeState)
|
||||
}
|
||||
// Before 8037, return initial-remaining
|
||||
if gp.initial < gp.remaining {
|
||||
panic("gas used underflow")
|
||||
panic(fmt.Sprintf("gas used underflow: %v %v", gp.initial, gp.remaining))
|
||||
}
|
||||
return gp.initial - gp.remaining
|
||||
}
|
||||
|
|
@ -89,9 +125,11 @@ func (gp *GasPool) Used() uint64 {
|
|||
// Snapshot returns the deep-copied object as the snapshot.
|
||||
func (gp *GasPool) Snapshot() *GasPool {
|
||||
return &GasPool{
|
||||
initial: gp.initial,
|
||||
remaining: gp.remaining,
|
||||
cumulativeUsed: gp.cumulativeUsed,
|
||||
initial: gp.initial,
|
||||
remaining: gp.remaining,
|
||||
cumulativeUsed: gp.cumulativeUsed,
|
||||
cumulativeRegular: gp.cumulativeRegular,
|
||||
cumulativeState: gp.cumulativeState,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -100,6 +138,8 @@ func (gp *GasPool) Set(other *GasPool) {
|
|||
gp.initial = other.initial
|
||||
gp.remaining = other.remaining
|
||||
gp.cumulativeUsed = other.cumulativeUsed
|
||||
gp.cumulativeRegular = other.cumulativeRegular
|
||||
gp.cumulativeState = other.cumulativeState
|
||||
}
|
||||
|
||||
func (gp *GasPool) String() string {
|
||||
|
|
|
|||
|
|
@ -271,6 +271,21 @@ func ApplyTransaction(evm *vm.EVM, gp *GasPool, statedb *state.StateDB, header *
|
|||
return ApplyTransactionWithEVM(msg, gp, statedb, header.Number, header.Hash(), header.Time, tx, evm)
|
||||
}
|
||||
|
||||
// systemCallGasBudget returns the gas budget for system calls.
|
||||
func systemCallGasBudget(evm *vm.EVM) (gasLimit uint64, gasBudget vm.GasBudget) {
|
||||
if !evm.GetRules().IsAmsterdam {
|
||||
gasLimit = 30_000_000
|
||||
gasBudget = vm.NewGasBudget(gasLimit, 0)
|
||||
} else {
|
||||
// SYSTEM_MAX_SSTORES_PER_CALL = 16 is the upper bound on the number of
|
||||
// new storage slots a single system call is expected to write.
|
||||
stateBudget := params.SystemMaxSStoresPerCall * evm.Context.CostPerStateByte * params.StorageCreationSize
|
||||
gasLimit = 30_000_000
|
||||
gasBudget = vm.NewGasBudget(gasLimit, stateBudget)
|
||||
}
|
||||
return gasLimit, gasBudget
|
||||
}
|
||||
|
||||
// ProcessBeaconBlockRoot applies the EIP-4788 system call to the beacon block root
|
||||
// contract. This method is exported to be used in tests.
|
||||
func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM, blockAccessList *bal.ConstructionBlockAccessList) {
|
||||
|
|
@ -280,9 +295,10 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM, blockAccessList
|
|||
defer tracer.OnSystemCallEnd()
|
||||
}
|
||||
}
|
||||
gasLimit, gasBudget := systemCallGasBudget(evm)
|
||||
msg := &Message{
|
||||
From: params.SystemAddress,
|
||||
GasLimit: 30_000_000,
|
||||
GasLimit: gasLimit,
|
||||
GasPrice: uint256.NewInt(0),
|
||||
GasFeeCap: uint256.NewInt(0),
|
||||
GasTipCap: uint256.NewInt(0),
|
||||
|
|
@ -293,7 +309,7 @@ func ProcessBeaconBlockRoot(beaconRoot common.Hash, evm *vm.EVM, blockAccessList
|
|||
evm.StateDB.Prepare(evm.GetRules(), common.Address{}, common.Address{}, nil, nil, nil)
|
||||
evm.StateDB.SetTxContext(common.Hash{}, 0, 0)
|
||||
evm.StateDB.AddAddressToAccessList(params.BeaconRootsAddress)
|
||||
_, _, _ = evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560)
|
||||
_, _, _ = evm.Call(msg.From, *msg.To, msg.Data, gasBudget, common.U2560)
|
||||
if evm.StateDB.AccessEvents() != nil {
|
||||
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
|
||||
}
|
||||
|
|
@ -309,9 +325,10 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM, blockAccessList *
|
|||
defer tracer.OnSystemCallEnd()
|
||||
}
|
||||
}
|
||||
gasLimit, gasBudget := systemCallGasBudget(evm)
|
||||
msg := &Message{
|
||||
From: params.SystemAddress,
|
||||
GasLimit: 30_000_000,
|
||||
GasLimit: gasLimit,
|
||||
GasPrice: uint256.NewInt(0),
|
||||
GasFeeCap: uint256.NewInt(0),
|
||||
GasTipCap: uint256.NewInt(0),
|
||||
|
|
@ -322,7 +339,7 @@ func ProcessParentBlockHash(prevHash common.Hash, evm *vm.EVM, blockAccessList *
|
|||
evm.StateDB.Prepare(evm.GetRules(), common.Address{}, common.Address{}, nil, nil, nil)
|
||||
evm.StateDB.SetTxContext(common.Hash{}, 0, 0)
|
||||
evm.StateDB.AddAddressToAccessList(params.HistoryStorageAddress)
|
||||
_, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560)
|
||||
_, _, err := evm.Call(msg.From, *msg.To, msg.Data, gasBudget, common.U2560)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
|
@ -351,9 +368,10 @@ func processRequestsSystemCall(requests *[][]byte, rules params.Rules, evm *vm.E
|
|||
defer tracer.OnSystemCallEnd()
|
||||
}
|
||||
}
|
||||
gasLimit, gasBudget := systemCallGasBudget(evm)
|
||||
msg := &Message{
|
||||
From: params.SystemAddress,
|
||||
GasLimit: 30_000_000,
|
||||
GasLimit: gasLimit,
|
||||
GasPrice: uint256.NewInt(0),
|
||||
GasFeeCap: uint256.NewInt(0),
|
||||
GasTipCap: uint256.NewInt(0),
|
||||
|
|
@ -363,7 +381,7 @@ func processRequestsSystemCall(requests *[][]byte, rules params.Rules, evm *vm.E
|
|||
evm.StateDB.Prepare(rules, common.Address{}, common.Address{}, nil, nil, nil)
|
||||
evm.StateDB.SetTxContext(common.Hash{}, 0, blockAccessIndex)
|
||||
evm.StateDB.AddAddressToAccessList(addr)
|
||||
ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, vm.NewGasBudget(30_000_000), common.U2560)
|
||||
ret, _, err := evm.Call(msg.From, *msg.To, msg.Data, gasBudget, common.U2560)
|
||||
if evm.StateDB.AccessEvents() != nil {
|
||||
evm.StateDB.AccessEvents().Merge(evm.AccessEvents)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,13 +68,27 @@ func (result *ExecutionResult) Revert() []byte {
|
|||
}
|
||||
|
||||
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
|
||||
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation, isHomestead, isEIP2028, isEIP3860, isAmsterdam bool) (vm.GasCosts, error) {
|
||||
func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.SetCodeAuthorization, isContractCreation bool, rules params.Rules, costPerStateByte uint64) (vm.GasCosts, error) {
|
||||
// Set the starting gas for the raw transaction
|
||||
var gas uint64
|
||||
if isContractCreation && isHomestead {
|
||||
gas = params.TxGasContractCreation
|
||||
var gas vm.GasCosts
|
||||
if isContractCreation && rules.IsHomestead {
|
||||
if rules.IsAmsterdam {
|
||||
gas.RegularGas = params.TxGas + params.CreateGasAmsterdam
|
||||
gas.StateGas = params.AccountCreationSize * costPerStateByte
|
||||
} else {
|
||||
gas.RegularGas = params.TxGasContractCreation
|
||||
}
|
||||
} else {
|
||||
gas = params.TxGas
|
||||
gas.RegularGas = params.TxGas
|
||||
}
|
||||
// Add gas for authorizations
|
||||
if authList != nil {
|
||||
if rules.IsAmsterdam {
|
||||
gas.RegularGas += uint64(len(authList)) * params.TxAuthTupleRegularGas
|
||||
gas.StateGas += uint64(len(authList)) * (params.AuthorizationCreationSize + params.AccountCreationSize) * costPerStateByte
|
||||
} else {
|
||||
gas.RegularGas += uint64(len(authList)) * params.CallNewAccountGas
|
||||
}
|
||||
}
|
||||
dataLen := uint64(len(data))
|
||||
// Bump the required gas by the amount of transactional data
|
||||
|
|
@ -85,59 +99,56 @@ func IntrinsicGas(data []byte, accessList types.AccessList, authList []types.Set
|
|||
|
||||
// Make sure we don't exceed uint64 for all data combinations
|
||||
nonZeroGas := params.TxDataNonZeroGasFrontier
|
||||
if isEIP2028 {
|
||||
if rules.IsIstanbul {
|
||||
nonZeroGas = params.TxDataNonZeroGasEIP2028
|
||||
}
|
||||
if (math.MaxUint64-gas)/nonZeroGas < nz {
|
||||
if (math.MaxUint64-gas.RegularGas)/nonZeroGas < nz {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += nz * nonZeroGas
|
||||
gas.RegularGas += nz * nonZeroGas
|
||||
|
||||
if (math.MaxUint64-gas)/params.TxDataZeroGas < z {
|
||||
if (math.MaxUint64-gas.RegularGas)/params.TxDataZeroGas < z {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += z * params.TxDataZeroGas
|
||||
gas.RegularGas += z * params.TxDataZeroGas
|
||||
|
||||
if isContractCreation && isEIP3860 {
|
||||
if isContractCreation && rules.IsShanghai {
|
||||
lenWords := toWordSize(dataLen)
|
||||
if (math.MaxUint64-gas)/params.InitCodeWordGas < lenWords {
|
||||
if (math.MaxUint64-gas.RegularGas)/params.InitCodeWordGas < lenWords {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += lenWords * params.InitCodeWordGas
|
||||
gas.RegularGas += lenWords * params.InitCodeWordGas
|
||||
}
|
||||
}
|
||||
if accessList != nil {
|
||||
addresses := uint64(len(accessList))
|
||||
storageKeys := uint64(accessList.StorageKeys())
|
||||
if (math.MaxUint64-gas)/params.TxAccessListAddressGas < addresses {
|
||||
if (math.MaxUint64-gas.RegularGas)/params.TxAccessListAddressGas < addresses {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += addresses * params.TxAccessListAddressGas
|
||||
if (math.MaxUint64-gas)/params.TxAccessListStorageKeyGas < storageKeys {
|
||||
gas.RegularGas += addresses * params.TxAccessListAddressGas
|
||||
if (math.MaxUint64-gas.RegularGas)/params.TxAccessListStorageKeyGas < storageKeys {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += storageKeys * params.TxAccessListStorageKeyGas
|
||||
gas.RegularGas += storageKeys * params.TxAccessListStorageKeyGas
|
||||
|
||||
// EIP-7981: access list data is charged in addition to the base charge.
|
||||
if isAmsterdam {
|
||||
if rules.IsAmsterdam {
|
||||
const (
|
||||
addressCost = common.AddressLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
|
||||
storageKeyCost = common.HashLength * params.TxCostFloorPerToken7976 * params.TxTokenPerNonZeroByte
|
||||
)
|
||||
if (math.MaxUint64-gas)/addressCost < addresses {
|
||||
if (math.MaxUint64-gas.RegularGas)/addressCost < addresses {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += addresses * addressCost
|
||||
if (math.MaxUint64-gas)/storageKeyCost < storageKeys {
|
||||
gas.RegularGas += addresses * addressCost
|
||||
if (math.MaxUint64-gas.RegularGas)/storageKeyCost < storageKeys {
|
||||
return vm.GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
gas += storageKeys * storageKeyCost
|
||||
gas.RegularGas += storageKeys * storageKeyCost
|
||||
}
|
||||
}
|
||||
if authList != nil {
|
||||
gas += uint64(len(authList)) * params.CallNewAccountGas
|
||||
}
|
||||
return vm.GasCosts{RegularGas: gas}, nil
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
// FloorDataGas computes the minimum gas required for a transaction based on its data tokens (EIP-7623).
|
||||
|
|
@ -338,12 +349,11 @@ func ApplyMessage(evm *vm.EVM, msg *Message, gp *GasPool) (*ExecutionResult, err
|
|||
// 5. Run Script section
|
||||
// 6. Derive new state root
|
||||
type stateTransition struct {
|
||||
gp *GasPool
|
||||
msg *Message
|
||||
initialBudget vm.GasBudget
|
||||
gasRemaining vm.GasBudget
|
||||
state vm.StateDB
|
||||
evm *vm.EVM
|
||||
gp *GasPool
|
||||
msg *Message
|
||||
gasRemaining vm.GasBudget
|
||||
state vm.StateDB
|
||||
evm *vm.EVM
|
||||
}
|
||||
|
||||
// newStateTransition initialises and returns a new state transition object.
|
||||
|
|
@ -364,6 +374,24 @@ func (st *stateTransition) to() common.Address {
|
|||
return *st.msg.To
|
||||
}
|
||||
|
||||
// buyGas pre-pays gas from the sender's balance and initializes the
|
||||
// transaction's gas budget. It is invoked at the tail of preCheck.
|
||||
//
|
||||
// The balance requirement is the worst-case ETH the tx may need to lock
|
||||
// up: `msg.GasLimit × max(msg.GasPrice, msg.GasFeeCap) + msg.Value`,
|
||||
// plus `blobGas × msg.BlobGasFeeCap` under Cancun. Insufficient balance
|
||||
// returns ErrInsufficientFunds. After the check, the sender is actually
|
||||
// debited `msg.GasLimit × msg.GasPrice` (plus `blobGas × blobBaseFee`
|
||||
// under Cancun), the cap-vs-tip differential is settled at tx end.
|
||||
//
|
||||
// The gas budget is seeded into both `initialBudget` (frozen snapshot
|
||||
// for tx-end accounting) and `gasRemaining` (live running balance):
|
||||
//
|
||||
// - Pre-Amsterdam: one-dimensional regular budget equal to
|
||||
// `msg.GasLimit`; the state-gas reservoir is zero.
|
||||
// - Amsterdam+ (EIP-8037): two-dimensional budget. Regular gas is
|
||||
// capped at `MaxTxGas` (EIP-7825, 16_777_216); any excess from
|
||||
// `msg.GasLimit` above that cap becomes the state-gas reservoir.
|
||||
func (st *stateTransition) buyGas() error {
|
||||
mgval := new(uint256.Int).SetUint64(st.msg.GasLimit)
|
||||
_, overflow := mgval.MulOverflow(mgval, st.msg.GasPrice)
|
||||
|
|
@ -416,22 +444,53 @@ func (st *stateTransition) buyGas() error {
|
|||
if have, want := st.state.GetBalance(st.msg.From), balanceCheck; have.Cmp(want) < 0 {
|
||||
return fmt.Errorf("%w: address %v have %v want %v", ErrInsufficientFunds, st.msg.From.Hex(), have, want)
|
||||
}
|
||||
if err := st.gp.SubGas(st.msg.GasLimit); err != nil {
|
||||
isAmsterdam := st.evm.ChainConfig().IsAmsterdam(st.evm.Context.BlockNumber, st.evm.Context.Time)
|
||||
|
||||
// Reserve the gas budget in the block gas pool
|
||||
var err error
|
||||
if isAmsterdam {
|
||||
err = st.gp.CheckGasAmsterdam(min(st.msg.GasLimit, params.MaxTxGas), st.msg.GasLimit)
|
||||
} else {
|
||||
err = st.gp.CheckGasLegacy(st.msg.GasLimit)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if st.evm.Config.Tracer.HasGasHook() {
|
||||
empty := vm.GasBudget{}
|
||||
initial := vm.NewGasBudget(st.msg.GasLimit)
|
||||
st.evm.Config.Tracer.EmitGasChange(empty.AsTracing(), initial.AsTracing(), tracing.GasChangeTxInitialBalance)
|
||||
// After Amsterdam we limit the regular gas to 16M, the data gas to the transaction limit
|
||||
limit := st.msg.GasLimit
|
||||
if isAmsterdam {
|
||||
limit = min(st.msg.GasLimit, params.MaxTxGas)
|
||||
}
|
||||
st.gasRemaining = vm.NewGasBudget(st.msg.GasLimit)
|
||||
st.initialBudget = st.gasRemaining.Copy()
|
||||
st.gasRemaining = vm.NewGasBudget(limit, st.msg.GasLimit-limit)
|
||||
|
||||
if st.evm.Config.Tracer.HasGasHook() {
|
||||
st.evm.Config.Tracer.EmitGasChange(tracing.Gas{}, st.gasRemaining.AsTracing(), tracing.GasChangeTxInitialBalance)
|
||||
}
|
||||
// Deduct the gas cost from the sender's balance
|
||||
st.state.SubBalance(st.msg.From, mgval, tracing.BalanceDecreaseGasBuy)
|
||||
return nil
|
||||
}
|
||||
|
||||
// preCheck performs all pre-execution validation that does not require
|
||||
// the EVM to run, then ends by calling buyGas to lock in the gas budget.
|
||||
// It returns a consensus error if any of the following fail:
|
||||
//
|
||||
// - Sender nonce matches state and is not at 2^64-1 (EIP-2681).
|
||||
// - EIP-7825 per-tx gas-limit cap on Osaka chains pre-Amsterdam
|
||||
// (the cap also bounds the regular dimension after Amsterdam, but
|
||||
// it is enforced there via the two-dimensional budget in buyGas).
|
||||
// - EIP-3607 sender-is-EOA, allowing accounts whose only code is an
|
||||
// EIP-7702 delegation designator.
|
||||
// - EIP-1559 fee-cap, tip-cap and base-fee constraints (London+).
|
||||
// - Blob-tx structural checks: non-nil `To`, non-empty hash list,
|
||||
// valid KZG versioned hashes, count below `BlobTxMaxBlobs` (Osaka+).
|
||||
// - Blob fee-cap not below the current blob base fee (Cancun+).
|
||||
// - EIP-7702 set-code-tx shape: non-nil `To` and non-empty
|
||||
// authorization list.
|
||||
//
|
||||
// The SkipNonceChecks / SkipTransactionChecks / NoBaseFee flags bypass
|
||||
// subsets of these checks for simulation paths (eth_call, eth_estimateGas).
|
||||
func (st *stateTransition) preCheck() error {
|
||||
// Only check transactions that are not fake
|
||||
msg := st.msg
|
||||
|
|
@ -449,11 +508,13 @@ func (st *stateTransition) preCheck() error {
|
|||
msg.From.Hex(), stNonce)
|
||||
}
|
||||
}
|
||||
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)
|
||||
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 {
|
||||
// Verify tx gas limit does not exceed EIP-7825 cap.
|
||||
if isOsaka && !isAmsterdam && msg.GasLimit > params.MaxTxGas {
|
||||
if !isAmsterdam && isOsaka && msg.GasLimit > params.MaxTxGas {
|
||||
return fmt.Errorf("%w (cap: %d, tx: %d)", ErrGasLimitTooHigh, params.MaxTxGas, msg.GasLimit)
|
||||
}
|
||||
// Make sure the sender is an EOA
|
||||
|
|
@ -527,40 +588,33 @@ func (st *stateTransition) preCheck() error {
|
|||
return st.buyGas()
|
||||
}
|
||||
|
||||
// execute will transition the state by applying the current message and
|
||||
// returning the evm execution result with following fields.
|
||||
// execute transitions the state by applying the current message and
|
||||
// returns the EVM execution result with the following fields:
|
||||
//
|
||||
// - used gas: total gas used (including gas being refunded)
|
||||
// - returndata: the returned data from evm
|
||||
// - concrete execution error: various EVM errors which abort the execution, e.g.
|
||||
// ErrOutOfGas, ErrExecutionReverted
|
||||
// - used gas: total gas used, including gas refunded
|
||||
// - peak used gas: maximum gas used before applying refunds
|
||||
// - returndata: data returned by the EVM
|
||||
// - execution error: EVM-level errors that abort execution, such as
|
||||
// ErrOutOfGas or ErrExecutionReverted
|
||||
//
|
||||
// However if any consensus issue encountered, return the error directly with
|
||||
// nil evm execution result.
|
||||
// If a consensus error is encountered, it is returned directly with a
|
||||
// nil EVM execution result.
|
||||
func (st *stateTransition) execute() (*ExecutionResult, error) {
|
||||
// First check this message satisfies all consensus rules before
|
||||
// applying the message. The rules include these clauses
|
||||
//
|
||||
// 1. the nonce of the message caller is correct
|
||||
// 2. caller has enough balance to cover transaction fee(gaslimit * gasprice)
|
||||
// 3. the amount of gas required is available in the block
|
||||
// 4. the purchased gas is enough to cover intrinsic usage
|
||||
// 5. there is no overflow when calculating intrinsic gas
|
||||
// 6. caller has enough balance to cover asset transfer for **topmost** call
|
||||
|
||||
// Check clauses 1-3, buy gas if everything is correct
|
||||
// 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 (
|
||||
msg = st.msg
|
||||
rules = st.evm.ChainConfig().Rules(st.evm.Context.BlockNumber, st.evm.Context.Random != nil, st.evm.Context.Time)
|
||||
contractCreation = msg.To == nil
|
||||
floorDataGas uint64
|
||||
)
|
||||
// Check clauses 4-5, subtract intrinsic gas if everything is correct
|
||||
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam)
|
||||
cost, err := IntrinsicGas(msg.Data, msg.AccessList, msg.SetCodeAuthorizations, contractCreation, rules, st.evm.Context.CostPerStateByte)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -571,15 +625,24 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
if st.evm.Config.Tracer.HasGasHook() {
|
||||
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxIntrinsicGas)
|
||||
}
|
||||
// Gas limit suffices for the floor data cost (EIP-7623)
|
||||
|
||||
// Validate the EIP-7623 calldata floor against the gas limit. The floor inflates
|
||||
// the total gas usage at tx end, so the gas limit must be sufficient to cover that.
|
||||
if rules.IsPrague {
|
||||
floorDataGas, err = FloorDataGas(rules, msg.Data, msg.AccessList)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Make sure the transaction has sufficient gas allowance to
|
||||
// pay the floor cost.
|
||||
if msg.GasLimit < floorDataGas {
|
||||
return nil, fmt.Errorf("%w: have %d, want %d", ErrFloorDataGas, msg.GasLimit, floorDataGas)
|
||||
}
|
||||
// In Amsterdam, the transaction gas limit is allowed to exceed
|
||||
// params.MaxTxGas, but the calldata floor cost is capped by it.
|
||||
if rules.IsAmsterdam && max(cost.RegularGas, floorDataGas) > params.MaxTxGas {
|
||||
return nil, fmt.Errorf("%w: regular intrisic cost %v, floor: %v", ErrFloorDataGas, cost.RegularGas, floorDataGas)
|
||||
}
|
||||
}
|
||||
|
||||
if rules.IsEIP4762 {
|
||||
|
|
@ -590,7 +653,8 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
}
|
||||
}
|
||||
|
||||
// Check clause 6
|
||||
// Top-call affordability, the sender must still be able to cover the value
|
||||
// transfer of the top frame after gas pre-pay.
|
||||
value := msg.Value
|
||||
if value == nil {
|
||||
value = new(uint256.Int)
|
||||
|
|
@ -599,36 +663,43 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
return nil, fmt.Errorf("%w: address %v", ErrInsufficientFundsForTransfer, msg.From.Hex())
|
||||
}
|
||||
|
||||
// Check whether the init code size has been exceeded.
|
||||
if contractCreation {
|
||||
if err := vm.CheckMaxInitCodeSize(&rules, uint64(len(msg.Data))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Execute the preparatory steps for state transition which includes:
|
||||
// - prepare accessList(post-berlin)
|
||||
// - reset transient storage(EIP-1153)
|
||||
// - enable block-level accessList construction (EIP-7928)
|
||||
st.state.Prepare(rules, msg.From, st.evm.Context.Coinbase, msg.To, vm.ActivePrecompiles(rules), msg.AccessList)
|
||||
|
||||
// Execute the top-most frame
|
||||
var (
|
||||
ret []byte
|
||||
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
|
||||
ret []byte
|
||||
vmerr error // vm errors do not effect consensus and are therefore not assigned to err
|
||||
result vm.GasBudget
|
||||
|
||||
// Capture the forwarded regular-gas amount BEFORE ForwardAll consumes
|
||||
// it, so Absorb can back out state-gas spillover from UsedRegularGas
|
||||
// per EIP-8037.
|
||||
forwarded = st.gasRemaining.RegularGas
|
||||
)
|
||||
if contractCreation {
|
||||
ret, _, st.gasRemaining, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining, value)
|
||||
// Check whether the init code size has been exceeded.
|
||||
if err := vm.CheckMaxInitCodeSize(&rules, uint64(len(msg.Data))); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Execute the transaction's creation.
|
||||
ret, _, result, vmerr = st.evm.Create(msg.From, msg.Data, st.gasRemaining.ForwardAll(), value)
|
||||
st.gasRemaining.Absorb(result, forwarded)
|
||||
|
||||
// If the contract creation failed, refund the account-creation state
|
||||
// gas pre-charged in IntrinsicGas.
|
||||
if rules.IsAmsterdam && vmerr != nil {
|
||||
st.gasRemaining.RefundState(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
|
||||
}
|
||||
} else {
|
||||
// Increment the nonce for the next transaction.
|
||||
st.state.SetNonce(msg.From, st.state.GetNonce(msg.From)+1, tracing.NonceChangeEoACall)
|
||||
|
||||
// Apply EIP-7702 authorizations.
|
||||
if msg.SetCodeAuthorizations != nil {
|
||||
for _, auth := range msg.SetCodeAuthorizations {
|
||||
// Note errors are ignored, we simply skip invalid authorizations here.
|
||||
st.applyAuthorization(&auth)
|
||||
}
|
||||
}
|
||||
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
|
||||
|
|
@ -638,44 +709,18 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
if addr, ok := types.ParseDelegation(st.state.GetCode(*msg.To)); ok {
|
||||
st.state.AddAddressToAccessList(addr)
|
||||
}
|
||||
|
||||
// Execute the transaction's call.
|
||||
ret, st.gasRemaining, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining, value)
|
||||
ret, result, vmerr = st.evm.Call(msg.From, st.to(), msg.Data, st.gasRemaining.ForwardAll(), value)
|
||||
st.gasRemaining.Absorb(result, forwarded)
|
||||
}
|
||||
|
||||
// Record the gas used excluding gas refunds. This value represents the actual
|
||||
// gas allowance required to complete execution.
|
||||
peakGasUsed := st.gasUsed()
|
||||
|
||||
// Compute refund counter, capped to a refund quotient.
|
||||
st.gasRemaining.Refund(st.calcRefund())
|
||||
|
||||
if rules.IsPrague {
|
||||
// After EIP-7623: Data-heavy transactions pay the floor gas.
|
||||
if used := st.gasUsed(); used < floorDataGas {
|
||||
prior, _ := st.gasRemaining.Charge(vm.GasCosts{RegularGas: floorDataGas - used})
|
||||
if st.evm.Config.Tracer.HasGasHook() {
|
||||
st.evm.Config.Tracer.EmitGasChange(prior.AsTracing(), st.gasRemaining.AsTracing(), tracing.GasChangeTxDataFloor)
|
||||
}
|
||||
}
|
||||
if peakGasUsed < floorDataGas {
|
||||
peakGasUsed = floorDataGas
|
||||
}
|
||||
}
|
||||
// Return gas to the user
|
||||
st.returnGas()
|
||||
|
||||
// Return gas to the gas pool
|
||||
if rules.IsAmsterdam {
|
||||
// Refund is excluded for returning
|
||||
err = st.gp.ReturnGas(st.initialBudget.RegularGas-peakGasUsed, st.gasUsed())
|
||||
} else {
|
||||
// Refund is included for returning
|
||||
err = st.gp.ReturnGas(st.gasRemaining.RegularGas, st.gasUsed())
|
||||
}
|
||||
// Settle down the gas usage and refund the ETH back if any remaining
|
||||
gasUsed, peakUsed, err := st.settleGas(rules, floorDataGas)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Pay the effective transaction fee to the specific coinbase
|
||||
effectiveTip := msg.GasPrice
|
||||
if rules.IsLondon {
|
||||
baseFee, overflow := uint256.FromBig(st.evm.Context.BaseFee)
|
||||
|
|
@ -684,13 +729,12 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
}
|
||||
effectiveTip = new(uint256.Int).Sub(msg.GasPrice, baseFee)
|
||||
}
|
||||
|
||||
if st.evm.Config.NoBaseFee && msg.GasFeeCap.Sign() == 0 && msg.GasTipCap.Sign() == 0 {
|
||||
// Skip fee payment when NoBaseFee is set and the fee fields
|
||||
// are 0. This avoids a negative effectiveTip being applied to
|
||||
// the coinbase when simulating calls.
|
||||
} else {
|
||||
fee := new(uint256.Int).SetUint64(st.gasUsed())
|
||||
fee := new(uint256.Int).SetUint64(gasUsed)
|
||||
fee.Mul(fee, effectiveTip)
|
||||
st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
|
||||
|
||||
|
|
@ -699,19 +743,105 @@ func (st *stateTransition) execute() (*ExecutionResult, error) {
|
|||
st.evm.AccessEvents.AddAccount(st.evm.Context.Coinbase, true, math.MaxUint64)
|
||||
}
|
||||
}
|
||||
|
||||
// EIP-7708: Emit the ETH-burn logs
|
||||
if rules.IsAmsterdam {
|
||||
for _, log := range st.evm.StateDB.LogsForBurnAccounts() {
|
||||
st.evm.StateDB.AddLog(log)
|
||||
}
|
||||
}
|
||||
return &ExecutionResult{
|
||||
UsedGas: st.gasUsed(),
|
||||
MaxUsedGas: peakGasUsed,
|
||||
UsedGas: gasUsed,
|
||||
MaxUsedGas: peakUsed,
|
||||
Err: vmerr,
|
||||
ReturnData: ret,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// settleGas finalizes the per-tx gas accounting after EVM execution:
|
||||
//
|
||||
// - Snapshots the EIP-8037 block-level 2D figures (tx_regular_gas,
|
||||
// tx_state_gas) before any refund or floor:
|
||||
//
|
||||
// tx_gas_used_before_refund = tx.gas - gas_left - state_gas_reservoir
|
||||
// tx_state_gas = state_gas_used
|
||||
// tx_regular_gas = tx_gas_used_before_refund - tx_state_gas
|
||||
//
|
||||
// - Computes the receipt scalar tx_gas_used by applying the EIP-3529
|
||||
// refund (capped at tx_gas_used_before_refund/5) and the EIP-7623
|
||||
// calldata floor:
|
||||
//
|
||||
// tx_gas_used = max(tx_gas_used_before_refund - tx_gas_refund, calldata_floor)
|
||||
//
|
||||
// - Charges the block gas pool (2D under Amsterdam, scalar pre-Amsterdam).
|
||||
//
|
||||
// - Refunds the leftover gas to the sender as ETH.
|
||||
//
|
||||
// Returns the receipt-level tx_gas_used and the pre-refund peak (consumed
|
||||
// by gas-estimation callers via ExecutionResult.MaxUsedGas). UsedStateGas
|
||||
// should never become negative in the top-most frame, since state-gas
|
||||
// refunds occur only when state creation is reverted within the same
|
||||
// transaction and clearing pre-existing state is never refunded.
|
||||
func (st *stateTransition) settleGas(rules params.Rules, floorDataGas uint64) (gasUsed, peakUsed uint64, err error) {
|
||||
if st.gasRemaining.UsedStateGas < 0 {
|
||||
return 0, 0, fmt.Errorf("negative topmost frame state gas usage, %d", st.gasRemaining.UsedStateGas)
|
||||
}
|
||||
txStateGas := uint64(st.gasRemaining.UsedStateGas)
|
||||
|
||||
// EIP-8037:
|
||||
// tx_gas_used_before_refund = tx.gas - tx_output.gas_left - tx_output.state_gas_reservoir
|
||||
// tx_state_gas = intrinsic_state_gas + tx_output.execution_state_gas_used
|
||||
// tx_regular_gas = tx_gas_used_before_refund - tx_state_gas
|
||||
gasLeft := st.gasRemaining.RegularGas + st.gasRemaining.StateGas
|
||||
gasUsedBeforeRefund := st.msg.GasLimit - gasLeft
|
||||
|
||||
if gasUsedBeforeRefund < txStateGas {
|
||||
return 0, 0, fmt.Errorf("negative topmost frame regular gas usage, total: %d, state: %d", gasUsedBeforeRefund, txStateGas)
|
||||
}
|
||||
txRegularGas := gasUsedBeforeRefund - txStateGas
|
||||
|
||||
// EIP-3529: tx_gas_refund = min(tx_gas_used_before_refund/5, refund_counter).
|
||||
refund := st.calcRefund(gasUsedBeforeRefund)
|
||||
if st.evm.Config.Tracer.HasGasHook() {
|
||||
st.evm.Config.Tracer.EmitGasChange(tracing.Gas{Regular: gasLeft}, tracing.Gas{Regular: gasLeft + refund}, tracing.GasChangeTxRefunds)
|
||||
}
|
||||
gasLeft += refund
|
||||
gasUsed = gasUsedBeforeRefund - refund
|
||||
|
||||
// EIP-7623: tx_gas_used = max(tx_gas_used_after_refund, calldata_floor).
|
||||
peakUsed = gasUsedBeforeRefund
|
||||
if rules.IsPrague && gasUsed < floorDataGas {
|
||||
diff := floorDataGas - gasUsed
|
||||
if st.evm.Config.Tracer.HasGasHook() {
|
||||
st.evm.Config.Tracer.EmitGasChange(tracing.Gas{Regular: gasLeft}, tracing.Gas{Regular: gasLeft - diff}, tracing.GasChangeTxDataFloor)
|
||||
}
|
||||
gasLeft -= diff
|
||||
gasUsed = floorDataGas
|
||||
peakUsed = max(peakUsed, floorDataGas)
|
||||
}
|
||||
|
||||
if rules.IsAmsterdam {
|
||||
if err = st.gp.ChargeGasAmsterdam(txRegularGas, txStateGas, gasUsed); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
} else {
|
||||
if err = st.gp.ChargeGasLegacy(gasLeft, gasUsed); err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
}
|
||||
|
||||
// Refund leftover gas to the sender as ETH.
|
||||
if gasLeft > 0 {
|
||||
refund := new(uint256.Int).Mul(uint256.NewInt(gasLeft), st.msg.GasPrice)
|
||||
st.state.AddBalance(st.msg.From, refund, tracing.BalanceIncreaseGasReturn)
|
||||
|
||||
if st.evm.Config.Tracer.HasGasHook() {
|
||||
st.evm.Config.Tracer.EmitGasChange(tracing.Gas{Regular: gasLeft}, tracing.Gas{}, tracing.GasChangeTxLeftOverReturned)
|
||||
}
|
||||
}
|
||||
return gasUsed, peakUsed, nil
|
||||
}
|
||||
|
||||
// validateAuthorization validates an EIP-7702 authorization against the state.
|
||||
func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorization) (authority common.Address, err error) {
|
||||
// Verify chain ID is null or equal to current chain ID.
|
||||
|
|
@ -743,72 +873,93 @@ func (st *stateTransition) validateAuthorization(auth *types.SetCodeAuthorizatio
|
|||
return authority, nil
|
||||
}
|
||||
|
||||
// applyAuthorization applies an EIP-7702 code delegation to the state.
|
||||
func (st *stateTransition) applyAuthorization(auth *types.SetCodeAuthorization) error {
|
||||
// applyAuthorization applies an EIP-7702 code delegation to the state and,
|
||||
// under EIP-8037, reconciles the per-authorization intrinsic state-gas pre-
|
||||
// charge so that, per authority:
|
||||
//
|
||||
// - the account portion (AccountCreationSize × CPSB) is charged at most
|
||||
// once, and only when the account did not exist before the tx
|
||||
//
|
||||
// - the delegation-indicator portion (AuthorizationCreationSize × CPSB) is
|
||||
// charged at most once, and only when the authority ends the tx delegated
|
||||
// having started it undelegated.
|
||||
func (st *stateTransition) applyAuthorization(rules params.Rules, auth *types.SetCodeAuthorization, delegates map[common.Address]bool) error {
|
||||
authority, err := st.validateAuthorization(auth)
|
||||
if err != nil {
|
||||
if rules.IsAmsterdam {
|
||||
st.gasRemaining.RefundState((params.AccountCreationSize + params.AuthorizationCreationSize) * st.evm.Context.CostPerStateByte)
|
||||
}
|
||||
return err
|
||||
}
|
||||
prevDelegation, curDelegated := types.ParseDelegation(st.state.GetCode(authority))
|
||||
|
||||
// If the account already exists in state, refund the new account cost
|
||||
// charged in the intrinsic calculation.
|
||||
if st.state.Exist(authority) {
|
||||
st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas)
|
||||
if !rules.IsAmsterdam {
|
||||
if st.state.Exist(authority) {
|
||||
st.state.AddRefund(params.CallNewAccountGas - params.TxAuthTupleGas)
|
||||
}
|
||||
} else {
|
||||
if st.state.Exist(authority) {
|
||||
st.gasRemaining.RefundState(params.AccountCreationSize * st.evm.Context.CostPerStateByte)
|
||||
}
|
||||
authBase := params.AuthorizationCreationSize * st.evm.Context.CostPerStateByte
|
||||
|
||||
preDelegated, ok := delegates[authority]
|
||||
if !ok {
|
||||
preDelegated = curDelegated
|
||||
delegates[authority] = preDelegated
|
||||
}
|
||||
if auth.Address == (common.Address{}) {
|
||||
// Clearing writes no indicator, refill this auth's state charge.
|
||||
st.gasRemaining.RefundState(authBase)
|
||||
|
||||
// The indicator was created by an earlier auth within the same
|
||||
// transaction, refill the state charge as it's no longer justified.
|
||||
if curDelegated && !preDelegated {
|
||||
st.gasRemaining.RefundState(authBase)
|
||||
}
|
||||
} else if curDelegated || preDelegated {
|
||||
// The 23-byte slot is already occupied, overwriting it writes no
|
||||
// new bytes, refill the state charge.
|
||||
st.gasRemaining.RefundState(authBase)
|
||||
}
|
||||
}
|
||||
|
||||
// Update nonce and account code.
|
||||
st.state.SetNonce(authority, auth.Nonce+1, tracing.NonceChangeAuthorization)
|
||||
|
||||
// Delegation to zero address means clear.
|
||||
if auth.Address == (common.Address{}) {
|
||||
// Delegation to zero address means clear.
|
||||
st.state.SetCode(authority, nil, tracing.CodeChangeAuthorizationClear)
|
||||
if curDelegated {
|
||||
st.state.SetCode(authority, nil, tracing.CodeChangeAuthorizationClear)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Otherwise install delegation to auth.Address.
|
||||
st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization)
|
||||
|
||||
// Install delegation to auth.Address if the delegation changed
|
||||
if !curDelegated || auth.Address != prevDelegation {
|
||||
st.state.SetCode(authority, types.AddressToDelegation(auth.Address), tracing.CodeChangeAuthorization)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// calcRefund computes refund counter, capped to a refund quotient.
|
||||
func (st *stateTransition) calcRefund() vm.GasBudget {
|
||||
var refund uint64
|
||||
if !st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
|
||||
// Before EIP-3529: refunds were capped to gasUsed / 2
|
||||
refund = st.gasUsed() / params.RefundQuotient
|
||||
} else {
|
||||
// After EIP-3529: refunds are capped to gasUsed / 5
|
||||
refund = st.gasUsed() / params.RefundQuotientEIP3529
|
||||
// applyAuthorizations applies an EIP-7702 code delegation to the state.
|
||||
func (st *stateTransition) applyAuthorizations(rules params.Rules, auths []types.SetCodeAuthorization) {
|
||||
preDelegated := make(map[common.Address]bool)
|
||||
for _, auth := range auths {
|
||||
st.applyAuthorization(rules, &auth, preDelegated)
|
||||
}
|
||||
}
|
||||
|
||||
// calcRefund computes the EIP-3529 refund cap against tx_gas_used_before_refund.
|
||||
func (st *stateTransition) calcRefund(gasUsedBeforeRefund uint64) uint64 {
|
||||
quotient := params.RefundQuotient
|
||||
if st.evm.ChainConfig().IsLondon(st.evm.Context.BlockNumber) {
|
||||
quotient = params.RefundQuotientEIP3529
|
||||
}
|
||||
refund := gasUsedBeforeRefund / quotient
|
||||
if refund > st.state.GetRefund() {
|
||||
refund = st.state.GetRefund()
|
||||
}
|
||||
if refund > 0 && st.evm.Config.Tracer.HasGasHook() {
|
||||
after := st.gasRemaining
|
||||
after.RegularGas += refund
|
||||
|
||||
st.evm.Config.Tracer.EmitGasChange(st.gasRemaining.AsTracing(), after.AsTracing(), tracing.GasChangeTxRefunds)
|
||||
}
|
||||
return vm.NewGasBudget(refund)
|
||||
}
|
||||
|
||||
// returnGas returns ETH for remaining gas,
|
||||
// exchanged at the original rate.
|
||||
func (st *stateTransition) returnGas() {
|
||||
remaining := uint256.NewInt(st.gasRemaining.RegularGas)
|
||||
remaining.Mul(remaining, st.msg.GasPrice)
|
||||
st.state.AddBalance(st.msg.From, remaining, tracing.BalanceIncreaseGasReturn)
|
||||
|
||||
if st.gasRemaining.RegularGas > 0 && st.evm.Config.Tracer.HasGasHook() {
|
||||
after := st.gasRemaining
|
||||
after.RegularGas = 0
|
||||
st.evm.Config.Tracer.EmitGasChange(st.gasRemaining.AsTracing(), after.AsTracing(), tracing.GasChangeTxLeftOverReturned)
|
||||
}
|
||||
}
|
||||
|
||||
// gasUsed returns the amount of gas used up by the state transition.
|
||||
func (st *stateTransition) gasUsed() uint64 {
|
||||
return st.gasRemaining.Used(st.initialBudget)
|
||||
return refund
|
||||
}
|
||||
|
||||
// blobGasUsed returns the amount of blob gas used by the message.
|
||||
|
|
|
|||
|
|
@ -155,50 +155,50 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
isEIP2028 bool
|
||||
isEIP3860 bool
|
||||
isAmsterdam bool
|
||||
want uint64
|
||||
want vm.GasCosts
|
||||
}{
|
||||
{
|
||||
name: "frontier/empty-call",
|
||||
want: params.TxGas,
|
||||
want: vm.GasCosts{RegularGas: params.TxGas},
|
||||
},
|
||||
{
|
||||
name: "frontier/contract-creation-pre-homestead",
|
||||
creation: true,
|
||||
isHomestead: false,
|
||||
// pre-homestead, contract creation still uses TxGas
|
||||
want: params.TxGas,
|
||||
want: vm.GasCosts{RegularGas: params.TxGas},
|
||||
},
|
||||
{
|
||||
name: "homestead/contract-creation",
|
||||
creation: true,
|
||||
isHomestead: true,
|
||||
want: params.TxGasContractCreation,
|
||||
want: vm.GasCosts{RegularGas: params.TxGasContractCreation},
|
||||
},
|
||||
{
|
||||
name: "frontier/non-zero-data",
|
||||
data: bytes.Repeat([]byte{0xff}, 100),
|
||||
// 100 nz bytes * 68 (frontier)
|
||||
want: params.TxGas + 100*params.TxDataNonZeroGasFrontier,
|
||||
want: vm.GasCosts{RegularGas: params.TxGas + 100*params.TxDataNonZeroGasFrontier},
|
||||
},
|
||||
{
|
||||
name: "istanbul/non-zero-data",
|
||||
data: bytes.Repeat([]byte{0xff}, 100),
|
||||
isEIP2028: true,
|
||||
// 100 nz bytes * 16 (post-EIP2028)
|
||||
want: params.TxGas + 100*params.TxDataNonZeroGasEIP2028,
|
||||
want: vm.GasCosts{RegularGas: params.TxGas + 100*params.TxDataNonZeroGasEIP2028},
|
||||
},
|
||||
{
|
||||
name: "istanbul/zero-data",
|
||||
data: bytes.Repeat([]byte{0x00}, 100),
|
||||
isEIP2028: true,
|
||||
// 100 zero bytes * 4
|
||||
want: params.TxGas + 100*params.TxDataZeroGas,
|
||||
want: vm.GasCosts{RegularGas: params.TxGas + 100*params.TxDataZeroGas},
|
||||
},
|
||||
{
|
||||
name: "istanbul/mixed-data",
|
||||
data: append(bytes.Repeat([]byte{0x00}, 50), bytes.Repeat([]byte{0xff}, 50)...),
|
||||
isEIP2028: true,
|
||||
want: params.TxGas + 50*params.TxDataZeroGas + 50*params.TxDataNonZeroGasEIP2028,
|
||||
want: vm.GasCosts{RegularGas: params.TxGas + 50*params.TxDataZeroGas + 50*params.TxDataNonZeroGasEIP2028},
|
||||
},
|
||||
{
|
||||
name: "shanghai/init-code-word-gas",
|
||||
|
|
@ -208,7 +208,7 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
isEIP2028: true,
|
||||
isEIP3860: true,
|
||||
// TxGasContractCreation + 64 zero bytes * 4 + 2 words * 2
|
||||
want: params.TxGasContractCreation + 64*params.TxDataZeroGas + 2*params.InitCodeWordGas,
|
||||
want: vm.GasCosts{RegularGas: params.TxGasContractCreation + 64*params.TxDataZeroGas + 2*params.InitCodeWordGas},
|
||||
},
|
||||
{
|
||||
name: "shanghai/init-code-non-multiple-of-32",
|
||||
|
|
@ -217,7 +217,7 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
isHomestead: true,
|
||||
isEIP2028: true,
|
||||
isEIP3860: true,
|
||||
want: params.TxGasContractCreation + 33*params.TxDataZeroGas + 2*params.InitCodeWordGas,
|
||||
want: vm.GasCosts{RegularGas: params.TxGasContractCreation + 33*params.TxDataZeroGas + 2*params.InitCodeWordGas},
|
||||
},
|
||||
{
|
||||
name: "berlin/access-list",
|
||||
|
|
@ -227,7 +227,7 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
},
|
||||
isEIP2028: true,
|
||||
// 2 addrs * 2400 + 3 keys * 1900
|
||||
want: params.TxGas + 2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas,
|
||||
want: vm.GasCosts{RegularGas: params.TxGas + 2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas},
|
||||
},
|
||||
{
|
||||
name: "amsterdam/access-list-extra-cost",
|
||||
|
|
@ -238,9 +238,9 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
isEIP2028: true,
|
||||
isAmsterdam: true,
|
||||
// base access-list charge + EIP-7981 extra
|
||||
want: params.TxGas +
|
||||
want: vm.GasCosts{RegularGas: params.TxGas +
|
||||
2*params.TxAccessListAddressGas + 3*params.TxAccessListStorageKeyGas +
|
||||
2*amsterdamAddressCost + 3*amsterdamStorageKeyCost,
|
||||
2*amsterdamAddressCost + 3*amsterdamStorageKeyCost},
|
||||
},
|
||||
{
|
||||
name: "prague/auth-list",
|
||||
|
|
@ -250,8 +250,54 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
{Address: addr1},
|
||||
},
|
||||
isEIP2028: true,
|
||||
// 3 auths * 25000
|
||||
want: params.TxGas + 3*params.CallNewAccountGas,
|
||||
// 3 auths * 25000 (pre-Amsterdam: CallNewAccountGas per auth tuple)
|
||||
want: vm.GasCosts{RegularGas: params.TxGas + 3*params.CallNewAccountGas},
|
||||
},
|
||||
{
|
||||
name: "amsterdam/contract-creation-empty",
|
||||
creation: true,
|
||||
isHomestead: true,
|
||||
isEIP2028: true,
|
||||
isAmsterdam: true,
|
||||
// EIP-8037: creation regular gas is TxGas + CreateGasAmsterdam (not TxGasContractCreation),
|
||||
// and account-creation cost is moved to state gas.
|
||||
want: vm.GasCosts{
|
||||
RegularGas: params.TxGas + params.CreateGasAmsterdam,
|
||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "amsterdam/contract-creation-init-code",
|
||||
data: bytes.Repeat([]byte{0x00}, 64), // 2 words of init code
|
||||
creation: true,
|
||||
isHomestead: true,
|
||||
isEIP2028: true,
|
||||
isEIP3860: true, // Shanghai gates init-code word gas
|
||||
isAmsterdam: true,
|
||||
want: vm.GasCosts{
|
||||
RegularGas: params.TxGas + params.CreateGasAmsterdam +
|
||||
64*params.TxDataZeroGas + 2*params.InitCodeWordGas,
|
||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "amsterdam/contract-creation-with-access-list",
|
||||
data: bytes.Repeat([]byte{0xff}, 32), // 1 word of non-zero init code
|
||||
accessList: types.AccessList{
|
||||
{Address: addr1, StorageKeys: []common.Hash{key1}},
|
||||
},
|
||||
creation: true,
|
||||
isHomestead: true,
|
||||
isEIP2028: true,
|
||||
isEIP3860: true,
|
||||
isAmsterdam: true,
|
||||
want: vm.GasCosts{
|
||||
RegularGas: params.TxGas + params.CreateGasAmsterdam +
|
||||
32*params.TxDataNonZeroGasEIP2028 + 1*params.InitCodeWordGas +
|
||||
1*params.TxAccessListAddressGas + 1*params.TxAccessListStorageKeyGas +
|
||||
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost,
|
||||
StateGas: params.AccountCreationSize * params.CostPerStateByte,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "amsterdam/combined",
|
||||
|
|
@ -264,23 +310,34 @@ func TestIntrinsicGas(t *testing.T) {
|
|||
},
|
||||
isEIP2028: true,
|
||||
isAmsterdam: true,
|
||||
want: params.TxGas +
|
||||
100*params.TxDataNonZeroGasEIP2028 +
|
||||
1*params.TxAccessListAddressGas + 1*params.TxAccessListStorageKeyGas +
|
||||
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost +
|
||||
1*params.CallNewAccountGas,
|
||||
// EIP-8037 splits the auth-tuple charge into regular + state gas:
|
||||
// regular: TxAuthTupleRegularGas (7500) per auth
|
||||
// state: (AuthorizationCreationSize + AccountCreationSize) * CostPerStateByte per auth
|
||||
want: vm.GasCosts{
|
||||
RegularGas: params.TxGas +
|
||||
100*params.TxDataNonZeroGasEIP2028 +
|
||||
1*params.TxAccessListAddressGas + 1*params.TxAccessListStorageKeyGas +
|
||||
1*amsterdamAddressCost + 1*amsterdamStorageKeyCost +
|
||||
1*params.TxAuthTupleRegularGas,
|
||||
StateGas: 1 * (params.AuthorizationCreationSize + params.AccountCreationSize) * params.CostPerStateByte,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
rules := params.Rules{
|
||||
IsHomestead: tt.isHomestead,
|
||||
IsIstanbul: tt.isEIP2028,
|
||||
IsShanghai: tt.isEIP3860,
|
||||
IsAmsterdam: tt.isAmsterdam,
|
||||
}
|
||||
got, err := IntrinsicGas(tt.data, tt.accessList, tt.authList,
|
||||
tt.creation, tt.isHomestead, tt.isEIP2028, tt.isEIP3860, tt.isAmsterdam)
|
||||
tt.creation, rules, params.CostPerStateByte)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
want := vm.GasCosts{RegularGas: tt.want}
|
||||
if got != want {
|
||||
t.Fatalf("gas mismatch: got %+v, want %+v", got, want)
|
||||
if got != tt.want {
|
||||
t.Fatalf("gas mismatch: got %+v, want %+v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,21 +28,22 @@ func _() {
|
|||
_ = x[GasChangeWitnessCodeChunk-17]
|
||||
_ = x[GasChangeWitnessContractCollisionCheck-18]
|
||||
_ = x[GasChangeTxDataFloor-19]
|
||||
_ = x[GasChangeAccountCreation-20]
|
||||
_ = x[GasChangeIgnored-255]
|
||||
}
|
||||
|
||||
const (
|
||||
_GasChangeReason_name_0 = "UnspecifiedTxInitialBalanceTxIntrinsicGasTxRefundsTxLeftOverReturnedCallInitialBalanceCallLeftOverReturnedCallLeftOverRefundedCallContractCreationCallContractCreation2CallCodeStorageCallOpCodeCallPrecompiledContractCallStorageColdAccessCallFailedExecutionWitnessContractInitWitnessContractCreationWitnessCodeChunkWitnessContractCollisionCheckTxDataFloor"
|
||||
_GasChangeReason_name_0 = "UnspecifiedTxInitialBalanceTxIntrinsicGasTxRefundsTxLeftOverReturnedCallInitialBalanceCallLeftOverReturnedCallLeftOverRefundedCallContractCreationCallContractCreation2CallCodeStorageCallOpCodeCallPrecompiledContractCallStorageColdAccessCallFailedExecutionWitnessContractInitWitnessContractCreationWitnessCodeChunkWitnessContractCollisionCheckTxDataFloorAccountCreation"
|
||||
_GasChangeReason_name_1 = "Ignored"
|
||||
)
|
||||
|
||||
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}
|
||||
_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, 368}
|
||||
)
|
||||
|
||||
func (i GasChangeReason) String() string {
|
||||
switch {
|
||||
case i <= 19:
|
||||
case i <= 20:
|
||||
return _GasChangeReason_name_0[_GasChangeReason_index_0[i]:_GasChangeReason_index_0[i+1]]
|
||||
case i == 255:
|
||||
return _GasChangeReason_name_1
|
||||
|
|
|
|||
|
|
@ -472,6 +472,10 @@ const (
|
|||
// transaction data. This change will always be a negative change.
|
||||
GasChangeTxDataFloor GasChangeReason = 19
|
||||
|
||||
// GasChangeAccountCreation represents the state gas charging for account
|
||||
// creation inside the call/create frame.
|
||||
GasChangeAccountCreation GasChangeReason = 20
|
||||
|
||||
// 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.
|
||||
GasChangeIgnored GasChangeReason = 0xFF
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
}
|
||||
// Ensure the transaction has more gas than the bare minimum needed to cover
|
||||
// the transaction metadata
|
||||
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, true, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam)
|
||||
intrGas, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
@ -138,9 +138,16 @@ func ValidateTransaction(tx *types.Transaction, head *types.Header, signer types
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Make sure the transaction has sufficient gas allowance to
|
||||
// pay the floor cost.
|
||||
if tx.Gas() < floorDataGas {
|
||||
return fmt.Errorf("%w: gas %v, minimum needed %v", core.ErrFloorDataGas, tx.Gas(), floorDataGas)
|
||||
}
|
||||
// In Amsterdam, the transaction gas limit is allowed to exceed
|
||||
// params.MaxTxGas, but the calldata floor cost is capped by it.
|
||||
if rules.IsAmsterdam && max(intrGas.RegularGas, floorDataGas) > params.MaxTxGas {
|
||||
return fmt.Errorf("%w: regular intrisic cost %v, floor: %v", core.ErrFloorDataGas, intrGas.RegularGas, floorDataGas)
|
||||
}
|
||||
}
|
||||
// Ensure the gasprice is high enough to cover the requirement of the calling pool
|
||||
if tx.GasTipCapIntCmp(opts.MinTip) < 0 {
|
||||
|
|
|
|||
|
|
@ -112,6 +112,5 @@ func toWordSize(size uint64) uint64 {
|
|||
if size > math.MaxUint64-31 {
|
||||
return math.MaxUint64/32 + 1
|
||||
}
|
||||
|
||||
return (size + 31) / 32
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@ type Contract struct {
|
|||
IsDeployment bool
|
||||
IsSystemCall bool
|
||||
|
||||
// Gas carries the unified gas state for this frame: running balance,
|
||||
// reservoir, and per-frame usage accumulators. See GasBudget.
|
||||
Gas GasBudget
|
||||
value *uint256.Int
|
||||
}
|
||||
|
|
@ -113,7 +115,6 @@ func (c *Contract) GetOp(n uint64) OpCode {
|
|||
if n < uint64(len(c.Code)) {
|
||||
return OpCode(c.Code[n])
|
||||
}
|
||||
|
||||
return STOP
|
||||
}
|
||||
|
||||
|
|
@ -125,9 +126,10 @@ func (c *Contract) Caller() common.Address {
|
|||
return c.caller
|
||||
}
|
||||
|
||||
// UseGas attempts the use gas and subtracts it and returns true on success
|
||||
func (c *Contract) UseGas(cost GasCosts, logger *tracing.Hooks, reason tracing.GasChangeReason) (ok bool) {
|
||||
prior, ok := c.Gas.Charge(cost)
|
||||
// chargeRegular deducts regular gas only, with tracer integration.
|
||||
// Returns false on OOG. Delegates the arithmetic to GasBudget.ChargeRegular.
|
||||
func (c *Contract) chargeRegular(r uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) bool {
|
||||
prior, ok := c.Gas.ChargeRegular(r)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
|
@ -137,15 +139,44 @@ func (c *Contract) UseGas(cost GasCosts, logger *tracing.Hooks, reason tracing.G
|
|||
return true
|
||||
}
|
||||
|
||||
// RefundGas refunds the leftover gas budget back to the contract.
|
||||
func (c *Contract) RefundGas(refund GasBudget, logger *tracing.Hooks, reason tracing.GasChangeReason) {
|
||||
prior, changed := c.Gas.Refund(refund)
|
||||
if !changed {
|
||||
return
|
||||
// chargeState deducts state gas (spilling into regular when the reservoir is
|
||||
// exhausted), with tracer integration. Returns false on OOG.
|
||||
func (c *Contract) chargeState(s uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) bool {
|
||||
prior, ok := c.Gas.ChargeState(s)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if logger.HasGasHook() && reason != tracing.GasChangeIgnored {
|
||||
logger.EmitGasChange(prior.AsTracing(), c.Gas.AsTracing(), reason)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// refundGas absorbs a sub-call's leftover GasBudget into this contract's gas state.
|
||||
func (c *Contract) refundGas(child GasBudget, forwarded uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) {
|
||||
prior := c.Gas
|
||||
c.Gas.Absorb(child, forwarded)
|
||||
if logger.HasGasHook() && reason != tracing.GasChangeIgnored {
|
||||
logger.EmitGasChange(prior.AsTracing(), c.Gas.AsTracing(), reason)
|
||||
}
|
||||
}
|
||||
|
||||
// forwardGas drains `regular` regular gas and the entire state reservoir
|
||||
// from this contract's running budget and returns the initial GasBudget for
|
||||
// a child frame. The caller's UsedRegularGas is bumped by the forwarded
|
||||
// amount so that the absorb-on-return path correctly reclaims the unused
|
||||
// portion. Thin wrapper around GasBudget.Forward with tracer integration.
|
||||
//
|
||||
// Caller must ensure `regular` is no larger than the running balance (the
|
||||
// opcode's dynamic gas table is expected to validate that before invoking
|
||||
// the opcode handler).
|
||||
func (c *Contract) forwardGas(regular uint64, logger *tracing.Hooks, reason tracing.GasChangeReason) GasBudget {
|
||||
prior := c.Gas
|
||||
child := c.Gas.Forward(regular)
|
||||
if logger.HasGasHook() && reason != tracing.GasChangeIgnored {
|
||||
logger.EmitGasChange(prior.AsTracing(), c.Gas.AsTracing(), reason)
|
||||
}
|
||||
return child
|
||||
}
|
||||
|
||||
// Address returns the contracts address
|
||||
|
|
|
|||
|
|
@ -264,9 +264,8 @@ func ActivePrecompiles(rules params.Rules) []common.Address {
|
|||
// - any error that occurred
|
||||
func RunPrecompiledContract(stateDB StateDB, p PrecompiledContract, address common.Address, input []byte, gas GasBudget, logger *tracing.Hooks, rules params.Rules) (ret []byte, remaining GasBudget, err error) {
|
||||
gasCost := p.RequiredGas(input)
|
||||
prior, ok := gas.Charge(GasCosts{RegularGas: gasCost})
|
||||
prior, ok := gas.ChargeRegular(gasCost)
|
||||
if !ok {
|
||||
gas.Exhaust()
|
||||
return nil, gas, ErrOutOfGas
|
||||
}
|
||||
if logger.HasGasHook() {
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ func FuzzPrecompiledContracts(f *testing.F) {
|
|||
return
|
||||
}
|
||||
inWant := string(input)
|
||||
RunPrecompiledContract(nil, p, a, input, NewGasBudget(gas), nil, params.Rules{})
|
||||
RunPrecompiledContract(nil, p, a, input, NewGasBudget(gas, 0), nil, params.Rules{})
|
||||
if inHave := string(input); inWant != inHave {
|
||||
t.Errorf("Precompiled %v modified input data", a)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,7 +100,7 @@ func testPrecompiled(addr string, test precompiledTest, t *testing.T) {
|
|||
in := common.Hex2Bytes(test.Input)
|
||||
gas := p.RequiredGas(in)
|
||||
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
|
||||
if res, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas), nil, params.Rules{}); err != nil {
|
||||
if res, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas, 0), nil, params.Rules{}); err != nil {
|
||||
t.Error(err)
|
||||
} else if common.Bytes2Hex(res) != test.Expected {
|
||||
t.Errorf("Expected %v, got %v", test.Expected, common.Bytes2Hex(res))
|
||||
|
|
@ -122,7 +122,7 @@ func testPrecompiledOOG(addr string, test precompiledTest, t *testing.T) {
|
|||
gas := test.Gas - 1
|
||||
|
||||
t.Run(fmt.Sprintf("%s-Gas=%d", test.Name, gas), func(t *testing.T) {
|
||||
_, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas), nil, params.Rules{})
|
||||
_, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas, 0), nil, params.Rules{})
|
||||
if err.Error() != "out of gas" {
|
||||
t.Errorf("Expected error [out of gas], got [%v]", err)
|
||||
}
|
||||
|
|
@ -139,7 +139,7 @@ func testPrecompiledFailure(addr string, test precompiledFailureTest, t *testing
|
|||
in := common.Hex2Bytes(test.Input)
|
||||
gas := p.RequiredGas(in)
|
||||
t.Run(test.Name, func(t *testing.T) {
|
||||
_, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas), nil, params.Rules{})
|
||||
_, _, err := RunPrecompiledContract(nil, p, common.HexToAddress(addr), in, NewGasBudget(gas, 0), nil, params.Rules{})
|
||||
if err.Error() != test.ExpectedError {
|
||||
t.Errorf("Expected error [%v], got [%v]", test.ExpectedError, err)
|
||||
}
|
||||
|
|
@ -170,7 +170,7 @@ func benchmarkPrecompiled(addr string, test precompiledTest, bench *testing.B) {
|
|||
start := time.Now()
|
||||
for bench.Loop() {
|
||||
copy(data, in)
|
||||
res, _, err = RunPrecompiledContract(nil, p, common.HexToAddress(addr), data, NewGasBudget(reqGas), nil, params.Rules{})
|
||||
res, _, err = RunPrecompiledContract(nil, p, common.HexToAddress(addr), data, NewGasBudget(reqGas, 0), nil, params.Rules{})
|
||||
}
|
||||
elapsed := uint64(time.Since(start))
|
||||
if elapsed < 1 {
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ var activators = map[int]func(*JumpTable){
|
|||
7939: enable7939,
|
||||
8024: enable8024,
|
||||
7843: enable7843,
|
||||
8037: enable8037,
|
||||
}
|
||||
|
||||
// EnableEIP enables the given EIP on the config.
|
||||
|
|
@ -377,7 +378,7 @@ func opExtCodeCopyEIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, er
|
|||
code := evm.StateDB.GetCode(addr)
|
||||
paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64())
|
||||
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(code)), false, scope.Contract.Gas.RegularGas)
|
||||
scope.Contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeUnspecified)
|
||||
scope.Contract.chargeRegular(consumed, evm.Config.Tracer, tracing.GasChangeUnspecified)
|
||||
if consumed < wanted {
|
||||
return nil, ErrOutOfGas
|
||||
}
|
||||
|
|
@ -403,7 +404,7 @@ func opPush1EIP4762(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
// advanced past this boundary.
|
||||
contractAddr := scope.Contract.Address()
|
||||
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas.RegularGas)
|
||||
scope.Contract.UseGas(GasCosts{RegularGas: wanted}, evm.Config.Tracer, tracing.GasChangeUnspecified)
|
||||
scope.Contract.chargeRegular(wanted, evm.Config.Tracer, tracing.GasChangeUnspecified)
|
||||
if consumed < wanted {
|
||||
return nil, ErrOutOfGas
|
||||
}
|
||||
|
|
@ -430,7 +431,7 @@ func makePushEIP4762(size uint64, pushByteSize int) executionFunc {
|
|||
if !scope.Contract.IsDeployment && !scope.Contract.IsSystemCall {
|
||||
contractAddr := scope.Contract.Address()
|
||||
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(contractAddr, uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false, scope.Contract.Gas.RegularGas)
|
||||
scope.Contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeUnspecified)
|
||||
scope.Contract.chargeRegular(consumed, evm.Config.Tracer, tracing.GasChangeUnspecified)
|
||||
if consumed < wanted {
|
||||
return nil, ErrOutOfGas
|
||||
}
|
||||
|
|
@ -590,3 +591,11 @@ func enable7843(jt *JumpTable) {
|
|||
maxStack: maxStack(0, 1),
|
||||
}
|
||||
}
|
||||
|
||||
// enable8037 enables the multidimensional-metering as specified in EIP-8037.
|
||||
func enable8037(jt *JumpTable) {
|
||||
jt[CREATE].constantGas = params.CreateGasAmsterdam
|
||||
jt[CREATE2].constantGas = params.CreateGasAmsterdam
|
||||
jt[SELFDESTRUCT].dynamicGas = gasSelfdestruct8037
|
||||
jt[SSTORE].dynamicGas = gasSStore8037
|
||||
}
|
||||
|
|
|
|||
255
core/vm/evm.go
255
core/vm/evm.go
|
|
@ -67,6 +67,8 @@ type BlockContext struct {
|
|||
BlobBaseFee *big.Int // Provides information for BLOBBASEFEE (0 if vm runs with NoBaseFee flag and 0 blob gas price)
|
||||
Random *common.Hash // Provides information for PREVRANDAO
|
||||
SlotNum uint64 // Provides information for SLOTNUM
|
||||
|
||||
CostPerStateByte uint64 // CostPerByte for new state after EIP-8037
|
||||
}
|
||||
|
||||
// TxContext provides the EVM with information about a transaction.
|
||||
|
|
@ -245,13 +247,13 @@ func isSystemCall(caller common.Address) bool {
|
|||
// parameters. It also handles any necessary value transfer required and takse
|
||||
// the necessary steps to create accounts and reverses the state in case of an
|
||||
// execution error or failed value transfer.
|
||||
func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, leftOverGas GasBudget, err error) {
|
||||
func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, result GasBudget, err error) {
|
||||
// Capture the tracer start/end events in debug mode
|
||||
if evm.Config.Tracer != nil {
|
||||
evm.captureBegin(evm.depth, CALL, caller, addr, input, gas.RegularGas, value.ToBig())
|
||||
defer func(startGas uint64) {
|
||||
evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err)
|
||||
}(gas.RegularGas)
|
||||
evm.captureBegin(evm.depth, CALL, caller, addr, input, gas, value.ToBig())
|
||||
defer func(startGas GasBudget) {
|
||||
evm.captureEnd(evm.depth, startGas, result, ret, err)
|
||||
}(gas)
|
||||
}
|
||||
// Fail if we're trying to execute above the call depth limit
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
|
|
@ -263,7 +265,7 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
|||
if !syscall && !value.IsZero() && !evm.Context.CanTransfer(evm.StateDB, caller, value) {
|
||||
return nil, gas, ErrInsufficientBalance
|
||||
}
|
||||
snapshot := evm.StateDB.Snapshot()
|
||||
snapshot, reservoir := evm.StateDB.Snapshot(), gas.StateGas
|
||||
p, isPrecompile := evm.precompile(addr)
|
||||
if !evm.StateDB.Exist(addr) {
|
||||
if !isPrecompile && evm.chainRules.IsEIP4762 && !isSystemCall(caller) {
|
||||
|
|
@ -275,10 +277,9 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
|||
// hash leaf to the access list, then account creation will proceed unimpaired.
|
||||
// Thus, only pay for the creation of the code hash leaf here.
|
||||
wgas := evm.AccessEvents.CodeHashGas(addr, true, gas.RegularGas, false)
|
||||
if _, ok := gas.Charge(GasCosts{RegularGas: wgas}); !ok {
|
||||
if _, ok := gas.ChargeRegular(wgas); !ok {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
gas.Exhaust()
|
||||
return nil, gas, ErrOutOfGas
|
||||
return nil, gas.ExitHalt(reservoir), ErrOutOfGas
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -288,6 +289,16 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
|||
}
|
||||
evm.StateDB.CreateAccount(addr)
|
||||
}
|
||||
if evm.chainRules.IsAmsterdam && !value.IsZero() && evm.StateDB.Empty(addr) {
|
||||
prev, ok := gas.ChargeState(params.AccountCreationSize * evm.Context.CostPerStateByte)
|
||||
if !ok {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
return nil, gas.ExitHalt(reservoir), ErrOutOfGas
|
||||
}
|
||||
if evm.Config.Tracer.HasGasHook() {
|
||||
evm.Config.Tracer.EmitGasChange(prev.AsTracing(), gas.AsTracing(), tracing.GasChangeAccountCreation)
|
||||
}
|
||||
}
|
||||
// Perform the value transfer only in non-syscall mode.
|
||||
// Calling this is required even for zero-value transfers,
|
||||
// to ensure the state clearing mechanism is applied.
|
||||
|
|
@ -311,22 +322,19 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
|||
gas = contract.Gas
|
||||
}
|
||||
}
|
||||
// When an error was returned by the EVM or when setting the creation code
|
||||
// above we revert to the snapshot and consume any gas remaining. Additionally,
|
||||
// when we're in homestead this also counts for code storage gas errors.
|
||||
|
||||
// Calculate the remaining gas at the end of frame
|
||||
exitGas := gas.Exit(err, reservoir)
|
||||
if err != nil {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
|
||||
if err != ErrExecutionReverted {
|
||||
if evm.Config.Tracer.HasGasHook() {
|
||||
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
|
||||
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), exitGas.AsTracing(), tracing.GasChangeCallFailedExecution)
|
||||
}
|
||||
gas.Exhaust()
|
||||
}
|
||||
// TODO: consider clearing up unused snapshots:
|
||||
//} else {
|
||||
// evm.StateDB.DiscardSnapshot(snapshot)
|
||||
}
|
||||
return ret, gas, err
|
||||
return ret, exitGas, err
|
||||
}
|
||||
|
||||
// CallCode executes the contract associated with the addr with the given input
|
||||
|
|
@ -336,26 +344,23 @@ func (evm *EVM) Call(caller common.Address, addr common.Address, input []byte, g
|
|||
//
|
||||
// CallCode differs from Call in the sense that it executes the given address'
|
||||
// code with the caller as context.
|
||||
func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, leftOverGas GasBudget, err error) {
|
||||
func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, result GasBudget, err error) {
|
||||
// Invoke tracer hooks that signal entering/exiting a call frame
|
||||
if evm.Config.Tracer != nil {
|
||||
evm.captureBegin(evm.depth, CALLCODE, caller, addr, input, gas.RegularGas, value.ToBig())
|
||||
defer func(startGas uint64) {
|
||||
evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err)
|
||||
}(gas.RegularGas)
|
||||
evm.captureBegin(evm.depth, CALLCODE, caller, addr, input, gas, value.ToBig())
|
||||
defer func(startGas GasBudget) {
|
||||
evm.captureEnd(evm.depth, startGas, result, ret, err)
|
||||
}(gas)
|
||||
}
|
||||
// Fail if we're trying to execute above the call depth limit
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
return nil, gas, ErrDepth
|
||||
}
|
||||
// Fail if we're trying to transfer more than the available balance
|
||||
// Note although it's noop to transfer X ether to caller itself. But
|
||||
// if caller doesn't have enough balance, it would be an error to allow
|
||||
// over-charging itself. So the check here is necessary.
|
||||
if !evm.Context.CanTransfer(evm.StateDB, caller, value) {
|
||||
return nil, gas, ErrInsufficientBalance
|
||||
}
|
||||
var snapshot = evm.StateDB.Snapshot()
|
||||
snapshot, reservoir := evm.StateDB.Snapshot(), gas.StateGas
|
||||
|
||||
// It is allowed to call precompiles, even via delegatecall
|
||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||
|
|
@ -368,16 +373,19 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
|
|||
ret, err = evm.Run(contract, input, false)
|
||||
gas = contract.Gas
|
||||
}
|
||||
|
||||
// Calculate the remaining gas at the end of frame
|
||||
exitGas := gas.Exit(err, reservoir)
|
||||
if err != nil {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
|
||||
if err != ErrExecutionReverted {
|
||||
if evm.Config.Tracer.HasGasHook() {
|
||||
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
|
||||
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), exitGas.AsTracing(), tracing.GasChangeCallFailedExecution)
|
||||
}
|
||||
gas.Exhaust()
|
||||
}
|
||||
}
|
||||
return ret, gas, err
|
||||
return ret, exitGas, err
|
||||
}
|
||||
|
||||
// DelegateCall executes the contract associated with the addr with the given input
|
||||
|
|
@ -385,56 +393,56 @@ func (evm *EVM) CallCode(caller common.Address, addr common.Address, input []byt
|
|||
//
|
||||
// DelegateCall differs from CallCode in the sense that it executes the given address'
|
||||
// code with the caller as context and the caller is set to the caller of the caller.
|
||||
func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, leftOverGas GasBudget, err error) {
|
||||
func (evm *EVM) DelegateCall(originCaller common.Address, caller common.Address, addr common.Address, input []byte, gas GasBudget, value *uint256.Int) (ret []byte, result GasBudget, err error) {
|
||||
// Invoke tracer hooks that signal entering/exiting a call frame
|
||||
if evm.Config.Tracer != nil {
|
||||
// DELEGATECALL inherits value from parent call
|
||||
evm.captureBegin(evm.depth, DELEGATECALL, caller, addr, input, gas.RegularGas, value.ToBig())
|
||||
defer func(startGas uint64) {
|
||||
evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err)
|
||||
}(gas.RegularGas)
|
||||
evm.captureBegin(evm.depth, DELEGATECALL, caller, addr, input, gas, value.ToBig())
|
||||
defer func(startGas GasBudget) {
|
||||
evm.captureEnd(evm.depth, startGas, result, ret, err)
|
||||
}(gas)
|
||||
}
|
||||
// Fail if we're trying to execute above the call depth limit
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
return nil, gas, ErrDepth
|
||||
}
|
||||
var snapshot = evm.StateDB.Snapshot()
|
||||
snapshot, reservoir := evm.StateDB.Snapshot(), gas.StateGas
|
||||
|
||||
// It is allowed to call precompiles, even via delegatecall
|
||||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(evm.StateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules)
|
||||
} else {
|
||||
// Initialise a new contract and make initialise the delegate values
|
||||
//
|
||||
// Note: The value refers to the original value from the parent call.
|
||||
contract := NewContract(originCaller, caller, value, gas, evm.jumpDests)
|
||||
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
|
||||
ret, err = evm.Run(contract, input, false)
|
||||
gas = contract.Gas
|
||||
}
|
||||
|
||||
// Calculate the remaining gas at the end of frame
|
||||
exitGas := gas.Exit(err, reservoir)
|
||||
if err != nil {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
|
||||
if err != ErrExecutionReverted {
|
||||
if evm.Config.Tracer.HasGasHook() {
|
||||
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
|
||||
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), exitGas.AsTracing(), tracing.GasChangeCallFailedExecution)
|
||||
}
|
||||
gas.Exhaust()
|
||||
}
|
||||
}
|
||||
return ret, gas, err
|
||||
return ret, exitGas, err
|
||||
}
|
||||
|
||||
// StaticCall executes the contract associated with the addr with the given input
|
||||
// as parameters while disallowing any modifications to the state during the call.
|
||||
// Opcodes that attempt to perform such modifications will result in exceptions
|
||||
// instead of performing the modifications.
|
||||
func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []byte, gas GasBudget) (ret []byte, leftOverGas GasBudget, err error) {
|
||||
func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []byte, gas GasBudget) (ret []byte, result GasBudget, err error) {
|
||||
// Invoke tracer hooks that signal entering/exiting a call frame
|
||||
if evm.Config.Tracer != nil {
|
||||
evm.captureBegin(evm.depth, STATICCALL, caller, addr, input, gas.RegularGas, nil)
|
||||
defer func(startGas uint64) {
|
||||
evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err)
|
||||
}(gas.RegularGas)
|
||||
evm.captureBegin(evm.depth, STATICCALL, caller, addr, input, gas, nil)
|
||||
defer func(startGas GasBudget) {
|
||||
evm.captureEnd(evm.depth, startGas, result, ret, err)
|
||||
}(gas)
|
||||
}
|
||||
// Fail if we're trying to execute above the call depth limit
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
|
|
@ -445,7 +453,7 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
|
|||
// after all empty accounts were deleted, so this is not required. However, if we omit this,
|
||||
// then certain tests start failing; stRevertTest/RevertPrecompiledTouchExactOOG.json.
|
||||
// We could change this, but for now it's left for legacy reasons
|
||||
var snapshot = evm.StateDB.Snapshot()
|
||||
snapshot, reservoir := evm.StateDB.Snapshot(), gas.StateGas
|
||||
|
||||
// We do an AddBalance of zero here, just in order to trigger a touch.
|
||||
// This doesn't matter on Mainnet, where all empties are gone at the time of Byzantium,
|
||||
|
|
@ -456,58 +464,59 @@ func (evm *EVM) StaticCall(caller common.Address, addr common.Address, input []b
|
|||
if p, isPrecompile := evm.precompile(addr); isPrecompile {
|
||||
ret, gas, err = RunPrecompiledContract(evm.StateDB, p, addr, input, gas, evm.Config.Tracer, evm.chainRules)
|
||||
} else {
|
||||
// Initialise a new contract and set the code that is to be used by the EVM.
|
||||
// The contract is a scoped environment for this execution context only.
|
||||
contract := NewContract(caller, addr, new(uint256.Int), gas, evm.jumpDests)
|
||||
contract.SetCallCode(evm.resolveCodeHash(addr), evm.resolveCode(addr))
|
||||
|
||||
// When an error was returned by the EVM or when setting the creation code
|
||||
// above we revert to the snapshot and consume any gas remaining. Additionally
|
||||
// when we're in Homestead this also counts for code storage gas errors.
|
||||
ret, err = evm.Run(contract, input, true)
|
||||
gas = contract.Gas
|
||||
}
|
||||
|
||||
// Calculate the remaining gas at the end of frame
|
||||
exitGas := gas.Exit(err, reservoir)
|
||||
if err != nil {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
if err != ErrExecutionReverted {
|
||||
if evm.Config.Tracer.HasGasHook() {
|
||||
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
|
||||
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), exitGas.AsTracing(), tracing.GasChangeCallFailedExecution)
|
||||
}
|
||||
gas.Exhaust()
|
||||
}
|
||||
}
|
||||
return ret, gas, err
|
||||
return ret, exitGas, err
|
||||
}
|
||||
|
||||
// create creates a new contract using code as deployment code.
|
||||
func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, leftOverGas GasBudget, err error) {
|
||||
if evm.Config.Tracer != nil {
|
||||
evm.captureBegin(evm.depth, typ, caller, address, code, gas.RegularGas, value.ToBig())
|
||||
defer func(startGas uint64) {
|
||||
evm.captureEnd(evm.depth, startGas, leftOverGas.RegularGas, ret, err)
|
||||
}(gas.RegularGas)
|
||||
}
|
||||
func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int, address common.Address, typ OpCode) (ret []byte, createAddress common.Address, result GasBudget, err error) {
|
||||
// Depth check execution. Fail if we're trying to execute above the
|
||||
// limit.
|
||||
var nonce uint64
|
||||
if evm.depth > int(params.CallCreateDepth) {
|
||||
return nil, common.Address{}, gas, ErrDepth
|
||||
err = ErrDepth
|
||||
} else if !evm.Context.CanTransfer(evm.StateDB, caller, value) {
|
||||
err = ErrInsufficientBalance
|
||||
} else {
|
||||
nonce = evm.StateDB.GetNonce(caller)
|
||||
if nonce+1 < nonce {
|
||||
err = ErrNonceUintOverflow
|
||||
}
|
||||
}
|
||||
if !evm.Context.CanTransfer(evm.StateDB, caller, value) {
|
||||
return nil, common.Address{}, gas, ErrInsufficientBalance
|
||||
if evm.Config.Tracer != nil {
|
||||
evm.captureBegin(evm.depth, typ, caller, address, code, gas, value.ToBig())
|
||||
defer func(startGas GasBudget) {
|
||||
evm.captureEnd(evm.depth, startGas, result, ret, err)
|
||||
}(gas)
|
||||
}
|
||||
nonce := evm.StateDB.GetNonce(caller)
|
||||
if nonce+1 < nonce {
|
||||
return nil, common.Address{}, gas, ErrNonceUintOverflow
|
||||
if err != nil {
|
||||
return nil, common.Address{}, gas, err
|
||||
}
|
||||
// Increment the caller's nonce after passing all validations
|
||||
evm.StateDB.SetNonce(caller, nonce+1, tracing.NonceChangeContractCreator)
|
||||
reservoir := gas.StateGas
|
||||
|
||||
// Charge the contract creation init gas in verkle mode
|
||||
if evm.chainRules.IsEIP4762 {
|
||||
statelessGas := evm.AccessEvents.ContractCreatePreCheckGas(address, gas.RegularGas)
|
||||
prior, ok := gas.Charge(GasCosts{RegularGas: statelessGas})
|
||||
if !ok {
|
||||
gas.Exhaust()
|
||||
return nil, common.Address{}, gas, ErrOutOfGas
|
||||
return nil, common.Address{}, gas.ExitHalt(reservoir), ErrOutOfGas
|
||||
}
|
||||
if evm.Config.Tracer.HasGasHook() {
|
||||
evm.Config.Tracer.EmitGasChange(prior.AsTracing(), gas.AsTracing(), tracing.GasChangeWitnessContractCollisionCheck)
|
||||
|
|
@ -528,11 +537,13 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
|||
if evm.StateDB.GetNonce(address) != 0 ||
|
||||
(contractHash != (common.Hash{}) && contractHash != types.EmptyCodeHash) || // non-empty code
|
||||
isEIP7610RejectedAccount(evm.ChainConfig().ChainID, address, evm.chainRules.IsEIP158) {
|
||||
halt := gas.ExitHalt(reservoir)
|
||||
if evm.Config.Tracer.HasGasHook() {
|
||||
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallFailedExecution)
|
||||
evm.Config.Tracer.EmitGasChange(gas.AsTracing(), halt.AsTracing(), tracing.GasChangeCallFailedExecution)
|
||||
}
|
||||
gas.Exhaust()
|
||||
return nil, common.Address{}, gas, ErrContractAddressCollision
|
||||
// EIP-8037 collision rule: the state reservoir is fully preserved on
|
||||
// address collision while regular gas is burnt.
|
||||
return nil, common.Address{}, halt, ErrContractAddressCollision
|
||||
}
|
||||
// Create a new account on the state only if the object was not present.
|
||||
// It might be possible the contract code is deployed to a pre-existent
|
||||
|
|
@ -540,6 +551,18 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
|||
snapshot := evm.StateDB.Snapshot()
|
||||
if !evm.StateDB.Exist(address) {
|
||||
evm.StateDB.CreateAccount(address)
|
||||
|
||||
if evm.chainRules.IsAmsterdam && evm.depth > 0 {
|
||||
// Only charge state gas if we are not doing a create transaction.
|
||||
// Prevents double charging with IntrinsicGas.
|
||||
prev, ok := gas.ChargeState(params.AccountCreationSize * evm.Context.CostPerStateByte)
|
||||
if !ok {
|
||||
return nil, common.Address{}, gas.ExitHalt(reservoir), ErrOutOfGas
|
||||
}
|
||||
if evm.Config.Tracer.HasGasHook() {
|
||||
evm.Config.Tracer.EmitGasChange(prev.AsTracing(), gas.AsTracing(), tracing.GasChangeAccountCreation)
|
||||
}
|
||||
}
|
||||
}
|
||||
// CreateContract means that regardless of whether the account previously existed
|
||||
// in the state trie or not, it _now_ becomes created as a _contract_ account.
|
||||
|
|
@ -554,8 +577,7 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
|||
if evm.chainRules.IsEIP4762 {
|
||||
consumed, wanted := evm.AccessEvents.ContractCreateInitGas(address, gas.RegularGas)
|
||||
if consumed < wanted {
|
||||
gas.Exhaust()
|
||||
return nil, common.Address{}, gas, ErrOutOfGas
|
||||
return nil, common.Address{}, gas.ExitHalt(reservoir), ErrOutOfGas
|
||||
}
|
||||
prior, _ := gas.Charge(GasCosts{RegularGas: consumed})
|
||||
if evm.Config.Tracer.HasGasHook() {
|
||||
|
|
@ -574,13 +596,23 @@ func (evm *EVM) create(caller common.Address, code []byte, gas GasBudget, value
|
|||
contract.IsDeployment = true
|
||||
|
||||
ret, err = evm.initNewContract(contract, address)
|
||||
|
||||
// Special case: ErrCodeStoreOutOfGas pre-Homestead does NOT roll back
|
||||
// state and gas is preserved (i.e., treated as success).
|
||||
if err != nil && (evm.chainRules.IsHomestead || err != ErrCodeStoreOutOfGas) {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
|
||||
exit := contract.Gas.Exit(err, reservoir)
|
||||
if err != ErrExecutionReverted {
|
||||
contract.UseGas(GasCosts{RegularGas: contract.Gas.RegularGas}, evm.Config.Tracer, tracing.GasChangeCallFailedExecution)
|
||||
if evm.Config.Tracer.HasGasHook() {
|
||||
evm.Config.Tracer.EmitGasChange(contract.Gas.AsTracing(), exit.AsTracing(), tracing.GasChangeCallFailedExecution)
|
||||
}
|
||||
}
|
||||
return ret, address, exit, err
|
||||
}
|
||||
return ret, address, contract.Gas, err
|
||||
// Either success, or pre-Homestead ErrCodeStoreOutOfGas (gas preserved).
|
||||
// Both packaged as a success-form GasBudget.
|
||||
return ret, address, contract.Gas.ExitSuccess(), err
|
||||
}
|
||||
|
||||
// initNewContract runs a new contract's creation code, performs checks on the
|
||||
|
|
@ -590,30 +622,45 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
|
|||
if err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Check whether the max code size has been exceeded, assign err if the case.
|
||||
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Check prefix before gas calculation.
|
||||
// Reject code starting with 0xEF if EIP-3541 is enabled.
|
||||
if len(ret) >= 1 && ret[0] == 0xEF && evm.chainRules.IsLondon {
|
||||
return ret, ErrInvalidCode
|
||||
}
|
||||
|
||||
if !evm.chainRules.IsEIP4762 {
|
||||
createDataGas := uint64(len(ret)) * params.CreateDataGas
|
||||
if !contract.UseGas(GasCosts{RegularGas: createDataGas}, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
|
||||
return ret, ErrCodeStoreOutOfGas
|
||||
}
|
||||
} else {
|
||||
if evm.chainRules.IsEIP4762 {
|
||||
consumed, wanted := evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true, contract.Gas.RegularGas)
|
||||
contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
|
||||
contract.chargeRegular(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
|
||||
if len(ret) > 0 && (consumed < wanted) {
|
||||
return ret, ErrCodeStoreOutOfGas
|
||||
}
|
||||
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
|
||||
return ret, err
|
||||
}
|
||||
} else if evm.chainRules.IsAmsterdam {
|
||||
// Check max code size BEFORE charging gas so over-max code
|
||||
// does not consume state gas (which would inflate tx_state).
|
||||
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
|
||||
return ret, err
|
||||
}
|
||||
// Charge regular gas (hash cost) before state gas.
|
||||
regularCost := toWordSize(uint64(len(ret))) * params.Keccak256WordGas
|
||||
if !contract.chargeRegular(regularCost, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
|
||||
return ret, ErrCodeStoreOutOfGas
|
||||
}
|
||||
// Charge state gas (code-deposit) afterwards.
|
||||
stateCost := uint64(len(ret)) * evm.Context.CostPerStateByte
|
||||
if !contract.chargeState(stateCost, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
|
||||
return ret, ErrCodeStoreOutOfGas
|
||||
}
|
||||
} else {
|
||||
createDataCost := uint64(len(ret)) * params.CreateDataGas
|
||||
if !contract.chargeRegular(createDataCost, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
|
||||
return ret, ErrCodeStoreOutOfGas
|
||||
}
|
||||
if err := CheckMaxCodeSize(&evm.chainRules, uint64(len(ret))); err != nil {
|
||||
return ret, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(ret) > 0 {
|
||||
evm.StateDB.SetCode(address, ret, tracing.CodeChangeContractCreation)
|
||||
}
|
||||
|
|
@ -621,7 +668,7 @@ func (evm *EVM) initNewContract(contract *Contract, address common.Address) ([]b
|
|||
}
|
||||
|
||||
// Create creates a new contract using code as deployment code.
|
||||
func (evm *EVM) Create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas GasBudget, err error) {
|
||||
func (evm *EVM) Create(caller common.Address, code []byte, gas GasBudget, value *uint256.Int) (ret []byte, contractAddr common.Address, result GasBudget, err error) {
|
||||
contractAddr = crypto.CreateAddress(caller, evm.StateDB.GetNonce(caller))
|
||||
return evm.create(caller, code, gas, value, contractAddr, CREATE)
|
||||
}
|
||||
|
|
@ -630,7 +677,7 @@ func (evm *EVM) Create(caller common.Address, code []byte, gas GasBudget, value
|
|||
//
|
||||
// The different between Create2 with Create is Create2 uses keccak256(0xff ++ msg.sender ++ salt ++ keccak256(init_code))[12:]
|
||||
// instead of the usual sender-and-nonce-hash as the address where the contract is initialized at.
|
||||
func (evm *EVM) Create2(caller common.Address, code []byte, gas GasBudget, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas GasBudget, err error) {
|
||||
func (evm *EVM) Create2(caller common.Address, code []byte, gas GasBudget, endowment *uint256.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, result GasBudget, err error) {
|
||||
inithash := crypto.Keccak256Hash(code)
|
||||
contractAddr = crypto.CreateAddress2(caller, salt.Bytes32(), inithash[:])
|
||||
return evm.create(caller, code, gas, endowment, contractAddr, CREATE2)
|
||||
|
|
@ -668,22 +715,20 @@ func (evm *EVM) resolveCodeHash(addr common.Address) common.Hash {
|
|||
// ChainConfig returns the environment's chain configuration
|
||||
func (evm *EVM) ChainConfig() *params.ChainConfig { return evm.chainConfig }
|
||||
|
||||
func (evm *EVM) captureBegin(depth int, typ OpCode, from common.Address, to common.Address, input []byte, startGas uint64, value *big.Int) {
|
||||
func (evm *EVM) captureBegin(depth int, typ OpCode, from common.Address, to common.Address, input []byte, startGas GasBudget, value *big.Int) {
|
||||
tracer := evm.Config.Tracer
|
||||
if tracer.OnEnter != nil {
|
||||
tracer.OnEnter(depth, byte(typ), from, to, input, startGas, value)
|
||||
tracer.OnEnter(depth, byte(typ), from, to, input, startGas.RegularGas, value)
|
||||
}
|
||||
if tracer.HasGasHook() {
|
||||
initial := NewGasBudget(startGas)
|
||||
tracer.EmitGasChange(tracing.Gas{}, initial.AsTracing(), tracing.GasChangeCallInitialBalance)
|
||||
tracer.EmitGasChange(tracing.Gas{}, startGas.AsTracing(), tracing.GasChangeCallInitialBalance)
|
||||
}
|
||||
}
|
||||
|
||||
func (evm *EVM) captureEnd(depth int, startGas uint64, leftOverGas uint64, ret []byte, err error) {
|
||||
func (evm *EVM) captureEnd(depth int, startGas GasBudget, leftOverGas GasBudget, ret []byte, err error) {
|
||||
tracer := evm.Config.Tracer
|
||||
if leftOverGas != 0 && tracer.HasGasHook() {
|
||||
leftover := NewGasBudget(leftOverGas)
|
||||
tracer.EmitGasChange(leftover.AsTracing(), tracing.Gas{}, tracing.GasChangeCallLeftOverReturned)
|
||||
if !leftOverGas.IsZero() && tracer.HasGasHook() {
|
||||
tracer.EmitGasChange(leftOverGas.AsTracing(), tracing.Gas{}, tracing.GasChangeCallLeftOverReturned)
|
||||
}
|
||||
var reverted bool
|
||||
if err != nil {
|
||||
|
|
@ -693,7 +738,7 @@ func (evm *EVM) captureEnd(depth int, startGas uint64, leftOverGas uint64, ret [
|
|||
reverted = false
|
||||
}
|
||||
if tracer.OnExit != nil {
|
||||
tracer.OnExit(depth, ret, startGas-leftOverGas, VMErrorFromErr(err), reverted)
|
||||
tracer.OnExit(depth, ret, startGas.RegularGas-leftOverGas.RegularGas, VMErrorFromErr(err), reverted)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -291,10 +291,19 @@ var (
|
|||
gasMLoad = pureMemoryGascost
|
||||
gasMStore8 = pureMemoryGascost
|
||||
gasMStore = pureMemoryGascost
|
||||
gasCreate = pureMemoryGascost
|
||||
)
|
||||
|
||||
func gasCreate(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
if evm.readOnly {
|
||||
return GasCosts{}, ErrWriteProtection
|
||||
}
|
||||
return pureMemoryGascost(evm, contract, stack, mem, memorySize)
|
||||
}
|
||||
|
||||
func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
if evm.readOnly {
|
||||
return GasCosts{}, ErrWriteProtection
|
||||
}
|
||||
gas, err := memoryGasCost(mem, memorySize)
|
||||
if err != nil {
|
||||
return GasCosts{}, err
|
||||
|
|
@ -313,6 +322,9 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS
|
|||
}
|
||||
|
||||
func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
if evm.readOnly {
|
||||
return GasCosts{}, ErrWriteProtection
|
||||
}
|
||||
gas, err := memoryGasCost(mem, memorySize)
|
||||
if err != nil {
|
||||
return GasCosts{}, err
|
||||
|
|
@ -331,7 +343,11 @@ func gasCreateEip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
|
|||
}
|
||||
return GasCosts{RegularGas: gas}, nil
|
||||
}
|
||||
|
||||
func gasCreate2Eip3860(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
if evm.readOnly {
|
||||
return GasCosts{}, ErrWriteProtection
|
||||
}
|
||||
gas, err := memoryGasCost(mem, memorySize)
|
||||
if err != nil {
|
||||
return GasCosts{}, err
|
||||
|
|
@ -384,17 +400,17 @@ var (
|
|||
gasStaticCall = makeCallVariantGasCost(gasStaticCallIntrinsic)
|
||||
)
|
||||
|
||||
func makeCallVariantGasCost(intrinsicFunc gasFunc) gasFunc {
|
||||
func makeCallVariantGasCost(intrinsicFunc intrinsicGasFunc) gasFunc {
|
||||
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
intrinsic, err := intrinsicFunc(evm, contract, stack, mem, memorySize)
|
||||
if err != nil {
|
||||
return GasCosts{}, err
|
||||
}
|
||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsic.RegularGas, stack.back(0))
|
||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsic, stack.back(0))
|
||||
if err != nil {
|
||||
return GasCosts{}, err
|
||||
}
|
||||
gas, overflow := math.SafeAdd(intrinsic.RegularGas, evm.callGasTemp)
|
||||
gas, overflow := math.SafeAdd(intrinsic, evm.callGasTemp)
|
||||
if overflow {
|
||||
return GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
|
|
@ -402,19 +418,19 @@ func makeCallVariantGasCost(intrinsicFunc gasFunc) gasFunc {
|
|||
}
|
||||
}
|
||||
|
||||
func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
var (
|
||||
gas uint64
|
||||
transfersValue = !stack.back(2).IsZero()
|
||||
address = common.Address(stack.back(1).Bytes20())
|
||||
)
|
||||
if evm.readOnly && transfersValue {
|
||||
return GasCosts{}, ErrWriteProtection
|
||||
return 0, ErrWriteProtection
|
||||
}
|
||||
// Stateless check
|
||||
memoryGas, err := memoryGasCost(mem, memorySize)
|
||||
if err != nil {
|
||||
return GasCosts{}, err
|
||||
return 0, err
|
||||
}
|
||||
var transferGas uint64
|
||||
if transfersValue && !evm.chainRules.IsEIP4762 {
|
||||
|
|
@ -422,12 +438,12 @@ func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
|
|||
}
|
||||
var overflow bool
|
||||
if gas, overflow = math.SafeAdd(memoryGas, transferGas); overflow {
|
||||
return GasCosts{}, ErrGasUintOverflow
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
// Terminate the gas measurement if the leftover gas is not sufficient,
|
||||
// it can effectively prevent accessing the states in the following steps.
|
||||
if contract.Gas.RegularGas < gas {
|
||||
return GasCosts{}, ErrOutOfGas
|
||||
return 0, ErrOutOfGas
|
||||
}
|
||||
// Stateful check
|
||||
var stateGas uint64
|
||||
|
|
@ -439,15 +455,15 @@ func gasCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, m
|
|||
stateGas += params.CallNewAccountGas
|
||||
}
|
||||
if gas, overflow = math.SafeAdd(gas, stateGas); overflow {
|
||||
return GasCosts{}, ErrGasUintOverflow
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
return GasCosts{RegularGas: gas}, nil
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
memoryGas, err := memoryGasCost(mem, memorySize)
|
||||
if err != nil {
|
||||
return GasCosts{}, err
|
||||
return 0, err
|
||||
}
|
||||
var (
|
||||
gas uint64
|
||||
|
|
@ -457,38 +473,36 @@ func gasCallCodeIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memor
|
|||
gas += params.CallValueTransferGas
|
||||
}
|
||||
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
||||
return GasCosts{}, ErrGasUintOverflow
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
return GasCosts{RegularGas: gas}, nil
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasDelegateCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
func gasDelegateCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
gas, err := memoryGasCost(mem, memorySize)
|
||||
if err != nil {
|
||||
return GasCosts{}, err
|
||||
return 0, err
|
||||
}
|
||||
return GasCosts{RegularGas: gas}, nil
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasStaticCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
func gasStaticCallIntrinsic(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
gas, err := memoryGasCost(mem, memorySize)
|
||||
if err != nil {
|
||||
return GasCosts{}, err
|
||||
return 0, err
|
||||
}
|
||||
return GasCosts{RegularGas: gas}, nil
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
if evm.readOnly {
|
||||
return GasCosts{}, ErrWriteProtection
|
||||
}
|
||||
|
||||
var gas uint64
|
||||
// EIP150 homestead gas reprice fork:
|
||||
if evm.chainRules.IsEIP150 {
|
||||
gas = params.SelfdestructGasEIP150
|
||||
var address = common.Address(stack.back(0).Bytes20())
|
||||
|
||||
if evm.chainRules.IsEIP158 {
|
||||
// if empty and transfers value
|
||||
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
|
||||
|
|
@ -504,3 +518,104 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
|
|||
}
|
||||
return GasCosts{RegularGas: gas}, nil
|
||||
}
|
||||
|
||||
func gasSelfdestruct8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
if evm.readOnly {
|
||||
return GasCosts{}, ErrWriteProtection
|
||||
}
|
||||
var (
|
||||
gas GasCosts
|
||||
address = common.Address(stack.peek().Bytes20())
|
||||
)
|
||||
if !evm.StateDB.AddressInAccessList(address) {
|
||||
// If the caller cannot afford the cost, this change will be rolled back
|
||||
evm.StateDB.AddAddressToAccessList(address)
|
||||
gas.RegularGas = params.ColdAccountAccessCostEIP2929
|
||||
}
|
||||
// Check we have enough regular gas before we add the address to the BAL
|
||||
if contract.Gas.RegularGas < gas.RegularGas {
|
||||
return gas, ErrOutOfGas
|
||||
}
|
||||
// Important: use StateDB.Empty instead of !StateDB.Exist. An account may exist
|
||||
// in the current state yet still be considered non-existent by EIP-161 if its
|
||||
// nonce, balance, and code are all zero. Such accounts can appear temporarily
|
||||
// during execution (e.g. via SELFDESTRUCT) and are removed at tx end.
|
||||
//
|
||||
// Funding such an account makes it permanent state growth and must be charged.
|
||||
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
|
||||
gas.StateGas += params.AccountCreationSize * evm.Context.CostPerStateByte
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasSStore8037(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
if evm.readOnly {
|
||||
return GasCosts{}, ErrWriteProtection
|
||||
}
|
||||
// If we fail the minimum gas availability invariant, fail (0)
|
||||
if contract.Gas.RegularGas <= params.SstoreSentryGasEIP2200 {
|
||||
return GasCosts{}, errors.New("not enough gas for reentrancy sentry")
|
||||
}
|
||||
// Gas sentry honoured, do the actual gas calculation based on the stored value
|
||||
var (
|
||||
y, x = stack.back(1), stack.peek()
|
||||
slot = common.Hash(x.Bytes32())
|
||||
current, original = evm.StateDB.GetStateAndCommittedState(contract.Address(), slot)
|
||||
cost GasCosts
|
||||
)
|
||||
// Check slot presence in the access list
|
||||
if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
|
||||
cost = GasCosts{RegularGas: params.ColdSloadCostEIP2929}
|
||||
// If the caller cannot afford the cost, this change will be rolled back
|
||||
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
|
||||
}
|
||||
value := common.Hash(y.Bytes32())
|
||||
|
||||
if current == value { // noop (1)
|
||||
// EIP 2200 original clause:
|
||||
// return params.SloadGasEIP2200, nil
|
||||
return GasCosts{RegularGas: cost.RegularGas + params.WarmStorageReadCostEIP2929}, nil // SLOAD_GAS
|
||||
}
|
||||
if original == current {
|
||||
if original == (common.Hash{}) { // create slot (2.1.1)
|
||||
return GasCosts{
|
||||
RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929,
|
||||
StateGas: params.StorageCreationSize * evm.Context.CostPerStateByte,
|
||||
}, nil
|
||||
}
|
||||
if value == (common.Hash{}) { // delete slot (2.1.2b)
|
||||
evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP3529)
|
||||
}
|
||||
// EIP-2200 original clause:
|
||||
// return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2)
|
||||
return GasCosts{RegularGas: cost.RegularGas + params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929}, nil // write existing slot (2.1.2)
|
||||
}
|
||||
if original != (common.Hash{}) {
|
||||
if current == (common.Hash{}) { // recreate slot (2.2.1.1)
|
||||
evm.StateDB.SubRefund(params.SstoreClearsScheduleRefundEIP3529)
|
||||
} else if value == (common.Hash{}) { // delete slot (2.2.1.2)
|
||||
evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP3529)
|
||||
}
|
||||
}
|
||||
if original == value {
|
||||
if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1)
|
||||
// EIP-8037 point (2): refund state gas directly to the reservoir
|
||||
// at the SSTORE restoration point (0→x→0 in same tx); not to the
|
||||
// refund counter, which is capped at gas_used/5.
|
||||
contract.Gas.RefundState(params.StorageCreationSize * evm.Context.CostPerStateByte)
|
||||
|
||||
// Regular portion of the refund still goes through the refund counter.
|
||||
evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929 - params.WarmStorageReadCostEIP2929)
|
||||
} else { // reset to original existing slot (2.2.2.2)
|
||||
// EIP 2200 Original clause:
|
||||
// evm.StateDB.AddRefund(params.SstoreResetGasEIP2200 - params.SloadGasEIP2200)
|
||||
// - SSTORE_RESET_GAS redefined as (5000 - COLD_SLOAD_COST)
|
||||
// - SLOAD_GAS redefined as WARM_STORAGE_READ_COST
|
||||
// Final: (5000 - COLD_SLOAD_COST) - WARM_STORAGE_READ_COST
|
||||
evm.StateDB.AddRefund((params.SstoreResetGasEIP2200 - params.ColdSloadCostEIP2929) - params.WarmStorageReadCostEIP2929)
|
||||
}
|
||||
}
|
||||
// EIP-2200 original clause:
|
||||
//return params.SloadGasEIP2200, nil // dirty update (2.2)
|
||||
return GasCosts{RegularGas: cost.RegularGas + params.WarmStorageReadCostEIP2929}, nil // dirty update (2.2)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,12 +97,12 @@ func TestEIP2200(t *testing.T) {
|
|||
Transfer: func(StateDB, common.Address, common.Address, *uint256.Int, *params.Rules) {},
|
||||
}
|
||||
evm := NewEVM(vmctx, statedb, params.AllEthashProtocolChanges, Config{ExtraEips: []int{2200}})
|
||||
initialGas := NewGasBudget(tt.gaspool)
|
||||
_, leftOver, err := evm.Call(common.Address{}, address, nil, initialGas.Copy(), new(uint256.Int))
|
||||
initialGas := NewGasBudget(tt.gaspool, 0)
|
||||
_, result, err := evm.Call(common.Address{}, address, nil, initialGas, new(uint256.Int))
|
||||
if !errors.Is(err, tt.failure) {
|
||||
t.Errorf("test %d: failure mismatch: have %v, want %v", i, err, tt.failure)
|
||||
}
|
||||
if used := leftOver.Used(initialGas); used != tt.used {
|
||||
if used := result.Used(initialGas); used != tt.used {
|
||||
t.Errorf("test %d: gas used mismatch: have %v, want %v", i, used, tt.used)
|
||||
}
|
||||
if refund := evm.StateDB.GetRefund(); refund != tt.refund {
|
||||
|
|
@ -157,12 +157,12 @@ func TestCreateGas(t *testing.T) {
|
|||
}
|
||||
|
||||
evm := NewEVM(vmctx, statedb, chainConfig, config)
|
||||
initialGas := NewGasBudget(uint64(testGas))
|
||||
ret, leftOver, err := evm.Call(common.Address{}, address, nil, initialGas.Copy(), new(uint256.Int))
|
||||
initialGas := NewGasBudget(uint64(testGas), 0)
|
||||
ret, result, err := evm.Call(common.Address{}, address, nil, initialGas, new(uint256.Int))
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
gasUsed = leftOver.Used(initialGas)
|
||||
gasUsed = result.Used(initialGas)
|
||||
if len(ret) != 32 {
|
||||
t.Fatalf("test %d: expected 32 bytes returned, have %d", i, len(ret))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,11 +20,11 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
)
|
||||
|
||||
// GasCosts denotes a vector of gas costs in the
|
||||
// multidimensional metering paradigm. It represents the cost
|
||||
// charged by an individual operation.
|
||||
// GasCosts denotes a vector of gas costs in the multidimensional metering
|
||||
// paradigm. It represents the cost charged by an individual operation.
|
||||
type GasCosts struct {
|
||||
RegularGas uint64
|
||||
StateGas uint64
|
||||
|
|
@ -40,67 +40,254 @@ func (g GasCosts) String() string {
|
|||
return fmt.Sprintf("<%v,%v>", g.RegularGas, g.StateGas)
|
||||
}
|
||||
|
||||
// GasBudget denotes a vector of remaining gas allowances available
|
||||
// for EVM execution in the multidimensional metering paradigm.
|
||||
// Unlike GasCosts which represents the price of an operation,
|
||||
// GasBudget tracks how much gas is left to spend.
|
||||
// GasBudget is the unified gas-state structure used throughout the EVM.
|
||||
// It carries two pairs of fields:
|
||||
//
|
||||
// - RegularGas / StateGas: the running balance during execution, or the
|
||||
// leftover balance the caller must absorb after a sub-call.
|
||||
// - UsedRegularGas / UsedStateGas: per-frame accumulators tracking gross
|
||||
// consumption. UsedStateGas is signed so it can be decremented by inline
|
||||
// state-gas refunds (e.g., SSTORE 0->A->0).
|
||||
//
|
||||
// The same struct serves three roles:
|
||||
//
|
||||
// - During execution: Charge / ChargeRegular / ChargeState / RefundState
|
||||
// and RefundRegular mutate the running balance and the usage accumulators
|
||||
// in lockstep.
|
||||
//
|
||||
// - At frame exit: ExitSuccess / ExitRevert / ExitHalt produce a new
|
||||
// GasBudget in "leftover" form that packages the result for the caller.
|
||||
//
|
||||
// - At absorption: the caller's Absorb method merges the child's leftover
|
||||
// budget into its own running budget.
|
||||
type GasBudget struct {
|
||||
RegularGas uint64 // The leftover gas for execution and state gas usage
|
||||
StateGas uint64 // The state gas reservoir
|
||||
RegularGas uint64 // remaining regular-gas balance (or leftover for caller to absorb)
|
||||
StateGas uint64 // remaining state-gas reservoir (or leftover for caller to absorb)
|
||||
UsedRegularGas uint64 // gross regular gas consumed in this frame
|
||||
UsedStateGas int64 // signed net state-gas consumed in this frame
|
||||
}
|
||||
|
||||
// NewGasBudget creates a GasBudget with the given initial regular gas allowance.
|
||||
func NewGasBudget(gas uint64) GasBudget {
|
||||
return GasBudget{RegularGas: gas}
|
||||
// NewGasBudget initializes a fresh GasBudget for execution / forwarding,
|
||||
// with both usage accumulators set to zero.
|
||||
func NewGasBudget(regular, state uint64) GasBudget {
|
||||
return GasBudget{RegularGas: regular, StateGas: state}
|
||||
}
|
||||
|
||||
// Used returns the amount of regular gas consumed so far.
|
||||
// Used returns the total scalar gas consumed relative to an initial budget
|
||||
// (= (initial.regular + initial.state) − (current.regular + current.state)).
|
||||
// This is the payment scalar (EIP-8037's tx_gas_used_before_refund).
|
||||
func (g GasBudget) Used(initial GasBudget) uint64 {
|
||||
return initial.RegularGas - g.RegularGas
|
||||
return (initial.RegularGas + initial.StateGas) - (g.RegularGas + g.StateGas)
|
||||
}
|
||||
|
||||
// Exhaust sets all remaining gas to zero, preserving the initial amount
|
||||
// for usage tracking.
|
||||
func (g *GasBudget) Exhaust() {
|
||||
g.RegularGas = 0
|
||||
g.StateGas = 0
|
||||
}
|
||||
|
||||
func (g *GasBudget) Copy() GasBudget {
|
||||
return GasBudget{RegularGas: g.RegularGas, StateGas: g.StateGas}
|
||||
}
|
||||
|
||||
// String returns a visual representation of the gas budget vector.
|
||||
// String returns a visual representation of the budget.
|
||||
func (g GasBudget) String() string {
|
||||
return fmt.Sprintf("<%v,%v>", g.RegularGas, g.StateGas)
|
||||
return fmt.Sprintf("<%v,%v,used=<%v,%v>>", g.RegularGas, g.StateGas, g.UsedRegularGas, g.UsedStateGas)
|
||||
}
|
||||
|
||||
// CanAfford reports whether the budget has sufficient gas to cover the cost.
|
||||
// CanAfford reports whether the running balance can cover the given cost.
|
||||
// State-gas charges that exceed the reservoir spill into regular gas.
|
||||
func (g GasBudget) CanAfford(cost GasCosts) bool {
|
||||
return g.RegularGas >= cost.RegularGas
|
||||
if g.RegularGas < cost.RegularGas {
|
||||
return false
|
||||
}
|
||||
if cost.StateGas > g.StateGas {
|
||||
spillover := cost.StateGas - g.StateGas
|
||||
if spillover > g.RegularGas-cost.RegularGas {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Charge deducts the given gas cost from the budget. It returns the
|
||||
// pre-charge budget and false if the budget does not have sufficient
|
||||
// gas to cover the cost.
|
||||
// Charge deducts a combined regular+state cost from the running balance and
|
||||
// updates the usage accumulators. State-gas in excess of the reservoir spills
|
||||
// into regular_gas.
|
||||
func (g *GasBudget) Charge(cost GasCosts) (GasBudget, bool) {
|
||||
prior := *g
|
||||
if g.RegularGas < cost.RegularGas {
|
||||
if !g.CanAfford(cost) {
|
||||
return prior, false
|
||||
}
|
||||
// Charge regular gas
|
||||
g.RegularGas -= cost.RegularGas
|
||||
return prior, true
|
||||
}
|
||||
g.UsedRegularGas += cost.RegularGas
|
||||
|
||||
// Refund adds the given gas budget back. It returns the pre-refund budget
|
||||
// and whether the budget was actually changed.
|
||||
func (g *GasBudget) Refund(other GasBudget) (GasBudget, bool) {
|
||||
prior := *g
|
||||
g.RegularGas += other.RegularGas
|
||||
return prior, g.RegularGas != prior.RegularGas
|
||||
// Charge state gas
|
||||
if cost.StateGas > g.StateGas {
|
||||
spillover := cost.StateGas - g.StateGas
|
||||
g.StateGas = 0
|
||||
g.RegularGas -= spillover
|
||||
} else {
|
||||
g.StateGas -= cost.StateGas
|
||||
}
|
||||
g.UsedStateGas += int64(cost.StateGas)
|
||||
return prior, true
|
||||
}
|
||||
|
||||
// AsTracing converts the GasBudget into the tracing-facing Gas vector.
|
||||
func (g GasBudget) AsTracing() tracing.Gas {
|
||||
return tracing.Gas{Regular: g.RegularGas, State: g.StateGas}
|
||||
}
|
||||
|
||||
// ChargeRegular is a convenience that deducts a regular-only cost.
|
||||
func (g *GasBudget) ChargeRegular(r uint64) (GasBudget, bool) {
|
||||
return g.Charge(GasCosts{RegularGas: r})
|
||||
}
|
||||
|
||||
// ChargeState is a convenience that deducts a state-only cost (spills to
|
||||
// regular when the reservoir is exhausted). Returns false on OOG.
|
||||
func (g *GasBudget) ChargeState(s uint64) (GasBudget, bool) {
|
||||
return g.Charge(GasCosts{StateGas: s})
|
||||
}
|
||||
|
||||
// IsZero returns an indicator if the gas budget has been exhausted.
|
||||
func (g *GasBudget) IsZero() bool {
|
||||
return g.RegularGas == 0 && g.StateGas == 0
|
||||
}
|
||||
|
||||
// RefundState applies an inline state-gas refund (e.g., SSTORE 0->A->0).
|
||||
// The reservoir is credited and the signed usage counter is decremented
|
||||
// in lockstep, preserving the per-frame invariant:
|
||||
//
|
||||
// StateGas + UsedStateGas == initialStateGas + spillover_so_far
|
||||
//
|
||||
// which the revert path relies on for the correct gross refund.
|
||||
func (g *GasBudget) RefundState(s uint64) {
|
||||
g.StateGas += s
|
||||
g.UsedStateGas -= int64(s)
|
||||
}
|
||||
|
||||
// Forward drains `regular` regular gas and the entire state reservoir from
|
||||
// the parent's running budget and returns the initial GasBudget for a child
|
||||
// frame. The parent's UsedRegularGas is bumped by the forwarded amount so
|
||||
// that the absorb-on-return path correctly reclaims the unused portion.
|
||||
//
|
||||
// Used by frame boundaries where the regular forward has NOT been pre-
|
||||
// deducted: tx-level dispatch (state_transition) and CREATE / CREATE2. The
|
||||
// CALL family pre-deducts the forward via the dynamic gas table for tracer-
|
||||
// reporting reasons and therefore constructs its child budget directly.
|
||||
//
|
||||
// Caller must ensure `regular` does not exceed the running balance and
|
||||
// apply any EIP-150 1/64 retention before calling Forward.
|
||||
func (g *GasBudget) Forward(regular uint64) GasBudget {
|
||||
g.RegularGas -= regular
|
||||
g.UsedRegularGas += regular
|
||||
|
||||
child := GasBudget{
|
||||
RegularGas: regular,
|
||||
StateGas: g.StateGas,
|
||||
}
|
||||
g.StateGas = 0
|
||||
return child
|
||||
}
|
||||
|
||||
// ForwardAll forwards the parent's full remaining budget (both regular and
|
||||
// state) to a child frame. Equivalent to Forward(g.RegularGas) — used at
|
||||
// the tx boundary where there is no 1/64 retention.
|
||||
func (g *GasBudget) ForwardAll() GasBudget {
|
||||
return g.Forward(g.RegularGas)
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Exit-form constructors. These take a post-execution running budget and
|
||||
// produce a new GasBudget in "leftover form", the value the caller should
|
||||
// absorb to update its own state.
|
||||
// ============================================================================
|
||||
|
||||
// ExitSuccess produces the leftover form for a successful frame. Inline
|
||||
// state-gas refunds have already been folded into StateGas / UsedStateGas
|
||||
// during execution; the running budget IS the exit budget on success.
|
||||
func (g GasBudget) ExitSuccess() GasBudget {
|
||||
return g
|
||||
}
|
||||
|
||||
// ExitRevert produces the leftover for a REVERT exit. Per EIP-8037, all state
|
||||
// gas charged by the reverted frame is refunded to the caller's reservoir:
|
||||
//
|
||||
// leftover.StateGas = StateGas + UsedStateGas
|
||||
//
|
||||
// UsedStateGas is reset since the frame's state changes are discarded.
|
||||
func (g GasBudget) ExitRevert() GasBudget {
|
||||
reservoir := int64(g.StateGas) + g.UsedStateGas
|
||||
if reservoir < 0 {
|
||||
// Reservoir should never be negative. By construction it equals
|
||||
// the initial state-gas allocation plus any spillover to regular
|
||||
// gas.
|
||||
reservoir = 0
|
||||
log.Warn("Negative reservoir at revert", "remaining", g.StateGas, "used", g.UsedStateGas)
|
||||
}
|
||||
return GasBudget{
|
||||
RegularGas: g.RegularGas,
|
||||
StateGas: uint64(reservoir),
|
||||
UsedRegularGas: g.UsedRegularGas,
|
||||
UsedStateGas: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// ExitHalt produces the leftover for an exceptional halt.
|
||||
//
|
||||
// - state_gas_reservoir is reset back to its value at the start of the child frame
|
||||
// - the gas_left initially given to the child is consumed (set to zero)
|
||||
func (g GasBudget) ExitHalt(initStateReservoir uint64) GasBudget {
|
||||
reservoir := int64(g.StateGas) + g.UsedStateGas
|
||||
if reservoir < 0 {
|
||||
// Reservoir should never be negative. By construction it equals
|
||||
// the initial state-gas allocation plus any spillover to regular
|
||||
// gas.
|
||||
reservoir = 0
|
||||
log.Warn("Negative reservoir at halt", "remaining", g.StateGas, "used", g.UsedStateGas)
|
||||
}
|
||||
// The portion of state gas charged from regular gas is also burned
|
||||
// together with the regular gas, rather than being returned to the
|
||||
// parent's state-gas reservoir.
|
||||
var spilled uint64
|
||||
if uint64(reservoir) > initStateReservoir {
|
||||
spilled = uint64(reservoir) - initStateReservoir
|
||||
}
|
||||
return GasBudget{
|
||||
RegularGas: 0,
|
||||
StateGas: initStateReservoir,
|
||||
UsedRegularGas: g.UsedRegularGas + g.RegularGas + spilled,
|
||||
UsedStateGas: 0,
|
||||
}
|
||||
}
|
||||
|
||||
// Exit dispatches on err to the appropriate exit-form constructor
|
||||
// for the post-evm.Run path:
|
||||
//
|
||||
// - err == nil → ExitSuccess
|
||||
// - err == ErrExecutionReverted → ExitRevert
|
||||
// - any other err → ExitHalt
|
||||
//
|
||||
// Soft validation failures (occurring BEFORE evm.Run) should call Preserved
|
||||
// directly instead of going through this dispatcher.
|
||||
func (g GasBudget) Exit(err error, initStateReservoir uint64) GasBudget {
|
||||
switch {
|
||||
case err == nil:
|
||||
return g.ExitSuccess()
|
||||
case err == ErrExecutionReverted:
|
||||
return g.ExitRevert()
|
||||
default:
|
||||
return g.ExitHalt(initStateReservoir)
|
||||
}
|
||||
}
|
||||
|
||||
// Absorb merges a sub-call's leftover GasBudget into this (caller's) running
|
||||
// budget. Additionally, it does an EIP-8037 spillover correction:
|
||||
// state-gas that spilled into the regular pool inside the child frame is
|
||||
// excluded from the UsedRegularGas.
|
||||
//
|
||||
// spillover = forwarded - child.RegularGas - child.UsedRegularGas
|
||||
//
|
||||
// forwarded is the regular-gas amount that was passed to the child at call
|
||||
// entry (i.e., the regular initial of the child's GasBudget).
|
||||
func (g *GasBudget) Absorb(child GasBudget, forwarded uint64) {
|
||||
spillover := forwarded - child.RegularGas - child.UsedRegularGas
|
||||
|
||||
g.UsedRegularGas -= child.RegularGas
|
||||
g.RegularGas += child.RegularGas
|
||||
g.StateGas = child.StateGas
|
||||
g.UsedStateGas += child.UsedStateGas
|
||||
|
||||
g.UsedRegularGas -= spillover
|
||||
}
|
||||
|
|
|
|||
|
|
@ -647,25 +647,22 @@ func opSwap16(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
}
|
||||
|
||||
func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||
if evm.readOnly {
|
||||
return nil, ErrWriteProtection
|
||||
}
|
||||
var (
|
||||
value = scope.Stack.pop()
|
||||
offset, size = scope.Stack.pop(), scope.Stack.pop()
|
||||
input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64())
|
||||
gas = scope.Contract.Gas.RegularGas
|
||||
)
|
||||
// Apply EIP-150 to the regular gas left after the state charge.
|
||||
forward := scope.Contract.Gas.RegularGas
|
||||
if evm.chainRules.IsEIP150 {
|
||||
gas -= gas / 64
|
||||
forward -= forward / 64
|
||||
}
|
||||
|
||||
// reuse size int for stackvalue
|
||||
stackvalue := size
|
||||
|
||||
scope.Contract.UseGas(GasCosts{RegularGas: gas}, evm.Config.Tracer, tracing.GasChangeCallContractCreation)
|
||||
|
||||
res, addr, returnGas, suberr := evm.Create(scope.Contract.Address(), input, NewGasBudget(gas), &value)
|
||||
child := scope.Contract.forwardGas(forward, evm.Config.Tracer, tracing.GasChangeCallContractCreation)
|
||||
res, addr, result, suberr := evm.Create(scope.Contract.Address(), input, child, &value)
|
||||
// Push item on the stack based on the returned error. If the ruleset is
|
||||
// homestead we must check for CodeStoreOutOfGasError (homestead only
|
||||
// rule) and treat as an error, if the ruleset is frontier we must
|
||||
|
|
@ -679,7 +676,8 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
}
|
||||
scope.Stack.push(&stackvalue)
|
||||
|
||||
scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||
// Refund the leftover gas back to current frame
|
||||
scope.Contract.refundGas(result, forward, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||
|
||||
if suberr == ErrExecutionReverted {
|
||||
evm.returnData = res // set REVERT data to return data buffer
|
||||
|
|
@ -690,24 +688,20 @@ func opCreate(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
}
|
||||
|
||||
func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
||||
if evm.readOnly {
|
||||
return nil, ErrWriteProtection
|
||||
}
|
||||
var (
|
||||
endowment = scope.Stack.pop()
|
||||
offset, size = scope.Stack.pop(), scope.Stack.pop()
|
||||
salt = scope.Stack.pop()
|
||||
input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64())
|
||||
gas = scope.Contract.Gas.RegularGas
|
||||
)
|
||||
// Apply EIP-150 to the regular gas left after the state charge.
|
||||
forward := scope.Contract.Gas.RegularGas
|
||||
forward -= forward / 64
|
||||
|
||||
// Apply EIP150
|
||||
gas -= gas / 64
|
||||
scope.Contract.UseGas(GasCosts{RegularGas: gas}, evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
|
||||
// reuse size int for stackvalue
|
||||
stackvalue := size
|
||||
res, addr, returnGas, suberr := evm.Create2(scope.Contract.Address(), input, NewGasBudget(gas),
|
||||
&endowment, &salt)
|
||||
child := scope.Contract.forwardGas(forward, evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
|
||||
res, addr, result, suberr := evm.Create2(scope.Contract.Address(), input, child, &endowment, &salt)
|
||||
// Push item on the stack based on the returned error.
|
||||
if suberr != nil {
|
||||
stackvalue.Clear()
|
||||
|
|
@ -715,7 +709,9 @@ func opCreate2(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
stackvalue.SetBytes(addr.Bytes())
|
||||
}
|
||||
scope.Stack.push(&stackvalue)
|
||||
scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||
|
||||
// Refund the leftover gas back to current frame
|
||||
scope.Contract.refundGas(result, forward, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||
|
||||
if suberr == ErrExecutionReverted {
|
||||
evm.returnData = res // set REVERT data to return data buffer
|
||||
|
|
@ -743,7 +739,12 @@ func opCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
if !value.IsZero() {
|
||||
gas += params.CallStipend
|
||||
}
|
||||
ret, returnGas, err := evm.Call(scope.Contract.Address(), toAddr, args, NewGasBudget(gas), &value)
|
||||
|
||||
// Regular gas for the forward was already pre-deducted by the dynamic
|
||||
// gas table (see makeCallVariantGasCallEIP*); only the state reservoir
|
||||
// needs to be handed off to the child here.
|
||||
childBudget := NewGasBudget(gas, scope.Contract.Gas.StateGas)
|
||||
ret, result, err := evm.Call(scope.Contract.Address(), toAddr, args, childBudget, &value)
|
||||
|
||||
if err != nil {
|
||||
temp.Clear()
|
||||
|
|
@ -751,11 +752,11 @@ func opCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
temp.SetOne()
|
||||
}
|
||||
stack.push(&temp)
|
||||
|
||||
if err == nil || err == ErrExecutionReverted {
|
||||
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
||||
}
|
||||
|
||||
scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||
scope.Contract.refundGas(result, gas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||
|
||||
evm.returnData = ret
|
||||
return ret, nil
|
||||
|
|
@ -776,8 +777,11 @@ func opCallCode(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
if !value.IsZero() {
|
||||
gas += params.CallStipend
|
||||
}
|
||||
|
||||
ret, returnGas, err := evm.CallCode(scope.Contract.Address(), toAddr, args, NewGasBudget(gas), &value)
|
||||
// Regular gas for the forward was already pre-deducted by the dynamic
|
||||
// gas table, only the state reservoir needs to be handed off to the
|
||||
// child here.
|
||||
childBudget := NewGasBudget(gas, scope.Contract.Gas.StateGas)
|
||||
ret, result, err := evm.CallCode(scope.Contract.Address(), toAddr, args, childBudget, &value)
|
||||
if err != nil {
|
||||
temp.Clear()
|
||||
} else {
|
||||
|
|
@ -788,7 +792,7 @@ func opCallCode(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
||||
}
|
||||
|
||||
scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||
scope.Contract.refundGas(result, gas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||
|
||||
evm.returnData = ret
|
||||
return ret, nil
|
||||
|
|
@ -806,7 +810,11 @@ func opDelegateCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
// Get arguments from the memory.
|
||||
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
|
||||
|
||||
ret, returnGas, err := evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, NewGasBudget(gas), scope.Contract.value)
|
||||
// Regular gas for the forward was already pre-deducted by the dynamic
|
||||
// gas table, only the state reservoir needs to be handed off to the
|
||||
// child here.
|
||||
childBudget := NewGasBudget(gas, scope.Contract.Gas.StateGas)
|
||||
ret, result, err := evm.DelegateCall(scope.Contract.Caller(), scope.Contract.Address(), toAddr, args, childBudget, scope.Contract.value)
|
||||
if err != nil {
|
||||
temp.Clear()
|
||||
} else {
|
||||
|
|
@ -816,8 +824,7 @@ func opDelegateCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
if err == nil || err == ErrExecutionReverted {
|
||||
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
||||
}
|
||||
|
||||
scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||
scope.Contract.refundGas(result, gas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||
|
||||
evm.returnData = ret
|
||||
return ret, nil
|
||||
|
|
@ -835,7 +842,11 @@ func opStaticCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
// Get arguments from the memory.
|
||||
args := scope.Memory.GetPtr(inOffset.Uint64(), inSize.Uint64())
|
||||
|
||||
ret, returnGas, err := evm.StaticCall(scope.Contract.Address(), toAddr, args, NewGasBudget(gas))
|
||||
// Regular gas for the forward was already pre-deducted by the dynamic
|
||||
// gas table, only the state reservoir needs to be handed off to the
|
||||
// child here.
|
||||
childBudget := NewGasBudget(gas, scope.Contract.Gas.StateGas)
|
||||
ret, result, err := evm.StaticCall(scope.Contract.Address(), toAddr, args, childBudget)
|
||||
if err != nil {
|
||||
temp.Clear()
|
||||
} else {
|
||||
|
|
@ -846,7 +857,7 @@ func opStaticCall(pc *uint64, evm *EVM, scope *ScopeContext) ([]byte, error) {
|
|||
scope.Memory.Set(retOffset.Uint64(), retSize.Uint64(), ret)
|
||||
}
|
||||
|
||||
scope.Contract.RefundGas(returnGas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||
scope.Contract.refundGas(result, gas, evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||
|
||||
evm.returnData = ret
|
||||
return ret, nil
|
||||
|
|
|
|||
|
|
@ -174,7 +174,7 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
|
|||
// associated costs.
|
||||
contractAddr := contract.Address()
|
||||
consumed, wanted := evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false, contract.Gas.RegularGas)
|
||||
contract.UseGas(GasCosts{RegularGas: consumed}, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
|
||||
contract.chargeRegular(consumed, evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk)
|
||||
if consumed < wanted {
|
||||
return nil, ErrOutOfGas
|
||||
}
|
||||
|
|
@ -192,10 +192,8 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
|
|||
return nil, &ErrStackOverflow{stackLen: sLen, limit: operation.maxStack}
|
||||
}
|
||||
// for tracing: this gas consumption event is emitted below in the debug section.
|
||||
if contract.Gas.RegularGas < cost {
|
||||
if !contract.chargeRegular(cost, nil, tracing.GasChangeIgnored) {
|
||||
return nil, ErrOutOfGas
|
||||
} else {
|
||||
contract.Gas.RegularGas -= cost
|
||||
}
|
||||
|
||||
// All ops with a dynamic memory usage also has a dynamic gas cost.
|
||||
|
|
@ -224,11 +222,13 @@ func (evm *EVM) Run(contract *Contract, input []byte, readOnly bool) (ret []byte
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: %v", ErrOutOfGas, err)
|
||||
}
|
||||
// for tracing: this gas consumption event is emitted below in the debug section.
|
||||
if contract.Gas.RegularGas < dynamicCost.RegularGas {
|
||||
// EIP-8037: charge regular gas before state gas. The state charge
|
||||
// is a no-op when dynamicCost.StateGas == 0 (e.g., pre-Amsterdam).
|
||||
if !contract.chargeRegular(dynamicCost.RegularGas, nil, tracing.GasChangeIgnored) {
|
||||
return nil, ErrOutOfGas
|
||||
}
|
||||
if !contract.chargeState(dynamicCost.StateGas, nil, tracing.GasChangeIgnored) {
|
||||
return nil, ErrOutOfGas
|
||||
} else {
|
||||
contract.Gas.RegularGas -= dynamicCost.RegularGas
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ func TestLoopInterrupt(t *testing.T) {
|
|||
timeout := make(chan bool)
|
||||
|
||||
go func(evm *EVM) {
|
||||
_, _, err := evm.Call(common.Address{}, address, nil, NewGasBudget(math.MaxUint64), new(uint256.Int))
|
||||
_, _, err := evm.Call(common.Address{}, address, nil, NewGasBudget(math.MaxUint64, 0), new(uint256.Int))
|
||||
errChannel <- err
|
||||
}(evm)
|
||||
|
||||
|
|
@ -85,7 +85,7 @@ func BenchmarkInterpreter(b *testing.B) {
|
|||
value = uint256.NewInt(0)
|
||||
stack = newStackForTesting()
|
||||
mem = NewMemory()
|
||||
contract = NewContract(common.Address{}, common.Address{}, value, NewGasBudget(startGas), nil)
|
||||
contract = NewContract(common.Address{}, common.Address{}, value, NewGasBudget(startGas, 0), nil)
|
||||
)
|
||||
stack.push(uint256.NewInt(123))
|
||||
stack.push(uint256.NewInt(123))
|
||||
|
|
|
|||
|
|
@ -23,8 +23,9 @@ import (
|
|||
)
|
||||
|
||||
type (
|
||||
executionFunc func(pc *uint64, evm *EVM, callContext *ScopeContext) ([]byte, error)
|
||||
gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (GasCosts, error) // last parameter is the requested memory size as a uint64
|
||||
executionFunc func(pc *uint64, evm *EVM, callContext *ScopeContext) ([]byte, error)
|
||||
gasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (GasCosts, error) // last parameter is the requested memory size as a uint64
|
||||
intrinsicGasFunc func(*EVM, *Contract, *Stack, *Memory, uint64) (uint64, error) // last parameter is the requested memory size as a uint64
|
||||
// memorySizeFunc returns the required size, and whether the operation overflowed a uint64
|
||||
memorySizeFunc func(*Stack) (size uint64, overflow bool)
|
||||
)
|
||||
|
|
@ -97,6 +98,7 @@ func newAmsterdamInstructionSet() JumpTable {
|
|||
instructionSet := newOsakaInstructionSet()
|
||||
enable7843(&instructionSet) // EIP-7843 (SLOTNUM opcode)
|
||||
enable8024(&instructionSet) // EIP-8024 (Backward compatible SWAPN, DUPN, EXCHANGE)
|
||||
enable8037(&instructionSet) // EIP-8037 (State creation gas cost increase)
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -168,7 +168,7 @@ func makeCallVariantGasCallEIP2929(oldCalculator gasFunc, addressPosition int) g
|
|||
evm.StateDB.AddAddressToAccessList(addr)
|
||||
// Charge the remaining difference here already, to correctly calculate available
|
||||
// gas for call
|
||||
if !contract.UseGas(GasCosts{RegularGas: coldCost}, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||
if !contract.chargeRegular(coldCost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||
return GasCosts{}, ErrOutOfGas
|
||||
}
|
||||
}
|
||||
|
|
@ -276,7 +276,7 @@ func gasCallEIP7702(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
|
|||
return innerGasCallEIP7702(evm, contract, stack, mem, memorySize)
|
||||
}
|
||||
|
||||
func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
|
||||
func makeCallVariantGasCallEIP7702(intrinsicFunc intrinsicGasFunc) gasFunc {
|
||||
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (GasCosts, error) {
|
||||
var (
|
||||
eip2929Cost uint64
|
||||
|
|
@ -295,7 +295,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
|
|||
|
||||
// Charge the remaining difference here already, to correctly calculate
|
||||
// available gas for call
|
||||
if !contract.UseGas(GasCosts{RegularGas: eip2929Cost}, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||
if !contract.chargeRegular(eip2929Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||
return GasCosts{}, ErrOutOfGas
|
||||
}
|
||||
}
|
||||
|
|
@ -312,7 +312,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
|
|||
// Terminate the gas measurement if the leftover gas is not sufficient,
|
||||
// it can effectively prevent accessing the states in the following steps.
|
||||
// It's an essential safeguard before any stateful check.
|
||||
if contract.Gas.RegularGas < intrinsicCost.RegularGas {
|
||||
if contract.Gas.RegularGas < intrinsicCost {
|
||||
return GasCosts{}, ErrOutOfGas
|
||||
}
|
||||
|
||||
|
|
@ -324,13 +324,13 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
|
|||
evm.StateDB.AddAddressToAccessList(target)
|
||||
eip7702Cost = params.ColdAccountAccessCostEIP2929
|
||||
}
|
||||
if !contract.UseGas(GasCosts{RegularGas: eip7702Cost}, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||
if !contract.chargeRegular(eip7702Cost, evm.Config.Tracer, tracing.GasChangeCallStorageColdAccess) {
|
||||
return GasCosts{}, ErrOutOfGas
|
||||
}
|
||||
}
|
||||
// Calculate the gas budget for the nested call. The costs defined by
|
||||
// EIP-2929 and EIP-7702 have already been applied.
|
||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsicCost.RegularGas, stack.back(0))
|
||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas.RegularGas, intrinsicCost, stack.back(0))
|
||||
if err != nil {
|
||||
return GasCosts{}, err
|
||||
}
|
||||
|
|
@ -339,6 +339,10 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
|
|||
// part of the dynamic gas. This will ensure it is correctly reported to
|
||||
// tracers.
|
||||
contract.Gas.RegularGas += eip2929Cost + eip7702Cost
|
||||
// Undo the RegularGasUsed increments from the direct UseGas charges,
|
||||
// since this gas will be re-charged via the returned cost.
|
||||
contract.Gas.UsedRegularGas -= eip2929Cost
|
||||
contract.Gas.UsedRegularGas -= eip7702Cost
|
||||
|
||||
// Aggregate the gas costs from all components, including EIP-2929, EIP-7702,
|
||||
// the CALL opcode itself, and the cost incurred by nested calls.
|
||||
|
|
@ -349,7 +353,7 @@ func makeCallVariantGasCallEIP7702(intrinsicFunc gasFunc) gasFunc {
|
|||
if totalCost, overflow = math.SafeAdd(eip2929Cost, eip7702Cost); overflow {
|
||||
return GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
if totalCost, overflow = math.SafeAdd(totalCost, intrinsicCost.RegularGas); overflow {
|
||||
if totalCost, overflow = math.SafeAdd(totalCost, intrinsicCost); overflow {
|
||||
return GasCosts{}, ErrGasUintOverflow
|
||||
}
|
||||
if totalCost, overflow = math.SafeAdd(totalCost, evm.callGasTemp); overflow {
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ package runtime
|
|||
import (
|
||||
"github.com/ethereum/go-ethereum/core"
|
||||
"github.com/ethereum/go-ethereum/core/vm"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
|
|
@ -29,17 +30,18 @@ func NewEnv(cfg *Config) *vm.EVM {
|
|||
BlobHashes: cfg.BlobHashes,
|
||||
}
|
||||
blockContext := vm.BlockContext{
|
||||
CanTransfer: core.CanTransfer,
|
||||
Transfer: core.Transfer,
|
||||
GetHash: cfg.GetHashFn,
|
||||
Coinbase: cfg.Coinbase,
|
||||
BlockNumber: cfg.BlockNumber,
|
||||
Time: cfg.Time,
|
||||
Difficulty: cfg.Difficulty,
|
||||
GasLimit: cfg.GasLimit,
|
||||
BaseFee: cfg.BaseFee,
|
||||
BlobBaseFee: cfg.BlobBaseFee,
|
||||
Random: cfg.Random,
|
||||
CanTransfer: core.CanTransfer,
|
||||
Transfer: core.Transfer,
|
||||
GetHash: cfg.GetHashFn,
|
||||
Coinbase: cfg.Coinbase,
|
||||
BlockNumber: cfg.BlockNumber,
|
||||
Time: cfg.Time,
|
||||
Difficulty: cfg.Difficulty,
|
||||
GasLimit: cfg.GasLimit,
|
||||
BaseFee: cfg.BaseFee,
|
||||
BlobBaseFee: cfg.BlobBaseFee,
|
||||
Random: cfg.Random,
|
||||
CostPerStateByte: params.CostPerStateByte,
|
||||
}
|
||||
|
||||
evm := vm.NewEVM(blockContext, cfg.State, cfg.ChainConfig, cfg.EVMConfig)
|
||||
|
|
|
|||
|
|
@ -144,15 +144,15 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
|
|||
// set the receiver's (the executing contract) code for execution.
|
||||
cfg.State.SetCode(address, code, tracing.CodeChangeUnspecified)
|
||||
// Call the code with the given configuration.
|
||||
ret, leftOverGas, err := vmenv.Call(
|
||||
ret, result, err := vmenv.Call(
|
||||
cfg.Origin,
|
||||
common.BytesToAddress([]byte("contract")),
|
||||
input,
|
||||
vm.NewGasBudget(cfg.GasLimit),
|
||||
vm.NewGasBudget(cfg.GasLimit, 0),
|
||||
uint256.MustFromBig(cfg.Value),
|
||||
)
|
||||
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil {
|
||||
cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - leftOverGas.RegularGas}, err)
|
||||
cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - result.RegularGas}, err)
|
||||
}
|
||||
return ret, cfg.State, err
|
||||
}
|
||||
|
|
@ -179,16 +179,16 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
|
|||
// - reset transient storage(eip 1153)
|
||||
cfg.State.Prepare(rules, cfg.Origin, cfg.Coinbase, nil, vm.ActivePrecompiles(rules), nil)
|
||||
// Call the code with the given configuration.
|
||||
code, address, leftOverGas, err := vmenv.Create(
|
||||
code, address, result, err := vmenv.Create(
|
||||
cfg.Origin,
|
||||
input,
|
||||
vm.NewGasBudget(cfg.GasLimit),
|
||||
vm.NewGasBudget(cfg.GasLimit, 0),
|
||||
uint256.MustFromBig(cfg.Value),
|
||||
)
|
||||
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil {
|
||||
cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - leftOverGas.RegularGas}, err)
|
||||
cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - result.RegularGas}, err)
|
||||
}
|
||||
return code, address, leftOverGas.RegularGas, err
|
||||
return code, address, result.RegularGas, err
|
||||
}
|
||||
|
||||
// Call executes the code given by the contract's address. It will return the
|
||||
|
|
@ -213,15 +213,15 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er
|
|||
statedb.Prepare(rules, cfg.Origin, cfg.Coinbase, &address, vm.ActivePrecompiles(rules), nil)
|
||||
|
||||
// Call the code with the given configuration.
|
||||
ret, leftOverGas, err := vmenv.Call(
|
||||
ret, result, err := vmenv.Call(
|
||||
cfg.Origin,
|
||||
address,
|
||||
input,
|
||||
vm.NewGasBudget(cfg.GasLimit),
|
||||
vm.NewGasBudget(cfg.GasLimit, 0),
|
||||
uint256.MustFromBig(cfg.Value),
|
||||
)
|
||||
if cfg.EVMConfig.Tracer != nil && cfg.EVMConfig.Tracer.OnTxEnd != nil {
|
||||
cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - leftOverGas.RegularGas}, err)
|
||||
cfg.EVMConfig.Tracer.OnTxEnd(&types.Receipt{GasUsed: cfg.GasLimit - result.RegularGas}, err)
|
||||
}
|
||||
return ret, leftOverGas.RegularGas, err
|
||||
return ret, result.RegularGas, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ func runTrace(tracer *tracers.Tracer, vmctx *vmContext, chaincfg *params.ChainCo
|
|||
gasLimit uint64 = 31000
|
||||
startGas uint64 = 10000
|
||||
value = uint256.NewInt(0)
|
||||
contract = vm.NewContract(common.Address{}, common.Address{}, value, vm.NewGasBudget(startGas), nil)
|
||||
contract = vm.NewContract(common.Address{}, common.Address{}, value, vm.NewGasBudget(startGas, 0), nil)
|
||||
)
|
||||
evm.SetTxContext(vmctx.txCtx)
|
||||
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x1, 0x0}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ func TestStoreCapture(t *testing.T) {
|
|||
var (
|
||||
logger = NewStructLogger(nil)
|
||||
evm = vm.NewEVM(vm.BlockContext{}, &dummyStatedb{}, params.TestChainConfig, vm.Config{Tracer: logger.Hooks()})
|
||||
contract = vm.NewContract(common.Address{}, common.Address{}, new(uint256.Int), vm.NewGasBudget(100000), nil)
|
||||
contract = vm.NewContract(common.Address{}, common.Address{}, new(uint256.Int), vm.NewGasBudget(100000, 0), nil)
|
||||
)
|
||||
contract.Code = []byte{byte(vm.PUSH1), 0x1, byte(vm.PUSH1), 0x0, byte(vm.SSTORE)}
|
||||
var index common.Hash
|
||||
|
|
|
|||
|
|
@ -88,6 +88,7 @@ const (
|
|||
LogTopicGas uint64 = 375 // Multiplied by the * of the LOG*, per LOG transaction. e.g. LOG0 incurs 0 * c_txLogTopicGas, LOG4 incurs 4 * c_txLogTopicGas.
|
||||
CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction.
|
||||
Create2Gas uint64 = 32000 // Once per CREATE2 operation
|
||||
CreateGasAmsterdam uint64 = 9000 // Regular gas portion of CREATE in Amsterdam (EIP-8037); state gas is charged separately.
|
||||
CreateNGasEip4762 uint64 = 1000 // Once per CREATEn operations post-verkle
|
||||
SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation.
|
||||
MemoryGas uint64 = 3 // Times the address of the (highest referenced byte in memory + 1). NOTE: referencing happens on read, write and in instructions such as RETURN and CALL.
|
||||
|
|
@ -100,6 +101,7 @@ const (
|
|||
TxAccessListAddressGas uint64 = 2400 // Per address 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
|
||||
TxAuthTupleRegularGas uint64 = 7500 // Per auth tuple regular gas specified in EIP-8037
|
||||
|
||||
// These have been changed during the course of the chain
|
||||
CallGasFrontier uint64 = 40 // Once per CALL operation & message call transaction.
|
||||
|
|
@ -196,6 +198,12 @@ const (
|
|||
// the bound has a small safety margin for system-contract accesses that
|
||||
// don't consume block gas.
|
||||
BALItemCost uint64 = 2000
|
||||
|
||||
AccountCreationSize = 120
|
||||
StorageCreationSize = 64
|
||||
AuthorizationCreationSize = 23
|
||||
CostPerStateByte = 1530
|
||||
SystemMaxSStoresPerCall = 16
|
||||
)
|
||||
|
||||
// Bls12381G1MultiExpDiscountTable is the gas discount table for BLS12-381 G1 multi exponentiation operation
|
||||
|
|
|
|||
|
|
@ -325,10 +325,10 @@ func runBenchmark(b *testing.B, t *StateTest) {
|
|||
b.StartTimer()
|
||||
start := time.Now()
|
||||
|
||||
initialGas := vm.NewGasBudget(msg.GasLimit)
|
||||
initialGas := vm.NewGasBudget(msg.GasLimit, 0)
|
||||
|
||||
// Execute the message.
|
||||
_, leftOverGas, err := evm.Call(sender.Address(), *msg.To, msg.Data, initialGas.Copy(), msg.Value)
|
||||
_, result, err := evm.Call(sender.Address(), *msg.To, msg.Data, initialGas, msg.Value)
|
||||
if err != nil {
|
||||
b.Error(err)
|
||||
return
|
||||
|
|
@ -337,7 +337,7 @@ func runBenchmark(b *testing.B, t *StateTest) {
|
|||
b.StopTimer()
|
||||
elapsed += uint64(time.Since(start))
|
||||
refund += state.StateDB.GetRefund()
|
||||
gasUsed += leftOverGas.Used(initialGas)
|
||||
gasUsed += result.Used(initialGas)
|
||||
|
||||
state.StateDB.RevertToSnapshot(snapshot)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,8 +80,8 @@ func (tt *TransactionTest) Run() error {
|
|||
if err != nil {
|
||||
return
|
||||
}
|
||||
// Intrinsic gas
|
||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules.IsHomestead, rules.IsIstanbul, rules.IsShanghai, rules.IsAmsterdam)
|
||||
// Intrinsic cost
|
||||
cost, err := core.IntrinsicGas(tx.Data(), tx.AccessList(), tx.SetCodeAuthorizations(), tx.To() == nil, rules, params.CostPerStateByte)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue