From b2edbf10ff93c5868634e359c6acdfc6f68fa314 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Fri, 10 Dec 2021 07:10:32 +0000 Subject: [PATCH 01/17] make push dynamically-charged. charge witness gas costs for push. refactor evm witness gas charging to move logic for touching a range of bytecode into a helper method 'touchEachChunksAndChargeGas' --- consensus/ethash/consensus.go | 12 ++- core/state_processor.go | 4 +- core/vm/common.go | 12 +++ core/vm/evm.go | 22 ++--- core/vm/gas_table.go | 113 ++------------------- core/vm/instructions.go | 179 ++++++++++++++++------------------ core/vm/interpreter.go | 52 ++-------- core/vm/jump_table.go | 2 - 8 files changed, 133 insertions(+), 263 deletions(-) diff --git a/consensus/ethash/consensus.go b/consensus/ethash/consensus.go index 56b35d72f2d7..6eff8d9055f1 100644 --- a/consensus/ethash/consensus.go +++ b/consensus/ethash/consensus.go @@ -661,14 +661,20 @@ func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header r.Sub(r, header.Number) r.Mul(r, blockReward) r.Div(r, big8) - uncleCoinbase := utils.GetTreeKeyBalance(uncle.Coinbase.Bytes()) - state.Witness().TouchAddress(uncleCoinbase, state.GetBalance(uncle.Coinbase).Bytes()) + + if state.Witness() != nil { + uncleCoinbase := utils.GetTreeKeyBalance(uncle.Coinbase.Bytes()) + state.Witness().TouchAddress(uncleCoinbase, state.GetBalance(uncle.Coinbase).Bytes()) + } state.AddBalance(uncle.Coinbase, r) r.Div(blockReward, big32) reward.Add(reward, r) } coinbase := utils.GetTreeKeyBalance(header.Coinbase.Bytes()) - state.Witness().TouchAddress(coinbase, state.GetBalance(header.Coinbase).Bytes()) + + if state.Witness() != nil { + state.Witness().TouchAddress(coinbase, state.GetBalance(header.Coinbase).Bytes()) + } state.AddBalance(header.Coinbase, reward) } diff --git a/core/state_processor.go b/core/state_processor.go index fcebeffce5a8..174ec77ff34e 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -128,7 +128,9 @@ func applyTransaction(msg types.Message, config *params.ChainConfig, bc ChainCon receipt.ContractAddress = crypto.CreateAddress(evm.TxContext.Origin, tx.Nonce()) } - statedb.Witness().Merge(txContext.Accesses) + if config.IsCancun(blockNumber) { + statedb.Witness().Merge(txContext.Accesses) + } // Set the receipt logs and create the bloom filter. receipt.Logs = statedb.GetLogs(tx.Hash(), blockHash) diff --git a/core/vm/common.go b/core/vm/common.go index 90ba4a4ad15b..b79186c24513 100644 --- a/core/vm/common.go +++ b/core/vm/common.go @@ -63,6 +63,18 @@ func getData(data []byte, start uint64, size uint64) []byte { return common.RightPadBytes(data[start:end], int(size)) } +func getDataAndAdjustedBounds(data []byte, start uint64, size uint64) (codeCopyPadded []byte, actualStart uint64, sizeNonPadded uint64) { + length := uint64(len(data)) + if start > length { + start = length + } + end := start + size + if end > length { + end = length + } + return common.RightPadBytes(data[start:end], int(size)), start, end +} + // toWordSize returns the ceiled word size required for memory expansion. func toWordSize(size uint64) uint64 { if size > math.MaxUint64-31 { diff --git a/core/vm/evm.go b/core/vm/evm.go index f5bdff8303f2..81570ed86ea5 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -125,8 +125,6 @@ type EVM struct { // available gas is calculated in gasCall* according to the 63/64 rule and later // applied in opCall*. callGasTemp uint64 - - accesses map[common.Hash]common.Hash } // NewEVM returns a new EVM. The returned EVM is not thread safe and should @@ -232,15 +230,17 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if len(code) == 0 { ret, err = nil, nil // gas is unchanged } else { - // 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()) + 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 diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index c42f0e61152d..19d2198af6bd 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -22,8 +22,6 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "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 @@ -88,102 +86,14 @@ func memoryCopierGas(stackpos int) gasFunc { } } -func gasExtCodeSize(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - usedGas := uint64(0) - slot := stack.Back(0) - if evm.accesses != nil { - index := trieUtils.GetTreeKeyCodeSize(slot.Bytes()) - usedGas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil) - } - - return usedGas, nil -} - var ( - gasCallDataCopy = memoryCopierGas(2) - gasCodeCopyStateful = memoryCopierGas(2) - gasExtCodeCopyStateful = memoryCopierGas(3) - gasReturnDataCopy = memoryCopierGas(2) + gasCallDataCopy = memoryCopierGas(2) + gasCodeCopy = memoryCopierGas(2) + gasExtCodeCopy = memoryCopierGas(3) + gasReturnDataCopy = memoryCopierGas(2) ) -func gasCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - var statelessGas uint64 - if evm.accesses != nil { - var ( - codeOffset = stack.Back(1) - length = stack.Back(2) - ) - uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() - if overflow { - uint64CodeOffset = 0xffffffffffffffff - } - uint64CodeEnd, overflow := new(uint256.Int).Add(codeOffset, length).Uint64WithOverflow() - if overflow { - uint64CodeEnd = 0xffffffffffffffff - } - addr := contract.Address() - chunk := uint64CodeOffset / 31 - endChunk := uint64CodeEnd / 31 - // XXX uint64 overflow in condition check - for ; chunk < endChunk; chunk++ { - - // TODO make a version of GetTreeKeyCodeChunk without the bigint - index := trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(chunk)) - statelessGas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil) - } - - } - usedGas, err := gasCodeCopyStateful(evm, contract, stack, mem, memorySize) - return usedGas + statelessGas, err -} - -func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - var statelessGas uint64 - if evm.accesses != nil { - var ( - a = stack.Back(0) - codeOffset = stack.Back(2) - length = stack.Back(3) - ) - uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() - if overflow { - uint64CodeOffset = 0xffffffffffffffff - } - uint64CodeEnd, overflow := new(uint256.Int).Add(codeOffset, length).Uint64WithOverflow() - if overflow { - uint64CodeEnd = 0xffffffffffffffff - } - addr := common.Address(a.Bytes20()) - chunk := uint64CodeOffset / 31 - endChunk := uint64CodeEnd / 31 - // XXX uint64 overflow in condition check - for ; chunk < endChunk; chunk++ { - // TODO(@gballet) make a version of GetTreeKeyCodeChunk without the bigint - index := trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(chunk)) - statelessGas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil) - } - - } - usedGas, err := gasExtCodeCopyStateful(evm, contract, stack, mem, memorySize) - return usedGas + statelessGas, err -} - -func gasSLoad(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - usedGas := uint64(0) - - if evm.accesses != nil { - where := stack.Back(0) - addr := contract.Address() - index := trieUtils.GetTreeKeyStorageSlot(addr[:], where) - usedGas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil) - } - - return usedGas, nil -} - func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { - // Apply the witness access costs, err is nil - accessGas, _ := gasSLoad(evm, contract, stack, mem, memorySize) var ( y, x = stack.Back(1), stack.Back(0) current = evm.StateDB.GetState(contract.Address(), x.Bytes32()) @@ -199,15 +109,14 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi // 3. From a non-zero to a non-zero (CHANGE) switch { case current == (common.Hash{}) && y.Sign() != 0: // 0 => non 0 - return params.SstoreSetGas + accessGas, nil + return params.SstoreSetGas, nil case current != (common.Hash{}) && y.Sign() == 0: // non 0 => 0 evm.StateDB.AddRefund(params.SstoreRefundGas) - return params.SstoreClearGas + accessGas, nil + return params.SstoreClearGas, nil default: // non 0 => non 0 (or 0 => 0) - return params.SstoreResetGas + accessGas, nil + return params.SstoreResetGas, nil } } - // The new gas metering is based on net gas costs (EIP-1283): // // 1. If current value equals new value (this is a no-op), 200 gas is deducted. @@ -422,14 +331,6 @@ 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) { gas += params.CallNewAccountGas diff --git a/core/vm/instructions.go b/core/vm/instructions.go index fdc6e3a37c27..fc914ae048e3 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -343,9 +343,10 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { slot := scope.Stack.peek() cs := uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20())) - if interpreter.evm.accesses != nil { + if interpreter.evm.TxContext.Accesses != nil { index := trieUtils.GetTreeKeyCodeSize(slot.Bytes()) - interpreter.evm.TxContext.Accesses.TouchAddress(index, uint256.NewInt(cs).Bytes()) + statelessGas := interpreter.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, uint256.NewInt(cs).Bytes()) + scope.Contract.UseGas(statelessGas) } slot.SetUint64(cs) return nil, nil @@ -368,63 +369,80 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ if overflow { uint64CodeOffset = 0xffffffffffffffff } - uint64CodeEnd, overflow := new(uint256.Int).Add(&codeOffset, &length).Uint64WithOverflow() - if overflow { - uint64CodeEnd = 0xffffffffffffffff - } - if interpreter.evm.accesses != nil { - copyCodeFromAccesses(scope.Contract.Address(), uint64CodeOffset, uint64CodeEnd, memOffset.Uint64(), interpreter, scope) - } else { - codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64()) - scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) - touchEachChunks(uint64CodeOffset, uint64CodeEnd, codeCopy, scope.Contract, interpreter.evm) + paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64()) + if interpreter.evm.TxContext.Accesses != nil { + statelessGas := touchEachChunksAndChargeGas(copyOffset, nonPaddedCopyLength, scope.Contract.Address().Bytes()[:], scope.Contract, interpreter.evm.Accesses) + scope.Contract.UseGas(statelessGas) } - + scope.Memory.Set(memOffset.Uint64(), uint64(len(paddedCodeCopy)), paddedCodeCopy) return nil, nil } -// Helper function to touch every chunk in a code range -func touchEachChunks(start, end uint64, code []byte, contract *Contract, evm *EVM) { - for chunk := start / 31; chunk <= end/31 && chunk <= uint64(len(code))/31; chunk++ { - index := trieUtils.GetTreeKeyCodeChunk(contract.Address().Bytes(), uint256.NewInt(chunk)) - count := uint64(0) - end := (chunk + 1) * 31 +// touchEachChunksAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs +func touchEachChunksAndChargeGas(offset, size uint64, address []byte, contract *Contract, accesses *types.AccessWitness) uint64 { + if size == 0 || offset > uint64(len(contract.Code)) { + return 0 + } + var statelessGasCharged uint64 + // start:end encompasses the range between the offset of + // the first byte in the first leaf of the code range that is touched + // and the last byte in the last leaf that is touched. If the contract + // code does not fill the last leaf, 'end' is the final byte of contract code + // in the last leaf that is touched + start := offset - (offset % 31) + var end uint64 + if start+size > uint64(len(contract.Code)) { + end = uint64(len(contract.Code)) + } else { + end = start + size + (start+size)%31 + } + code := contract.Code[:] + numLeaves := (end - start) / 31 + index := make([]byte, 32, 32) + + chunkOffset := new(uint256.Int) + treeIndex := new(uint256.Int) + + for i := 0; i < int(numLeaves); i++ { + chunkOffset.Add(trieUtils.CodeOffset, uint256.NewInt(uint64(i))) + treeIndex.Div(chunkOffset, trieUtils.VerkleNodeWidth) + var subIndex byte + subIndexMod := chunkOffset.Mod(chunkOffset, trieUtils.VerkleNodeWidth).Bytes() + if len(subIndexMod) == 0 { + subIndex = 0 + } else { + subIndex = subIndexMod[0] + } + treeKey := trieUtils.GetTreeKey(address, treeIndex, subIndex) + copy(index[0:31], treeKey) + index[31] = subIndex + + var value []byte + // the offset into the leaf that the first PUSH occurs + var firstPushOffset uint64 = 0 // Look for the first code byte (i.e. no pushdata) - for ; count < 31 && end+count < uint64(len(contract.Code)) && !contract.IsCode(chunk*31+count); count++ { + for ; firstPushOffset < 31 && firstPushOffset+uint64(i)*31 < uint64(len(contract.Code)) && !contract.IsCode(uint64(i)*31+firstPushOffset); firstPushOffset++ { } - var value [32]byte - value[0] = byte(count) - if end > uint64(len(code)) { - end = uint64(len(code)) + curEnd := (uint64(i) + 1) * 31 + if curEnd > end { + curEnd = end } - copy(value[1:], code[chunk*31:end]) - evm.Accesses.TouchAddress(index, value[:]) - } -} - -// copyCodeFromAccesses perform codecopy from the witness, not from the db. -func copyCodeFromAccesses(addr common.Address, codeOffset, codeEnd, memOffset uint64, in *EVMInterpreter, scope *ScopeContext) { - chunk := codeOffset / 31 - endChunk := codeEnd / 31 - start := codeOffset % 31 // start inside the first code chunk - offset := uint64(0) // memory offset to write to - // XXX uint64 overflow in condition check - for end := uint64(31); chunk < endChunk; chunk, start = chunk+1, 0 { - // case of the last chunk: figure out how many bytes need to - // be extracted from the last chunk. - if chunk+1 == endChunk { - end = codeEnd % 31 + valueSize := curEnd - (uint64(i) * 31) + value = make([]byte, 32, 32) + value[0] = byte(firstPushOffset) + + copy(value[1:valueSize+1], code[i*31:curEnd]) + if valueSize < 31 { + padding := make([]byte, 31-valueSize, 31-valueSize) + copy(value[valueSize+1:], padding) } - // TODO make a version of GetTreeKeyCodeChunk without the bigint - index := common.BytesToHash(trieUtils.GetTreeKeyCodeChunk(addr[:], uint256.NewInt(chunk))) - h := in.evm.accesses[index] - //in.evm.Accesses.TouchAddress(index.Bytes(), h[1+start:1+end]) - scope.Memory.Set(memOffset+offset, end-start, h[1+start:end]) - offset += 31 - start + statelessGasCharged += accesses.TouchAddressAndChargeGas(index, value) } + + return statelessGasCharged } func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { @@ -439,18 +457,12 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) if overflow { uint64CodeOffset = 0xffffffffffffffff } - uint64CodeEnd, overflow := new(uint256.Int).Add(&codeOffset, &length).Uint64WithOverflow() - if overflow { - uint64CodeEnd = 0xffffffffffffffff - } addr := common.Address(a.Bytes20()) - if interpreter.evm.accesses != nil { - copyCodeFromAccesses(addr, uint64CodeOffset, uint64CodeEnd, memOffset.Uint64(), interpreter, scope) + if interpreter.evm.TxContext.Accesses != nil { + panic("extcodecopy not implemented for verkle") } else { codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) - - touchEachChunks(uint64CodeOffset, uint64CodeEnd, codeCopy, scope.Contract, interpreter.evm) } return nil, nil @@ -579,10 +591,12 @@ func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by hash := common.Hash(loc.Bytes32()) val := interpreter.evm.StateDB.GetState(scope.Contract.Address(), hash) loc.SetBytes(val.Bytes()) - // Get the initial value as it might not be present - index := trieUtils.GetTreeKeyStorageSlot(scope.Contract.Address().Bytes(), loc) - interpreter.evm.TxContext.Accesses.TouchAddress(index, val.Bytes()) + if interpreter.evm.TxContext.Accesses != nil { + index := trieUtils.GetTreeKeyStorageSlot(scope.Contract.Address().Bytes(), loc) + statelessGas := interpreter.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, val.Bytes()) + scope.Contract.UseGas(statelessGas) + } return nil, nil } @@ -907,9 +921,11 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by *pc += 1 if *pc < codeLen { scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc]))) - // touch next chunk if PUSH1 is at the boundary. if so, *pc has - // advanced past this boundary. - if *pc%31 == 0 { + + if interpreter.evm.Accesses != nil && *pc%31 == 0 { + // touch next chunk if PUSH1 is at the boundary. if so, *pc has + // advanced past this boundary. + // touch push data by adding the last byte of the pushdata var value [32]byte chunk := *pc / 31 @@ -924,7 +940,8 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by } copy(value[1:], scope.Contract.Code[chunk*31:endMin]) index := trieUtils.GetTreeKeyCodeChunk(scope.Contract.Address().Bytes(), uint256.NewInt(chunk)) - interpreter.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil) + statelessGas := interpreter.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil) + scope.Contract.UseGas(statelessGas) } } else { scope.Stack.push(integer.Clear()) @@ -947,43 +964,15 @@ func makePush(size uint64, pushByteSize int) executionFunc { endMin = startMin + pushByteSize } + if interpreter.evm.TxContext.Accesses != nil { + statelessGas := touchEachChunksAndChargeGas(uint64(startMin), uint64(pushByteSize), scope.Contract.Address().Bytes()[:], scope.Contract, interpreter.evm.TxContext.Accesses) + scope.Contract.UseGas(statelessGas) + } + integer := new(uint256.Int) scope.Stack.push(integer.SetBytes(common.RightPadBytes( scope.Contract.Code[startMin:endMin], pushByteSize))) - // touch push data by adding the last byte of the pushdata - var value [32]byte - chunk := uint64(endMin-1) / 31 - count := uint64(0) - // Look for the first code byte (i.e. no pushdata) - for ; count < 31 && !scope.Contract.IsCode(chunk*31+count); count++ { - } - value[0] = byte(count) - copy(value[1:], scope.Contract.Code[chunk*31:endMin]) - index := trieUtils.GetTreeKeyCodeChunk(scope.Contract.Address().Bytes(), uint256.NewInt(chunk)) - interpreter.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil) - - // in the case of PUSH32, the end data might be two chunks away, - // so also get the middle chunk. There is a boundary condition - // check (endMin > 2) in the case the code is a single PUSH32 - // insctruction, whose immediate are just 0s. - if pushByteSize == 32 && endMin > 2 { - chunk = uint64(endMin-2) / 31 - count = uint64(0) - // Look for the first code byte (i.e. no pushdata) - for ; count < 31 && !scope.Contract.IsCode(chunk*31+count); count++ { - } - value[0] = byte(count) - end := (chunk + 1) * 31 - if end > uint64(len(scope.Contract.Code)) { - end = uint64(len(scope.Contract.Code)) - } - copy(value[1:], scope.Contract.Code[chunk*31:end]) - index := trieUtils.GetTreeKeyCodeChunk(scope.Contract.Address().Bytes(), uint256.NewInt(chunk)) - interpreter.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil) - - } - *pc += size return nil, nil } diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index 68a1f33794e6..9352d342fc1e 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -17,15 +17,12 @@ package vm import ( - "errors" "hash" "sync/atomic" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/log" - trieUtils "github.com/ethereum/go-ethereum/trie/utils" - "github.com/holiman/uint256" ) // Config are the configuration options for the Interpreter @@ -194,50 +191,15 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) ( logged, pcCopy, gasCopy = false, pc, contract.Gas } - // if the PC ends up in a new "page" of verkleized code, charge the - // associated witness costs. - inWitness := false - var codePage common.Hash - if in.evm.chainRules.IsCancun { - index := trieUtils.GetTreeKeyCodeChunk(contract.Address().Bytes(), uint256.NewInt(pc/31)) - - var value [32]byte - if in.evm.accesses != nil { - codePage, inWitness = in.evm.accesses[common.BytesToHash(index)] - // Return an error if we're in stateless mode - // and the code isn't in the witness. It means - // that if code is read beyond the actual code - // size, pages of 0s need to be added to the - // witness. - if !inWitness { - return nil, errors.New("code chunk missing from proof") - } - copy(value[:], codePage[:]) - } else { - // Calculate the chunk - chunk := pc / 31 - end := (chunk + 1) * 31 - if end >= uint64(len(contract.Code)) { - end = uint64(len(contract.Code)) - } - count := uint64(0) - // Look for the first code byte (i.e. no pushdata) - for ; chunk*31+count < end && count < 31 && !contract.IsCode(chunk*31+count); count++ { - } - value[0] = byte(count) - copy(value[1:], contract.Code[chunk*31:end]) - } - contract.Gas -= in.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, value[:]) + if in.evm.TxContext.Accesses != nil { + // if the PC ends up in a new "page" of verkleized code, charge the + // associated witness costs. + contract.Gas -= touchEachChunksAndChargeGas(pc, 1, contract.Address().Bytes()[:], contract, in.evm.TxContext.Accesses) } - if inWitness { - // Get the op from the tree, skipping the header byte - op = OpCode(codePage[1+pc%31]) - } else { - // If we are in witness mode, then raise an error - op = contract.GetOp(pc) - - } + // TODO how can we tell if we are in stateless mode here and need to get the op from the witness + // If we are in witness mode, then raise an error + op = contract.GetOp(pc) // Get the operation from the jump table and validate the stack to ensure there are // enough stack items available to perform the operation. diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 15250f15a8d0..329ad77cbf83 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -433,7 +433,6 @@ func newFrontierInstructionSet() JumpTable { EXTCODESIZE: { execute: opExtCodeSize, constantGas: params.ExtcodeSizeGasFrontier, - dynamicGas: gasExtCodeSize, minStack: minStack(1, 1), maxStack: maxStack(1, 1), }, @@ -514,7 +513,6 @@ func newFrontierInstructionSet() JumpTable { SLOAD: { execute: opSload, constantGas: params.SloadGasFrontier, - dynamicGas: gasSLoad, minStack: minStack(1, 1), maxStack: maxStack(1, 1), }, From 3b7edd86fbccd07e011ca638601157e41ee0f450 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 13 Dec 2021 01:41:22 +0000 Subject: [PATCH 02/17] add witness gas calculation for CodeCopy, ExtCodeCopy, SLoad back to gas_table.go --- core/vm/gas_table.go | 49 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 2 deletions(-) diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 19d2198af6bd..8ca197ff7090 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -22,6 +22,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/math" "github.com/ethereum/go-ethereum/params" + trieUtils "github.com/ethereum/go-ethereum/trie/utils" ) // memoryGasCost calculates the quadratic gas for memory expansion. It does so @@ -88,11 +89,55 @@ func memoryCopierGas(stackpos int) gasFunc { var ( gasCallDataCopy = memoryCopierGas(2) - gasCodeCopy = memoryCopierGas(2) - gasExtCodeCopy = memoryCopierGas(3) + gasCodeCopyStateful = memoryCopierGas(2) + gasExtCodeCopyStateful = memoryCopierGas(3) gasReturnDataCopy = memoryCopierGas(2) ) +func gasCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var statelessGas uint64 + if evm.Accesses != nil { + var ( + codeOffset = stack.Back(1) + length = stack.Back(2) + ) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + if overflow { + uint64CodeOffset = 0xffffffffffffffff + } + uint64Length, overflow := length.Uint64WithOverflow() + if overflow { + uint64Length = 0xffffffffffffffff + } + _, offset, nonPaddedSize := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, uint64Length) + statelessGas = touchEachChunksAndChargeGas(offset, nonPaddedSize, contract.Address().Bytes()[:], contract, evm.Accesses) + } + usedGas, err := gasCodeCopyStateful(evm, contract, stack, mem, memorySize) + return usedGas + statelessGas, err +} + +func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + var statelessGas uint64 + if evm.Accesses != nil { + panic("extcodecopy not implemented for verkle") + } + usedGas, err := gasExtCodeCopyStateful(evm, contract, stack, mem, memorySize) + return usedGas + statelessGas, err +} + +func gasSLoad(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + usedGas := uint64(0) + + if evm.Accesses != nil { + where := stack.Back(0) + addr := contract.Address() + index := trieUtils.GetTreeKeyStorageSlot(addr[:], where) + usedGas += evm.Accesses.TouchAddressAndChargeGas(index, nil) + } + + return usedGas, nil +} + func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var ( y, x = stack.Back(1), stack.Back(0) From cf38ac51d1aa972e5311730b0c0f809f67280a84 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 13 Dec 2021 04:36:10 +0000 Subject: [PATCH 03/17] witness gas charging for CALL --- core/vm/instructions.go | 49 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index fc914ae048e3..7712f2c1a042 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -17,6 +17,9 @@ package vm import ( + "math/big" + "encoding/binary" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" @@ -726,6 +729,18 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] return nil, nil } +// TODO there should be an already-existing method to do this +func padBigInt(val *big.Int) [32]byte { + valBytes := val.Bytes() + if len(valBytes) > 32 { + panic("value too big") + } + + result := [32]byte{} + copy(result[32-len(valBytes):], valBytes[:]) + return result +} + func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { stack := scope.Stack // Pop gas. The actual gas in interpreter.evm.callGasTemp. @@ -737,6 +752,38 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt toAddr := common.Address(addr.Bytes20()) // Get the arguments from the memory. args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) + sourceAddrBytes := scope.Contract.Address().Bytes() + + if interpreter.evm.Accesses != nil { + // touch the balance + index := trieUtils.GetTreeKeyAccountLeaf(sourceAddrBytes, trieUtils.BalanceLeafKey) + balanceBytes := padBigInt(interpreter.evm.StateDB.GetBalance(scope.Contract.Address())) + if !scope.Contract.UseGas(interpreter.evm.Accesses.TouchAddressAndChargeGas(index, balanceBytes[:])) { + return nil, ErrOutOfGas + } + // touch the nonce + index = trieUtils.GetTreeKeyAccountLeaf(sourceAddrBytes, trieUtils.BalanceLeafKey) + nonceBytes := [32]byte{} + nonce := interpreter.evm.StateDB.GetNonce(scope.Contract.Address()) + binary.BigEndian.PutUint64(nonceBytes[24:32], nonce) + if !scope.Contract.UseGas(interpreter.evm.Accesses.TouchAddressAndChargeGas(index, nonceBytes[:])) { + return nil, ErrOutOfGas + } + // touch the code hash + index = trieUtils.GetTreeKeyAccountLeaf(sourceAddrBytes, trieUtils.CodeKeccakLeafKey) + codeHash := interpreter.evm.StateDB.GetCodeHash(scope.Contract.Address()) + if !scope.Contract.UseGas(interpreter.evm.Accesses.TouchAddressAndChargeGas(index, codeHash[:])) { + return nil, ErrOutOfGas + } + // set the code size + index = trieUtils.GetTreeKeyAccountLeaf(sourceAddrBytes, trieUtils.CodeSizeLeafKey) + codeSize := uint64(interpreter.evm.StateDB.GetCodeSize(scope.Contract.Address())) + codeSizeLeafBytes := [32]byte{} + binary.BigEndian.PutUint64(codeSizeLeafBytes[24:32], codeSize) + if !scope.Contract.UseGas(interpreter.evm.Accesses.TouchAddressAndChargeGas(index, codeSizeLeafBytes[:])) { + return nil, ErrOutOfGas + } + } var bigVal = big0 //TODO: use uint256.Int instead of converting with toBig() @@ -747,6 +794,8 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt bigVal = value.ToBig() } + + ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, bigVal) if err != nil { From 4bf2e6ef5f65d4f6fb87138d73b8ff32c4f01532 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 13 Dec 2021 04:45:18 +0000 Subject: [PATCH 04/17] remove explicit reference to evm.TxContext --- core/state_transition.go | 10 +++++----- core/vm/instructions.go | 18 +++++++++--------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/core/state_transition.go b/core/state_transition.go index 393e620ad860..1e39dee6569e 100644 --- a/core/state_transition.go +++ b/core/state_transition.go @@ -304,26 +304,26 @@ func (st *StateTransition) TransitionDb() (*ExecutionResult, error) { if st.gas < gas { return nil, fmt.Errorf("%w: have %d, want %d", ErrIntrinsicGas, st.gas, gas) } - if st.evm.TxContext.Accesses != nil { + if st.evm.Accesses != nil { if msg.To() != nil { toBalance := trieUtils.GetTreeKeyBalance(msg.To().Bytes()) pre := st.state.GetBalance(*msg.To()) - gas += st.evm.TxContext.Accesses.TouchAddressAndChargeGas(toBalance, pre.Bytes()) + 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.TxContext.Accesses.TouchAddressAndChargeGas(fromNonce, preTN[:]) + gas += st.evm.Accesses.TouchAddressAndChargeGas(fromNonce, preTN[:]) } 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.TxContext.Accesses.TouchAddressAndChargeGas(fromNonce, preFN[:]) - gas += st.evm.TxContext.Accesses.TouchAddressAndChargeGas(fromBalance, preFB[:]) + gas += st.evm.Accesses.TouchAddressAndChargeGas(fromNonce, preFN[:]) + gas += st.evm.Accesses.TouchAddressAndChargeGas(fromBalance, preFB[:]) } st.gas -= gas diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 7712f2c1a042..5fd2428c1dcd 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -346,9 +346,9 @@ func opReturnDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { slot := scope.Stack.peek() cs := uint64(interpreter.evm.StateDB.GetCodeSize(slot.Bytes20())) - if interpreter.evm.TxContext.Accesses != nil { + if interpreter.evm.Accesses != nil { index := trieUtils.GetTreeKeyCodeSize(slot.Bytes()) - statelessGas := interpreter.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, uint256.NewInt(cs).Bytes()) + statelessGas := interpreter.evm.Accesses.TouchAddressAndChargeGas(index, uint256.NewInt(cs).Bytes()) scope.Contract.UseGas(statelessGas) } slot.SetUint64(cs) @@ -374,7 +374,7 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ } paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64()) - if interpreter.evm.TxContext.Accesses != nil { + if interpreter.evm.Accesses != nil { statelessGas := touchEachChunksAndChargeGas(copyOffset, nonPaddedCopyLength, scope.Contract.Address().Bytes()[:], scope.Contract, interpreter.evm.Accesses) scope.Contract.UseGas(statelessGas) } @@ -461,7 +461,7 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) uint64CodeOffset = 0xffffffffffffffff } addr := common.Address(a.Bytes20()) - if interpreter.evm.TxContext.Accesses != nil { + if interpreter.evm.Accesses != nil { panic("extcodecopy not implemented for verkle") } else { codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) @@ -595,9 +595,9 @@ func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by val := interpreter.evm.StateDB.GetState(scope.Contract.Address(), hash) loc.SetBytes(val.Bytes()) - if interpreter.evm.TxContext.Accesses != nil { + if interpreter.evm.Accesses != nil { index := trieUtils.GetTreeKeyStorageSlot(scope.Contract.Address().Bytes(), loc) - statelessGas := interpreter.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, val.Bytes()) + statelessGas := interpreter.evm.Accesses.TouchAddressAndChargeGas(index, val.Bytes()) scope.Contract.UseGas(statelessGas) } return nil, nil @@ -989,7 +989,7 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by } copy(value[1:], scope.Contract.Code[chunk*31:endMin]) index := trieUtils.GetTreeKeyCodeChunk(scope.Contract.Address().Bytes(), uint256.NewInt(chunk)) - statelessGas := interpreter.evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil) + statelessGas := interpreter.evm.Accesses.TouchAddressAndChargeGas(index, nil) scope.Contract.UseGas(statelessGas) } } else { @@ -1013,8 +1013,8 @@ func makePush(size uint64, pushByteSize int) executionFunc { endMin = startMin + pushByteSize } - if interpreter.evm.TxContext.Accesses != nil { - statelessGas := touchEachChunksAndChargeGas(uint64(startMin), uint64(pushByteSize), scope.Contract.Address().Bytes()[:], scope.Contract, interpreter.evm.TxContext.Accesses) + if interpreter.evm.Accesses != nil { + statelessGas := touchEachChunksAndChargeGas(uint64(startMin), uint64(pushByteSize), scope.Contract.Address().Bytes()[:], scope.Contract, interpreter.evm.Accesses) scope.Contract.UseGas(statelessGas) } From c89d46527a1dcdbc09b06c4c475838380d5a6ec8 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 13 Dec 2021 04:52:00 +0000 Subject: [PATCH 05/17] core/vm: make touchEachChunksAndCharge gas handle nil code value --- core/vm/instructions.go | 45 +++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 5fd2428c1dcd..a2e386716825 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -396,12 +396,15 @@ func touchEachChunksAndChargeGas(offset, size uint64, address []byte, contract * // in the last leaf that is touched start := offset - (offset % 31) var end uint64 - if start+size > uint64(len(contract.Code)) { + if contract != nil && start+size > uint64(len(contract.Code)) { end = uint64(len(contract.Code)) } else { - end = start + size + (start+size)%31 + end = start + size + (start + size) % 31 + } + var code []byte + if contract != nil { + code := contract.Code[:] } - code := contract.Code[:] numLeaves := (end - start) / 31 index := make([]byte, 32, 32) @@ -423,23 +426,25 @@ func touchEachChunksAndChargeGas(offset, size uint64, address []byte, contract * index[31] = subIndex var value []byte - // the offset into the leaf that the first PUSH occurs - var firstPushOffset uint64 = 0 - // Look for the first code byte (i.e. no pushdata) - for ; firstPushOffset < 31 && firstPushOffset+uint64(i)*31 < uint64(len(contract.Code)) && !contract.IsCode(uint64(i)*31+firstPushOffset); firstPushOffset++ { - } - curEnd := (uint64(i) + 1) * 31 - if curEnd > end { - curEnd = end - } - valueSize := curEnd - (uint64(i) * 31) - value = make([]byte, 32, 32) - value[0] = byte(firstPushOffset) - - copy(value[1:valueSize+1], code[i*31:curEnd]) - if valueSize < 31 { - padding := make([]byte, 31-valueSize, 31-valueSize) - copy(value[valueSize+1:], padding) + if contract != nil { + // the offset into the leaf that the first PUSH occurs + var firstPushOffset uint64 = 0 + // Look for the first code byte (i.e. no pushdata) + for ; firstPushOffset < 31 && firstPushOffset+uint64(i)*31 < uint64(len(contract.Code)) && !contract.IsCode(uint64(i)*31+firstPushOffset); firstPushOffset++ { + } + curEnd := (uint64(i) + 1) * 31 + if curEnd > end { + curEnd = end + } + valueSize := curEnd - (uint64(i) * 31) + value = make([]byte, 32, 32) + value[0] = byte(firstPushOffset) + + copy(value[1:valueSize+1], code[i*31:curEnd]) + if valueSize < 31 { + padding := make([]byte, 31-valueSize, 31-valueSize) + copy(value[valueSize+1:], padding) + } } statelessGasCharged += accesses.TouchAddressAndChargeGas(index, value) From 34ff553a4197aabcebfeb97d89973c3aaef0ebc9 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 13 Dec 2021 05:05:24 +0000 Subject: [PATCH 06/17] core/vm: call implementation, separate out witnesses into touch/set --- core/vm/gas_table.go | 16 ++++++++++++++++ core/vm/instructions.go | 18 +++++------------- 2 files changed, 21 insertions(+), 13 deletions(-) diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 8ca197ff7090..de1be7feac88 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -402,6 +402,22 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize if gas, overflow = math.SafeAdd(gas, evm.callGasTemp); overflow { return 0, ErrGasUintOverflow } + + sourceAddrBytes := contract.Address().Bytes() + if evm.Accesses != nil { + // touch the balance + index := trieUtils.GetTreeKeyAccountLeaf(sourceAddrBytes, trieUtils.BalanceLeafKey) + gas += evm.Accesses.TouchAddressAndChargeGas(index, nil) + // touch the nonce + index = trieUtils.GetTreeKeyAccountLeaf(sourceAddrBytes, trieUtils.BalanceLeafKey) + gas += evm.Accesses.TouchAddressAndChargeGas(index, nil) + // touch the code hash + index = trieUtils.GetTreeKeyAccountLeaf(sourceAddrBytes, trieUtils.CodeKeccakLeafKey) + gas += evm.Accesses.TouchAddressAndChargeGas(index, nil) + // touch the code size + index = trieUtils.GetTreeKeyAccountLeaf(sourceAddrBytes, trieUtils.CodeSizeLeafKey) + gas += evm.Accesses.TouchAddressAndChargeGas(index, nil) + } return gas, nil } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index a2e386716825..21e48e792fea 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -403,7 +403,7 @@ func touchEachChunksAndChargeGas(offset, size uint64, address []byte, contract * } var code []byte if contract != nil { - code := contract.Code[:] + code = contract.Code[:] } numLeaves := (end - start) / 31 index := make([]byte, 32, 32) @@ -763,31 +763,23 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt // touch the balance index := trieUtils.GetTreeKeyAccountLeaf(sourceAddrBytes, trieUtils.BalanceLeafKey) balanceBytes := padBigInt(interpreter.evm.StateDB.GetBalance(scope.Contract.Address())) - if !scope.Contract.UseGas(interpreter.evm.Accesses.TouchAddressAndChargeGas(index, balanceBytes[:])) { - return nil, ErrOutOfGas - } + interpreter.evm.Accesses.TouchAddress(index, balanceBytes[:]) // touch the nonce index = trieUtils.GetTreeKeyAccountLeaf(sourceAddrBytes, trieUtils.BalanceLeafKey) nonceBytes := [32]byte{} nonce := interpreter.evm.StateDB.GetNonce(scope.Contract.Address()) binary.BigEndian.PutUint64(nonceBytes[24:32], nonce) - if !scope.Contract.UseGas(interpreter.evm.Accesses.TouchAddressAndChargeGas(index, nonceBytes[:])) { - return nil, ErrOutOfGas - } + interpreter.evm.Accesses.TouchAddress(index, nonceBytes[:]) // touch the code hash index = trieUtils.GetTreeKeyAccountLeaf(sourceAddrBytes, trieUtils.CodeKeccakLeafKey) codeHash := interpreter.evm.StateDB.GetCodeHash(scope.Contract.Address()) - if !scope.Contract.UseGas(interpreter.evm.Accesses.TouchAddressAndChargeGas(index, codeHash[:])) { - return nil, ErrOutOfGas - } + interpreter.evm.Accesses.TouchAddress(index, codeHash[:]) // set the code size index = trieUtils.GetTreeKeyAccountLeaf(sourceAddrBytes, trieUtils.CodeSizeLeafKey) codeSize := uint64(interpreter.evm.StateDB.GetCodeSize(scope.Contract.Address())) codeSizeLeafBytes := [32]byte{} binary.BigEndian.PutUint64(codeSizeLeafBytes[24:32], codeSize) - if !scope.Contract.UseGas(interpreter.evm.Accesses.TouchAddressAndChargeGas(index, codeSizeLeafBytes[:])) { - return nil, ErrOutOfGas - } + interpreter.evm.Accesses.TouchAddress(index, codeSizeLeafBytes[:]) } var bigVal = big0 From d38f6b9ec769d127d312898a82eacceda152623e Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 13 Dec 2021 05:15:17 +0000 Subject: [PATCH 07/17] some fixes --- core/vm/gas_table.go | 2 +- core/vm/instructions.go | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index de1be7feac88..9bd0b62497ff 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -110,7 +110,7 @@ func gasCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory uint64Length = 0xffffffffffffffff } _, offset, nonPaddedSize := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, uint64Length) - statelessGas = touchEachChunksAndChargeGas(offset, nonPaddedSize, contract.Address().Bytes()[:], contract, evm.Accesses) + statelessGas = touchEachChunksAndChargeGas(offset, nonPaddedSize, contract.Address().Bytes()[:], nil, evm.Accesses) } usedGas, err := gasCodeCopyStateful(evm, contract, stack, mem, memorySize) return usedGas + statelessGas, err diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 21e48e792fea..5c0d74ad0ab0 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -375,8 +375,7 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64()) if interpreter.evm.Accesses != nil { - statelessGas := touchEachChunksAndChargeGas(copyOffset, nonPaddedCopyLength, scope.Contract.Address().Bytes()[:], scope.Contract, interpreter.evm.Accesses) - scope.Contract.UseGas(statelessGas) + touchEachChunksAndChargeGas(copyOffset, nonPaddedCopyLength, scope.Contract.Address().Bytes()[:], scope.Contract, interpreter.evm.Accesses) } scope.Memory.Set(memOffset.Uint64(), uint64(len(paddedCodeCopy)), paddedCodeCopy) return nil, nil @@ -602,8 +601,7 @@ func opSload(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by if interpreter.evm.Accesses != nil { index := trieUtils.GetTreeKeyStorageSlot(scope.Contract.Address().Bytes(), loc) - statelessGas := interpreter.evm.Accesses.TouchAddressAndChargeGas(index, val.Bytes()) - scope.Contract.UseGas(statelessGas) + interpreter.evm.Accesses.TouchAddressAndChargeGas(index, val.Bytes()) } return nil, nil } From c4d898b94a8173e89b62ad639cf5cc8042c90cae Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 13 Dec 2021 06:19:28 +0000 Subject: [PATCH 08/17] remove witness touching from opCall: this will go in evm.go --- core/vm/instructions.go | 23 ----------------------- 1 file changed, 23 deletions(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 5c0d74ad0ab0..083643b370a2 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -757,29 +757,6 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) sourceAddrBytes := scope.Contract.Address().Bytes() - if interpreter.evm.Accesses != nil { - // touch the balance - index := trieUtils.GetTreeKeyAccountLeaf(sourceAddrBytes, trieUtils.BalanceLeafKey) - balanceBytes := padBigInt(interpreter.evm.StateDB.GetBalance(scope.Contract.Address())) - interpreter.evm.Accesses.TouchAddress(index, balanceBytes[:]) - // touch the nonce - index = trieUtils.GetTreeKeyAccountLeaf(sourceAddrBytes, trieUtils.BalanceLeafKey) - nonceBytes := [32]byte{} - nonce := interpreter.evm.StateDB.GetNonce(scope.Contract.Address()) - binary.BigEndian.PutUint64(nonceBytes[24:32], nonce) - interpreter.evm.Accesses.TouchAddress(index, nonceBytes[:]) - // touch the code hash - index = trieUtils.GetTreeKeyAccountLeaf(sourceAddrBytes, trieUtils.CodeKeccakLeafKey) - codeHash := interpreter.evm.StateDB.GetCodeHash(scope.Contract.Address()) - interpreter.evm.Accesses.TouchAddress(index, codeHash[:]) - // set the code size - index = trieUtils.GetTreeKeyAccountLeaf(sourceAddrBytes, trieUtils.CodeSizeLeafKey) - codeSize := uint64(interpreter.evm.StateDB.GetCodeSize(scope.Contract.Address())) - codeSizeLeafBytes := [32]byte{} - binary.BigEndian.PutUint64(codeSizeLeafBytes[24:32], codeSize) - interpreter.evm.Accesses.TouchAddress(index, codeSizeLeafBytes[:]) - } - var bigVal = big0 //TODO: use uint256.Int instead of converting with toBig() // By using big0 here, we save an alloc for the most common case (non-ether-transferring contract calls), From e3d935d2129b08ef2216bc694541fc529fed7f71 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 13 Dec 2021 06:22:14 +0000 Subject: [PATCH 09/17] remove witness touching for call from gas_table.go --- core/vm/gas_table.go | 15 --------------- core/vm/instructions.go | 2 -- 2 files changed, 17 deletions(-) diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 9bd0b62497ff..66ecdaa0733e 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -403,21 +403,6 @@ func gasCall(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize return 0, ErrGasUintOverflow } - sourceAddrBytes := contract.Address().Bytes() - if evm.Accesses != nil { - // touch the balance - index := trieUtils.GetTreeKeyAccountLeaf(sourceAddrBytes, trieUtils.BalanceLeafKey) - gas += evm.Accesses.TouchAddressAndChargeGas(index, nil) - // touch the nonce - index = trieUtils.GetTreeKeyAccountLeaf(sourceAddrBytes, trieUtils.BalanceLeafKey) - gas += evm.Accesses.TouchAddressAndChargeGas(index, nil) - // touch the code hash - index = trieUtils.GetTreeKeyAccountLeaf(sourceAddrBytes, trieUtils.CodeKeccakLeafKey) - gas += evm.Accesses.TouchAddressAndChargeGas(index, nil) - // touch the code size - index = trieUtils.GetTreeKeyAccountLeaf(sourceAddrBytes, trieUtils.CodeSizeLeafKey) - gas += evm.Accesses.TouchAddressAndChargeGas(index, nil) - } return gas, nil } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 083643b370a2..b4cc4accc460 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -766,8 +766,6 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt bigVal = value.ToBig() } - - ret, returnGas, err := interpreter.evm.Call(scope.Contract, toAddr, args, gas, bigVal) if err != nil { From c08aa4e316abea8d3815bf49e750f8c5aa41bb3b Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Mon, 13 Dec 2021 08:30:29 +0000 Subject: [PATCH 10/17] (hopefully) fix tests --- core/vm/gas_table.go | 8 ++++---- core/vm/instructions.go | 33 ++++++++++----------------------- 2 files changed, 14 insertions(+), 27 deletions(-) diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 66ecdaa0733e..5b797cb79b6a 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -88,10 +88,10 @@ func memoryCopierGas(stackpos int) gasFunc { } var ( - gasCallDataCopy = memoryCopierGas(2) - gasCodeCopyStateful = memoryCopierGas(2) - gasExtCodeCopyStateful = memoryCopierGas(3) - gasReturnDataCopy = memoryCopierGas(2) + gasCallDataCopy = memoryCopierGas(2) + gasCodeCopyStateful = memoryCopierGas(2) + gasExtCodeCopyStateful = memoryCopierGas(3) + gasReturnDataCopy = memoryCopierGas(2) ) func gasCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { diff --git a/core/vm/instructions.go b/core/vm/instructions.go index b4cc4accc460..f057f8d518c7 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -17,9 +17,6 @@ package vm import ( - "math/big" - "encoding/binary" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/params" @@ -383,7 +380,7 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ // touchEachChunksAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs func touchEachChunksAndChargeGas(offset, size uint64, address []byte, contract *Contract, accesses *types.AccessWitness) uint64 { - if size == 0 || offset > uint64(len(contract.Code)) { + if contract != nil && (size == 0 || offset > uint64(len(contract.Code))) { return 0 } @@ -394,17 +391,20 @@ func touchEachChunksAndChargeGas(offset, size uint64, address []byte, contract * // code does not fill the last leaf, 'end' is the final byte of contract code // in the last leaf that is touched start := offset - (offset % 31) - var end uint64 + var endOffset uint64 if contract != nil && start+size > uint64(len(contract.Code)) { - end = uint64(len(contract.Code)) + endOffset = uint64(len(contract.Code)) } else { - end = start + size + (start + size) % 31 + endOffset = start + size } + var code []byte if contract != nil { code = contract.Code[:] } - numLeaves := (end - start) / 31 + // the EVM code offset of the last byte in the last leaf touched + endLeafOffset := endOffset + (endOffset % 31) + numLeaves := (endLeafOffset - start) / 31 index := make([]byte, 32, 32) chunkOffset := new(uint256.Int) @@ -432,8 +432,8 @@ func touchEachChunksAndChargeGas(offset, size uint64, address []byte, contract * for ; firstPushOffset < 31 && firstPushOffset+uint64(i)*31 < uint64(len(contract.Code)) && !contract.IsCode(uint64(i)*31+firstPushOffset); firstPushOffset++ { } curEnd := (uint64(i) + 1) * 31 - if curEnd > end { - curEnd = end + if curEnd > endOffset { + curEnd = endOffset } valueSize := curEnd - (uint64(i) * 31) value = make([]byte, 32, 32) @@ -732,18 +732,6 @@ func opCreate2(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([] return nil, nil } -// TODO there should be an already-existing method to do this -func padBigInt(val *big.Int) [32]byte { - valBytes := val.Bytes() - if len(valBytes) > 32 { - panic("value too big") - } - - result := [32]byte{} - copy(result[32-len(valBytes):], valBytes[:]) - return result -} - func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { stack := scope.Stack // Pop gas. The actual gas in interpreter.evm.callGasTemp. @@ -755,7 +743,6 @@ func opCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt toAddr := common.Address(addr.Bytes20()) // Get the arguments from the memory. args := scope.Memory.GetPtr(int64(inOffset.Uint64()), int64(inSize.Uint64())) - sourceAddrBytes := scope.Contract.Address().Bytes() var bigVal = big0 //TODO: use uint256.Int instead of converting with toBig() From c54a54b2e89262c7b54342e71eb3b08cd7e34227 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Tue, 14 Dec 2021 02:40:04 +0000 Subject: [PATCH 11/17] add SSTORE witness charging that was removed mistakenly --- core/vm/gas_table.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 5b797cb79b6a..34c7d8c61fef 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -139,6 +139,8 @@ func gasSLoad(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySiz } func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + // Apply the witness access costs, err is nil + accessGas, _ := gasSLoad(evm, contract, stack, mem, memorySize) var ( y, x = stack.Back(1), stack.Back(0) current = evm.StateDB.GetState(contract.Address(), x.Bytes32()) @@ -154,12 +156,12 @@ func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySi // 3. From a non-zero to a non-zero (CHANGE) switch { case current == (common.Hash{}) && y.Sign() != 0: // 0 => non 0 - return params.SstoreSetGas, nil + return params.SstoreSetGas + accessGas, nil case current != (common.Hash{}) && y.Sign() == 0: // non 0 => 0 evm.StateDB.AddRefund(params.SstoreRefundGas) - return params.SstoreClearGas, nil + return params.SstoreClearGas + accessGas, nil default: // non 0 => non 0 (or 0 => 0) - return params.SstoreResetGas, nil + return params.SstoreResetGas + accessGas, nil } } // The new gas metering is based on net gas costs (EIP-1283): From a834f7267b3ad9f2475177507529b551573aceac Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Tue, 14 Dec 2021 02:56:54 +0000 Subject: [PATCH 12/17] charge witness gas for call --- core/vm/evm.go | 33 ++++++++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 81570ed86ea5..587e93683780 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -168,6 +168,19 @@ func (evm *EVM) Interpreter() *EVMInterpreter { return evm.interpreter } +// 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 +} + // 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 @@ -233,13 +246,23 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if evm.Accesses != nil { // Touch the account data var data [32]byte - evm.Accesses.TouchAddress(utils.GetTreeKeyVersion(addr.Bytes()), data[:]) + if !tryConsumeGas(&gas, evm.Accesses.TouchAddressAndChargeGas(utils.GetTreeKeyVersion(addr.Bytes()), data[:])) { + return nil, 0, ErrOutOfGas + } 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()) + if !tryConsumeGas(&gas, evm.Accesses.TouchAddressAndChargeGas(utils.GetTreeKeyNonce(addr[:]), data[:])) { + return nil, 0, ErrOutOfGas + } + if !tryConsumeGas(&gas, evm.Accesses.TouchAddressAndChargeGas(utils.GetTreeKeyBalance(addr[:]), evm.StateDB.GetBalance(addr).Bytes())) { + return nil, 0, ErrOutOfGas + } 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()) + if !tryConsumeGas(&gas, evm.Accesses.TouchAddressAndChargeGas(utils.GetTreeKeyCodeSize(addr[:]), data[:])) { + return nil, 0, ErrOutOfGas + } + if !tryConsumeGas(&gas, evm.Accesses.TouchAddressAndChargeGas(utils.GetTreeKeyCodeKeccak(addr[:]), evm.StateDB.GetCodeHash(addr).Bytes())) { + return nil, 0, ErrOutOfGas + } } addrCopy := addr From 155949a0de12a2b42fc7a1fa9d04997ce0a14331 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Tue, 14 Dec 2021 03:16:38 +0000 Subject: [PATCH 13/17] clean up and comment touchEachChunksAndChargeGas --- core/vm/instructions.go | 43 ++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 22 deletions(-) diff --git a/core/vm/instructions.go b/core/vm/instructions.go index f057f8d518c7..f2c616cc77ae 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -383,30 +383,29 @@ func touchEachChunksAndChargeGas(offset, size uint64, address []byte, contract * if contract != nil && (size == 0 || offset > uint64(len(contract.Code))) { return 0 } - - var statelessGasCharged uint64 - // start:end encompasses the range between the offset of - // the first byte in the first leaf of the code range that is touched - // and the last byte in the last leaf that is touched. If the contract - // code does not fill the last leaf, 'end' is the final byte of contract code - // in the last leaf that is touched - start := offset - (offset % 31) - var endOffset uint64 - if contract != nil && start+size > uint64(len(contract.Code)) { - endOffset = uint64(len(contract.Code)) - } else { - endOffset = start + size - } - - var code []byte + var ( + statelessGasCharged uint64 + startLeafOffset uint64 + endLeafOffset uint64 + startOffset uint64 + endOffset uint64 + numLeaves uint64 + code []byte + index [32]byte + ) if contract != nil { code = contract.Code[:] } - // the EVM code offset of the last byte in the last leaf touched - endLeafOffset := endOffset + (endOffset % 31) - numLeaves := (endLeafOffset - start) / 31 - index := make([]byte, 32, 32) - + // startLeafOffset, endLeafOffset is the evm code offset of the first byte in the first leaf touched + // and the evm code offset of the last byte in the last leaf touched + startOffset = offset - (offset % 31) + if contract != nil && startOffset+size > uint64(len(contract.Code)) { + endOffset = uint64(len(contract.Code)) + } else { + endOffset = startOffset + size + } + endLeafOffset = endOffset + (endOffset % 31) + numLeaves = (endLeafOffset - startLeafOffset) / 31 chunkOffset := new(uint256.Int) treeIndex := new(uint256.Int) @@ -446,7 +445,7 @@ func touchEachChunksAndChargeGas(offset, size uint64, address []byte, contract * } } - statelessGasCharged += accesses.TouchAddressAndChargeGas(index, value) + statelessGasCharged += accesses.TouchAddressAndChargeGas(index[:], value) } return statelessGasCharged From c0ca6ccefae4b57ee96f018a46c8bc9ebcfa1804 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Tue, 14 Dec 2021 22:48:25 +0000 Subject: [PATCH 14/17] make suggested changes --- core/vm/gas_table.go | 19 +++++++++++++++++++ core/vm/jump_table.go | 2 ++ 2 files changed, 21 insertions(+) diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 34c7d8c61fef..2050e1b61920 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -94,6 +94,17 @@ var ( gasReturnDataCopy = memoryCopierGas(2) ) +func gasExtCodeSize(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { + usedGas := uint64(0) + slot := stack.Back(0) + if evm.Accesses != nil { + index := trieUtils.GetTreeKeyCodeSize(slot.Bytes()) + usedGas += evm.TxContext.Accesses.TouchAddressAndChargeGas(index, nil) + } + + return usedGas, nil +} + func gasCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var statelessGas uint64 if evm.Accesses != nil { @@ -378,6 +389,14 @@ 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) { gas += params.CallNewAccountGas diff --git a/core/vm/jump_table.go b/core/vm/jump_table.go index 329ad77cbf83..15250f15a8d0 100644 --- a/core/vm/jump_table.go +++ b/core/vm/jump_table.go @@ -433,6 +433,7 @@ func newFrontierInstructionSet() JumpTable { EXTCODESIZE: { execute: opExtCodeSize, constantGas: params.ExtcodeSizeGasFrontier, + dynamicGas: gasExtCodeSize, minStack: minStack(1, 1), maxStack: maxStack(1, 1), }, @@ -513,6 +514,7 @@ func newFrontierInstructionSet() JumpTable { SLOAD: { execute: opSload, constantGas: params.SloadGasFrontier, + dynamicGas: gasSLoad, minStack: minStack(1, 1), maxStack: maxStack(1, 1), }, From 16099e31e6cf179eb6d47cc0e895c395ae604557 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Tue, 14 Dec 2021 23:10:20 +0000 Subject: [PATCH 15/17] address remaining points --- core/vm/gas_table.go | 20 +++++++++++++++++++- core/vm/instructions.go | 8 +++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 2050e1b61920..393934d8e064 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -130,7 +130,25 @@ func gasCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error) { var statelessGas uint64 if evm.Accesses != nil { - panic("extcodecopy not implemented for verkle") + var ( + codeOffset = stack.Back(2) + length = stack.Back(3) + ) + uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow() + if overflow { + uint64CodeOffset = 0xffffffffffffffff + } + uint64Length, overflow := length.Uint64WithOverflow() + if overflow { + uint64Length = 0xffffffffffffffff + } + // note: we must charge witness costs for the specified range regardless of whether it + // is in-bounds of the actual target account code. This is because we must charge the cost + // before hitting the db to be able to now what the actual code size is. This is different + // behavior from CODECOPY which only charges witness access costs for the part of the range + // which overlaps in the account code. TODO: clarify this is desired behavior and amend the + // spec. + statelessGas = touchEachChunksAndChargeGas(offset, nonPaddedSize, nil, nil, evm.Accesses) } usedGas, err := gasExtCodeCopyStateful(evm, contract, stack, mem, memorySize) return usedGas + statelessGas, err diff --git a/core/vm/instructions.go b/core/vm/instructions.go index f2c616cc77ae..5ec4f88989e1 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -380,6 +380,12 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([ // touchEachChunksAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs func touchEachChunksAndChargeGas(offset, size uint64, address []byte, contract *Contract, accesses *types.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 contract != nil && (size == 0 || offset > uint64(len(contract.Code))) { return 0 } @@ -465,7 +471,7 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) } addr := common.Address(a.Bytes20()) if interpreter.evm.Accesses != nil { - panic("extcodecopy not implemented for verkle") + log.Warn("setting witness values for extcodecopy is not currently implemented") } else { codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64()) scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy) From ea37fd7d7b89cdef1b5a4277829d2bf1ab8743e9 Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Tue, 14 Dec 2021 23:14:51 +0000 Subject: [PATCH 16/17] fix build issues --- core/vm/gas_table.go | 2 +- core/vm/instructions.go | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/core/vm/gas_table.go b/core/vm/gas_table.go index 393934d8e064..7fe7b92e8bb8 100644 --- a/core/vm/gas_table.go +++ b/core/vm/gas_table.go @@ -148,7 +148,7 @@ func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem // behavior from CODECOPY which only charges witness access costs for the part of the range // which overlaps in the account code. TODO: clarify this is desired behavior and amend the // spec. - statelessGas = touchEachChunksAndChargeGas(offset, nonPaddedSize, nil, nil, evm.Accesses) + statelessGas = touchEachChunksAndChargeGas(uint64CodeOffset, uint64Length, nil, nil, evm.Accesses) } usedGas, err := gasExtCodeCopyStateful(evm, contract, stack, mem, memorySize) return usedGas + statelessGas, err diff --git a/core/vm/instructions.go b/core/vm/instructions.go index 5ec4f88989e1..4440aec4b44c 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -19,6 +19,7 @@ package vm import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/params" trieUtils "github.com/ethereum/go-ethereum/trie/utils" "github.com/holiman/uint256" From 624fa3067808a35aec7d17b61aa3239cb1d366ce Mon Sep 17 00:00:00 2001 From: Jared Wasinger Date: Wed, 15 Dec 2021 23:03:26 +0000 Subject: [PATCH 17/17] remove double-charging for contract creation witness gas charging --- core/vm/evm.go | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/core/vm/evm.go b/core/vm/evm.go index 587e93683780..3cb0a0ee53e8 100644 --- a/core/vm/evm.go +++ b/core/vm/evm.go @@ -246,23 +246,13 @@ func (evm *EVM) Call(caller ContractRef, addr common.Address, input []byte, gas if evm.Accesses != nil { // Touch the account data var data [32]byte - if !tryConsumeGas(&gas, evm.Accesses.TouchAddressAndChargeGas(utils.GetTreeKeyVersion(addr.Bytes()), data[:])) { - return nil, 0, ErrOutOfGas - } + evm.Accesses.TouchAddress(utils.GetTreeKeyVersion(addr.Bytes()), data[:]) binary.BigEndian.PutUint64(data[:], evm.StateDB.GetNonce(addr)) - if !tryConsumeGas(&gas, evm.Accesses.TouchAddressAndChargeGas(utils.GetTreeKeyNonce(addr[:]), data[:])) { - return nil, 0, ErrOutOfGas - } - if !tryConsumeGas(&gas, evm.Accesses.TouchAddressAndChargeGas(utils.GetTreeKeyBalance(addr[:]), evm.StateDB.GetBalance(addr).Bytes())) { - return nil, 0, ErrOutOfGas - } + 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))) - if !tryConsumeGas(&gas, evm.Accesses.TouchAddressAndChargeGas(utils.GetTreeKeyCodeSize(addr[:]), data[:])) { - return nil, 0, ErrOutOfGas - } - if !tryConsumeGas(&gas, evm.Accesses.TouchAddressAndChargeGas(utils.GetTreeKeyCodeKeccak(addr[:]), evm.StateDB.GetCodeHash(addr).Bytes())) { - return nil, 0, ErrOutOfGas - } + evm.Accesses.TouchAddress(utils.GetTreeKeyCodeSize(addr[:]), data[:]) + evm.Accesses.TouchAddress(utils.GetTreeKeyCodeKeccak(addr[:]), evm.StateDB.GetCodeHash(addr).Bytes()) } addrCopy := addr