mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-07-25 22:26:42 +00:00
core/vm: remove EOF
This commit is contained in:
parent
0983cd789e
commit
c52616459c
11 changed files with 0 additions and 2114 deletions
|
|
@ -1,98 +0,0 @@
|
||||||
// Copyright 2024 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package vm
|
|
||||||
|
|
||||||
// eofCodeBitmap collects data locations in code.
|
|
||||||
func eofCodeBitmap(code []byte) bitvec {
|
|
||||||
// The bitmap is 4 bytes longer than necessary, in case the code
|
|
||||||
// ends with a PUSH32, the algorithm will push zeroes onto the
|
|
||||||
// bitvector outside the bounds of the actual code.
|
|
||||||
bits := make(bitvec, len(code)/8+1+4)
|
|
||||||
return eofCodeBitmapInternal(code, bits)
|
|
||||||
}
|
|
||||||
|
|
||||||
// eofCodeBitmapInternal is the internal implementation of codeBitmap for EOF
|
|
||||||
// code validation.
|
|
||||||
func eofCodeBitmapInternal(code, bits bitvec) bitvec {
|
|
||||||
for pc := uint64(0); pc < uint64(len(code)); {
|
|
||||||
var (
|
|
||||||
op = OpCode(code[pc])
|
|
||||||
numbits uint16
|
|
||||||
)
|
|
||||||
pc++
|
|
||||||
|
|
||||||
if op == RJUMPV {
|
|
||||||
// RJUMPV is unique as it has a variable sized operand.
|
|
||||||
// The total size is determined by the count byte which
|
|
||||||
// immediate follows RJUMPV. Truncation will be caught
|
|
||||||
// in other validation steps -- for now, just return a
|
|
||||||
// valid bitmap for as much of the code as is
|
|
||||||
// available.
|
|
||||||
end := uint64(len(code))
|
|
||||||
if pc >= end {
|
|
||||||
// Count missing, no more bits to mark.
|
|
||||||
return bits
|
|
||||||
}
|
|
||||||
numbits = uint16(code[pc])*2 + 3
|
|
||||||
if pc+uint64(numbits) > end {
|
|
||||||
// Jump table is truncated, mark as many bits
|
|
||||||
// as possible.
|
|
||||||
numbits = uint16(end - pc)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
numbits = uint16(Immediates(op))
|
|
||||||
if numbits == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if numbits >= 8 {
|
|
||||||
for ; numbits >= 16; numbits -= 16 {
|
|
||||||
bits.set16(pc)
|
|
||||||
pc += 16
|
|
||||||
}
|
|
||||||
for ; numbits >= 8; numbits -= 8 {
|
|
||||||
bits.set8(pc)
|
|
||||||
pc += 8
|
|
||||||
}
|
|
||||||
}
|
|
||||||
switch numbits {
|
|
||||||
case 1:
|
|
||||||
bits.set1(pc)
|
|
||||||
pc += 1
|
|
||||||
case 2:
|
|
||||||
bits.setN(set2BitsMask, pc)
|
|
||||||
pc += 2
|
|
||||||
case 3:
|
|
||||||
bits.setN(set3BitsMask, pc)
|
|
||||||
pc += 3
|
|
||||||
case 4:
|
|
||||||
bits.setN(set4BitsMask, pc)
|
|
||||||
pc += 4
|
|
||||||
case 5:
|
|
||||||
bits.setN(set5BitsMask, pc)
|
|
||||||
pc += 5
|
|
||||||
case 6:
|
|
||||||
bits.setN(set6BitsMask, pc)
|
|
||||||
pc += 6
|
|
||||||
case 7:
|
|
||||||
bits.setN(set7BitsMask, pc)
|
|
||||||
pc += 7
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return bits
|
|
||||||
}
|
|
||||||
|
|
@ -105,31 +105,3 @@ func BenchmarkJumpdestOpAnalysis(bench *testing.B) {
|
||||||
op = STOP
|
op = STOP
|
||||||
bench.Run(op.String(), bencher)
|
bench.Run(op.String(), bencher)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkJumpdestOpEOFAnalysis(bench *testing.B) {
|
|
||||||
var op OpCode
|
|
||||||
bencher := func(b *testing.B) {
|
|
||||||
code := make([]byte, analysisCodeSize)
|
|
||||||
b.SetBytes(analysisCodeSize)
|
|
||||||
for i := range code {
|
|
||||||
code[i] = byte(op)
|
|
||||||
}
|
|
||||||
bits := make(bitvec, len(code)/8+1+4)
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
clear(bits)
|
|
||||||
eofCodeBitmapInternal(code, bits)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for op = PUSH1; op <= PUSH32; op++ {
|
|
||||||
bench.Run(op.String(), bencher)
|
|
||||||
}
|
|
||||||
op = JUMPDEST
|
|
||||||
bench.Run(op.String(), bencher)
|
|
||||||
op = STOP
|
|
||||||
bench.Run(op.String(), bencher)
|
|
||||||
op = RJUMPV
|
|
||||||
bench.Run(op.String(), bencher)
|
|
||||||
op = EOFCREATE
|
|
||||||
bench.Run(op.String(), bencher)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
170
core/vm/eips.go
170
core/vm/eips.go
|
|
@ -531,176 +531,6 @@ func enable4762(jt *JumpTable) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// enableEOF applies the EOF changes.
|
|
||||||
// OBS! For EOF, there are two changes:
|
|
||||||
// 1. Two separate jumptables are required. One, EOF-jumptable, is used by
|
|
||||||
// eof contracts. This one contains things like RJUMP.
|
|
||||||
// 2. The regular non-eof jumptable also needs to be modified, specifically to
|
|
||||||
// modify how EXTCODECOPY works under the hood.
|
|
||||||
//
|
|
||||||
// This method _only_ deals with case 1.
|
|
||||||
func enableEOF(jt *JumpTable) {
|
|
||||||
// Deprecate opcodes
|
|
||||||
undefined := &operation{
|
|
||||||
execute: opUndefined,
|
|
||||||
constantGas: 0,
|
|
||||||
minStack: minStack(0, 0),
|
|
||||||
maxStack: maxStack(0, 0),
|
|
||||||
undefined: true,
|
|
||||||
}
|
|
||||||
jt[CALL] = undefined
|
|
||||||
jt[CALLCODE] = undefined
|
|
||||||
jt[DELEGATECALL] = undefined
|
|
||||||
jt[STATICCALL] = undefined
|
|
||||||
jt[SELFDESTRUCT] = undefined
|
|
||||||
jt[JUMP] = undefined
|
|
||||||
jt[JUMPI] = undefined
|
|
||||||
jt[PC] = undefined
|
|
||||||
jt[CREATE] = undefined
|
|
||||||
jt[CREATE2] = undefined
|
|
||||||
jt[CODESIZE] = undefined
|
|
||||||
jt[CODECOPY] = undefined
|
|
||||||
jt[EXTCODESIZE] = undefined
|
|
||||||
jt[EXTCODECOPY] = undefined
|
|
||||||
jt[EXTCODEHASH] = undefined
|
|
||||||
jt[GAS] = undefined
|
|
||||||
// Allow 0xFE to terminate sections
|
|
||||||
jt[INVALID] = &operation{
|
|
||||||
execute: opUndefined,
|
|
||||||
constantGas: 0,
|
|
||||||
minStack: minStack(0, 0),
|
|
||||||
maxStack: maxStack(0, 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
// New opcodes
|
|
||||||
jt[RJUMP] = &operation{
|
|
||||||
execute: opRjump,
|
|
||||||
constantGas: GasQuickStep,
|
|
||||||
minStack: minStack(0, 0),
|
|
||||||
maxStack: maxStack(0, 0),
|
|
||||||
}
|
|
||||||
jt[RJUMPI] = &operation{
|
|
||||||
execute: opRjumpi,
|
|
||||||
constantGas: GasFastishStep,
|
|
||||||
minStack: minStack(1, 0),
|
|
||||||
maxStack: maxStack(1, 0),
|
|
||||||
}
|
|
||||||
jt[RJUMPV] = &operation{
|
|
||||||
execute: opRjumpv,
|
|
||||||
constantGas: GasFastishStep,
|
|
||||||
minStack: minStack(1, 0),
|
|
||||||
maxStack: maxStack(1, 0),
|
|
||||||
}
|
|
||||||
jt[CALLF] = &operation{
|
|
||||||
execute: opCallf,
|
|
||||||
constantGas: GasFastStep,
|
|
||||||
minStack: minStack(0, 0),
|
|
||||||
maxStack: maxStack(0, 0),
|
|
||||||
}
|
|
||||||
jt[RETF] = &operation{
|
|
||||||
execute: opRetf,
|
|
||||||
constantGas: GasFastestStep,
|
|
||||||
minStack: minStack(0, 0),
|
|
||||||
maxStack: maxStack(0, 0),
|
|
||||||
}
|
|
||||||
jt[JUMPF] = &operation{
|
|
||||||
execute: opJumpf,
|
|
||||||
constantGas: GasFastStep,
|
|
||||||
minStack: minStack(0, 0),
|
|
||||||
maxStack: maxStack(0, 0),
|
|
||||||
}
|
|
||||||
jt[EOFCREATE] = &operation{
|
|
||||||
execute: opEOFCreate,
|
|
||||||
constantGas: params.Create2Gas,
|
|
||||||
dynamicGas: gasEOFCreate,
|
|
||||||
minStack: minStack(4, 1),
|
|
||||||
maxStack: maxStack(4, 1),
|
|
||||||
memorySize: memoryEOFCreate,
|
|
||||||
}
|
|
||||||
jt[RETURNCONTRACT] = &operation{
|
|
||||||
execute: opReturnContract,
|
|
||||||
// returncontract has zero constant gas cost
|
|
||||||
dynamicGas: pureMemoryGascost,
|
|
||||||
minStack: minStack(2, 0),
|
|
||||||
maxStack: maxStack(2, 0),
|
|
||||||
memorySize: memoryReturnContract,
|
|
||||||
}
|
|
||||||
jt[DATALOAD] = &operation{
|
|
||||||
execute: opDataLoad,
|
|
||||||
constantGas: GasFastishStep,
|
|
||||||
minStack: minStack(1, 1),
|
|
||||||
maxStack: maxStack(1, 1),
|
|
||||||
}
|
|
||||||
jt[DATALOADN] = &operation{
|
|
||||||
execute: opDataLoadN,
|
|
||||||
constantGas: GasFastestStep,
|
|
||||||
minStack: minStack(0, 1),
|
|
||||||
maxStack: maxStack(0, 1),
|
|
||||||
}
|
|
||||||
jt[DATASIZE] = &operation{
|
|
||||||
execute: opDataSize,
|
|
||||||
constantGas: GasQuickStep,
|
|
||||||
minStack: minStack(0, 1),
|
|
||||||
maxStack: maxStack(0, 1),
|
|
||||||
}
|
|
||||||
jt[DATACOPY] = &operation{
|
|
||||||
execute: opDataCopy,
|
|
||||||
constantGas: GasFastestStep,
|
|
||||||
dynamicGas: memoryCopierGas(2),
|
|
||||||
minStack: minStack(3, 0),
|
|
||||||
maxStack: maxStack(3, 0),
|
|
||||||
memorySize: memoryDataCopy,
|
|
||||||
}
|
|
||||||
jt[DUPN] = &operation{
|
|
||||||
execute: opDupN,
|
|
||||||
constantGas: GasFastestStep,
|
|
||||||
minStack: minStack(0, 1),
|
|
||||||
maxStack: maxStack(0, 1),
|
|
||||||
}
|
|
||||||
jt[SWAPN] = &operation{
|
|
||||||
execute: opSwapN,
|
|
||||||
constantGas: GasFastestStep,
|
|
||||||
minStack: minStack(0, 0),
|
|
||||||
maxStack: maxStack(0, 0),
|
|
||||||
}
|
|
||||||
jt[EXCHANGE] = &operation{
|
|
||||||
execute: opExchange,
|
|
||||||
constantGas: GasFastestStep,
|
|
||||||
minStack: minStack(0, 0),
|
|
||||||
maxStack: maxStack(0, 0),
|
|
||||||
}
|
|
||||||
jt[RETURNDATALOAD] = &operation{
|
|
||||||
execute: opReturnDataLoad,
|
|
||||||
constantGas: GasFastestStep,
|
|
||||||
minStack: minStack(1, 1),
|
|
||||||
maxStack: maxStack(1, 1),
|
|
||||||
}
|
|
||||||
jt[EXTCALL] = &operation{
|
|
||||||
execute: opExtCall,
|
|
||||||
constantGas: params.WarmStorageReadCostEIP2929,
|
|
||||||
dynamicGas: makeCallVariantGasCallEIP2929(gasExtCall, 0),
|
|
||||||
minStack: minStack(4, 1),
|
|
||||||
maxStack: maxStack(4, 1),
|
|
||||||
memorySize: memoryExtCall,
|
|
||||||
}
|
|
||||||
jt[EXTDELEGATECALL] = &operation{
|
|
||||||
execute: opExtDelegateCall,
|
|
||||||
dynamicGas: makeCallVariantGasCallEIP2929(gasExtDelegateCall, 0),
|
|
||||||
constantGas: params.WarmStorageReadCostEIP2929,
|
|
||||||
minStack: minStack(3, 1),
|
|
||||||
maxStack: maxStack(3, 1),
|
|
||||||
memorySize: memoryExtCall,
|
|
||||||
}
|
|
||||||
jt[EXTSTATICCALL] = &operation{
|
|
||||||
execute: opExtStaticCall,
|
|
||||||
constantGas: params.WarmStorageReadCostEIP2929,
|
|
||||||
dynamicGas: makeCallVariantGasCallEIP2929(gasExtStaticCall, 0),
|
|
||||||
minStack: minStack(3, 1),
|
|
||||||
maxStack: maxStack(3, 1),
|
|
||||||
memorySize: memoryExtCall,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// enable7702 the EIP-7702 changes to support delegation designators.
|
// enable7702 the EIP-7702 changes to support delegation designators.
|
||||||
func enable7702(jt *JumpTable) {
|
func enable7702(jt *JumpTable) {
|
||||||
jt[CALL].dynamicGas = gasCallEIP7702
|
jt[CALL].dynamicGas = gasCallEIP7702
|
||||||
|
|
|
||||||
501
core/vm/eof.go
501
core/vm/eof.go
|
|
@ -1,501 +0,0 @@
|
||||||
// Copyright 2024 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package vm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
offsetVersion = 2
|
|
||||||
offsetTypesKind = 3
|
|
||||||
offsetCodeKind = 6
|
|
||||||
|
|
||||||
kindTypes = 1
|
|
||||||
kindCode = 2
|
|
||||||
kindContainer = 3
|
|
||||||
kindData = 4
|
|
||||||
|
|
||||||
eofFormatByte = 0xef
|
|
||||||
eof1Version = 1
|
|
||||||
|
|
||||||
maxInputItems = 127
|
|
||||||
maxOutputItems = 128
|
|
||||||
maxStackHeight = 1023
|
|
||||||
maxContainerSections = 256
|
|
||||||
)
|
|
||||||
|
|
||||||
var eofMagic = []byte{0xef, 0x00}
|
|
||||||
|
|
||||||
// HasEOFByte returns true if code starts with 0xEF byte
|
|
||||||
func HasEOFByte(code []byte) bool {
|
|
||||||
return len(code) != 0 && code[0] == eofFormatByte
|
|
||||||
}
|
|
||||||
|
|
||||||
// hasEOFMagic returns true if code starts with magic defined by EIP-3540
|
|
||||||
func hasEOFMagic(code []byte) bool {
|
|
||||||
return len(eofMagic) <= len(code) && bytes.Equal(eofMagic, code[0:len(eofMagic)])
|
|
||||||
}
|
|
||||||
|
|
||||||
// isEOFVersion1 returns true if the code's version byte equals eof1Version. It
|
|
||||||
// does not verify the EOF magic is valid.
|
|
||||||
func isEOFVersion1(code []byte) bool {
|
|
||||||
return 2 < len(code) && code[2] == byte(eof1Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Container is an EOF container object.
|
|
||||||
type Container struct {
|
|
||||||
types []*functionMetadata
|
|
||||||
codeSections [][]byte
|
|
||||||
subContainers []*Container
|
|
||||||
subContainerCodes [][]byte
|
|
||||||
data []byte
|
|
||||||
dataSize int // might be more than len(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// functionMetadata is an EOF function signature.
|
|
||||||
type functionMetadata struct {
|
|
||||||
inputs uint8
|
|
||||||
outputs uint8
|
|
||||||
maxStackHeight uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
// stackDelta returns the #outputs - #inputs
|
|
||||||
func (meta *functionMetadata) stackDelta() int {
|
|
||||||
return int(meta.outputs) - int(meta.inputs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkInputs checks the current minimum stack (stackMin) against the required inputs
|
|
||||||
// of the metadata, and returns an error if the stack is too shallow.
|
|
||||||
func (meta *functionMetadata) checkInputs(stackMin int) error {
|
|
||||||
if int(meta.inputs) > stackMin {
|
|
||||||
return ErrStackUnderflow{stackLen: stackMin, required: int(meta.inputs)}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkStackMax checks the if current maximum stack combined with the
|
|
||||||
// function max stack will result in a stack overflow, and if so returns an error.
|
|
||||||
func (meta *functionMetadata) checkStackMax(stackMax int) error {
|
|
||||||
newMaxStack := stackMax + int(meta.maxStackHeight) - int(meta.inputs)
|
|
||||||
if newMaxStack > int(params.StackLimit) {
|
|
||||||
return ErrStackOverflow{stackLen: newMaxStack, limit: int(params.StackLimit)}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// MarshalBinary encodes an EOF container into binary format.
|
|
||||||
func (c *Container) MarshalBinary() []byte {
|
|
||||||
// Build EOF prefix.
|
|
||||||
b := make([]byte, 2)
|
|
||||||
copy(b, eofMagic)
|
|
||||||
b = append(b, eof1Version)
|
|
||||||
|
|
||||||
// Write section headers.
|
|
||||||
b = append(b, kindTypes)
|
|
||||||
b = binary.BigEndian.AppendUint16(b, uint16(len(c.types)*4))
|
|
||||||
b = append(b, kindCode)
|
|
||||||
b = binary.BigEndian.AppendUint16(b, uint16(len(c.codeSections)))
|
|
||||||
for _, codeSection := range c.codeSections {
|
|
||||||
b = binary.BigEndian.AppendUint16(b, uint16(len(codeSection)))
|
|
||||||
}
|
|
||||||
var encodedContainer [][]byte
|
|
||||||
if len(c.subContainers) != 0 {
|
|
||||||
b = append(b, kindContainer)
|
|
||||||
b = binary.BigEndian.AppendUint16(b, uint16(len(c.subContainers)))
|
|
||||||
for _, section := range c.subContainers {
|
|
||||||
encoded := section.MarshalBinary()
|
|
||||||
b = binary.BigEndian.AppendUint16(b, uint16(len(encoded)))
|
|
||||||
encodedContainer = append(encodedContainer, encoded)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
b = append(b, kindData)
|
|
||||||
b = binary.BigEndian.AppendUint16(b, uint16(c.dataSize))
|
|
||||||
b = append(b, 0) // terminator
|
|
||||||
|
|
||||||
// Write section contents.
|
|
||||||
for _, ty := range c.types {
|
|
||||||
b = append(b, []byte{ty.inputs, ty.outputs, byte(ty.maxStackHeight >> 8), byte(ty.maxStackHeight & 0x00ff)}...)
|
|
||||||
}
|
|
||||||
for _, code := range c.codeSections {
|
|
||||||
b = append(b, code...)
|
|
||||||
}
|
|
||||||
for _, section := range encodedContainer {
|
|
||||||
b = append(b, section...)
|
|
||||||
}
|
|
||||||
b = append(b, c.data...)
|
|
||||||
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalBinary decodes an EOF container.
|
|
||||||
func (c *Container) UnmarshalBinary(b []byte, isInitcode bool) error {
|
|
||||||
return c.unmarshalContainer(b, isInitcode, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalSubContainer decodes an EOF container that is inside another container.
|
|
||||||
func (c *Container) UnmarshalSubContainer(b []byte, isInitcode bool) error {
|
|
||||||
return c.unmarshalContainer(b, isInitcode, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool) error {
|
|
||||||
if !hasEOFMagic(b) {
|
|
||||||
return fmt.Errorf("%w: want %x", errInvalidMagic, eofMagic)
|
|
||||||
}
|
|
||||||
if len(b) < 14 {
|
|
||||||
return io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
if len(b) > params.MaxInitCodeSize {
|
|
||||||
return ErrMaxInitCodeSizeExceeded
|
|
||||||
}
|
|
||||||
if !isEOFVersion1(b) {
|
|
||||||
return fmt.Errorf("%w: have %d, want %d", errInvalidVersion, b[2], eof1Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
kind, typesSize, dataSize int
|
|
||||||
codeSizes []int
|
|
||||||
err error
|
|
||||||
)
|
|
||||||
|
|
||||||
// Parse type section header.
|
|
||||||
kind, typesSize, err = parseSection(b, offsetTypesKind)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if kind != kindTypes {
|
|
||||||
return fmt.Errorf("%w: found section kind %x instead", errMissingTypeHeader, kind)
|
|
||||||
}
|
|
||||||
if typesSize < 4 || typesSize%4 != 0 {
|
|
||||||
return fmt.Errorf("%w: type section size must be divisible by 4, have %d", errInvalidTypeSize, typesSize)
|
|
||||||
}
|
|
||||||
if typesSize/4 > 1024 {
|
|
||||||
return fmt.Errorf("%w: type section must not exceed 4*1024, have %d", errInvalidTypeSize, typesSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse code section header.
|
|
||||||
kind, codeSizes, err = parseSectionList(b, offsetCodeKind)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if kind != kindCode {
|
|
||||||
return fmt.Errorf("%w: found section kind %x instead", errMissingCodeHeader, kind)
|
|
||||||
}
|
|
||||||
if len(codeSizes) != typesSize/4 {
|
|
||||||
return fmt.Errorf("%w: mismatch of code sections found and type signatures, types %d, code %d", errInvalidCodeSize, typesSize/4, len(codeSizes))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse (optional) container section header.
|
|
||||||
var containerSizes []int
|
|
||||||
offset := offsetCodeKind + 2 + 2*len(codeSizes) + 1
|
|
||||||
if offset < len(b) && b[offset] == kindContainer {
|
|
||||||
kind, containerSizes, err = parseSectionList(b, offset)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if kind != kindContainer {
|
|
||||||
panic("somethings wrong")
|
|
||||||
}
|
|
||||||
if len(containerSizes) == 0 {
|
|
||||||
return fmt.Errorf("%w: total container count must not be zero", errInvalidContainerSectionSize)
|
|
||||||
}
|
|
||||||
offset = offset + 2 + 2*len(containerSizes) + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse data section header.
|
|
||||||
kind, dataSize, err = parseSection(b, offset)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if kind != kindData {
|
|
||||||
return fmt.Errorf("%w: found section %x instead", errMissingDataHeader, kind)
|
|
||||||
}
|
|
||||||
c.dataSize = dataSize
|
|
||||||
|
|
||||||
// Check for terminator.
|
|
||||||
offsetTerminator := offset + 3
|
|
||||||
if len(b) < offsetTerminator {
|
|
||||||
return fmt.Errorf("%w: invalid offset terminator", io.ErrUnexpectedEOF)
|
|
||||||
}
|
|
||||||
if b[offsetTerminator] != 0 {
|
|
||||||
return fmt.Errorf("%w: have %x", errMissingTerminator, b[offsetTerminator])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify overall container size.
|
|
||||||
expectedSize := offsetTerminator + typesSize + sum(codeSizes) + dataSize + 1
|
|
||||||
if len(containerSizes) != 0 {
|
|
||||||
expectedSize += sum(containerSizes)
|
|
||||||
}
|
|
||||||
if len(b) < expectedSize-dataSize {
|
|
||||||
return fmt.Errorf("%w: have %d, want %d", errInvalidContainerSize, len(b), expectedSize)
|
|
||||||
}
|
|
||||||
// Only check that the expected size is not exceed on non-initcode
|
|
||||||
if (!topLevel || !isInitcode) && len(b) > expectedSize {
|
|
||||||
return fmt.Errorf("%w: have %d, want %d", errInvalidContainerSize, len(b), expectedSize)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse types section.
|
|
||||||
idx := offsetTerminator + 1
|
|
||||||
var types = make([]*functionMetadata, 0, typesSize/4)
|
|
||||||
for i := 0; i < typesSize/4; i++ {
|
|
||||||
sig := &functionMetadata{
|
|
||||||
inputs: b[idx+i*4],
|
|
||||||
outputs: b[idx+i*4+1],
|
|
||||||
maxStackHeight: binary.BigEndian.Uint16(b[idx+i*4+2:]),
|
|
||||||
}
|
|
||||||
if sig.inputs > maxInputItems {
|
|
||||||
return fmt.Errorf("%w for section %d: have %d", errTooManyInputs, i, sig.inputs)
|
|
||||||
}
|
|
||||||
if sig.outputs > maxOutputItems {
|
|
||||||
return fmt.Errorf("%w for section %d: have %d", errTooManyOutputs, i, sig.outputs)
|
|
||||||
}
|
|
||||||
if sig.maxStackHeight > maxStackHeight {
|
|
||||||
return fmt.Errorf("%w for section %d: have %d", errTooLargeMaxStackHeight, i, sig.maxStackHeight)
|
|
||||||
}
|
|
||||||
types = append(types, sig)
|
|
||||||
}
|
|
||||||
if types[0].inputs != 0 || types[0].outputs != 0x80 {
|
|
||||||
return fmt.Errorf("%w: have %d, %d", errInvalidSection0Type, types[0].inputs, types[0].outputs)
|
|
||||||
}
|
|
||||||
c.types = types
|
|
||||||
|
|
||||||
// Parse code sections.
|
|
||||||
idx += typesSize
|
|
||||||
codeSections := make([][]byte, len(codeSizes))
|
|
||||||
for i, size := range codeSizes {
|
|
||||||
if size == 0 {
|
|
||||||
return fmt.Errorf("%w for section %d: size must not be 0", errInvalidCodeSize, i)
|
|
||||||
}
|
|
||||||
codeSections[i] = b[idx : idx+size]
|
|
||||||
idx += size
|
|
||||||
}
|
|
||||||
c.codeSections = codeSections
|
|
||||||
// Parse the optional container sizes.
|
|
||||||
if len(containerSizes) != 0 {
|
|
||||||
if len(containerSizes) > maxContainerSections {
|
|
||||||
return fmt.Errorf("%w number of container section exceed: %v: have %v", errInvalidContainerSectionSize, maxContainerSections, len(containerSizes))
|
|
||||||
}
|
|
||||||
subContainerCodes := make([][]byte, 0, len(containerSizes))
|
|
||||||
subContainers := make([]*Container, 0, len(containerSizes))
|
|
||||||
for i, size := range containerSizes {
|
|
||||||
if size == 0 || idx+size > len(b) {
|
|
||||||
return fmt.Errorf("%w for section %d: size must not be 0", errInvalidContainerSectionSize, i)
|
|
||||||
}
|
|
||||||
subC := new(Container)
|
|
||||||
end := min(idx+size, len(b))
|
|
||||||
if err := subC.unmarshalContainer(b[idx:end], isInitcode, false); err != nil {
|
|
||||||
if topLevel {
|
|
||||||
return fmt.Errorf("%w in sub container %d", err, i)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
subContainers = append(subContainers, subC)
|
|
||||||
subContainerCodes = append(subContainerCodes, b[idx:end])
|
|
||||||
|
|
||||||
idx += size
|
|
||||||
}
|
|
||||||
c.subContainers = subContainers
|
|
||||||
c.subContainerCodes = subContainerCodes
|
|
||||||
}
|
|
||||||
|
|
||||||
//Parse data section.
|
|
||||||
end := len(b)
|
|
||||||
if !isInitcode {
|
|
||||||
end = min(idx+dataSize, len(b))
|
|
||||||
}
|
|
||||||
if topLevel && len(b) != idx+dataSize {
|
|
||||||
return errTruncatedTopLevelContainer
|
|
||||||
}
|
|
||||||
c.data = b[idx:end]
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ValidateCode validates each code section of the container against the EOF v1
|
|
||||||
// rule set.
|
|
||||||
func (c *Container) ValidateCode(jt *JumpTable, isInitCode bool) error {
|
|
||||||
refBy := notRefByEither
|
|
||||||
if isInitCode {
|
|
||||||
refBy = refByEOFCreate
|
|
||||||
}
|
|
||||||
return c.validateSubContainer(jt, refBy)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) validateSubContainer(jt *JumpTable, refBy int) error {
|
|
||||||
visited := make(map[int]struct{})
|
|
||||||
subContainerVisited := make(map[int]int)
|
|
||||||
toVisit := []int{0}
|
|
||||||
for len(toVisit) > 0 {
|
|
||||||
// TODO check if this can be used as a DOS
|
|
||||||
// Theres and edge case here where we mark something as visited that we visit before,
|
|
||||||
// This should not trigger a re-visit
|
|
||||||
// e.g. 0 -> 1, 2, 3
|
|
||||||
// 1 -> 2, 3
|
|
||||||
// should not mean 2 and 3 should be visited twice
|
|
||||||
var (
|
|
||||||
index = toVisit[0]
|
|
||||||
code = c.codeSections[index]
|
|
||||||
)
|
|
||||||
if _, ok := visited[index]; !ok {
|
|
||||||
res, err := validateCode(code, index, c, jt, refBy == refByEOFCreate)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
visited[index] = struct{}{}
|
|
||||||
// Mark all sections that can be visited from here.
|
|
||||||
for idx := range res.visitedCode {
|
|
||||||
if _, ok := visited[idx]; !ok {
|
|
||||||
toVisit = append(toVisit, idx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Mark all subcontainer that can be visited from here.
|
|
||||||
for idx, reference := range res.visitedSubContainers {
|
|
||||||
// Make sure subcontainers are only ever referenced by either EOFCreate or ReturnContract
|
|
||||||
if ref, ok := subContainerVisited[idx]; ok && ref != reference {
|
|
||||||
return errors.New("section referenced by both EOFCreate and ReturnContract")
|
|
||||||
}
|
|
||||||
subContainerVisited[idx] = reference
|
|
||||||
}
|
|
||||||
if refBy == refByReturnContract && res.isInitCode {
|
|
||||||
return errIncompatibleContainerKind
|
|
||||||
}
|
|
||||||
if refBy == refByEOFCreate && res.isRuntime {
|
|
||||||
return errIncompatibleContainerKind
|
|
||||||
}
|
|
||||||
}
|
|
||||||
toVisit = toVisit[1:]
|
|
||||||
}
|
|
||||||
// Make sure every code section is visited at least once.
|
|
||||||
if len(visited) != len(c.codeSections) {
|
|
||||||
return errUnreachableCode
|
|
||||||
}
|
|
||||||
for idx, container := range c.subContainers {
|
|
||||||
reference, ok := subContainerVisited[idx]
|
|
||||||
if !ok {
|
|
||||||
return errOrphanedSubcontainer
|
|
||||||
}
|
|
||||||
if err := container.validateSubContainer(jt, reference); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseSection decodes a (kind, size) pair from an EOF header.
|
|
||||||
func parseSection(b []byte, idx int) (kind, size int, err error) {
|
|
||||||
if idx+3 >= len(b) {
|
|
||||||
return 0, 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
kind = int(b[idx])
|
|
||||||
size = int(binary.BigEndian.Uint16(b[idx+1:]))
|
|
||||||
return kind, size, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseSectionList decodes a (kind, len, []codeSize) section list from an EOF
|
|
||||||
// header.
|
|
||||||
func parseSectionList(b []byte, idx int) (kind int, list []int, err error) {
|
|
||||||
if idx >= len(b) {
|
|
||||||
return 0, nil, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
kind = int(b[idx])
|
|
||||||
list, err = parseList(b, idx+1)
|
|
||||||
if err != nil {
|
|
||||||
return 0, nil, err
|
|
||||||
}
|
|
||||||
return kind, list, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseList decodes a list of uint16..
|
|
||||||
func parseList(b []byte, idx int) ([]int, error) {
|
|
||||||
if len(b) < idx+2 {
|
|
||||||
return nil, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
count := binary.BigEndian.Uint16(b[idx:])
|
|
||||||
if len(b) <= idx+2+int(count)*2 {
|
|
||||||
return nil, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
list := make([]int, count)
|
|
||||||
for i := 0; i < int(count); i++ {
|
|
||||||
list[i] = int(binary.BigEndian.Uint16(b[idx+2+2*i:]))
|
|
||||||
}
|
|
||||||
return list, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseUint16 parses a 16 bit unsigned integer.
|
|
||||||
func parseUint16(b []byte) (int, error) {
|
|
||||||
if len(b) < 2 {
|
|
||||||
return 0, io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
return int(binary.BigEndian.Uint16(b)), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// parseInt16 parses a 16 bit signed integer.
|
|
||||||
func parseInt16(b []byte) int {
|
|
||||||
return int(int16(b[1]) | int16(b[0])<<8)
|
|
||||||
}
|
|
||||||
|
|
||||||
// sum computes the sum of a slice.
|
|
||||||
func sum(list []int) (s int) {
|
|
||||||
for _, n := range list {
|
|
||||||
s += n
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *Container) String() string {
|
|
||||||
var output = []string{
|
|
||||||
"Header",
|
|
||||||
fmt.Sprintf(" - EOFMagic: %02x", eofMagic),
|
|
||||||
fmt.Sprintf(" - EOFVersion: %02x", eof1Version),
|
|
||||||
fmt.Sprintf(" - KindType: %02x", kindTypes),
|
|
||||||
fmt.Sprintf(" - TypesSize: %04x", len(c.types)*4),
|
|
||||||
fmt.Sprintf(" - KindCode: %02x", kindCode),
|
|
||||||
fmt.Sprintf(" - KindData: %02x", kindData),
|
|
||||||
fmt.Sprintf(" - DataSize: %04x", len(c.data)),
|
|
||||||
fmt.Sprintf(" - Number of code sections: %d", len(c.codeSections)),
|
|
||||||
}
|
|
||||||
for i, code := range c.codeSections {
|
|
||||||
output = append(output, fmt.Sprintf(" - Code section %d length: %04x", i, len(code)))
|
|
||||||
}
|
|
||||||
|
|
||||||
output = append(output, fmt.Sprintf(" - Number of subcontainers: %d", len(c.subContainers)))
|
|
||||||
if len(c.subContainers) > 0 {
|
|
||||||
for i, section := range c.subContainers {
|
|
||||||
output = append(output, fmt.Sprintf(" - subcontainer %d length: %04x\n", i, len(section.MarshalBinary())))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
output = append(output, "Body")
|
|
||||||
for i, typ := range c.types {
|
|
||||||
output = append(output, fmt.Sprintf(" - Type %v: %x", i,
|
|
||||||
[]byte{typ.inputs, typ.outputs, byte(typ.maxStackHeight >> 8), byte(typ.maxStackHeight & 0x00ff)}))
|
|
||||||
}
|
|
||||||
for i, code := range c.codeSections {
|
|
||||||
output = append(output, fmt.Sprintf(" - Code section %d: %#x", i, code))
|
|
||||||
}
|
|
||||||
for i, section := range c.subContainers {
|
|
||||||
output = append(output, fmt.Sprintf(" - Subcontainer %d: %x", i, section.MarshalBinary()))
|
|
||||||
}
|
|
||||||
output = append(output, fmt.Sprintf(" - Data: %#x", c.data))
|
|
||||||
return strings.Join(output, "\n")
|
|
||||||
}
|
|
||||||
|
|
@ -1,235 +0,0 @@
|
||||||
// Copyright 2024 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package vm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
func validateControlFlow(code []byte, section int, metadata []*functionMetadata, jt *JumpTable) (int, error) {
|
|
||||||
var (
|
|
||||||
maxStackHeight = int(metadata[section].inputs)
|
|
||||||
visitCount = 0
|
|
||||||
next = make([]int, 0, 1)
|
|
||||||
)
|
|
||||||
var (
|
|
||||||
stackBoundsMax = make([]uint16, len(code))
|
|
||||||
stackBoundsMin = make([]uint16, len(code))
|
|
||||||
)
|
|
||||||
setBounds := func(pos, min, maxi int) {
|
|
||||||
// The stackboundMax slice is a bit peculiar. We use `0` to denote
|
|
||||||
// not set. Therefore, we use `1` to represent the value `0`, and so on.
|
|
||||||
// So if the caller wants to store `1` as max bound, we internally store it as
|
|
||||||
// `2`.
|
|
||||||
if stackBoundsMax[pos] == 0 { // Not yet set
|
|
||||||
visitCount++
|
|
||||||
}
|
|
||||||
if maxi < 65535 {
|
|
||||||
stackBoundsMax[pos] = uint16(maxi + 1)
|
|
||||||
}
|
|
||||||
stackBoundsMin[pos] = uint16(min)
|
|
||||||
maxStackHeight = max(maxStackHeight, maxi)
|
|
||||||
}
|
|
||||||
getStackMaxMin := func(pos int) (ok bool, min, max int) {
|
|
||||||
maxi := stackBoundsMax[pos]
|
|
||||||
if maxi == 0 { // Not yet set
|
|
||||||
return false, 0, 0
|
|
||||||
}
|
|
||||||
return true, int(stackBoundsMin[pos]), int(maxi - 1)
|
|
||||||
}
|
|
||||||
// set the initial stack bounds
|
|
||||||
setBounds(0, int(metadata[section].inputs), int(metadata[section].inputs))
|
|
||||||
|
|
||||||
qualifiedExit := false
|
|
||||||
for pos := 0; pos < len(code); pos++ {
|
|
||||||
op := OpCode(code[pos])
|
|
||||||
ok, currentStackMin, currentStackMax := getStackMaxMin(pos)
|
|
||||||
if !ok {
|
|
||||||
return 0, errUnreachableCode
|
|
||||||
}
|
|
||||||
|
|
||||||
switch op {
|
|
||||||
case CALLF:
|
|
||||||
arg, _ := parseUint16(code[pos+1:])
|
|
||||||
newSection := metadata[arg]
|
|
||||||
if err := newSection.checkInputs(currentStackMin); err != nil {
|
|
||||||
return 0, fmt.Errorf("%w: at pos %d", err, pos)
|
|
||||||
}
|
|
||||||
if err := newSection.checkStackMax(currentStackMax); err != nil {
|
|
||||||
return 0, fmt.Errorf("%w: at pos %d", err, pos)
|
|
||||||
}
|
|
||||||
delta := newSection.stackDelta()
|
|
||||||
currentStackMax += delta
|
|
||||||
currentStackMin += delta
|
|
||||||
case RETF:
|
|
||||||
/* From the spec:
|
|
||||||
> for RETF the following must hold: stack_height_max == stack_height_min == types[current_code_index].outputs,
|
|
||||||
|
|
||||||
In other words: RETF must unambiguously return all items remaining on the stack.
|
|
||||||
*/
|
|
||||||
if currentStackMax != currentStackMin {
|
|
||||||
return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", errInvalidOutputs, currentStackMax, currentStackMin, pos)
|
|
||||||
}
|
|
||||||
numOutputs := int(metadata[section].outputs)
|
|
||||||
if numOutputs >= maxOutputItems {
|
|
||||||
return 0, fmt.Errorf("%w: at pos %d", errInvalidNonReturningFlag, pos)
|
|
||||||
}
|
|
||||||
if numOutputs != currentStackMin {
|
|
||||||
return 0, fmt.Errorf("%w: have %d, want %d, at pos %d", errInvalidOutputs, numOutputs, currentStackMin, pos)
|
|
||||||
}
|
|
||||||
qualifiedExit = true
|
|
||||||
case JUMPF:
|
|
||||||
arg, _ := parseUint16(code[pos+1:])
|
|
||||||
newSection := metadata[arg]
|
|
||||||
|
|
||||||
if err := newSection.checkStackMax(currentStackMax); err != nil {
|
|
||||||
return 0, fmt.Errorf("%w: at pos %d", err, pos)
|
|
||||||
}
|
|
||||||
|
|
||||||
if newSection.outputs == 0x80 {
|
|
||||||
if err := newSection.checkInputs(currentStackMin); err != nil {
|
|
||||||
return 0, fmt.Errorf("%w: at pos %d", err, pos)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if currentStackMax != currentStackMin {
|
|
||||||
return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", errInvalidOutputs, currentStackMax, currentStackMin, pos)
|
|
||||||
}
|
|
||||||
wantStack := int(metadata[section].outputs) - newSection.stackDelta()
|
|
||||||
if currentStackMax != wantStack {
|
|
||||||
return 0, fmt.Errorf("%w: at pos %d", errInvalidOutputs, pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
qualifiedExit = qualifiedExit || newSection.outputs < maxOutputItems
|
|
||||||
case DUPN:
|
|
||||||
arg := int(code[pos+1]) + 1
|
|
||||||
if want, have := arg, currentStackMin; want > have {
|
|
||||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
|
||||||
}
|
|
||||||
case SWAPN:
|
|
||||||
arg := int(code[pos+1]) + 1
|
|
||||||
if want, have := arg+1, currentStackMin; want > have {
|
|
||||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
|
||||||
}
|
|
||||||
case EXCHANGE:
|
|
||||||
arg := int(code[pos+1])
|
|
||||||
n := arg>>4 + 1
|
|
||||||
m := arg&0x0f + 1
|
|
||||||
if want, have := n+m+1, currentStackMin; want > have {
|
|
||||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if want, have := jt[op].minStack, currentStackMin; want > have {
|
|
||||||
return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !terminals[op] && op != CALLF {
|
|
||||||
change := int(params.StackLimit) - jt[op].maxStack
|
|
||||||
currentStackMax += change
|
|
||||||
currentStackMin += change
|
|
||||||
}
|
|
||||||
next = next[:0]
|
|
||||||
switch op {
|
|
||||||
case RJUMP:
|
|
||||||
nextPos := pos + 2 + parseInt16(code[pos+1:])
|
|
||||||
next = append(next, nextPos)
|
|
||||||
// We set the stack bounds of the destination
|
|
||||||
// and skip the argument, only for RJUMP, all other opcodes are handled later
|
|
||||||
if nextPos+1 < pos {
|
|
||||||
ok, nextMin, nextMax := getStackMaxMin(nextPos + 1)
|
|
||||||
if !ok {
|
|
||||||
return 0, errInvalidBackwardJump
|
|
||||||
}
|
|
||||||
if nextMax != currentStackMax || nextMin != currentStackMin {
|
|
||||||
return 0, errInvalidMaxStackHeight
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ok, nextMin, nextMax := getStackMaxMin(nextPos + 1)
|
|
||||||
if !ok {
|
|
||||||
setBounds(nextPos+1, currentStackMin, currentStackMax)
|
|
||||||
} else {
|
|
||||||
setBounds(nextPos+1, min(nextMin, currentStackMin), max(nextMax, currentStackMax))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case RJUMPI:
|
|
||||||
arg := parseInt16(code[pos+1:])
|
|
||||||
next = append(next, pos+2)
|
|
||||||
next = append(next, pos+2+arg)
|
|
||||||
case RJUMPV:
|
|
||||||
count := int(code[pos+1]) + 1
|
|
||||||
next = append(next, pos+1+2*count)
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
arg := parseInt16(code[pos+2+2*i:])
|
|
||||||
next = append(next, pos+1+2*count+arg)
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
if imm := int(immediates[op]); imm != 0 {
|
|
||||||
next = append(next, pos+imm)
|
|
||||||
} else {
|
|
||||||
// Simple op, no operand.
|
|
||||||
next = append(next, pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if op != RJUMP && !terminals[op] {
|
|
||||||
for _, instr := range next {
|
|
||||||
nextPC := instr + 1
|
|
||||||
if nextPC >= len(code) {
|
|
||||||
return 0, fmt.Errorf("%w: end with %s, pos %d", errInvalidCodeTermination, op, pos)
|
|
||||||
}
|
|
||||||
if nextPC > pos {
|
|
||||||
// target reached via forward jump or seq flow
|
|
||||||
ok, nextMin, nextMax := getStackMaxMin(nextPC)
|
|
||||||
if !ok {
|
|
||||||
setBounds(nextPC, currentStackMin, currentStackMax)
|
|
||||||
} else {
|
|
||||||
setBounds(nextPC, min(nextMin, currentStackMin), max(nextMax, currentStackMax))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// target reached via backwards jump
|
|
||||||
ok, nextMin, nextMax := getStackMaxMin(nextPC)
|
|
||||||
if !ok {
|
|
||||||
return 0, errInvalidBackwardJump
|
|
||||||
}
|
|
||||||
if currentStackMax != nextMax {
|
|
||||||
return 0, fmt.Errorf("%w want %d as current max got %d at pos %d,", errInvalidBackwardJump, currentStackMax, nextMax, pos)
|
|
||||||
}
|
|
||||||
if currentStackMin != nextMin {
|
|
||||||
return 0, fmt.Errorf("%w want %d as current min got %d at pos %d,", errInvalidBackwardJump, currentStackMin, nextMin, pos)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if op == RJUMP {
|
|
||||||
pos += 2 // skip the immediate
|
|
||||||
} else {
|
|
||||||
pos = next[0]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if qualifiedExit != (metadata[section].outputs < maxOutputItems) {
|
|
||||||
return 0, fmt.Errorf("%w no RETF or qualified JUMPF", errInvalidNonReturningFlag)
|
|
||||||
}
|
|
||||||
if maxStackHeight >= int(params.StackLimit) {
|
|
||||||
return 0, ErrStackOverflow{maxStackHeight, int(params.StackLimit)}
|
|
||||||
}
|
|
||||||
if maxStackHeight != int(metadata[section].maxStackHeight) {
|
|
||||||
return 0, fmt.Errorf("%w in code section %d: have %d, want %d", errInvalidMaxStackHeight, section, maxStackHeight, metadata[section].maxStackHeight)
|
|
||||||
}
|
|
||||||
return visitCount, nil
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
// Copyright 2024 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package vm
|
|
||||||
|
|
||||||
// immediate denotes how many immediate bytes an operation uses. This information
|
|
||||||
// is not required during runtime, only during EOF-validation, so is not
|
|
||||||
// places into the op-struct in the instruction table.
|
|
||||||
// Note: the immediates is fork-agnostic, and assumes that validity of opcodes at
|
|
||||||
// the given time is performed elsewhere.
|
|
||||||
var immediates [256]uint8
|
|
||||||
|
|
||||||
// terminals denotes whether instructions can be the final opcode in a code section.
|
|
||||||
// Note: the terminals is fork-agnostic, and assumes that validity of opcodes at
|
|
||||||
// the given time is performed elsewhere.
|
|
||||||
var terminals [256]bool
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
// The legacy pushes
|
|
||||||
for i := uint8(1); i < 33; i++ {
|
|
||||||
immediates[int(PUSH0)+int(i)] = i
|
|
||||||
}
|
|
||||||
// And new eof opcodes.
|
|
||||||
immediates[DATALOADN] = 2
|
|
||||||
immediates[RJUMP] = 2
|
|
||||||
immediates[RJUMPI] = 2
|
|
||||||
immediates[RJUMPV] = 3
|
|
||||||
immediates[CALLF] = 2
|
|
||||||
immediates[JUMPF] = 2
|
|
||||||
immediates[DUPN] = 1
|
|
||||||
immediates[SWAPN] = 1
|
|
||||||
immediates[EXCHANGE] = 1
|
|
||||||
immediates[EOFCREATE] = 1
|
|
||||||
immediates[RETURNCONTRACT] = 1
|
|
||||||
|
|
||||||
// Define the terminals.
|
|
||||||
terminals[STOP] = true
|
|
||||||
terminals[RETF] = true
|
|
||||||
terminals[JUMPF] = true
|
|
||||||
terminals[RETURNCONTRACT] = true
|
|
||||||
terminals[RETURN] = true
|
|
||||||
terminals[REVERT] = true
|
|
||||||
terminals[INVALID] = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Immediates returns the number bytes of immediates (argument not from
|
|
||||||
// stack but from code) a given opcode has.
|
|
||||||
// OBS:
|
|
||||||
// - This function assumes EOF instruction-set. It cannot be upon in
|
|
||||||
// a. pre-EOF code
|
|
||||||
// b. post-EOF but legacy code
|
|
||||||
// - RJUMPV is unique as it has a variable sized operand. The total size is
|
|
||||||
// determined by the count byte which immediately follows RJUMPV. This method
|
|
||||||
// will return '3' for RJUMPV, which is the minimum.
|
|
||||||
func Immediates(op OpCode) int {
|
|
||||||
return int(immediates[op])
|
|
||||||
}
|
|
||||||
|
|
@ -1,112 +0,0 @@
|
||||||
// Copyright 2024 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package vm
|
|
||||||
|
|
||||||
// opRjump implements the RJUMP opcode.
|
|
||||||
func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// opRjumpi implements the RJUMPI opcode
|
|
||||||
func opRjumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// opRjumpv implements the RJUMPV opcode
|
|
||||||
func opRjumpv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// opCallf implements the CALLF opcode
|
|
||||||
func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// opRetf implements the RETF opcode
|
|
||||||
func opRetf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// opJumpf implements the JUMPF opcode
|
|
||||||
func opJumpf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// opEOFCreate implements the EOFCREATE opcode
|
|
||||||
func opEOFCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// opReturnContract implements the RETURNCONTRACT opcode
|
|
||||||
func opReturnContract(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// opDataLoad implements the DATALOAD opcode
|
|
||||||
func opDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// opDataLoadN implements the DATALOADN opcode
|
|
||||||
func opDataLoadN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// opDataSize implements the DATASIZE opcode
|
|
||||||
func opDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// opDataCopy implements the DATACOPY opcode
|
|
||||||
func opDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// opDupN implements the DUPN opcode
|
|
||||||
func opDupN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// opSwapN implements the SWAPN opcode
|
|
||||||
func opSwapN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// opExchange implements the EXCHANGE opcode
|
|
||||||
func opExchange(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// opReturnDataLoad implements the RETURNDATALOAD opcode
|
|
||||||
func opReturnDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// opExtCall implements the EOFCREATE opcode
|
|
||||||
func opExtCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// opExtDelegateCall implements the EXTDELEGATECALL opcode
|
|
||||||
func opExtDelegateCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
||||||
// opExtStaticCall implements the EXTSTATICCALL opcode
|
|
||||||
func opExtStaticCall(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
|
|
||||||
panic("not implemented")
|
|
||||||
}
|
|
||||||
|
|
@ -1,117 +0,0 @@
|
||||||
// Copyright 2024 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package vm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestEOFMarshaling(t *testing.T) {
|
|
||||||
for i, test := range []struct {
|
|
||||||
want Container
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
want: Container{
|
|
||||||
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
|
||||||
codeSections: [][]byte{common.Hex2Bytes("604200")},
|
|
||||||
data: []byte{0x01, 0x02, 0x03},
|
|
||||||
dataSize: 3,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
want: Container{
|
|
||||||
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
|
||||||
codeSections: [][]byte{common.Hex2Bytes("604200")},
|
|
||||||
data: []byte{0x01, 0x02, 0x03},
|
|
||||||
dataSize: 3,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
want: Container{
|
|
||||||
types: []*functionMetadata{
|
|
||||||
{inputs: 0, outputs: 0x80, maxStackHeight: 1},
|
|
||||||
{inputs: 2, outputs: 3, maxStackHeight: 4},
|
|
||||||
{inputs: 1, outputs: 1, maxStackHeight: 1},
|
|
||||||
},
|
|
||||||
codeSections: [][]byte{
|
|
||||||
common.Hex2Bytes("604200"),
|
|
||||||
common.Hex2Bytes("6042604200"),
|
|
||||||
common.Hex2Bytes("00"),
|
|
||||||
},
|
|
||||||
data: []byte{},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
var (
|
|
||||||
b = test.want.MarshalBinary()
|
|
||||||
got Container
|
|
||||||
)
|
|
||||||
t.Logf("b: %#x", b)
|
|
||||||
if err := got.UnmarshalBinary(b, true); err != nil && err != test.err {
|
|
||||||
t.Fatalf("test %d: got error \"%v\", want \"%v\"", i, err, test.err)
|
|
||||||
}
|
|
||||||
if !reflect.DeepEqual(got, test.want) {
|
|
||||||
t.Fatalf("test %d: got %+v, want %+v", i, got, test.want)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestEOFSubcontainer(t *testing.T) {
|
|
||||||
var subcontainer = new(Container)
|
|
||||||
if err := subcontainer.UnmarshalBinary(common.Hex2Bytes("ef000101000402000100010400000000800000fe"), true); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
container := Container{
|
|
||||||
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
|
||||||
codeSections: [][]byte{common.Hex2Bytes("604200")},
|
|
||||||
subContainers: []*Container{subcontainer},
|
|
||||||
data: []byte{0x01, 0x02, 0x03},
|
|
||||||
dataSize: 3,
|
|
||||||
}
|
|
||||||
var (
|
|
||||||
b = container.MarshalBinary()
|
|
||||||
got Container
|
|
||||||
)
|
|
||||||
if err := got.UnmarshalBinary(b, true); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if res := got.MarshalBinary(); !reflect.DeepEqual(res, b) {
|
|
||||||
t.Fatalf("invalid marshalling, want %v got %v", b, res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMarshaling(t *testing.T) {
|
|
||||||
tests := []string{
|
|
||||||
"EF000101000402000100040400000000800000E0000000",
|
|
||||||
"ef0001010004020001000d04000000008000025fe100055f5fe000035f600100",
|
|
||||||
}
|
|
||||||
for i, test := range tests {
|
|
||||||
s, err := hex.DecodeString(test)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("test %d: error decoding: %v", i, err)
|
|
||||||
}
|
|
||||||
var got Container
|
|
||||||
if err := got.UnmarshalBinary(s, true); err != nil {
|
|
||||||
t.Fatalf("test %d: got error %v", i, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,255 +0,0 @@
|
||||||
// Copyright 2024 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package vm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Below are all possible errors that can occur during validation of
|
|
||||||
// EOF containers.
|
|
||||||
var (
|
|
||||||
errInvalidMagic = errors.New("invalid magic")
|
|
||||||
errUndefinedInstruction = errors.New("undefined instruction")
|
|
||||||
errTruncatedImmediate = errors.New("truncated immediate")
|
|
||||||
errInvalidSectionArgument = errors.New("invalid section argument")
|
|
||||||
errInvalidCallArgument = errors.New("callf into non-returning section")
|
|
||||||
errInvalidDataloadNArgument = errors.New("invalid dataloadN argument")
|
|
||||||
errInvalidJumpDest = errors.New("invalid jump destination")
|
|
||||||
errInvalidBackwardJump = errors.New("invalid backward jump")
|
|
||||||
errInvalidOutputs = errors.New("invalid number of outputs")
|
|
||||||
errInvalidMaxStackHeight = errors.New("invalid max stack height")
|
|
||||||
errInvalidCodeTermination = errors.New("invalid code termination")
|
|
||||||
errEOFCreateWithTruncatedSection = errors.New("eofcreate with truncated section")
|
|
||||||
errOrphanedSubcontainer = errors.New("subcontainer not referenced at all")
|
|
||||||
errIncompatibleContainerKind = errors.New("incompatible container kind")
|
|
||||||
errStopAndReturnContract = errors.New("Stop/Return and Returncontract in the same code section")
|
|
||||||
errStopInInitCode = errors.New("initcode contains a RETURN or STOP opcode")
|
|
||||||
errTruncatedTopLevelContainer = errors.New("truncated top level container")
|
|
||||||
errUnreachableCode = errors.New("unreachable code")
|
|
||||||
errInvalidNonReturningFlag = errors.New("invalid non-returning flag, bad RETF")
|
|
||||||
errInvalidVersion = errors.New("invalid version")
|
|
||||||
errMissingTypeHeader = errors.New("missing type header")
|
|
||||||
errInvalidTypeSize = errors.New("invalid type section size")
|
|
||||||
errMissingCodeHeader = errors.New("missing code header")
|
|
||||||
errInvalidCodeSize = errors.New("invalid code size")
|
|
||||||
errInvalidContainerSectionSize = errors.New("invalid container section size")
|
|
||||||
errMissingDataHeader = errors.New("missing data header")
|
|
||||||
errMissingTerminator = errors.New("missing header terminator")
|
|
||||||
errTooManyInputs = errors.New("invalid type content, too many inputs")
|
|
||||||
errTooManyOutputs = errors.New("invalid type content, too many outputs")
|
|
||||||
errInvalidSection0Type = errors.New("invalid section 0 type, input and output should be zero and non-returning (0x80)")
|
|
||||||
errTooLargeMaxStackHeight = errors.New("invalid type content, max stack height exceeds limit")
|
|
||||||
errInvalidContainerSize = errors.New("invalid container size")
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
notRefByEither = iota
|
|
||||||
refByReturnContract
|
|
||||||
refByEOFCreate
|
|
||||||
)
|
|
||||||
|
|
||||||
type validationResult struct {
|
|
||||||
visitedCode map[int]struct{}
|
|
||||||
visitedSubContainers map[int]int
|
|
||||||
isInitCode bool
|
|
||||||
isRuntime bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateCode validates the code parameter against the EOF v1 validity requirements.
|
|
||||||
func validateCode(code []byte, section int, container *Container, jt *JumpTable, isInitCode bool) (*validationResult, error) {
|
|
||||||
var (
|
|
||||||
i = 0
|
|
||||||
// Tracks the number of actual instructions in the code (e.g.
|
|
||||||
// non-immediate values). This is used at the end to determine
|
|
||||||
// if each instruction is reachable.
|
|
||||||
count = 0
|
|
||||||
op OpCode
|
|
||||||
analysis bitvec
|
|
||||||
visitedCode map[int]struct{}
|
|
||||||
visitedSubcontainers map[int]int
|
|
||||||
hasReturnContract bool
|
|
||||||
hasStop bool
|
|
||||||
)
|
|
||||||
// This loop visits every single instruction and verifies:
|
|
||||||
// * if the instruction is valid for the given jump table.
|
|
||||||
// * if the instruction has an immediate value, it is not truncated.
|
|
||||||
// * if performing a relative jump, all jump destinations are valid.
|
|
||||||
// * if changing code sections, the new code section index is valid and
|
|
||||||
// will not cause a stack overflow.
|
|
||||||
for i < len(code) {
|
|
||||||
count++
|
|
||||||
op = OpCode(code[i])
|
|
||||||
if jt[op].undefined {
|
|
||||||
return nil, fmt.Errorf("%w: op %s, pos %d", errUndefinedInstruction, op, i)
|
|
||||||
}
|
|
||||||
size := int(immediates[op])
|
|
||||||
if size != 0 && len(code) <= i+size {
|
|
||||||
return nil, fmt.Errorf("%w: op %s, pos %d", errTruncatedImmediate, op, i)
|
|
||||||
}
|
|
||||||
switch op {
|
|
||||||
case RJUMP, RJUMPI:
|
|
||||||
if err := checkDest(code, &analysis, i+1, i+3, len(code)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
case RJUMPV:
|
|
||||||
maxSize := int(code[i+1])
|
|
||||||
length := maxSize + 1
|
|
||||||
if len(code) <= i+length {
|
|
||||||
return nil, fmt.Errorf("%w: jump table truncated, op %s, pos %d", errTruncatedImmediate, op, i)
|
|
||||||
}
|
|
||||||
offset := i + 2
|
|
||||||
for j := 0; j < length; j++ {
|
|
||||||
if err := checkDest(code, &analysis, offset+j*2, offset+(length*2), len(code)); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i += 2 * maxSize
|
|
||||||
case CALLF:
|
|
||||||
arg, _ := parseUint16(code[i+1:])
|
|
||||||
if arg >= len(container.types) {
|
|
||||||
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidSectionArgument, arg, len(container.types), i)
|
|
||||||
}
|
|
||||||
if container.types[arg].outputs == 0x80 {
|
|
||||||
return nil, fmt.Errorf("%w: section %v", errInvalidCallArgument, arg)
|
|
||||||
}
|
|
||||||
if visitedCode == nil {
|
|
||||||
visitedCode = make(map[int]struct{})
|
|
||||||
}
|
|
||||||
visitedCode[arg] = struct{}{}
|
|
||||||
case JUMPF:
|
|
||||||
arg, _ := parseUint16(code[i+1:])
|
|
||||||
if arg >= len(container.types) {
|
|
||||||
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidSectionArgument, arg, len(container.types), i)
|
|
||||||
}
|
|
||||||
if container.types[arg].outputs != 0x80 && container.types[arg].outputs > container.types[section].outputs {
|
|
||||||
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidOutputs, arg, len(container.types), i)
|
|
||||||
}
|
|
||||||
if visitedCode == nil {
|
|
||||||
visitedCode = make(map[int]struct{})
|
|
||||||
}
|
|
||||||
visitedCode[arg] = struct{}{}
|
|
||||||
case DATALOADN:
|
|
||||||
arg, _ := parseUint16(code[i+1:])
|
|
||||||
// TODO why are we checking this? We should just pad
|
|
||||||
if arg+32 > len(container.data) {
|
|
||||||
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidDataloadNArgument, arg, len(container.data), i)
|
|
||||||
}
|
|
||||||
case RETURNCONTRACT:
|
|
||||||
if !isInitCode {
|
|
||||||
return nil, errIncompatibleContainerKind
|
|
||||||
}
|
|
||||||
arg := int(code[i+1])
|
|
||||||
if arg >= len(container.subContainers) {
|
|
||||||
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errUnreachableCode, arg, len(container.subContainers), i)
|
|
||||||
}
|
|
||||||
if visitedSubcontainers == nil {
|
|
||||||
visitedSubcontainers = make(map[int]int)
|
|
||||||
}
|
|
||||||
// We need to store per subcontainer how it was referenced
|
|
||||||
if v, ok := visitedSubcontainers[arg]; ok && v != refByReturnContract {
|
|
||||||
return nil, fmt.Errorf("section already referenced, arg :%d", arg)
|
|
||||||
}
|
|
||||||
if hasStop {
|
|
||||||
return nil, errStopAndReturnContract
|
|
||||||
}
|
|
||||||
hasReturnContract = true
|
|
||||||
visitedSubcontainers[arg] = refByReturnContract
|
|
||||||
case EOFCREATE:
|
|
||||||
arg := int(code[i+1])
|
|
||||||
if arg >= len(container.subContainers) {
|
|
||||||
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errUnreachableCode, arg, len(container.subContainers), i)
|
|
||||||
}
|
|
||||||
if ct := container.subContainers[arg]; len(ct.data) != ct.dataSize {
|
|
||||||
return nil, fmt.Errorf("%w: container %d, have %d, claimed %d, pos %d", errEOFCreateWithTruncatedSection, arg, len(ct.data), ct.dataSize, i)
|
|
||||||
}
|
|
||||||
if visitedSubcontainers == nil {
|
|
||||||
visitedSubcontainers = make(map[int]int)
|
|
||||||
}
|
|
||||||
// We need to store per subcontainer how it was referenced
|
|
||||||
if v, ok := visitedSubcontainers[arg]; ok && v != refByEOFCreate {
|
|
||||||
return nil, fmt.Errorf("section already referenced, arg :%d", arg)
|
|
||||||
}
|
|
||||||
visitedSubcontainers[arg] = refByEOFCreate
|
|
||||||
case STOP, RETURN:
|
|
||||||
if isInitCode {
|
|
||||||
return nil, errStopInInitCode
|
|
||||||
}
|
|
||||||
if hasReturnContract {
|
|
||||||
return nil, errStopAndReturnContract
|
|
||||||
}
|
|
||||||
hasStop = true
|
|
||||||
}
|
|
||||||
i += size + 1
|
|
||||||
}
|
|
||||||
// Code sections may not "fall through" and require proper termination.
|
|
||||||
// Therefore, the last instruction must be considered terminal or RJUMP.
|
|
||||||
if !terminals[op] && op != RJUMP {
|
|
||||||
return nil, fmt.Errorf("%w: end with %s, pos %d", errInvalidCodeTermination, op, i)
|
|
||||||
}
|
|
||||||
if paths, err := validateControlFlow(code, section, container.types, jt); err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else if paths != count {
|
|
||||||
// TODO(matt): return actual position of unreachable code
|
|
||||||
return nil, errUnreachableCode
|
|
||||||
}
|
|
||||||
return &validationResult{
|
|
||||||
visitedCode: visitedCode,
|
|
||||||
visitedSubContainers: visitedSubcontainers,
|
|
||||||
isInitCode: hasReturnContract,
|
|
||||||
isRuntime: hasStop,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// checkDest parses a relative offset at code[0:2] and checks if it is a valid jump destination.
|
|
||||||
func checkDest(code []byte, analysis *bitvec, imm, from, length int) error {
|
|
||||||
if len(code) < imm+2 {
|
|
||||||
return io.ErrUnexpectedEOF
|
|
||||||
}
|
|
||||||
if analysis != nil && *analysis == nil {
|
|
||||||
*analysis = eofCodeBitmap(code)
|
|
||||||
}
|
|
||||||
offset := parseInt16(code[imm:])
|
|
||||||
dest := from + offset
|
|
||||||
if dest < 0 || dest >= length {
|
|
||||||
return fmt.Errorf("%w: out-of-bounds offset: offset %d, dest %d, pos %d", errInvalidJumpDest, offset, dest, imm)
|
|
||||||
}
|
|
||||||
if !analysis.codeSegment(uint64(dest)) {
|
|
||||||
return fmt.Errorf("%w: offset into immediate: offset %d, dest %d, pos %d", errInvalidJumpDest, offset, dest, imm)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
//// disasm is a helper utility to show a sequence of comma-separated operations,
|
|
||||||
//// with immediates shown inline,
|
|
||||||
//// e.g: PUSH1(0x00),EOFCREATE(0x00),
|
|
||||||
//func disasm(code []byte) string {
|
|
||||||
// var ops []string
|
|
||||||
// for i := 0; i < len(code); i++ {
|
|
||||||
// var op string
|
|
||||||
// if args := immediates[code[i]]; args > 0 {
|
|
||||||
// op = fmt.Sprintf("%v(%#x)", OpCode(code[i]).String(), code[i+1:i+1+int(args)])
|
|
||||||
// i += int(args)
|
|
||||||
// } else {
|
|
||||||
// op = OpCode(code[i]).String()
|
|
||||||
// }
|
|
||||||
// ops = append(ops, op)
|
|
||||||
// }
|
|
||||||
// return strings.Join(ops, ",")
|
|
||||||
//}
|
|
||||||
|
|
@ -1,517 +0,0 @@
|
||||||
// Copyright 2024 The go-ethereum Authors
|
|
||||||
// This file is part of the go-ethereum library.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
||||||
// it under the terms of the GNU Lesser General Public License as published by
|
|
||||||
// the Free Software Foundation, either version 3 of the License, or
|
|
||||||
// (at your option) any later version.
|
|
||||||
//
|
|
||||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
||||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
// GNU Lesser General Public License for more details.
|
|
||||||
//
|
|
||||||
// You should have received a copy of the GNU Lesser General Public License
|
|
||||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
package vm
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/binary"
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/common"
|
|
||||||
"github.com/ethereum/go-ethereum/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestValidateCode(t *testing.T) {
|
|
||||||
for i, test := range []struct {
|
|
||||||
code []byte
|
|
||||||
section int
|
|
||||||
metadata []*functionMetadata
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
code: []byte{
|
|
||||||
byte(CALLER),
|
|
||||||
byte(POP),
|
|
||||||
byte(STOP),
|
|
||||||
},
|
|
||||||
section: 0,
|
|
||||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: []byte{
|
|
||||||
byte(CALLF), 0x00, 0x00,
|
|
||||||
byte(RETF),
|
|
||||||
},
|
|
||||||
section: 0,
|
|
||||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0, maxStackHeight: 0}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: []byte{
|
|
||||||
byte(ADDRESS),
|
|
||||||
byte(CALLF), 0x00, 0x00,
|
|
||||||
byte(POP),
|
|
||||||
byte(RETF),
|
|
||||||
},
|
|
||||||
section: 0,
|
|
||||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0, maxStackHeight: 1}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: []byte{
|
|
||||||
byte(CALLER),
|
|
||||||
byte(POP),
|
|
||||||
},
|
|
||||||
section: 0,
|
|
||||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
|
||||||
err: errInvalidCodeTermination,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: []byte{
|
|
||||||
byte(RJUMP),
|
|
||||||
byte(0x00),
|
|
||||||
byte(0x01),
|
|
||||||
byte(CALLER),
|
|
||||||
byte(STOP),
|
|
||||||
},
|
|
||||||
section: 0,
|
|
||||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 0}},
|
|
||||||
err: errUnreachableCode,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: []byte{
|
|
||||||
byte(PUSH1),
|
|
||||||
byte(0x42),
|
|
||||||
byte(ADD),
|
|
||||||
byte(STOP),
|
|
||||||
},
|
|
||||||
section: 0,
|
|
||||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
|
||||||
err: ErrStackUnderflow{stackLen: 1, required: 2},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: []byte{
|
|
||||||
byte(PUSH1),
|
|
||||||
byte(0x42),
|
|
||||||
byte(POP),
|
|
||||||
byte(STOP),
|
|
||||||
},
|
|
||||||
section: 0,
|
|
||||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 2}},
|
|
||||||
err: errInvalidMaxStackHeight,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: []byte{
|
|
||||||
byte(PUSH0),
|
|
||||||
byte(RJUMPI),
|
|
||||||
byte(0x00),
|
|
||||||
byte(0x01),
|
|
||||||
byte(PUSH1),
|
|
||||||
byte(0x42), // jumps to here
|
|
||||||
byte(POP),
|
|
||||||
byte(STOP),
|
|
||||||
},
|
|
||||||
section: 0,
|
|
||||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
|
||||||
err: errInvalidJumpDest,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: []byte{
|
|
||||||
byte(PUSH0),
|
|
||||||
byte(RJUMPV),
|
|
||||||
byte(0x01),
|
|
||||||
byte(0x00),
|
|
||||||
byte(0x01),
|
|
||||||
byte(0x00),
|
|
||||||
byte(0x02),
|
|
||||||
byte(PUSH1),
|
|
||||||
byte(0x42), // jumps to here
|
|
||||||
byte(POP), // and here
|
|
||||||
byte(STOP),
|
|
||||||
},
|
|
||||||
section: 0,
|
|
||||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
|
||||||
err: errInvalidJumpDest,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: []byte{
|
|
||||||
byte(PUSH0),
|
|
||||||
byte(RJUMPV),
|
|
||||||
byte(0x00),
|
|
||||||
byte(STOP),
|
|
||||||
},
|
|
||||||
section: 0,
|
|
||||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
|
||||||
err: errTruncatedImmediate,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: []byte{
|
|
||||||
byte(RJUMP), 0x00, 0x03,
|
|
||||||
byte(JUMPDEST), // this code is unreachable to forward jumps alone
|
|
||||||
byte(JUMPDEST),
|
|
||||||
byte(RETURN),
|
|
||||||
byte(PUSH1), 20,
|
|
||||||
byte(PUSH1), 39,
|
|
||||||
byte(PUSH1), 0x00,
|
|
||||||
byte(DATACOPY),
|
|
||||||
byte(PUSH1), 20,
|
|
||||||
byte(PUSH1), 0x00,
|
|
||||||
byte(RJUMP), 0xff, 0xef,
|
|
||||||
},
|
|
||||||
section: 0,
|
|
||||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 3}},
|
|
||||||
err: errUnreachableCode,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: []byte{
|
|
||||||
byte(PUSH1), 1,
|
|
||||||
byte(RJUMPI), 0x00, 0x03,
|
|
||||||
byte(JUMPDEST),
|
|
||||||
byte(JUMPDEST),
|
|
||||||
byte(STOP),
|
|
||||||
byte(PUSH1), 20,
|
|
||||||
byte(PUSH1), 39,
|
|
||||||
byte(PUSH1), 0x00,
|
|
||||||
byte(DATACOPY),
|
|
||||||
byte(PUSH1), 20,
|
|
||||||
byte(PUSH1), 0x00,
|
|
||||||
byte(RETURN),
|
|
||||||
},
|
|
||||||
section: 0,
|
|
||||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 3}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: []byte{
|
|
||||||
byte(PUSH1), 1,
|
|
||||||
byte(RJUMPV), 0x01, 0x00, 0x03, 0xff, 0xf8,
|
|
||||||
byte(JUMPDEST),
|
|
||||||
byte(JUMPDEST),
|
|
||||||
byte(STOP),
|
|
||||||
byte(PUSH1), 20,
|
|
||||||
byte(PUSH1), 39,
|
|
||||||
byte(PUSH1), 0x00,
|
|
||||||
byte(DATACOPY),
|
|
||||||
byte(PUSH1), 20,
|
|
||||||
byte(PUSH1), 0x00,
|
|
||||||
byte(RETURN),
|
|
||||||
},
|
|
||||||
section: 0,
|
|
||||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 3}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: []byte{
|
|
||||||
byte(STOP),
|
|
||||||
byte(STOP),
|
|
||||||
byte(INVALID),
|
|
||||||
},
|
|
||||||
section: 0,
|
|
||||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 0}},
|
|
||||||
err: errUnreachableCode,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: []byte{
|
|
||||||
byte(RETF),
|
|
||||||
},
|
|
||||||
section: 0,
|
|
||||||
metadata: []*functionMetadata{{inputs: 0, outputs: 1, maxStackHeight: 0}},
|
|
||||||
err: errInvalidOutputs,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: []byte{
|
|
||||||
byte(RETF),
|
|
||||||
},
|
|
||||||
section: 0,
|
|
||||||
metadata: []*functionMetadata{{inputs: 3, outputs: 3, maxStackHeight: 3}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: []byte{
|
|
||||||
byte(CALLF), 0x00, 0x01,
|
|
||||||
byte(POP),
|
|
||||||
byte(STOP),
|
|
||||||
},
|
|
||||||
section: 0,
|
|
||||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}, {inputs: 0, outputs: 1, maxStackHeight: 0}},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
code: []byte{
|
|
||||||
byte(ORIGIN),
|
|
||||||
byte(ORIGIN),
|
|
||||||
byte(CALLF), 0x00, 0x01,
|
|
||||||
byte(POP),
|
|
||||||
byte(RETF),
|
|
||||||
},
|
|
||||||
section: 0,
|
|
||||||
metadata: []*functionMetadata{{inputs: 0, outputs: 0, maxStackHeight: 2}, {inputs: 2, outputs: 1, maxStackHeight: 2}},
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
container := &Container{
|
|
||||||
types: test.metadata,
|
|
||||||
data: make([]byte, 0),
|
|
||||||
subContainers: make([]*Container, 0),
|
|
||||||
}
|
|
||||||
_, err := validateCode(test.code, test.section, container, &eofInstructionSet, false)
|
|
||||||
if !errors.Is(err, test.err) {
|
|
||||||
t.Errorf("test %d (%s): unexpected error (want: %v, got: %v)", i, common.Bytes2Hex(test.code), test.err, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BenchmarkRJUMPI tries to benchmark the RJUMPI opcode validation
|
|
||||||
// For this we do a bunch of RJUMPIs that jump backwards (in a potential infinite loop).
|
|
||||||
func BenchmarkRJUMPI(b *testing.B) {
|
|
||||||
snippet := []byte{
|
|
||||||
byte(PUSH0),
|
|
||||||
byte(RJUMPI), 0xFF, 0xFC,
|
|
||||||
}
|
|
||||||
code := []byte{}
|
|
||||||
for i := 0; i < params.MaxCodeSize/len(snippet)-1; i++ {
|
|
||||||
code = append(code, snippet...)
|
|
||||||
}
|
|
||||||
code = append(code, byte(STOP))
|
|
||||||
container := &Container{
|
|
||||||
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
|
||||||
data: make([]byte, 0),
|
|
||||||
subContainers: make([]*Container, 0),
|
|
||||||
}
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, err := validateCode(code, 0, container, &eofInstructionSet, false)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BenchmarkRJUMPV tries to benchmark the validation of the RJUMPV opcode
|
|
||||||
// for this we set up as many RJUMPV opcodes with a full jumptable (containing 0s) as possible.
|
|
||||||
func BenchmarkRJUMPV(b *testing.B) {
|
|
||||||
snippet := []byte{
|
|
||||||
byte(PUSH0),
|
|
||||||
byte(RJUMPV),
|
|
||||||
0xff, // count
|
|
||||||
0x00, 0x00,
|
|
||||||
}
|
|
||||||
for i := 0; i < 255; i++ {
|
|
||||||
snippet = append(snippet, []byte{0x00, 0x00}...)
|
|
||||||
}
|
|
||||||
code := []byte{}
|
|
||||||
for i := 0; i < 24576/len(snippet)-1; i++ {
|
|
||||||
code = append(code, snippet...)
|
|
||||||
}
|
|
||||||
code = append(code, byte(PUSH0))
|
|
||||||
code = append(code, byte(STOP))
|
|
||||||
container := &Container{
|
|
||||||
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
|
||||||
data: make([]byte, 0),
|
|
||||||
subContainers: make([]*Container, 0),
|
|
||||||
}
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, err := validateCode(code, 0, container, &pragueInstructionSet, false)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BenchmarkEOFValidation tries to benchmark the code validation for the CALLF/RETF operation.
|
|
||||||
// For this we set up code that calls into 1024 code sections which can either
|
|
||||||
// - just contain a RETF opcode
|
|
||||||
// - or code to again call into 1024 code sections.
|
|
||||||
// We can't have all code sections calling each other, otherwise we would exceed 48KB.
|
|
||||||
func BenchmarkEOFValidation(b *testing.B) {
|
|
||||||
var container Container
|
|
||||||
var code []byte
|
|
||||||
maxSections := 1024
|
|
||||||
for i := 0; i < maxSections; i++ {
|
|
||||||
code = append(code, byte(CALLF))
|
|
||||||
code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1)
|
|
||||||
}
|
|
||||||
// First container
|
|
||||||
container.codeSections = append(container.codeSections, append(code, byte(STOP)))
|
|
||||||
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
|
|
||||||
|
|
||||||
inner := []byte{
|
|
||||||
byte(RETF),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < 1023; i++ {
|
|
||||||
container.codeSections = append(container.codeSections, inner)
|
|
||||||
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < 12; i++ {
|
|
||||||
container.codeSections[i+1] = append(code, byte(RETF))
|
|
||||||
}
|
|
||||||
|
|
||||||
bin := container.MarshalBinary()
|
|
||||||
if len(bin) > 48*1024 {
|
|
||||||
b.Fatal("Exceeds 48Kb")
|
|
||||||
}
|
|
||||||
|
|
||||||
var container2 Container
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
if err := container2.UnmarshalBinary(bin, true); err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := container2.ValidateCode(&pragueInstructionSet, false); err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BenchmarkEOFValidation2 tries to benchmark the code validation for the CALLF/RETF operation.
|
|
||||||
// For this we set up code that calls into 1024 code sections which
|
|
||||||
// - contain calls to some other code sections.
|
|
||||||
// We can't have all code sections calling each other, otherwise we would exceed 48KB.
|
|
||||||
func BenchmarkEOFValidation2(b *testing.B) {
|
|
||||||
var container Container
|
|
||||||
var code []byte
|
|
||||||
maxSections := 1024
|
|
||||||
for i := 0; i < maxSections; i++ {
|
|
||||||
code = append(code, byte(CALLF))
|
|
||||||
code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1)
|
|
||||||
}
|
|
||||||
code = append(code, byte(STOP))
|
|
||||||
// First container
|
|
||||||
container.codeSections = append(container.codeSections, code)
|
|
||||||
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
|
|
||||||
|
|
||||||
inner := []byte{
|
|
||||||
byte(CALLF), 0x03, 0xE8,
|
|
||||||
byte(CALLF), 0x03, 0xE9,
|
|
||||||
byte(CALLF), 0x03, 0xF0,
|
|
||||||
byte(CALLF), 0x03, 0xF1,
|
|
||||||
byte(CALLF), 0x03, 0xF2,
|
|
||||||
byte(CALLF), 0x03, 0xF3,
|
|
||||||
byte(CALLF), 0x03, 0xF4,
|
|
||||||
byte(CALLF), 0x03, 0xF5,
|
|
||||||
byte(CALLF), 0x03, 0xF6,
|
|
||||||
byte(CALLF), 0x03, 0xF7,
|
|
||||||
byte(CALLF), 0x03, 0xF8,
|
|
||||||
byte(CALLF), 0x03, 0xF,
|
|
||||||
byte(RETF),
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < 1023; i++ {
|
|
||||||
container.codeSections = append(container.codeSections, inner)
|
|
||||||
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
|
|
||||||
}
|
|
||||||
|
|
||||||
bin := container.MarshalBinary()
|
|
||||||
if len(bin) > 48*1024 {
|
|
||||||
b.Fatal("Exceeds 48Kb")
|
|
||||||
}
|
|
||||||
|
|
||||||
var container2 Container
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
if err := container2.UnmarshalBinary(bin, true); err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := container2.ValidateCode(&pragueInstructionSet, false); err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// BenchmarkEOFValidation3 tries to benchmark the code validation for the CALLF/RETF and RJUMPI/V operations.
|
|
||||||
// For this we set up code that calls into 1024 code sections which either
|
|
||||||
// - contain an RJUMP opcode
|
|
||||||
// - contain calls to other code sections
|
|
||||||
// We can't have all code sections calling each other, otherwise we would exceed 48KB.
|
|
||||||
func BenchmarkEOFValidation3(b *testing.B) {
|
|
||||||
var container Container
|
|
||||||
var code []byte
|
|
||||||
snippet := []byte{
|
|
||||||
byte(PUSH0),
|
|
||||||
byte(RJUMPV),
|
|
||||||
0xff, // count
|
|
||||||
0x00, 0x00,
|
|
||||||
}
|
|
||||||
for i := 0; i < 255; i++ {
|
|
||||||
snippet = append(snippet, []byte{0x00, 0x00}...)
|
|
||||||
}
|
|
||||||
code = append(code, snippet...)
|
|
||||||
// First container, calls into all other containers
|
|
||||||
maxSections := 1024
|
|
||||||
for i := 0; i < maxSections; i++ {
|
|
||||||
code = append(code, byte(CALLF))
|
|
||||||
code = binary.BigEndian.AppendUint16(code, uint16(i%(maxSections-1))+1)
|
|
||||||
}
|
|
||||||
code = append(code, byte(STOP))
|
|
||||||
container.codeSections = append(container.codeSections, code)
|
|
||||||
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 1})
|
|
||||||
|
|
||||||
// Other containers
|
|
||||||
for i := 0; i < 1023; i++ {
|
|
||||||
container.codeSections = append(container.codeSections, []byte{byte(RJUMP), 0x00, 0x00, byte(RETF)})
|
|
||||||
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
|
|
||||||
}
|
|
||||||
// Other containers
|
|
||||||
for i := 0; i < 68; i++ {
|
|
||||||
container.codeSections[i+1] = append(snippet, byte(RETF))
|
|
||||||
container.types[i+1] = &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 1}
|
|
||||||
}
|
|
||||||
bin := container.MarshalBinary()
|
|
||||||
if len(bin) > 48*1024 {
|
|
||||||
b.Fatal("Exceeds 48Kb")
|
|
||||||
}
|
|
||||||
b.ResetTimer()
|
|
||||||
b.ReportMetric(float64(len(bin)), "bytes")
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
for k := 0; k < 40; k++ {
|
|
||||||
var container2 Container
|
|
||||||
if err := container2.UnmarshalBinary(bin, true); err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := container2.ValidateCode(&pragueInstructionSet, false); err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkRJUMPI_2(b *testing.B) {
|
|
||||||
code := []byte{
|
|
||||||
byte(PUSH0),
|
|
||||||
byte(RJUMPI), 0xFF, 0xFC,
|
|
||||||
}
|
|
||||||
for i := 0; i < params.MaxCodeSize/4-1; i++ {
|
|
||||||
code = append(code, byte(PUSH0))
|
|
||||||
x := -4 * i
|
|
||||||
code = append(code, byte(RJUMPI))
|
|
||||||
code = binary.BigEndian.AppendUint16(code, uint16(x))
|
|
||||||
}
|
|
||||||
code = append(code, byte(STOP))
|
|
||||||
container := &Container{
|
|
||||||
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
|
|
||||||
data: make([]byte, 0),
|
|
||||||
subContainers: make([]*Container, 0),
|
|
||||||
}
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, err := validateCode(code, 0, container, &pragueInstructionSet, false)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func FuzzUnmarshalBinary(f *testing.F) {
|
|
||||||
f.Fuzz(func(_ *testing.T, input []byte) {
|
|
||||||
var container Container
|
|
||||||
container.UnmarshalBinary(input, true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func FuzzValidate(f *testing.F) {
|
|
||||||
f.Fuzz(func(_ *testing.T, code []byte, maxStack uint16) {
|
|
||||||
var container Container
|
|
||||||
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: maxStack})
|
|
||||||
validateCode(code, 0, &container, &pragueInstructionSet, true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
@ -62,7 +62,6 @@ var (
|
||||||
cancunInstructionSet = newCancunInstructionSet()
|
cancunInstructionSet = newCancunInstructionSet()
|
||||||
verkleInstructionSet = newVerkleInstructionSet()
|
verkleInstructionSet = newVerkleInstructionSet()
|
||||||
pragueInstructionSet = newPragueInstructionSet()
|
pragueInstructionSet = newPragueInstructionSet()
|
||||||
eofInstructionSet = newEOFInstructionSetForTesting()
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// JumpTable contains the EVM opcodes supported at a given fork.
|
// JumpTable contains the EVM opcodes supported at a given fork.
|
||||||
|
|
@ -92,16 +91,6 @@ func newVerkleInstructionSet() JumpTable {
|
||||||
return validate(instructionSet)
|
return validate(instructionSet)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEOFInstructionSetForTesting() JumpTable {
|
|
||||||
return newEOFInstructionSetForTesting()
|
|
||||||
}
|
|
||||||
|
|
||||||
func newEOFInstructionSetForTesting() JumpTable {
|
|
||||||
instructionSet := newPragueInstructionSet()
|
|
||||||
enableEOF(&instructionSet)
|
|
||||||
return validate(instructionSet)
|
|
||||||
}
|
|
||||||
|
|
||||||
func newPragueInstructionSet() JumpTable {
|
func newPragueInstructionSet() JumpTable {
|
||||||
instructionSet := newCancunInstructionSet()
|
instructionSet := newCancunInstructionSet()
|
||||||
enable7702(&instructionSet) // EIP-7702 Setcode transaction type
|
enable7702(&instructionSet) // EIP-7702 Setcode transaction type
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue