simplified gas accounting layer (#405)

* simplified gas accounting layer

* integrate some review feedback

* Apply suggestions from code review

Co-authored-by: Ignacio Hagopian <jsign.uy@gmail.com>

* more suggestions from code review

* don't charge creation gas + charge code chunks in create

* A couple more fixes

* make linter happy

* fix create init gas consumption issue

* fix: in gas funcs, use tx witness instead of global witness

* fix linter issue

* Apply suggestions from code review

Co-authored-by: Ignacio Hagopian <jsign.uy@gmail.com>

* fix: EXTCODECOPY gas consumption

* fix warm gas costs

* fix the order gas is charged in during contract creation epilogue

* fix selfdestruct

* fix #365 in eip rewrite (#407)

* fix: OOG type in code creation OOG (#408)

* core/vm: charge BLOCKHASH witness cost (#409)

* core/vm: charge BLOCKHASH witness cost

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* remove gas optimization for now

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

---------

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* remove redundant logic for contract creation (#413)

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* fix precompile address check for charging witness costs & fix missing value-bearing rule (#412)

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* core/vm: fix wrong check (#416)

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* charge for account creation if selfdestruct creates a new account (#417)

* add key comparison test (#418)

* core/vm: charge contract init before execution logic (#419)

* core/vm: charge contract init before execution logic

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* fix CREATE2 as well

---------

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
Co-authored-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com>

* quell linter

---------

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>
Co-authored-by: Ignacio Hagopian <jsign.uy@gmail.com>
This commit is contained in:
Guillaume Ballet 2024-04-15 13:41:28 +02:00
parent 0d70489104
commit 1930b97b65
20 changed files with 424 additions and 283 deletions

View file

@ -32,7 +32,6 @@ import (
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/utils" "github.com/ethereum/go-ethereum/trie/utils"
"github.com/ethereum/go-verkle" "github.com/ethereum/go-verkle"
"github.com/holiman/uint256"
) )
// Proof-of-stake protocol constants. // Proof-of-stake protocol constants.
@ -357,11 +356,7 @@ func (beacon *Beacon) Finalize(chain consensus.ChainHeaderReader, header *types.
state.AddBalance(w.Address, amount) state.AddBalance(w.Address, amount)
// The returned gas is not charged // The returned gas is not charged
state.Witness().TouchAddressOnWriteAndComputeGas(w.Address[:], uint256.Int{}, utils.VersionLeafKey) state.Witness().TouchFullAccount(w.Address[:], true)
state.Witness().TouchAddressOnWriteAndComputeGas(w.Address[:], uint256.Int{}, utils.BalanceLeafKey)
state.Witness().TouchAddressOnWriteAndComputeGas(w.Address[:], uint256.Int{}, utils.NonceLeafKey)
state.Witness().TouchAddressOnWriteAndComputeGas(w.Address[:], uint256.Int{}, utils.CodeKeccakLeafKey)
state.Witness().TouchAddressOnWriteAndComputeGas(w.Address[:], uint256.Int{}, utils.CodeSizeLeafKey)
} }
} }

View file

@ -33,8 +33,6 @@ import (
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
"golang.org/x/crypto/sha3" "golang.org/x/crypto/sha3"
) )
@ -568,19 +566,10 @@ func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header
r.Div(r, big8) r.Div(r, big8)
// This should not happen, but it's useful for replay tests // This should not happen, but it's useful for replay tests
if config.IsPrague(header.Number, header.Time) {
state.Witness().TouchAddressOnReadAndComputeGas(uncle.Coinbase.Bytes(), uint256.Int{}, utils.BalanceLeafKey)
}
state.AddBalance(uncle.Coinbase, r) state.AddBalance(uncle.Coinbase, r)
r.Div(blockReward, big32) r.Div(blockReward, big32)
reward.Add(reward, r) reward.Add(reward, r)
} }
if config.IsPrague(header.Number, header.Time) {
state.Witness().TouchAddressOnReadAndComputeGas(header.Coinbase.Bytes(), uint256.Int{}, utils.BalanceLeafKey)
state.Witness().TouchAddressOnReadAndComputeGas(header.Coinbase.Bytes(), uint256.Int{}, utils.VersionLeafKey)
state.Witness().TouchAddressOnReadAndComputeGas(header.Coinbase.Bytes(), uint256.Int{}, utils.NonceLeafKey)
state.Witness().TouchAddressOnReadAndComputeGas(header.Coinbase.Bytes(), uint256.Int{}, utils.CodeKeccakLeafKey)
}
state.AddBalance(header.Coinbase, reward) state.AddBalance(header.Coinbase, reward)
} }

View file

@ -18,6 +18,7 @@ package state
import ( import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie/utils" "github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256" "github.com/holiman/uint256"
@ -88,27 +89,25 @@ func (aw *AccessWitness) Copy() *AccessWitness {
return naw return naw
} }
func (aw *AccessWitness) TouchAndChargeProofOfAbsence(addr []byte) uint64 { func (aw *AccessWitness) TouchFullAccount(addr []byte, isWrite bool) uint64 {
var gas uint64 var gas uint64
gas += aw.TouchAddressOnReadAndComputeGas(addr, zeroTreeIndex, utils.VersionLeafKey) for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ {
gas += aw.TouchAddressOnReadAndComputeGas(addr, zeroTreeIndex, utils.BalanceLeafKey) gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, byte(i), isWrite)
gas += aw.TouchAddressOnReadAndComputeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey) }
gas += aw.TouchAddressOnReadAndComputeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey)
gas += aw.TouchAddressOnReadAndComputeGas(addr, zeroTreeIndex, utils.NonceLeafKey)
return gas return gas
} }
func (aw *AccessWitness) TouchAndChargeMessageCall(addr []byte) uint64 { func (aw *AccessWitness) TouchAndChargeMessageCall(addr []byte) uint64 {
var gas uint64 var gas uint64
gas += aw.TouchAddressOnReadAndComputeGas(addr, zeroTreeIndex, utils.VersionLeafKey) gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, false)
gas += aw.TouchAddressOnReadAndComputeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey) gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey, false)
return gas return gas
} }
func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte) uint64 { func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte) uint64 {
var gas uint64 var gas uint64
gas += aw.TouchAddressOnWriteAndComputeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey) gas += aw.touchAddressAndChargeGas(callerAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
gas += aw.TouchAddressOnWriteAndComputeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey) gas += aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
return gas return gas
} }
@ -116,33 +115,18 @@ func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []by
// a contract creation // a contract creation
func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, createSendsValue bool) uint64 { func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte, createSendsValue bool) uint64 {
var gas uint64 var gas uint64
gas += aw.TouchAddressOnWriteAndComputeGas(addr, zeroTreeIndex, utils.VersionLeafKey) gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.VersionLeafKey, true)
gas += aw.TouchAddressOnWriteAndComputeGas(addr, zeroTreeIndex, utils.NonceLeafKey) gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.NonceLeafKey, true)
if createSendsValue { if createSendsValue {
gas += aw.TouchAddressOnWriteAndComputeGas(addr, zeroTreeIndex, utils.BalanceLeafKey) gas += aw.touchAddressAndChargeGas(addr, zeroTreeIndex, utils.BalanceLeafKey, true)
} }
return gas return gas
} }
// TouchAndChargeContractCreateCompleted charges access access costs after
// the completion of a contract creation to populate the created account in
// the tree
func (aw *AccessWitness) TouchAndChargeContractCreateCompleted(addr []byte) uint64 {
var gas uint64
gas += aw.TouchAddressOnWriteAndComputeGas(addr, zeroTreeIndex, utils.VersionLeafKey)
gas += aw.TouchAddressOnWriteAndComputeGas(addr, zeroTreeIndex, utils.BalanceLeafKey)
gas += aw.TouchAddressOnWriteAndComputeGas(addr, zeroTreeIndex, utils.CodeSizeLeafKey)
gas += aw.TouchAddressOnWriteAndComputeGas(addr, zeroTreeIndex, utils.CodeKeccakLeafKey)
gas += aw.TouchAddressOnWriteAndComputeGas(addr, zeroTreeIndex, utils.NonceLeafKey)
return gas
}
func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte) uint64 { func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte) uint64 {
aw.TouchAddressOnReadAndComputeGas(originAddr, zeroTreeIndex, utils.VersionLeafKey) for i := utils.VersionLeafKey; i <= utils.CodeSizeLeafKey; i++ {
aw.TouchAddressOnReadAndComputeGas(originAddr, zeroTreeIndex, utils.CodeSizeLeafKey) aw.touchAddressAndChargeGas(originAddr, zeroTreeIndex, byte(i), i == utils.BalanceLeafKey || i == utils.NonceLeafKey)
aw.TouchAddressOnReadAndComputeGas(originAddr, zeroTreeIndex, utils.CodeKeccakLeafKey) }
aw.TouchAddressOnWriteAndComputeGas(originAddr, zeroTreeIndex, utils.NonceLeafKey)
aw.TouchAddressOnWriteAndComputeGas(originAddr, zeroTreeIndex, utils.BalanceLeafKey)
// Kaustinen note: we're currently experimenting with stop chargin gas for the origin address // 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. // so simple transfer still take 21000 gas. This is to potentially avoid breaking existing tooling.
@ -152,14 +136,14 @@ func (aw *AccessWitness) TouchTxOriginAndComputeGas(originAddr []byte) uint64 {
} }
func (aw *AccessWitness) TouchTxExistingAndComputeGas(targetAddr []byte, sendsValue bool) uint64 { func (aw *AccessWitness) TouchTxExistingAndComputeGas(targetAddr []byte, sendsValue bool) uint64 {
aw.TouchAddressOnReadAndComputeGas(targetAddr, zeroTreeIndex, utils.VersionLeafKey) aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.VersionLeafKey, false)
aw.TouchAddressOnReadAndComputeGas(targetAddr, zeroTreeIndex, utils.CodeSizeLeafKey) aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeSizeLeafKey, false)
aw.TouchAddressOnReadAndComputeGas(targetAddr, zeroTreeIndex, utils.CodeKeccakLeafKey) aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.CodeHashLeafKey, false)
aw.TouchAddressOnReadAndComputeGas(targetAddr, zeroTreeIndex, utils.NonceLeafKey) aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.NonceLeafKey, false)
if sendsValue { if sendsValue {
aw.TouchAddressOnWriteAndComputeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey) aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, true)
} else { } else {
aw.TouchAddressOnReadAndComputeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey) aw.touchAddressAndChargeGas(targetAddr, zeroTreeIndex, utils.BalanceLeafKey, false)
} }
// Kaustinen note: we're currently experimenting with stop chargin gas for the origin address // Kaustinen note: we're currently experimenting with stop chargin gas for the origin address
@ -169,12 +153,9 @@ func (aw *AccessWitness) TouchTxExistingAndComputeGas(targetAddr []byte, sendsVa
return 0 return 0
} }
func (aw *AccessWitness) TouchAddressOnWriteAndComputeGas(addr []byte, treeIndex uint256.Int, subIndex byte) uint64 { func (aw *AccessWitness) TouchSlotAndChargeGas(addr []byte, slot common.Hash, isWrite bool) uint64 {
return aw.touchAddressAndChargeGas(addr, treeIndex, subIndex, true) treeIndex, subIndex := utils.GetTreeKeyStorageSlotTreeIndexes(slot.Bytes())
} return aw.touchAddressAndChargeGas(addr, *treeIndex, subIndex, isWrite)
func (aw *AccessWitness) TouchAddressOnReadAndComputeGas(addr []byte, treeIndex uint256.Int, subIndex byte) uint64 {
return aw.touchAddressAndChargeGas(addr, treeIndex, subIndex, false)
} }
func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 { func (aw *AccessWitness) touchAddressAndChargeGas(addr []byte, treeIndex uint256.Int, subIndex byte, isWrite bool) uint64 {
@ -259,3 +240,58 @@ func newChunkAccessKey(branchKey branchAccessKey, leafKey byte) chunkAccessKey {
lk.leafKey = leafKey lk.leafKey = leafKey
return lk 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.CodeHashLeafKey, isWrite)
}

View file

@ -1367,7 +1367,7 @@ func (s *StateDB) Commit(block uint64, deleteEmptyObjects bool) (common.Hash, er
// - Add coinbase to access list (EIP-3651) // - Add coinbase to access list (EIP-3651)
// - Reset transient storage (EIP-1153) // - 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) { 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 // Clear out any leftover from previous executions
al := newAccessList() al := newAccessList()
s.accessList = al s.accessList = al

View file

@ -35,7 +35,6 @@ import (
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie" "github.com/ethereum/go-ethereum/trie"
"github.com/ethereum/go-ethereum/trie/utils"
tutils "github.com/ethereum/go-ethereum/trie/utils" tutils "github.com/ethereum/go-ethereum/trie/utils"
"github.com/ethereum/go-verkle" "github.com/ethereum/go-verkle"
"github.com/holiman/uint256" "github.com/holiman/uint256"
@ -261,7 +260,7 @@ func (kvm *keyValueMigrator) addAccount(addr []byte, acc *types.StateAccount) {
binary.LittleEndian.PutUint64(nonce[:8], acc.Nonce) binary.LittleEndian.PutUint64(nonce[:8], acc.Nonce)
leafNodeData.Values[tutils.NonceLeafKey] = nonce[:] leafNodeData.Values[tutils.NonceLeafKey] = nonce[:]
leafNodeData.Values[tutils.CodeKeccakLeafKey] = acc.CodeHash[:] leafNodeData.Values[tutils.CodeHashLeafKey] = acc.CodeHash[:]
} }
func (kvm *keyValueMigrator) addAccountCode(addr []byte, codeSize uint64, chunks []byte) { func (kvm *keyValueMigrator) addAccountCode(addr []byte, codeSize uint64, chunks []byte) {
@ -369,7 +368,13 @@ func (kvm *keyValueMigrator) migrateCollectedKeyValues(tree *trie.VerkleTrie) er
return nil return nil
} }
// InsertBlockHashHistoryAtEip2935Fork handles the insertion of all previous 256
// blocks on the eip2935 activation block. It also adds the account header of the
// history contract to the witness.
func InsertBlockHashHistoryAtEip2935Fork(statedb *state.StateDB, prevNumber uint64, prevHash common.Hash, chain consensus.ChainHeaderReader) { func InsertBlockHashHistoryAtEip2935Fork(statedb *state.StateDB, prevNumber uint64, prevHash common.Hash, chain consensus.ChainHeaderReader) {
// Make sure that the historical contract is added to the witness
statedb.Witness().TouchFullAccount(params.HistoryStorageAddress[:], true)
ancestor := chain.GetHeader(prevHash, prevNumber) ancestor := chain.GetHeader(prevHash, prevNumber)
for i := prevNumber; i > 0 && i >= prevNumber-params.Eip2935BlockHashHistorySize; i-- { for i := prevNumber; i > 0 && i >= prevNumber-params.Eip2935BlockHashHistorySize; i-- {
ProcessParentBlockHash(statedb, i, ancestor.Hash()) ProcessParentBlockHash(statedb, i, ancestor.Hash())
@ -382,6 +387,5 @@ func ProcessParentBlockHash(statedb *state.StateDB, prevNumber uint64, prevHash
var key common.Hash var key common.Hash
binary.BigEndian.PutUint64(key[24:], ringIndex) binary.BigEndian.PutUint64(key[24:], ringIndex)
statedb.SetState(params.HistoryStorageAddress, key, prevHash) statedb.SetState(params.HistoryStorageAddress, key, prevHash)
index, suffix := utils.GetTreeKeyStorageSlotTreeIndexes(key[:]) statedb.Witness().TouchSlotAndChargeGas(params.HistoryStorageAddress[:], key, true)
statedb.Witness().TouchAddressOnWriteAndComputeGas(params.HistoryStorageAddress[:], *index, suffix)
} }

View file

@ -20,6 +20,9 @@ import (
"bytes" "bytes"
"crypto/ecdsa" "crypto/ecdsa"
"encoding/binary" "encoding/binary"
"encoding/json"
"fmt"
"os"
//"fmt" //"fmt"
"math/big" "math/big"
@ -37,7 +40,9 @@ import (
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/tracers/logger"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rlp"
"github.com/ethereum/go-ethereum/trie/utils" "github.com/ethereum/go-ethereum/trie/utils"
//"github.com/ethereum/go-ethereum/rlp" //"github.com/ethereum/go-ethereum/rlp"
@ -472,11 +477,19 @@ func TestProcessVerkle(t *testing.T) {
}, },
}, },
} }
loggerCfg = &logger.Config{}
) )
os.MkdirAll("output", 0755)
traceFile, err := os.Create("./output/traces.jsonl")
if err != nil {
t.Fatal(err)
}
// Verkle trees use the snapshot, which must be enabled before the // Verkle trees use the snapshot, which must be enabled before the
// data is saved into the tree+database. // data is saved into the tree+database.
genesis := gspec.MustCommit(bcdb) genesis := gspec.MustCommit(bcdb)
blockchain, _ := NewBlockChain(bcdb, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{}, nil, nil) blockchain, _ := NewBlockChain(bcdb, nil, gspec, nil, beacon.New(ethash.NewFaker()), vm.Config{Tracer: logger.NewJSONLogger(loggerCfg, traceFile)}, nil, nil)
defer blockchain.Stop() defer blockchain.Stop()
// Commit the genesis block to the block-generation database as it // Commit the genesis block to the block-generation database as it
@ -485,8 +498,8 @@ func TestProcessVerkle(t *testing.T) {
txCost1 := params.TxGas txCost1 := params.TxGas
txCost2 := params.TxGas txCost2 := params.TxGas
contractCreationCost := intrinsicContractCreationGas + uint64(5600+700+700+700 /* creation with value */ +2739 /* execution costs */) contractCreationCost := intrinsicContractCreationGas + uint64(5600+700+700+700 /* creation with value */ +1439 /* execution costs */)
codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(5600+700 /* creation */ +302044 /* execution costs */) codeWithExtCodeCopyGas := intrinsicCodeWithExtCodeCopyGas + uint64(5600+700 /* creation */ +44044 /* execution costs */)
blockGasUsagesExpected := []uint64{ blockGasUsagesExpected := []uint64{
txCost1*2 + txCost2, txCost1*2 + txCost2,
txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas, txCost1*2 + txCost2 + contractCreationCost + codeWithExtCodeCopyGas,
@ -513,6 +526,33 @@ func TestProcessVerkle(t *testing.T) {
} }
}) })
kvjson, err := json.Marshal(keyvals)
if err != nil {
t.Fatal(err)
}
err = os.WriteFile("./output/statediffs.json", kvjson, 0644)
if err != nil {
t.Fatal(err)
}
blockrlp, err := rlp.EncodeToBytes(genesis)
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(fmt.Sprintf("./output/block%d.rlp.hex", 0), []byte(fmt.Sprintf("%x", blockrlp)), 0644)
if err != nil {
t.Fatal(err)
}
for _, block := range chain {
blockrlp, err := rlp.EncodeToBytes(block)
if err != nil {
t.Fatal(err)
}
err = os.WriteFile(fmt.Sprintf("./output/block%d.rlp.hex", block.NumberU64()), []byte(fmt.Sprintf("%x", blockrlp)), 0644)
if err != nil {
t.Fatal(err)
}
}
// Uncomment to extract block #2 // Uncomment to extract block #2
//f, _ := os.Create("block2.rlp") //f, _ := os.Create("block2.rlp")
//defer f.Close() //defer f.Close()
@ -521,7 +561,7 @@ func TestProcessVerkle(t *testing.T) {
//f.Write(buf.Bytes()) //f.Write(buf.Bytes())
//fmt.Printf("root= %x\n", chain[0].Root()) //fmt.Printf("root= %x\n", chain[0].Root())
// check the proof for the last block // check the proof for the last block
err := trie.DeserializeAndVerifyVerkleProof(proofs[1], chain[0].Root().Bytes(), chain[1].Root().Bytes(), keyvals[1]) err = trie.DeserializeAndVerifyVerkleProof(proofs[1], chain[0].Root().Bytes(), chain[1].Root().Bytes(), keyvals[1])
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -913,7 +953,7 @@ func TestProcessVerklExtCodeHashOpcode(t *testing.T) {
} }
codeHashStateDiff := statediff[1][stateDiffIdx].SuffixDiffs[0] codeHashStateDiff := statediff[1][stateDiffIdx].SuffixDiffs[0]
if codeHashStateDiff.Suffix != utils.CodeKeccakLeafKey { if codeHashStateDiff.Suffix != utils.CodeHashLeafKey {
t.Fatalf("code hash invalid suffix") t.Fatalf("code hash invalid suffix")
} }
if codeHashStateDiff.CurrentValue == nil { if codeHashStateDiff.CurrentValue == nil {

View file

@ -27,10 +27,7 @@ import (
"github.com/ethereum/go-ethereum/consensus/misc/eip4844" "github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
) )
// ExecutionResult includes all output after executing given evm // ExecutionResult includes all output after executing given evm
@ -405,7 +402,7 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
} }
st.gasRemaining -= gas st.gasRemaining -= gas
if rules.IsPrague { if rules.IsEIP4762 {
targetAddr := msg.To targetAddr := msg.To
originAddr := msg.From originAddr := msg.From
@ -413,7 +410,6 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
if !tryConsumeGas(&st.gasRemaining, statelessGasOrigin) { 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) 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 { if msg.To != nil {
statelessGasDest := st.evm.Accesses.TouchTxExistingAndComputeGas(targetAddr.Bytes(), msg.Value.Sign() != 0) statelessGasDest := st.evm.Accesses.TouchTxExistingAndComputeGas(targetAddr.Bytes(), msg.Value.Sign() != 0)
@ -423,11 +419,6 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
// ensure the code size ends up in the access witness // ensure the code size ends up in the access witness
st.evm.StateDB.GetCodeSize(*targetAddr) 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)
}
} }
} }
@ -480,12 +471,8 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) {
st.state.AddBalance(st.evm.Context.Coinbase, fee) st.state.AddBalance(st.evm.Context.Coinbase, fee)
// add the coinbase to the witness iff the fee is greater than 0 // add the coinbase to the witness iff the fee is greater than 0
if rules.IsPrague && fee.Sign() != 0 { if rules.IsEIP4762 && fee.Sign() != 0 {
st.evm.Accesses.TouchAddressOnWriteAndComputeGas(st.evm.Context.Coinbase[:], uint256.Int{}, utils.VersionLeafKey) st.evm.Accesses.TouchFullAccount(st.evm.Context.Coinbase[:], true)
st.evm.Accesses.TouchAddressOnWriteAndComputeGas(st.evm.Context.Coinbase[:], uint256.Int{}, utils.BalanceLeafKey)
st.evm.Accesses.TouchAddressOnWriteAndComputeGas(st.evm.Context.Coinbase[:], uint256.Int{}, utils.NonceLeafKey)
st.evm.Accesses.TouchAddressOnWriteAndComputeGas(st.evm.Context.Coinbase[:], uint256.Int{}, utils.CodeKeccakLeafKey)
st.evm.Accesses.TouchAddressOnWriteAndComputeGas(st.evm.Context.Coinbase[:], uint256.Int{}, utils.CodeSizeLeafKey)
} }
} }

View file

@ -37,6 +37,7 @@ var activators = map[int]func(*JumpTable){
1884: enable1884, 1884: enable1884,
1344: enable1344, 1344: enable1344,
1153: enable1153, 1153: enable1153,
4762: enable4762,
} }
// EnableEIP enables the given EIP on the config. // EnableEIP enables the given EIP on the config.
@ -303,3 +304,29 @@ func enable6780(jt *JumpTable) {
maxStack: maxStack(1, 0), 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
}

View file

@ -178,20 +178,6 @@ func (evm *EVM) SetBlockContext(blockCtx BlockContext) {
evm.chainRules = evm.chainConfig.Rules(num, blockCtx.Random != nil, timestamp) evm.chainRules = evm.chainConfig.Rules(num, blockCtx.Random != nil, timestamp)
} }
// 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 {
// XXX check this is still needed as a func
if *gasPool < gas {
*gasPool = 0
return false
}
*gasPool -= gas
return true
}
// Call executes the contract associated with the addr with the given input as // Call executes the contract associated with the addr with the given input as
// parameters. It also handles any necessary value transfer required and takes // parameters. It also handles any necessary value transfer required and takes
// the necessary steps to create accounts and reverses the state in case of an // the necessary steps to create accounts and reverses the state in case of an
@ -211,11 +197,17 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas
var creation bool var creation bool
if !evm.StateDB.Exist(addr) { if !evm.StateDB.Exist(addr) {
if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { if !isPrecompile && evm.chainRules.IsEIP4762 {
if evm.chainRules.IsPrague { // add proof of absence to witness
// proof of absence wgas := evm.Accesses.TouchFullAccount(addr.Bytes(), false)
tryConsumeGas(&gas, evm.Accesses.TouchAndChargeProofOfAbsence(addr.Bytes())) if gas < wgas {
evm.StateDB.RevertToSnapshot(snapshot)
return nil, 0, ErrOutOfGas
} }
gas -= wgas
}
if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 {
// Calling a non existing account, don't do anything, but ping the tracer // Calling a non existing account, don't do anything, but ping the tracer
if debug { if debug {
if evm.depth == 0 { if evm.depth == 0 {
@ -463,7 +455,7 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
evm.StateDB.SetNonce(caller.Address(), nonce+1) evm.StateDB.SetNonce(caller.Address(), nonce+1)
// We add this to the access list _before_ taking a snapshot. Even if the creation fails, // 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 // the access-list change should not be rolled back
if evm.chainRules.IsBerlin { if evm.chainRules.IsEIP2929 {
evm.StateDB.AddAddressToAccessList(address) evm.StateDB.AddAddressToAccessList(address)
} }
// Ensure there's no existing contract already at the designated address // Ensure there's no existing contract already at the designated address
@ -486,6 +478,14 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
contract.SetCodeOptionalHash(&address, codeAndHash) contract.SetCodeOptionalHash(&address, codeAndHash)
contract.IsDeployment = true contract.IsDeployment = true
// Charge the contract creation init gas in verkle mode
var err error
if evm.chainRules.IsEIP4762 {
if !contract.UseGas(evm.Accesses.TouchAndChargeContractCreateInit(address.Bytes(), value.Sign() != 0)) {
err = ErrOutOfGas
}
}
if evm.Config.Tracer != nil { if evm.Config.Tracer != nil {
if evm.depth == 0 { if evm.depth == 0 {
evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value) evm.Config.Tracer.CaptureStart(evm, caller.Address(), address, true, codeAndHash.code, gas, value)
@ -494,7 +494,10 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64,
} }
} }
ret, err := evm.interpreter.Run(contract, nil, false) var ret []byte
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. // Check whether the max code size has been exceeded, assign err if the case.
if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize { if err == nil && evm.chainRules.IsEIP158 && len(ret) > params.MaxCodeSize {
@ -511,20 +514,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 // be stored due to not enough gas set an error and let it be handled
// by the error checking condition below. // by the error checking condition below.
if err == nil { if err == nil {
createDataGas := uint64(len(ret)) * params.CreateDataGas if !evm.chainRules.IsEIP4762 {
if contract.UseGas(createDataGas) { createDataGas := uint64(len(ret)) * params.CreateDataGas
evm.StateDB.SetCode(address, ret) if !contract.UseGas(createDataGas) {
err = ErrCodeStoreOutOfGas
}
} else { } else {
err = ErrCodeStoreOutOfGas // Contract creation completed, touch the missing fields in the contract
} if !contract.UseGas(evm.Accesses.TouchFullAccount(address.Bytes()[:], true)) {
} err = ErrCodeStoreOutOfGas
}
if err == nil && evm.chainRules.IsPrague { if err == nil && len(ret) > 0 && !contract.UseGas(evm.Accesses.TouchCodeChunksRangeAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), true)) {
if len(ret) > 0 { err = ErrCodeStoreOutOfGas
touchCodeChunksRangeOnReadAndChargeGas(address.Bytes(), 0, uint64(len(ret)), uint64(len(ret)), evm.Accesses) }
} }
if !contract.UseGas(evm.Accesses.TouchAndChargeContractCreateCompleted(address.Bytes()[:])) {
err = ErrOutOfGas if err == nil {
evm.StateDB.SetCode(address, ret)
} }
} }

View file

@ -23,8 +23,6 @@ import (
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256"
) )
// memoryGasCost calculates the quadratic gas for memory expansion. It does so // memoryGasCost calculates the quadratic gas for memory expansion. It does so
@ -100,24 +98,12 @@ var (
func gasExtCodeSize(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasExtCodeSize(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
usedGas := uint64(0) usedGas := uint64(0)
slot := stack.Back(0)
address := slot.Bytes20()
if evm.chainRules.IsPrague {
usedGas += evm.TxContext.Accesses.TouchAddressOnReadAndComputeGas(address[:], uint256.Int{}, trieUtils.CodeSizeLeafKey)
}
return usedGas, nil return usedGas, nil
} }
func gasSLoad(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { func gasSLoad(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) {
usedGas := uint64(0) usedGas := uint64(0)
if evm.chainRules.IsPrague {
where := stack.Back(0)
treeIndex, subIndex := trieUtils.GetTreeKeyStorageSlotTreeIndexes(where.Bytes())
usedGas += evm.Accesses.TouchAddressOnReadAndComputeGas(contract.Address().Bytes(), *treeIndex, subIndex)
}
return usedGas, nil return usedGas, nil
} }
@ -405,7 +391,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
} else if !evm.StateDB.Exist(address) { } else if !evm.StateDB.Exist(address) {
gas += params.CallNewAccountGas gas += params.CallNewAccountGas
} }
if transfersValue { if transfersValue && !evm.chainRules.IsEIP4762 {
gas += params.CallValueTransferGas gas += params.CallValueTransferGas
} }
memoryGas, err := memoryGasCost(mem, memorySize) memoryGas, err := memoryGasCost(mem, memorySize)
@ -424,13 +410,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
if evm.chainRules.IsPrague { 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 { if transfersValue {
gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeValueTransfer(contract.Address().Bytes()[:], address.Bytes()[:])) gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeValueTransfer(contract.Address().Bytes()[:], address.Bytes()[:]))
if overflow { if overflow {
@ -451,7 +431,7 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
gas uint64 gas uint64
overflow bool overflow bool
) )
if stack.Back(2).Sign() != 0 { if stack.Back(2).Sign() != 0 && !evm.chainRules.IsEIP4762 {
gas += params.CallValueTransferGas gas += params.CallValueTransferGas
} }
if gas, overflow = math.SafeAdd(gas, memoryGas); overflow { if gas, overflow = math.SafeAdd(gas, memoryGas); overflow {
@ -464,10 +444,11 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
if evm.chainRules.IsPrague { if evm.chainRules.IsEIP4762 {
address := common.Address(stack.Back(1).Bytes20()) address := common.Address(stack.Back(1).Bytes20())
if _, isPrecompile := evm.precompile(address); !isPrecompile { transfersValue := !stack.Back(2).IsZero()
gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeMessageCall(address.Bytes())) if transfersValue {
gas, overflow = math.SafeAdd(gas, evm.Accesses.TouchAndChargeValueTransfer(contract.Address().Bytes()[:], address.Bytes()[:]))
if overflow { if overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
@ -489,15 +470,6 @@ func gasDelegateCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
if evm.chainRules.IsPrague {
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 return gas, nil
} }
@ -514,15 +486,6 @@ func gasStaticCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memo
if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow {
return 0, ErrGasUintOverflow return 0, ErrGasUintOverflow
} }
if evm.chainRules.IsPrague {
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 return gas, nil
} }

View file

@ -20,12 +20,10 @@ import (
"encoding/binary" "encoding/binary"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie/utils"
"github.com/holiman/uint256" "github.com/holiman/uint256"
) )
@ -264,13 +262,6 @@ func opAddress(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
func opBalance(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { func opBalance(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
slot := scope.Stack.peek() slot := scope.Stack.peek()
address := common.Address(slot.Bytes20()) address := common.Address(slot.Bytes20())
if interpreter.evm.chainRules.IsPrague {
statelessGas := interpreter.evm.Accesses.TouchAddressOnReadAndComputeGas(address[:], uint256.Int{}, utils.BalanceLeafKey)
if !scope.Contract.UseGas(statelessGas) {
scope.Contract.Gas = 0
return nil, ErrOutOfGas
}
}
slot.SetFromBig(interpreter.evm.StateDB.GetBalance(address)) slot.SetFromBig(interpreter.evm.StateDB.GetBalance(address))
return nil, nil return nil, nil
} }
@ -355,13 +346,6 @@ func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
slot := scope.Stack.peek() slot := scope.Stack.peek()
address := slot.Bytes20() address := slot.Bytes20()
cs := uint64(interpreter.evm.StateDB.GetCodeSize(address)) cs := uint64(interpreter.evm.StateDB.GetCodeSize(address))
if interpreter.evm.chainRules.IsPrague {
statelessGas := interpreter.evm.Accesses.TouchAddressOnReadAndComputeGas(address[:], uint256.Int{}, utils.CodeSizeLeafKey)
if !scope.Contract.UseGas(statelessGas) {
scope.Contract.Gas = 0
return nil, ErrOutOfGas
}
}
slot.SetUint64(cs) slot.SetUint64(cs)
return nil, nil return nil, nil
} }
@ -386,8 +370,8 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
contractAddr := scope.Contract.Address() contractAddr := scope.Contract.Address()
paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64()) paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64())
if interpreter.evm.chainRules.IsPrague && !scope.Contract.IsDeployment { if interpreter.evm.chainRules.IsEIP4762 && !scope.Contract.IsDeployment {
statelessGas := touchCodeChunksRangeOnReadAndChargeGas(contractAddr[:], copyOffset, nonPaddedCopyLength, uint64(len(scope.Contract.Code)), interpreter.evm.Accesses) statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(contractAddr[:], copyOffset, nonPaddedCopyLength, uint64(len(scope.Contract.Code)), false)
if !scope.Contract.UseGas(statelessGas) { if !scope.Contract.UseGas(statelessGas) {
scope.Contract.Gas = 0 scope.Contract.Gas = 0
return nil, ErrOutOfGas return nil, ErrOutOfGas
@ -397,41 +381,6 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
return nil, nil return nil, nil
} }
// touchCodeChunksRangeOnReadAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs
func touchCodeChunksRangeOnReadAndChargeGas(contractAddr []byte, startPC, size uint64, codeLen uint64, accesses *state.AccessWitness) 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 := accesses.TouchAddressOnReadAndComputeGas(contractAddr, treeIndex, subIndex)
var overflow bool
statelessGasCharged, overflow = math.SafeAdd(statelessGasCharged, gas)
if overflow {
panic("overflow when adding gas")
}
}
return statelessGasCharged
}
func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
var ( var (
stack = scope.Stack stack = scope.Stack
@ -445,14 +394,14 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
uint64CodeOffset = 0xffffffffffffffff uint64CodeOffset = 0xffffffffffffffff
} }
addr := common.Address(a.Bytes20()) addr := common.Address(a.Bytes20())
if interpreter.evm.chainRules.IsPrague { if interpreter.evm.chainRules.IsEIP4762 {
code := interpreter.evm.StateDB.GetCode(addr) code := interpreter.evm.StateDB.GetCode(addr)
contract := &Contract{ contract := &Contract{
Code: code, Code: code,
self: AccountRef(addr), self: AccountRef(addr),
} }
paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64()) paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64())
statelessGas := touchCodeChunksRangeOnReadAndChargeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), interpreter.evm.Accesses) statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(addr[:], copyOffset, nonPaddedCopyLength, uint64(len(contract.Code)), false)
if !scope.Contract.UseGas(statelessGas) { if !scope.Contract.UseGas(statelessGas) {
scope.Contract.Gas = 0 scope.Contract.Gas = 0
return nil, ErrOutOfGas return nil, ErrOutOfGas
@ -495,13 +444,6 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { func opExtCodeHash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
slot := scope.Stack.peek() slot := scope.Stack.peek()
address := common.Address(slot.Bytes20()) address := common.Address(slot.Bytes20())
if interpreter.evm.chainRules.IsPrague {
statelessGas := interpreter.evm.Accesses.TouchAddressOnReadAndComputeGas(address[:], uint256.Int{}, utils.CodeKeccakLeafKey)
if !scope.Contract.UseGas(statelessGas) {
scope.Contract.Gas = 0
return nil, ErrOutOfGas
}
}
if interpreter.evm.StateDB.Empty(address) { if interpreter.evm.StateDB.Empty(address) {
slot.Clear() slot.Clear()
} else { } else {
@ -516,13 +458,12 @@ func opGasprice(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
return nil, nil return nil, nil
} }
func getBlockHashFromContract(number uint64, statedb StateDB, witness *state.AccessWitness) common.Hash { func getBlockHashFromContract(number uint64, statedb StateDB, witness *state.AccessWitness) (common.Hash, uint64) {
ringIndex := number % params.Eip2935BlockHashHistorySize ringIndex := number % params.Eip2935BlockHashHistorySize
var pnum common.Hash var pnum common.Hash
binary.BigEndian.PutUint64(pnum[24:], ringIndex) binary.BigEndian.PutUint64(pnum[24:], ringIndex)
treeIndex, suffix := utils.GetTreeKeyStorageSlotTreeIndexes(pnum.Bytes()) statelessGas := witness.TouchSlotAndChargeGas(params.HistoryStorageAddress[:], pnum, false)
witness.TouchAddressOnReadAndComputeGas(params.HistoryStorageAddress[:], *treeIndex, suffix) return statedb.GetState(params.HistoryStorageAddress, pnum), statelessGas
return statedb.GetState(params.HistoryStorageAddress, pnum)
} }
func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
@ -545,7 +486,13 @@ func opBlockhash(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
if num64 >= lower && num64 < upper { if num64 >= lower && num64 < upper {
// if Prague is active, read it from the history contract (EIP 2935). // if Prague is active, read it from the history contract (EIP 2935).
if evm.chainRules.IsPrague { if evm.chainRules.IsPrague {
num.SetBytes(getBlockHashFromContract(num64, evm.StateDB, evm.Accesses).Bytes()) blockHash, statelessGas := getBlockHashFromContract(num64, evm.StateDB, evm.Accesses)
if interpreter.evm.chainRules.IsEIP4762 {
if !scope.Contract.UseGas(statelessGas) {
return nil, ErrExecutionReverted
}
}
num.SetBytes(blockHash.Bytes())
} else { } else {
num.SetBytes(interpreter.evm.Context.GetHash(num64).Bytes()) num.SetBytes(interpreter.evm.Context.GetHash(num64).Bytes())
} }
@ -681,22 +628,25 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
if interpreter.readOnly { if interpreter.readOnly {
return nil, ErrWriteProtection return nil, ErrWriteProtection
} }
value := scope.Stack.pop()
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) {
return nil, ErrExecutionReverted
}
}
var ( var (
value = scope.Stack.pop()
offset, size = scope.Stack.pop(), scope.Stack.pop() offset, size = scope.Stack.pop(), scope.Stack.pop()
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
gas = scope.Contract.Gas gas = scope.Contract.Gas
) )
if interpreter.evm.chainRules.IsPrague {
contractAddress := crypto.CreateAddress(scope.Contract.Address(), interpreter.evm.StateDB.GetNonce(scope.Contract.Address()))
statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:], value.Sign() != 0)
if !tryConsumeGas(&gas, statelessGas) {
return nil, ErrExecutionReverted
}
}
if interpreter.evm.chainRules.IsEIP150 { if interpreter.evm.chainRules.IsEIP150 {
gas -= gas / 64 gas -= gas / 64
} }
// reuse size int for stackvalue // reuse size int for stackvalue
stackvalue := size stackvalue := size
@ -739,17 +689,17 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]
offset, size = scope.Stack.pop(), scope.Stack.pop() offset, size = scope.Stack.pop(), scope.Stack.pop()
salt = scope.Stack.pop() salt = scope.Stack.pop()
input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
gas = scope.Contract.Gas
) )
if interpreter.evm.chainRules.IsPrague { if interpreter.evm.chainRules.IsEIP4762 {
codeAndHash := &codeAndHash{code: input} codeAndHash := &codeAndHash{code: input}
contractAddress := crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) contractAddress := crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes())
statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:], endowment.Sign() != 0) statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:], endowment.Sign() != 0)
if !tryConsumeGas(&gas, statelessGas) { if !scope.Contract.UseGas(statelessGas) {
return nil, ErrExecutionReverted return nil, ErrExecutionReverted
} }
} }
var gas = scope.Contract.Gas
// Apply EIP150 // Apply EIP150
gas -= gas / 64 gas -= gas / 64
scope.Contract.UseGas(gas) scope.Contract.UseGas(gas)
@ -962,22 +912,6 @@ func opSelfdestruct6780(pc *uint64, interpreter *EVMInterpreter, scope *ScopeCon
tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance) tracer.CaptureEnter(SELFDESTRUCT, scope.Contract.Address(), beneficiary.Bytes20(), []byte{}, 0, balance)
tracer.CaptureExit([]byte{}, 0, nil) tracer.CaptureExit([]byte{}, 0, nil)
} }
if interpreter.evm.chainRules.IsPrague {
contractAddr := scope.Contract.Address()
beneficiaryAddr := beneficiary.Bytes20()
// If the beneficiary isn't the contract, we need to touch the beneficiary's balance.
// If the beneficiary is the contract itself, there're two possibilities:
// 1. The contract was created in the same transaction: the balance is already touched (no need to touch again)
// 2. The contract wasn't created in the same transaction: there's no net change in balance,
// and SELFDESTRUCT will perform no action on the account header. (we touch since we did SubBalance+AddBalance above)
if contractAddr != beneficiaryAddr || interpreter.evm.StateDB.WasCreatedInCurrentTx(contractAddr) {
statelessGas := interpreter.evm.Accesses.TouchAddressOnReadAndComputeGas(beneficiaryAddr[:], uint256.Int{}, utils.BalanceLeafKey)
if !scope.Contract.UseGas(statelessGas) {
scope.Contract.Gas = 0
return nil, ErrOutOfGas
}
}
}
return nil, errStopToken return nil, errStopToken
} }
@ -1025,7 +959,7 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
// touch next chunk if PUSH1 is at the boundary. if so, *pc has // touch next chunk if PUSH1 is at the boundary. if so, *pc has
// advanced past this boundary. // advanced past this boundary.
contractAddr := scope.Contract.Address() contractAddr := scope.Contract.Address()
statelessGas := touchCodeChunksRangeOnReadAndChargeGas(contractAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code)), interpreter.evm.Accesses) statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(contractAddr[:], *pc+1, uint64(1), uint64(len(scope.Contract.Code)), false)
if !scope.Contract.UseGas(statelessGas) { if !scope.Contract.UseGas(statelessGas) {
scope.Contract.Gas = 0 scope.Contract.Gas = 0
return nil, ErrOutOfGas return nil, ErrOutOfGas
@ -1054,7 +988,7 @@ func makePush(size uint64, pushByteSize int) executionFunc {
if !scope.Contract.IsDeployment && interpreter.evm.chainRules.IsPrague { if !scope.Contract.IsDeployment && interpreter.evm.chainRules.IsPrague {
contractAddr := scope.Contract.Address() contractAddr := scope.Contract.Address()
statelessGas := touchCodeChunksRangeOnReadAndChargeGas(contractAddr[:], uint64(startMin), uint64(pushByteSize), uint64(len(scope.Contract.Code)), interpreter.evm.Accesses) statelessGas := interpreter.evm.Accesses.TouchCodeChunksRangeAndChargeGas(contractAddr[:], uint64(startMin), uint64(pushByteSize), uint64(len(scope.Contract.Code)), false)
if !scope.Contract.UseGas(statelessGas) { if !scope.Contract.UseGas(statelessGas) {
scope.Contract.Gas = 0 scope.Contract.Gas = 0
return nil, ErrOutOfGas return nil, ErrOutOfGas

View file

@ -179,11 +179,11 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
logged, pcCopy, gasCopy = false, pc, contract.Gas logged, pcCopy, gasCopy = false, pc, contract.Gas
} }
if in.evm.chainRules.IsPrague && !contract.IsDeployment { if in.evm.chainRules.IsEIP4762 && !contract.IsDeployment {
// if the PC ends up in a new "chunk" of verkleized code, charge the // if the PC ends up in a new "chunk" of verkleized code, charge the
// associated costs. // associated costs.
contractAddr := contract.Address() contractAddr := contract.Address()
contract.Gas -= touchCodeChunksRangeOnReadAndChargeGas(contractAddr[:], pc, 1, uint64(len(contract.Code)), in.evm.TxContext.Accesses) 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 // Get the operation from the jump table and validate the stack to ensure there are

View file

@ -84,6 +84,7 @@ func validate(jt JumpTable) JumpTable {
func newPragueInstructionSet() JumpTable { func newPragueInstructionSet() JumpTable {
instructionSet := newShanghaiInstructionSet() instructionSet := newShanghaiInstructionSet()
enable6780(&instructionSet) enable6780(&instructionSet)
enable4762(&instructionSet)
return validate(instructionSet) return validate(instructionSet)
} }

View file

@ -22,7 +22,6 @@ import (
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/params" "github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/trie/utils"
) )
func makeGasSStoreFunc(clearingRefund uint64) gasFunc { func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
@ -52,11 +51,6 @@ func makeGasSStoreFunc(clearingRefund uint64) gasFunc {
} }
value := common.Hash(y.Bytes32()) value := common.Hash(y.Bytes32())
if evm.chainRules.IsPrague {
treeIndex, subIndex := utils.GetTreeKeyStorageSlotTreeIndexes(x.Bytes())
cost += evm.Accesses.TouchAddressOnWriteAndComputeGas(contract.Address().Bytes(), *treeIndex, subIndex)
}
if current == value { // noop (1) if current == value { // noop (1)
// EIP 2200 original clause: // EIP 2200 original clause:
// return params.SloadGasEIP2200, nil // return params.SloadGasEIP2200, nil
@ -111,13 +105,6 @@ func gasSLoadEIP2929(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me
slot := common.Hash(loc.Bytes32()) slot := common.Hash(loc.Bytes32())
var gasUsed uint64 var gasUsed uint64
if evm.chainRules.IsPrague {
where := stack.Back(0)
treeIndex, subIndex := utils.GetTreeKeyStorageSlotTreeIndexes(where.Bytes())
addr := contract.Address()
gasUsed += evm.Accesses.TouchAddressOnReadAndComputeGas(addr.Bytes(), *treeIndex, subIndex)
}
// Check slot presence in the access list // Check slot presence in the access list
if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent { if _, slotPresent := evm.StateDB.SlotInAccessList(contract.Address(), slot); !slotPresent {
// If the caller cannot afford the cost, this change will be rolled back // If the caller cannot afford the cost, this change will be rolled back

View file

@ -0,0 +1,147 @@
// 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.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()
if _, isPrecompile := evm.precompile(address); isPrecompile {
return 0, nil
}
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()
if _, isPrecompile := evm.precompile(address); isPrecompile {
return 0, nil
}
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
}
if _, isPrecompile := evm.precompile(contract.Address()); isPrecompile {
return gas, nil
}
wgas := evm.Accesses.TouchAndChargeMessageCall(contract.Address().Bytes())
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())
if _, isPrecompile := evm.precompile(beneficiaryAddr); isPrecompile {
return 0, nil
}
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)
}
// Case when the beneficiary does not exist: touch the account
// but leave code hash and size alone.
if evm.StateDB.Empty(beneficiaryAddr) {
statelessGas += evm.Accesses.TouchVersion(beneficiaryAddr[:], true)
statelessGas += evm.Accesses.TouchNonce(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
}

View file

@ -806,6 +806,7 @@ func (err *ConfigCompatError) Error() string {
type Rules struct { type Rules struct {
ChainID *big.Int ChainID *big.Int
IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool IsHomestead, IsEIP150, IsEIP155, IsEIP158 bool
IsEIP2929, IsEIP4762 bool
IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool IsByzantium, IsConstantinople, IsPetersburg, IsIstanbul bool
IsBerlin, IsLondon bool IsBerlin, IsLondon bool
IsMerge, IsShanghai, IsCancun, IsPrague bool IsMerge, IsShanghai, IsCancun, IsPrague bool
@ -828,6 +829,8 @@ func (c *ChainConfig) Rules(num *big.Int, isMerge bool, timestamp uint64) Rules
IsPetersburg: c.IsPetersburg(num), IsPetersburg: c.IsPetersburg(num),
IsIstanbul: c.IsIstanbul(num), IsIstanbul: c.IsIstanbul(num),
IsBerlin: c.IsBerlin(num), IsBerlin: c.IsBerlin(num),
IsEIP2929: c.IsBerlin(num) && !c.IsPrague(num, timestamp),
IsEIP4762: c.IsPrague(num, timestamp),
IsLondon: c.IsLondon(num), IsLondon: c.IsLondon(num),
IsMerge: isMerge, IsMerge: isMerge,
IsShanghai: c.IsShanghai(num, timestamp), IsShanghai: c.IsShanghai(num, timestamp),

View file

@ -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. 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. CreateGas uint64 = 32000 // Once per CREATE operation & contract-creation transaction.
Create2Gas uint64 = 32000 // Once per CREATE2 operation Create2Gas uint64 = 32000 // Once per CREATE2 operation
CreateNGasEip4762 uint64 = 1000 // Once per CREATEn operations post-verkle
SelfdestructRefundGas uint64 = 24000 // Refunded following a selfdestruct operation. 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. 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.

View file

@ -26,11 +26,11 @@ import (
) )
const ( const (
VersionLeafKey = 0 VersionLeafKey = 0
BalanceLeafKey = 1 BalanceLeafKey = 1
NonceLeafKey = 2 NonceLeafKey = 2
CodeKeccakLeafKey = 3 CodeHashLeafKey = 3
CodeSizeLeafKey = 4 CodeSizeLeafKey = 4
maxPointCacheByteSize = 100 << 20 maxPointCacheByteSize = 100 << 20
) )
@ -152,7 +152,7 @@ func GetTreeKeyNonce(address []byte) []byte {
} }
func GetTreeKeyCodeKeccak(address []byte) []byte { func GetTreeKeyCodeKeccak(address []byte) []byte {
return GetTreeKey(address, zero, CodeKeccakLeafKey) return GetTreeKey(address, zero, CodeHashLeafKey)
} }
func GetTreeKeyCodeSize(address []byte) []byte { func GetTreeKeyCodeSize(address []byte) []byte {

View file

@ -17,10 +17,11 @@
package utils package utils
import ( import (
"bytes"
"crypto/rand"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"math/big" "math/big"
"math/rand"
"testing" "testing"
"github.com/ethereum/go-verkle" "github.com/ethereum/go-verkle"
@ -77,7 +78,7 @@ func sha256GetTreeKeyCodeSize(addr []byte) []byte {
copy(payload[:len(treeIndexBytes)], treeIndexBytes) copy(payload[:len(treeIndexBytes)], treeIndexBytes)
digest.Write(payload[:]) digest.Write(payload[:])
h := digest.Sum(nil) h := digest.Sum(nil)
h[31] = CodeKeccakLeafKey h[31] = CodeHashLeafKey
return h return h
} }
@ -93,3 +94,22 @@ func BenchmarkSha256Hash(b *testing.B) {
sha256GetTreeKeyCodeSize(addr[:]) sha256GetTreeKeyCodeSize(addr[:])
} }
} }
func TestCompareGetTreeKeyWithEvaluated(t *testing.T) {
var addr [32]byte
rand.Read(addr[:])
addrpoint := EvaluateAddressPoint(addr[:])
for i := 0; i < 100; i++ {
var val [32]byte
rand.Read(val[:])
n := uint256.NewInt(0).SetBytes(val[:])
n.Lsh(n, 8)
subindex := val[0]
tk1 := GetTreeKey(addr[:], n, subindex)
tk2 := GetTreeKeyWithEvaluatedAddess(addrpoint, n, subindex)
if !bytes.Equal(tk1, tk2) {
t.Fatalf("differing key: slot=%x, addr=%x", val, addr)
}
}
}

View file

@ -125,7 +125,7 @@ func (t *VerkleTrie) GetAccount(addr common.Address) (*types.StateAccount, error
// been recreated after that, then its code keccak will NOT be 0. So return `nil` if // been recreated after that, then its code keccak will NOT be 0. So return `nil` if
// the nonce, and values[10], and code keccak is 0. // the nonce, and values[10], and code keccak is 0.
if acc.Nonce == 0 && len(values) > 10 && len(values[10]) > 0 && bytes.Equal(values[utils.CodeKeccakLeafKey], zero[:]) { if acc.Nonce == 0 && len(values) > 10 && len(values[10]) > 0 && bytes.Equal(values[utils.CodeHashLeafKey], zero[:]) {
if !t.ended { if !t.ended {
return nil, errDeletedAccount return nil, errDeletedAccount
} else { } else {
@ -144,7 +144,7 @@ func (t *VerkleTrie) GetAccount(addr common.Address) (*types.StateAccount, error
// } // }
// } // }
acc.Balance = new(big.Int).SetBytes(balance[:]) acc.Balance = new(big.Int).SetBytes(balance[:])
acc.CodeHash = values[utils.CodeKeccakLeafKey] acc.CodeHash = values[utils.CodeHashLeafKey]
// TODO fix the code size as well // TODO fix the code size as well
return acc, nil return acc, nil
@ -164,7 +164,7 @@ func (t *VerkleTrie) UpdateAccount(addr common.Address, acc *types.StateAccount)
values[utils.VersionLeafKey] = zero[:] values[utils.VersionLeafKey] = zero[:]
values[utils.NonceLeafKey] = nonce[:] values[utils.NonceLeafKey] = nonce[:]
values[utils.BalanceLeafKey] = balance[:] values[utils.BalanceLeafKey] = balance[:]
values[utils.CodeKeccakLeafKey] = acc.CodeHash[:] values[utils.CodeHashLeafKey] = acc.CodeHash[:]
binary.LittleEndian.PutUint64(nonce[:], acc.Nonce) binary.LittleEndian.PutUint64(nonce[:], acc.Nonce)
bbytes := acc.Balance.Bytes() bbytes := acc.Balance.Bytes()