diff --git a/cmd/geth/verkle.go b/cmd/geth/verkle.go
index ff3931356e..9eb37fb5a8 100644
--- a/cmd/geth/verkle.go
+++ b/cmd/geth/verkle.go
@@ -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"
)
diff --git a/consensus/beacon/consensus.go b/consensus/beacon/consensus.go
index 4e3fbeb09a..dd273a6f59 100644
--- a/consensus/beacon/consensus.go
+++ b/consensus/beacon/consensus.go
@@ -360,6 +360,10 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
amount := new(uint256.Int).SetUint64(w.Amount)
amount = amount.Mul(amount, uint256.NewInt(params.GWei))
state.AddBalance(w.Address, amount, tracing.BalanceIncreaseWithdrawal)
+
+ if chain.Config().IsEIP4762(header.Number, header.Time) {
+ state.Witness().TouchFullAccount(w.Address[:], true)
+ }
}
// No block reward which is issued by consensus layer instead.
}
diff --git a/core/error.go b/core/error.go
index e6e6ba2f90..161538fe43 100644
--- a/core/error.go
+++ b/core/error.go
@@ -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")
diff --git a/core/state/access_witness.go b/core/state/access_witness.go
new file mode 100644
index 0000000000..10db53992e
--- /dev/null
+++ b/core/state/access_witness.go
@@ -0,0 +1,297 @@
+// 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 .
+
+package state
+
+import (
+ "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
+
+// AccessWitness lists the locations of the state that are being accessed
+// during the production of a block.
+type AccessWitness struct {
+ branches map[branchAccessKey]mode
+ chunks map[chunkAccessKey]mode
+
+ pointCache *utils.PointCache
+}
+
+func NewAccessWitness(pointCache *utils.PointCache) *AccessWitness {
+ return &AccessWitness{
+ branches: make(map[branchAccessKey]mode),
+ chunks: make(map[chunkAccessKey]mode),
+ pointCache: pointCache,
+ }
+}
+
+// Merge is used to merge the witness that got generated during the execution
+// of a tx, with the accumulation of witnesses that were generated during the
+// execution of all the txs preceding this one in a given block.
+func (aw *AccessWitness) Merge(other *AccessWitness) {
+ for k := range other.branches {
+ aw.branches[k] |= other.branches[k]
+ }
+ for k, chunk := range other.chunks {
+ aw.chunks[k] |= chunk
+ }
+}
+
+// Key returns, predictably, the list of keys that were touched during the
+// buildup of the access witness.
+func (aw *AccessWitness) Keys() [][]byte {
+ // TODO: consider if parallelizing this is worth it, probably depending on len(aw.chunks).
+ keys := make([][]byte, 0, len(aw.chunks))
+ for chunk := range aw.chunks {
+ basePoint := aw.pointCache.Get(chunk.addr[:])
+ key := utils.GetTreeKeyWithEvaluatedAddress(basePoint, &chunk.treeIndex, chunk.leafKey)
+ keys = append(keys, key)
+ }
+ return keys
+}
+
+func (aw *AccessWitness) Copy() *AccessWitness {
+ naw := &AccessWitness{
+ branches: make(map[branchAccessKey]mode),
+ chunks: make(map[chunkAccessKey]mode),
+ pointCache: aw.pointCache,
+ }
+ naw.Merge(aw)
+ return naw
+}
+
+func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool) uint64 {
+ var gas uint64
+ for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ {
+ gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), isWrite)
+ }
+ return gas
+}
+
+func (aw *AccessWitness) TouchAndChargeMessageCall(addr []byte) uint64 {
+ var gas uint64
+ gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, false)
+ gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, false)
+ return gas
+}
+
+func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte) uint64 {
+ var gas uint64
+ gas += aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
+ gas += aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
+ return gas
+}
+
+// TouchAndChargeContractCreateInit charges access costs to initiate
+// a contract creation
+func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, createSendsValue bool) uint64 {
+ var gas uint64
+ gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true)
+ gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, true)
+ if createSendsValue {
+ gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, true)
+ }
+ return gas
+}
+
+func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte) uint64 {
+ for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ {
+ aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BalanceLeafKey || i == utils.NonceLeafKey)
+ }
+
+ // Kaustinen note: we're currently experimenting with stop chargin gas for the origin address
+ // so simple transfer still take 21000 gas. This is to potentially avoid breaking existing tooling.
+ // This is the reason why we return 0 instead of `gas`.
+ // Note that we still have to touch the addresses to make sure the witness is correct.
+ return 0
+}
+
+func (aw *AccessWitness) TouchTxExistingAndComputeGas(targetAddr []byte, sendsValue bool) uint64 {
+ aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.VersionLeafKey, false)
+ aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeSizeLeafKey, false)
+ aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeKeccakLeafKey, false)
+ aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.NonceLeafKey, false)
+ if sendsValue {
+ aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
+ } else {
+ aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, false)
+ }
+
+ // Kaustinen note: we're currently experimenting with stop chargin gas for the origin address
+ // so simple transfer still take 21000 gas. This is to potentially avoid breaking existing tooling.
+ // This is the reason why we return 0 instead of `gas`.
+ // Note that we still have to touch the addresses to make sure the witness is correct.
+ return 0
+}
+
+func (aw *AccessWitness) TouchSlotAndChargeGas(addr []byte, slot common.Hash, isWrite bool) uint64 {
+ treeIndex, subIndex := utils.StorageIndex(slot.Bytes())
+ return aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite)
+}
+
+func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 {
+ stemRead, selectorRead, stemWrite, selectorWrite, selectorFill := aw.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 witness.
+func (aw *AccessWitness) touchAddress(addr []byte, 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 := aw.branches[branchKey]; !hasStem {
+ branchRead = true
+ aw.branches[branchKey] = AccessWitnessReadFlag
+ }
+ if _, hasSelector := aw.chunks[chunkKey]; !hasSelector {
+ chunkRead = true
+ aw.chunks[chunkKey] = AccessWitnessReadFlag
+ }
+
+ // Write access.
+ var branchWrite, chunkWrite, chunkFill bool
+ if isWrite {
+ if (aw.branches[branchKey] & AccessWitnessWriteFlag) == 0 {
+ branchWrite = true
+ aw.branches[branchKey] |= AccessWitnessWriteFlag
+ }
+
+ chunkValue := aw.chunks[chunkKey]
+ if (chunkValue & AccessWitnessWriteFlag) == 0 {
+ chunkWrite = true
+ aw.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 []byte, treeIndex uint256.Int) branchAccessKey {
+ var sk branchAccessKey
+ copy(sk.addr[20-len(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
+}
+
+// touchCodeChunksRangeOnReadAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs
+func (aw *AccessWitness) TouchCodeChunksRangeAndChargeGas(contractAddr []byte, 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 := aw.touchAddressAndChargeGas(contractAddr, treeIndex, subIndex, isWrite)
+ var overflow bool
+ statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas)
+ if overflow {
+ panic("overflow when adding gas")
+ }
+ }
+
+ return statelessGasCharged
+}
+
+func (aw *AccessWitness) TouchVersion(addr []byte, isWrite bool) uint64 {
+ return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, isWrite)
+}
+
+func (aw *AccessWitness) TouchBalance(addr []byte, isWrite bool) uint64 {
+ return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, isWrite)
+}
+
+func (aw *AccessWitness) TouchNonce(addr []byte, isWrite bool) uint64 {
+ return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, isWrite)
+}
+
+func (aw *AccessWitness) TouchCodeSize(addr []byte, isWrite bool) uint64 {
+ return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, isWrite)
+}
+
+func (aw *AccessWitness) TouchCodeHash(addr []byte, isWrite bool) uint64 {
+ return aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey, isWrite)
+}
diff --git a/core/state/database.go b/core/state/database.go
index 188ecf0c86..365a28d900 100644
--- a/core/state/database.go
+++ b/core/state/database.go
@@ -139,6 +139,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
@@ -180,7 +183,7 @@ type cachingDB struct {
// 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, utils.NewPointCache(100))
}
tr, err := trie.NewStateTrie(trie.StateTrieID(root), db.triedb)
if err != nil {
diff --git a/core/state/statedb.go b/core/state/statedb.go
index 4a934fe82c..42adf401c0 100644
--- a/core/state/statedb.go
+++ b/core/state/statedb.go
@@ -36,6 +36,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"
)
@@ -139,6 +140,9 @@ type StateDB struct {
// Transient storage
transientStorage transientStorage
+ // Verkle witness
+ witness *AccessWitness
+
// Journal of state modifications. This is the backbone of
// Snapshot and RevertToSnapshot.
journal *journal
@@ -193,12 +197,30 @@ func New(root common.Hash, db Database, snaps *snapshot.Tree) (*StateDB, error)
transientStorage: newTransientStorage(),
hasher: crypto.NewKeccakState(),
}
+ if tr.IsVerkle() {
+ sdb.witness = sdb.NewAccessWitness()
+ }
if sdb.snaps != nil {
sdb.snap = sdb.snaps.Snapshot(root)
}
return sdb, nil
}
+func (s *StateDB) NewAccessWitness() *AccessWitness {
+ return NewAccessWitness(utils.NewPointCache(100))
+}
+
+func (s *StateDB) Witness() *AccessWitness {
+ if s.witness == nil {
+ s.witness = s.NewAccessWitness()
+ }
+ return s.witness
+}
+
+func (s *StateDB) SetWitness(aw *AccessWitness) {
+ s.witness = aw
+}
+
// SetLogger sets the logger for account update hooks.
func (s *StateDB) SetLogger(l *tracing.Hooks) {
s.logger = l
@@ -1275,7 +1297,7 @@ 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 {
// Clear out any leftover from previous executions
al := newAccessList()
s.accessList = al
diff --git a/core/state_processor.go b/core/state_processor.go
index b1a8938f67..7d69718c03 100644
--- a/core/state_processor.go
+++ b/core/state_processor.go
@@ -120,6 +120,7 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo
}
// Create a new context to be used in the EVM environment.
txContext := NewEVMTxContext(msg)
+ txContext.Accesses = statedb.NewAccessWitness()
evm.Reset(txContext, statedb)
// Apply the transaction to the current state (included in the env).
@@ -158,6 +159,10 @@ func ApplyTransactionWithEVM(msg *Message, config *params.ChainConfig, gp *GasPo
receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce())
}
+ if statedb.Witness() != nil {
+ statedb.Witness().Merge(txContext.Accesses)
+ }
+
// Set the receipt logs and create the bloom filter.
receipt.Logs = statedb.GetLogs(tx.Hash(), blockNumber.Uint64(), blockHash)
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
diff --git a/core/state_transition.go b/core/state_transition.go
index a52e24dc43..1e4bab6559 100644
--- a/core/state_transition.go
+++ b/core/state_transition.go
@@ -26,6 +26,7 @@ import (
"github.com/ethereum/go-ethereum/core/tracing"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/kzg4844"
"github.com/ethereum/go-ethereum/params"
"github.com/holiman/uint256"
@@ -68,7 +69,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 {
@@ -359,6 +360,19 @@ func (st *StateTransition) preCheck() error {
return st.buyGas()
}
+// tryConsumeGas tries to subtract gas from gasPool, setting the result in gasPool
+// if subtracting more gas than remains in gasPool, set gasPool = 0 and return false
+// otherwise, do the subtraction setting the result in gasPool and return true
+func tryConsumeGas(gasPool *uint64, gas uint64) bool {
+ if *gasPool < gas {
+ *gasPool = 0
+ return false
+ }
+
+ *gasPool -= gas
+ return true
+}
+
// TransitionDb will transition the state by applying the current message and
// returning the evm execution result with following fields.
//
@@ -405,6 +419,32 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
}
st.gasRemaining -= gas
+ if rules.IsEIP4762 {
+ targetAddr := msg.To
+ originAddr := msg.From
+
+ statelessGasOrigin := st.evm.Accesses.TouchTxOriginAndComputeGas(originAddr.Bytes())
+ if !tryConsumeGas(&st.gasRemaining, statelessGasOrigin) {
+ return nil, fmt.Errorf("%w: Insufficient funds to cover witness access costs for transaction: have %d, want %d", ErrInsufficientBalanceWitness, st.gasRemaining, gas)
+ }
+ originNonce := st.evm.StateDB.GetNonce(originAddr)
+
+ if msg.To != nil {
+ statelessGasDest := st.evm.Accesses.TouchTxExistingAndComputeGas(targetAddr.Bytes(), msg.Value.Sign() != 0)
+ if !tryConsumeGas(&st.gasRemaining, statelessGasDest) {
+ return nil, fmt.Errorf("%w: Insufficient funds to cover witness access costs for transaction: have %d, want %d", ErrInsufficientBalanceWitness, st.gasRemaining, gas)
+ }
+
+ // ensure the code size ends up in the access witness
+ st.evm.StateDB.GetCodeSize(*targetAddr)
+ } else {
+ contractAddr := crypto.CreateAddress(originAddr, originNonce)
+ if !tryConsumeGas(&st.gasRemaining, st.evm.Accesses.TouchAndChargeContractCreateInit(contractAddr.Bytes(), msg.Value.Sign() != 0)) {
+ return nil, fmt.Errorf("%w: Insufficient funds to cover witness access costs for transaction: have %d, want %d", ErrInsufficientBalanceWitness, st.gasRemaining, gas)
+ }
+ }
+ }
+
// Check clause 6
value, overflow := uint256.FromBig(msg.Value)
if overflow {
@@ -458,6 +498,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.Accesses.TouchFullAccount(st.evm.Context.Coinbase[:], true)
+ }
}
return &ExecutionResult{
diff --git a/core/vm/common.go b/core/vm/common.go
index 90ba4a4ad1..ba75950e37 100644
--- a/core/vm/common.go
+++ b/core/vm/common.go
@@ -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 {
diff --git a/core/vm/contract.go b/core/vm/contract.go
index 4e28260a67..cfda75b27e 100644
--- a/core/vm/contract.go
+++ b/core/vm/contract.go
@@ -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
}
diff --git a/core/vm/eips.go b/core/vm/eips.go
index 9f06b2818f..ff01a67e54 100644
--- a/core/vm/eips.go
+++ b/core/vm/eips.go
@@ -37,6 +37,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 +320,29 @@ func enable6780(jt *JumpTable) {
maxStack: maxStack(1, 0),
}
}
+
+func enable4762(jt *JumpTable) {
+ jt[SSTORE].constantGas = 0
+ jt[SSTORE].dynamicGas = gasSStore4762
+ jt[SLOAD].constantGas = 0
+ jt[SLOAD].dynamicGas = gasSLoad4762
+ jt[BALANCE].dynamicGas = gasBalance4762
+ jt[BALANCE].constantGas = 0
+ jt[EXTCODESIZE].constantGas = 0
+ jt[EXTCODESIZE].dynamicGas = gasExtCodeSize4762
+ jt[EXTCODEHASH].constantGas = 0
+ jt[EXTCODEHASH].dynamicGas = gasExtCodeHash4762
+ jt[EXTCODECOPY].constantGas = 0
+ jt[EXTCODECOPY].dynamicGas = gasExtCodeCopyEIP4762
+ jt[SELFDESTRUCT].dynamicGas = gasSelfdestructEIP4762
+ jt[CREATE].constantGas = params.CreateNGasEip4762
+ jt[CREATE2].constantGas = params.CreateNGasEip4762
+ jt[CALL].constantGas = 0
+ jt[CALL].dynamicGas = gasCallEIP4762
+ jt[CALLCODE].constantGas = 0
+ jt[CALLCODE].dynamicGas = gasCallCodeEIP4762
+ jt[STATICCALL].constantGas = 0
+ jt[STATICCALL].dynamicGas = gasStaticCallEIP4762
+ jt[DELEGATECALL].constantGas = 0
+ jt[DELEGATECALL].dynamicGas = gasDelegateCallEIP4762
+}
diff --git a/core/vm/evm.go b/core/vm/evm.go
index c18353a973..b8be848e68 100644
--- a/core/vm/evm.go
+++ b/core/vm/evm.go
@@ -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"
@@ -44,7 +45,7 @@ func (evm *EVM) precompile(addr common.Address) (PrecompiledContract, bool) {
switch {
case evm.chainRules.IsPrague:
precompiles = PrecompiledContractsPrague
- case evm.chainRules.IsCancun:
+ case evm.chainRules.IsCancun, evm.chainRules.IsVerkle:
precompiles = PrecompiledContractsCancun
case evm.chainRules.IsBerlin:
precompiles = PrecompiledContractsBerlin
@@ -85,10 +86,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
+ Accesses *state.AccessWitness // Capture all state accesses for this tx
}
// EVM is the Ethereum Virtual Machine base object and provides
@@ -149,6 +151,9 @@ func NewEVM(blockCtx BlockContext, txCtx TxContext, statedb StateDB, chainConfig
chainConfig: chainConfig,
chainRules: chainConfig.Rules(blockCtx.BlockNumber, blockCtx.Random != nil, blockCtx.Time),
}
+ if txCtx.Accesses == nil && chainConfig.IsPrague(blockCtx.BlockNumber, blockCtx.Time) {
+ evm.Accesses = evm.StateDB.(*state.StateDB).NewAccessWitness()
+ }
evm.interpreter = NewEVMInterpreter(evm)
return evm
}
@@ -156,6 +161,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 txCtx.Accesses == nil && evm.chainRules.IsPrague {
+ txCtx.Accesses = evm.StateDB.(*state.StateDB).NewAccessWitness()
+ }
evm.TxContext = txCtx
evm.StateDB = statedb
}
@@ -199,12 +207,24 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
snapshot := evm.StateDB.Snapshot()
p, isPrecompile := evm.precompile(addr)
+ var creation bool
if !evm.StateDB.Exist(addr) {
+ if !isPrecompile && evm.chainRules.IsEIP4762 {
+ // add proof of absence to witness
+ wgas := evm.Accesses.TouchFullAccount(addr.Bytes(), 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
}
evm.StateDB.CreateAccount(addr)
+ creation = true
}
evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value)
@@ -222,6 +242,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
// The depth-check is already done, and precompiles handled above
contract := NewContract(caller, AccountRef(addrCopy), value, gas)
contract.SetCallCode(&addrCopy, evm.StateDB.GetCodeHash(addrCopy), code)
+ contract.IsDeployment = creation
ret, err = evm.interpreter.Run(contract, input, false)
gas = contract.Gas
}
@@ -436,10 +457,16 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
return nil, common.Address{}, gas, ErrNonceUintOverflow
}
evm.StateDB.SetNonce(caller.Address(), nonce+1)
+<<<<<<< HEAD
// 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 {
+=======
+ // 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.IsEIP2929 {
+>>>>>>> fc62f50eb (cmd, core, params, trie: Add verkle access witness)
evm.StateDB.AddAddressToAccessList(address)
}
// Ensure there's no existing contract already at the designated address.
@@ -479,8 +506,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.Accesses.TouchAndChargeContractCreateInit(address.Bytes(), value.Sign() != 0), evm.Config.Tracer, tracing.GasChangeUnspecified) {
+ 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 +534,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.Accesses.TouchFullAccount(address.Bytes()[:], true), evm.Config.Tracer, tracing.GasChangeUnspecified) {
+ err = ErrOutOfGas
+ }
+
+ if err != nil && len(ret) > 0 && !contract.UseGas(evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true), evm.Config.Tracer, tracing.GasChangeUnspecified) {
+ err = ErrOutOfGas
+ }
+ }
+
+ if err == nil {
+ evm.StateDB.SetCode(address, ret)
}
}
diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go
index fd5fa14cf5..ca50ff9281 100644
--- a/core/vm/gas_table.go
+++ b/core/vm/gas_table.go
@@ -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)
@@ -402,6 +402,21 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow
}
+ if evm.chainRules.IsEIP4762 {
+ if _, isPrecompile := evm.precompile(address); !isPrecompile {
+ gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes()[:]))
+ if overflow {
+ return 0, ErrGasUintOverflow
+ }
+ }
+ if transfersValue {
+ gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeValueTransfer(contract.Address().Bytes()[:], address.Bytes()[:]))
+ if overflow {
+ return 0, ErrGasUintOverflow
+ }
+ }
+ }
+
return gas, nil
}
@@ -414,7 +429,7 @@ 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 {
@@ -427,6 +442,15 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow
}
+ if evm.chainRules.IsEIP4762 {
+ address := common.Address(stack.Back(1).Bytes20())
+ if _, isPrecompile := evm.precompile(address); !isPrecompile {
+ gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes()))
+ if overflow {
+ return 0, ErrGasUintOverflow
+ }
+ }
+ }
return gas, nil
}
@@ -443,6 +467,15 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow
}
+ if evm.chainRules.IsEIP4762 {
+ address := common.Address(stack.Back(1).Bytes20())
+ if _, isPrecompile := evm.precompile(address); !isPrecompile {
+ gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes()))
+ if overflow {
+ return 0, ErrGasUintOverflow
+ }
+ }
+ }
return gas, nil
}
@@ -459,6 +492,15 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow
}
+ if evm.chainRules.IsEIP4762 {
+ address := common.Address(stack.Back(1).Bytes20())
+ if _, isPrecompile := evm.precompile(address); !isPrecompile {
+ gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes()))
+ if overflow {
+ return 0, ErrGasUintOverflow
+ }
+ }
+ }
return gas, nil
}
diff --git a/core/vm/instructions.go b/core/vm/instructions.go
index a062bb15ff..8603366514 100644
--- a/core/vm/instructions.go
+++ b/core/vm/instructions.go
@@ -363,9 +363,17 @@ 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)
+ contractAddr := scope.Contract.Address()
+ paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64())
+ if interpreter.evm.chainRules.IsEIP4762 && !scope.Contract.IsDeployment {
+ statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(contractAddr[:], copyOffset, nonPaddedCopyLength, uint64(len(scope.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(), uint64(len(paddedCodeCopy)), paddedCodeCopy)
return nil, nil
}
@@ -382,8 +390,23 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
uint64CodeOffset = math.MaxUint64
}
addr := common.Address(a.Bytes20())
- codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64())
- scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
+ if interpreter.evm.chainRules.IsEIP4762 {
+ code := interpreter.evm.StateDB.GetCode(addr)
+ contract := &Contract{
+ Code: code,
+ self: AccountRef(addr),
+ }
+ paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64())
+ statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(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)
+ } else {
+ codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64())
+ scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
+ }
return nil, nil
}
@@ -438,6 +461,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 {
@@ -587,6 +611,15 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
if interpreter.evm.chainRules.IsEIP150 {
gas -= gas / 64
}
+
+ if interpreter.evm.chainRules.IsEIP4762 {
+ contractAddress := crypto.CreateAddress(scope.Contract.Address(), interpreter.evm.StateDB.GetNonce(scope.Contract.Address()))
+ statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:], value.Sign() != 0)
+ if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) {
+ return nil, ErrExecutionReverted
+ }
+ }
+
// reuse size int for stackvalue
stackvalue := size
@@ -627,6 +660,15 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
gas = scope.Contract.Gas
)
+ if interpreter.evm.chainRules.IsEIP4762 {
+ codeAndHash := &codeAndHash{code: input}
+ contractAddress := crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes())
+ statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:], endowment.Sign() != 0)
+ if !scope.Contract.UseGas(statelessGas, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified) {
+ return nil, ErrExecutionReverted
+ }
+ }
+
// Apply EIP150
gas -= gas / 64
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
@@ -641,7 +683,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 {
@@ -880,6 +921,17 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
*pc += 1
if *pc < codeLen {
scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc])))
+
+ if !scope.Contract.IsDeployment && interpreter.evm.chainRules.IsEIP4762 && *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.Accesses.TouchCodeChunksRangeAndChargeGas(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())
}
@@ -900,6 +952,16 @@ func makePush(size uint64, pushByteSize int) executionFunc {
pushByteSize,
)),
)
+
+ if !scope.Contract.IsDeployment && interpreter.evm.chainRules.IsEIP4762 {
+ contractAddr := scope.Contract.Address()
+ statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(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
}
diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go
index 406927e321..a10363b7f3 100644
--- a/core/vm/interpreter.go
+++ b/core/vm/interpreter.go
@@ -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 prooper 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.Accesses.TouchCodeChunksRangeAndChargeGas(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)
diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go
index 65716f9442..37e63eaab6 100644
--- a/core/vm/jump_table.go
+++ b/core/vm/jump_table.go
@@ -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,13 @@ func validate(jt JumpTable) JumpTable {
return jt
}
+func newVerkleInstructionSet() JumpTable {
+ instructionSet := newShanghaiInstructionSet()
+ enable6780(&instructionSet)
+ enable4762(&instructionSet)
+ return validate(instructionSet)
+}
+
func newCancunInstructionSet() JumpTable {
instructionSet := newShanghaiInstructionSet()
enable4844(&instructionSet) // EIP-4844 (BLOBHASH opcode)
diff --git a/core/vm/operations_verkle.go b/core/vm/operations_verkle.go
new file mode 100644
index 0000000000..a342622cf5
--- /dev/null
+++ b/core/vm/operations_verkle.go
@@ -0,0 +1,129 @@
+// 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 .
+
+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.Accesses.TouchSlotAndChargeGas(contract.Address().Bytes(), common.Hash(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.Accesses.TouchSlotAndChargeGas(contract.Address().Bytes(), common.Hash(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.Accesses.TouchBalance(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()
+ wgas := evm.Accesses.TouchVersion(address[:], false)
+ wgas += evm.Accesses.TouchCodeSize(address[:], false)
+ if wgas == 0 {
+ wgas = params.WarmStorageReadCostEIP2929
+ }
+
+ return wgas, nil
+}
+
+func gasExtCodeHash4762(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
+ address := stack.peek().Bytes20()
+ codehashgas := evm.Accesses.TouchCodeHash(address[:], false)
+ if codehashgas == 0 {
+ codehashgas = params.WarmStorageReadCostEIP2929
+ }
+ return codehashgas, 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
+ }
+ wgas := evm.Accesses.TouchCodeSize(contract.Address().Bytes(), false)
+ wgas += evm.Accesses.TouchCodeHash(contract.Address().Bytes(), false)
+ if wgas == 0 {
+ wgas = params.WarmStorageReadCostEIP2929
+ }
+ return wgas + 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())
+ contractAddr := contract.Address()
+ statelessGas := evm.Accesses.TouchVersion(contractAddr[:], false)
+ statelessGas += evm.Accesses.TouchCodeSize(contractAddr[:], false)
+ statelessGas += evm.Accesses.TouchBalance(contractAddr[:], false)
+ if contractAddr != beneficiaryAddr {
+ statelessGas += evm.Accesses.TouchBalance(beneficiaryAddr[:], false)
+ }
+ // Charge write costs if it transfers value
+ if evm.StateDB.GetBalance(contractAddr).Sign() != 0 {
+ statelessGas += evm.Accesses.TouchBalance(contractAddr[:], true)
+ if contractAddr != beneficiaryAddr {
+ statelessGas += evm.Accesses.TouchBalance(beneficiaryAddr[:], true)
+ }
+ }
+ return statelessGas, 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.Accesses.TouchVersion(addr[:], false)
+ wgas += evm.Accesses.TouchCodeSize(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
+}
diff --git a/go.mod b/go.mod
index 3cd0d82bdf..cf76ae35eb 100644
--- a/go.mod
+++ b/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
diff --git a/go.sum b/go.sum
index a7b4eb1c13..76e6feccdb 100644
--- a/go.sum
+++ b/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=
diff --git a/params/config.go b/params/config.go
index 439e882189..207cbb9b6b 100644
--- a/params/config.go
+++ b/params/config.go
@@ -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 {
@@ -901,6 +906,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
@@ -926,6 +932,8 @@ 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) && !c.IsPrague(num, timestamp),
+ IsEIP4762: c.IsPrague(num, timestamp),
IsLondon: c.IsLondon(num),
IsMerge: isMerge,
IsShanghai: isMerge && c.IsShanghai(num, timestamp),
diff --git a/params/protocol_params.go b/params/protocol_params.go
index 863cf58ece..d375a96642 100644
--- a/params/protocol_params.go
+++ b/params/protocol_params.go
@@ -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.
diff --git a/params/verkle_params.go b/params/verkle_params.go
new file mode 100644
index 0000000000..93d4f7cd64
--- /dev/null
+++ b/params/verkle_params.go
@@ -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 .
+
+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
+}
diff --git a/trie/secure_trie.go b/trie/secure_trie.go
index efd4dfb5d3..e38d5ac4dc 100644
--- a/trie/secure_trie.go
+++ b/trie/secure_trie.go
@@ -284,3 +284,7 @@ func (t *StateTrie) getSecKeyCache() map[string][]byte {
}
return t.secKeyCache
}
+
+func (t *StateTrie) IsVerkle() bool {
+ return false
+}
diff --git a/trie/utils/verkle.go b/trie/utils/verkle.go
index 328b2d2527..2a4a632d49 100644
--- a/trie/utils/verkle.go
+++ b/trie/utils/verkle.go
@@ -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)
}
diff --git a/trie/verkle.go b/trie/verkle.go
index 01d813d9ec..bb0c54857f 100644
--- a/trie/verkle.go
+++ b/trie/verkle.go
@@ -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"
)