cmd, core, params, trie: add verkle access witness gas charging (#29338)
Implements some of the changes required to charge and do gas accounting in verkle testnet.
This commit is contained in:
parent
47af69c2bc
commit
44a50c9f96
|
@ -103,17 +103,17 @@ func TestAttachWelcome(t *testing.T) {
|
|||
"--http", "--http.port", httpPort,
|
||||
"--ws", "--ws.port", wsPort)
|
||||
t.Run("ipc", func(t *testing.T) {
|
||||
waitForEndpoint(t, ipc, 3*time.Second)
|
||||
waitForEndpoint(t, ipc, 4*time.Second)
|
||||
testAttachWelcome(t, geth, "ipc:"+ipc, ipcAPIs)
|
||||
})
|
||||
t.Run("http", func(t *testing.T) {
|
||||
endpoint := "http://127.0.0.1:" + httpPort
|
||||
waitForEndpoint(t, endpoint, 3*time.Second)
|
||||
waitForEndpoint(t, endpoint, 4*time.Second)
|
||||
testAttachWelcome(t, geth, endpoint, httpAPIs)
|
||||
})
|
||||
t.Run("ws", func(t *testing.T) {
|
||||
endpoint := "ws://127.0.0.1:" + wsPort
|
||||
waitForEndpoint(t, endpoint, 3*time.Second)
|
||||
waitForEndpoint(t, endpoint, 4*time.Second)
|
||||
testAttachWelcome(t, geth, endpoint, httpAPIs)
|
||||
})
|
||||
geth.Kill()
|
||||
|
|
|
@ -28,7 +28,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
"github.com/ethereum/go-ethereum/internal/flags"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/gballet/go-verkle"
|
||||
"github.com/ethereum/go-verkle"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
|
|
|
@ -32,7 +32,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/ethdb"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/triedb"
|
||||
"github.com/gballet/go-verkle"
|
||||
"github.com/ethereum/go-verkle"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
|
|
|
@ -64,6 +64,11 @@ var (
|
|||
// than init code size limit.
|
||||
ErrMaxInitCodeSizeExceeded = errors.New("max initcode size exceeded")
|
||||
|
||||
// ErrInsufficientBalanceWitness is returned if the transaction sender has enough
|
||||
// funds to cover the transfer, but not enough to pay for witness access/modification
|
||||
// costs for the transaction
|
||||
ErrInsufficientBalanceWitness = errors.New("insufficient funds to cover witness access costs for transaction")
|
||||
|
||||
// ErrInsufficientFunds is returned if the total cost of executing a transaction
|
||||
// is higher than the balance of the user's account.
|
||||
ErrInsufficientFunds = errors.New("insufficient funds for gas * price + value")
|
||||
|
|
|
@ -0,0 +1,320 @@
|
|||
// Copyright 2021 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 (
|
||||
"maps"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie/utils"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
// mode specifies how a tree location has been accessed
|
||||
// for the byte value:
|
||||
// * the first bit is set if the branch has been edited
|
||||
// * the second bit is set if the branch has been read
|
||||
type mode byte
|
||||
|
||||
const (
|
||||
AccessWitnessReadFlag = mode(1)
|
||||
AccessWitnessWriteFlag = mode(2)
|
||||
)
|
||||
|
||||
var zeroTreeIndex uint256.Int
|
||||
|
||||
// AccessEvents lists the locations of the state that are being accessed
|
||||
// during the production of a block.
|
||||
type AccessEvents struct {
|
||||
branches map[branchAccessKey]mode
|
||||
chunks map[chunkAccessKey]mode
|
||||
|
||||
pointCache *utils.PointCache
|
||||
}
|
||||
|
||||
func NewAccessEvents(pointCache *utils.PointCache) *AccessEvents {
|
||||
return &AccessEvents{
|
||||
branches: make(map[branchAccessKey]mode),
|
||||
chunks: make(map[chunkAccessKey]mode),
|
||||
pointCache: pointCache,
|
||||
}
|
||||
}
|
||||
|
||||
// Merge is used to merge the access events that were generated during the
|
||||
// execution of a tx, with the accumulation of all access events that were
|
||||
// generated during the execution of all txs preceding this one in a block.
|
||||
func (ae *AccessEvents) Merge(other *AccessEvents) {
|
||||
for k := range other.branches {
|
||||
ae.branches[k] |= other.branches[k]
|
||||
}
|
||||
for k, chunk := range other.chunks {
|
||||
ae.chunks[k] |= chunk
|
||||
}
|
||||
}
|
||||
|
||||
// Keys returns, predictably, the list of keys that were touched during the
|
||||
// buildup of the access witness.
|
||||
func (ae *AccessEvents) Keys() [][]byte {
|
||||
// TODO: consider if parallelizing this is worth it, probably depending on len(ae.chunks).
|
||||
keys := make([][]byte, 0, len(ae.chunks))
|
||||
for chunk := range ae.chunks {
|
||||
basePoint := ae.pointCache.Get(chunk.addr[:])
|
||||
key := utils.GetTreeKeyWithEvaluatedAddress(basePoint, &chunk.treeIndex, chunk.leafKey)
|
||||
keys = append(keys, key)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
func (ae *AccessEvents) Copy() *AccessEvents {
|
||||
cpy := &AccessEvents{
|
||||
branches: maps.Clone(ae.branches),
|
||||
chunks: maps.Clone(ae.chunks),
|
||||
pointCache: ae.pointCache,
|
||||
}
|
||||
return cpy
|
||||
}
|
||||
|
||||
// AddAccount returns the gas to be charged for each of the currently cold
|
||||
// member fields of an account.
|
||||
func (ae *AccessEvents) AddAccount(addr common.Address, isWrite bool) uint64 {
|
||||
var gas uint64
|
||||
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite)
|
||||
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite)
|
||||
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite)
|
||||
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite)
|
||||
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite)
|
||||
return gas
|
||||
}
|
||||
|
||||
// MessageCallGas returns the gas to be charged for each of the currently
|
||||
// cold member fields of an account, that need to be touched when making a message
|
||||
// call to that account.
|
||||
func (ae *AccessEvents) MessageCallGas(destination common.Address) uint64 {
|
||||
var gas uint64
|
||||
gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.VersionLeafKey, false)
|
||||
gas += ae.touchAddressAndChargeGas(destination, zeroTreeIndex, utils.CodeSizeLeafKey, false)
|
||||
return gas
|
||||
}
|
||||
|
||||
// ValueTransferGas returns the gas to be charged for each of the currently
|
||||
// cold balance member fields of the caller and the callee accounts.
|
||||
func (ae *AccessEvents) ValueTransferGas(callerAddr, targetAddr common.Address) uint64 {
|
||||
var gas uint64
|
||||
gas += ae.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
|
||||
gas += ae.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
|
||||
return gas
|
||||
}
|
||||
|
||||
// ContractCreateInitGas returns the access gas costs for the initialization of
|
||||
// a contract creation.
|
||||
func (ae *AccessEvents) ContractCreateInitGas(addr common.Address, createSendsValue bool) uint64 {
|
||||
var gas uint64
|
||||
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true)
|
||||
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, true)
|
||||
if createSendsValue {
|
||||
gas += ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, true)
|
||||
}
|
||||
return gas
|
||||
}
|
||||
|
||||
// AddTxOrigin adds the member fields of the sender account to the access event list,
|
||||
// so that cold accesses are not charged, since they are covered by the 21000 gas.
|
||||
func (ae *AccessEvents) AddTxOrigin(originAddr common.Address) {
|
||||
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.VersionLeafKey, false)
|
||||
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
|
||||
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.NonceLeafKey, true)
|
||||
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeKeccakLeafKey, false)
|
||||
ae.touchAddressAndChargeGas(originAddr, zeroTreeIndex, utils.CodeSizeLeafKey, false)
|
||||
}
|
||||
|
||||
// AddTxDestination adds the member fields of the sender account to the access event list,
|
||||
// so that cold accesses are not charged, since they are covered by the 21000 gas.
|
||||
func (ae *AccessEvents) AddTxDestination(addr common.Address, sendsValue bool) {
|
||||
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, false)
|
||||
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, sendsValue)
|
||||
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, false)
|
||||
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, false)
|
||||
ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, false)
|
||||
}
|
||||
|
||||
// SlotGas returns the amount of gas to be charged for a cold storage access.
|
||||
func (ae *AccessEvents) SlotGas(addr common.Address, slot common.Hash, isWrite bool) uint64 {
|
||||
treeIndex, subIndex := utils.StorageIndex(slot.Bytes())
|
||||
return ae.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite)
|
||||
}
|
||||
|
||||
// touchAddressAndChargeGas adds any missing access event to the access event list, and returns the cold
|
||||
// access cost to be charged, if need be.
|
||||
func (ae *AccessEvents) touchAddressAndChargeGas(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 {
|
||||
stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := ae.touchAddress(addr, treeIndex, subIndex, isWrite)
|
||||
|
||||
var gas uint64
|
||||
if stemRead {
|
||||
gas += params.WitnessBranchReadCost
|
||||
}
|
||||
if selectorRead {
|
||||
gas += params.WitnessChunkReadCost
|
||||
}
|
||||
if stemWrite {
|
||||
gas += params.WitnessBranchWriteCost
|
||||
}
|
||||
if selectorWrite {
|
||||
gas += params.WitnessChunkWriteCost
|
||||
}
|
||||
if selectorFill {
|
||||
gas += params.WitnessChunkFillCost
|
||||
}
|
||||
return gas
|
||||
}
|
||||
|
||||
// touchAddress adds any missing access event to the access event list.
|
||||
func (ae *AccessEvents) touchAddress(addr common.Address, treeIndex uint256.Int, subIndex byte, isWrite bool) (bool, bool, bool, bool, bool) {
|
||||
branchKey := newBranchAccessKey(addr, treeIndex)
|
||||
chunkKey := newChunkAccessKey(branchKey, subIndex)
|
||||
|
||||
// Read access.
|
||||
var branchRead, chunkRead bool
|
||||
if _, hasStem := ae.branches[branchKey]; !hasStem {
|
||||
branchRead = true
|
||||
ae.branches[branchKey] = AccessWitnessReadFlag
|
||||
}
|
||||
if _, hasSelector := ae.chunks[chunkKey]; !hasSelector {
|
||||
chunkRead = true
|
||||
ae.chunks[chunkKey] = AccessWitnessReadFlag
|
||||
}
|
||||
|
||||
// Write access.
|
||||
var branchWrite, chunkWrite, chunkFill bool
|
||||
if isWrite {
|
||||
if (ae.branches[branchKey] & AccessWitnessWriteFlag) == 0 {
|
||||
branchWrite = true
|
||||
ae.branches[branchKey] |= AccessWitnessWriteFlag
|
||||
}
|
||||
|
||||
chunkValue := ae.chunks[chunkKey]
|
||||
if (chunkValue & AccessWitnessWriteFlag) == 0 {
|
||||
chunkWrite = true
|
||||
ae.chunks[chunkKey] |= AccessWitnessWriteFlag
|
||||
}
|
||||
// TODO: charge chunk filling costs if the leaf was previously empty in the state
|
||||
}
|
||||
return branchRead, chunkRead, branchWrite, chunkWrite, chunkFill
|
||||
}
|
||||
|
||||
type branchAccessKey struct {
|
||||
addr common.Address
|
||||
treeIndex uint256.Int
|
||||
}
|
||||
|
||||
func newBranchAccessKey(addr common.Address, treeIndex uint256.Int) branchAccessKey {
|
||||
var sk branchAccessKey
|
||||
sk.addr = addr
|
||||
sk.treeIndex = treeIndex
|
||||
return sk
|
||||
}
|
||||
|
||||
type chunkAccessKey struct {
|
||||
branchAccessKey
|
||||
leafKey byte
|
||||
}
|
||||
|
||||
func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey {
|
||||
var lk chunkAccessKey
|
||||
lk.branchAccessKey = branchKey
|
||||
lk.leafKey = leafKey
|
||||
return lk
|
||||
}
|
||||
|
||||
// CodeChunksRangeGas is a helper function to touch every chunk in a code range and charge witness gas costs
|
||||
func (ae *AccessEvents) CodeChunksRangeGas(contractAddr common.Address, startPC, size uint64, codeLen uint64, isWrite bool) uint64 {
|
||||
// note that in the case where the copied code is outside the range of the
|
||||
// contract code but touches the last leaf with contract code in it,
|
||||
// we don't include the last leaf of code in the AccessWitness. The
|
||||
// reason that we do not need the last leaf is the account's code size
|
||||
// is already in the AccessWitness so a stateless verifier can see that
|
||||
// the code from the last leaf is not needed.
|
||||
if (codeLen == 0 && size == 0) || startPC > codeLen {
|
||||
return 0
|
||||
}
|
||||
|
||||
endPC := startPC + size
|
||||
if endPC > codeLen {
|
||||
endPC = codeLen
|
||||
}
|
||||
if endPC > 0 {
|
||||
endPC -= 1 // endPC is the last bytecode that will be touched.
|
||||
}
|
||||
|
||||
var statelessGasCharged uint64
|
||||
for chunkNumber := startPC / 31; chunkNumber <= endPC/31; chunkNumber++ {
|
||||
treeIndex := *uint256.NewInt((chunkNumber + 128) / 256)
|
||||
subIndex := byte((chunkNumber + 128) % 256)
|
||||
gas := ae.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite)
|
||||
var overflow bool
|
||||
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas)
|
||||
if overflow {
|
||||
panic("overflow when adding gas")
|
||||
}
|
||||
}
|
||||
return statelessGasCharged
|
||||
}
|
||||
|
||||
// VersionGas adds the account's version to the accessed data, and returns the
|
||||
// amount of gas that it costs.
|
||||
// Note that an access in write mode implies an access in read mode, whereas an
|
||||
// access in read mode does not imply an access in write mode.
|
||||
func (ae *AccessEvents) VersionGas(addr common.Address, isWrite bool) uint64 {
|
||||
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite)
|
||||
}
|
||||
|
||||
// BalanceGas adds the account's balance to the accessed data, and returns the
|
||||
// amount of gas that it costs.
|
||||
// in write mode. If false, the charged gas corresponds to an access in read mode.
|
||||
// Note that an access in write mode implies an access in read mode, whereas an access in
|
||||
// read mode does not imply an access in write mode.
|
||||
func (ae *AccessEvents) BalanceGas(addr common.Address, isWrite bool) uint64 {
|
||||
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite)
|
||||
}
|
||||
|
||||
// NonceGas adds the account's nonce to the accessed data, and returns the
|
||||
// amount of gas that it costs.
|
||||
// in write mode. If false, the charged gas corresponds to an access in read mode.
|
||||
// Note that an access in write mode implies an access in read mode, whereas an access in
|
||||
// read mode does not imply an access in write mode.
|
||||
func (ae *AccessEvents) NonceGas(addr common.Address, isWrite bool) uint64 {
|
||||
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite)
|
||||
}
|
||||
|
||||
// CodeSizeGas adds the account's code size to the accessed data, and returns the
|
||||
// amount of gas that it costs.
|
||||
// in write mode. If false, the charged gas corresponds to an access in read mode.
|
||||
// Note that an access in write mode implies an access in read mode, whereas an access in
|
||||
// read mode does not imply an access in write mode.
|
||||
func (ae *AccessEvents) CodeSizeGas(addr common.Address, isWrite bool) uint64 {
|
||||
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite)
|
||||
}
|
||||
|
||||
// CodeHashGas adds the account's code hash to the accessed data, and returns the
|
||||
// amount of gas that it costs.
|
||||
// in write mode. If false, the charged gas corresponds to an access in read mode.
|
||||
// Note that an access in write mode implies an access in read mode, whereas an access in
|
||||
// read mode does not imply an access in write mode.
|
||||
func (ae *AccessEvents) CodeHashGas(addr common.Address, isWrite bool) uint64 {
|
||||
return ae.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite)
|
||||
}
|
|
@ -0,0 +1,153 @@
|
|||
// Copyright 2021 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 (
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
testAddr [20]byte
|
||||
testAddr2 [20]byte
|
||||
)
|
||||
|
||||
func init() {
|
||||
for i := byte(0); i < 20; i++ {
|
||||
testAddr[i] = i
|
||||
testAddr[2] = 2 * i
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccountHeaderGas(t *testing.T) {
|
||||
ae := NewAccessEvents(utils.NewPointCache(1024))
|
||||
|
||||
// Check cold read cost
|
||||
gas := ae.VersionGas(testAddr, false)
|
||||
if gas != params.WitnessBranchReadCost+params.WitnessChunkReadCost {
|
||||
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchReadCost+params.WitnessChunkReadCost)
|
||||
}
|
||||
|
||||
// Check warm read cost
|
||||
gas = ae.VersionGas(testAddr, false)
|
||||
if gas != 0 {
|
||||
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
|
||||
}
|
||||
|
||||
// Check cold read costs in the same group no longer incur the branch read cost
|
||||
gas = ae.BalanceGas(testAddr, false)
|
||||
if gas != params.WitnessChunkReadCost {
|
||||
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
|
||||
}
|
||||
gas = ae.NonceGas(testAddr, false)
|
||||
if gas != params.WitnessChunkReadCost {
|
||||
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
|
||||
}
|
||||
gas = ae.CodeSizeGas(testAddr, false)
|
||||
if gas != params.WitnessChunkReadCost {
|
||||
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
|
||||
}
|
||||
gas = ae.CodeHashGas(testAddr, false)
|
||||
if gas != params.WitnessChunkReadCost {
|
||||
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
|
||||
}
|
||||
|
||||
// Check cold write cost
|
||||
gas = ae.VersionGas(testAddr, true)
|
||||
if gas != params.WitnessBranchWriteCost+params.WitnessChunkWriteCost {
|
||||
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchReadCost+params.WitnessBranchWriteCost)
|
||||
}
|
||||
|
||||
// Check warm write cost
|
||||
gas = ae.VersionGas(testAddr, true)
|
||||
if gas != 0 {
|
||||
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
|
||||
}
|
||||
|
||||
// Check a write without a read charges both read and write costs
|
||||
gas = ae.BalanceGas(testAddr2, true)
|
||||
if gas != params.WitnessBranchReadCost+params.WitnessBranchWriteCost+params.WitnessChunkWriteCost+params.WitnessChunkReadCost {
|
||||
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchReadCost+params.WitnessBranchWriteCost+params.WitnessChunkWriteCost+params.WitnessChunkReadCost)
|
||||
}
|
||||
|
||||
// Check that a write followed by a read charges nothing
|
||||
gas = ae.BalanceGas(testAddr2, false)
|
||||
if gas != 0 {
|
||||
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
|
||||
}
|
||||
|
||||
// Check that reading a slot from the account header only charges the
|
||||
// chunk read cost.
|
||||
gas = ae.SlotGas(testAddr, common.Hash{}, false)
|
||||
if gas != params.WitnessChunkReadCost {
|
||||
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessChunkReadCost)
|
||||
}
|
||||
}
|
||||
|
||||
// TestContractCreateInitGas checks that the gas cost of contract creation is correctly
|
||||
// calculated.
|
||||
func TestContractCreateInitGas(t *testing.T) {
|
||||
ae := NewAccessEvents(utils.NewPointCache(1024))
|
||||
|
||||
var testAddr [20]byte
|
||||
for i := byte(0); i < 20; i++ {
|
||||
testAddr[i] = i
|
||||
}
|
||||
|
||||
// Check cold read cost, without a value
|
||||
gas := ae.ContractCreateInitGas(testAddr, false)
|
||||
if gas != params.WitnessBranchWriteCost+params.WitnessBranchReadCost+params.WitnessChunkWriteCost*2+params.WitnessChunkReadCost*2 {
|
||||
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchWriteCost+params.WitnessBranchReadCost+params.WitnessChunkWriteCost*3)
|
||||
}
|
||||
|
||||
// Check warm read cost
|
||||
gas = ae.ContractCreateInitGas(testAddr, false)
|
||||
if gas != 0 {
|
||||
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// TestMessageCallGas checks that the gas cost of message calls is correctly
|
||||
// calculated.
|
||||
func TestMessageCallGas(t *testing.T) {
|
||||
ae := NewAccessEvents(utils.NewPointCache(1024))
|
||||
|
||||
// Check cold read cost, without a value
|
||||
gas := ae.MessageCallGas(testAddr)
|
||||
if gas != params.WitnessBranchReadCost+params.WitnessChunkReadCost*2 {
|
||||
t.Fatalf("incorrect gas computed, got %d, want %d", gas, params.WitnessBranchReadCost+params.WitnessChunkReadCost*2)
|
||||
}
|
||||
|
||||
// Check that reading the version and code size of the same account does not incur the branch read cost
|
||||
gas = ae.VersionGas(testAddr, false)
|
||||
if gas != 0 {
|
||||
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
|
||||
}
|
||||
gas = ae.CodeSizeGas(testAddr, false)
|
||||
if gas != 0 {
|
||||
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
|
||||
}
|
||||
|
||||
// Check warm read cost
|
||||
gas = ae.MessageCallGas(testAddr)
|
||||
if gas != 0 {
|
||||
t.Fatalf("incorrect gas computed, got %d, want %d", gas, 0)
|
||||
}
|
||||
}
|
|
@ -20,7 +20,6 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/crate-crypto/go-ipa/banderwagon"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/lru"
|
||||
"github.com/ethereum/go-ethereum/core/rawdb"
|
||||
|
@ -40,11 +39,8 @@ const (
|
|||
// Cache size granted for caching clean code.
|
||||
codeCacheSize = 64 * 1024 * 1024
|
||||
|
||||
// commitmentSize is the size of commitment stored in cache.
|
||||
commitmentSize = banderwagon.UncompressedSize
|
||||
|
||||
// Cache item granted for caching commitment results.
|
||||
commitmentCacheItems = 64 * 1024 * 1024 / (commitmentSize + common.AddressLength)
|
||||
// Number of address->curve point associations to keep.
|
||||
pointCacheSize = 4096
|
||||
)
|
||||
|
||||
// Database wraps access to tries and contract code.
|
||||
|
@ -67,6 +63,9 @@ type Database interface {
|
|||
// DiskDB returns the underlying key-value disk database.
|
||||
DiskDB() ethdb.KeyValueStore
|
||||
|
||||
// PointCache returns the cache holding points used in verkle tree key computation
|
||||
PointCache() *utils.PointCache
|
||||
|
||||
// TrieDB returns the underlying trie database for managing trie nodes.
|
||||
TrieDB() *triedb.Database
|
||||
}
|
||||
|
@ -139,6 +138,9 @@ type Trie interface {
|
|||
// nodes of the longest existing prefix of the key (at least the root), ending
|
||||
// with the node that proves the absence of the key.
|
||||
Prove(key []byte, proofDb ethdb.KeyValueWriter) error
|
||||
|
||||
// IsVerkle returns true if the trie is verkle-tree based
|
||||
IsVerkle() bool
|
||||
}
|
||||
|
||||
// NewDatabase creates a backing store for state. The returned database is safe for
|
||||
|
@ -157,6 +159,7 @@ func NewDatabaseWithConfig(db ethdb.Database, config *triedb.Config) Database {
|
|||
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
|
||||
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
|
||||
triedb: triedb.NewDatabase(db, config),
|
||||
pointCache: utils.NewPointCache(pointCacheSize),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -167,6 +170,7 @@ func NewDatabaseWithNodeDB(db ethdb.Database, triedb *triedb.Database) Database
|
|||
codeSizeCache: lru.NewCache[common.Hash, int](codeSizeCacheSize),
|
||||
codeCache: lru.NewSizeConstrainedCache[common.Hash, []byte](codeCacheSize),
|
||||
triedb: triedb,
|
||||
pointCache: utils.NewPointCache(pointCacheSize),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -175,12 +179,13 @@ type cachingDB struct {
|
|||
codeSizeCache *lru.Cache[common.Hash, int]
|
||||
codeCache *lru.SizeConstrainedCache[common.Hash, []byte]
|
||||
triedb *triedb.Database
|
||||
pointCache *utils.PointCache
|
||||
}
|
||||
|
||||
// OpenTrie opens the main account trie at a specific root hash.
|
||||
func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
|
||||
if db.triedb.IsVerkle() {
|
||||
return trie.NewVerkleTrie(root, db.triedb, utils.NewPointCache(commitmentCacheItems))
|
||||
return trie.NewVerkleTrie(root, db.triedb, db.pointCache)
|
||||
}
|
||||
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
|
||||
if err != nil {
|
||||
|
@ -266,3 +271,8 @@ func (db *cachingDB) DiskDB() ethdb.KeyValueStore {
|
|||
func (db *cachingDB) TrieDB() *triedb.Database {
|
||||
return db.triedb
|
||||
}
|
||||
|
||||
// PointCache returns the cache of evaluated curve points.
|
||||
func (db *cachingDB) PointCache() *utils.PointCache {
|
||||
return db.pointCache
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/trie"
|
||||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"github.com/ethereum/go-ethereum/trie/triestate"
|
||||
"github.com/ethereum/go-ethereum/trie/utils"
|
||||
"github.com/holiman/uint256"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
@ -1327,7 +1328,10 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
|
|||
// - Add coinbase to access list (EIP-3651)
|
||||
// - Reset transient storage (EIP-1153)
|
||||
func (s *StateDB) Prepare(rules params.Rules, sender, coinbase common.Address, dst *common.Address, precompiles []common.Address, list types.AccessList) {
|
||||
if rules.IsBerlin {
|
||||
if rules.IsEIP2929 && rules.IsEIP4762 {
|
||||
panic("eip2929 and eip4762 are both activated")
|
||||
}
|
||||
if rules.IsEIP2929 {
|
||||
// Clear out any leftover from previous executions
|
||||
al := newAccessList()
|
||||
s.accessList = al
|
||||
|
@ -1439,3 +1443,7 @@ func (s *StateDB) markUpdate(addr common.Address) {
|
|||
s.mutations[addr].applied = false
|
||||
s.mutations[addr].typ = update
|
||||
}
|
||||
|
||||
func (s *StateDB) PointCache() *utils.PointCache {
|
||||
return s.db.PointCache()
|
||||
}
|
||||
|
|
|
@ -482,7 +482,7 @@ func TestProcessVerkle(t *testing.T) {
|
|||
txCost1 := params.TxGas
|
||||
txCost2 := params.TxGas
|
||||
contractCreationCost := intrinsicContractCreationGas + uint64(2039 /* execution costs */)
|
||||
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(293644 /* execution costs */)
|
||||
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(57444 /* execution costs */)
|
||||
blockGasUsagesExpected := []uint64{
|
||||
txCost1*2 + txCost2,
|
||||
txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas,
|
||||
|
|
|
@ -68,7 +68,7 @@ func (result *ExecutionResult) Revert() []byte {
|
|||
}
|
||||
|
||||
// IntrinsicGas computes the 'intrinsic gas' for a message with the given data.
|
||||
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation bool, isHomestead, isEIP2028, isEIP3860 bool) (uint64, error) {
|
||||
func IntrinsicGas(data []byte, accessList types.AccessList, isContractCreation, isHomestead, isEIP2028, isEIP3860 bool) (uint64, error) {
|
||||
// Set the starting gas for the raw transaction
|
||||
var gas uint64
|
||||
if isContractCreation && isHomestead {
|
||||
|
@ -405,6 +405,14 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
|||
}
|
||||
st.gasRemaining -= gas
|
||||
|
||||
if rules.IsEIP4762 {
|
||||
st.evm.AccessEvents.AddTxOrigin(msg.From)
|
||||
|
||||
if targetAddr := msg.To; targetAddr != nil {
|
||||
st.evm.AccessEvents.AddTxDestination(*targetAddr, msg.Value.Sign() != 0)
|
||||
}
|
||||
}
|
||||
|
||||
// Check clause 6
|
||||
value, overflow := uint256.FromBig(msg.Value)
|
||||
if overflow {
|
||||
|
@ -458,6 +466,11 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
|
|||
fee := new(uint256.Int).SetUint64(st.gasUsed())
|
||||
fee.Mul(fee, effectiveTipU256)
|
||||
st.state.AddBalance(st.evm.Context.Coinbase, fee, tracing.BalanceIncreaseRewardTransactionFee)
|
||||
|
||||
// add the coinbase to the witness iff the fee is greater than 0
|
||||
if rules.IsEIP4762 && fee.Sign() != 0 {
|
||||
st.evm.AccessEvents.BalanceGas(st.evm.Context.Coinbase, true)
|
||||
}
|
||||
}
|
||||
|
||||
return &ExecutionResult{
|
||||
|
|
|
@ -298,6 +298,12 @@ const (
|
|||
GasChangeCallStorageColdAccess GasChangeReason = 13
|
||||
// GasChangeCallFailedExecution is the burning of the remaining gas when the execution failed without a revert.
|
||||
GasChangeCallFailedExecution GasChangeReason = 14
|
||||
// GasChangeWitnessContractInit is the amount charged for adding to the witness during the contract creation initialization step
|
||||
GasChangeWitnessContractInit GasChangeReason = 15
|
||||
// GasChangeWitnessContractCreation is the amount charged for adding to the witness during the contract creation finalization step
|
||||
GasChangeWitnessContractCreation GasChangeReason = 16
|
||||
// GasChangeWitnessCodeChunk is the amount charged for touching one or more contract code chunks
|
||||
GasChangeWitnessCodeChunk GasChangeReason = 17
|
||||
|
||||
// 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.
|
||||
|
|
|
@ -63,6 +63,18 @@ func getData(data []byte, start uint64, size uint64) []byte {
|
|||
return common.RightPadBytes(data[start:end], int(size))
|
||||
}
|
||||
|
||||
func getDataAndAdjustedBounds(data []byte, start uint64, size uint64) (codeCopyPadded []byte, actualStart uint64, sizeNonPadded uint64) {
|
||||
length := uint64(len(data))
|
||||
if start > length {
|
||||
start = length
|
||||
}
|
||||
end := start + size
|
||||
if end > length {
|
||||
end = length
|
||||
}
|
||||
return common.RightPadBytes(data[start:end], int(size)), start, end - start
|
||||
}
|
||||
|
||||
// toWordSize returns the ceiled word size required for memory expansion.
|
||||
func toWordSize(size uint64) uint64 {
|
||||
if size > math.MaxUint64-31 {
|
||||
|
|
|
@ -57,6 +57,9 @@ type Contract struct {
|
|||
CodeAddr *common.Address
|
||||
Input []byte
|
||||
|
||||
// is the execution frame represented by this object a contract deployment
|
||||
IsDeployment bool
|
||||
|
||||
Gas uint64
|
||||
value *uint256.Int
|
||||
}
|
||||
|
|
|
@ -137,6 +137,8 @@ var PrecompiledContractsPrague = map[common.Address]PrecompiledContract{
|
|||
|
||||
var PrecompiledContractsBLS = PrecompiledContractsPrague
|
||||
|
||||
var PrecompiledContractsVerkle = PrecompiledContractsPrague
|
||||
|
||||
var (
|
||||
PrecompiledAddressesPrague []common.Address
|
||||
PrecompiledAddressesCancun []common.Address
|
||||
|
|
214
core/vm/eips.go
214
core/vm/eips.go
|
@ -18,9 +18,11 @@ package vm
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
@ -37,6 +39,7 @@ var activators = map[int]func(*JumpTable){
|
|||
1884: enable1884,
|
||||
1344: enable1344,
|
||||
1153: enable1153,
|
||||
4762: enable4762,
|
||||
}
|
||||
|
||||
// EnableEIP enables the given EIP on the config.
|
||||
|
@ -319,3 +322,214 @@ func enable6780(jt *JumpTable) {
|
|||
maxStack: maxStack(1, 0),
|
||||
}
|
||||
}
|
||||
|
||||
func opExtCodeCopyEIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
var (
|
||||
stack = scope.Stack
|
||||
a = stack.pop()
|
||||
memOffset = stack.pop()
|
||||
codeOffset = stack.pop()
|
||||
length = stack.pop()
|
||||
)
|
||||
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
|
||||
if overflow {
|
||||
uint64CodeOffset = math.MaxUint64
|
||||
}
|
||||
addr := common.Address(a.Bytes20())
|
||||
code := interpreter.evm.StateDB.GetCode(addr)
|
||||
contract := &Contract{
|
||||
Code: code,
|
||||
self: AccountRef(addr),
|
||||
}
|
||||
paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64())
|
||||
statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(addr, copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false)
|
||||
if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) {
|
||||
scope.Contract.Gas = 0
|
||||
return nil, ErrOutOfGas
|
||||
}
|
||||
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// opPush1EIP4762 handles the special case of PUSH1 opcode for EIP-4762, which
|
||||
// need not worry about the adjusted bound logic when adding the PUSHDATA to
|
||||
// the list of access events.
|
||||
func opPush1EIP4762(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
var (
|
||||
codeLen = uint64(len(scope.Contract.Code))
|
||||
integer = new(uint256.Int)
|
||||
)
|
||||
*pc += 1
|
||||
if *pc < codeLen {
|
||||
scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc])))
|
||||
|
||||
if !scope.Contract.IsDeployment && *pc%31 == 0 {
|
||||
// touch next chunk if PUSH1 is at the boundary. if so, *pc has
|
||||
// advanced past this boundary.
|
||||
contractAddr := scope.Contract.Address()
|
||||
statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false)
|
||||
if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) {
|
||||
scope.Contract.Gas = 0
|
||||
return nil, ErrOutOfGas
|
||||
}
|
||||
}
|
||||
} else {
|
||||
scope.Stack.push(integer.Clear())
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func makePushEIP4762(size uint64, pushByteSize int) executionFunc {
|
||||
return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
||||
var (
|
||||
codeLen = len(scope.Contract.Code)
|
||||
start = min(codeLen, int(*pc+1))
|
||||
end = min(codeLen, start+pushByteSize)
|
||||
)
|
||||
scope.Stack.push(new(uint256.Int).SetBytes(
|
||||
common.RightPadBytes(
|
||||
scope.Contract.Code[start:end],
|
||||
pushByteSize,
|
||||
)),
|
||||
)
|
||||
|
||||
if !scope.Contract.IsDeployment {
|
||||
contractAddr := scope.Contract.Address()
|
||||
statelessGas := interpreter.evm.AccessEvents.CodeChunksRangeGas(contractAddr, uint64(start), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false)
|
||||
if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) {
|
||||
scope.Contract.Gas = 0
|
||||
return nil, ErrOutOfGas
|
||||
}
|
||||
}
|
||||
|
||||
*pc += size
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
func enable4762(jt *JumpTable) {
|
||||
jt[SSTORE] = &operation{
|
||||
dynamicGas: gasSStore4762,
|
||||
execute: opSstore,
|
||||
minStack: minStack(2, 0),
|
||||
maxStack: maxStack(2, 0),
|
||||
}
|
||||
jt[SLOAD] = &operation{
|
||||
dynamicGas: gasSLoad4762,
|
||||
execute: opSload,
|
||||
minStack: minStack(1, 1),
|
||||
maxStack: maxStack(1, 1),
|
||||
}
|
||||
|
||||
jt[BALANCE] = &operation{
|
||||
execute: opBalance,
|
||||
dynamicGas: gasBalance4762,
|
||||
minStack: minStack(1, 1),
|
||||
maxStack: maxStack(1, 1),
|
||||
}
|
||||
|
||||
jt[EXTCODESIZE] = &operation{
|
||||
execute: opExtCodeSize,
|
||||
dynamicGas: gasExtCodeSize4762,
|
||||
minStack: minStack(1, 1),
|
||||
maxStack: maxStack(1, 1),
|
||||
}
|
||||
|
||||
jt[EXTCODEHASH] = &operation{
|
||||
execute: opExtCodeHash,
|
||||
dynamicGas: gasExtCodeHash4762,
|
||||
minStack: minStack(1, 1),
|
||||
maxStack: maxStack(1, 1),
|
||||
}
|
||||
|
||||
jt[EXTCODECOPY] = &operation{
|
||||
execute: opExtCodeCopyEIP4762,
|
||||
dynamicGas: gasExtCodeCopyEIP4762,
|
||||
minStack: minStack(4, 0),
|
||||
maxStack: maxStack(4, 0),
|
||||
memorySize: memoryExtCodeCopy,
|
||||
}
|
||||
|
||||
jt[CODECOPY] = &operation{
|
||||
execute: opCodeCopy,
|
||||
constantGas: GasFastestStep,
|
||||
dynamicGas: gasCodeCopyEip4762,
|
||||
minStack: minStack(3, 0),
|
||||
maxStack: maxStack(3, 0),
|
||||
memorySize: memoryCodeCopy,
|
||||
}
|
||||
|
||||
jt[SELFDESTRUCT] = &operation{
|
||||
execute: opSelfdestruct6780,
|
||||
dynamicGas: gasSelfdestructEIP4762,
|
||||
constantGas: params.SelfdestructGasEIP150,
|
||||
minStack: minStack(1, 0),
|
||||
maxStack: maxStack(1, 0),
|
||||
}
|
||||
|
||||
jt[CREATE] = &operation{
|
||||
execute: opCreate,
|
||||
constantGas: params.CreateNGasEip4762,
|
||||
dynamicGas: gasCreateEip3860,
|
||||
minStack: minStack(3, 1),
|
||||
maxStack: maxStack(3, 1),
|
||||
memorySize: memoryCreate,
|
||||
}
|
||||
|
||||
jt[CREATE2] = &operation{
|
||||
execute: opCreate2,
|
||||
constantGas: params.CreateNGasEip4762,
|
||||
dynamicGas: gasCreate2Eip3860,
|
||||
minStack: minStack(4, 1),
|
||||
maxStack: maxStack(4, 1),
|
||||
memorySize: memoryCreate2,
|
||||
}
|
||||
|
||||
jt[CALL] = &operation{
|
||||
execute: opCall,
|
||||
dynamicGas: gasCallEIP4762,
|
||||
minStack: minStack(7, 1),
|
||||
maxStack: maxStack(7, 1),
|
||||
memorySize: memoryCall,
|
||||
}
|
||||
|
||||
jt[CALLCODE] = &operation{
|
||||
execute: opCallCode,
|
||||
dynamicGas: gasCallCodeEIP4762,
|
||||
minStack: minStack(7, 1),
|
||||
maxStack: maxStack(7, 1),
|
||||
memorySize: memoryCall,
|
||||
}
|
||||
|
||||
jt[STATICCALL] = &operation{
|
||||
execute: opStaticCall,
|
||||
dynamicGas: gasStaticCallEIP4762,
|
||||
minStack: minStack(6, 1),
|
||||
maxStack: maxStack(6, 1),
|
||||
memorySize: memoryStaticCall,
|
||||
}
|
||||
|
||||
jt[DELEGATECALL] = &operation{
|
||||
execute: opDelegateCall,
|
||||
dynamicGas: gasDelegateCallEIP4762,
|
||||
minStack: minStack(6, 1),
|
||||
maxStack: maxStack(6, 1),
|
||||
memorySize: memoryDelegateCall,
|
||||
}
|
||||
|
||||
jt[PUSH1] = &operation{
|
||||
execute: opPush1EIP4762,
|
||||
constantGas: GasFastestStep,
|
||||
minStack: minStack(0, 1),
|
||||
maxStack: maxStack(0, 1),
|
||||
}
|
||||
for i := 1; i < 32; i++ {
|
||||
jt[PUSH1+OpCode(i)] = &operation{
|
||||
execute: makePushEIP4762(uint64(i+1), i+1),
|
||||
constantGas: GasFastestStep,
|
||||
minStack: minStack(0, 1),
|
||||
maxStack: maxStack(0, 1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import (
|
|||
"sync/atomic"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/state"
|
||||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
|
@ -42,6 +43,8 @@ type (
|
|||
func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
|
||||
var precompiles map[common.Address]PrecompiledContract
|
||||
switch {
|
||||
case evm.chainRules.IsVerkle:
|
||||
precompiles = PrecompiledContractsVerkle
|
||||
case evm.chainRules.IsPrague:
|
||||
precompiles = PrecompiledContractsPrague
|
||||
case evm.chainRules.IsCancun:
|
||||
|
@ -85,10 +88,11 @@ type BlockContext struct {
|
|||
// All fields can change between transactions.
|
||||
type TxContext struct {
|
||||
// Message information
|
||||
Origin common.Address // Provides information for ORIGIN
|
||||
GasPrice *big.Int // Provides information for GASPRICE (and is used to zero the basefee if NoBaseFee is set)
|
||||
BlobHashes []common.Hash // Provides information for BLOBHASH
|
||||
BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set
|
||||
Origin common.Address // Provides information for ORIGIN
|
||||
GasPrice *big.Int // Provides information for GASPRICE (and is used to zero the basefee if NoBaseFee is set)
|
||||
BlobHashes []common.Hash // Provides information for BLOBHASH
|
||||
BlobFeeCap *big.Int // Is used to zero the blobbasefee if NoBaseFee is set
|
||||
AccessEvents *state.AccessEvents // Capture all state accesses for this tx
|
||||
}
|
||||
|
||||
// EVM is the Ethereum Virtual Machine base object and provides
|
||||
|
@ -156,6 +160,9 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig
|
|||
// Reset resets the EVM with a new transaction context.Reset
|
||||
// This is not threadsafe and should only be done very cautiously.
|
||||
func (evm *EVM) Reset(txCtx TxContext, statedb StateDB) {
|
||||
if evm.chainRules.IsEIP4762 {
|
||||
txCtx.AccessEvents = state.NewAccessEvents(statedb.PointCache())
|
||||
}
|
||||
evm.TxContext = txCtx
|
||||
evm.StateDB = statedb
|
||||
}
|
||||
|
@ -200,6 +207,16 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
|
|||
p, isPrecompile := evm.precompile(addr)
|
||||
|
||||
if !evm.StateDB.Exist(addr) {
|
||||
if !isPrecompile && evm.chainRules.IsEIP4762 {
|
||||
// add proof of absence to witness
|
||||
wgas := evm.AccessEvents.AddAccount(addr, false)
|
||||
if gas < wgas {
|
||||
evm.StateDB.RevertToSnapshot(snapshot)
|
||||
return nil, 0, ErrOutOfGas
|
||||
}
|
||||
gas -= wgas
|
||||
}
|
||||
|
||||
if !isPrecompile && evm.chainRules.IsEIP158 && value.IsZero() {
|
||||
// Calling a non-existing account, don't do anything.
|
||||
return nil, gas, nil
|
||||
|
@ -439,7 +456,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
|||
|
||||
// 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.IsBerlin {
|
||||
if evm.chainRules.IsEIP2929 {
|
||||
evm.StateDB.AddAddressToAccessList(address)
|
||||
}
|
||||
// Ensure there's no existing contract already at the designated address.
|
||||
|
@ -479,8 +496,18 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
|||
// The contract is a scoped environment for this execution context only.
|
||||
contract := NewContract(caller, AccountRef(address), value, gas)
|
||||
contract.SetCodeOptionalHash(&address, codeAndHash)
|
||||
contract.IsDeployment = true
|
||||
|
||||
ret, err = evm.interpreter.Run(contract, nil, false)
|
||||
// Charge the contract creation init gas in verkle mode
|
||||
if evm.chainRules.IsEIP4762 {
|
||||
if !contract.UseGas(evm.AccessEvents.ContractCreateInitGas(address, value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeWitnessContractInit) {
|
||||
err = ErrOutOfGas
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
ret, err = evm.interpreter.Run(contract, nil, false)
|
||||
}
|
||||
|
||||
// Check whether the max code size has been exceeded, assign err if the case.
|
||||
if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
|
||||
|
@ -497,11 +524,24 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
|
|||
// be stored due to not enough gas set an error and let it be handled
|
||||
// by the error checking condition below.
|
||||
if err == nil {
|
||||
createDataGas := uint64(len(ret)) * params.CreateDataGas
|
||||
if contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
|
||||
evm.StateDB.SetCode(address, ret)
|
||||
if !evm.chainRules.IsEIP4762 {
|
||||
createDataGas := uint64(len(ret)) * params.CreateDataGas
|
||||
if !contract.UseGas(createDataGas, evm.Config.Tracer, tracing.GasChangeCallCodeStorage) {
|
||||
err = ErrCodeStoreOutOfGas
|
||||
}
|
||||
} else {
|
||||
err = ErrCodeStoreOutOfGas
|
||||
// Contract creation completed, touch the missing fields in the contract
|
||||
if !contract.UseGas(evm.AccessEvents.AddAccount(address, true), evm.Config.Tracer, tracing.GasChangeWitnessContractCreation) {
|
||||
err = ErrCodeStoreOutOfGas
|
||||
}
|
||||
|
||||
if err == nil && len(ret) > 0 && !contract.UseGas(evm.AccessEvents.CodeChunksRangeGas(address, 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeWitnessCodeChunk) {
|
||||
err = ErrCodeStoreOutOfGas
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
evm.StateDB.SetCode(address, ret)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -383,7 +383,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
|
|||
} else if !evm.StateDB.Exist(address) {
|
||||
gas += params.CallNewAccountGas
|
||||
}
|
||||
if transfersValue {
|
||||
if transfersValue && !evm.chainRules.IsEIP4762 {
|
||||
gas += params.CallValueTransferGas
|
||||
}
|
||||
memoryGas, err := memoryGasCost(mem, memorySize)
|
||||
|
@ -394,7 +394,14 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
|
|||
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
|
||||
if evm.chainRules.IsEIP4762 {
|
||||
if transfersValue {
|
||||
gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address))
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
}
|
||||
}
|
||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
@ -402,6 +409,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
|
|||
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
|
@ -414,12 +422,22 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
|
|||
gas uint64
|
||||
overflow bool
|
||||
)
|
||||
if stack.Back(2).Sign() != 0 {
|
||||
if stack.Back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 {
|
||||
gas += params.CallValueTransferGas
|
||||
}
|
||||
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
if evm.chainRules.IsEIP4762 {
|
||||
address := common.Address(stack.Back(1).Bytes20())
|
||||
transfersValue := !stack.Back(2).IsZero()
|
||||
if transfersValue {
|
||||
gas, overflow = math.SafeAdd(gas, evm.AccessEvents.ValueTransferGas(contract.Address(), address))
|
||||
if overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
}
|
||||
}
|
||||
evm.callGasTemp, err = callGas(evm.chainRules.IsEIP150, contract.Gas, gas, stack.Back(0))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
|
|
|
@ -359,9 +359,9 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
|
|||
if overflow {
|
||||
uint64CodeOffset = math.MaxUint64
|
||||
}
|
||||
|
||||
codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64())
|
||||
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
@ -434,6 +434,7 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
|
|||
num.Clear()
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var upper, lower uint64
|
||||
upper = interpreter.evm.Context.BlockNumber.Uint64()
|
||||
if upper < 257 {
|
||||
|
@ -583,6 +584,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
|
|||
if interpreter.evm.chainRules.IsEIP150 {
|
||||
gas -= gas / 64
|
||||
}
|
||||
|
||||
// reuse size int for stackvalue
|
||||
stackvalue := size
|
||||
|
||||
|
@ -623,6 +625,7 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
|
|||
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
|
||||
gas = scope.Contract.Gas
|
||||
)
|
||||
|
||||
// Apply EIP150
|
||||
gas -= gas / 64
|
||||
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
|
||||
|
@ -637,7 +640,6 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
|
|||
stackvalue.SetBytes(addr.Bytes())
|
||||
}
|
||||
scope.Stack.push(&stackvalue)
|
||||
|
||||
scope.Contract.RefundGas(returnGas, interpreter.evm.Config.Tracer, tracing.GasChangeCallLeftOverRefunded)
|
||||
|
||||
if suberr == ErrExecutionReverted {
|
||||
|
@ -896,6 +898,7 @@ func makePush(size uint64, pushByteSize int) executionFunc {
|
|||
pushByteSize,
|
||||
)),
|
||||
)
|
||||
|
||||
*pc += size
|
||||
return nil, nil
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/core/tracing"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
"github.com/ethereum/go-ethereum/trie/utils"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
|
@ -75,6 +76,10 @@ type StateDB interface {
|
|||
// 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)
|
||||
|
||||
// PointCache returns the point cache used in computations
|
||||
PointCache() *utils.PointCache
|
||||
|
||||
Prepare(rules params.Rules, sender, coinbase common.Address, dest *common.Address, precompiles []common.Address, txAccesses types.AccessList)
|
||||
|
||||
RevertToSnapshot(int)
|
||||
|
|
|
@ -99,6 +99,9 @@ func NewEVMInterpreter(evm *EVM) *EVMInterpreter {
|
|||
// If jump table was not initialised we set the default one.
|
||||
var table *JumpTable
|
||||
switch {
|
||||
case evm.chainRules.IsVerkle:
|
||||
// TODO replace with proper instruction set when fork is specified
|
||||
table = &verkleInstructionSet
|
||||
case evm.chainRules.IsCancun:
|
||||
table = &cancunInstructionSet
|
||||
case evm.chainRules.IsShanghai:
|
||||
|
@ -219,6 +222,14 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
|||
// Capture pre-execution values for tracing.
|
||||
logged, pcCopy, gasCopy = false, pc, contract.Gas
|
||||
}
|
||||
|
||||
if in.evm.chainRules.IsEIP4762 && !contract.IsDeployment {
|
||||
// if the PC ends up in a new "chunk" of verkleized code, charge the
|
||||
// associated costs.
|
||||
contractAddr := contract.Address()
|
||||
contract.Gas -= in.evm.TxContext.AccessEvents.CodeChunksRangeGas(contractAddr, pc, 1, uint64(len(contract.Code)), false)
|
||||
}
|
||||
|
||||
// Get the operation from the jump table and validate the stack to ensure there are
|
||||
// enough stack items available to perform the operation.
|
||||
op = contract.GetOp(pc)
|
||||
|
|
|
@ -57,6 +57,7 @@ var (
|
|||
mergeInstructionSet = newMergeInstructionSet()
|
||||
shanghaiInstructionSet = newShanghaiInstructionSet()
|
||||
cancunInstructionSet = newCancunInstructionSet()
|
||||
verkleInstructionSet = newVerkleInstructionSet()
|
||||
)
|
||||
|
||||
// JumpTable contains the EVM opcodes supported at a given fork.
|
||||
|
@ -80,6 +81,12 @@ func validate(jt JumpTable) JumpTable {
|
|||
return jt
|
||||
}
|
||||
|
||||
func newVerkleInstructionSet() JumpTable {
|
||||
instructionSet := newCancunInstructionSet()
|
||||
enable4762(&instructionSet)
|
||||
return validate(instructionSet)
|
||||
}
|
||||
|
||||
func newCancunInstructionSet() JumpTable {
|
||||
instructionSet := newShanghaiInstructionSet()
|
||||
enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode)
|
||||
|
|
|
@ -0,0 +1,159 @@
|
|||
// Copyright 2024 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 (
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/ethereum/go-ethereum/params"
|
||||
)
|
||||
|
||||
func gasSStore4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
gas := evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), true)
|
||||
if gas == 0 {
|
||||
gas = params.WarmStorageReadCostEIP2929
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasSLoad4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
gas := evm.AccessEvents.SlotGas(contract.Address(), stack.peek().Bytes32(), false)
|
||||
if gas == 0 {
|
||||
gas = params.WarmStorageReadCostEIP2929
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasBalance4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
address := stack.peek().Bytes20()
|
||||
gas := evm.AccessEvents.BalanceGas(address, false)
|
||||
if gas == 0 {
|
||||
gas = params.WarmStorageReadCostEIP2929
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasExtCodeSize4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
address := stack.peek().Bytes20()
|
||||
if _, isPrecompile := evm.precompile(address); isPrecompile {
|
||||
return 0, nil
|
||||
}
|
||||
gas := evm.AccessEvents.VersionGas(address, false)
|
||||
gas += evm.AccessEvents.CodeSizeGas(address, false)
|
||||
if gas == 0 {
|
||||
gas = params.WarmStorageReadCostEIP2929
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
address := stack.peek().Bytes20()
|
||||
if _, isPrecompile := evm.precompile(address); isPrecompile {
|
||||
return 0, nil
|
||||
}
|
||||
gas := evm.AccessEvents.CodeHashGas(address, false)
|
||||
if gas == 0 {
|
||||
gas = params.WarmStorageReadCostEIP2929
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func makeCallVariantGasEIP4762(oldCalculator gasFunc) gasFunc {
|
||||
return func(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
gas, err := oldCalculator(evm, contract, stack, mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if _, isPrecompile := evm.precompile(contract.Address()); isPrecompile {
|
||||
return gas, nil
|
||||
}
|
||||
witnessGas := evm.AccessEvents.MessageCallGas(contract.Address())
|
||||
if witnessGas == 0 {
|
||||
witnessGas = params.WarmStorageReadCostEIP2929
|
||||
}
|
||||
return witnessGas + gas, nil
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
gasCallEIP4762 = makeCallVariantGasEIP4762(gasCall)
|
||||
gasCallCodeEIP4762 = makeCallVariantGasEIP4762(gasCallCode)
|
||||
gasStaticCallEIP4762 = makeCallVariantGasEIP4762(gasStaticCall)
|
||||
gasDelegateCallEIP4762 = makeCallVariantGasEIP4762(gasDelegateCall)
|
||||
)
|
||||
|
||||
func gasSelfdestructEIP4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
beneficiaryAddr := common.Address(stack.peek().Bytes20())
|
||||
if _, isPrecompile := evm.precompile(beneficiaryAddr); isPrecompile {
|
||||
return 0, nil
|
||||
}
|
||||
contractAddr := contract.Address()
|
||||
statelessGas := evm.AccessEvents.VersionGas(contractAddr, false)
|
||||
statelessGas += evm.AccessEvents.CodeSizeGas(contractAddr, false)
|
||||
statelessGas += evm.AccessEvents.BalanceGas(contractAddr, false)
|
||||
if contractAddr != beneficiaryAddr {
|
||||
statelessGas += evm.AccessEvents.BalanceGas(beneficiaryAddr, false)
|
||||
}
|
||||
// Charge write costs if it transfers value
|
||||
if evm.StateDB.GetBalance(contractAddr).Sign() != 0 {
|
||||
statelessGas += evm.AccessEvents.BalanceGas(contractAddr, true)
|
||||
if contractAddr != beneficiaryAddr {
|
||||
statelessGas += evm.AccessEvents.BalanceGas(beneficiaryAddr, true)
|
||||
}
|
||||
}
|
||||
return statelessGas, nil
|
||||
}
|
||||
|
||||
func gasCodeCopyEip4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
|
||||
gas, err := gasCodeCopy(evm, contract, stack, mem, memorySize)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
var (
|
||||
codeOffset = stack.Back(1)
|
||||
length = stack.Back(2)
|
||||
)
|
||||
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
|
||||
if overflow {
|
||||
uint64CodeOffset = math.MaxUint64
|
||||
}
|
||||
_, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, length.Uint64())
|
||||
if !contract.IsDeployment {
|
||||
gas += evm.AccessEvents.CodeChunksRangeGas(contract.Address(), copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false)
|
||||
}
|
||||
return gas, nil
|
||||
}
|
||||
|
||||
func gasExtCodeCopyEIP4762(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())
|
||||
wgas := evm.AccessEvents.VersionGas(addr, false)
|
||||
wgas += evm.AccessEvents.CodeSizeGas(addr, false)
|
||||
if wgas == 0 {
|
||||
wgas = params.WarmStorageReadCostEIP2929
|
||||
}
|
||||
var overflow bool
|
||||
// We charge (cold-warm), since 'warm' is already charged as constantGas
|
||||
if gas, overflow = math.SafeAdd(gas, wgas); overflow {
|
||||
return 0, ErrGasUintOverflow
|
||||
}
|
||||
return gas, nil
|
||||
}
|
4
go.mod
4
go.mod
|
@ -15,20 +15,20 @@ require (
|
|||
github.com/cloudflare/cloudflare-go v0.79.0
|
||||
github.com/cockroachdb/pebble v1.1.0
|
||||
github.com/consensys/gnark-crypto v0.12.1
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c
|
||||
github.com/crate-crypto/go-kzg-4844 v1.0.0
|
||||
github.com/davecgh/go-spew v1.1.1
|
||||
github.com/deckarep/golang-set/v2 v2.1.0
|
||||
github.com/donovanhide/eventsource v0.0.0-20210830082556-c59027999da0
|
||||
github.com/dop251/goja v0.0.0-20230605162241-28ee0ee714f3
|
||||
github.com/ethereum/c-kzg-4844 v1.0.0
|
||||
github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0
|
||||
github.com/fatih/color v1.13.0
|
||||
github.com/ferranbt/fastssz v0.1.2
|
||||
github.com/fjl/gencodec v0.0.0-20230517082657-f9840df7b83e
|
||||
github.com/fjl/memsize v0.0.2
|
||||
github.com/fsnotify/fsnotify v1.6.0
|
||||
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff
|
||||
github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46
|
||||
github.com/gofrs/flock v0.8.1
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/golang/protobuf v1.5.4
|
||||
|
|
8
go.sum
8
go.sum
|
@ -133,8 +133,8 @@ github.com/consensys/gnark-crypto v0.12.1 h1:lHH39WuuFgVHONRl3J0LRBtuYdQTumFSDtJ
|
|||
github.com/consensys/gnark-crypto v0.12.1/go.mod h1:v2Gy7L/4ZRosZ7Ivs+9SfUDr0f5UlG+EM5t7MPHiLuY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233 h1:d28BXYi+wUpz1KBmiF9bWrjEMacUEREV6MBi2ODnrfQ=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20231025140028-3c0104f4b233/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c h1:uQYC5Z1mdLRPrZhHjHxufI8+2UG/i25QG92j0Er9p6I=
|
||||
github.com/crate-crypto/go-ipa v0.0.0-20240223125850-b1e8a79f509c/go.mod h1:geZJZH3SzKCqnz5VT0q/DyIG/tvu/dZk+VIfXicupJs=
|
||||
github.com/crate-crypto/go-kzg-4844 v1.0.0 h1:TsSgHwrkTKecKJ4kadtHi4b3xHW5dCFUDFnUp1TsawI=
|
||||
github.com/crate-crypto/go-kzg-4844 v1.0.0/go.mod h1:1kMhvPgI0Ky3yIa+9lFySEBUBXkYxeOi8ZF1sYioxhc=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
|
@ -169,6 +169,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
|||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA=
|
||||
github.com/ethereum/c-kzg-4844 v1.0.0/go.mod h1:VewdlzQmpT5QSrVhbBuGoCdFJkpaJlO1aQputP83wc0=
|
||||
github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0 h1:KrE8I4reeVvf7C1tm8elRjj4BdscTYzz/WAbYyf/JI4=
|
||||
github.com/ethereum/go-verkle v0.1.1-0.20240306133620-7d920df305f0/go.mod h1:D9AJLVXSyZQXJQVk8oh1EwjISE+sJTn2duYIZC0dy3w=
|
||||
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
|
||||
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||
github.com/ferranbt/fastssz v0.1.2 h1:Dky6dXlngF6Qjc+EfDipAkE83N5I5DE68bY6O0VLNPk=
|
||||
|
@ -185,8 +187,6 @@ github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61 h1:IZqZOB2fydHte3kUgx
|
|||
github.com/garslo/gogen v0.0.0-20170306192744-1d203ffc1f61/go.mod h1:Q0X6pkwTILDlzrGEckF6HKjXe48EgsY/l7K7vhY4MW8=
|
||||
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff h1:tY80oXqGNY4FhTFhk+o9oFHGINQ/+vhlm8HFzi6znCI=
|
||||
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff/go.mod h1:x7DCsMOv1taUwEWCzT4cmDeAkigA5/QCwUodaVOe8Ww=
|
||||
github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46 h1:BAIP2GihuqhwdILrV+7GJel5lyPV3u1+PgzrWLc0TkE=
|
||||
github.com/gballet/go-verkle v0.1.1-0.20231031103413-a67434b50f46/go.mod h1:QNpY22eby74jVhqH4WhDLDwxc/vqsern6pW+u2kbkpc=
|
||||
github.com/getkin/kin-openapi v0.53.0/go.mod h1:7Yn5whZr5kJi6t+kShccXS8ae1APpYTW6yheSwk8Yi4=
|
||||
github.com/getsentry/sentry-go v0.18.0 h1:MtBW5H9QgdcJabtZcuJG80BMOwaBpkRDZkxRkNC1sN0=
|
||||
github.com/getsentry/sentry-go v0.18.0/go.mod h1:Kgon4Mby+FJ7ZWHFUAZgVaIa8sxHtnRJRLTXZr51aKQ=
|
||||
|
|
|
@ -581,6 +581,11 @@ func (c *ChainConfig) IsVerkle(num *big.Int, time uint64) bool {
|
|||
return c.IsLondon(num) && isTimestampForked(c.VerkleTime, time)
|
||||
}
|
||||
|
||||
// IsEIP4762 returns whether eip 4762 has been activated at given block.
|
||||
func (c *ChainConfig) IsEIP4762(num *big.Int, time uint64) bool {
|
||||
return c.IsVerkle(num, time)
|
||||
}
|
||||
|
||||
// CheckCompatible checks whether scheduled fork transitions have been imported
|
||||
// with a mismatching chain configuration.
|
||||
func (c *ChainConfig) CheckCompatible(newcfg *ChainConfig, height uint64, time uint64) *ConfigCompatError {
|
||||
|
@ -909,6 +914,7 @@ func (err *ConfigCompatError) Error() string {
|
|||
type Rules struct {
|
||||
ChainID *big.Int
|
||||
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
|
||||
IsEIP2929, IsEIP4762 bool
|
||||
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
|
||||
IsBerlin, IsLondon bool
|
||||
IsMerge, IsShanghai, IsCancun, IsPrague bool
|
||||
|
@ -923,6 +929,7 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules
|
|||
}
|
||||
// disallow setting Merge out of order
|
||||
isMerge = isMerge && c.IsLondon(num)
|
||||
isVerkle := isMerge && c.IsVerkle(num, timestamp)
|
||||
return Rules{
|
||||
ChainID: new(big.Int).Set(chainID),
|
||||
IsHomestead: c.IsHomestead(num),
|
||||
|
@ -934,11 +941,13 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules
|
|||
IsPetersburg: c.IsPetersburg(num),
|
||||
IsIstanbul: c.IsIstanbul(num),
|
||||
IsBerlin: c.IsBerlin(num),
|
||||
IsEIP2929: c.IsBerlin(num) && !isVerkle,
|
||||
IsLondon: c.IsLondon(num),
|
||||
IsMerge: isMerge,
|
||||
IsShanghai: isMerge && c.IsShanghai(num, timestamp),
|
||||
IsCancun: isMerge && c.IsCancun(num, timestamp),
|
||||
IsPrague: isMerge && c.IsPrague(num, timestamp),
|
||||
IsVerkle: isMerge && c.IsVerkle(num, timestamp),
|
||||
IsVerkle: isVerkle,
|
||||
IsEIP4762: isVerkle,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -86,6 +86,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
|
||||
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.
|
||||
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright 2023 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 params
|
||||
|
||||
// Verkle tree EIP: costs associated to witness accesses
|
||||
var (
|
||||
WitnessBranchReadCost uint64 = 1900
|
||||
WitnessChunkReadCost uint64 = 200
|
||||
WitnessBranchWriteCost uint64 = 3000
|
||||
WitnessChunkWriteCost uint64 = 500
|
||||
WitnessChunkFillCost uint64 = 6200
|
||||
)
|
||||
|
||||
// ClearVerkleWitnessCosts sets all witness costs to 0, which is necessary
|
||||
// for historical block replay simulations.
|
||||
func ClearVerkleWitnessCosts() {
|
||||
WitnessBranchReadCost = 0
|
||||
WitnessChunkReadCost = 0
|
||||
WitnessBranchWriteCost = 0
|
||||
WitnessChunkWriteCost = 0
|
||||
WitnessChunkFillCost = 0
|
||||
}
|
|
@ -284,3 +284,7 @@ func (t *StateTrie) getSecKeyCache() map[string][]byte {
|
|||
}
|
||||
return t.secKeyCache
|
||||
}
|
||||
|
||||
func (t *StateTrie) IsVerkle() bool {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ import (
|
|||
"github.com/crate-crypto/go-ipa/bandersnatch/fr"
|
||||
"github.com/ethereum/go-ethereum/common/lru"
|
||||
"github.com/ethereum/go-ethereum/metrics"
|
||||
"github.com/gballet/go-verkle"
|
||||
"github.com/ethereum/go-verkle"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
|
@ -219,7 +219,7 @@ func CodeChunkKey(address []byte, chunk *uint256.Int) []byte {
|
|||
return GetTreeKey(address, treeIndex, subIndex)
|
||||
}
|
||||
|
||||
func storageIndex(bytes []byte) (*uint256.Int, byte) {
|
||||
func StorageIndex(bytes []byte) (*uint256.Int, byte) {
|
||||
// If the storage slot is in the header, we need to add the header offset.
|
||||
var key uint256.Int
|
||||
key.SetBytes(bytes)
|
||||
|
@ -245,7 +245,7 @@ func storageIndex(bytes []byte) (*uint256.Int, byte) {
|
|||
// StorageSlotKey returns the verkle tree key of the storage slot for the
|
||||
// specified account.
|
||||
func StorageSlotKey(address []byte, storageKey []byte) []byte {
|
||||
treeIndex, subIndex := storageIndex(storageKey)
|
||||
treeIndex, subIndex := StorageIndex(storageKey)
|
||||
return GetTreeKey(address, treeIndex, subIndex)
|
||||
}
|
||||
|
||||
|
@ -296,7 +296,7 @@ func CodeChunkKeyWithEvaluatedAddress(addressPoint *verkle.Point, chunk *uint256
|
|||
// slot for the specified account. The difference between StorageSlotKey is the
|
||||
// address evaluation is already computed to minimize the computational overhead.
|
||||
func StorageSlotKeyWithEvaluatedAddress(evaluated *verkle.Point, storageKey []byte) []byte {
|
||||
treeIndex, subIndex := storageIndex(storageKey)
|
||||
treeIndex, subIndex := StorageIndex(storageKey)
|
||||
return GetTreeKeyWithEvaluatedAddress(evaluated, treeIndex, subIndex)
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import (
|
|||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/gballet/go-verkle"
|
||||
"github.com/ethereum/go-verkle"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ import (
|
|||
"github.com/ethereum/go-ethereum/trie/trienode"
|
||||
"github.com/ethereum/go-ethereum/trie/utils"
|
||||
"github.com/ethereum/go-ethereum/triedb/database"
|
||||
"github.com/gballet/go-verkle"
|
||||
"github.com/ethereum/go-verkle"
|
||||
"github.com/holiman/uint256"
|
||||
)
|
||||
|
||||
|
|
Loading…
Reference in New Issue