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..2bef6f0a75 100644 --- a/core/vm/eof.go +++ b/core/vm/eof.go @@ -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") } diff --git a/core/vm/eof_instructions.go b/core/vm/eof_instructions.go index a0ed9ada83..4fc5c2c568 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,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 ) diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go index 0a9cf638ce..707e87e584 100644 --- a/core/vm/eof_test.go +++ b/core/vm/eof_test.go @@ -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 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 diff --git a/statetest b/statetest new file mode 100644 index 0000000000..19be488d3a Binary files /dev/null and b/statetest differ