From 5769e366ebcc1fb2d59962ccd6641488d676d058 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Wed, 5 Jan 2022 06:19:05 +0000 Subject: [PATCH] charge witness gas costs for calls, creates. fix: touch codesize key in trie if account is EOA --- core/state/statedb.go | 50 +++++++++++---- core/state_transition.go | 75 ++++++++++++++++------ core/types/access_witness.go | 121 +++++++++++++++++++++++++++++++++++ core/vm/evm.go | 102 +++++++++++++++++++++++++---- core/vm/gas_table.go | 94 ++++++++++++++++++++++++--- core/vm/instructions.go | 17 +++++ core/vm/interface.go | 3 + trie/verkle.go | 1 - 8 files changed, 409 insertions(+), 54 deletions(-) diff --git a/core/state/statedb.go b/core/state/statedb.go index 593150523384..38b13cdcc26f 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -286,6 +286,25 @@ func (s *StateDB) GetBalance(addr common.Address) *big.Int { return common.Big0 } +func (s *StateDB) GetNonceLittleEndian(address common.Address) []byte { + var paddedNonceBytes [32]byte + nonce := s.GetNonce(address) + binary.LittleEndian.PutUint64(paddedNonceBytes[0:8], nonce) + return paddedNonceBytes[:] +} + +func (s *StateDB) GetBalanceLittleEndian(address common.Address) []byte { + var paddedBalance [32]byte + balanceBytes := s.GetBalance(address).Bytes() + // swap big-endian to little-endian + for i, j := 0, len(balanceBytes)-1; i < j; i, j = i+1, j-1 { + balanceBytes[i], balanceBytes[j] = balanceBytes[j], balanceBytes[i] + } + + copy(paddedBalance[:len(balanceBytes)], balanceBytes) + return paddedBalance[:] +} + func (s *StateDB) GetNonce(addr common.Address) uint64 { stateObject := s.getStateObject(addr) if stateObject != nil { @@ -484,20 +503,27 @@ func (s *StateDB) updateStateObject(obj *stateObject) { if err := s.trie.TryUpdateAccount(addr[:], &obj.data); err != nil { s.setError(fmt.Errorf("updateStateObject (%x) error: %w", addr[:], err)) } - if len(obj.code) > 0 && s.trie.IsVerkle() { - cs := make([]byte, 32) - binary.BigEndian.PutUint64(cs, uint64(len(obj.code))) - if err := s.trie.TryUpdate(trieUtils.GetTreeKeyCodeSize(addr[:]), cs); err != nil { - s.setError(fmt.Errorf("updateStateObject (%x) error: %w", addr[:], err)) - } + if s.trie.IsVerkle() { + if len(obj.code) > 0 { + cs := make([]byte, 32) + binary.BigEndian.PutUint64(cs, uint64(len(obj.code))) + if err := s.trie.TryUpdate(trieUtils.GetTreeKeyCodeSize(addr[:]), cs); err != nil { + s.setError(fmt.Errorf("updateStateObject (%x) error: %w", addr[:], err)) + } - if obj.dirtyCode { - if chunks, err := trie.ChunkifyCode(addr, obj.code); err == nil { - for i := range chunks { - s.trie.TryUpdate(trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(uint64(i))), chunks[i][:]) + if obj.dirtyCode { + if chunks, err := trie.ChunkifyCode(addr, obj.code); err == nil { + for i := range chunks { + s.trie.TryUpdate(trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(uint64(i))), chunks[i][:]) + } + } else { + s.setError(err) } - } else { - s.setError(err) + } + } else { + cs := []byte{0} + if err := s.trie.TryUpdate(trieUtils.GetTreeKeyCodeSize(addr[:]), cs); err != nil { + s.setError(fmt.Errorf("updateStateObject (%x) error: %w", addr[:], err)) } } } diff --git a/core/state_transition.go b/core/state_transition.go index 1e39dee6569e..dddf84501e0f 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -17,10 +17,10 @@ package core import ( - "encoding/binary" - "fmt" "math" "math/big" + "encoding/binary" + "fmt" "github.com/ethereum/go-ethereum/common" cmath "github.com/ethereum/go-ethereum/common/math" @@ -28,7 +28,6 @@ import ( "github.com/ethereum/go-ethereum/core/vm" "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/params" - trieUtils "github.com/ethereum/go-ethereum/trie/utils" ) var emptyCodeHash = crypto.Keccak256Hash(nil) @@ -261,6 +260,34 @@ 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 +} + +func bigIntToLittleEndianBytes(value *big.Int) []byte { + var paddedVal [32] byte + if len(value.Bytes()) > 32 { + panic("value larger than 32 bytes") + } + + valBytes := value.Bytes() + for i, j := 0, len(valBytes)-1; i < j; i, j = i+1, j-1 { + valBytes[i], valBytes[j] = valBytes[j], valBytes[i] + } + + copy(paddedVal[:len(valBytes)], valBytes) + return paddedVal[:] +} + // TransitionDb will transition the state by applying the current message and // returning the evm execution result with following fields. // @@ -305,25 +332,33 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gas, gas) } if st.evm.Accesses != nil { + var targetBalance, targetNonce, targetCodeSize, targetCodeKeccak, originBalance, originNonce []byte + + targetAddr := msg.To() + originAddr := msg.From() + + statelessGasOrigin := st.evm.Accesses.TouchTxOriginAndChargeGas(originAddr.Bytes()) + if !tryConsumeGas(&st.gas, statelessGasOrigin) { + return nil, fmt.Errorf("insufficient gas to cover witness access costs") + } + originBalance = st.evm.StateDB.GetBalanceLittleEndian(originAddr) + originNonce = st.evm.StateDB.GetNonceLittleEndian(originAddr) + st.evm.Accesses.SetTxTouchedLeaves(originAddr.Bytes(), originBalance, originNonce) + if msg.To() != nil { - toBalance := trieUtils.GetTreeKeyBalance(msg.To().Bytes()) - pre := st.state.GetBalance(*msg.To()) - gas += st.evm.Accesses.TouchAddressAndChargeGas(toBalance, pre.Bytes()) - - // NOTE: Nonce also needs to be charged, because it is needed for execution - // on the statless side. - var preTN [8]byte - fromNonce := trieUtils.GetTreeKeyNonce(msg.To().Bytes()) - binary.BigEndian.PutUint64(preTN[:], st.state.GetNonce(*msg.To())) - gas += st.evm.Accesses.TouchAddressAndChargeGas(fromNonce, preTN[:]) + statelessGasDest := st.evm.Accesses.TouchTxNonCreationAndChargeGas(targetAddr.Bytes()) + if !tryConsumeGas(&st.gas, statelessGasDest) { + return nil, fmt.Errorf("insufficient gas to cover witness access costs") + } + targetBalance = st.evm.StateDB.GetBalanceLittleEndian(*targetAddr) + targetNonce = st.evm.StateDB.GetNonceLittleEndian(*targetAddr) + targetCodeKeccak = st.evm.StateDB.GetCodeHash(*targetAddr).Bytes() + + codeSize := uint64(st.evm.StateDB.GetCodeSize(*targetAddr)) + var codeSizeBytes [32]byte + binary.LittleEndian.PutUint64(codeSizeBytes[:8], codeSize) + st.evm.Accesses.SetTxNonCreationTouchedLeaves(targetAddr.Bytes(), targetBalance, targetNonce, targetCodeSize, targetCodeKeccak) } - fromBalance := trieUtils.GetTreeKeyBalance(msg.From().Bytes()) - preFB := st.state.GetBalance(msg.From()).Bytes() - fromNonce := trieUtils.GetTreeKeyNonce(msg.From().Bytes()) - var preFN [8]byte - binary.BigEndian.PutUint64(preFN[:], st.state.GetNonce(msg.From())) - gas += st.evm.Accesses.TouchAddressAndChargeGas(fromNonce, preFN[:]) - gas += st.evm.Accesses.TouchAddressAndChargeGas(fromBalance, preFB[:]) } st.gas -= gas diff --git a/core/types/access_witness.go b/core/types/access_witness.go index 3fe6b78dc555..b9d3f3894c1e 100644 --- a/core/types/access_witness.go +++ b/core/types/access_witness.go @@ -19,6 +19,7 @@ package types import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie/utils" ) // AccessWitness lists the locations of the state that are being accessed @@ -45,6 +46,126 @@ func NewAccessWitness() *AccessWitness { } } +func (aw *AccessWitness) TouchAndChargeMessageCall(addr []byte) uint64 { + var gas uint64 + gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyVersion(addr[:]), nil) + gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyCodeSize(addr[:]), nil) + return gas +} + +func (aw *AccessWitness) SetLeafValuesMessageCall(addr, codeSize []byte) { + var data [32]byte + aw.TouchAddress(utils.GetTreeKeyVersion(addr[:]), data[:]) + aw.TouchAddress(utils.GetTreeKeyCodeSize(addr[:]), codeSize[:]) +} + +func (aw *AccessWitness) TouchAndChargeValueTransfer(callerAddr, targetAddr []byte) uint64 { + var gas uint64 + gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyBalance(callerAddr[:]), nil) + gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyBalance(targetAddr[:]), nil) + return gas +} + +func (aw *AccessWitness) SetLeafValuesValueTransfer(callerAddr, targetAddr, callerBalance, targetBalance []byte) { + aw.TouchAddress(utils.GetTreeKeyBalance(callerAddr[:]), callerBalance) + aw.TouchAddress(utils.GetTreeKeyBalance(targetAddr[:]), targetBalance) +} + +// TouchAndChargeContractCreate init creates the following write events for the +// created addresss: +// (contract_address, 0, VERSION_LEAF_KEY) +// (contract_address, 0, NONCE_LEAF_KEY) +func (aw *AccessWitness) TouchAndChargeContractCreateInit(addr []byte) uint64 { + var gas uint64 + gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyVersion(addr[:]), nil) + gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyNonce(addr[:]), nil) + return gas +} + +func (aw *AccessWitness) SetLeafValuesContractCreateInit(addr, nonce []byte) { + var version [32]byte + aw.TouchAddress(utils.GetTreeKeyVersion(addr[:]), version[:]) + aw.TouchAddress(utils.GetTreeKeyNonce(addr[:]), nonce) +} + +// TouchAndChargeContractCreateCompleted creates the following write events for the created +// address: +// (contract_address, 0, VERSION_LEAF_KEY) +// (contract_address, 0, NONCE_LEAF_KEY) +// (contract_address, 0, BALANCE_LEAF_KEY) +// (contract_address, 0, CODE_KECCAK_LEAF_KEY) +// (contract_address, 0, CODE_SIZE_LEAF_KEY) +func (aw *AccessWitness) TouchAndChargeContractCreateCompleted(addr []byte, withValue bool) uint64 { + var gas uint64 + gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyVersion(addr[:]), nil) + gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyNonce(addr[:]), nil) + if withValue { + gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyBalance(addr[:]), nil) + } + gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyCodeSize(addr[:]), nil) + gas += aw.TouchAddressAndChargeGas(utils.GetTreeKeyCodeKeccak(addr[:]), nil) + return gas +} + +func (aw *AccessWitness) SetLeafValuesContractCreateCompleted(addr, codeSize, codeKeccak []byte) { + aw.TouchAddress(utils.GetTreeKeyCodeSize(addr[:]), codeSize) + aw.TouchAddress(utils.GetTreeKeyCodeKeccak(addr[:]), codeKeccak) +} + +func (aw *AccessWitness) TouchTxAndChargeGas(originAddr, targetAddr []byte) uint64 { + var gasUsed uint64 + var version [32]byte + gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyVersion(originAddr[:]), version[:]) + gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyBalance(originAddr[:]), nil) + gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyNonce(originAddr[:]), nil) + + gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyVersion(targetAddr[:]), version[:]) + gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyBalance(targetAddr[:]), nil) + gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyNonce(targetAddr[:]), nil) + gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyCodeSize(targetAddr[:]), nil) + gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyCodeKeccak(targetAddr[:]), nil) + return gasUsed + // TODO write events: + /* + (tx.origin, 0, NONCE_LEAF_KEY) + if value is non-zero: + (tx.origin, 0, BALANCE_LEAF_KEY) + (tx.target, 0, BALANCE_LEAF_KEY) + */ +} + +func (aw *AccessWitness) TouchTxOriginAndChargeGas(originAddr []byte) uint64 { + var gasUsed uint64 + var version [32]byte + gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyVersion(originAddr[:]), version[:]) + gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyBalance(originAddr[:]), nil) + gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyNonce(originAddr[:]), nil) + return gasUsed +} + +func (aw *AccessWitness) TouchTxNonCreationAndChargeGas(targetAddr []byte) uint64 { + var gasUsed uint64 + var version [32]byte + gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyVersion(targetAddr[:]), version[:]) + gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyBalance(targetAddr[:]), nil) + gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyNonce(targetAddr[:]), nil) + gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyCodeSize(targetAddr[:]), nil) + gasUsed += aw.TouchAddressAndChargeGas(utils.GetTreeKeyCodeKeccak(targetAddr[:]), nil) + return gasUsed +} + +func (aw *AccessWitness) SetTxTouchedLeaves(originAddr, originBalance, originNonce []byte) { + aw.TouchAddress(utils.GetTreeKeyBalance(originAddr[:]), originBalance) + aw.TouchAddress(utils.GetTreeKeyNonce(originAddr[:]), originNonce) +} + +func (aw *AccessWitness) SetTxNonCreationTouchedLeaves(targetAddr, targetBalance, targetNonce, targetCodeSize, targetCodeHash []byte) { + aw.TouchAddress(utils.GetTreeKeyBalance(targetAddr[:]), targetBalance) + aw.TouchAddress(utils.GetTreeKeyNonce(targetAddr[:]), targetNonce) + aw.TouchAddress(utils.GetTreeKeyCodeSize(targetAddr[:]), targetCodeSize) + aw.TouchAddress(utils.GetTreeKeyCodeKeccak(targetAddr[:]), targetCodeHash) +} + // TouchAddress adds any missing addr to the witness and returns respectively // true if the stem or the stub weren't arleady present. func (aw *AccessWitness) TouchAddress(addr, value []byte) (bool, bool) { diff --git a/core/vm/evm.go b/core/vm/evm.go index 3cb0a0ee53e8..3f83b717a1f2 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -181,6 +181,27 @@ func tryConsumeGas(gasPool *uint64, gas uint64) bool { return true } +// TODO refactor getBalanceLittleEndian and getNonceLittleEndian to not duplicate so much code + +func getNonceLittleEndian(evm *EVM, address common.Address) []byte { + var paddedNonceBytes [32]byte + nonce := evm.StateDB.GetNonce(address) + binary.LittleEndian.PutUint64(paddedNonceBytes[0:8], nonce) + return paddedNonceBytes[:] +} + +func getBalanceLittleEndian(evm *EVM, address common.Address) []byte { + var paddedBalance [32]byte + balanceBytes := evm.StateDB.GetBalance(address).Bytes() + // swap big-endian to little-endian + for i, j := 0, len(balanceBytes)-1; i < j; i, j = i+1, j-1 { + balanceBytes[i], balanceBytes[j] = balanceBytes[j], balanceBytes[i] + } + + copy(paddedBalance[:len(balanceBytes)], balanceBytes) + return paddedBalance[:] +} + // Call executes the contract associated with the addr with the given input as // 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 @@ -202,6 +223,7 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if !evm.StateDB.Exist(addr) { if !isPrecompile && evm.chainRules.IsEIP158 && value.Sign() == 0 { + // TODO proof of absence here? // Calling a non existing account, don't do anything, but ping the tracer if evm.Config.Debug { if evm.depth == 0 { @@ -214,8 +236,14 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas } return nil, gas, nil } + evm.StateDB.CreateAccount(addr) } + if evm.Accesses != nil && value.Sign() != 0 { + callerBalanceBefore := evm.StateDB.GetBalanceLittleEndian(caller.Address()) + targetBalanceBefore := evm.StateDB.GetBalanceLittleEndian(addr) + evm.Accesses.SetLeafValuesValueTransfer(caller.Address().Bytes()[:], addr[:], callerBalanceBefore, targetBalanceBefore) + } evm.Context.Transfer(evm.StateDB, caller.Address(), addr, value) // Capture the tracer start/end events in debug mode @@ -240,21 +268,16 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. code := evm.StateDB.GetCode(addr) + if evm.Accesses != nil { + codeSize := uint64(len(code)) + var codeSizeBytes [32]byte + binary.LittleEndian.PutUint64(codeSizeBytes[:8], codeSize) + evm.Accesses.SetLeafValuesMessageCall(addr[:], codeSizeBytes[:]) + } + if len(code) == 0 { ret, err = nil, nil // gas is unchanged } else { - if evm.Accesses != nil { - // Touch the account data - var data [32]byte - evm.Accesses.TouchAddress(utils.GetTreeKeyVersion(addr.Bytes()), data[:]) - binary.BigEndian.PutUint64(data[:], evm.StateDB.GetNonce(addr)) - evm.Accesses.TouchAddress(utils.GetTreeKeyNonce(addr[:]), data[:]) - evm.Accesses.TouchAddress(utils.GetTreeKeyBalance(addr[:]), evm.StateDB.GetBalance(addr).Bytes()) - binary.BigEndian.PutUint64(data[:], uint64(len(code))) - evm.Accesses.TouchAddress(utils.GetTreeKeyCodeSize(addr[:]), data[:]) - evm.Accesses.TouchAddress(utils.GetTreeKeyCodeKeccak(addr[:]), evm.StateDB.GetCodeHash(addr).Bytes()) - } - addrCopy := addr // If the account has no code, we can abort here // The depth-check is already done, and precompiles handled above @@ -315,6 +338,12 @@ func (evm *EVM) CallCode(caller ContractRef, addr common.Address, input []byte, if p, isPrecompile := evm.precompile(addr); isPrecompile { ret, gas, err = RunPrecompiledContract(p, input, gas) } else { + if evm.Accesses != nil { + codeSize := uint64(evm.StateDB.GetCodeSize(addr)) + var codeSizeBytes [32]byte + binary.LittleEndian.PutUint64(codeSizeBytes[:8], codeSize) + evm.Accesses.SetLeafValuesMessageCall(addr.Bytes()[:], codeSizeBytes[:]) + } addrCopy := addr // Initialise a new contract and set the code that is to be used by the EVM. // The contract is a scoped environment for this execution context only. @@ -359,6 +388,12 @@ func (evm *EVM) DelegateCall(caller ContractRef, addr common.Address, input []by if p, isPrecompile := evm.precompile(addr); isPrecompile { ret, gas, err = RunPrecompiledContract(p, input, gas) } else { + if evm.Accesses != nil { + codeSize := uint64(evm.StateDB.GetCodeSize(addr)) + var codeSizeBytes [32]byte + binary.LittleEndian.PutUint64(codeSizeBytes[:8], codeSize) + evm.Accesses.SetLeafValuesMessageCall(addr.Bytes()[:], codeSizeBytes[:]) + } addrCopy := addr // Initialise a new contract and make initialise the delegate values contract := NewContract(caller, AccountRef(caller.Address()), nil, gas).AsDelegate() @@ -408,9 +443,16 @@ func (evm *EVM) StaticCall(caller ContractRef, addr common.Address, input []byte }(gas) } + if p, isPrecompile := evm.precompile(addr); isPrecompile { ret, gas, err = RunPrecompiledContract(p, input, gas) } else { + if evm.Accesses != nil { + codeSize := uint64(evm.StateDB.GetCodeSize(addr)) + var codeSizeBytes [32]byte + binary.LittleEndian.PutUint64(codeSizeBytes[:8], codeSize) + evm.Accesses.SetLeafValuesMessageCall(addr.Bytes()[:], codeSizeBytes[:]) + } // At this point, we use a copy of address. If we don't, the go compiler will // leak the 'contract' to the outer scope, and make allocation for 'contract' // even if the actual execution ends on RunPrecompiled above. @@ -448,6 +490,13 @@ func (c *codeAndHash) Hash() common.Hash { // create creates a new contract using code as deployment code. func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, value *big.Int, address common.Address, typ OpCode) ([]byte, common.Address, uint64, error) { + var zeroVerkleLeaf [32]byte + var balanceBefore []byte + + if evm.Accesses != nil { + evm.Accesses.SetLeafValuesContractCreateInit(address.Bytes()[:], zeroVerkleLeaf[:]) + } + // Depth check execution. Fail if we're trying to execute above the // limit. if evm.depth > int(params.CallCreateDepth) { @@ -469,14 +518,25 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, // Ensure there's no existing contract already at the designated address contractHash := evm.StateDB.GetCodeHash(address) if evm.StateDB.GetNonce(address) != 0 || (contractHash != (common.Hash{}) && contractHash != emptyCodeHash) { + // TODO this is a case where the witness should touch the already-created account + // however, it seems impossible to ever actually encounter this. return nil, common.Address{}, 0, ErrContractAddressCollision } // Create a new account on the state snapshot := evm.StateDB.Snapshot() + evm.StateDB.CreateAccount(address) if evm.chainRules.IsEIP158 { evm.StateDB.SetNonce(address, 1) } + if evm.Accesses != nil { + // note: assumption is that the nonce, code size, code hash + // will be 0x0000...00 at the target account before it is created + // otherwise would imply contract creation collision which is + // impossible if self-destruct is removed + balanceBefore = getBalanceLittleEndian(evm, address) + } + evm.Context.Transfer(evm.StateDB, caller.Address(), address, value) // Initialise a new contract and set the code that is to be used by the EVM. @@ -533,6 +593,22 @@ func (evm *EVM) create(caller ContractRef, codeAndHash *codeAndHash, gas uint64, } } + if err == nil && evm.Accesses != nil { + // TODO: + // unsure whether to charge this in gas_table.go, the spec would indicate + // that doing it here (after the creation payload has been executed) is + // the right place for it + if !contract.UseGas(evm.Accesses.TouchAndChargeContractCreateCompleted(address.Bytes()[:], value.Sign() != 0)) { + evm.StateDB.RevertToSnapshot(snapshot) + err = ErrOutOfGas + } else { + evm.Accesses.SetLeafValuesContractCreateCompleted(address.Bytes()[:], zeroVerkleLeaf[:], zeroVerkleLeaf[:]) + if value.Sign() != 0 { + evm.Accesses.TouchAddress(utils.GetTreeKeyBalance(address.Bytes()[:]), balanceBefore) + } + } + } + if evm.Config.Debug { if evm.depth == 0 { evm.Config.Tracer.CaptureEnd(ret, gas-contract.Gas, time.Since(start), err) @@ -553,7 +629,7 @@ func (evm *EVM) Create(caller ContractRef, code []byte, gas uint64, value *big.I // // The different between Create2 with Create is Create2 uses sha3(0xff ++ msg.sender ++ salt ++ sha3(init_code))[12:] // instead of the usual sender-and-nonce-hash as the address where the contract is initialized at. -func (evm *EVM) Create2(caller ContractRef, code []byte, gas uint64, endowment *big.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { +func (evm *EVM) Create2(caller *Contract, code []byte, gas uint64, endowment *big.Int, salt *uint256.Int) (ret []byte, contractAddr common.Address, leftOverGas uint64, err error) { codeAndHash := &codeAndHash{code: code} contractAddr = crypto.CreateAddress2(caller.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) return evm.create(caller, codeAndHash, gas, endowment, contractAddr, CREATE2) diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 7fe7b92e8bb8..1dd8a6fbd85d 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -19,6 +19,7 @@ package vm import ( "errors" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/params" @@ -354,9 +355,32 @@ var ( gasMLoad = pureMemoryGascost gasMStore8 = pureMemoryGascost gasMStore = pureMemoryGascost - gasCreate = pureMemoryGascost + statefulGasCreate = pureMemoryGascost ) +func gasCreate(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var gasUsed uint64 + if gasUsed, err := statefulGasCreate(evm, contract, stack, mem, memorySize); err != nil { + return gasUsed, err + } + + if evm.Accesses != nil { + var overflow bool + statelessGas := evm.Accesses.TouchAndChargeContractCreateInit(contract.Address().Bytes()[:]) + if gasUsed, overflow = math.SafeAdd(gasUsed, statelessGas); overflow { + return 0, ErrGasUintOverflow + } + if stack.Back(0).Sign() != 0 { + valueTransferGas := evm.Accesses.TouchAddressAndChargeGas(contract.Address().Bytes()[:], nil) + if gasUsed, overflow = math.SafeAdd(gasUsed, valueTransferGas); overflow { + return 0, ErrGasUintOverflow + } + } + } + + return gasUsed, nil +} + func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { gas, err := memoryGasCost(mem, memorySize) if err != nil { @@ -372,6 +396,19 @@ func gasCreate2(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memoryS if gas, overflow = math.SafeAdd(gas, wordGas); overflow { return 0, ErrGasUintOverflow } + if evm.Accesses != nil { + var overflow bool + statelessGas := evm.Accesses.TouchAndChargeContractCreateInit(contract.Address().Bytes()[:]) + if gas, overflow = math.SafeAdd(gas, statelessGas); overflow { + return 0, ErrGasUintOverflow + } + if stack.Back(0).Sign() != 0 { + valueTransferGas := evm.Accesses.TouchAddressAndChargeGas(contract.Address().Bytes()[:], nil) + if gas, overflow = math.SafeAdd(gas, valueTransferGas); overflow { + return 0, ErrGasUintOverflow + } + } + } return gas, nil } @@ -407,13 +444,7 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize transfersValue = !stack.Back(2).IsZero() address = common.Address(stack.Back(1).Bytes20()) ) - if evm.Accesses != nil { - // Charge witness costs - for i := trieUtils.VersionLeafKey; i <= trieUtils.CodeSizeLeafKey; i++ { - index := trieUtils.GetTreeKeyAccountLeaf(address[:], byte(i)) - gas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil) - } - } + if evm.chainRules.IsEIP158 { if transfersValue && evm.StateDB.Empty(address) { @@ -441,6 +472,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.Accesses != nil { + // TODO omit these for call to self? + 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 } @@ -454,6 +500,7 @@ func gasCallCode(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory gas uint64 overflow bool ) + if stack.Back(2).Sign() != 0 { gas += params.CallValueTransferGas } @@ -467,6 +514,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.Accesses != nil { + 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 } @@ -483,6 +539,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.Accesses != nil { + 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 } @@ -499,6 +564,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.Accesses != nil { + 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 } @@ -519,6 +593,10 @@ func gasSelfdestruct(evm *EVM, contract *Contract, stack *Stack, mem *Memory, me } } + if evm.Accesses != nil { + log.Warn("verkle witnesses accumulation not supported for selfdestruct") + } + if !evm.StateDB.HasSuicided(contract.Address()) { evm.StateDB.AddRefund(params.SelfdestructRefundGas) } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 4440aec4b44c..c52bfe3cd7d9 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -17,6 +17,7 @@ package vm import ( + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/log" @@ -668,6 +669,13 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b input = scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64())) gas = scope.Contract.Gas ) + if interpreter.evm.Accesses != nil { + contractAddress := crypto.CreateAddress(scope.Contract.Address(), interpreter.evm.StateDB.GetNonce(scope.Contract.Address())) + statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:]) + if !tryConsumeGas(&gas, statelessGas) { + return nil, ErrExecutionReverted + } + } if interpreter.evm.chainRules.IsEIP150 { gas -= gas / 64 } @@ -675,6 +683,7 @@ func opCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b stackvalue := size scope.Contract.UseGas(gas) + //TODO: use uint256.Int instead of converting with toBig() var bigVal = big0 if !value.IsZero() { @@ -710,6 +719,14 @@ 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.Accesses != nil { + codeAndHash := &codeAndHash{code: input} + contractAddress := crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), codeAndHash.Hash().Bytes()) + statelessGas := interpreter.evm.Accesses.TouchAndChargeContractCreateInit(contractAddress.Bytes()[:]) + if !tryConsumeGas(&gas, statelessGas) { + return nil, ErrExecutionReverted + } + } // Apply EIP150 gas -= gas / 64 diff --git a/core/vm/interface.go b/core/vm/interface.go index b9c2eccb66f2..37a7eab63912 100644 --- a/core/vm/interface.go +++ b/core/vm/interface.go @@ -47,6 +47,9 @@ type StateDB interface { GetState(common.Address, common.Hash) common.Hash SetState(common.Address, common.Hash, common.Hash) + GetBalanceLittleEndian(address common.Address) []byte + GetNonceLittleEndian(address common.Address) []byte + Suicide(common.Address) bool HasSuicided(common.Address) bool diff --git a/trie/verkle.go b/trie/verkle.go index 85d8f569292e..61b161ab4e83 100644 --- a/trie/verkle.go +++ b/trie/verkle.go @@ -79,7 +79,6 @@ func (t *VerkleTrie) TryUpdateAccount(key []byte, acc *types.StateAccount) error if err = t.TryUpdate(utils.GetTreeKeyCodeKeccak(key), acc.CodeHash); err != nil { return fmt.Errorf("updateStateObject (%x) error: %v", key, err) } - return nil }