diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index e175a17034..f99c480834 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -545,7 +545,7 @@ func asmPushBytes(ops *OpStream, spec *OpSpec, args []string) error { return nil } -func base32DecdodeAnyPadding(x string) (val []byte, err error) { +func base32DecodeAnyPadding(x string) (val []byte, err error) { val, err = base32.StdEncoding.WithPadding(base32.NoPadding).DecodeString(x) if err != nil { // try again with standard padding @@ -567,7 +567,7 @@ func parseBinaryArgs(args []string) (val []byte, consumed int, err error) { err = errors.New("byte base32 arg lacks close paren") return } - val, err = base32DecdodeAnyPadding(arg[open+1 : close]) + val, err = base32DecodeAnyPadding(arg[open+1 : close]) if err != nil { return } @@ -595,7 +595,7 @@ func parseBinaryArgs(args []string) (val []byte, consumed int, err error) { err = fmt.Errorf("need literal after 'byte %s'", arg) return } - val, err = base32DecdodeAnyPadding(args[1]) + val, err = base32DecodeAnyPadding(args[1]) if err != nil { return } @@ -1399,25 +1399,29 @@ func typecheck(expected, got StackType) bool { return expected == got } -var spaces = [256]uint8{'\t': 1, ' ': 1} +// newline not included since handled in scanner +var tokenSeparators = [256]bool{'\t': true, ' ': true, ';': true} -func fieldsFromLine(line string) []string { - var fields []string +func tokensFromLine(line string) []string { + var tokens []string i := 0 - for i < len(line) && spaces[line[i]] != 0 { + for i < len(line) && tokenSeparators[line[i]] { + if line[i] == ';' { + tokens = append(tokens, ";") + } i++ } start := i - inString := false - inBase64 := false + inString := false // tracked to allow spaces and comments inside + inBase64 := false // tracked to allow '//' inside for i < len(line) { - if spaces[line[i]] == 0 { // if not space + if !tokenSeparators[line[i]] { // if not space switch line[i] { case '"': // is a string literal? if !inString { - if i == 0 || i > 0 && spaces[line[i-1]] != 0 { + if i == 0 || i > 0 && tokenSeparators[line[i-1]] { inString = true } } else { @@ -1428,9 +1432,9 @@ func fieldsFromLine(line string) []string { case '/': // is a comment? if i < len(line)-1 && line[i+1] == '/' && !inBase64 && !inString { if start != i { // if a comment without whitespace - fields = append(fields, line[start:i]) + tokens = append(tokens, line[start:i]) } - return fields + return tokens } case '(': // is base64( seq? prefix := line[start:i] @@ -1446,19 +1450,29 @@ func fieldsFromLine(line string) []string { i++ continue } + + // we've hit a space, end last token unless inString + if !inString { - field := line[start:i] - fields = append(fields, field) - if field == "base64" || field == "b64" { - inBase64 = true - } else if inBase64 { + token := line[start:i] + tokens = append(tokens, token) + if line[i] == ';' { + tokens = append(tokens, ";") + } + if inBase64 { inBase64 = false + } else if token == "base64" || token == "b64" { + inBase64 = true } } i++ + // gobble up consecutive whitespace (but notice semis) if !inString { - for i < len(line) && spaces[line[i]] != 0 { + for i < len(line) && tokenSeparators[line[i]] { + if line[i] == ';' { + tokens = append(tokens, ";") + } i++ } start = i @@ -1467,10 +1481,10 @@ func fieldsFromLine(line string) []string { // add rest of the string if any if start < len(line) { - fields = append(fields, line[start:i]) + tokens = append(tokens, line[start:i]) } - return fields + return tokens } func (ops *OpStream) trace(format string, args ...interface{}) { @@ -1531,6 +1545,16 @@ func (ops *OpStream) trackStack(args StackTypes, returns StackTypes, instruction } } +// splitTokens breaks tokens into two slices at the first semicolon. +func splitTokens(tokens []string) (current, rest []string) { + for i, token := range tokens { + if token == ";" { + return tokens[:i], tokens[i+1:] + } + } + return tokens, nil +} + // assemble reads text from an input and accumulates the program func (ops *OpStream) assemble(text string) error { fin := strings.NewReader(text) @@ -1541,73 +1565,80 @@ func (ops *OpStream) assemble(text string) error { for scanner.Scan() { ops.sourceLine++ line := scanner.Text() - line = strings.TrimSpace(line) - if len(line) == 0 { - ops.trace("%3d: 0 line\n", ops.sourceLine) - continue - } - if strings.HasPrefix(line, "//") { - ops.trace("%3d: // line\n", ops.sourceLine) - continue - } - if strings.HasPrefix(line, "#pragma") { - ops.trace("%3d: #pragma line\n", ops.sourceLine) - ops.pragma(line) - continue - } - fields := fieldsFromLine(line) - if len(fields) == 0 { - ops.trace("%3d: no fields\n", ops.sourceLine) - continue - } - // we're about to begin processing opcodes, so settle the Version - if ops.Version == assemblerNoVersion { - ops.Version = AssemblerDefaultVersion - } - if ops.versionedPseudoOps == nil { - ops.versionedPseudoOps = prepareVersionedPseudoTable(ops.Version) - } - opstring := fields[0] - if opstring[len(opstring)-1] == ':' { - ops.createLabel(opstring[:len(opstring)-1]) - fields = fields[1:] - if len(fields) == 0 { - ops.trace("%3d: label only\n", ops.sourceLine) + tokens := tokensFromLine(line) + if len(tokens) > 0 { + if first := tokens[0]; first[0] == '#' { + directive := first[1:] + switch directive { + case "pragma": + ops.pragma(tokens) + ops.trace("%3d: #pragma line\n", ops.sourceLine) + default: + ops.errorf("Unknown directive: %s", directive) + } continue } - opstring = fields[0] } - spec, expandedName, ok := getSpec(ops, opstring, fields[1:]) - if ok { - ops.trace("%3d: %s\t", ops.sourceLine, opstring) - ops.recordSourceLine() - if spec.Modes == modeApp { - ops.HasStatefulOps = true + for current, next := splitTokens(tokens); len(current) > 0 || len(next) > 0; current, next = splitTokens(next) { + if len(current) == 0 { + continue } - args, returns := spec.Arg.Types, spec.Return.Types - if spec.refine != nil { - nargs, nreturns := spec.refine(&ops.known, fields[1:]) - if nargs != nil { - args = nargs - } - if nreturns != nil { - returns = nreturns - } + // we're about to begin processing opcodes, so settle the Version + if ops.Version == assemblerNoVersion { + ops.Version = AssemblerDefaultVersion } - ops.trackStack(args, returns, append([]string{expandedName}, fields[1:]...)) - spec.asm(ops, &spec, fields[1:]) - if spec.deadens() { // An unconditional branch deadens the following code - ops.known.deaden() + if ops.versionedPseudoOps == nil { + ops.versionedPseudoOps = prepareVersionedPseudoTable(ops.Version) } - if spec.Name == "callsub" { - // since retsub comes back to the callsub, it is an entry point like a label - ops.known.label() + opstring := current[0] + if opstring[len(opstring)-1] == ':' { + ops.createLabel(opstring[:len(opstring)-1]) + current = current[1:] + if len(current) == 0 { + ops.trace("%3d: label only\n", ops.sourceLine) + continue + } + opstring = current[0] + } + spec, expandedName, ok := getSpec(ops, opstring, current[1:]) + if ok { + ops.trace("%3d: %s\t", ops.sourceLine, opstring) + ops.recordSourceLine() + if spec.Modes == modeApp { + ops.HasStatefulOps = true + } + args, returns := spec.Arg.Types, spec.Return.Types + if spec.refine != nil { + nargs, nreturns := spec.refine(&ops.known, current[1:]) + if nargs != nil { + args = nargs + } + if nreturns != nil { + returns = nreturns + } + } + ops.trackStack(args, returns, append([]string{expandedName}, current[1:]...)) + spec.asm(ops, &spec, current[1:]) + if spec.deadens() { // An unconditional branch deadens the following code + ops.known.deaden() + } + if spec.Name == "callsub" { + // since retsub comes back to the callsub, it is an entry point like a label + ops.known.label() + } } ops.trace("\n") continue } } + if err := scanner.Err(); err != nil { + if errors.Is(err, bufio.ErrTooLong) { + err = errors.New("line too long") + } + ops.error(err) + } + // backward compatibility: do not allow jumps behind last instruction in v1 if ops.Version <= 1 { for label, dest := range ops.labels { @@ -1635,21 +1666,20 @@ func (ops *OpStream) assemble(text string) error { return nil } -func (ops *OpStream) pragma(line string) error { - fields := strings.Split(line, " ") - if fields[0] != "#pragma" { - return ops.errorf("invalid syntax: %s", fields[0]) +func (ops *OpStream) pragma(tokens []string) error { + if tokens[0] != "#pragma" { + return ops.errorf("invalid syntax: %s", tokens[0]) } - if len(fields) < 2 { + if len(tokens) < 2 { return ops.error("empty pragma") } - key := fields[1] + key := tokens[1] switch key { case "version": - if len(fields) < 3 { + if len(tokens) < 3 { return ops.error("no version value") } - value := fields[2] + value := tokens[2] var ver uint64 if ops.pending.Len() > 0 { return ops.error("#pragma version is only allowed before instructions") @@ -1674,10 +1704,10 @@ func (ops *OpStream) pragma(line string) error { } return nil case "typetrack": - if len(fields) < 3 { + if len(tokens) < 3 { return ops.error("no typetrack value") } - value := fields[2] + value := tokens[2] on, err := strconv.ParseBool(value) if err != nil { return ops.errorf("bad #pragma typetrack: %#v", value) diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 24d9dffd07..f1afb9cc25 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -552,8 +552,7 @@ func summarize(trace *strings.Builder) string { func testProg(t testing.TB, source string, ver uint64, expected ...Expect) *OpStream { t.Helper() - program := strings.ReplaceAll(source, ";", "\n") - ops, err := assembleWithTrace(program, ver) + ops, err := assembleWithTrace(source, ver) if len(expected) == 0 { if len(ops.Errors) > 0 || err != nil || ops == nil || ops.Program == nil { t.Log(summarize(ops.Trace)) @@ -567,13 +566,13 @@ func testProg(t testing.TB, source string, ver uint64, expected ...Expect) *OpSt require.NotNil(t, ops.Program) // It should always be possible to Disassemble dis, err := Disassemble(ops.Program) - require.NoError(t, err, program) + require.NoError(t, err, source) // And, while the disassembly may not match input // exactly, the assembly of the disassembly should // give the same bytecode ops2, err := AssembleStringWithVersion(notrack(dis), ver) if len(ops2.Errors) > 0 || err != nil || ops2 == nil || ops2.Program == nil { - t.Log(program) + t.Log(source) t.Log(dis) } require.Empty(t, ops2.Errors) @@ -581,7 +580,7 @@ func testProg(t testing.TB, source string, ver uint64, expected ...Expect) *OpSt require.Equal(t, ops.Program, ops2.Program) } else { if err == nil { - t.Log(program) + t.Log(source) } require.Error(t, err) errors := ops.Errors @@ -701,9 +700,9 @@ func TestAssembleGlobal(t *testing.T) { testProg(t, "global MinTxnFee; int 2; +", AssemblerMaxVersion) testProg(t, "global ZeroAddress; byte 0x12; concat; len", AssemblerMaxVersion) testProg(t, "global MinTxnFee; byte 0x12; concat", AssemblerMaxVersion, - Expect{3, "concat arg 0 wanted type []byte..."}) + Expect{1, "concat arg 0 wanted type []byte..."}) testProg(t, "int 2; global ZeroAddress; +", AssemblerMaxVersion, - Expect{3, "+ arg 1 wanted type uint64..."}) + Expect{1, "+ arg 1 wanted type uint64..."}) } func TestAssembleDefault(t *testing.T) { @@ -1111,209 +1110,91 @@ func TestFieldsFromLine(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - line := "op arg" - fields := fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "arg", fields[1]) - - line = "op arg // test" - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "arg", fields[1]) - - line = "op base64 ABC//==" - fields = fieldsFromLine(line) - require.Equal(t, 3, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "base64", fields[1]) - require.Equal(t, "ABC//==", fields[2]) - - line = "op base64 ABC/==" - fields = fieldsFromLine(line) - require.Equal(t, 3, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "base64", fields[1]) - require.Equal(t, "ABC/==", fields[2]) - - line = "op base64 ABC/== /" - fields = fieldsFromLine(line) - require.Equal(t, 4, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "base64", fields[1]) - require.Equal(t, "ABC/==", fields[2]) - require.Equal(t, "/", fields[3]) - - line = "op base64 ABC/== //" - fields = fieldsFromLine(line) - require.Equal(t, 3, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "base64", fields[1]) - require.Equal(t, "ABC/==", fields[2]) - - line = "op base64 ABC//== //" - fields = fieldsFromLine(line) - require.Equal(t, 3, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "base64", fields[1]) - require.Equal(t, "ABC//==", fields[2]) - - line = "op b64 ABC//== //" - fields = fieldsFromLine(line) - require.Equal(t, 3, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "b64", fields[1]) - require.Equal(t, "ABC//==", fields[2]) - - line = "op b64(ABC//==) // comment" - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "b64(ABC//==)", fields[1]) - - line = "op base64(ABC//==) // comment" - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "base64(ABC//==)", fields[1]) - - line = "op b64(ABC/==) // comment" - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "b64(ABC/==)", fields[1]) - - line = "op base64(ABC/==) // comment" - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "base64(ABC/==)", fields[1]) - - line = "base64(ABC//==)" - fields = fieldsFromLine(line) - require.Equal(t, 1, len(fields)) - require.Equal(t, "base64(ABC//==)", fields[0]) - - line = "b(ABC//==)" - fields = fieldsFromLine(line) - require.Equal(t, 1, len(fields)) - require.Equal(t, "b(ABC", fields[0]) - - line = "b(ABC//==) //" - fields = fieldsFromLine(line) - require.Equal(t, 1, len(fields)) - require.Equal(t, "b(ABC", fields[0]) - - line = "b(ABC ==) //" - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "b(ABC", fields[0]) - require.Equal(t, "==)", fields[1]) - - line = "op base64 ABC)" - fields = fieldsFromLine(line) - require.Equal(t, 3, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "base64", fields[1]) - require.Equal(t, "ABC)", fields[2]) - - line = "op base64 ABC) // comment" - fields = fieldsFromLine(line) - require.Equal(t, 3, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "base64", fields[1]) - require.Equal(t, "ABC)", fields[2]) - - line = "op base64 ABC//) // comment" - fields = fieldsFromLine(line) - require.Equal(t, 3, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, "base64", fields[1]) - require.Equal(t, "ABC//)", fields[2]) - - line = `op "test"` - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `"test"`, fields[1]) - - line = `op "test1 test2"` - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `"test1 test2"`, fields[1]) - - line = `op "test1 test2" // comment` - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `"test1 test2"`, fields[1]) - - line = `op "test1 test2 // not a comment"` - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `"test1 test2 // not a comment"`, fields[1]) - - line = `op "test1 test2 // not a comment" // comment` - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `"test1 test2 // not a comment"`, fields[1]) - - line = `op "test1 test2 // not a comment" // comment` - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `"test1 test2 // not a comment"`, fields[1]) - - line = `op "test1 test2" //` - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `"test1 test2"`, fields[1]) - - line = `op "test1 test2"//` - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `"test1 test2"`, fields[1]) - - line = `op "test1 test2` // non-terminated string literal - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `"test1 test2`, fields[1]) - - line = `op "test1 test2\"` // non-terminated string literal - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `"test1 test2\"`, fields[1]) - - line = `op \"test1 test2\"` // not a string literal - fields = fieldsFromLine(line) - require.Equal(t, 3, len(fields)) - require.Equal(t, "op", fields[0]) - require.Equal(t, `\"test1`, fields[1]) - require.Equal(t, `test2\"`, fields[2]) - - line = `"test1 test2"` - fields = fieldsFromLine(line) - require.Equal(t, 1, len(fields)) - require.Equal(t, `"test1 test2"`, fields[0]) - - line = `\"test1 test2"` - fields = fieldsFromLine(line) - require.Equal(t, 2, len(fields)) - require.Equal(t, `\"test1`, fields[0]) - require.Equal(t, `test2"`, fields[1]) - - line = `"" // test` - fields = fieldsFromLine(line) - require.Equal(t, 1, len(fields)) - require.Equal(t, `""`, fields[0]) + check := func(line string, tokens ...string) { + t.Helper() + assert.Equal(t, tokensFromLine(line), tokens) + } + + check("op arg", "op", "arg") + check("op arg // test", "op", "arg") + check("op base64 ABC//==", "op", "base64", "ABC//==") + check("op base64 base64", "op", "base64", "base64") + check("op base64 base64 //comment", "op", "base64", "base64") + check("op base64 base64; op2 //done", "op", "base64", "base64", ";", "op2") + check("op base64 ABC/==", "op", "base64", "ABC/==") + check("op base64 ABC/== /", "op", "base64", "ABC/==", "/") + check("op base64 ABC/== //", "op", "base64", "ABC/==") + check("op base64 ABC//== //", "op", "base64", "ABC//==") + check("op b64 ABC//== //", "op", "b64", "ABC//==") + check("op b64(ABC//==) // comment", "op", "b64(ABC//==)") + check("op base64(ABC//==) // comment", "op", "base64(ABC//==)") + check("op b64(ABC/==) // comment", "op", "b64(ABC/==)") + check("op base64(ABC/==) // comment", "op", "base64(ABC/==)") + check("base64(ABC//==)", "base64(ABC//==)") + check("b(ABC//==)", "b(ABC") + check("b(ABC//==) //", "b(ABC") + check("b(ABC ==) //", "b(ABC", "==)") + check("op base64 ABC)", "op", "base64", "ABC)") + check("op base64 ABC) // comment", "op", "base64", "ABC)") + check("op base64 ABC//) // comment", "op", "base64", "ABC//)") + check(`op "test"`, "op", `"test"`) + check(`op "test1 test2"`, "op", `"test1 test2"`) + check(`op "test1 test2" // comment`, "op", `"test1 test2"`) + check(`op "test1 test2 // not a comment"`, "op", `"test1 test2 // not a comment"`) + check(`op "test1 test2 // not a comment" // comment`, "op", `"test1 test2 // not a comment"`) + check(`op "test1 test2" //`, "op", `"test1 test2"`) + check(`op "test1 test2"//`, "op", `"test1 test2"`) + check(`op "test1 test2`, "op", `"test1 test2`) // non-terminated string literal + check(`op "test1 test2\"`, "op", `"test1 test2\"`) // non-terminated string literal + check(`op \"test1 test2\"`, "op", `\"test1`, `test2\"`) // not a string literal + check(`"test1 test2"`, `"test1 test2"`) + check(`\"test1 test2"`, `\"test1`, `test2"`) + check(`"" // test`, `""`) + check("int 1; int 2", "int", "1", ";", "int", "2") + check("int 1;;;int 2", "int", "1", ";", ";", ";", "int", "2") + check("int 1; ;int 2;; ; ;; ", "int", "1", ";", ";", "int", "2", ";", ";", ";", ";", ";") + check(";", ";") + check("; ; ;;;;", ";", ";", ";", ";", ";", ";") + check(" ;", ";") + check(" ; ", ";") +} + +func TestSplitTokens(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + check := func(tokens []string, left []string, right []string) { + t.Helper() + current, next := splitTokens(tokens) + assert.Equal(t, left, current) + assert.Equal(t, right, next) + } + + check([]string{"hey,", "how's", ";", ";", "it", "going", ";"}, + []string{"hey,", "how's"}, + []string{";", "it", "going", ";"}, + ) + + check([]string{";"}, + []string{}, + []string{}, + ) + + check([]string{";", "it", "going"}, + []string{}, + []string{"it", "going"}, + ) + + check([]string{"hey,", "how's"}, + []string{"hey,", "how's"}, + nil, + ) + + check([]string{`"hey in quotes;"`, "getting", `";"`, ";", "tricky"}, + []string{`"hey in quotes;"`, "getting", `";"`}, + []string{"tricky"}, + ) + } func TestAssembleRejectNegJump(t *testing.T) { @@ -1798,22 +1679,22 @@ func TestAssembleAsset(t *testing.T) { testProg(t, "asset_holding_get ABC 1", v, Expect{1, "asset_holding_get ABC 1 expects 2 stack arguments..."}) testProg(t, "int 1; asset_holding_get ABC 1", v, - Expect{2, "asset_holding_get ABC 1 expects 2 stack arguments..."}) + Expect{1, "asset_holding_get ABC 1 expects 2 stack arguments..."}) testProg(t, "int 1; int 1; asset_holding_get ABC 1", v, - Expect{3, "asset_holding_get expects 1 immediate argument"}) + Expect{1, "asset_holding_get expects 1 immediate argument"}) testProg(t, "int 1; int 1; asset_holding_get ABC", v, - Expect{3, "asset_holding_get unknown field: \"ABC\""}) + Expect{1, "asset_holding_get unknown field: \"ABC\""}) testProg(t, "byte 0x1234; asset_params_get ABC 1", v, - Expect{2, "asset_params_get ABC 1 arg 0 wanted type uint64..."}) + Expect{1, "asset_params_get ABC 1 arg 0 wanted type uint64..."}) // Test that AssetUnitName is known to return bytes testProg(t, "int 1; asset_params_get AssetUnitName; pop; int 1; +", v, - Expect{5, "+ arg 0 wanted type uint64..."}) + Expect{1, "+ arg 0 wanted type uint64..."}) // Test that AssetTotal is known to return uint64 testProg(t, "int 1; asset_params_get AssetTotal; pop; byte 0x12; concat", v, - Expect{5, "concat arg 0 wanted type []byte..."}) + Expect{1, "concat arg 0 wanted type []byte..."}) testLine(t, "asset_params_get ABC 1", v, "asset_params_get expects 1 immediate argument") testLine(t, "asset_params_get ABC", v, "asset_params_get unknown field: \"ABC\"") @@ -2422,29 +2303,29 @@ func TestSwapTypeCheck(t *testing.T) { t.Parallel() /* reconfirm that we detect this type error */ - testProg(t, "int 1; byte 0x1234; +", AssemblerMaxVersion, Expect{3, "+ arg 1..."}) + testProg(t, "int 1; byte 0x1234; +", AssemblerMaxVersion, Expect{1, "+ arg 1..."}) /* despite swap, we track types */ - testProg(t, "int 1; byte 0x1234; swap; +", AssemblerMaxVersion, Expect{4, "+ arg 0..."}) - testProg(t, "byte 0x1234; int 1; swap; +", AssemblerMaxVersion, Expect{4, "+ arg 1..."}) + testProg(t, "int 1; byte 0x1234; swap; +", AssemblerMaxVersion, Expect{1, "+ arg 0..."}) + testProg(t, "byte 0x1234; int 1; swap; +", AssemblerMaxVersion, Expect{1, "+ arg 1..."}) } func TestDigAsm(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "int 1; dig; +", AssemblerMaxVersion, Expect{2, "dig expects 1 immediate..."}) - testProg(t, "int 1; dig junk; +", AssemblerMaxVersion, Expect{2, "dig unable to parse..."}) + testProg(t, "int 1; dig; +", AssemblerMaxVersion, Expect{1, "dig expects 1 immediate..."}) + testProg(t, "int 1; dig junk; +", AssemblerMaxVersion, Expect{1, "dig unable to parse..."}) testProg(t, "int 1; byte 0x1234; int 2; dig 2; +", AssemblerMaxVersion) testProg(t, "byte 0x32; byte 0x1234; int 2; dig 2; +", AssemblerMaxVersion, - Expect{5, "+ arg 1..."}) + Expect{1, "+ arg 1..."}) testProg(t, "byte 0x32; byte 0x1234; int 2; dig 3; +", AssemblerMaxVersion, - Expect{4, "dig 3 expects 4..."}) + Expect{1, "dig 3 expects 4..."}) testProg(t, "int 1; byte 0x1234; int 2; dig 12; +", AssemblerMaxVersion, - Expect{4, "dig 12 expects 13..."}) + Expect{1, "dig 12 expects 13..."}) // Confirm that digging something out does not ruin our knowledge about the types in the middle testProg(t, "int 1; byte 0x1234; byte 0x1234; dig 2; dig 3; +; pop; +", AssemblerMaxVersion, - Expect{8, "+ arg 1..."}) + Expect{1, "+ arg 1..."}) testProg(t, "int 3; pushbytes \"123456\"; int 1; dig 2; substring3", AssemblerMaxVersion) } @@ -2452,39 +2333,39 @@ func TestDigAsm(t *testing.T) { func TestEqualsTypeCheck(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "int 1; byte 0x1234; ==", AssemblerMaxVersion, Expect{3, "== arg 0..."}) - testProg(t, "int 1; byte 0x1234; !=", AssemblerMaxVersion, Expect{3, "!= arg 0..."}) - testProg(t, "byte 0x1234; int 1; ==", AssemblerMaxVersion, Expect{3, "== arg 0..."}) - testProg(t, "byte 0x1234; int 1; !=", AssemblerMaxVersion, Expect{3, "!= arg 0..."}) + testProg(t, "int 1; byte 0x1234; ==", AssemblerMaxVersion, Expect{1, "== arg 0..."}) + testProg(t, "int 1; byte 0x1234; !=", AssemblerMaxVersion, Expect{1, "!= arg 0..."}) + testProg(t, "byte 0x1234; int 1; ==", AssemblerMaxVersion, Expect{1, "== arg 0..."}) + testProg(t, "byte 0x1234; int 1; !=", AssemblerMaxVersion, Expect{1, "!= arg 0..."}) } func TestDupTypeCheck(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "byte 0x1234; dup; int 1; +", AssemblerMaxVersion, Expect{4, "+ arg 0..."}) + testProg(t, "byte 0x1234; dup; int 1; +", AssemblerMaxVersion, Expect{1, "+ arg 0..."}) testProg(t, "byte 0x1234; int 1; dup; +", AssemblerMaxVersion) - testProg(t, "byte 0x1234; int 1; dup2; +", AssemblerMaxVersion, Expect{4, "+ arg 0..."}) - testProg(t, "int 1; byte 0x1234; dup2; +", AssemblerMaxVersion, Expect{4, "+ arg 1..."}) + testProg(t, "byte 0x1234; int 1; dup2; +", AssemblerMaxVersion, Expect{1, "+ arg 0..."}) + testProg(t, "int 1; byte 0x1234; dup2; +", AssemblerMaxVersion, Expect{1, "+ arg 1..."}) - testProg(t, "byte 0x1234; int 1; dup; dig 1; len", AssemblerMaxVersion, Expect{5, "len arg 0..."}) - testProg(t, "int 1; byte 0x1234; dup; dig 1; !", AssemblerMaxVersion, Expect{5, "! arg 0..."}) + testProg(t, "byte 0x1234; int 1; dup; dig 1; len", AssemblerMaxVersion, Expect{1, "len arg 0..."}) + testProg(t, "int 1; byte 0x1234; dup; dig 1; !", AssemblerMaxVersion, Expect{1, "! arg 0..."}) - testProg(t, "byte 0x1234; int 1; dup2; dig 2; len", AssemblerMaxVersion, Expect{5, "len arg 0..."}) - testProg(t, "int 1; byte 0x1234; dup2; dig 2; !", AssemblerMaxVersion, Expect{5, "! arg 0..."}) + testProg(t, "byte 0x1234; int 1; dup2; dig 2; len", AssemblerMaxVersion, Expect{1, "len arg 0..."}) + testProg(t, "int 1; byte 0x1234; dup2; dig 2; !", AssemblerMaxVersion, Expect{1, "! arg 0..."}) } func TestSelectTypeCheck(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "int 1; int 2; int 3; select; len", AssemblerMaxVersion, Expect{5, "len arg 0..."}) - testProg(t, "byte 0x1234; byte 0x5678; int 3; select; !", AssemblerMaxVersion, Expect{5, "! arg 0..."}) + testProg(t, "int 1; int 2; int 3; select; len", AssemblerMaxVersion, Expect{1, "len arg 0..."}) + testProg(t, "byte 0x1234; byte 0x5678; int 3; select; !", AssemblerMaxVersion, Expect{1, "! arg 0..."}) } func TestSetBitTypeCheck(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "int 1; int 2; int 3; setbit; len", AssemblerMaxVersion, Expect{5, "len arg 0..."}) - testProg(t, "byte 0x1234; int 2; int 3; setbit; !", AssemblerMaxVersion, Expect{5, "! arg 0..."}) + testProg(t, "int 1; int 2; int 3; setbit; len", AssemblerMaxVersion, Expect{1, "len arg 0..."}) + testProg(t, "byte 0x1234; int 2; int 3; setbit; !", AssemblerMaxVersion, Expect{1, "! arg 0..."}) } func TestScratchTypeCheck(t *testing.T) { @@ -2493,13 +2374,13 @@ func TestScratchTypeCheck(t *testing.T) { // All scratch slots should start as uint64 testProg(t, "load 0; int 1; +", AssemblerMaxVersion) // Check load and store accurately using the scratch space - testProg(t, "byte 0x01; store 0; load 0; int 1; +", AssemblerMaxVersion, Expect{5, "+ arg 0..."}) + testProg(t, "byte 0x01; store 0; load 0; int 1; +", AssemblerMaxVersion, Expect{1, "+ arg 0..."}) // Loads should know the type it's loading if all the slots are the same type - testProg(t, "int 0; loads; btoi", AssemblerMaxVersion, Expect{3, "btoi arg 0..."}) + testProg(t, "int 0; loads; btoi", AssemblerMaxVersion, Expect{1, "btoi arg 0..."}) // Loads doesn't know the type when slot types vary testProg(t, "byte 0x01; store 0; int 1; loads; btoi", AssemblerMaxVersion) // Stores should only set slots to StackAny if they are not the same type as what is being stored - testProg(t, "byte 0x01; store 0; int 3; byte 0x01; stores; load 0; int 1; +", AssemblerMaxVersion, Expect{8, "+ arg 0..."}) + testProg(t, "byte 0x01; store 0; int 3; byte 0x01; stores; load 0; int 1; +", AssemblerMaxVersion, Expect{1, "+ arg 0..."}) // ScratchSpace should reset after hitting label in deadcode testProg(t, "byte 0x01; store 0; b label1; label1:; load 0; int 1; +", AssemblerMaxVersion) // But it should reset to StackAny not uint64 @@ -2507,7 +2388,7 @@ func TestScratchTypeCheck(t *testing.T) { // Callsubs should also reset the scratch space testProg(t, "callsub A; load 0; btoi; return; A: byte 0x01; store 0; retsub", AssemblerMaxVersion) // But the scratchspace should still be tracked after the callsub - testProg(t, "callsub A; int 1; store 0; load 0; btoi; return; A: retsub", AssemblerMaxVersion, Expect{5, "btoi arg 0..."}) + testProg(t, "callsub A; int 1; store 0; load 0; btoi; return; A: retsub", AssemblerMaxVersion, Expect{1, "btoi arg 0..."}) } func TestCoverAsm(t *testing.T) { @@ -2515,9 +2396,9 @@ func TestCoverAsm(t *testing.T) { t.Parallel() testProg(t, `int 4; byte "john"; int 5; cover 2; pop; +`, AssemblerMaxVersion) testProg(t, `int 4; byte "ayush"; int 5; cover 1; pop; +`, AssemblerMaxVersion) - testProg(t, `int 4; byte "john"; int 5; cover 2; +`, AssemblerMaxVersion, Expect{5, "+ arg 1..."}) + testProg(t, `int 4; byte "john"; int 5; cover 2; +`, AssemblerMaxVersion, Expect{1, "+ arg 1..."}) - testProg(t, `int 4; cover junk`, AssemblerMaxVersion, Expect{2, "cover unable to parse n ..."}) + testProg(t, `int 4; cover junk`, AssemblerMaxVersion, Expect{1, "cover unable to parse n ..."}) } func TestUncoverAsm(t *testing.T) { @@ -2526,38 +2407,38 @@ func TestUncoverAsm(t *testing.T) { testProg(t, `int 4; byte "john"; int 5; uncover 2; +`, AssemblerMaxVersion) testProg(t, `int 4; byte "ayush"; int 5; uncover 1; pop; +`, AssemblerMaxVersion) testProg(t, `int 1; byte "jj"; byte "ayush"; byte "john"; int 5; uncover 4; +`, AssemblerMaxVersion) - testProg(t, `int 4; byte "ayush"; int 5; uncover 1; +`, AssemblerMaxVersion, Expect{5, "+ arg 1..."}) + testProg(t, `int 4; byte "ayush"; int 5; uncover 1; +`, AssemblerMaxVersion, Expect{1, "+ arg 1..."}) } func TestTxTypes(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "itxn_begin; itxn_field Sender", 5, Expect{2, "itxn_field Sender expects 1 stack argument..."}) - testProg(t, "itxn_begin; int 1; itxn_field Sender", 5, Expect{3, "...wanted type []byte got uint64"}) + testProg(t, "itxn_begin; itxn_field Sender", 5, Expect{1, "itxn_field Sender expects 1 stack argument..."}) + testProg(t, "itxn_begin; int 1; itxn_field Sender", 5, Expect{1, "...wanted type []byte got uint64"}) testProg(t, "itxn_begin; byte 0x56127823; itxn_field Sender", 5) - testProg(t, "itxn_begin; itxn_field Amount", 5, Expect{2, "itxn_field Amount expects 1 stack argument..."}) - testProg(t, "itxn_begin; byte 0x87123376; itxn_field Amount", 5, Expect{3, "...wanted type uint64 got []byte"}) + testProg(t, "itxn_begin; itxn_field Amount", 5, Expect{1, "itxn_field Amount expects 1 stack argument..."}) + testProg(t, "itxn_begin; byte 0x87123376; itxn_field Amount", 5, Expect{1, "...wanted type uint64 got []byte"}) testProg(t, "itxn_begin; int 1; itxn_field Amount", 5) } func TestBadInnerFields(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testProg(t, "itxn_begin; int 1000; itxn_field FirstValid", 5, Expect{3, "...is not allowed."}) - testProg(t, "itxn_begin; int 1000; itxn_field FirstValidTime", 5, Expect{3, "...is not allowed."}) - testProg(t, "itxn_begin; int 1000; itxn_field LastValid", 5, Expect{3, "...is not allowed."}) - testProg(t, "itxn_begin; int 32; bzero; itxn_field Lease", 5, Expect{4, "...is not allowed."}) - testProg(t, "itxn_begin; byte 0x7263; itxn_field Note", 5, Expect{3, "...Note field was introduced in v6..."}) - testProg(t, "itxn_begin; byte 0x7263; itxn_field VotePK", 5, Expect{3, "...VotePK field was introduced in v6..."}) - testProg(t, "itxn_begin; int 32; bzero; itxn_field TxID", 5, Expect{4, "...is not allowed."}) - - testProg(t, "itxn_begin; int 1000; itxn_field FirstValid", 6, Expect{3, "...is not allowed."}) - testProg(t, "itxn_begin; int 1000; itxn_field LastValid", 6, Expect{3, "...is not allowed."}) - testProg(t, "itxn_begin; int 32; bzero; itxn_field Lease", 6, Expect{4, "...is not allowed."}) + testProg(t, "itxn_begin; int 1000; itxn_field FirstValid", 5, Expect{1, "...is not allowed."}) + testProg(t, "itxn_begin; int 1000; itxn_field FirstValidTime", 5, Expect{1, "...is not allowed."}) + testProg(t, "itxn_begin; int 1000; itxn_field LastValid", 5, Expect{1, "...is not allowed."}) + testProg(t, "itxn_begin; int 32; bzero; itxn_field Lease", 5, Expect{1, "...is not allowed."}) + testProg(t, "itxn_begin; byte 0x7263; itxn_field Note", 5, Expect{1, "...Note field was introduced in v6..."}) + testProg(t, "itxn_begin; byte 0x7263; itxn_field VotePK", 5, Expect{1, "...VotePK field was introduced in v6..."}) + testProg(t, "itxn_begin; int 32; bzero; itxn_field TxID", 5, Expect{1, "...is not allowed."}) + + testProg(t, "itxn_begin; int 1000; itxn_field FirstValid", 6, Expect{1, "...is not allowed."}) + testProg(t, "itxn_begin; int 1000; itxn_field LastValid", 6, Expect{1, "...is not allowed."}) + testProg(t, "itxn_begin; int 32; bzero; itxn_field Lease", 6, Expect{1, "...is not allowed."}) testProg(t, "itxn_begin; byte 0x7263; itxn_field Note", 6) testProg(t, "itxn_begin; byte 0x7263; itxn_field VotePK", 6) - testProg(t, "itxn_begin; int 32; bzero; itxn_field TxID", 6, Expect{4, "...is not allowed."}) + testProg(t, "itxn_begin; int 32; bzero; itxn_field TxID", 6, Expect{1, "...is not allowed."}) } func TestTypeTracking(t *testing.T) { @@ -2573,7 +2454,7 @@ func TestTypeTracking(t *testing.T) { // but we do want to ensure we're not just treating the code after callsub as dead testProg(t, "callsub A; int 1; concat; return; A: int 1; int 2; retsub", LogicVersion, - Expect{3, "concat arg 1 wanted..."}) + Expect{1, "concat arg 1 wanted..."}) // retsub deadens code, like any unconditional branch testProg(t, "callsub A; +; return; A: int 1; int 2; retsub; concat", LogicVersion) @@ -2697,7 +2578,7 @@ func TestAddPseudoDocTags(t *testing.T) { delete(opDocByName, "any") }() - pseudoOps["tests"] = map[int]OpSpec{2: OpSpec{Name: "multiple"}, 1: OpSpec{Name: "single"}, 0: OpSpec{Name: "none"}, anyImmediates: OpSpec{Name: "any"}} + pseudoOps["tests"] = map[int]OpSpec{2: {Name: "multiple"}, 1: {Name: "single"}, 0: {Name: "none"}, anyImmediates: {Name: "any"}} addPseudoDocTags() require.Equal(t, "`multiple` can be called using `tests` with 2 immediates.", opDocByName["multiple"]) require.Equal(t, "`single` can be called using `tests` with 1 immediate.", opDocByName["single"]) @@ -2711,7 +2592,45 @@ func TestReplacePseudo(t *testing.T) { for v := uint64(replaceVersion); v <= AssemblerMaxVersion; v++ { testProg(t, "byte 0x0000; byte 0x1234; replace 0", v) testProg(t, "byte 0x0000; int 0; byte 0x1234; replace", v) - testProg(t, "byte 0x0000; byte 0x1234; replace", v, Expect{3, "replace without immediates expects 3 stack arguments but stack height is 2"}) - testProg(t, "byte 0x0000; int 0; byte 0x1234; replace 0", v, Expect{4, "replace 0 arg 0 wanted type []byte got uint64"}) + testProg(t, "byte 0x0000; byte 0x1234; replace", v, Expect{1, "replace without immediates expects 3 stack arguments but stack height is 2"}) + testProg(t, "byte 0x0000; int 0; byte 0x1234; replace 0", v, Expect{1, "replace 0 arg 0 wanted type []byte got uint64"}) + } +} + +func checkSame(t *testing.T, version uint64, first string, compares ...string) { + t.Helper() + if version == 0 { + version = assemblerNoVersion + } + ops, err := AssembleStringWithVersion(first, version) + require.NoError(t, err, first) + for _, compare := range compares { + other, err := AssembleStringWithVersion(compare, version) + assert.NoError(t, err, compare) + assert.Equal(t, other.Program, ops.Program, "%s unlike %s", first, compare) } } + +func TestSemiColon(t *testing.T) { + partitiontest.PartitionTest(t) + t.Parallel() + + checkSame(t, AssemblerMaxVersion, + "pushint 0 ; pushint 1 ; +; int 3 ; *", + "pushint 0\npushint 1\n+\nint 3\n*", + "pushint 0; pushint 1; +; int 3; *; // comment; int 2", + "pushint 0; ; ; pushint 1 ; +; int 3 ; *//check", + ) + + checkSame(t, 0, + "#pragma version 7\nint 1", + "// junk;\n#pragma version 7\nint 1", + "// junk;\n #pragma version 7\nint 1", + ) + + checkSame(t, AssemblerMaxVersion, + `byte "test;this"; pop;`, + `byte "test;this"; ; pop;`, + `byte "test;this";;;pop;`, + ) +} diff --git a/data/transactions/logic/backwardCompat_test.go b/data/transactions/logic/backwardCompat_test.go index 086741dcde..13a19ddaa7 100644 --- a/data/transactions/logic/backwardCompat_test.go +++ b/data/transactions/logic/backwardCompat_test.go @@ -467,15 +467,15 @@ func TestBackwardCompatAssemble(t *testing.T) { source := "int 1; int 1; bnz done; done:" t.Run("v=default", func(t *testing.T) { - testProg(t, source, assemblerNoVersion, Expect{4, "label \"done\" is too far away"}) + testProg(t, source, assemblerNoVersion, Expect{1, "label \"done\" is too far away"}) }) t.Run("v=default", func(t *testing.T) { - testProg(t, source, 0, Expect{4, "label \"done\" is too far away"}) + testProg(t, source, 0, Expect{1, "label \"done\" is too far away"}) }) t.Run("v=default", func(t *testing.T) { - testProg(t, source, 1, Expect{4, "label \"done\" is too far away"}) + testProg(t, source, 1, Expect{1, "label \"done\" is too far away"}) }) for v := uint64(2); v <= AssemblerMaxVersion; v++ { diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 3e051fc6be..bb2f1e0f1d 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -314,7 +314,7 @@ func TestBalance(t *testing.T) { text = `txn Accounts 1; balance; int 177; ==;` // won't assemble in old version teal - testProg(t, text, directRefEnabledVersion-1, Expect{2, "balance arg 0 wanted type uint64..."}) + testProg(t, text, directRefEnabledVersion-1, Expect{1, "balance arg 0 wanted type uint64..."}) // but legal after that testApp(t, text, ep) @@ -475,7 +475,7 @@ func TestMinBalance(t *testing.T) { testApp(t, "int 1; min_balance; int 1001; ==", ep) // 1 == Accounts[0] testProg(t, "txn Accounts 1; min_balance; int 1001; ==", directRefEnabledVersion-1, - Expect{2, "min_balance arg 0 wanted type uint64..."}) + Expect{1, "min_balance arg 0 wanted type uint64..."}) testProg(t, "txn Accounts 1; min_balance; int 1001; ==", directRefEnabledVersion) testApp(t, "txn Accounts 1; min_balance; int 1001; ==", ep) // 1 == Accounts[0] // Receiver opts in @@ -528,7 +528,7 @@ func TestAppCheckOptedIn(t *testing.T) { testApp(t, "int 1; int 2; app_opted_in; int 0; ==", pre) // in pre, int 2 is an actual app id testApp(t, "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui01\"; int 2; app_opted_in; int 1; ==", now) testProg(t, "byte \"aoeuiaoeuiaoeuiaoeuiaoeuiaoeui01\"; int 2; app_opted_in; int 1; ==", directRefEnabledVersion-1, - Expect{3, "app_opted_in arg 0 wanted type uint64..."}) + Expect{1, "app_opted_in arg 0 wanted type uint64..."}) // Receiver opts into 888, the current app in testApp ledger.NewLocals(txn.Txn.Receiver, 888) @@ -939,7 +939,7 @@ func testAssetsByVersion(t *testing.T, assetsTestProgram string, version uint64) // it wasn't legal to use a direct ref for account testProg(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"; int 54; asset_holding_get AssetBalance`, - directRefEnabledVersion-1, Expect{3, "asset_holding_get AssetBalance arg 0 wanted type uint64..."}) + directRefEnabledVersion-1, Expect{1, "asset_holding_get AssetBalance arg 0 wanted type uint64..."}) // but it is now (empty asset yields 0,0 on stack) testApp(t, `byte "aoeuiaoeuiaoeuiaoeuiaoeuiaoeui00"; int 55; asset_holding_get AssetBalance; ==`, now) // This is receiver, who is in Assets array diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index ba7df73e9c..45a8ceeb22 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -340,12 +340,12 @@ func TestSimpleMath(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testAccepts(t, "int 2; int 3; + ;int 5;==", 1) - testAccepts(t, "int 22; int 3; - ;int 19;==", 1) - testAccepts(t, "int 8; int 7; * ;int 56;==", 1) - testAccepts(t, "int 21; int 7; / ;int 3;==", 1) + testAccepts(t, "int 2; int 3; + ; int 5; ==", 1) + testAccepts(t, "int 22; int 3; - ; int 19; ==", 1) + testAccepts(t, "int 8; int 7; * ; int 56; ==", 1) + testAccepts(t, "int 21; int 7; / ; int 3; ==", 1) - testPanics(t, "int 1; int 2; - ;int 0; ==", 1) + testPanics(t, "int 1; int 2; - ; int 0; ==", 1) } func TestSha256EqArg(t *testing.T) { @@ -962,7 +962,7 @@ func TestArg(t *testing.T) { t.Parallel() for v := uint64(1); v <= AssemblerMaxVersion; v++ { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { - source := "arg 0; arg 1; ==; arg 2; arg 3; !=; &&; arg 4; len; int 9; <; &&;" + source := "arg 0; arg 1; ==; arg 2; arg 3; !=; &&; arg 4; len; int 9; <; &&; " if v >= 5 { source += "int 0; args; int 1; args; ==; assert; int 2; args; int 3; args; !=; assert" } @@ -2836,16 +2836,16 @@ func TestSlowLogic(t *testing.T) { t.Parallel() fragment := `byte 0x666E6F7264; keccak256 - byte 0xc195eca25a6f4c82bfba0287082ddb0d602ae9230f9cf1f1a40b68f8e2c41567; ==;` + byte 0xc195eca25a6f4c82bfba0287082ddb0d602ae9230f9cf1f1a40b68f8e2c41567; ==; ` // Sanity check. Running a short sequence of these fragments passes in all versions. - source := fragment + strings.Repeat(fragment+"&&;", 5) + source := fragment + strings.Repeat(fragment+"&&; ", 5) testAccepts(t, source, 1) // in v1, each repeat costs 30 - v1overspend := fragment + strings.Repeat(fragment+"&&;", 20000/30) + v1overspend := fragment + strings.Repeat(fragment+"&&; ", 20000/30) // in v2,v3 each repeat costs 134 - v2overspend := fragment + strings.Repeat(fragment+"&&;", 20000/134) + v2overspend := fragment + strings.Repeat(fragment+"&&; ", 20000/134) // v1overspend fails (on v1) ops := testProg(t, v1overspend, 1) @@ -3546,8 +3546,7 @@ func benchmarkOperation(b *testing.B, prefix string, operation string, suffix st b.Helper() runs := 1 + b.N/2000 inst := strings.Count(operation, ";") + strings.Count(operation, "\n") - source := prefix + ";" + strings.Repeat(operation+";", 2000) + ";" + suffix - source = strings.ReplaceAll(source, ";", "\n") + source := prefix + ";" + strings.Repeat(operation+"\n", 2000) + ";" + suffix ops := testProg(b, source, AssemblerMaxVersion) evalLoop(b, runs, ops.Program) b.ReportMetric(float64(inst), "extra/op") @@ -3860,9 +3859,9 @@ func TestStackOverflow(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - source := "int 1; int 2;" + source := "int 1; int 2; " for i := 1; i < maxStackDepth/2; i++ { - source += "dup2;" + source += "dup2; " } testAccepts(t, source+"return", 2) testPanics(t, source+"dup2; return", 2) @@ -4266,11 +4265,11 @@ func TestAssert(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - testAccepts(t, "int 1;assert;int 1", 3) - testRejects(t, "int 1;assert;int 0", 3) - testPanics(t, "int 0;assert;int 1", 3) - testPanics(t, notrack("assert;int 1"), 3) - testPanics(t, notrack(`byte "john";assert;int 1`), 3) + testAccepts(t, "int 1; assert; int 1", 3) + testRejects(t, "int 1; assert; int 0", 3) + testPanics(t, "int 0; assert; int 1", 3) + testPanics(t, notrack("assert; int 1"), 3) + testPanics(t, notrack(`byte "john"; assert; int 1`), 3) } func TestBits(t *testing.T) { @@ -4766,7 +4765,7 @@ func TestLog(t *testing.T) { loglen: 2, }, { - source: fmt.Sprintf(`%s int 1`, strings.Repeat(`byte "a logging message"; log;`, maxLogCalls)), + source: fmt.Sprintf(`%s int 1`, strings.Repeat(`byte "a logging message"; log; `, maxLogCalls)), loglen: maxLogCalls, }, { @@ -4811,7 +4810,7 @@ func TestLog(t *testing.T) { runMode: modeApp, }, { - source: fmt.Sprintf(`%s; int 1`, strings.Repeat(`byte "a"; log;`, maxLogCalls+1)), + source: fmt.Sprintf(`%s; int 1`, strings.Repeat(`byte "a"; log; `, maxLogCalls+1)), errContains: "too many log calls", runMode: modeApp, }, @@ -5123,7 +5122,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64; int 0; ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": 3}, \"key5\": 18446744073709551615 }"; @@ -5131,7 +5130,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64; int 18446744073709551615; //max uint64 value ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": 3}, \"key5\": 18446744073709551615 }"; @@ -5139,7 +5138,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString; byte "algo"; ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 0,\"key1\": \"\\u0061\\u006C\\u0067\\u006F\",\"key2\":{\"key3\": \"teal\", \"key4\": 3}, \"key5\": 18446744073709551615 }"; @@ -5147,7 +5146,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString; byte "algo"; ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": {\"key40\": 10}}, \"key5\": 18446744073709551615 }"; @@ -5159,7 +5158,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64 int 10 ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}, {9, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}, {5, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": {\"key40\": 10}}, \"key5\": 18446744073709551615 }"; @@ -5169,7 +5168,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString; byte "teal" ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}, {9, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}, {5, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"\\"teal\\"\", \"key4\": {\"key40\": 10}}, \"key5\": 18446744073709551615 }"; @@ -5179,7 +5178,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString; byte ""teal"" // quotes match ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}, {9, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}, {5, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \" teal \", \"key4\": {\"key40\": 10}}, \"key5\": 18446744073709551615 }"; @@ -5189,7 +5188,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString; byte " teal " // spaces match ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}, {9, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}, {5, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": {\"key40\": 10, \"key40\": \"10\"}}, \"key5\": 18446744073709551615 }"; @@ -5200,7 +5199,7 @@ func TestOpJSONRef(t *testing.T) { byte "{\"key40\": 10, \"key40\": \"10\"}" == `, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "{\"rawId\": \"responseId\",\"id\": \"0\",\"response\": {\"attestationObject\": \"based64url_encoded_buffer\",\"clientDataJSON\": \" based64url_encoded_client_data\"},\"getClientExtensionResults\": {},\"type\": \"public-key\"}"; @@ -5208,7 +5207,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONObject; byte "{\"attestationObject\": \"based64url_encoded_buffer\",\"clientDataJSON\": \" based64url_encoded_client_data\"}" // object as it appeared in input ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "{\"rawId\": \"responseId\",\"id\": \"0\",\"response\": {\"attestationObject\": \"based64url_encoded_buffer\",\"clientD\\u0061taJSON\": \" based64url_encoded_client_data\"},\"getClientExtensionResults\": {},\"type\": \"public-key\"}"; @@ -5216,7 +5215,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONObject; byte "{\"attestationObject\": \"based64url_encoded_buffer\",\"clientD\\u0061taJSON\": \" based64url_encoded_client_data\"}" // object as it appeared in input ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "{\"rawId\": \"responseId\",\"id\": \"0\",\"response\": {\"attestationObject\": \"based64url_encoded_buffer\",\"clientDataJSON\": \" based64url_encoded_client_data\"},\"getClientExtensionResults\": {},\"type\": \"public-key\"}"; @@ -5226,7 +5225,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString; byte " based64url_encoded_client_data"; ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}, {9, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}, {5, "unknown opcode: json_ref"}}, }, { source: `byte "{\"\\u0072\\u0061\\u0077\\u0049\\u0044\": \"responseId\",\"id\": \"0\",\"response\": {\"attestationObject\": \"based64url_encoded_buffer\",\"clientDataJSON\": \" based64url_encoded_client_data\"},\"getClientExtensionResults\": {},\"type\": \"public-key\"}"; @@ -5234,7 +5233,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString; byte "responseId" ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, // JavaScript MAX_SAFE_INTEGER { @@ -5243,7 +5242,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64; int 9007199254740991; ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, // maximum uint64 { @@ -5252,7 +5251,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64; int 18446744073709551615; ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, // larger-than-uint64s are allowed if not requested { @@ -5261,7 +5260,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64; int 0; ==`, - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, } @@ -5301,52 +5300,52 @@ func TestOpJSONRef(t *testing.T) { { source: `byte "{\"key0\": 1 }"; byte "key0"; json_ref JSONString;`, error: "json: cannot unmarshal number into Go value of type string", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": [1] }"; byte "key0"; json_ref JSONString;`, error: "json: cannot unmarshal array into Go value of type string", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": {\"key1\":1} }"; byte "key0"; json_ref JSONString;`, error: "json: cannot unmarshal object into Go value of type string", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": \"1\" }"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal string into Go value of type uint64", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": [\"1\"] }"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal array into Go value of type uint64", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": {\"key1\":1} }"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal object into Go value of type uint64", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": [1]}"; byte "key0"; json_ref JSONObject;`, error: "json: cannot unmarshal array into Go value of type map[string]json.RawMessage", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 1}"; byte "key0"; json_ref JSONObject;`, error: "json: cannot unmarshal number into Go value of type map[string]json.RawMessage", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": \"1\"}"; byte "key0"; json_ref JSONObject;`, error: "json: cannot unmarshal string into Go value of type map[string]json.RawMessage", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 1,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": [1,2,3]} }"; byte "key3"; json_ref JSONString;`, error: "key key3 not found in JSON text", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 1,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": [1,2,3]}}"; @@ -5356,52 +5355,52 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString `, error: "key key5 not found in JSON text", - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}, {9, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}, {5, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": -0,\"key1\": 2.5,\"key2\": -3}"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal number -0 into Go value of type uint64", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 1e10,\"key1\": 2.5,\"key2\": -3}"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal number 1e10 into Go value of type uint64", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 0.2e-2,\"key1\": 2.5,\"key2\": -3}"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal number 0.2e-2 into Go value of type uint64", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 1.0,\"key1\": 2.5,\"key2\": -3}"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal number 1.0 into Go value of type uint64", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 1.0,\"key1\": 2.5,\"key2\": -3}"; byte "key1"; json_ref JSONUint64;`, error: "json: cannot unmarshal number 2.5 into Go value of type uint64", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 1.0,\"key1\": 2.5,\"key2\": -3}"; byte "key2"; json_ref JSONUint64;`, error: "json: cannot unmarshal number -3 into Go value of type uint64", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 18446744073709551616}"; byte "key0"; json_ref JSONUint64;`, error: "json: cannot unmarshal number 18446744073709551616 into Go value of type uint64", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 1,}"; byte "key0"; json_ref JSONString;`, error: "error while parsing JSON text, invalid json text", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 1, \"key0\": \"3\"}"; byte "key0"; json_ref JSONString;`, error: "error while parsing JSON text, invalid json text, duplicate keys not allowed", - previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{1, "unknown opcode: json_ref"}}, }, { source: `byte "{\"key0\": 0,\"key1\": \"algo\",\"key2\":{\"key3\": \"teal\", \"key4\": {\"key40\": 10, \"key40\": \"should fail!\"}}}"; @@ -5413,7 +5412,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONString `, error: "error while parsing JSON text, invalid json text, duplicate keys not allowed", - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}, {9, "unknown opcode: json_ref"}, {13, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}, {5, "unknown opcode: json_ref"}, {7, "unknown opcode: json_ref"}}, }, { source: `byte "[1,2,3]"; @@ -5421,7 +5420,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64 `, error: "error while parsing JSON text, invalid json text, only json object is allowed", - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "2"; @@ -5429,7 +5428,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64 `, error: "error while parsing JSON text, invalid json text, only json object is allowed", - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "null"; @@ -5437,7 +5436,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64 `, error: "error while parsing JSON text, invalid json text, only json object is allowed", - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "true"; @@ -5445,7 +5444,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64 `, error: "error while parsing JSON text, invalid json text, only json object is allowed", - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "\"sometext\""; @@ -5453,7 +5452,7 @@ func TestOpJSONRef(t *testing.T) { json_ref JSONUint64 `, error: "error while parsing JSON text, invalid json text, only json object is allowed", - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, { source: `byte "{noquotes: \"shouldn't work\"}"; @@ -5462,7 +5461,7 @@ func TestOpJSONRef(t *testing.T) { byte "shouldn't work"; ==`, error: "error while parsing JSON text, invalid json text", - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, // max uint64 + 1 should fail { @@ -5472,7 +5471,7 @@ func TestOpJSONRef(t *testing.T) { int 1; return`, error: "json: cannot unmarshal number 18446744073709551616 into Go value of type uint64", - previousVersErrors: []Expect{{5, "unknown opcode: json_ref"}}, + previousVersErrors: []Expect{{3, "unknown opcode: json_ref"}}, }, } diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go index 0a29288134..2b5008f5c8 100644 --- a/data/transactions/logic/fields_test.go +++ b/data/transactions/logic/fields_test.go @@ -225,7 +225,7 @@ func TestAssetParamsFieldsVersions(t *testing.T) { ep, _, _ := makeSampleEnv() ep.Proto.LogicSigVersion = v if field.version > v { - testProg(t, text, v, Expect{3, "...was introduced in..."}) + testProg(t, text, v, Expect{1, "...was introduced in..."}) ops := testProg(t, text, field.version) // assemble in the future ops.Program[0] = byte(v) testAppBytes(t, ops.Program, ep, "invalid asset_params_get field")