diff --git a/core/vm/contract.go b/core/vm/contract.go index 8ab4cb8f47..dd53f934c5 100644 --- a/core/vm/contract.go +++ b/core/vm/contract.go @@ -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) { diff --git a/core/vm/eof.go b/core/vm/eof.go index a5406283d5..5a7d104aeb 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -66,12 +66,12 @@ 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 // contains all the offsets of the codeSections. The last item marks the end + subContainers []*Container + subContainerOffsets []int // contains all the offsets of the subContainers. The last item marks the end + dataSize int // might be more than len(data) + rawContainer []byte } // functionMetadata is an EOF function signature. @@ -105,8 +105,44 @@ func (meta *functionMetadata) checkStackMax(stackMax int) error { return nil } +// codeSectionAt returns the code section at index. +// returns an empty slice if the index is out of bounds. +func (c *Container) codeSectionAt(index int) []byte { + if index >= len(c.codeSectionOffsets)-1 || index < 0 { + return c.rawContainer[0:0] + } + return c.rawContainer[c.codeSectionOffsets[index]:c.codeSectionOffsets[index+1]] +} + +// subContainerAt returns the sub container at index. +// returns an empty slice if the index is out of bounds. +func (c *Container) subContainerAt(index int) []byte { + if index >= len(c.subContainerOffsets)-1 || index < 0 { + return c.rawContainer[0:0] + } + return c.rawContainer[c.subContainerOffsets[index]:c.subContainerOffsets[index+1]] +} + +func (c *Container) dataOffset() int { + if len(c.subContainerOffsets) > 0 { + return c.subContainerOffsets[len(c.subContainerOffsets)-1] + } + return c.codeSectionOffsets[len(c.codeSectionOffsets)-1] +} + +func (c *Container) dataLen() int { + return len(c.rawContainer) - c.dataOffset() +} + +func (c *Container) getDataAt(offset, length uint64) []byte { + return getData(c.rawContainer, uint64(c.dataOffset())+offset, length) +} + // MarshalBinary encodes an EOF container into binary format. func (c *Container) MarshalBinary() []byte { + // Drop the end markers + codeSectionOffsets := c.codeSectionOffsets[:len(c.codeSectionOffsets)-1] + // Build EOF prefix. b := make([]byte, 2) copy(b, eofMagic) @@ -116,9 +152,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(codeSectionOffsets))) + for s := range codeSectionOffsets { + b = binary.BigEndian.AppendUint16(b, uint16(len(c.codeSectionAt(s)))) } var encodedContainer [][]byte if len(c.subContainers) != 0 { @@ -138,13 +174,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 codeSectionOffsets { + b = append(b, c.codeSectionAt(s)...) } for _, section := range encodedContainer { b = append(b, section...) } - b = append(b, c.data...) + b = append(b, c.rawContainer[c.dataOffset():]...) return b } @@ -282,21 +318,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 + // add the end marker to the codeSection offsets + c.codeSectionOffsets = append(codeSectionOffsets, 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 +348,21 @@ 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 + // add the end marker to the subContainer offsets + c.subContainerOffsets = append(subContainerOffsets, idx) } //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.rawContainer = b return nil } @@ -355,7 +390,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.codeSectionAt(index) ) if _, ok := visited[index]; !ok { res, err := validateCode(code, index, c, jt, refBy == refByEOFCreate) @@ -387,7 +422,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)-1 { return errUnreachableCode } for idx, container := range c.subContainers { @@ -464,6 +499,8 @@ func sum(list []int) (s int) { } func (c *Container) String() string { + // Drop the end markers + codeSectionOffsets := c.codeSectionOffsets[:len(c.codeSectionOffsets)-1] var output = []string{ "Header", fmt.Sprintf(" - EOFMagic: %02x", eofMagic), @@ -471,14 +508,13 @@ func (c *Container) String() string { fmt.Sprintf(" - KindType: %02x", kindTypes), fmt.Sprintf(" - TypesSize: %04x", len(c.types)*4), fmt.Sprintf(" - KindCode: %02x", kindCode), + fmt.Sprintf(" - Number of code sections: %d", len(codeSectionOffsets)), 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.dataSize), } - for i, code := range c.codeSections { - output = append(output, fmt.Sprintf(" - Code section %d length: %04x", i, len(code))) + for i := range codeSectionOffsets { + output = append(output, fmt.Sprintf(" - Code section %d length: %04x", i, len(c.codeSectionAt(i)))) } - output = append(output, fmt.Sprintf(" - Number of subcontainers: %d", len(c.subContainers))) if len(c.subContainers) > 0 { for i, section := range c.subContainers { @@ -490,12 +526,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 codeSectionOffsets { + output = append(output, fmt.Sprintf(" - Code section %d: %#x", i, c.codeSectionAt(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.dataOffset():])) return strings.Join(output, "\n") } diff --git a/core/vm/eof_instructions.go b/core/vm/eof_instructions.go index a0ed9ada83..7b1dcaf969 100644 --- a/core/vm/eof_instructions.go +++ b/core/vm/eof_instructions.go @@ -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,20 @@ 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)-1 { return nil, fmt.Errorf("invalid subcontainer") } + subContainer := scope.Contract.Container.subContainerAt(int(idx)) // 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(len(subContainer)) + 31) / 32) if ok := scope.Contract.UseGas(hashingCharge, interpreter.evm.Config.Tracer, tracing.GasChangeUnspecified); !ok { return nil, ErrGasUintOverflow } @@ -159,7 +155,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, subContainer, gas, &value, &salt) if suberr != nil { stackvalue.Clear() } else { @@ -182,16 +178,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)-1 { return nil, fmt.Errorf("invalid subcontainer") } ret := scope.Memory.GetPtr(offset.Uint64(), size.Uint64()) - containerCode := scope.Contract.Container.subContainerCodes[idx] + containerCode := scope.Contract.Container.subContainerAt(int(idx)) if len(containerCode) == 0 { return nil, errors.New("nonexistant subcontainer") } @@ -202,11 +197,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 +226,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 := scope.Contract.Container.getDataAt(offset, 32) scope.Stack.push(stackItem.SetBytes(data)) } return nil, nil @@ -239,10 +235,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 := scope.Contract.Container.getDataAt(offset, 32) scope.Stack.push(new(uint256.Int).SetBytes(data)) *pc += 2 // move past 2 byte immediate return nil, nil @@ -250,8 +245,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 +259,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 := scope.Contract.Container.getDataAt(offset.Uint64(), size.Uint64()) scope.Memory.Set(memOffset.Uint64(), size.Uint64(), data) return nil, nil } @@ -273,8 +267,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 +277,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 +287,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 ) diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go index 0a9cf638ce..fd4fbe30b1 100644 --- a/core/vm/eof_test.go +++ b/core/vm/eof_test.go @@ -24,6 +24,49 @@ 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...) + } + codeSectionOffsets = append(codeSectionOffsets, idx) + + var subContainerOffsets []int + 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...) + } + // set the subContainer end marker + subContainerOffsets = append(subContainerOffsets, idx) + } + + testBytes = append(testBytes, data...) + + return Container{ + types: types, + codeSectionOffsets: codeSectionOffsets, + subContainers: subContainers, + subContainerOffsets: subContainerOffsets, + dataSize: dataSize, + rawContainer: testBytes, + } +} + func TestEOFMarshaling(t *testing.T) { for i, test := range []struct { want Container @@ -31,18 +74,10 @@ 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, 22}, // 604200, endMarker + dataSize: 3, + rawContainer: common.Hex2Bytes("ef000101000402000100030400030000800001604200010203"), }, }, { @@ -52,12 +87,8 @@ 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, 40}, // 604200, 6042604200, 00, endMarker + rawContainer: common.Hex2Bytes("ef000101000c02000300030005000104000000008000010203000401010001604200604260420000"), }, }, } { @@ -80,13 +111,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 diff --git a/core/vm/eof_validation.go b/core/vm/eof_validation.go index 514f9fb58c..6334ff9c6b 100644 --- a/core/vm/eof_validation.go +++ b/core/vm/eof_validation.go @@ -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) diff --git a/core/vm/eof_validation_test.go b/core/vm/eof_validation_test.go index f262744a21..424000a4c4 100644 --- a/core/vm/eof_validation_test.go +++ b/core/vm/eof_validation_test.go @@ -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) } diff --git a/core/vm/instructions.go b/core/vm/instructions.go index a297008bdd..508f73de0c 100644 --- a/core/vm/instructions.go +++ b/core/vm/instructions.go @@ -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) ) diff --git a/core/vm/interpreter.go b/core/vm/interpreter.go index ea08500de0..5ba4e07623 100644 --- a/core/vm/interpreter.go +++ b/core/vm/interpreter.go @@ -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