Verkle EXTCODECOPY implementation (#55)
* core/vm: verkle extcodecopy naive way (do jumpdest analysis on target contract every EXTCODECOPY) * no double-charge * address edge-case in touchEachChunksAndChargeGas * simplify line Co-authored-by: Guillaume Ballet <3272758+gballet@users.noreply.github.com>
This commit is contained in:
parent
d761880fd2
commit
952be80177
@ -61,8 +61,8 @@ func (bits bitvec) set16(pos uint64) {
|
|||||||
bits[pos/8+2] = ^a
|
bits[pos/8+2] = ^a
|
||||||
}
|
}
|
||||||
|
|
||||||
// codeSegment checks if the position is in a code segment.
|
// IsCode checks if the position is in a code segment.
|
||||||
func (bits *bitvec) codeSegment(pos uint64) bool {
|
func (bits *bitvec) IsCode(pos uint64) bool {
|
||||||
return ((*bits)[pos/8] & (0x80 >> (pos % 8))) == 0
|
return ((*bits)[pos/8] & (0x80 >> (pos % 8))) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +28,13 @@ type ContractRef interface {
|
|||||||
Address() common.Address
|
Address() common.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AnalyzedContract is an interface for a piece of contract
|
||||||
|
// code that has undegone jumpdest analysis, and whose bytecode
|
||||||
|
// can be queried to determine if it is "contract code"
|
||||||
|
type AnalyzedContract interface {
|
||||||
|
IsCode(dest uint64) bool
|
||||||
|
}
|
||||||
|
|
||||||
// AccountRef implements ContractRef.
|
// AccountRef implements ContractRef.
|
||||||
//
|
//
|
||||||
// Account references are used during EVM initialisation and
|
// Account references are used during EVM initialisation and
|
||||||
@ -104,7 +111,7 @@ func (c *Contract) validJumpdest(dest *uint256.Int) bool {
|
|||||||
func (c *Contract) IsCode(udest uint64) bool {
|
func (c *Contract) IsCode(udest uint64) bool {
|
||||||
// Do we already have an analysis laying around?
|
// Do we already have an analysis laying around?
|
||||||
if c.analysis != nil {
|
if c.analysis != nil {
|
||||||
return c.analysis.codeSegment(udest)
|
return c.analysis.IsCode(udest)
|
||||||
}
|
}
|
||||||
// Do we have a contract hash already?
|
// Do we have a contract hash already?
|
||||||
// If we do have a hash, that means it's a 'regular' contract. For regular
|
// If we do have a hash, that means it's a 'regular' contract. For regular
|
||||||
@ -120,7 +127,7 @@ func (c *Contract) IsCode(udest uint64) bool {
|
|||||||
}
|
}
|
||||||
// Also stash it in current contract for faster access
|
// Also stash it in current contract for faster access
|
||||||
c.analysis = analysis
|
c.analysis = analysis
|
||||||
return analysis.codeSegment(udest)
|
return analysis.IsCode(udest)
|
||||||
}
|
}
|
||||||
// We don't have the code hash, most likely a piece of initcode not already
|
// We don't have the code hash, most likely a piece of initcode not already
|
||||||
// in state trie. In that case, we do an analysis, and save it locally, so
|
// in state trie. In that case, we do an analysis, and save it locally, so
|
||||||
@ -129,7 +136,7 @@ func (c *Contract) IsCode(udest uint64) bool {
|
|||||||
if c.analysis == nil {
|
if c.analysis == nil {
|
||||||
c.analysis = codeBitmap(c.Code)
|
c.analysis = codeBitmap(c.Code)
|
||||||
}
|
}
|
||||||
return c.analysis.codeSegment(udest)
|
return c.analysis.IsCode(udest)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AsDelegate sets the contract to be a delegate call and returns the current
|
// AsDelegate sets the contract to be a delegate call and returns the current
|
||||||
|
@ -121,7 +121,7 @@ func gasCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memory
|
|||||||
uint64Length = 0xffffffffffffffff
|
uint64Length = 0xffffffffffffffff
|
||||||
}
|
}
|
||||||
_, offset, nonPaddedSize := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, uint64Length)
|
_, offset, nonPaddedSize := getDataAndAdjustedBounds(contract.Code, uint64CodeOffset, uint64Length)
|
||||||
statelessGas = touchEachChunksAndChargeGas(offset, nonPaddedSize, contract.Address().Bytes()[:], nil, evm.Accesses)
|
statelessGas = touchEachChunksAndChargeGas(offset, nonPaddedSize, contract.Address().Bytes()[:], nil, nil, evm.Accesses)
|
||||||
}
|
}
|
||||||
usedGas, err := gasCodeCopyStateful(evm, contract, stack, mem, memorySize)
|
usedGas, err := gasCodeCopyStateful(evm, contract, stack, mem, memorySize)
|
||||||
return usedGas + statelessGas, err
|
return usedGas + statelessGas, err
|
||||||
@ -133,6 +133,7 @@ func gasExtCodeCopy(evm *EVM, contract *Contract, stack *Stack, mem *Memory, mem
|
|||||||
var (
|
var (
|
||||||
codeOffset = stack.Back(2)
|
codeOffset = stack.Back(2)
|
||||||
length = stack.Back(3)
|
length = stack.Back(3)
|
||||||
|
targetAddr = stack.Back(0).Bytes20()
|
||||||
)
|
)
|
||||||
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
|
uint64CodeOffset, overflow := codeOffset.Uint64WithOverflow()
|
||||||
if overflow {
|
if overflow {
|
||||||
@ -148,7 +149,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
|
// 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
|
// which overlaps in the account code. TODO: clarify this is desired behavior and amend the
|
||||||
// spec.
|
// spec.
|
||||||
statelessGas = touchEachChunksAndChargeGas(uint64CodeOffset, uint64Length, nil, nil, evm.Accesses)
|
statelessGas = touchEachChunksAndChargeGas(uint64CodeOffset, uint64Length, targetAddr[:], nil, nil, evm.Accesses)
|
||||||
}
|
}
|
||||||
usedGas, err := gasExtCodeCopyStateful(evm, contract, stack, mem, memorySize)
|
usedGas, err := gasExtCodeCopyStateful(evm, contract, stack, mem, memorySize)
|
||||||
return usedGas + statelessGas, err
|
return usedGas + statelessGas, err
|
||||||
|
@ -19,7 +19,6 @@ package vm
|
|||||||
import (
|
import (
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/core/types"
|
"github.com/ethereum/go-ethereum/core/types"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
"github.com/ethereum/go-ethereum/params"
|
||||||
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
|
trieUtils "github.com/ethereum/go-ethereum/trie/utils"
|
||||||
"github.com/holiman/uint256"
|
"github.com/holiman/uint256"
|
||||||
@ -373,21 +372,21 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
|
|||||||
|
|
||||||
paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64())
|
paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(scope.Contract.Code, uint64CodeOffset, length.Uint64())
|
||||||
if interpreter.evm.Accesses != nil {
|
if interpreter.evm.Accesses != nil {
|
||||||
touchEachChunksAndChargeGas(copyOffset, nonPaddedCopyLength, scope.Contract.Address().Bytes()[:], scope.Contract, interpreter.evm.Accesses)
|
touchEachChunksAndChargeGas(copyOffset, nonPaddedCopyLength, scope.Contract.Address().Bytes()[:], scope.Contract.Code, scope.Contract, interpreter.evm.Accesses)
|
||||||
}
|
}
|
||||||
scope.Memory.Set(memOffset.Uint64(), uint64(len(paddedCodeCopy)), paddedCodeCopy)
|
scope.Memory.Set(memOffset.Uint64(), uint64(len(paddedCodeCopy)), paddedCodeCopy)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// touchEachChunksAndChargeGas is a helper function to touch every chunk in a code range and charge witness gas costs
|
// 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 {
|
func touchEachChunksAndChargeGas(offset, size uint64, address []byte, code []byte, contract AnalyzedContract, accesses *types.AccessWitness) uint64 {
|
||||||
// note that in the case where the copied code is outside the range of the
|
// 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,
|
// 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
|
// 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
|
// 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
|
// is already in the AccessWitness so a stateless verifier can see that
|
||||||
// the code from the last leaf is not needed.
|
// the code from the last leaf is not needed.
|
||||||
if contract != nil && (size == 0 || offset > uint64(len(contract.Code))) {
|
if contract != nil && (size == 0 || offset > uint64(len(code))) {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
var (
|
var (
|
||||||
@ -397,17 +396,13 @@ func touchEachChunksAndChargeGas(offset, size uint64, address []byte, contract *
|
|||||||
startOffset uint64
|
startOffset uint64
|
||||||
endOffset uint64
|
endOffset uint64
|
||||||
numLeaves uint64
|
numLeaves uint64
|
||||||
code []byte
|
|
||||||
index [32]byte
|
index [32]byte
|
||||||
)
|
)
|
||||||
if contract != nil {
|
|
||||||
code = contract.Code[:]
|
|
||||||
}
|
|
||||||
// startLeafOffset, endLeafOffset is the evm code offset of the first byte in the first leaf touched
|
// 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
|
// and the evm code offset of the last byte in the last leaf touched
|
||||||
startOffset = offset - (offset % 31)
|
startOffset = offset - (offset % 31)
|
||||||
if contract != nil && startOffset+size > uint64(len(contract.Code)) {
|
if contract != nil && startOffset+size > uint64(len(code)) {
|
||||||
endOffset = uint64(len(contract.Code))
|
endOffset = uint64(len(code))
|
||||||
} else {
|
} else {
|
||||||
endOffset = startOffset + size
|
endOffset = startOffset + size
|
||||||
}
|
}
|
||||||
@ -431,11 +426,11 @@ func touchEachChunksAndChargeGas(offset, size uint64, address []byte, contract *
|
|||||||
index[31] = subIndex
|
index[31] = subIndex
|
||||||
|
|
||||||
var value []byte
|
var value []byte
|
||||||
if contract != nil {
|
if len(code) > 0 {
|
||||||
// the offset into the leaf that the first PUSH occurs
|
// the offset into the leaf that the first PUSH occurs
|
||||||
var firstPushOffset uint64 = 0
|
var firstPushOffset uint64 = 0
|
||||||
// Look for the first code byte (i.e. no pushdata)
|
// 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++ {
|
for ; firstPushOffset < 31 && firstPushOffset+uint64(i)*31 < uint64(len(code)) && !contract.IsCode(uint64(i)*31+firstPushOffset); firstPushOffset++ {
|
||||||
}
|
}
|
||||||
curEnd := (uint64(i) + 1) * 31
|
curEnd := (uint64(i) + 1) * 31
|
||||||
if curEnd > endOffset {
|
if curEnd > endOffset {
|
||||||
@ -472,7 +467,11 @@ func opExtCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
|
|||||||
}
|
}
|
||||||
addr := common.Address(a.Bytes20())
|
addr := common.Address(a.Bytes20())
|
||||||
if interpreter.evm.Accesses != nil {
|
if interpreter.evm.Accesses != nil {
|
||||||
log.Warn("setting witness values for extcodecopy is not currently implemented")
|
code := interpreter.evm.StateDB.GetCode(addr)
|
||||||
|
paddedCodeCopy, copyOffset, nonPaddedCopyLength := getDataAndAdjustedBounds(code, uint64CodeOffset, length.Uint64())
|
||||||
|
cb := codeBitmap(code)
|
||||||
|
touchEachChunksAndChargeGas(copyOffset, nonPaddedCopyLength, addr[:], code, &cb, interpreter.evm.Accesses)
|
||||||
|
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), paddedCodeCopy)
|
||||||
} else {
|
} else {
|
||||||
codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64())
|
codeCopy := getData(interpreter.evm.StateDB.GetCode(addr), uint64CodeOffset, length.Uint64())
|
||||||
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
|
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
|
||||||
@ -977,7 +976,7 @@ func makePush(size uint64, pushByteSize int) executionFunc {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if interpreter.evm.Accesses != nil {
|
if interpreter.evm.Accesses != nil {
|
||||||
statelessGas := touchEachChunksAndChargeGas(uint64(startMin), uint64(pushByteSize), scope.Contract.Address().Bytes()[:], scope.Contract, interpreter.evm.Accesses)
|
statelessGas := touchEachChunksAndChargeGas(uint64(startMin), uint64(pushByteSize), scope.Contract.Address().Bytes()[:], scope.Contract.Code, scope.Contract, interpreter.evm.Accesses)
|
||||||
scope.Contract.UseGas(statelessGas)
|
scope.Contract.UseGas(statelessGas)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -194,7 +194,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool) (
|
|||||||
if in.evm.TxContext.Accesses != nil && !contract.IsDeployment {
|
if in.evm.TxContext.Accesses != nil && !contract.IsDeployment {
|
||||||
// if the PC ends up in a new "page" of verkleized code, charge the
|
// if the PC ends up in a new "page" of verkleized code, charge the
|
||||||
// associated witness costs.
|
// associated witness costs.
|
||||||
contract.Gas -= touchEachChunksAndChargeGas(pc, 1, contract.Address().Bytes()[:], contract, in.evm.TxContext.Accesses)
|
contract.Gas -= touchEachChunksAndChargeGas(pc, 1, contract.Address().Bytes()[:], contract.Code, contract, in.evm.TxContext.Accesses)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO how can we tell if we are in stateless mode here and need to get the op from the witness
|
// TODO how can we tell if we are in stateless mode here and need to get the op from the witness
|
||||||
|
Loading…
x
Reference in New Issue
Block a user