mirror of https://github.com/ethereum/go-ethereum
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 nitpickspull/21745/head
parent
fb2c79df19
commit
6487c002f6
@ -0,0 +1,136 @@ |
|||||||
|
// 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 state |
||||||
|
|
||||||
|
import ( |
||||||
|
"github.com/ethereum/go-ethereum/common" |
||||||
|
) |
||||||
|
|
||||||
|
type accessList struct { |
||||||
|
addresses map[common.Address]int |
||||||
|
slots []map[common.Hash]struct{} |
||||||
|
} |
||||||
|
|
||||||
|
// ContainsAddress returns true if the address is in the access list.
|
||||||
|
func (al *accessList) ContainsAddress(address common.Address) bool { |
||||||
|
_, ok := al.addresses[address] |
||||||
|
return ok |
||||||
|
} |
||||||
|
|
||||||
|
// Contains checks if a slot within an account is present in the access list, returning
|
||||||
|
// separate flags for the presence of the account and the slot respectively.
|
||||||
|
func (al *accessList) Contains(address common.Address, slot common.Hash) (addressPresent bool, slotPresent bool) { |
||||||
|
idx, ok := al.addresses[address] |
||||||
|
if !ok { |
||||||
|
// no such address (and hence zero slots)
|
||||||
|
return false, false |
||||||
|
} |
||||||
|
if idx == -1 { |
||||||
|
// address yes, but no slots
|
||||||
|
return true, false |
||||||
|
} |
||||||
|
_, slotPresent = al.slots[idx][slot] |
||||||
|
return true, slotPresent |
||||||
|
} |
||||||
|
|
||||||
|
// newAccessList creates a new accessList.
|
||||||
|
func newAccessList() *accessList { |
||||||
|
return &accessList{ |
||||||
|
addresses: make(map[common.Address]int), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Copy creates an independent copy of an accessList.
|
||||||
|
func (a *accessList) Copy() *accessList { |
||||||
|
cp := newAccessList() |
||||||
|
for k, v := range a.addresses { |
||||||
|
cp.addresses[k] = v |
||||||
|
} |
||||||
|
cp.slots = make([]map[common.Hash]struct{}, len(a.slots)) |
||||||
|
for i, slotMap := range a.slots { |
||||||
|
newSlotmap := make(map[common.Hash]struct{}, len(slotMap)) |
||||||
|
for k := range slotMap { |
||||||
|
newSlotmap[k] = struct{}{} |
||||||
|
} |
||||||
|
cp.slots[i] = newSlotmap |
||||||
|
} |
||||||
|
return cp |
||||||
|
} |
||||||
|
|
||||||
|
// AddAddress adds an address to the access list, and returns 'true' if the operation
|
||||||
|
// caused a change (addr was not previously in the list).
|
||||||
|
func (al *accessList) AddAddress(address common.Address) bool { |
||||||
|
if _, present := al.addresses[address]; present { |
||||||
|
return false |
||||||
|
} |
||||||
|
al.addresses[address] = -1 |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// AddSlot adds the specified (addr, slot) combo to the access list.
|
||||||
|
// Return values are:
|
||||||
|
// - address added
|
||||||
|
// - slot added
|
||||||
|
// For any 'true' value returned, a corresponding journal entry must be made.
|
||||||
|
func (al *accessList) AddSlot(address common.Address, slot common.Hash) (addrChange bool, slotChange bool) { |
||||||
|
idx, addrPresent := al.addresses[address] |
||||||
|
if !addrPresent || idx == -1 { |
||||||
|
// Address not present, or addr present but no slots there
|
||||||
|
al.addresses[address] = len(al.slots) |
||||||
|
slotmap := map[common.Hash]struct{}{slot: {}} |
||||||
|
al.slots = append(al.slots, slotmap) |
||||||
|
return !addrPresent, true |
||||||
|
} |
||||||
|
// There is already an (address,slot) mapping
|
||||||
|
slotmap := al.slots[idx] |
||||||
|
if _, ok := slotmap[slot]; !ok { |
||||||
|
slotmap[slot] = struct{}{} |
||||||
|
// Journal add slot change
|
||||||
|
return false, true |
||||||
|
} |
||||||
|
// No changes required
|
||||||
|
return false, false |
||||||
|
} |
||||||
|
|
||||||
|
// DeleteSlot removes an (address, slot)-tuple from the access list.
|
||||||
|
// This operation needs to be performed in the same order as the addition happened.
|
||||||
|
// This method is meant to be used by the journal, which maintains ordering of
|
||||||
|
// operations.
|
||||||
|
func (al *accessList) DeleteSlot(address common.Address, slot common.Hash) { |
||||||
|
idx, addrOk := al.addresses[address] |
||||||
|
// There are two ways this can fail
|
||||||
|
if !addrOk { |
||||||
|
panic("reverting slot change, address not present in list") |
||||||
|
} |
||||||
|
slotmap := al.slots[idx] |
||||||
|
delete(slotmap, slot) |
||||||
|
// If that was the last (first) slot, remove it
|
||||||
|
// Since additions and rollbacks are always performed in order,
|
||||||
|
// we can delete the item without worrying about screwing up later indices
|
||||||
|
if len(slotmap) == 0 { |
||||||
|
al.slots = al.slots[:idx] |
||||||
|
al.addresses[address] = -1 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// DeleteAddress removes an address from the access list. This operation
|
||||||
|
// needs to be performed in the same order as the addition happened.
|
||||||
|
// This method is meant to be used by the journal, which maintains ordering of
|
||||||
|
// operations.
|
||||||
|
func (al *accessList) DeleteAddress(address common.Address) { |
||||||
|
delete(al.addresses, address) |
||||||
|
} |
@ -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 |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue