Move EOF interpreter to use same PC semantics as legacy EVM

Update the EOF container parsing to allow the PC to use the same semantics as the legacy EVM.
 Also, a new test container maker is necessary to handle the particulars of the unit tests, mostly unrelated to
This commit is contained in:
Danno Ferrin 2024-10-14 16:38:35 -06:00 committed by Marius van der Wijden
parent b0dedd720b
commit 5bfacf4ba2
9 changed files with 241 additions and 164 deletions

View file

@ -146,10 +146,8 @@ func (c *Contract) AsDelegate() *Contract {
}
// GetOp returns the n'th element in the contract's byte array
func (c *Contract) GetOp(n uint64, s uint64) OpCode {
if c.IsEOF() && n < uint64(len(c.Container.codeSections[s])) {
return OpCode(c.Container.codeSections[s][n])
} else if n < uint64(len(c.Code)) {
func (c *Contract) GetOp(n uint64) OpCode {
if n < uint64(len(c.Code)) {
return OpCode(c.Code[n])
}
return STOP
@ -201,13 +199,6 @@ func (c *Contract) IsEOF() bool {
return c.Container != nil
}
func (c *Contract) CodeAt(section uint64) []byte {
if c.Container == nil {
return c.Code
}
return c.Container.codeSections[section]
}
// SetCallCode sets the code of the contract and address of the backing data
// object
func (c *Contract) SetCallCode(addr *common.Address, hash common.Hash, code []byte, container *Container) {

View file

@ -66,12 +66,15 @@ func isEOFVersion1(code []byte) bool {
// 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)
types []*functionMetadata
codeSectionOffsets []int
codeSectionEnd int
subContainers []*Container
subContainerOffsets []int
subContainerEnd int
dataOffest int
dataSize int // might be more than len(data)
rawContainer []byte
}
// functionMetadata is an EOF function signature.
@ -105,6 +108,46 @@ func (meta *functionMetadata) checkStackMax(stackMax int) error {
return nil
}
func (c *Container) codeSectionSize(s int) int {
if s >= len(c.codeSectionOffsets) || s < 0 {
return 0
} else if s == len(c.codeSectionOffsets)-1 {
return c.codeSectionEnd - c.codeSectionOffsets[s]
}
return c.codeSectionOffsets[s+1] - c.codeSectionOffsets[s]
}
func (c *Container) codeSectionBytes(s int) []byte {
if s >= len(c.codeSectionOffsets) || s < 0 {
return c.rawContainer[0:0]
} else if s == len(c.codeSectionOffsets)-1 {
return c.rawContainer[c.codeSectionOffsets[s]:c.codeSectionEnd]
}
return c.rawContainer[c.codeSectionOffsets[s]:c.codeSectionOffsets[s+1]]
}
func (c *Container) subContainerSize(s int) int {
if s >= len(c.subContainerOffsets) || s < 0 {
return 0
} else if s == len(c.subContainerOffsets)-1 {
return c.subContainerEnd - c.subContainerOffsets[s]
}
return c.subContainerOffsets[s+1] - c.subContainerOffsets[s]
}
func (c *Container) subContainerBytes(s int) []byte {
if s >= len(c.subContainerOffsets) || s < 0 {
return c.rawContainer[0:0]
} else if s == len(c.subContainerOffsets)-1 {
return c.rawContainer[c.subContainerOffsets[s]:c.subContainerEnd]
}
return c.rawContainer[c.subContainerOffsets[s]:c.subContainerOffsets[s+1]]
}
func (c *Container) dataLen() int {
return len(c.rawContainer) - c.dataOffest
}
// MarshalBinary encodes an EOF container into binary format.
func (c *Container) MarshalBinary() []byte {
// Build EOF prefix.
@ -116,9 +159,9 @@ func (c *Container) MarshalBinary() []byte {
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)))
b = binary.BigEndian.AppendUint16(b, uint16(len(c.codeSectionOffsets)))
for s := range c.codeSectionOffsets {
b = binary.BigEndian.AppendUint16(b, uint16(c.codeSectionSize(s)))
}
var encodedContainer [][]byte
if len(c.subContainers) != 0 {
@ -138,13 +181,13 @@ func (c *Container) MarshalBinary() []byte {
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 s := range c.codeSectionOffsets {
b = append(b, c.codeSectionBytes(s)...)
}
for _, section := range encodedContainer {
b = append(b, section...)
}
b = append(b, c.data...)
b = append(b, c.rawContainer[c.dataOffest:]...)
return b
}
@ -282,21 +325,22 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
// Parse code sections.
idx += typesSize
codeSections := make([][]byte, len(codeSizes))
codeSectionOffsets := make([]int, 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]
codeSectionOffsets[i] = idx
idx += size
}
c.codeSections = codeSections
c.codeSectionOffsets = codeSectionOffsets
c.codeSectionEnd = idx
// 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))
subContainerOffsets := make([]int, 0, len(containerSizes))
subContainers := make([]*Container, 0, len(containerSizes))
for i, size := range containerSizes {
if size == 0 || idx+size > len(b) {
@ -311,23 +355,22 @@ func (c *Container) unmarshalContainer(b []byte, isInitcode bool, topLevel bool)
return err
}
subContainers = append(subContainers, subC)
subContainerCodes = append(subContainerCodes, b[idx:end])
subContainerOffsets = append(subContainerOffsets, idx)
idx += size
}
c.subContainers = subContainers
c.subContainerCodes = subContainerCodes
c.subContainerEnd = idx
c.subContainerOffsets = subContainerOffsets
}
//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]
c.dataOffest = idx
c.rawContainer = b
return nil
}
@ -355,7 +398,7 @@ func (c *Container) validateSubContainer(jt *JumpTable, refBy int) error {
// should not mean 2 and 3 should be visited twice
var (
index = toVisit[0]
code = c.codeSections[index]
code = c.codeSectionBytes(index)
)
if _, ok := visited[index]; !ok {
res, err := validateCode(code, index, c, jt, refBy == refByEOFCreate)
@ -387,7 +430,7 @@ func (c *Container) validateSubContainer(jt *JumpTable, refBy int) error {
toVisit = toVisit[1:]
}
// Make sure every code section is visited at least once.
if len(visited) != len(c.codeSections) {
if len(visited) != len(c.codeSectionOffsets) {
return errUnreachableCode
}
for idx, container := range c.subContainers {
@ -472,11 +515,11 @@ func (c *Container) String() string {
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)),
fmt.Sprintf(" - DataSize: %04x", c.dataLen()),
fmt.Sprintf(" - Number of code sections: %d", len(c.codeSectionOffsets)),
}
for i, code := range c.codeSections {
output = append(output, fmt.Sprintf(" - Code section %d length: %04x", i, len(code)))
for i := range c.codeSectionOffsets {
output = append(output, fmt.Sprintf(" - Code section %d length: %04x", i, c.codeSectionSize(i)))
}
output = append(output, fmt.Sprintf(" - Number of subcontainers: %d", len(c.subContainers)))
@ -490,12 +533,12 @@ func (c *Container) String() string {
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 := range c.codeSectionOffsets {
output = append(output, fmt.Sprintf(" - Code section %d: %#x", i, c.codeSectionBytes(i)))
}
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))
output = append(output, fmt.Sprintf(" - Data: %#x", c.rawContainer[c.dataOffest:]))
return strings.Join(output, "\n")
}

View file

@ -31,8 +31,7 @@ import (
// opRjump implements the RJUMP opcode.
func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
var (
code = scope.Contract.CodeAt(scope.CodeSection)
offset = parseInt16(code[*pc+1:])
offset = parseInt16(scope.Contract.Code[*pc+1:])
)
// move pc past op and operand (+3), add relative offset, subtract 1 to
// account for interpreter loop.
@ -54,8 +53,7 @@ func opRjumpi(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
// opRjumpv implements the RJUMPV opcode
func opRjumpv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
var (
code = scope.Contract.CodeAt(scope.CodeSection)
maxIndex = uint64(code[*pc+1]) + 1
maxIndex = uint64(scope.Contract.Code[*pc+1]) + 1
idx = scope.Stack.pop()
)
if idx, overflow := idx.Uint64WithOverflow(); overflow || idx >= maxIndex {
@ -64,7 +62,7 @@ func opRjumpv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
*pc += 1 + maxIndex*2
return nil, nil
}
offset := parseInt16(code[*pc+2+2*idx.Uint64():])
offset := parseInt16(scope.Contract.Code[*pc+2+2*idx.Uint64():])
// move pc past op and count byte (2), move past count number of 16bit offsets (count*2), add relative offset, subtract 1 to
// account for interpreter loop.
*pc = uint64(int64(*pc+2+maxIndex*2) + int64(offset) - 1)
@ -74,9 +72,8 @@ func opRjumpv(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
// opCallf implements the CALLF opcode
func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
var (
code = scope.Contract.CodeAt(scope.CodeSection)
idx = binary.BigEndian.Uint16(code[*pc+1:])
typ = scope.Contract.Container.types[idx]
idx = binary.BigEndian.Uint16(scope.Contract.Code[*pc+1:])
typ = scope.Contract.Container.types[idx]
)
if scope.Stack.len()+int(typ.maxStackHeight)-int(typ.inputs) > 1024 {
return nil, fmt.Errorf("stack overflow")
@ -91,7 +88,7 @@ func opCallf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
}
scope.ReturnStack = append(scope.ReturnStack, retCtx)
scope.CodeSection = uint64(idx)
*pc = uint64(math.MaxUint64)
*pc = uint64(scope.Contract.Container.codeSectionOffsets[idx]) - 1
return nil, nil
}
@ -111,15 +108,14 @@ func opRetf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
// opJumpf implements the JUMPF opcode
func opJumpf(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
var (
code = scope.Contract.CodeAt(scope.CodeSection)
idx = binary.BigEndian.Uint16(code[*pc+1:])
typ = scope.Contract.Container.types[idx]
idx = binary.BigEndian.Uint16(scope.Contract.Code[*pc+1:])
typ = scope.Contract.Container.types[idx]
)
if scope.Stack.len()+int(typ.maxStackHeight)-int(typ.inputs) > 1024 {
return nil, fmt.Errorf("stack overflow")
}
scope.CodeSection = uint64(idx)
*pc = uint64(math.MaxUint64)
*pc = uint64(scope.Contract.Container.codeSectionOffsets[idx]) - 1
return nil, nil
}
@ -129,20 +125,19 @@ func opEOFCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
return nil, ErrWriteProtection
}
var (
code = scope.Contract.CodeAt(scope.CodeSection)
idx = code[*pc+1]
idx = scope.Contract.Code[*pc+1]
value = scope.Stack.pop()
salt = scope.Stack.pop()
offset, size = scope.Stack.pop(), scope.Stack.pop()
input = scope.Memory.GetCopy(offset.Uint64(), size.Uint64())
)
if int(idx) >= len(scope.Contract.Container.subContainerCodes) {
if int(idx) >= len(scope.Contract.Container.subContainerOffsets) {
return nil, fmt.Errorf("invalid subcontainer")
}
// Deduct hashing charge
// Since size <= params.MaxInitCodeSize, these multiplication cannot overflow
hashingCharge := (params.Keccak256WordGas) * ((uint64(len(scope.Contract.Container.subContainerCodes[idx])) + 31) / 32)
hashingCharge := (params.Keccak256WordGas) * ((uint64(scope.Contract.Container.subContainerSize(int(idx))) + 31) / 32)
if ok := scope.Contract.UseGas(hashingCharge, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified); !ok {
return nil, ErrGasUintOverflow
}
@ -159,7 +154,7 @@ func opEOFCreate(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
scope.Contract.UseGas(gas, interpreter.evm.Config.Tracer, tracing.GasChangeCallContractCreation2)
// Skip the immediate
*pc += 1
res, addr, returnGas, suberr := interpreter.evm.EOFCreate(scope.Contract, input, scope.Contract.Container.subContainerCodes[idx], gas, &value, &salt)
res, addr, returnGas, suberr := interpreter.evm.EOFCreate(scope.Contract, input, scope.Contract.Container.subContainerBytes(int(idx)), gas, &value, &salt)
if suberr != nil {
stackvalue.Clear()
} else {
@ -182,16 +177,15 @@ func opReturnContract(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte
return nil, errors.New("returncontract in non-initcode mode")
}
var (
code = scope.Contract.CodeAt(scope.CodeSection)
idx = code[*pc+1]
idx = scope.Contract.Code[*pc+1]
offset = scope.Stack.pop()
size = scope.Stack.pop()
)
if int(idx) >= len(scope.Contract.Container.subContainerCodes) {
if int(idx) >= len(scope.Contract.Container.subContainerOffsets) {
return nil, fmt.Errorf("invalid subcontainer")
}
ret := scope.Memory.GetPtr(offset.Uint64(), size.Uint64())
containerCode := scope.Contract.Container.subContainerCodes[idx]
containerCode := scope.Contract.Container.subContainerBytes(int(idx))
if len(containerCode) == 0 {
return nil, errors.New("nonexistant subcontainer")
}
@ -202,11 +196,12 @@ func opReturnContract(pc *uint64, interpreter *EVMInterpreter, scope *ScopeConte
}
// append the auxdata
c.data = append(c.data, ret...)
if len(c.data) < c.dataSize {
c.rawContainer = append(c.rawContainer, ret...)
newDataSize := c.dataLen()
if newDataSize < c.dataSize {
return nil, errors.New("incomplete aux data")
}
c.dataSize = len(c.data)
c.dataSize = newDataSize
// probably unneeded as subcontainers are deeply validated
if err := c.ValidateCode(interpreter.tableEOF, false); err != nil {
@ -230,7 +225,7 @@ func opDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
stackItem.Clear()
scope.Stack.push(&stackItem)
} else {
data := getData(scope.Contract.Container.data, offset, 32)
data := getData(scope.Contract.Container.rawContainer, uint64(scope.Contract.Container.dataOffest)+offset, 32)
scope.Stack.push(stackItem.SetBytes(data))
}
return nil, nil
@ -239,10 +234,9 @@ func opDataLoad(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
// opDataLoadN implements the DATALOADN opcode
func opDataLoadN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
var (
code = scope.Contract.CodeAt(scope.CodeSection)
offset = uint64(binary.BigEndian.Uint16(code[*pc+1:]))
offset = uint64(binary.BigEndian.Uint16(scope.Contract.Code[*pc+1:]))
)
data := getData(scope.Contract.Container.data, offset, 32)
data := getData(scope.Contract.Container.rawContainer, uint64(scope.Contract.Container.dataOffest)+offset, 32)
scope.Stack.push(new(uint256.Int).SetBytes(data))
*pc += 2 // move past 2 byte immediate
return nil, nil
@ -250,8 +244,7 @@ func opDataLoadN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) (
// opDataSize implements the DATASIZE opcode
func opDataSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
length := len(scope.Contract.Container.data)
item := uint256.NewInt(uint64(length))
item := uint256.NewInt(uint64(scope.Contract.Container.dataLen()))
scope.Stack.push(item)
return nil, nil
}
@ -265,7 +258,7 @@ func opDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
)
// These values are checked for overflow during memory expansion calculation
// (the memorySize function on the opcode).
data := getData(scope.Contract.Container.data, offset.Uint64(), size.Uint64())
data := getData(scope.Contract.Container.rawContainer, uint64(scope.Contract.Container.dataOffest)+offset.Uint64(), size.Uint64())
scope.Memory.Set(memOffset.Uint64(), size.Uint64(), data)
return nil, nil
}
@ -273,8 +266,7 @@ func opDataCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
// opDupN implements the DUPN opcode
func opDupN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
var (
code = scope.Contract.CodeAt(scope.CodeSection)
index = int(code[*pc+1]) + 1
index = int(scope.Contract.Code[*pc+1]) + 1
)
scope.Stack.dup(index)
*pc += 1 // move past immediate
@ -284,8 +276,7 @@ func opDupN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byt
// opSwapN implements the SWAPN opcode
func opSwapN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
var (
code = scope.Contract.CodeAt(scope.CodeSection)
index = int(code[*pc+1]) + 1
index = int(scope.Contract.Code[*pc+1]) + 1
)
scope.Stack.swap(index + 1)
*pc += 1 // move past immediate
@ -295,8 +286,7 @@ func opSwapN(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
// opExchange implements the EXCHANGE opcode
func opExchange(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
var (
code = scope.Contract.CodeAt(scope.CodeSection)
index = int(code[*pc+1])
index = int(scope.Contract.Code[*pc+1])
n = (index >> 4) + 1
m = (index & 0x0F) + 1
)

View file

@ -24,6 +24,52 @@ import (
"github.com/ethereum/go-ethereum/common"
)
func MakeTestContainer(
types []*functionMetadata,
codeSections [][]byte,
subContainers []*Container,
data []byte,
dataSize int) Container {
testBytes := make([]byte, 0, 16*1024)
codeSectionOffsets := make([]int, 0, len(codeSections))
idx := 0
for _, code := range codeSections {
codeSectionOffsets = append(codeSectionOffsets, idx)
idx += len(code)
testBytes = append(testBytes, code...)
}
codeSectionEnd := idx
var subContainerOffsets []int
subContainerEnd := 0
if len(subContainers) > 0 {
subContainerOffsets = make([]int, len(subContainers))
for _, subContainer := range subContainers {
containerBytes := subContainer.MarshalBinary()
subContainerOffsets = append(subContainerOffsets, idx)
idx += len(containerBytes)
testBytes = append(testBytes, containerBytes...)
}
subContainerEnd = idx
}
testBytes = append(testBytes, data...)
return Container{
types: types,
codeSectionOffsets: codeSectionOffsets,
codeSectionEnd: codeSectionEnd,
subContainers: subContainers,
subContainerOffsets: subContainerOffsets,
subContainerEnd: subContainerEnd,
dataOffest: subContainerEnd,
dataSize: dataSize,
rawContainer: testBytes,
}
}
func TestEOFMarshaling(t *testing.T) {
for i, test := range []struct {
want Container
@ -31,18 +77,12 @@ func TestEOFMarshaling(t *testing.T) {
}{
{
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,
types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
codeSectionOffsets: []int{19}, // 604200
codeSectionEnd: 22,
dataOffest: 22,
dataSize: 3,
rawContainer: common.Hex2Bytes("ef000101000402000100030400030000800001604200010203"),
},
},
{
@ -52,12 +92,10 @@ func TestEOFMarshaling(t *testing.T) {
{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{},
codeSectionOffsets: []int{31, 34, 39}, // 604200, 6042604200, 00
codeSectionEnd: 40,
dataOffest: 40,
rawContainer: common.Hex2Bytes("ef000101000c02000300030005000104000000008000010203000401010001604200604260420000"),
},
},
} {
@ -80,13 +118,13 @@ func TestEOFSubcontainer(t *testing.T) {
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,
}
container := MakeTestContainer(
[]*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
[][]byte{common.Hex2Bytes("604200")},
[]*Container{subcontainer},
[]byte{0x01, 0x02, 0x03},
3,
)
var (
b = container.MarshalBinary()
got Container

View file

@ -148,8 +148,8 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
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)
if arg+32 > container.dataLen() {
return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", errInvalidDataloadNArgument, arg, container.dataLen(), i)
}
case RETURNCONTRACT:
if !isInitCode {
@ -176,8 +176,8 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable,
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 ct := container.subContainers[arg]; ct.dataLen() != ct.dataSize {
return nil, fmt.Errorf("%w: container %d, have %d, claimed %d, pos %d", errEOFCreateWithTruncatedSection, arg, ct.dataLen(), ct.dataSize, i)
}
if visitedSubcontainers == nil {
visitedSubcontainers = make(map[int]int)

View file

@ -246,12 +246,14 @@ func TestValidateCode(t *testing.T) {
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)
container := MakeTestContainer(
test.metadata,
[][]byte{test.code},
[]*Container{},
[]byte{},
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)
}
@ -270,14 +272,16 @@ func BenchmarkRJUMPI(b *testing.B) {
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),
}
container := MakeTestContainer(
[]*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
[][]byte{code},
[]*Container{},
[]byte{},
0,
)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := validateCode(code, 0, container, &eofInstructionSet, false)
_, err := validateCode(code, 0, &container, &eofInstructionSet, false)
if err != nil {
b.Fatal(err)
}
@ -302,14 +306,16 @@ func BenchmarkRJUMPV(b *testing.B) {
}
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),
}
container := MakeTestContainer(
[]*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
[][]byte{code},
[]*Container{},
[]byte{},
0,
)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := validateCode(code, 0, container, &eofInstructionSet, false)
_, err := validateCode(code, 0, &container, &eofInstructionSet, false)
if err != nil {
b.Fatal(err)
}
@ -322,30 +328,32 @@ func BenchmarkRJUMPV(b *testing.B) {
// - 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
types := make([]*functionMetadata, maxSections)
codeSections := make([][]byte, maxSections)
var code []byte
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})
codeSections = append(codeSections, append(code, byte(STOP)))
types = append(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})
codeSections = append(codeSections, inner)
types = append(types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
}
for i := 0; i < 12; i++ {
container.codeSections[i+1] = append(code, byte(RETF))
codeSections[i+1] = append(code, byte(RETF))
}
container := MakeTestContainer(types, codeSections, []*Container{}, []byte{}, 0)
bin := container.MarshalBinary()
if len(bin) > 48*1024 {
b.Fatal("Exceeds 48Kb")
@ -368,17 +376,18 @@ func BenchmarkEOFValidation(b *testing.B) {
// - 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
types := make([]*functionMetadata, maxSections)
codeSections := make([][]byte, maxSections)
var code []byte
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})
codeSections = append(codeSections, code)
types = append(types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0})
inner := []byte{
byte(CALLF), 0x03, 0xE8,
@ -397,10 +406,11 @@ func BenchmarkEOFValidation2(b *testing.B) {
}
for i := 0; i < 1023; i++ {
container.codeSections = append(container.codeSections, inner)
container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
codeSections = append(codeSections, inner)
types = append(types, &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 0})
}
container := MakeTestContainer(types, codeSections, []*Container{}, []byte{}, 0)
bin := container.MarshalBinary()
if len(bin) > 48*1024 {
b.Fatal("Exceeds 48Kb")
@ -424,7 +434,9 @@ func BenchmarkEOFValidation2(b *testing.B) {
// - 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
maxSections := 1024
types := make([]*functionMetadata, maxSections)
codeSections := make([][]byte, maxSections)
var code []byte
snippet := []byte{
byte(PUSH0),
@ -437,25 +449,25 @@ func BenchmarkEOFValidation3(b *testing.B) {
}
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})
codeSections = append(codeSections, code)
types = append(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})
codeSections = append(codeSections, []byte{byte(RJUMP), 0x00, 0x00, byte(RETF)})
types = append(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}
codeSections[i+1] = append(snippet, byte(RETF))
types[i+1] = &functionMetadata{inputs: 0, outputs: 0, maxStackHeight: 1}
}
container := MakeTestContainer(types, codeSections, []*Container{}, []byte{}, 0)
bin := container.MarshalBinary()
if len(bin) > 48*1024 {
b.Fatal("Exceeds 48Kb")
@ -487,14 +499,16 @@ func BenchmarkRJUMPI_2(b *testing.B) {
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),
}
container := MakeTestContainer(
[]*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}},
[][]byte{code},
[]*Container{},
[]byte{},
0,
)
b.ResetTimer()
for i := 0; i < b.N; i++ {
_, err := validateCode(code, 0, container, &eofInstructionSet, false)
_, err := validateCode(code, 0, &container, &eofInstructionSet, false)
if err != nil {
b.Fatal(err)
}

View file

@ -363,7 +363,7 @@ func opExtCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext)
}
func opCodeSize(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(scope.Contract.CodeAt(scope.CodeSection)))))
scope.Stack.push(new(uint256.Int).SetUint64(uint64(len(scope.Contract.Code))))
return nil, nil
}
@ -378,7 +378,7 @@ func opCodeCopy(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([
uint64CodeOffset = math.MaxUint64
}
codeCopy := getData(scope.Contract.CodeAt(scope.CodeSection), uint64CodeOffset, length.Uint64())
codeCopy := getData(scope.Contract.Code, uint64CodeOffset, length.Uint64())
scope.Memory.Set(memOffset.Uint64(), length.Uint64(), codeCopy)
return nil, nil
}
@ -909,7 +909,7 @@ func opRevert(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]b
}
func opUndefined(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
return nil, &ErrInvalidOpCode{opcode: OpCode(scope.Contract.CodeAt(scope.CodeSection)[*pc])}
return nil, &ErrInvalidOpCode{opcode: OpCode(scope.Contract.Code[*pc])}
}
func opStop(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
@ -988,13 +988,12 @@ func makeLog(size int) executionFunc {
// opPush1 is a specialized version of pushN
func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
var (
code = scope.Contract.CodeAt(scope.CodeSection)
codeLen = uint64(len(code))
codeLen = uint64(len(scope.Contract.Code))
integer = new(uint256.Int)
)
*pc += 1
if *pc < codeLen {
scope.Stack.push(integer.SetUint64(uint64(code[*pc])))
scope.Stack.push(integer.SetUint64(uint64(scope.Contract.Code[*pc])))
} else {
scope.Stack.push(integer.Clear())
}
@ -1005,8 +1004,7 @@ func opPush1(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]by
func makePush(size uint64, pushByteSize int) executionFunc {
return func(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) {
var (
code = scope.Contract.CodeAt(scope.CodeSection)
codeLen = len(code)
codeLen = len(scope.Contract.Code)
start = min(codeLen, int(*pc+1))
end = min(codeLen, start+pushByteSize)
)

View file

@ -232,6 +232,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool, i
res []byte // result of the opcode execution function
debug = in.evm.Config.Tracer != nil
)
// Don't move this deferred function, it's placed before the OnOpcode-deferred method,
// so that it gets executed _after_: the OnOpcode needs the stacks before
// they are returned to the pools
@ -243,6 +244,8 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool, i
if contract.IsEOF() {
jt = in.tableEOF
// Set EOF entrypoint
pc = uint64(contract.Container.codeSectionOffsets[0])
} else {
jt = in.table
}
@ -279,7 +282,7 @@ func (in *EVMInterpreter) Run(contract *Contract, input []byte, readOnly bool, i
// Get the operation from the jump table and validate the stack to ensure there are
// enough stack items available to perform the operation.
op = contract.GetOp(pc, callContext.CodeSection)
op = contract.GetOp(pc)
operation := jt[op]
cost = operation.constantGas // For tracing
// Validate stack

BIN
statetest Normal file

Binary file not shown.