diff --git a/cmd/eofdump/eofparser.go b/cmd/eofdump/eofparser.go index c6f3b87f43ca..298ec0557e84 100644 --- a/cmd/eofdump/eofparser.go +++ b/cmd/eofdump/eofparser.go @@ -215,7 +215,6 @@ func parseAndValidate(s string, isInitCode bool) (*vm.Container, error) { } func parse(b []byte, isInitCode bool) (*vm.Container, error) { - var c vm.Container if err := c.UnmarshalBinary(b, isInitCode); err != nil { return nil, err diff --git a/cmd/eofdump/parse_test.go b/cmd/eofdump/parse_test.go index 9953af7ed8dc..67df0ec8075e 100644 --- a/cmd/eofdump/parse_test.go +++ b/cmd/eofdump/parse_test.go @@ -50,13 +50,13 @@ func FuzzEofParsing(f *testing.F) { if err := c.UnmarshalBinary(data, true); err == nil { c.ValidateCode(&jt, true) if have := c.MarshalBinary(); !bytes.Equal(have, data) { - f.Fatal("Unmarshal-> Marshal failure!") + t.Fatal("Unmarshal-> Marshal failure!") } } if err := c.UnmarshalBinary(data, false); err == nil { c.ValidateCode(&jt, false) if have := c.MarshalBinary(); !bytes.Equal(have, data) { - f.Fatal("Unmarshal-> Marshal failure!") + t.Fatal("Unmarshal-> Marshal failure!") } } if !bytes.Equal(cpy, data) { @@ -131,7 +131,6 @@ func testEofParse(t *testing.T, isInitCode bool, wantFile string) { } } line++ - } corpus.Close() } diff --git a/core/vm/analysis_eof.go b/core/vm/analysis_eof.go index ac27788213a4..7cc35bd4bba8 100644 --- a/core/vm/analysis_eof.go +++ b/core/vm/analysis_eof.go @@ -36,7 +36,9 @@ func eofCodeBitmapInternal(code, bits bitvec) bitvec { pc++ switch { - case op >= PUSH1 && op <= PUSH32: + case op < PUSH1: + continue + case op <= PUSH32: numbits = uint16(op - PUSH1 + 1) case op == RJUMP || op == RJUMPI || op == CALLF || op == JUMPF || op == DATALOADN: numbits = 2 diff --git a/core/vm/eips.go b/core/vm/eips.go index 1f33dbc507c8..71d51f81efe0 100644 --- a/core/vm/eips.go +++ b/core/vm/eips.go @@ -535,6 +535,13 @@ func enable4762(jt *JumpTable) { } // enableEOF applies the EOF changes. +// OBS! For EOF, there are two changes: +// 1. Two separate jumptables are required. One, EOF-jumptable, is used by +// eof contracts. This one contains things like RJUMP. +// 2. The regular non-eof jumptable also needs to be modified, specifically to +// modify how EXTCODECOPY works under the hood. +// +// This method _only_ deals with case 1. func enableEOF(jt *JumpTable) { // Deprecate opcodes undefined := &operation{ diff --git a/core/vm/eof.go b/core/vm/eof.go index ac2d12682c55..d09f65fca720 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 - code [][]byte - sections []*Container - containerCode [][]byte - data []byte - dataSize int // might be more than len(data) + types []*functionMetadata + codeSections [][]byte + subContainers []*Container + subContainerCodes [][]byte + data []byte + dataSize int // might be more than len(data) } // functionMetadata is an EOF function signature. @@ -92,15 +92,15 @@ 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.code))) - for _, code := range c.code { - b = binary.BigEndian.AppendUint16(b, uint16(len(code))) + b = binary.BigEndian.AppendUint16(b, uint16(len(c.codeSections))) + for _, codeSection := range c.codeSections { + b = binary.BigEndian.AppendUint16(b, uint16(len(codeSection))) } var encodedContainer [][]byte - if len(c.sections) != 0 { + if len(c.subContainers) != 0 { b = append(b, kindContainer) - b = binary.BigEndian.AppendUint16(b, uint16(len(c.sections))) - for _, section := range c.sections { + b = binary.BigEndian.AppendUint16(b, uint16(len(c.subContainers))) + for _, section := range c.subContainers { encoded := section.MarshalBinary() b = binary.BigEndian.AppendUint16(b, uint16(len(encoded))) encodedContainer = append(encodedContainer, encoded) @@ -114,7 +114,7 @@ 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.code { + for _, code := range c.codeSections { b = append(b, code...) } for _, section := range encodedContainer { @@ -228,7 +228,7 @@ func (c *Container) unmarshalSubContainer(b []byte, isInitcode bool, topLevel bo // Parse types section. idx := offsetTerminator + 1 - var types []*functionMetadata + var types = make([]*functionMetadata, 0, typesSize/4) for i := 0; i < typesSize/4; i++ { sig := &functionMetadata{ inputs: b[idx+i*4], @@ -253,45 +253,44 @@ func (c *Container) unmarshalSubContainer(b []byte, isInitcode bool, topLevel bo // Parse code sections. idx += typesSize - code := make([][]byte, len(codeSizes)) + codeSections := make([][]byte, len(codeSizes)) for i, size := range codeSizes { if size == 0 { return fmt.Errorf("%w for section %d: size must not be 0", ErrInvalidCodeSize, i) } - code[i] = b[idx : idx+size] + codeSections[i] = b[idx : idx+size] idx += size } - c.code = code - + c.codeSections = codeSections // Parse the optional container sizes. if len(containerSizes) != 0 { if len(containerSizes) > maxContainerSections { return fmt.Errorf("%w number of container section exceed: %v: have %v", ErrInvalidContainerSectionSize, maxContainerSections, len(containerSizes)) } - containerCode := make([][]byte, 0, len(containerSizes)) - container := make([]*Container, 0, len(containerSizes)) + subContainerCodes := make([][]byte, 0, len(containerSizes)) + subContainers := make([]*Container, 0, len(containerSizes)) for i, size := range containerSizes { if size == 0 || idx+size > len(b) { return fmt.Errorf("%w for section %d: size must not be 0", ErrInvalidContainerSectionSize, i) } - c := new(Container) + subC := new(Container) end := min(idx+size, len(b)) - if err := c.unmarshalSubContainer(b[idx:end], isInitcode, false); err != nil { + if err := subC.unmarshalSubContainer(b[idx:end], isInitcode, false); err != nil { if topLevel { return fmt.Errorf("%w in sub container %d", err, i) } return err } - container = append(container, c) - containerCode = append(containerCode, b[idx:end]) + subContainers = append(subContainers, subC) + subContainerCodes = append(subContainerCodes, b[idx:end]) idx += size } - c.sections = container - c.containerCode = containerCode + c.subContainers = subContainers + c.subContainerCodes = subContainerCodes } - // Parse data section. + //Parse data section. end := len(b) if !isInitcode { end = min(idx+dataSize, len(b)) @@ -327,7 +326,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.code[index] + code = c.codeSections[index] ) if _, ok := visited[index]; !ok { res, err := validateCode(code, index, c, jt, refBy == refByEOFCreate) @@ -359,10 +358,10 @@ 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.code) { + if len(visited) != len(c.codeSections) { return ErrUnreachableCode } - for idx, container := range c.sections { + for idx, container := range c.subContainers { reference, ok := subContainerVisited[idx] if !ok { return ErrOrphanedSubcontainer @@ -444,14 +443,14 @@ func (c *Container) String() string { result += fmt.Sprintf("KindType: %02x\n", kindTypes) result += fmt.Sprintf("TypesSize: %04x\n", len(c.types)*4) result += fmt.Sprintf("KindCode: %02x\n", kindCode) - result += fmt.Sprintf("CodeSize: %04x\n", len(c.code)) - for i, code := range c.code { + result += fmt.Sprintf("CodeSize: %04x\n", len(c.codeSections)) + for i, code := range c.codeSections { result += fmt.Sprintf("Code %v length: %04x\n", i, len(code)) } - if len(c.sections) != 0 { + if len(c.subContainers) != 0 { result += fmt.Sprintf("KindContainer: %02x\n", kindContainer) - result += fmt.Sprintf("ContainerSize: %04x\n", len(c.sections)) - for i, section := range c.sections { + result += fmt.Sprintf("ContainerSize: %04x\n", len(c.subContainers)) + for i, section := range c.subContainers { result += fmt.Sprintf("Container %v length: %04x\n", i, len(section.MarshalBinary())) } } @@ -464,10 +463,10 @@ func (c *Container) String() string { for i, typ := range c.types { result += fmt.Sprintf("Type %v: %v\n", i, hex.EncodeToString([]byte{typ.inputs, typ.outputs, byte(typ.maxStackHeight >> 8), byte(typ.maxStackHeight & 0x00ff)})) } - for i, code := range c.code { + for i, code := range c.codeSections { result += fmt.Sprintf("Code %v: %v\n", i, hex.EncodeToString(code)) } - for i, section := range c.sections { + for i, section := range c.subContainers { result += fmt.Sprintf("Section %v: %v\n", i, hex.EncodeToString(section.MarshalBinary())) } result += fmt.Sprintf("Data: %v\n", hex.EncodeToString(c.data)) diff --git a/core/vm/eof_instructions.go b/core/vm/eof_instructions.go index 087a49f0f214..800d14d7b85a 100644 --- a/core/vm/eof_instructions.go +++ b/core/vm/eof_instructions.go @@ -16,11 +16,6 @@ package vm -// opExtCodeCopyEOF implements the EXTCODECOPY opcode for EOF-enabled forks. -func opExtCodeCopyEOF(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { - panic("not implemented") -} - // opRjump implements the RJUMP opcode. func opRjump(pc *uint64, interpreter *EVMInterpreter, scope *ScopeContext) ([]byte, error) { panic("not implemented") diff --git a/core/vm/eof_test.go b/core/vm/eof_test.go index e057db844f81..6c9925dec3c9 100644 --- a/core/vm/eof_test.go +++ b/core/vm/eof_test.go @@ -32,18 +32,18 @@ func TestEOFMarshaling(t *testing.T) { }{ { want: Container{ - types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, - code: [][]byte{common.Hex2Bytes("604200")}, - data: []byte{0x01, 0x02, 0x03}, - dataSize: 3, + 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}}, - code: [][]byte{common.Hex2Bytes("604200")}, - data: []byte{0x01, 0x02, 0x03}, - dataSize: 3, + types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, + codeSections: [][]byte{common.Hex2Bytes("604200")}, + data: []byte{0x01, 0x02, 0x03}, + dataSize: 3, }, }, { @@ -53,7 +53,7 @@ func TestEOFMarshaling(t *testing.T) { {inputs: 2, outputs: 3, maxStackHeight: 4}, {inputs: 1, outputs: 1, maxStackHeight: 1}, }, - code: [][]byte{ + codeSections: [][]byte{ common.Hex2Bytes("604200"), common.Hex2Bytes("6042604200"), common.Hex2Bytes("00"), @@ -82,11 +82,11 @@ func TestEOFSubcontainer(t *testing.T) { t.Fatal(err) } container := Container{ - types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, - code: [][]byte{common.Hex2Bytes("604200")}, - sections: []*Container{subcontainer}, - data: []byte{0x01, 0x02, 0x03}, - dataSize: 3, + types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, + codeSections: [][]byte{common.Hex2Bytes("604200")}, + subContainers: []*Container{subcontainer}, + data: []byte{0x01, 0x02, 0x03}, + dataSize: 3, } var ( b = container.MarshalBinary() diff --git a/core/vm/validate.go b/core/vm/validate.go index a235f67ca23b..70d3b0d1b922 100644 --- a/core/vm/validate.go +++ b/core/vm/validate.go @@ -82,8 +82,8 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable, count = 0 op OpCode analysis bitvec - visitedCode = make(map[int]struct{}) - visitedSubcontainers = make(map[int]int) + visitedCode map[int]struct{} + visitedSubcontainers map[int]int hasReturnContract bool hasStop bool ) @@ -129,6 +129,9 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable, if container.types[arg].outputs == 0x80 { return nil, fmt.Errorf("%w: section %v", ErrInvalidCallArgument, arg) } + if visitedCode == nil { + visitedCode = make(map[int]struct{}) + } visitedCode[arg] = struct{}{} case JUMPF: arg, _ := parseUint16(code[i+1:]) @@ -138,6 +141,9 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable, if container.types[arg].outputs != 0x80 && container.types[arg].outputs > container.types[section].outputs { return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrInvalidOutputs, arg, len(container.types), i) } + if visitedCode == nil { + visitedCode = make(map[int]struct{}) + } visitedCode[arg] = struct{}{} case DATALOADN: arg, _ := parseUint16(code[i+1:]) @@ -150,8 +156,11 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable, return nil, ErrIncompatibleContainerKind } arg := int(code[i+1]) - if arg >= len(container.sections) { - return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrUnreachableCode, arg, len(container.sections), i) + if arg >= len(container.subContainers) { + return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrUnreachableCode, arg, len(container.subContainers), i) + } + if visitedSubcontainers == nil { + visitedSubcontainers = make(map[int]int) } // We need to store per subcontainer how it was referenced if v, ok := visitedSubcontainers[arg]; ok && v != refByReturnContract { @@ -164,12 +173,15 @@ func validateCode(code []byte, section int, container *Container, jt *JumpTable, visitedSubcontainers[arg] = refByReturnContract case EOFCREATE: arg := int(code[i+1]) - if arg >= len(container.sections) { - return nil, fmt.Errorf("%w: arg %d, last %d, pos %d", ErrUnreachableCode, arg, len(container.sections), i) + 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.sections[arg]; len(ct.data) != ct.dataSize { + if ct := container.subContainers[arg]; len(ct.data) != ct.dataSize { return nil, fmt.Errorf("%w: container %d, have %d, claimed %d, pos %d", ErrEOFCreateWithTruncatedSection, arg, len(ct.data), ct.dataSize, i) } + if visitedSubcontainers == nil { + visitedSubcontainers = make(map[int]int) + } if _, ok := visitedSubcontainers[arg]; ok { return nil, fmt.Errorf("section already referenced, arg :%d", arg) } diff --git a/core/vm/validate_linear.go b/core/vm/validate_linear.go index d89483c63dfd..cdc001b9d914 100644 --- a/core/vm/validate_linear.go +++ b/core/vm/validate_linear.go @@ -6,22 +6,37 @@ import ( "github.com/ethereum/go-ethereum/params" ) -type bounds struct { - min int - max int -} - func validateControlFlow(code []byte, section int, metadata []*functionMetadata, jt *JumpTable) (int, error) { var ( - stackBounds = make(map[int]*bounds) maxStackHeight = int(metadata[section].inputs) debugging = !true + visitCount = 0 + next = make([]int, 0, 1) ) - - setBounds := func(pos, min, maxi int) *bounds { - stackBounds[pos] = &bounds{min, maxi} + var ( + stackBoundsMax = make([]uint16, len(code)) + stackBoundsMin = make([]uint16, len(code)) + ) + setBounds := func(pos, min, maxi int) { + // The stackboundMax slice is a bit peculiar. We use `0` to denote + // not set. Therefore, we use `1` to represent the value `0`, and so on. + // So if the caller wants to store `1` as max bound, we internally store it as + // `2`. + if stackBoundsMax[pos] == 0 { // Not yet set + visitCount++ + } + if maxi < 65535 { + stackBoundsMax[pos] = uint16(maxi + 1) + } + stackBoundsMin[pos] = uint16(min) maxStackHeight = max(maxStackHeight, maxi) - return stackBounds[pos] + } + getStackMaxMin := func(pos int) (ok bool, min, max int) { + maxi := stackBoundsMax[pos] + if maxi == 0 { // Not yet set + return false, 0, 0 + } + return true, int(stackBoundsMin[pos]), int(maxi - 1) } // set the initial stack bounds setBounds(0, int(metadata[section].inputs), int(metadata[section].inputs)) @@ -29,97 +44,89 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata, qualifiedExit := false for pos := 0; pos < len(code); pos++ { op := OpCode(code[pos]) - currentBounds := stackBounds[pos] - if currentBounds == nil { + ok, currentStackMin, currentStackMax := getStackMaxMin(pos) + if !ok { if debugging { fmt.Printf("Stack bounds not set: %v at %v \n", op, pos) } return 0, ErrUnreachableCode } - if debugging { - fmt.Println(pos, op, maxStackHeight, currentBounds) + fmt.Println(pos, op, maxStackHeight, currentStackMin, currentStackMax) } - var ( - currentStackMax = currentBounds.max - currentStackMin = currentBounds.min - ) - switch op { case CALLF: arg, _ := parseUint16(code[pos+1:]) newSection := metadata[arg] - if want, have := int(newSection.inputs), currentBounds.min; want > have { + if want, have := int(newSection.inputs), currentStackMin; want > have { return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos) } - if have, limit := currentBounds.max+int(newSection.maxStackHeight)-int(newSection.inputs), int(params.StackLimit); have > limit { + if have, limit := currentStackMax+int(newSection.maxStackHeight)-int(newSection.inputs), int(params.StackLimit); have > limit { return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: limit}, pos) } change := int(newSection.outputs) - int(newSection.inputs) currentStackMax += change currentStackMin += change case RETF: - if currentBounds.max != currentBounds.min { - return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", ErrInvalidOutputs, currentBounds.max, currentBounds.min, pos) + if currentStackMax != currentStackMin { + return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", ErrInvalidOutputs, currentStackMax, currentStackMin, pos) } have := int(metadata[section].outputs) if have >= maxOutputItems { return 0, fmt.Errorf("%w: at pos %d", ErrInvalidNonReturningFlag, pos) } - if want := currentBounds.min; have != want { + if want := currentStackMin; have != want { return 0, fmt.Errorf("%w: have %d, want %d, at pos %d", ErrInvalidOutputs, have, want, pos) } qualifiedExit = true case JUMPF: arg, _ := parseUint16(code[pos+1:]) newSection := metadata[arg] - if have, limit := currentBounds.max+int(newSection.maxStackHeight)-int(newSection.inputs), int(params.StackLimit); have > limit { + if have, limit := currentStackMax+int(newSection.maxStackHeight)-int(newSection.inputs), int(params.StackLimit); have > limit { return 0, fmt.Errorf("%w: at pos %d", ErrStackOverflow{stackLen: have, limit: limit}, pos) } if newSection.outputs == 0x80 { - if want, have := int(newSection.inputs), currentBounds.min; want > have { + if want, have := int(newSection.inputs), currentStackMin; want > have { return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos) } } else { - if currentBounds.max != currentBounds.min { - return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", ErrInvalidOutputs, currentBounds.max, currentBounds.min, pos) + if currentStackMax != currentStackMin { + return 0, fmt.Errorf("%w: max %d, min %d, at pos %d", ErrInvalidOutputs, currentStackMax, currentStackMin, pos) } - if have, want := currentBounds.max, int(metadata[section].outputs)+int(newSection.inputs)-int(newSection.outputs); have != want { + if have, want := currentStackMax, int(metadata[section].outputs)+int(newSection.inputs)-int(newSection.outputs); have != want { return 0, fmt.Errorf("%w: at pos %d", ErrInvalidOutputs, pos) } } qualifiedExit = qualifiedExit || newSection.outputs < maxOutputItems case DUPN: arg := int(code[pos+1]) + 1 - if want, have := arg, currentBounds.min; want > have { + if want, have := arg, currentStackMin; want > have { return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos) } case SWAPN: arg := int(code[pos+1]) + 1 - if want, have := arg+1, currentBounds.min; want > have { + if want, have := arg+1, currentStackMin; want > have { return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos) } case EXCHANGE: arg := int(code[pos+1]) n := arg>>4 + 1 m := arg&0x0f + 1 - if want, have := n+m+1, currentBounds.min; want > have { + if want, have := n+m+1, currentStackMin; want > have { return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos) } default: - if want, have := jt[op].minStack, currentBounds.min; want > have { + if want, have := jt[op].minStack, currentStackMin; want > have { return 0, fmt.Errorf("%w: at pos %d", ErrStackUnderflow{stackLen: have, required: want}, pos) } } - if !terminals[op] && op != CALLF { change := int(params.StackLimit) - jt[op].maxStack currentStackMax += change currentStackMin += change } - - var next []int + next = next[:0] switch op { case RJUMP: nextPos := pos + 2 + parseInt16(code[pos+1:]) @@ -127,19 +134,20 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata, // We set the stack bounds of the destination // and skip the argument, only for RJUMP, all other opcodes are handled later if nextPos+1 < pos { - nextBounds, ok := stackBounds[nextPos+1] + ok, nextMin, nextMax := getStackMaxMin(nextPos + 1) if !ok { return 0, ErrInvalidBackwardJump } - if nextBounds.max != currentStackMax || nextBounds.min != currentStackMin { + if nextMax != currentStackMax || nextMin != currentStackMin { return 0, ErrInvalidMaxStackHeight } - } - nextBounds, ok := stackBounds[nextPos+1] - if !ok { - setBounds(nextPos+1, currentStackMin, currentStackMax) } else { - setBounds(nextPos+1, min(nextBounds.min, currentStackMin), max(nextBounds.max, currentStackMax)) + ok, nextMin, nextMax := getStackMaxMin(nextPos + 1) + if !ok { + setBounds(nextPos+1, currentStackMin, currentStackMax) + } else { + setBounds(nextPos+1, min(nextMin, currentStackMin), max(nextMax, currentStackMax)) + } } case RJUMPI: arg := parseInt16(code[pos+1:]) @@ -172,23 +180,23 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata, } if nextPC > pos { // target reached via forward jump or seq flow - nextBounds, ok := stackBounds[nextPC] + ok, nextMin, nextMax := getStackMaxMin(nextPC) if !ok { setBounds(nextPC, currentStackMin, currentStackMax) } else { - setBounds(nextPC, min(nextBounds.min, currentStackMin), max(nextBounds.max, currentStackMax)) + setBounds(nextPC, min(nextMin, currentStackMin), max(nextMax, currentStackMax)) } } else { // target reached via backwards jump - nextBounds, ok := stackBounds[nextPC] + ok, nextMin, nextMax := getStackMaxMin(nextPC) if !ok { return 0, ErrInvalidBackwardJump } - if currentStackMax != nextBounds.max { - return 0, fmt.Errorf("%w want %d as current max got %d at pos %d,", ErrInvalidBackwardJump, currentStackMax, nextBounds.max, pos) + if currentStackMax != nextMax { + return 0, fmt.Errorf("%w want %d as current max got %d at pos %d,", ErrInvalidBackwardJump, currentStackMax, nextMax, pos) } - if currentStackMin != nextBounds.min { - return 0, fmt.Errorf("%w want %d as current min got %d at pos %d,", ErrInvalidBackwardJump, currentStackMin, nextBounds.min, pos) + if currentStackMin != nextMin { + return 0, fmt.Errorf("%w want %d as current min got %d at pos %d,", ErrInvalidBackwardJump, currentStackMin, nextMin, pos) } } } @@ -199,7 +207,6 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata, } else { pos = next[0] } - } if qualifiedExit != (metadata[section].outputs < maxOutputItems) { return 0, fmt.Errorf("%w no RETF or qualified JUMPF", ErrInvalidNonReturningFlag) @@ -213,5 +220,5 @@ func validateControlFlow(code []byte, section int, metadata []*functionMetadata, } return 0, fmt.Errorf("%w in code section %d: have %d, want %d", ErrInvalidMaxStackHeight, section, maxStackHeight, metadata[section].maxStackHeight) } - return len(stackBounds), nil + return visitCount, nil } diff --git a/core/vm/validate_test.go b/core/vm/validate_test.go index aa62d3881695..3dc3603db190 100644 --- a/core/vm/validate_test.go +++ b/core/vm/validate_test.go @@ -247,9 +247,9 @@ func TestValidateCode(t *testing.T) { }, } { container := &Container{ - types: test.metadata, - data: make([]byte, 0), - sections: make([]*Container, 0), + types: test.metadata, + data: make([]byte, 0), + subContainers: make([]*Container, 0), } _, err := validateCode(test.code, test.section, container, &pragueEOFInstructionSet, false) if !errors.Is(err, test.err) { @@ -269,9 +269,9 @@ func BenchmarkRJUMPI(b *testing.B) { } code = append(code, byte(STOP)) container := &Container{ - types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, - data: make([]byte, 0), - sections: make([]*Container, 0), + types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, + data: make([]byte, 0), + subContainers: make([]*Container, 0), } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -299,9 +299,9 @@ 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), - sections: make([]*Container, 0), + types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, + data: make([]byte, 0), + subContainers: make([]*Container, 0), } b.ResetTimer() for i := 0; i < b.N; i++ { @@ -322,7 +322,7 @@ func BenchmarkEOFValidation(b *testing.B) { } code = append(code, byte(STOP)) // First container - container.code = append(container.code, code) + container.codeSections = append(container.codeSections, code) container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0}) inner := []byte{ @@ -330,12 +330,12 @@ func BenchmarkEOFValidation(b *testing.B) { } for i := 0; i < 1023; i++ { - container.code = append(container.code, inner) + container.codeSections = append(container.codeSections, inner) container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0}) } for i := 0; i < 12; i++ { - container.code[i+1] = code + container.codeSections[i+1] = code } bin := container.MarshalBinary() @@ -365,7 +365,7 @@ func BenchmarkEOFValidation2(b *testing.B) { } code = append(code, byte(STOP)) // First container - container.code = append(container.code, code) + container.codeSections = append(container.codeSections, code) container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0}) inner := []byte{ @@ -384,7 +384,7 @@ func BenchmarkEOFValidation2(b *testing.B) { } for i := 0; i < 1023; i++ { - container.code = append(container.code, inner) + container.codeSections = append(container.codeSections, inner) container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0}) } @@ -425,15 +425,15 @@ func BenchmarkEOFValidation3(b *testing.B) { } code = append(code, byte(STOP)) // First container - container.code = append(container.code, code) + container.codeSections = append(container.codeSections, code) container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 1}) for i := 0; i < 1023; i++ { - container.code = append(container.code, []byte{byte(RJUMP), 0x00, 0x00, byte(JUMPF), 0x00, 0x00}) + container.codeSections = append(container.codeSections, []byte{byte(RJUMP), 0x00, 0x00, byte(JUMPF), 0x00, 0x00}) container.types = append(container.types, &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 0}) } for i := 0; i < 65; i++ { - container.code[i+1] = append(snippet, byte(STOP)) + container.codeSections[i+1] = append(snippet, byte(STOP)) container.types[i+1] = &functionMetadata{inputs: 0, outputs: 0x80, maxStackHeight: 1} } bin := container.MarshalBinary() @@ -468,9 +468,9 @@ func BenchmarkRJUMPI_2(b *testing.B) { } code = append(code, byte(STOP)) container := &Container{ - types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, - data: make([]byte, 0), - sections: make([]*Container, 0), + types: []*functionMetadata{{inputs: 0, outputs: 0x80, maxStackHeight: 1}}, + data: make([]byte, 0), + subContainers: make([]*Container, 0), } b.ResetTimer() for i := 0; i < b.N; i++ {