all: implement EIP-2929 (gas cost increases for state access opcodes) + yolo-v2 (#21509)
* core/vm, core/state: implement EIP-2929 + YOLOv2 * core/state, core/vm: fix some review concerns * core/state, core/vm: address review concerns * core/vm: address review concerns * core/vm: better documentation * core/vm: unify sload cost as fully dynamic * core/vm: fix typo * core/vm/runtime: fix compilation flaw * core/vm/runtime: fix renaming-err leftovers * core/vm: renaming * params/config: use correct yolov2 chainid for config * core, params: use a proper new genesis for yolov2 * core/state/tests: golinter nitpicks
This commit is contained in:
committed by
GitHub
parent
fb2c79df19
commit
6487c002f6
@ -78,9 +78,9 @@ var PrecompiledContractsIstanbul = map[common.Address]PrecompiledContract{
|
||||
common.BytesToAddress([]byte{9}): &blake2F{},
|
||||
}
|
||||
|
||||
// PrecompiledContractsYoloV1 contains the default set of pre-compiled Ethereum
|
||||
// contracts used in the Yolo v1 test release.
|
||||
var PrecompiledContractsYoloV1 = map[common.Address]PrecompiledContract{
|
||||
// PrecompiledContractsYoloV2 contains the default set of pre-compiled Ethereum
|
||||
// contracts used in the Yolo v2 test release.
|
||||
var PrecompiledContractsYoloV2 = map[common.Address]PrecompiledContract{
|
||||
common.BytesToAddress([]byte{1}): &ecrecover{},
|
||||
common.BytesToAddress([]byte{2}): &sha256hash{},
|
||||
common.BytesToAddress([]byte{3}): &ripemd160hash{},
|
||||
@ -101,6 +101,28 @@ var PrecompiledContractsYoloV1 = map[common.Address]PrecompiledContract{
|
||||
common.BytesToAddress([]byte{18}): &bls12381MapG2{},
|
||||
}
|
||||
|
||||
var (
|
||||
PrecompiledAddressesYoloV2 []common.Address
|
||||
PrecompiledAddressesIstanbul []common.Address
|
||||
PrecompiledAddressesByzantium []common.Address
|
||||
PrecompiledAddressesHomestead []common.Address
|
||||
)
|
||||
|
||||
func init() {
|
||||
for k := range PrecompiledContractsHomestead {
|
||||
PrecompiledAddressesHomestead = append(PrecompiledAddressesHomestead, k)
|
||||
}
|
||||
for k := range PrecompiledContractsByzantium {
|
||||
PrecompiledAddressesHomestead = append(PrecompiledAddressesByzantium, k)
|
||||
}
|
||||
for k := range PrecompiledContractsIstanbul {
|
||||
PrecompiledAddressesIstanbul = append(PrecompiledAddressesIstanbul, k)
|
||||
}
|
||||
for k := range PrecompiledContractsYoloV2 {
|
||||
PrecompiledAddressesYoloV2 = append(PrecompiledAddressesYoloV2, k)
|
||||
}
|
||||
}
|
||||
|
||||
// RunPrecompiledContract runs and evaluates the output of a precompiled contract.
|
||||
// It returns
|
||||
// - the returned bytes,
|
||||
|
@ -43,7 +43,7 @@ type precompiledFailureTest struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
var allPrecompiles = PrecompiledContractsYoloV1
|
||||
var allPrecompiles = PrecompiledContractsYoloV2
|
||||
|
||||
// EIP-152 test vectors
|
||||
var blake2FMalformedInputTests = []precompiledFailureTest{
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
)
|
||||
|
||||
var activators = map[int]func(*JumpTable){
|
||||
2929: enable2929,
|
||||
2200: enable2200,
|
||||
1884: enable1884,
|
||||
1344: enable1344,
|
||||
@ -134,3 +135,41 @@ func enable2315(jt *JumpTable) {
|
||||
jumps: true,
|
||||
}
|
||||
}
|
||||
|
||||
// enable2929 enables "EIP-2929: Gas cost increases for state access opcodes"
|
||||
// https://eips.ethereum.org/EIPS/eip-2929
|
||||
func enable2929(jt *JumpTable) {
|
||||
jt[SSTORE].dynamicGas = gasSStoreEIP2929
|
||||
|
||||
jt[SLOAD].constantGas = 0
|
||||
jt[SLOAD].dynamicGas = gasSLoadEIP2929
|
||||
|
||||
jt[EXTCODECOPY].constantGas = WarmStorageReadCostEIP2929
|
||||
jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP2929
|
||||
|
||||
jt[EXTCODESIZE].constantGas = WarmStorageReadCostEIP2929
|
||||
jt[EXTCODESIZE].dynamicGas = gasEip2929AccountCheck
|
||||
|
||||
jt[EXTCODEHASH].constantGas = WarmStorageReadCostEIP2929
|
||||
jt[EXTCODEHASH].dynamicGas = gasEip2929AccountCheck
|
||||
|
||||
jt[BALANCE].constantGas = WarmStorageReadCostEIP2929
|
||||
jt[BALANCE].dynamicGas = gasEip2929AccountCheck
|
||||
|
||||
jt[CALL].constantGas = WarmStorageReadCostEIP2929
|
||||
jt[CALL].dynamicGas = gasCallEIP2929
|
||||
|
||||
jt[CALLCODE].constantGas = WarmStorageReadCostEIP2929
|
||||
jt[CALLCODE].dynamicGas = gasCallCodeEIP2929
|
||||
|
||||
jt[STATICCALL].constantGas = WarmStorageReadCostEIP2929
|
||||
jt[STATICCALL].dynamicGas = gasStaticCallEIP2929
|
||||
|
||||
jt[DELEGATECALL].constantGas = WarmStorageReadCostEIP2929
|
||||
jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP2929
|
||||
|
||||
// This was previously part of the dynamic cost, but we're using it as a constantGas
|
||||
// factor here
|
||||
jt[SELFDESTRUCT].constantGas = params.SelfdestructGasEIP150
|
||||
jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP2929
|
||||
}
|
||||
|
@ -42,11 +42,26 @@ type (
|
||||
GetHashFunc func(uint64) common.Hash
|
||||
)
|
||||
|
||||
// ActivePrecompiles returns the addresses of the precompiles enabled with the current
|
||||
// configuration
|
||||
func (evm *EVM) ActivePrecompiles() []common.Address {
|
||||
switch {
|
||||
case evm.chainRules.IsYoloV2:
|
||||
return PrecompiledAddressesYoloV2
|
||||
case evm.chainRules.IsIstanbul:
|
||||
return PrecompiledAddressesIstanbul
|
||||
case evm.chainRules.IsByzantium:
|
||||
return PrecompiledAddressesByzantium
|
||||
default:
|
||||
return PrecompiledAddressesHomestead
|
||||
}
|
||||
}
|
||||
|
||||
func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
|
||||
var precompiles map[common.Address]PrecompiledContract
|
||||
switch {
|
||||
case evm.chainRules.IsYoloV1:
|
||||
precompiles = PrecompiledContractsYoloV1
|
||||
case evm.chainRules.IsYoloV2:
|
||||
precompiles = PrecompiledContractsYoloV2
|
||||
case evm.chainRules.IsIstanbul:
|
||||
precompiles = PrecompiledContractsIstanbul
|
||||
case evm.chainRules.IsByzantium:
|
||||
@ -416,7 +431,11 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
||||
}
|
||||
nonce := evm.StateDB.GetNonce(caller.Address())
|
||||
evm.StateDB.SetNonce(caller.Address(), nonce+1)
|
||||
|
||||
// We add this to the access list _before_ taking a snapshot. Even if the creation fails,
|
||||
// the access-list change should not be rolled back
|
||||
if evm.chainRules.IsYoloV2 {
|
||||
evm.StateDB.AddAddressToAccessList(address)
|
||||
}
|
||||
// Ensure there's no existing contract already at the designated address
|
||||
contractHash := evm.StateDB.GetCodeHash(address)
|
||||
if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) {
|
||||
|
@ -57,6 +57,15 @@ type StateDB interface {
|
||||
// is defined according to EIP161 (balance = nonce = code = 0).
|
||||
Empty(common.Address) bool
|
||||
|
||||
AddressInAccessList(addr common.Address) bool
|
||||
SlotInAccessList(addr common.Address, slot common.Hash) (addressOk bool, slotOk bool)
|
||||
// AddAddressToAccessList adds the given address to the access list. This operation is safe to perform
|
||||
// even if the feature/fork is not active yet
|
||||
AddAddressToAccessList(addr common.Address)
|
||||
// AddSlotToAccessList adds the given (address,slot) to the access list. This operation is safe to perform
|
||||
// even if the feature/fork is not active yet
|
||||
AddSlotToAccessList(addr common.Address, slot common.Hash)
|
||||
|
||||
RevertToSnapshot(int)
|
||||
Snapshot() int
|
||||
|
||||
|
@ -99,8 +99,8 @@ func NewEVMInterpreter(evm *EVM, cfg Config) *EVMInterpreter {
|
||||
if cfg.JumpTable[STOP] == nil {
|
||||
var jt JumpTable
|
||||
switch {
|
||||
case evm.chainRules.IsYoloV1:
|
||||
jt = yoloV1InstructionSet
|
||||
case evm.chainRules.IsYoloV2:
|
||||
jt = yoloV2InstructionSet
|
||||
case evm.chainRules.IsIstanbul:
|
||||
jt = istanbulInstructionSet
|
||||
case evm.chainRules.IsConstantinople:
|
||||
|
@ -56,17 +56,19 @@ var (
|
||||
byzantiumInstructionSet = newByzantiumInstructionSet()
|
||||
constantinopleInstructionSet = newConstantinopleInstructionSet()
|
||||
istanbulInstructionSet = newIstanbulInstructionSet()
|
||||
yoloV1InstructionSet = newYoloV1InstructionSet()
|
||||
yoloV2InstructionSet = newYoloV2InstructionSet()
|
||||
)
|
||||
|
||||
// JumpTable contains the EVM opcodes supported at a given fork.
|
||||
type JumpTable [256]*operation
|
||||
|
||||
func newYoloV1InstructionSet() JumpTable {
|
||||
// newYoloV2InstructionSet creates an instructionset containing
|
||||
// - "EIP-2315: Simple Subroutines"
|
||||
// - "EIP-2929: Gas cost increases for state access opcodes"
|
||||
func newYoloV2InstructionSet() JumpTable {
|
||||
instructionSet := newIstanbulInstructionSet()
|
||||
|
||||
enable2315(&instructionSet) // Subroutines - https://eips.ethereum.org/EIPS/eip-2315
|
||||
|
||||
enable2929(&instructionSet) // Access lists for trie accesses https://eips.ethereum.org/EIPS/eip-2929
|
||||
return instructionSet
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,7 @@ import (
|
||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
var errTraceLimitReached = errors.New("the number of logs reached the specified limit")
|
||||
@ -53,6 +54,8 @@ type LogConfig struct {
|
||||
DisableReturnData bool // disable return data capture
|
||||
Debug bool // print output during capture end
|
||||
Limit int // maximum length of output, but zero means unlimited
|
||||
// Chain overrides, can be used to execute a trace using future fork rules
|
||||
Overrides *params.ChainConfig `json:"overrides,omitempty"`
|
||||
}
|
||||
|
||||
//go:generate gencodec -type StructLog -field-override structLogMarshaling -out gen_structlog.go
|
||||
@ -314,8 +317,8 @@ func (t *mdLogger) CaptureStart(from common.Address, to common.Address, create b
|
||||
}
|
||||
|
||||
fmt.Fprintf(t.out, `
|
||||
| Pc | Op | Cost | Stack | RStack |
|
||||
|-------|-------------|------|-----------|-----------|
|
||||
| Pc | Op | Cost | Stack | RStack | Refund |
|
||||
|-------|-------------|------|-----------|-----------|---------|
|
||||
`)
|
||||
return nil
|
||||
}
|
||||
@ -327,7 +330,7 @@ func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64
|
||||
// format stack
|
||||
var a []string
|
||||
for _, elem := range stack.data {
|
||||
a = append(a, fmt.Sprintf("%d", elem))
|
||||
a = append(a, fmt.Sprintf("%v", elem.String()))
|
||||
}
|
||||
b := fmt.Sprintf("[%v]", strings.Join(a, ","))
|
||||
fmt.Fprintf(t.out, "%10v |", b)
|
||||
@ -340,6 +343,7 @@ func (t *mdLogger) CaptureState(env *EVM, pc uint64, op OpCode, gas, cost uint64
|
||||
b = fmt.Sprintf("[%v]", strings.Join(a, ","))
|
||||
fmt.Fprintf(t.out, "%10v |", b)
|
||||
}
|
||||
fmt.Fprintf(t.out, "%10v |", env.StateDB.GetRefund())
|
||||
fmt.Fprintln(t.out, "")
|
||||
if err != nil {
|
||||
fmt.Fprintf(t.out, "Error: %v\n", err)
|
||||
@ -355,11 +359,7 @@ func (t *mdLogger) CaptureFault(env *EVM, pc uint64, op OpCode, gas, cost uint64
|
||||
}
|
||||
|
||||
func (t *mdLogger) CaptureEnd(output []byte, gasUsed uint64, tm time.Duration, err error) error {
|
||||
fmt.Fprintf(t.out, `
|
||||
Output: 0x%x
|
||||
Consumed gas: %d
|
||||
Error: %v
|
||||
`,
|
||||
fmt.Fprintf(t.out, "\nOutput: `0x%x`\nConsumed gas: `%d`\nError: `%v`\n",
|
||||
output, gasUsed, err)
|
||||
return nil
|
||||
}
|
||||
|
222
core/vm/operations_acl.go
Normal file
222
core/vm/operations_acl.go
Normal file
@ -0,0 +1,222 @@
|
||||
// Copyright 2020 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package vm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
const (
|
||||
ColdAccountAccessCostEIP2929 = uint64(2600) // COLD_ACCOUNT_ACCESS_COST
|
||||
ColdSloadCostEIP2929 = uint64(2100) // COLD_SLOAD_COST
|
||||
WarmStorageReadCostEIP2929 = uint64(100) // WARM_STORAGE_READ_COST
|
||||
)
|
||||
|
||||
// gasSStoreEIP2929 implements gas cost for SSTORE according to EIP-2929"
|
||||
//
|
||||
// When calling SSTORE, check if the (address, storage_key) pair is in accessed_storage_keys.
|
||||
// If it is not, charge an additional COLD_SLOAD_COST gas, and add the pair to accessed_storage_keys.
|
||||
// Additionally, modify the parameters defined in EIP 2200 as follows:
|
||||
//
|
||||
// Parameter Old value New value
|
||||
// SLOAD_GAS 800 = WARM_STORAGE_READ_COST
|
||||
// SSTORE_RESET_GAS 5000 5000 - COLD_SLOAD_COST
|
||||
//
|
||||
//The other parameters defined in EIP 2200 are unchanged.
|
||||
// see gasSStoreEIP2200(...) in core/vm/gas_table.go for more info about how EIP 2200 is specified
|
||||
func gasSStoreEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
// If we fail the minimum gas availability invariant, fail (0)
|
||||
if contract.Gas <= params.SstoreSentryGasEIP2200 {
|
||||
return 0, 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 = evm.StateDB.GetState(contract.Address(), slot)
|
||||
cost = uint64(0)
|
||||
)
|
||||
// Check slot presence in the access list
|
||||
if addrPresent, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
|
||||
cost = ColdSloadCostEIP2929
|
||||
// If the caller cannot afford the cost, this change will be rolled back
|
||||
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
|
||||
if !addrPresent {
|
||||
// Once we're done with YOLOv2 and schedule this for mainnet, might
|
||||
// be good to remove this panic here, which is just really a
|
||||
// canary to have during testing
|
||||
panic("impossible case: address was not present in access list during sstore op")
|
||||
}
|
||||
}
|
||||
value := common.Hash(y.Bytes32())
|
||||
|
||||
if current == value { // noop (1)
|
||||
// EIP 2200 original clause:
|
||||
// return params.SloadGasEIP2200, nil
|
||||
return cost + WarmStorageReadCostEIP2929, nil // SLOAD_GAS
|
||||
}
|
||||
original := evm.StateDB.GetCommittedState(contract.Address(), common.Hash(x.Bytes32()))
|
||||
if original == current {
|
||||
if original == (common.Hash{}) { // create slot (2.1.1)
|
||||
return cost + params.SstoreSetGasEIP2200, nil
|
||||
}
|
||||
if value == (common.Hash{}) { // delete slot (2.1.2b)
|
||||
evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200)
|
||||
}
|
||||
// EIP-2200 original clause:
|
||||
// return params.SstoreResetGasEIP2200, nil // write existing slot (2.1.2)
|
||||
return cost + (params.SstoreResetGasEIP2200 - 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.SstoreClearsScheduleRefundEIP2200)
|
||||
} else if value == (common.Hash{}) { // delete slot (2.2.1.2)
|
||||
evm.StateDB.AddRefund(params.SstoreClearsScheduleRefundEIP2200)
|
||||
}
|
||||
}
|
||||
if original == value {
|
||||
if original == (common.Hash{}) { // reset to original inexistent slot (2.2.2.1)
|
||||
// EIP 2200 Original clause:
|
||||
//evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - params.SloadGasEIP2200)
|
||||
evm.StateDB.AddRefund(params.SstoreSetGasEIP2200 - 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 - ColdSloadCostEIP2929) - WarmStorageReadCostEIP2929)
|
||||
}
|
||||
}
|
||||
// EIP-2200 original clause:
|
||||
//return params.SloadGasEIP2200, nil // dirty update (2.2)
|
||||
return cost + WarmStorageReadCostEIP2929, nil // dirty update (2.2)
|
||||
}
|
||||
|
||||
// gasSLoadEIP2929 calculates dynamic gas for SLOAD according to EIP-2929
|
||||
// For SLOAD, if the (address, storage_key) pair (where address is the address of the contract
|
||||
// whose storage is being read) is not yet in accessed_storage_keys,
|
||||
// charge 2100 gas and add the pair to accessed_storage_keys.
|
||||
// If the pair is already in accessed_storage_keys, charge 100 gas.
|
||||
func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
loc := stack.peek()
|
||||
slot := common.Hash(loc.Bytes32())
|
||||
// Check slot presence in the access list
|
||||
if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
|
||||
// If the caller cannot afford the cost, this change will be rolled back
|
||||
// If he does afford it, we can skip checking the same thing later on, during execution
|
||||
evm.StateDB.AddSlotToAccessList(contract.Address(), slot)
|
||||
return ColdSloadCostEIP2929, nil
|
||||
}
|
||||
return WarmStorageReadCostEIP2929, nil
|
||||
}
|
||||
|
||||
// gasExtCodeCopyEIP2929 implements extcodecopy according to EIP-2929
|
||||
// EIP spec:
|
||||
// > If the target is not in accessed_addresses,
|
||||
// > charge COLD_ACCOUNT_ACCESS_COST gas, and add the address to accessed_addresses.
|
||||
// > Otherwise, charge WARM_STORAGE_READ_COST gas.
|
||||
func gasExtCodeCopyEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
// memory expansion first (dynamic part of pre-2929 implementation)
|
||||
gas, err := gasExtCodeCopy(evm, contract, stack, mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
addr := common.Address(stack.peek().Bytes20())
|
||||
// Check slot presence in the access list
|
||||
if !evm.StateDB.AddressInAccessList(addr) {
|
||||
evm.StateDB.AddAddressToAccessList(addr)
|
||||
var overflow bool
|
||||
// We charge (cold-warm), since 'warm' is already charged as constantGas
|
||||
if gas, overflow = math.SafeAdd(gas, ColdAccountAccessCostEIP2929-WarmStorageReadCostEIP2929); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
// gasEip2929AccountCheck checks whether the first stack item (as address) is present in the access list.
|
||||
// If it is, this method returns '0', otherwise 'cold-warm' gas, presuming that the opcode using it
|
||||
// is also using 'warm' as constant factor.
|
||||
// This method is used by:
|
||||
// - extcodehash,
|
||||
// - extcodesize,
|
||||
// - (ext) balance
|
||||
func gasEip2929AccountCheck(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
addr := common.Address(stack.peek().Bytes20())
|
||||
// Check slot presence in the access list
|
||||
if !evm.StateDB.AddressInAccessList(addr) {
|
||||
// If the caller cannot afford the cost, this change will be rolled back
|
||||
evm.StateDB.AddAddressToAccessList(addr)
|
||||
// The warm storage read cost is already charged as constantGas
|
||||
return ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929, nil
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
func makeCallVariantGasCallEIP2929(oldCalculator gasFunc) gasFunc {
|
||||
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
addr := common.Address(stack.Back(1).Bytes20())
|
||||
// Check slot presence in the access list
|
||||
if !evm.StateDB.AddressInAccessList(addr) {
|
||||
evm.StateDB.AddAddressToAccessList(addr)
|
||||
// The WarmStorageReadCostEIP2929 (100) is already deducted in the form of a constant cost
|
||||
if !contract.UseGas(ColdAccountAccessCostEIP2929 - WarmStorageReadCostEIP2929) {
|
||||
return 0, ErrOutOfGas
|
||||
}
|
||||
}
|
||||
// Now call the old calculator, which takes into account
|
||||
// - create new account
|
||||
// - transfer value
|
||||
// - memory expansion
|
||||
// - 63/64ths rule
|
||||
return oldCalculator(evm, contract, stack, mem, memorySize)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
gasCallEIP2929 = makeCallVariantGasCallEIP2929(gasCall)
|
||||
gasDelegateCallEIP2929 = makeCallVariantGasCallEIP2929(gasDelegateCall)
|
||||
gasStaticCallEIP2929 = makeCallVariantGasCallEIP2929(gasStaticCall)
|
||||
gasCallCodeEIP2929 = makeCallVariantGasCallEIP2929(gasCallCode)
|
||||
)
|
||||
|
||||
func gasSelfdestructEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
var (
|
||||
gas uint64
|
||||
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 = ColdAccountAccessCostEIP2929
|
||||
}
|
||||
// if empty and transfers value
|
||||
if evm.StateDB.Empty(address) && evm.StateDB.GetBalance(contract.Address()).Sign() != 0 {
|
||||
gas += params.CreateBySelfdestructGas
|
||||
}
|
||||
if !evm.StateDB.HasSuicided(contract.Address()) {
|
||||
evm.StateDB.AddRefund(params.SelfdestructRefundGas)
|
||||
}
|
||||
return gas, nil
|
||||
|
||||
}
|
@ -65,7 +65,7 @@ func setDefaults(cfg *Config) {
|
||||
PetersburgBlock: new(big.Int),
|
||||
IstanbulBlock: new(big.Int),
|
||||
MuirGlacierBlock: new(big.Int),
|
||||
YoloV1Block: nil,
|
||||
YoloV2Block: nil,
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,6 +113,14 @@ func Execute(code, input []byte, cfg *Config) ([]byte, *state.StateDB, error) {
|
||||
vmenv = NewEnv(cfg)
|
||||
sender = vm.AccountRef(cfg.Origin)
|
||||
)
|
||||
if cfg.ChainConfig.IsYoloV2(vmenv.BlockNumber) {
|
||||
cfg.State.AddAddressToAccessList(cfg.Origin)
|
||||
cfg.State.AddAddressToAccessList(address)
|
||||
for _, addr := range vmenv.ActivePrecompiles() {
|
||||
cfg.State.AddAddressToAccessList(addr)
|
||||
cfg.State.AddAddressToAccessList(addr)
|
||||
}
|
||||
}
|
||||
cfg.State.CreateAccount(address)
|
||||
// set the receiver's (the executing contract) code for execution.
|
||||
cfg.State.SetCode(address, code)
|
||||
@ -142,6 +150,12 @@ func Create(input []byte, cfg *Config) ([]byte, common.Address, uint64, error) {
|
||||
vmenv = NewEnv(cfg)
|
||||
sender = vm.AccountRef(cfg.Origin)
|
||||
)
|
||||
if cfg.ChainConfig.IsYoloV2(vmenv.BlockNumber) {
|
||||
cfg.State.AddAddressToAccessList(cfg.Origin)
|
||||
for _, addr := range vmenv.ActivePrecompiles() {
|
||||
cfg.State.AddAddressToAccessList(addr)
|
||||
}
|
||||
}
|
||||
|
||||
// Call the code with the given configuration.
|
||||
code, address, leftOverGas, err := vmenv.Create(
|
||||
@ -164,6 +178,14 @@ func Call(address common.Address, input []byte, cfg *Config) ([]byte, uint64, er
|
||||
vmenv := NewEnv(cfg)
|
||||
|
||||
sender := cfg.State.GetOrNewStateObject(cfg.Origin)
|
||||
if cfg.ChainConfig.IsYoloV2(vmenv.BlockNumber) {
|
||||
cfg.State.AddAddressToAccessList(cfg.Origin)
|
||||
cfg.State.AddAddressToAccessList(address)
|
||||
for _, addr := range vmenv.ActivePrecompiles() {
|
||||
cfg.State.AddAddressToAccessList(addr)
|
||||
}
|
||||
}
|
||||
|
||||
// Call the code with the given configuration.
|
||||
ret, leftOverGas, err := vmenv.Call(
|
||||
sender,
|
||||
|
@ -722,3 +722,115 @@ func BenchmarkSimpleLoop(b *testing.B) {
|
||||
//benchmarkNonModifyingCode(10000000, staticCallIdentity, "staticcall-identity-10M", b)
|
||||
//benchmarkNonModifyingCode(10000000, loopingCode, "loop-10M", b)
|
||||
}
|
||||
|
||||
// TestEip2929Cases contains various testcases that are used for
|
||||
// EIP-2929 about gas repricings
|
||||
func TestEip2929Cases(t *testing.T) {
|
||||
|
||||
id := 1
|
||||
prettyPrint := func(comment string, code []byte) {
|
||||
|
||||
instrs := make([]string, 0)
|
||||
it := asm.NewInstructionIterator(code)
|
||||
for it.Next() {
|
||||
if it.Arg() != nil && 0 < len(it.Arg()) {
|
||||
instrs = append(instrs, fmt.Sprintf("%v 0x%x", it.Op(), it.Arg()))
|
||||
} else {
|
||||
instrs = append(instrs, fmt.Sprintf("%v", it.Op()))
|
||||
}
|
||||
}
|
||||
ops := strings.Join(instrs, ", ")
|
||||
fmt.Printf("### Case %d\n\n", id)
|
||||
id++
|
||||
fmt.Printf("%v\n\nBytecode: \n```\n0x%x\n```\nOperations: \n```\n%v\n```\n\n",
|
||||
comment,
|
||||
code, ops)
|
||||
Execute(code, nil, &Config{
|
||||
EVMConfig: vm.Config{
|
||||
Debug: true,
|
||||
Tracer: vm.NewMarkdownLogger(nil, os.Stdout),
|
||||
ExtraEips: []int{2929},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
{ // First eip testcase
|
||||
code := []byte{
|
||||
// Three checks against a precompile
|
||||
byte(vm.PUSH1), 1, byte(vm.EXTCODEHASH), byte(vm.POP),
|
||||
byte(vm.PUSH1), 2, byte(vm.EXTCODESIZE), byte(vm.POP),
|
||||
byte(vm.PUSH1), 3, byte(vm.BALANCE), byte(vm.POP),
|
||||
// Three checks against a non-precompile
|
||||
byte(vm.PUSH1), 0xf1, byte(vm.EXTCODEHASH), byte(vm.POP),
|
||||
byte(vm.PUSH1), 0xf2, byte(vm.EXTCODESIZE), byte(vm.POP),
|
||||
byte(vm.PUSH1), 0xf3, byte(vm.BALANCE), byte(vm.POP),
|
||||
// Same three checks (should be cheaper)
|
||||
byte(vm.PUSH1), 0xf2, byte(vm.EXTCODEHASH), byte(vm.POP),
|
||||
byte(vm.PUSH1), 0xf3, byte(vm.EXTCODESIZE), byte(vm.POP),
|
||||
byte(vm.PUSH1), 0xf1, byte(vm.BALANCE), byte(vm.POP),
|
||||
// Check the origin, and the 'this'
|
||||
byte(vm.ORIGIN), byte(vm.BALANCE), byte(vm.POP),
|
||||
byte(vm.ADDRESS), byte(vm.BALANCE), byte(vm.POP),
|
||||
|
||||
byte(vm.STOP),
|
||||
}
|
||||
prettyPrint("This checks `EXT`(codehash,codesize,balance) of precompiles, which should be `100`, "+
|
||||
"and later checks the same operations twice against some non-precompiles. "+
|
||||
"Those are cheaper second time they are accessed. Lastly, it checks the `BALANCE` of `origin` and `this`.", code)
|
||||
}
|
||||
|
||||
{ // EXTCODECOPY
|
||||
code := []byte{
|
||||
// extcodecopy( 0xff,0,0,0,0)
|
||||
byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, //length, codeoffset, memoffset
|
||||
byte(vm.PUSH1), 0xff, byte(vm.EXTCODECOPY),
|
||||
// extcodecopy( 0xff,0,0,0,0)
|
||||
byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, //length, codeoffset, memoffset
|
||||
byte(vm.PUSH1), 0xff, byte(vm.EXTCODECOPY),
|
||||
// extcodecopy( this,0,0,0,0)
|
||||
byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, byte(vm.PUSH1), 0x00, //length, codeoffset, memoffset
|
||||
byte(vm.ADDRESS), byte(vm.EXTCODECOPY),
|
||||
|
||||
byte(vm.STOP),
|
||||
}
|
||||
prettyPrint("This checks `extcodecopy( 0xff,0,0,0,0)` twice, (should be expensive first time), "+
|
||||
"and then does `extcodecopy( this,0,0,0,0)`.", code)
|
||||
}
|
||||
|
||||
{ // SLOAD + SSTORE
|
||||
code := []byte{
|
||||
|
||||
// Add slot `0x1` to access list
|
||||
byte(vm.PUSH1), 0x01, byte(vm.SLOAD), byte(vm.POP), // SLOAD( 0x1) (add to access list)
|
||||
// Write to `0x1` which is already in access list
|
||||
byte(vm.PUSH1), 0x11, byte(vm.PUSH1), 0x01, byte(vm.SSTORE), // SSTORE( loc: 0x01, val: 0x11)
|
||||
// Write to `0x2` which is not in access list
|
||||
byte(vm.PUSH1), 0x11, byte(vm.PUSH1), 0x02, byte(vm.SSTORE), // SSTORE( loc: 0x02, val: 0x11)
|
||||
// Write again to `0x2`
|
||||
byte(vm.PUSH1), 0x11, byte(vm.PUSH1), 0x02, byte(vm.SSTORE), // SSTORE( loc: 0x02, val: 0x11)
|
||||
// Read slot in access list (0x2)
|
||||
byte(vm.PUSH1), 0x02, byte(vm.SLOAD), // SLOAD( 0x2)
|
||||
// Read slot in access list (0x1)
|
||||
byte(vm.PUSH1), 0x01, byte(vm.SLOAD), // SLOAD( 0x1)
|
||||
}
|
||||
prettyPrint("This checks `sload( 0x1)` followed by `sstore(loc: 0x01, val:0x11)`, then 'naked' sstore:"+
|
||||
"`sstore(loc: 0x02, val:0x11)` twice, and `sload(0x2)`, `sload(0x1)`. ", code)
|
||||
}
|
||||
{ // Call variants
|
||||
code := []byte{
|
||||
// identity precompile
|
||||
byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
|
||||
byte(vm.PUSH1), 0x04, byte(vm.PUSH1), 0x0, byte(vm.CALL), byte(vm.POP),
|
||||
|
||||
// random account - call 1
|
||||
byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
|
||||
byte(vm.PUSH1), 0xff, byte(vm.PUSH1), 0x0, byte(vm.CALL), byte(vm.POP),
|
||||
|
||||
// random account - call 2
|
||||
byte(vm.PUSH1), 0x0, byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1),
|
||||
byte(vm.PUSH1), 0xff, byte(vm.PUSH1), 0x0, byte(vm.STATICCALL), byte(vm.POP),
|
||||
}
|
||||
prettyPrint("This calls the `identity`-precompile (cheap), then calls an account (expensive) and `staticcall`s the same"+
|
||||
"account (cheap)", code)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user