From efdd4f564486982a94e380c197c770956b6adf82 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 22 Mar 2022 10:10:27 -0400 Subject: [PATCH 1/9] Attach field info to ecdsa_* so field dependent costs are shown. --- cmd/opdoc/opdoc.go | 52 ++- cmd/opdoc/tmLanguage.go | 10 +- data/transactions/logic/TEAL_opcodes.md | 4 +- data/transactions/logic/assembler.go | 39 ++- .../transactions/logic/backwardCompat_test.go | 6 +- data/transactions/logic/doc.go | 32 +- data/transactions/logic/doc_test.go | 4 +- data/transactions/logic/eval.go | 47 ++- data/transactions/logic/evalCrypto_test.go | 61 +++- data/transactions/logic/eval_test.go | 37 +- data/transactions/logic/fields.go | 315 +++++++++--------- data/transactions/logic/fields_test.go | 8 +- data/transactions/logic/opcodes.go | 54 ++- 13 files changed, 361 insertions(+), 308 deletions(-) diff --git a/cmd/opdoc/opdoc.go b/cmd/opdoc/opdoc.go index 9b6fff9793..a457fbfb8b 100644 --- a/cmd/opdoc/opdoc.go +++ b/cmd/opdoc/opdoc.go @@ -77,11 +77,7 @@ func integerConstantsTableMarkdown(out io.Writer) { out.Write([]byte("\n")) } -type speccer interface { - SpecByName(name string) logic.FieldSpec -} - -func fieldSpecsMarkdown(out io.Writer, names []string, specs speccer) { +func fieldSpecsMarkdown(out io.Writer, names []string, specs logic.FieldSpeccer) { showTypes := false showVers := false spec0 := specs.SpecByName(names[0]) @@ -127,37 +123,37 @@ func fieldSpecsMarkdown(out io.Writer, names []string, specs speccer) { func transactionFieldsMarkdown(out io.Writer) { fmt.Fprintf(out, "\n`txn` Fields (see [transaction reference](https://developer.algorand.org/docs/reference/transactions/)):\n\n") - fieldSpecsMarkdown(out, logic.TxnFieldNames, logic.TxnFieldSpecByName) + fieldSpecsMarkdown(out, logic.TxnFieldNames[:], logic.TxnFieldSpecByName) } func globalFieldsMarkdown(out io.Writer) { fmt.Fprintf(out, "\n`global` Fields:\n\n") - fieldSpecsMarkdown(out, logic.GlobalFieldNames, logic.GlobalFieldSpecByName) + fieldSpecsMarkdown(out, logic.GlobalFieldNames[:], logic.GlobalFieldSpecByName) } func assetHoldingFieldsMarkdown(out io.Writer) { fmt.Fprintf(out, "\n`asset_holding_get` Fields:\n\n") - fieldSpecsMarkdown(out, logic.AssetHoldingFieldNames, logic.AssetHoldingFieldSpecByName) + fieldSpecsMarkdown(out, logic.AssetHoldingFieldNames[:], logic.AssetHoldingFieldSpecByName) } func assetParamsFieldsMarkdown(out io.Writer) { fmt.Fprintf(out, "\n`asset_params_get` Fields:\n\n") - fieldSpecsMarkdown(out, logic.AssetParamsFieldNames, logic.AssetParamsFieldSpecByName) + fieldSpecsMarkdown(out, logic.AssetParamsFieldNames[:], logic.AssetParamsFieldSpecByName) } func appParamsFieldsMarkdown(out io.Writer) { fmt.Fprintf(out, "\n`app_params_get` Fields:\n\n") - fieldSpecsMarkdown(out, logic.AppParamsFieldNames, logic.AppParamsFieldSpecByName) + fieldSpecsMarkdown(out, logic.AppParamsFieldNames[:], logic.AppParamsFieldSpecByName) } func acctParamsFieldsMarkdown(out io.Writer) { fmt.Fprintf(out, "\n`acct_params_get` Fields:\n\n") - fieldSpecsMarkdown(out, logic.AcctParamsFieldNames, logic.AcctParamsFieldSpecByName) + fieldSpecsMarkdown(out, logic.AcctParamsFieldNames[:], logic.AcctParamsFieldSpecByName) } func ecDsaCurvesMarkdown(out io.Writer) { fmt.Fprintf(out, "\n`ECDSA` Curves:\n\n") - fieldSpecsMarkdown(out, logic.EcdsaCurveNames, logic.EcdsaCurveSpecByName) + fieldSpecsMarkdown(out, logic.EcdsaCurveNames[:], logic.EcdsaCurveSpecByName) } func immediateMarkdown(op *logic.OpSpec) string { @@ -214,19 +210,19 @@ func opToMarkdown(out io.Writer, op *logic.OpSpec) (err error) { fmt.Fprintf(out, "- **Cost**:\n") for _, cost := range costs { if cost.From == cost.To { - fmt.Fprintf(out, " - %d (v%d)\n", cost.Cost, cost.To) + fmt.Fprintf(out, " - %s (v%d)\n", cost.Cost, cost.To) } else { if cost.To < logic.LogicVersion { - fmt.Fprintf(out, " - %d (v%d - v%d)\n", cost.Cost, cost.From, cost.To) + fmt.Fprintf(out, " - %s (v%d - v%d)\n", cost.Cost, cost.From, cost.To) } else { - fmt.Fprintf(out, " - %d (since v%d)\n", cost.Cost, cost.From) + fmt.Fprintf(out, " - %s (since v%d)\n", cost.Cost, cost.From) } } } } else { cost := costs[0].Cost - if cost != 1 { - fmt.Fprintf(out, "- **Cost**: %d\n", cost) + if cost != "1" { + fmt.Fprintf(out, "- **Cost**: %s\n", cost) } } if op.Version > 1 { @@ -321,7 +317,7 @@ func typeString(types []logic.StackType) string { return string(out) } -func fieldsAndTypes(names []string, specs speccer) ([]string, string) { +func fieldsAndTypes(names []string, specs logic.FieldSpeccer) ([]string, string) { types := make([]logic.StackType, len(names)) for i, name := range names { types[i] = specs.SpecByName(name).Type() @@ -332,18 +328,18 @@ func fieldsAndTypes(names []string, specs speccer) ([]string, string) { func argEnums(name string) (names []string, types string) { switch name { case "txn", "gtxn", "gtxns", "itxn", "gitxn", "itxn_field": - return fieldsAndTypes(logic.TxnFieldNames, logic.TxnFieldSpecByName) + return fieldsAndTypes(logic.TxnFieldNames[:], logic.TxnFieldSpecByName) case "global": return case "txna", "gtxna", "gtxnsa", "txnas", "gtxnas", "gtxnsas", "itxna", "gitxna": // Map is the whole txn field spec map. That's fine, we only lookup the given names. return fieldsAndTypes(logic.TxnaFieldNames(), logic.TxnFieldSpecByName) case "asset_holding_get": - return fieldsAndTypes(logic.AssetHoldingFieldNames, logic.AssetHoldingFieldSpecByName) + return fieldsAndTypes(logic.AssetHoldingFieldNames[:], logic.AssetHoldingFieldSpecByName) case "asset_params_get": - return fieldsAndTypes(logic.AssetParamsFieldNames, logic.AssetParamsFieldSpecByName) + return fieldsAndTypes(logic.AssetParamsFieldNames[:], logic.AssetParamsFieldSpecByName) case "app_params_get": - return fieldsAndTypes(logic.AppParamsFieldNames, logic.AppParamsFieldSpecByName) + return fieldsAndTypes(logic.AppParamsFieldNames[:], logic.AppParamsFieldSpecByName) default: return nil, "" } @@ -401,27 +397,27 @@ func main() { constants.Close() txnfields := create("txn_fields.md") - fieldSpecsMarkdown(txnfields, logic.TxnFieldNames, logic.TxnFieldSpecByName) + fieldSpecsMarkdown(txnfields, logic.TxnFieldNames[:], logic.TxnFieldSpecByName) txnfields.Close() globalfields := create("global_fields.md") - fieldSpecsMarkdown(globalfields, logic.GlobalFieldNames, logic.GlobalFieldSpecByName) + fieldSpecsMarkdown(globalfields, logic.GlobalFieldNames[:], logic.GlobalFieldSpecByName) globalfields.Close() assetholding := create("asset_holding_fields.md") - fieldSpecsMarkdown(assetholding, logic.AssetHoldingFieldNames, logic.AssetHoldingFieldSpecByName) + fieldSpecsMarkdown(assetholding, logic.AssetHoldingFieldNames[:], logic.AssetHoldingFieldSpecByName) assetholding.Close() assetparams := create("asset_params_fields.md") - fieldSpecsMarkdown(assetparams, logic.AssetParamsFieldNames, logic.AssetParamsFieldSpecByName) + fieldSpecsMarkdown(assetparams, logic.AssetParamsFieldNames[:], logic.AssetParamsFieldSpecByName) assetparams.Close() appparams := create("app_params_fields.md") - fieldSpecsMarkdown(appparams, logic.AppParamsFieldNames, logic.AppParamsFieldSpecByName) + fieldSpecsMarkdown(appparams, logic.AppParamsFieldNames[:], logic.AppParamsFieldSpecByName) appparams.Close() acctparams, _ := os.Create("acct_params_fields.md") - fieldSpecsMarkdown(acctparams, logic.AcctParamsFieldNames, logic.AcctParamsFieldSpecByName) + fieldSpecsMarkdown(acctparams, logic.AcctParamsFieldNames[:], logic.AcctParamsFieldSpecByName) acctparams.Close() langspecjs := create("langspec.json") diff --git a/cmd/opdoc/tmLanguage.go b/cmd/opdoc/tmLanguage.go index 204068a622..06078e4547 100644 --- a/cmd/opdoc/tmLanguage.go +++ b/cmd/opdoc/tmLanguage.go @@ -122,11 +122,11 @@ func buildSyntaxHighlight() *tmLanguage { }, } var allNamedFields []string - allNamedFields = append(allNamedFields, logic.TxnFieldNames...) - allNamedFields = append(allNamedFields, logic.GlobalFieldNames...) - allNamedFields = append(allNamedFields, logic.AssetHoldingFieldNames...) - allNamedFields = append(allNamedFields, logic.AssetParamsFieldNames...) - allNamedFields = append(allNamedFields, logic.OnCompletionNames...) + allNamedFields = append(allNamedFields, logic.TxnFieldNames[:]...) + allNamedFields = append(allNamedFields, logic.GlobalFieldNames[:]...) + allNamedFields = append(allNamedFields, logic.AssetHoldingFieldNames[:]...) + allNamedFields = append(allNamedFields, logic.AssetParamsFieldNames[:]...) + allNamedFields = append(allNamedFields, logic.OnCompletionNames[:]...) literals.Patterns = append(literals.Patterns, pattern{ Name: "variable.parameter.teal", diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index c16a5017a0..b32b471547 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -50,7 +50,7 @@ The 32 byte public key is the last element on the stack, preceded by the 64 byte - Opcode: 0x05 {uint8 curve index} - Stack: ..., A: []byte, B: []byte, C: []byte, D: []byte, E: []byte → ..., uint64 - for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1} -- **Cost**: 1700 +- **Cost**: Secp256k1=1700 Secp256r1=2500 - Availability: v5 `ECDSA` Curves: @@ -68,7 +68,7 @@ The 32 byte Y-component of a public key is the last element on the stack, preced - Opcode: 0x06 {uint8 curve index} - Stack: ..., A: []byte → ..., X: []byte, Y: []byte - decompress pubkey A into components X, Y -- **Cost**: 650 +- **Cost**: Secp256k1=650 Secp256r1=2400 - Availability: v5 `ECDSA` Curves: diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 733ef4a58f..e01b1734bb 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -799,7 +799,7 @@ func txnFieldImm(name string, expectArray bool, ops *OpStream) (*txnFieldSpec, e } if fs.version > ops.Version { return nil, - fmt.Errorf("field %#v available in version %d. Missed #pragma version?", name, fs.version) + fmt.Errorf("%s field was introduced in TEAL v%d. Missed #pragma version?", name, fs.version) } return &fs, nil } @@ -1058,7 +1058,7 @@ func asmGlobal(ops *OpStream, spec *OpSpec, args []string) error { } if fs.version > ops.Version { //nolint:errcheck // we continue to maintain typestack - ops.errorf("%s %s available in version %d. Missed #pragma version?", spec.Name, args[0], fs.version) + ops.errorf("%s %s field was introduced in TEAL v%d. Missed #pragma version?", spec.Name, args[0], fs.version) } val := fs.field @@ -1079,7 +1079,7 @@ func asmAssetHolding(ops *OpStream, spec *OpSpec, args []string) error { } if fs.version > ops.Version { //nolint:errcheck // we continue to maintain typestack - ops.errorf("%s %s available in version %d. Missed #pragma version?", spec.Name, args[0], fs.version) + ops.errorf("%s %s field was introduced in TEAL v%d. Missed #pragma version?", spec.Name, args[0], fs.version) } val := fs.field @@ -1100,7 +1100,7 @@ func asmAssetParams(ops *OpStream, spec *OpSpec, args []string) error { } if fs.version > ops.Version { //nolint:errcheck // we continue to maintain typestack - ops.errorf("%s %s available in version %d. Missed #pragma version?", spec.Name, args[0], fs.version) + ops.errorf("%s %s field was introduced in TEAL v%d. Missed #pragma version?", spec.Name, args[0], fs.version) } val := fs.field @@ -1121,7 +1121,7 @@ func asmAppParams(ops *OpStream, spec *OpSpec, args []string) error { } if fs.version > ops.Version { //nolint:errcheck // we continue to maintain typestack - ops.errorf("%s %s available in version %d. Missed #pragma version?", spec.Name, args[0], fs.version) + ops.errorf("%s %s field was introduced in TEAL v%d. Missed #pragma version?", spec.Name, args[0], fs.version) } val := fs.field @@ -1142,7 +1142,7 @@ func asmAcctParams(ops *OpStream, spec *OpSpec, args []string) error { } if fs.version > ops.Version { //nolint:errcheck // we continue to maintain typestack - ops.errorf("%s %s available in version %d. Missed #pragma version?", spec.Name, args[0], fs.version) + ops.errorf("%s %s field was introduced in TEAL v%d. Missed #pragma version?", spec.Name, args[0], fs.version) } val := fs.field @@ -1153,7 +1153,7 @@ func asmAcctParams(ops *OpStream, spec *OpSpec, args []string) error { return nil } -func asmTxField(ops *OpStream, spec *OpSpec, args []string) error { +func asmItxnField(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { return ops.errorf("%s expects one argument", spec.Name) } @@ -1165,7 +1165,7 @@ func asmTxField(ops *OpStream, spec *OpSpec, args []string) error { return ops.errorf("%s %#v is not allowed.", spec.Name, args[0]) } if fs.itxVersion > ops.Version { - return ops.errorf("%s %#v available in version %d. Missed #pragma version?", spec.Name, args[0], fs.itxVersion) + return ops.errorf("%s %s field was introduced in TEAL v%d. Missed #pragma version?", spec.Name, args[0], fs.itxVersion) } ops.pending.WriteByte(spec.Opcode) ops.pending.WriteByte(uint8(fs.field)) @@ -1183,7 +1183,7 @@ func asmEcdsa(ops *OpStream, spec *OpSpec, args []string) error { } if cs.version > ops.Version { //nolint:errcheck // we continue to maintain typestack - ops.errorf("%s %s available in version %d. Missed #pragma version?", spec.Name, args[0], cs.version) + ops.errorf("%s %s field was introduced in TEAL v%d. Missed #pragma version?", spec.Name, args[0], cs.version) } val := cs.field @@ -1203,7 +1203,7 @@ func asmBase64Decode(ops *OpStream, spec *OpSpec, args []string) error { } if encoding.version > ops.Version { //nolint:errcheck // we continue to maintain typestack - ops.errorf("%s %s available in version %d. Missed #pragma version?", spec.Name, args[0], encoding.version) + ops.errorf("%s %s field was introduced in TEAL v%d. Missed #pragma version?", spec.Name, args[0], encoding.version) } val := encoding.field @@ -1224,7 +1224,7 @@ func asmJSONRef(ops *OpStream, spec *OpSpec, args []string) error { return ops.errorf("%s unsupported JSON value type: %#v", spec.Name, args[0]) } if jsonSpec.version > ops.Version { - return ops.errorf("%s %s available in version %d. Missed #pragma version?", spec.Name, args[0], jsonSpec.version) + return ops.errorf("%s %s field was introduced in TEAL v%d. Missed #pragma version?", spec.Name, args[0], jsonSpec.version) } valueType := jsonSpec.field @@ -1407,15 +1407,18 @@ func typeTxField(ops *OpStream, args []string) (StackTypes, StackTypes) { return StackTypes{fs.ftype}, nil } -// keywords handle parsing and assembling special asm language constructs like 'addr' -// We use OpSpec here, but somewhat degenerate, since they don't have opcodes or eval functions +var pseudoOpDetails = opDetails{1, nil, 2, nil, nil, nil} + +// keywords or "pseudo-ops" handle parsing and assembling special asm language +// constructs like 'addr' We use an OpSpec here, but it's somewhat degenerate, +// since they don't have opcodes or eval functions var keywords = map[string]OpSpec{ - "int": {0, "int", nil, asmInt, nil, nil, oneInt, 1, modeAny, opDetails{1, 2, nil, nil, nil}}, - "byte": {0, "byte", nil, asmByte, nil, nil, oneBytes, 1, modeAny, opDetails{1, 2, nil, nil, nil}}, + "int": {0, "int", nil, asmInt, nil, nil, oneInt, 1, modeAny, pseudoOpDetails}, + "byte": {0, "byte", nil, asmByte, nil, nil, oneBytes, 1, modeAny, pseudoOpDetails}, // parse basics.Address, actually just another []byte constant - "addr": {0, "addr", nil, asmAddr, nil, nil, oneBytes, 1, modeAny, opDetails{1, 2, nil, nil, nil}}, + "addr": {0, "addr", nil, asmAddr, nil, nil, oneBytes, 1, modeAny, pseudoOpDetails}, // take a signature, hash it, and take first 4 bytes, actually just another []byte constant - "method": {0, "method", nil, asmMethod, nil, nil, oneBytes, 1, modeAny, opDetails{1, 2, nil, nil, nil}}, + "method": {0, "method", nil, asmMethod, nil, nil, oneBytes, 1, modeAny, pseudoOpDetails}, } type lineError struct { @@ -2641,7 +2644,7 @@ func disAcctParams(dis *disassembleState, spec *OpSpec) (string, error) { return fmt.Sprintf("%s %s", spec.Name, AcctParamsFieldNames[arg]), nil } -func disTxField(dis *disassembleState, spec *OpSpec) (string, error) { +func disItxnField(dis *disassembleState, spec *OpSpec) (string, error) { lastIdx := dis.pc + 1 if len(dis.program) <= lastIdx { missing := lastIdx - len(dis.program) + 1 diff --git a/data/transactions/logic/backwardCompat_test.go b/data/transactions/logic/backwardCompat_test.go index b5917219df..428dc499d3 100644 --- a/data/transactions/logic/backwardCompat_test.go +++ b/data/transactions/logic/backwardCompat_test.go @@ -360,9 +360,9 @@ func TestBackwardCompatGlobalFields(t *testing.T) { for _, field := range fields { text := fmt.Sprintf("global %s", field.field.String()) // check assembler fails if version before introduction - testLine(t, text, assemblerNoVersion, "...available in version...") + testLine(t, text, assemblerNoVersion, "...was introduced in...") for v := uint64(0); v < field.version; v++ { - testLine(t, text, v, "...available in version...") + testLine(t, text, v, "...was introduced in...") } ops := testProg(t, text, AssemblerMaxVersion) @@ -410,7 +410,7 @@ func TestBackwardCompatTxnFields(t *testing.T) { field := fs.field.String() for _, command := range tests { text := fmt.Sprintf(command, field) - asmError := "...available in version ..." + asmError := "...was introduced in ..." if fs.array { parts := strings.Split(text, " ") op := parts[0] diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index ea714744d6..24e7bfe251 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -17,6 +17,8 @@ package logic import ( + "fmt" + "github.com/algorand/go-algorand/protocol" ) @@ -349,21 +351,35 @@ var OpGroups = map[string][]string{ type OpCost struct { From int To int - Cost int + Cost string } -// OpAllCosts returns an array of the cost score for an op by version. -// Each entry indicates the cost over a range of versions, so if the -// cost has remained constant, there is only one result, otherwise -// each entry shows the cost for a consecutive range of versions, -// inclusive. +// OpAllCosts returns an array of the cost of an op by version. Each entry +// indicates the cost over a range of versions, so if the cost has remained +// constant, there is only one result, otherwise each entry shows the cost for a +// consecutive range of versions, inclusive. func OpAllCosts(opName string) []OpCost { var costs []OpCost for v := 1; v <= LogicVersion; v++ { - cost := OpsByName[v][opName].Details.Cost - if cost == 0 { + spec, ok := OpsByName[v][opName] + if !ok { continue } + cost := fmt.Sprintf("%d", spec.Details.Cost) + if cost == "0" { + cost = "" + // This is quite brittle code, but sufficient for doc generation. + // Right now, costFuncs are only used to inspect the next byte to + // see the field in use. + fakeProgram := make([]byte, 2) + // brittle. these func are on single immediate opcodes right now. + group := spec.Details.Immediates[0].group + for _, name := range group.names { + fakeProgram[1] = group.spec.SpecByName(name).Field() + fcost := spec.Details.costFunc(fakeProgram, 0) + cost += fmt.Sprintf(" %s=%d", name, fcost) + } + } if costs == nil || cost != costs[len(costs)-1].Cost { costs = append(costs, OpCost{v, v, cost}) } else { diff --git a/data/transactions/logic/doc_test.go b/data/transactions/logic/doc_test.go index 85b97aaad1..07ad37fdfc 100644 --- a/data/transactions/logic/doc_test.go +++ b/data/transactions/logic/doc_test.go @@ -137,12 +137,12 @@ func TestOpAllCosts(t *testing.T) { a := OpAllCosts("+") require.Len(t, a, 1) - require.Equal(t, 1, a[0].Cost) + require.Equal(t, "1", a[0].Cost) a = OpAllCosts("sha256") require.Len(t, a, 2) for _, cost := range a { - require.True(t, cost.Cost > 1) + require.True(t, cost.Cost != "0") } } diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index b34c9a5e70..0534e58630 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -904,18 +904,23 @@ func (cx *EvalContext) step() error { if deets.Size != 0 && (cx.pc+deets.Size > len(cx.program)) { return fmt.Errorf("%3d %s program ends short of immediate values", cx.pc, spec.Name) } - cx.cost += deets.Cost + + opcost := deets.Cost + if opcost == 0 { + opcost = deets.costFunc(cx.program, cx.pc) + } + cx.cost += opcost if cx.PooledApplicationBudget != nil { - *cx.PooledApplicationBudget -= deets.Cost + *cx.PooledApplicationBudget -= opcost } if cx.remainingBudget() < 0 { // We're not going to execute the instruction, so give the cost back. // This only matters if this is an inner ClearState - the caller should // not be over debited. (Normally, failure causes total txtree failure.) - cx.cost -= deets.Cost + cx.cost -= opcost if cx.PooledApplicationBudget != nil { - *cx.PooledApplicationBudget += deets.Cost + *cx.PooledApplicationBudget += opcost } return fmt.Errorf("pc=%3d dynamic cost budget exceeded, executing %s: local program cost was %d", cx.pc, spec.Name, cx.cost) @@ -1019,6 +1024,10 @@ func (cx *EvalContext) checkStep() (int, error) { if deets.Size != 0 && (cx.pc+deets.Size > len(cx.program)) { return 0, fmt.Errorf("%3d %s program ends short of immediate values", cx.pc, spec.Name) } + opcost := deets.Cost + if opcost == 0 { + opcost = deets.costFunc(cx.program, cx.pc) + } prevpc := cx.pc if deets.checkFunc != nil { err := deets.checkFunc(cx) @@ -1042,7 +1051,7 @@ func (cx *EvalContext) checkStep() (int, error) { return 0, fmt.Errorf("branch target %d is not an aligned instruction", pc) } } - return deets.Cost, nil + return opcost, nil } func opErr(cx *EvalContext) error { @@ -2062,7 +2071,7 @@ func (cx *EvalContext) assetHoldingToValue(holding *basics.AssetHolding, fs asse return sv, fmt.Errorf("invalid asset_holding_get field %d", fs.field) } - if !typecheck(fs.ftype, sv.argType()) { + if fs.ftype != sv.argType() { return sv, fmt.Errorf("%s expected field type is %s but got %s", fs.field, fs.ftype, sv.argType()) } return sv, nil @@ -2098,7 +2107,7 @@ func (cx *EvalContext) assetParamsToValue(params *basics.AssetParams, creator ba return sv, fmt.Errorf("invalid asset_params_get field %d", fs.field) } - if !typecheck(fs.ftype, sv.argType()) { + if fs.ftype != sv.argType() { return sv, fmt.Errorf("%s expected field type is %s but got %s", fs.field, fs.ftype, sv.argType()) } return sv, nil @@ -2125,7 +2134,7 @@ func (cx *EvalContext) appParamsToValue(params *basics.AppParams, fs appParamsFi return sv, fmt.Errorf("invalid app_params_get field %d", fs.field) } - if !typecheck(fs.ftype, sv.argType()) { + if fs.ftype != sv.argType() { return sv, fmt.Errorf("%s expected field type is %s but got %s", fs.field, fs.ftype, sv.argType()) } return sv, nil @@ -2356,7 +2365,7 @@ func (cx *EvalContext) txnFieldToStack(stxn *transactions.SignedTxnWithAD, fs *t return sv, fmt.Errorf("invalid txn field %s", fs.field) } - if !typecheck(fs.ftype, sv.argType()) { + if fs.ftype != sv.argType() { return sv, fmt.Errorf("%s expected field type is %s but got %s", fs.field, fs.ftype, sv.argType()) } return sv, nil @@ -2804,8 +2813,8 @@ func (cx *EvalContext) globalFieldToValue(fs globalFieldSpec) (sv stackValue, er err = fmt.Errorf("invalid global field %d", fs.field) } - if !typecheck(fs.ftype, sv.argType()) { - err = fmt.Errorf("%s expected field type is %s but got %s", fs.field, fs.ftype, sv.argType()) + if fs.ftype != sv.argType() { + return sv, fmt.Errorf("%s expected field type is %s but got %s", fs.field, fs.ftype, sv.argType()) } return sv, err @@ -2964,6 +2973,11 @@ func unmarshalCompressed(curve elliptic.Curve, data []byte) (x, y *big.Int) { return } +var ecdsaVerifyCosts = map[byte]int{ + byte(Secp256k1): 1700, + byte(Secp256r1): 2500, +} + func opEcdsaVerify(cx *EvalContext) error { ecdsaCurve := EcdsaCurve(cx.program[cx.pc+1]) fs, ok := ecdsaCurveSpecByField[ecdsaCurve] @@ -3020,6 +3034,11 @@ func opEcdsaVerify(cx *EvalContext) error { return nil } +var ecdsaDecompressCosts = map[byte]int{ + byte(Secp256k1): 650, + byte(Secp256r1): 2400, +} + func opEcdsaPkDecompress(cx *EvalContext) error { ecdsaCurve := EcdsaCurve(cx.program[cx.pc+1]) fs, ok := ecdsaCurveSpecByField[ecdsaCurve] @@ -4130,7 +4149,7 @@ func opTxBegin(cx *EvalContext) error { return addInnerTxn(cx) } -func opTxNext(cx *EvalContext) error { +func opItxnNext(cx *EvalContext) error { if len(cx.subtxns) == 0 { return errors.New("itxn_next without itxn_begin") } @@ -4444,7 +4463,7 @@ func (cx *EvalContext) stackIntoTxnField(sv stackValue, fs *txnFieldSpec, txn *t return } -func opTxField(cx *EvalContext) error { +func opItxnField(cx *EvalContext) error { itx := len(cx.subtxns) - 1 if itx < 0 { return errors.New("itxn_field without itxn_begin") @@ -4461,7 +4480,7 @@ func opTxField(cx *EvalContext) error { return err } -func opTxSubmit(cx *EvalContext) error { +func opItxnSubmit(cx *EvalContext) error { // Should rarely trigger, since itxn_next checks these too. (but that check // must be imperfect, see its comment) In contrast to that check, subtxns is // already populated here. diff --git a/data/transactions/logic/evalCrypto_test.go b/data/transactions/logic/evalCrypto_test.go index f38fb85cc4..9520b30da6 100644 --- a/data/transactions/logic/evalCrypto_test.go +++ b/data/transactions/logic/evalCrypto_test.go @@ -437,9 +437,9 @@ byte 0x%s t.Log("decompressTests i", i) src := fmt.Sprintf(source, hex.EncodeToString(test.key), hex.EncodeToString(x), hex.EncodeToString(y)) if test.pass { - testAcceptsWithField(t, src, 5, fidoVersion) + testAccepts(t, src, fidoVersion) } else { - testPanicsWithField(t, src, 5, fidoVersion) + testPanics(t, src, fidoVersion) } }) } @@ -479,9 +479,9 @@ ecdsa_verify Secp256r1 t.Run(fmt.Sprintf("verify/pass=%v", test.pass), func(t *testing.T) { src := fmt.Sprintf(source, test.data, hex.EncodeToString(test.r), hex.EncodeToString(s), hex.EncodeToString(x), hex.EncodeToString(y)) if test.pass { - testAcceptsWithField(t, src, 5, fidoVersion) + testAccepts(t, src, fidoVersion) } else { - testRejectsWithField(t, src, 5, fidoVersion) + testRejects(t, src, fidoVersion) } }) } @@ -531,19 +531,59 @@ byte 0x5ce9454909639d2d17a3f753ce7d93fa0b9ab12e // addr testAccepts(t, progText, 5) } +func TestEcdsaCostVariation(t *testing.T) { + // Doesn't matter if it passes or fails. Just confirm the cost depends on curve. + source := ` +global ZeroAddress // need 32 bytes +byte "signature r" +byte "signature s" +byte "PK x" +byte "PK y" +ecdsa_verify Secp256k1 +! +assert +global OpcodeBudget +int ` + fmt.Sprintf("%d", 20_000-1700-8) + ` +== +` + testAccepts(t, source, 6) // Secp256k1 was 5, but OpcodeBudget is 6 + + source = ` +global ZeroAddress // need 32 bytes +byte "signature r" +byte "signature s" +byte "PK x" +byte "PK y" +ecdsa_verify Secp256r1 +! +assert +global OpcodeBudget +int ` + fmt.Sprintf("%d", 20_000-2500-8) + ` +== +` + testAccepts(t, source, fidoVersion) +} + func BenchmarkHash(b *testing.B) { for _, hash := range []string{"sha256", "keccak256", "sha512_256"} { - b.Run(hash+"-small", func(b *testing.B) { // hash 32 bytes + b.Run(hash+"-0w", func(b *testing.B) { // hash 0 bytes + benchmarkOperation(b, "byte 0x;", hash, "pop; int 1") + }) + b.Run(hash+"-1w", func(b *testing.B) { // hash 32 bytes benchmarkOperation(b, "int 32; bzero", hash, "pop; int 1") }) - b.Run(hash+"-med", func(b *testing.B) { // hash 128 bytes + b.Run(hash+"-4w", func(b *testing.B) { // hash 128 bytes benchmarkOperation(b, "int 32; bzero", "dup; concat; dup; concat;"+hash, "pop; int 1") }) - b.Run(hash+"-big", func(b *testing.B) { // hash 512 bytes + b.Run(hash+"-16w", func(b *testing.B) { // hash 512 bytes benchmarkOperation(b, "int 32; bzero", "dup; concat; dup; concat; dup; concat; dup; concat;"+hash, "pop; int 1") }) + b.Run(hash+"-128w", func(b *testing.B) { // hash 4k bytes + benchmarkOperation(b, "int 32; bzero", + "dup; concat; dup; concat; dup; concat; dup; concat; dup; concat; dup; concat; dup; concat;"+hash, "pop; int 1") + }) } } @@ -664,10 +704,9 @@ func benchmarkEcdsa(b *testing.B, source string, curve EcdsaCurve) { if curve == Secp256k1 { version = 5 } else if curve == Secp256r1 { - version = 6 + version = fidoVersion } - ops, err := AssembleStringWithVersion(source, version) - require.NoError(b, err) + ops := testProg(b, source, version) for i := 0; i < b.N; i++ { data[i].programs = ops.Program } @@ -707,7 +746,7 @@ ecdsa_verify Secp256k1` if LogicVersion >= fidoVersion { b.Run("ecdsa_verify secp256r1", func(b *testing.B) { source := `#pragma version ` + strconv.Itoa(fidoVersion) + ` - arg 0d + arg 0 arg 1 arg 2 arg 3 diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index b405d7c140..09913c2d9f 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -2855,12 +2855,15 @@ func TestPanic(t *testing.T) { ops := testProg(t, `int 1`, v) var hackedOpcode int var oldSpec OpSpec + // Find an unused opcode to temporarily convert to a panicing opcde, + // and append it to program. for opcode, spec := range opsByOpcode[v] { if spec.op == nil { hackedOpcode = opcode oldSpec = spec opsByOpcode[v][opcode].op = opPanic opsByOpcode[v][opcode].Modes = modeAny + opsByOpcode[v][opcode].Details.Cost = 1 opsByOpcode[v][opcode].Details.checkFunc = checkPanic ops.Program = append(ops.Program, byte(opcode)) break @@ -3921,18 +3924,9 @@ func obfuscate(program string) string { type evalTester func(pass bool, err error) bool -func testEvaluation(t *testing.T, program string, introduced uint64, tester evalTester, xtras ...uint64) error { +func testEvaluation(t *testing.T, program string, introduced uint64, tester evalTester) error { t.Helper() - numXtras := len(xtras) - require.LessOrEqual(t, numXtras, 1, "can handle at most 1 extra parameter but provided %d", numXtras) - withField := false - var introducedField uint64 - if numXtras == 1 { - withField = true - introducedField = xtras[0] - } - var outer error for v := uint64(1); v <= AssemblerMaxVersion; v++ { t.Run(fmt.Sprintf("v=%d", v), func(t *testing.T) { @@ -3940,9 +3934,6 @@ func testEvaluation(t *testing.T, program string, introduced uint64, tester eval if v < introduced { testProg(t, obfuscate(program), v, Expect{0, "...was introduced..."}) return - } else if withField && v < introducedField { - testProg(t, obfuscate(program), v, Expect{0, "...available in version..."}) - return } ops := testProg(t, program, v) // Programs created with a previous assembler @@ -3992,19 +3983,6 @@ func testRejects(t *testing.T, program string, introduced uint64) { return !pass && err == nil }) } -func testRejectsWithField(t *testing.T, program string, introducedOpcode, introducedField uint64) { - t.Helper() - testEvaluation(t, program, introducedOpcode, func(pass bool, err error) bool { - // Returned False, but didn't panic - return !pass && err == nil - }, introducedField) -} -func testAcceptsWithField(t *testing.T, program string, introducedOpcode, introducedField uint64) { - t.Helper() - testEvaluation(t, program, introducedOpcode, func(pass bool, err error) bool { - return pass && err == nil - }, introducedField) -} func testPanics(t *testing.T, program string, introduced uint64) error { t.Helper() return testEvaluation(t, program, introduced, func(pass bool, err error) bool { @@ -4012,13 +3990,6 @@ func testPanics(t *testing.T, program string, introduced uint64) error { return !pass && err != nil }) } -func testPanicsWithField(t *testing.T, program string, introducedOpcode, introducedField uint64) error { - t.Helper() - return testEvaluation(t, program, introducedOpcode, func(pass bool, err error) bool { - // TEAL panic! not just reject at exit - return !pass && err != nil - }, introducedField) -} func TestAssert(t *testing.T) { partitiontest.PartitionTest(t) diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 330f583056..226b76bb75 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -170,26 +170,38 @@ const ( // FieldSpec unifies the various specs for presentation type FieldSpec interface { + Field() byte Type() StackType OpVersion() uint64 Note() string Version() uint64 } -// TxnFieldNames are arguments to the 'txn' and 'txnById' opcodes -var TxnFieldNames []string +// FieldSpeccer is something that yields a FieldSpec, given a name for the field +type FieldSpeccer interface { + SpecByName(name string) FieldSpec +} + +// FieldGroup binds all the info for a field (names, int value, spec access) so +// they can be attached to opcodes and used by doc generation +type FieldGroup struct { + names []string + spec FieldSpeccer +} + +// TxnFieldNames are arguments to the 'txn' family of opcodes. +var TxnFieldNames [invalidTxnField]string -var txnFieldSpecByField map[TxnField]txnFieldSpec +var txnFieldSpecByField map[TxnField]txnFieldSpec = make(map[TxnField]txnFieldSpec, len(TxnFieldNames)) // TxnFieldSpecByName gives access to the field specs by field name -var TxnFieldSpecByName tfNameSpecMap +var TxnFieldSpecByName tfNameSpecMap = make(map[string]txnFieldSpec, len(TxnFieldNames)) // simple interface used by doc generator for fields versioning type tfNameSpecMap map[string]txnFieldSpec func (s tfNameSpecMap) SpecByName(name string) FieldSpec { - fs := s[name] - return &fs + return s[name] } type txnFieldSpec struct { @@ -201,19 +213,23 @@ type txnFieldSpec struct { effects bool // Is this a field on the "effects"? That is, something in ApplyData } -func (fs *txnFieldSpec) Type() StackType { +func (fs txnFieldSpec) Field() byte { + return byte(fs.field) +} + +func (fs txnFieldSpec) Type() StackType { return fs.ftype } -func (fs *txnFieldSpec) OpVersion() uint64 { +func (fs txnFieldSpec) OpVersion() uint64 { return 0 } -func (fs *txnFieldSpec) Version() uint64 { +func (fs txnFieldSpec) Version() uint64 { return fs.version } -func (fs *txnFieldSpec) Note() string { +func (fs txnFieldSpec) Note() string { note := txnFieldDocs[fs.field.String()] if fs.effects { note = addExtra(note, "Application mode only") @@ -287,6 +303,8 @@ var txnFieldSpecs = []txnFieldSpec{ {CreatedAssetID, StackUint64, false, 5, 0, true}, {CreatedApplicationID, StackUint64, false, 5, 0, true}, {LastLog, StackBytes, false, 6, 0, true}, + + // Not an effect. Just added after the effects fields. {StateProofPK, StackBytes, false, 6, 6, false}, } @@ -407,7 +425,7 @@ const ( ) // GlobalFieldNames are arguments to the 'global' opcode -var GlobalFieldNames []string +var GlobalFieldNames [invalidGlobalField]string type globalFieldSpec struct { field GlobalField @@ -416,18 +434,22 @@ type globalFieldSpec struct { version uint64 } -func (fs *globalFieldSpec) Type() StackType { +func (fs globalFieldSpec) Field() byte { + return byte(fs.field) +} + +func (fs globalFieldSpec) Type() StackType { return fs.ftype } -func (fs *globalFieldSpec) OpVersion() uint64 { +func (fs globalFieldSpec) OpVersion() uint64 { return 0 } -func (fs *globalFieldSpec) Version() uint64 { +func (fs globalFieldSpec) Version() uint64 { return fs.version } -func (fs *globalFieldSpec) Note() string { +func (fs globalFieldSpec) Note() string { note := globalFieldDocs[fs.field.String()] if fs.mode == runModeApplication { note = addExtra(note, "Application mode only.") @@ -454,16 +476,15 @@ var globalFieldSpecs = []globalFieldSpec{ {CallerApplicationAddress, StackBytes, runModeApplication, 6}, } -var globalFieldSpecByField map[GlobalField]globalFieldSpec +var globalFieldSpecByField map[GlobalField]globalFieldSpec = make(map[GlobalField]globalFieldSpec, len(GlobalFieldNames)) // GlobalFieldSpecByName gives access to the field specs by field name -var GlobalFieldSpecByName gfNameSpecMap +var GlobalFieldSpecByName gfNameSpecMap = make(gfNameSpecMap, len(GlobalFieldNames)) type gfNameSpecMap map[string]globalFieldSpec func (s gfNameSpecMap) SpecByName(name string) FieldSpec { - fs := s[name] - return &fs + return s[name] } // EcdsaCurve is an enum for `ecdsa_` opcodes @@ -478,26 +499,30 @@ const ( ) // EcdsaCurveNames are arguments to the 'ecdsa_' opcode -var EcdsaCurveNames []string +var EcdsaCurveNames [invalidEcdsaCurve]string type ecdsaCurveSpec struct { field EcdsaCurve version uint64 } -func (fs *ecdsaCurveSpec) Type() StackType { +func (fs ecdsaCurveSpec) Field() byte { + return byte(fs.field) +} + +func (fs ecdsaCurveSpec) Type() StackType { return StackNone // Will not show, since all are the same } -func (fs *ecdsaCurveSpec) OpVersion() uint64 { +func (fs ecdsaCurveSpec) OpVersion() uint64 { return 5 } -func (fs *ecdsaCurveSpec) Version() uint64 { +func (fs ecdsaCurveSpec) Version() uint64 { return fs.version } -func (fs *ecdsaCurveSpec) Note() string { +func (fs ecdsaCurveSpec) Note() string { note := EcdsaCurveDocs[fs.field.String()] return note } @@ -507,17 +532,20 @@ var ecdsaCurveSpecs = []ecdsaCurveSpec{ {Secp256r1, fidoVersion}, } -var ecdsaCurveSpecByField map[EcdsaCurve]ecdsaCurveSpec +var ecdsaCurveSpecByField map[EcdsaCurve]ecdsaCurveSpec = make(map[EcdsaCurve]ecdsaCurveSpec, len(EcdsaCurveNames)) // EcdsaCurveSpecByName gives access to the field specs by field name -var EcdsaCurveSpecByName ecDsaCurveNameSpecMap +var EcdsaCurveSpecByName ecDsaCurveNameSpecMap = make(ecDsaCurveNameSpecMap, len(EcdsaCurveNames)) -// simple interface used by doc generator for fields versioning type ecDsaCurveNameSpecMap map[string]ecdsaCurveSpec func (s ecDsaCurveNameSpecMap) SpecByName(name string) FieldSpec { - fs := s[name] - return &fs + return s[name] +} + +var ecdsaCurves = FieldGroup{ + names: EcdsaCurveNames[:], + spec: EcdsaCurveSpecByName, } // Base64Encoding is an enum for the `base64decode` opcode @@ -545,8 +573,8 @@ var base64EncodingSpecs = []base64EncodingSpec{ {StdEncoding, StackBytes, 6}, } -var base64EncodingSpecByField map[Base64Encoding]base64EncodingSpec -var base64EncodingSpecByName base64EncodingSpecMap +var base64EncodingSpecByField map[Base64Encoding]base64EncodingSpec = make(map[Base64Encoding]base64EncodingSpec, len(base64EncodingNames)) +var base64EncodingSpecByName base64EncodingSpecMap = make(base64EncodingSpecMap, len(base64EncodingNames)) type base64EncodingSpecMap map[string]base64EncodingSpec @@ -595,8 +623,8 @@ var jsonRefSpecs = []jsonRefSpec{ {JSONObject, StackBytes, fidoVersion}, } -var jsonRefSpecByField map[JSONRefType]jsonRefSpec -var jsonRefSpecByName jsonRefSpecMap +var jsonRefSpecByField map[JSONRefType]jsonRefSpec = make(map[JSONRefType]jsonRefSpec, len(jsonRefTypeNames)) +var jsonRefSpecByName jsonRefSpecMap = make(jsonRefSpecMap, len(jsonRefTypeNames)) type jsonRefSpecMap map[string]jsonRefSpec @@ -612,7 +640,7 @@ const ( ) // AssetHoldingFieldNames are arguments to the 'asset_holding_get' opcode -var AssetHoldingFieldNames []string +var AssetHoldingFieldNames [invalidAssetHoldingField]string type assetHoldingFieldSpec struct { field AssetHoldingField @@ -620,19 +648,23 @@ type assetHoldingFieldSpec struct { version uint64 } -func (fs *assetHoldingFieldSpec) Type() StackType { +func (fs assetHoldingFieldSpec) Field() byte { + return byte(fs.field) +} + +func (fs assetHoldingFieldSpec) Type() StackType { return fs.ftype } -func (fs *assetHoldingFieldSpec) OpVersion() uint64 { +func (fs assetHoldingFieldSpec) OpVersion() uint64 { return 2 } -func (fs *assetHoldingFieldSpec) Version() uint64 { +func (fs assetHoldingFieldSpec) Version() uint64 { return fs.version } -func (fs *assetHoldingFieldSpec) Note() string { +func (fs assetHoldingFieldSpec) Note() string { note := assetHoldingFieldDocs[fs.field.String()] return note } @@ -642,16 +674,15 @@ var assetHoldingFieldSpecs = []assetHoldingFieldSpec{ {AssetFrozen, StackUint64, 2}, } -var assetHoldingFieldSpecByField map[AssetHoldingField]assetHoldingFieldSpec +var assetHoldingFieldSpecByField map[AssetHoldingField]assetHoldingFieldSpec = make(map[AssetHoldingField]assetHoldingFieldSpec, len(AssetHoldingFieldNames)) // AssetHoldingFieldSpecByName gives access to the field specs by field name -var AssetHoldingFieldSpecByName ahfNameSpecMap +var AssetHoldingFieldSpecByName ahfNameSpecMap = make(ahfNameSpecMap, len(AssetHoldingFieldNames)) type ahfNameSpecMap map[string]assetHoldingFieldSpec func (s ahfNameSpecMap) SpecByName(name string) FieldSpec { - fs := s[name] - return &fs + return s[name] } // AssetParamsField is an enum for `asset_params_get` opcode @@ -688,7 +719,7 @@ const ( ) // AssetParamsFieldNames are arguments to the 'asset_params_get' opcode -var AssetParamsFieldNames []string +var AssetParamsFieldNames [invalidAssetParamsField]string type assetParamsFieldSpec struct { field AssetParamsField @@ -696,19 +727,23 @@ type assetParamsFieldSpec struct { version uint64 } -func (fs *assetParamsFieldSpec) Type() StackType { +func (fs assetParamsFieldSpec) Field() byte { + return byte(fs.field) +} + +func (fs assetParamsFieldSpec) Type() StackType { return fs.ftype } -func (fs *assetParamsFieldSpec) OpVersion() uint64 { +func (fs assetParamsFieldSpec) OpVersion() uint64 { return 2 } -func (fs *assetParamsFieldSpec) Version() uint64 { +func (fs assetParamsFieldSpec) Version() uint64 { return fs.version } -func (fs *assetParamsFieldSpec) Note() string { +func (fs assetParamsFieldSpec) Note() string { note := assetParamsFieldDocs[fs.field.String()] return note } @@ -728,16 +763,15 @@ var assetParamsFieldSpecs = []assetParamsFieldSpec{ {AssetCreator, StackBytes, 5}, } -var assetParamsFieldSpecByField map[AssetParamsField]assetParamsFieldSpec +var assetParamsFieldSpecByField map[AssetParamsField]assetParamsFieldSpec = make(map[AssetParamsField]assetParamsFieldSpec, len(AssetParamsFieldNames)) // AssetParamsFieldSpecByName gives access to the field specs by field name -var AssetParamsFieldSpecByName apfNameSpecMap +var AssetParamsFieldSpecByName apfNameSpecMap = make(apfNameSpecMap, len(AssetParamsFieldNames)) type apfNameSpecMap map[string]assetParamsFieldSpec func (s apfNameSpecMap) SpecByName(name string) FieldSpec { - fs := s[name] - return &fs + return s[name] } // AppParamsField is an enum for `app_params_get` opcode @@ -769,7 +803,7 @@ const ( ) // AppParamsFieldNames are arguments to the 'app_params_get' opcode -var AppParamsFieldNames []string +var AppParamsFieldNames [invalidAppParamsField]string type appParamsFieldSpec struct { field AppParamsField @@ -777,19 +811,23 @@ type appParamsFieldSpec struct { version uint64 } -func (fs *appParamsFieldSpec) Type() StackType { +func (fs appParamsFieldSpec) Field() byte { + return byte(fs.field) +} + +func (fs appParamsFieldSpec) Type() StackType { return fs.ftype } -func (fs *appParamsFieldSpec) OpVersion() uint64 { +func (fs appParamsFieldSpec) OpVersion() uint64 { return 5 } -func (fs *appParamsFieldSpec) Version() uint64 { +func (fs appParamsFieldSpec) Version() uint64 { return fs.version } -func (fs *appParamsFieldSpec) Note() string { +func (fs appParamsFieldSpec) Note() string { note := appParamsFieldDocs[fs.field.String()] return note } @@ -806,17 +844,16 @@ var appParamsFieldSpecs = []appParamsFieldSpec{ {AppAddress, StackBytes, 5}, } -var appParamsFieldSpecByField map[AppParamsField]appParamsFieldSpec +var appParamsFieldSpecByField map[AppParamsField]appParamsFieldSpec = make(map[AppParamsField]appParamsFieldSpec, len(AppParamsFieldNames)) // AppParamsFieldSpecByName gives access to the field specs by field name -var AppParamsFieldSpecByName appNameSpecMap +var AppParamsFieldSpecByName appNameSpecMap = make(appNameSpecMap, len(AppParamsFieldNames)) // simple interface used by doc generator for fields versioning type appNameSpecMap map[string]appParamsFieldSpec func (s appNameSpecMap) SpecByName(name string) FieldSpec { - fs := s[name] - return &fs + return s[name] } // AcctParamsField is an enum for `acct_params_get` opcode @@ -834,7 +871,7 @@ const ( ) // AcctParamsFieldNames are arguments to the 'acct_params_get' opcode -var AcctParamsFieldNames []string +var AcctParamsFieldNames [invalidAcctParamsField]string type acctParamsFieldSpec struct { field AcctParamsField @@ -842,19 +879,23 @@ type acctParamsFieldSpec struct { version uint64 } -func (fs *acctParamsFieldSpec) Type() StackType { +func (fs acctParamsFieldSpec) Field() byte { + return byte(fs.field) +} + +func (fs acctParamsFieldSpec) Type() StackType { return fs.ftype } -func (fs *acctParamsFieldSpec) OpVersion() uint64 { +func (fs acctParamsFieldSpec) OpVersion() uint64 { return 6 } -func (fs *acctParamsFieldSpec) Version() uint64 { +func (fs acctParamsFieldSpec) Version() uint64 { return fs.version } -func (fs *acctParamsFieldSpec) Note() string { +func (fs acctParamsFieldSpec) Note() string { note := acctParamsFieldDocs[fs.field.String()] return note } @@ -865,146 +906,96 @@ var acctParamsFieldSpecs = []acctParamsFieldSpec{ {AcctAuthAddr, StackBytes, 6}, } -var acctParamsFieldSpecByField map[AcctParamsField]acctParamsFieldSpec +var acctParamsFieldSpecByField map[AcctParamsField]acctParamsFieldSpec = make(map[AcctParamsField]acctParamsFieldSpec, len(AcctParamsFieldNames)) // AcctParamsFieldSpecByName gives access to the field specs by field name -var AcctParamsFieldSpecByName acctNameSpecMap +var AcctParamsFieldSpecByName acctNameSpecMap = make(acctNameSpecMap, len(AcctParamsFieldNames)) // simple interface used by doc generator for fields versioning type acctNameSpecMap map[string]acctParamsFieldSpec func (s acctNameSpecMap) SpecByName(name string) FieldSpec { - fs := s[name] - return &fs + return s[name] } func init() { - TxnFieldNames = make([]string, int(invalidTxnField)) - for fi := Sender; fi < invalidTxnField; fi++ { - TxnFieldNames[fi] = fi.String() - } - txnFieldSpecByField = make(map[TxnField]txnFieldSpec, len(TxnFieldNames)) for i, s := range txnFieldSpecs { if int(s.field) != i { panic("txnFieldSpecs disjoint with TxnField enum") } + TxnFieldNames[s.field] = s.field.String() txnFieldSpecByField[s.field] = s - } - TxnFieldSpecByName = make(map[string]txnFieldSpec, len(TxnFieldNames)) - for i, tfn := range TxnFieldNames { - TxnFieldSpecByName[tfn] = txnFieldSpecByField[TxnField(i)] + TxnFieldSpecByName[s.field.String()] = s } - GlobalFieldNames = make([]string, int(invalidGlobalField)) - for i := MinTxnFee; i < invalidGlobalField; i++ { - GlobalFieldNames[i] = i.String() - } - globalFieldSpecByField = make(map[GlobalField]globalFieldSpec, len(GlobalFieldNames)) for i, s := range globalFieldSpecs { if int(s.field) != i { panic("globalFieldSpecs disjoint with GlobalField enum") } + GlobalFieldNames[s.field] = s.field.String() globalFieldSpecByField[s.field] = s - } - GlobalFieldSpecByName = make(gfNameSpecMap, len(GlobalFieldNames)) - for i, gfn := range GlobalFieldNames { - GlobalFieldSpecByName[gfn] = globalFieldSpecByField[GlobalField(i)] + GlobalFieldSpecByName[s.field.String()] = s } - EcdsaCurveNames = make([]string, int(invalidEcdsaCurve)) - for i := Secp256k1; i < invalidEcdsaCurve; i++ { - EcdsaCurveNames[i] = i.String() - } - ecdsaCurveSpecByField = make(map[EcdsaCurve]ecdsaCurveSpec, len(EcdsaCurveNames)) - for _, s := range ecdsaCurveSpecs { + for i, s := range ecdsaCurveSpecs { + if int(s.field) != i { + panic("ecdsaCurveSpecs disjoint with EcdsaCurve enum") + } + EcdsaCurveNames[s.field] = s.field.String() ecdsaCurveSpecByField[s.field] = s + EcdsaCurveSpecByName[s.field.String()] = s } - EcdsaCurveSpecByName = make(ecDsaCurveNameSpecMap, len(EcdsaCurveNames)) - for i, ahfn := range EcdsaCurveNames { - EcdsaCurveSpecByName[ahfn] = ecdsaCurveSpecByField[EcdsaCurve(i)] - } - - base64EncodingSpecByField = make(map[Base64Encoding]base64EncodingSpec, len(base64EncodingNames)) - for _, s := range base64EncodingSpecs { - base64EncodingSpecByField[s.field] = s - } - - base64EncodingSpecByName = make(base64EncodingSpecMap, len(base64EncodingNames)) - for i, encoding := range base64EncodingNames { - base64EncodingSpecByName[encoding] = base64EncodingSpecByField[Base64Encoding(i)] - } - - base64EncodingSpecByField = make(map[Base64Encoding]base64EncodingSpec, len(base64EncodingNames)) - for _, s := range base64EncodingSpecs { + for i, s := range base64EncodingSpecs { + if int(s.field) != i { + panic("base64EncodingSpecs disjoint with Base64Encoding enum") + } base64EncodingSpecByField[s.field] = s + base64EncodingSpecByName[s.field.String()] = s } - base64EncodingSpecByName = make(base64EncodingSpecMap, len(base64EncodingNames)) - for i, encoding := range base64EncodingNames { - base64EncodingSpecByName[encoding] = base64EncodingSpecByField[Base64Encoding(i)] - } - - jsonRefSpecByField = make(map[JSONRefType]jsonRefSpec, len(jsonRefTypeNames)) - for _, s := range jsonRefSpecs { + for i, s := range jsonRefSpecs { + if int(s.field) != i { + panic("jsonRefSpecs disjoint with JSONRefType enum") + } jsonRefSpecByField[s.field] = s + jsonRefSpecByName[s.field.String()] = s } - jsonRefSpecByName = make(jsonRefSpecMap, len(jsonRefTypeNames)) - for i, typename := range jsonRefTypeNames { - jsonRefSpecByName[typename] = jsonRefSpecByField[JSONRefType(i)] - } - - AssetHoldingFieldNames = make([]string, int(invalidAssetHoldingField)) - for i := AssetBalance; i < invalidAssetHoldingField; i++ { - AssetHoldingFieldNames[i] = i.String() - } - assetHoldingFieldSpecByField = make(map[AssetHoldingField]assetHoldingFieldSpec, len(AssetHoldingFieldNames)) - for _, s := range assetHoldingFieldSpecs { + for i, s := range assetHoldingFieldSpecs { + if int(s.field) != i { + panic("assetHoldingFieldSpecs disjoint with AssetHoldingField enum") + } + AssetHoldingFieldNames[i] = s.field.String() assetHoldingFieldSpecByField[s.field] = s - } - AssetHoldingFieldSpecByName = make(ahfNameSpecMap, len(AssetHoldingFieldNames)) - for i, ahfn := range AssetHoldingFieldNames { - AssetHoldingFieldSpecByName[ahfn] = assetHoldingFieldSpecByField[AssetHoldingField(i)] + AssetHoldingFieldSpecByName[s.field.String()] = s } - AssetParamsFieldNames = make([]string, int(invalidAssetParamsField)) - for i := AssetTotal; i < invalidAssetParamsField; i++ { - AssetParamsFieldNames[i] = i.String() - } - assetParamsFieldSpecByField = make(map[AssetParamsField]assetParamsFieldSpec, len(AssetParamsFieldNames)) - for _, s := range assetParamsFieldSpecs { + for i, s := range assetParamsFieldSpecs { + if int(s.field) != i { + panic("assetParamsFieldSpecs disjoint with AssetParamsField enum") + } + AssetParamsFieldNames[i] = s.field.String() assetParamsFieldSpecByField[s.field] = s - } - AssetParamsFieldSpecByName = make(apfNameSpecMap, len(AssetParamsFieldNames)) - for i, apfn := range AssetParamsFieldNames { - AssetParamsFieldSpecByName[apfn] = assetParamsFieldSpecByField[AssetParamsField(i)] + AssetParamsFieldSpecByName[s.field.String()] = s } - AppParamsFieldNames = make([]string, int(invalidAppParamsField)) - for i := AppApprovalProgram; i < invalidAppParamsField; i++ { - AppParamsFieldNames[i] = i.String() - } - appParamsFieldSpecByField = make(map[AppParamsField]appParamsFieldSpec, len(AppParamsFieldNames)) - for _, s := range appParamsFieldSpecs { + for i, s := range appParamsFieldSpecs { + if int(s.field) != i { + panic("appParamsFieldSpecs disjoint with AppParamsField enum") + } + AppParamsFieldNames[i] = s.field.String() appParamsFieldSpecByField[s.field] = s - } - AppParamsFieldSpecByName = make(appNameSpecMap, len(AppParamsFieldNames)) - for i, apfn := range AppParamsFieldNames { - AppParamsFieldSpecByName[apfn] = appParamsFieldSpecByField[AppParamsField(i)] + AppParamsFieldSpecByName[s.field.String()] = s } - AcctParamsFieldNames = make([]string, int(invalidAcctParamsField)) - for i := AcctBalance; i < invalidAcctParamsField; i++ { - AcctParamsFieldNames[i] = i.String() - } - acctParamsFieldSpecByField = make(map[AcctParamsField]acctParamsFieldSpec, len(AcctParamsFieldNames)) - for _, s := range acctParamsFieldSpecs { + for i, s := range acctParamsFieldSpecs { + if int(s.field) != i { + panic("acctParamsFieldSpecs disjoint with AcctParamsField enum") + } + AcctParamsFieldNames[i] = s.field.String() acctParamsFieldSpecByField[s.field] = s - } - AcctParamsFieldSpecByName = make(acctNameSpecMap, len(AcctParamsFieldNames)) - for i, apfn := range AcctParamsFieldNames { - AcctParamsFieldSpecByName[apfn] = acctParamsFieldSpecByField[AcctParamsField(i)] + AcctParamsFieldSpecByName[s.field.String()] = s } txnTypeIndexes = make(map[string]uint64, len(TxnTypeNames)) diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go index b6d1a49893..46f98f4a38 100644 --- a/data/transactions/logic/fields_test.go +++ b/data/transactions/logic/fields_test.go @@ -45,9 +45,9 @@ func TestGlobalFieldsVersions(t *testing.T) { for _, field := range fields { text := fmt.Sprintf("global %s", field.field.String()) // check assembler fails if version before introduction - testLine(t, text, assemblerNoVersion, "...available in version...") + testLine(t, text, assemblerNoVersion, "...was introduced in...") for v := uint64(0); v < field.version; v++ { - testLine(t, text, v, "...available in version...") + testLine(t, text, v, "...was introduced in...") } testLine(t, text, field.version, "") @@ -108,7 +108,7 @@ func TestTxnFieldVersions(t *testing.T) { // TEAL version txn.Txn.RekeyTo = basics.Address{} txgroup := makeSampleTxnGroup(txn) - asmDefaultError := "...available in version ..." + asmDefaultError := "...was introduced in ..." for _, fs := range fields { field := fs.field.String() for _, command := range tests { @@ -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, "...available in version..."}) + testProg(t, text, v, Expect{3, "...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") diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index ab0402e8e4..da6f7dc51a 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -65,25 +65,28 @@ const fidoVersion = 7 // base64, json, secp256r1 // arguments, or dynamic layout controlled by a check function. type opDetails struct { Cost int + costFunc opCostFunc Size int checkFunc opCheckFunc Immediates []immediate typeFunc opTypeFunc } -var opDefault = opDetails{1, 1, nil, nil, nil} -var opBranch = opDetails{1, 3, checkBranch, []immediate{{"target", immLabel}}, nil} +type opCostFunc func(program []byte, pc int) int + +var opDefault = opDetails{1, nil, 1, nil, nil, nil} +var opBranch = opDetails{1, nil, 3, checkBranch, []immediate{{"target", immLabel, nil}}, nil} func costly(cost int) opDetails { - return opDetails{cost, 1, nil, nil, nil} + return opDetails{cost, nil, 1, nil, nil, nil} } func immediates(names ...string) opDetails { immediates := make([]immediate, len(names)) for i, name := range names { - immediates[i] = immediate{name, immByte} + immediates[i] = immediate{name, immByte, nil} } - return opDetails{1, 1 + len(immediates), nil, immediates, nil} + return opDetails{1, nil, 1 + len(immediates), nil, immediates, nil} } func stacky(typer opTypeFunc, imms ...string) opDetails { @@ -92,8 +95,8 @@ func stacky(typer opTypeFunc, imms ...string) opDetails { return d } -func varies(checker opCheckFunc, name string, kind immKind) opDetails { - return opDetails{1, 0, checker, []immediate{{name, kind}}, nil} +func sizeVaries(checker opCheckFunc, name string, kind immKind) opDetails { + return opDetails{1, nil, 0, checker, []immediate{{name, kind, nil}}, nil} } func costlyImm(cost int, names ...string) opDetails { @@ -102,6 +105,20 @@ func costlyImm(cost int, names ...string) opDetails { return opd } +func costByField(immediate string, group *FieldGroup, costs map[byte]int) opDetails { + opd := immediates(immediate) + opd.Immediates[0].group = group + opd.Cost = 0 + opd.costFunc = func(program []byte, pc int) int { + cost, ok := costs[program[pc+1]] + if ok { + return cost + } + return 1 + } + return opd +} + // immType describes the immediate arguments to an opcode type immKind byte @@ -115,8 +132,9 @@ const ( ) type immediate struct { - Name string - kind immKind + Name string + kind immKind + group *FieldGroup } // OpSpec defines an opcode @@ -168,8 +186,8 @@ var OpSpecs = []OpSpec{ {0x04, "ed25519verify", opEd25519Verify, asmDefault, disDefault, threeBytes, oneInt, 1, runModeSignature, costly(1900)}, {0x04, "ed25519verify", opEd25519Verify, asmDefault, disDefault, threeBytes, oneInt, 5, modeAny, costly(1900)}, - {0x05, "ecdsa_verify", opEcdsaVerify, asmEcdsa, disEcdsa, threeBytes.plus(twoBytes), oneInt, 5, modeAny, costlyImm(1700, "v")}, - {0x06, "ecdsa_pk_decompress", opEcdsaPkDecompress, asmEcdsa, disEcdsa, oneBytes, twoBytes, 5, modeAny, costlyImm(650, "v")}, + {0x05, "ecdsa_verify", opEcdsaVerify, asmEcdsa, disEcdsa, threeBytes.plus(twoBytes), oneInt, 5, modeAny, costByField("v", &ecdsaCurves, ecdsaVerifyCosts)}, + {0x06, "ecdsa_pk_decompress", opEcdsaPkDecompress, asmEcdsa, disEcdsa, oneBytes, twoBytes, 5, modeAny, costByField("v", &ecdsaCurves, ecdsaDecompressCosts)}, {0x07, "ecdsa_pk_recover", opEcdsaPkRecover, asmEcdsa, disEcdsa, oneBytes.plus(oneInt).plus(twoBytes), twoBytes, 5, modeAny, costlyImm(2000, "v")}, {0x08, "+", opPlus, asmDefault, disDefault, twoInts, oneInt, 1, modeAny, opDefault}, @@ -197,13 +215,13 @@ var OpSpecs = []OpSpec{ {0x1e, "addw", opAddw, asmDefault, disDefault, twoInts, twoInts, 2, modeAny, opDefault}, {0x1f, "divmodw", opDivModw, asmDefault, disDefault, twoInts.plus(twoInts), twoInts.plus(twoInts), 4, modeAny, costly(20)}, - {0x20, "intcblock", opIntConstBlock, asmIntCBlock, disIntcblock, nil, nil, 1, modeAny, varies(checkIntConstBlock, "uint ...", immInts)}, + {0x20, "intcblock", opIntConstBlock, asmIntCBlock, disIntcblock, nil, nil, 1, modeAny, sizeVaries(checkIntConstBlock, "uint ...", immInts)}, {0x21, "intc", opIntConstLoad, asmIntC, disIntc, nil, oneInt, 1, modeAny, immediates("i")}, {0x22, "intc_0", opIntConst0, asmDefault, disIntc, nil, oneInt, 1, modeAny, opDefault}, {0x23, "intc_1", opIntConst1, asmDefault, disIntc, nil, oneInt, 1, modeAny, opDefault}, {0x24, "intc_2", opIntConst2, asmDefault, disIntc, nil, oneInt, 1, modeAny, opDefault}, {0x25, "intc_3", opIntConst3, asmDefault, disIntc, nil, oneInt, 1, modeAny, opDefault}, - {0x26, "bytecblock", opByteConstBlock, asmByteCBlock, disBytecblock, nil, nil, 1, modeAny, varies(checkByteConstBlock, "bytes ...", immBytess)}, + {0x26, "bytecblock", opByteConstBlock, asmByteCBlock, disBytecblock, nil, nil, 1, modeAny, sizeVaries(checkByteConstBlock, "bytes ...", immBytess)}, {0x27, "bytec", opByteConstLoad, asmByteC, disBytec, nil, oneBytes, 1, modeAny, immediates("i")}, {0x28, "bytec_0", opByteConst0, asmDefault, disBytec, nil, oneBytes, 1, modeAny, opDefault}, {0x29, "bytec_1", opByteConst1, asmDefault, disBytec, nil, oneBytes, 1, modeAny, opDefault}, @@ -299,8 +317,8 @@ var OpSpecs = []OpSpec{ {0x78, "min_balance", opMinBalance, asmDefault, disDefault, oneAny, oneInt, directRefEnabledVersion, runModeApplication, opDefault}, // Immediate bytes and ints. Smaller code size for single use of constant. - {0x80, "pushbytes", opPushBytes, asmPushBytes, disPushBytes, nil, oneBytes, 3, modeAny, varies(opPushBytes, "bytes", immBytes)}, - {0x81, "pushint", opPushInt, asmPushInt, disPushInt, nil, oneInt, 3, modeAny, varies(opPushInt, "uint", immInt)}, + {0x80, "pushbytes", opPushBytes, asmPushBytes, disPushBytes, nil, oneBytes, 3, modeAny, sizeVaries(opPushBytes, "bytes", immBytes)}, + {0x81, "pushint", opPushInt, asmPushInt, disPushInt, nil, oneInt, 3, modeAny, sizeVaries(opPushInt, "uint", immInt)}, {0x84, "ed25519verify_bare", opEd25519VerifyBare, asmDefault, disDefault, threeBytes, oneInt, 7, modeAny, costly(1900)}, @@ -341,11 +359,11 @@ var OpSpecs = []OpSpec{ // AVM "effects" {0xb0, "log", opLog, asmDefault, disDefault, oneBytes, nil, 5, runModeApplication, opDefault}, {0xb1, "itxn_begin", opTxBegin, asmDefault, disDefault, nil, nil, 5, runModeApplication, opDefault}, - {0xb2, "itxn_field", opTxField, asmTxField, disTxField, oneAny, nil, 5, runModeApplication, stacky(typeTxField, "f")}, - {0xb3, "itxn_submit", opTxSubmit, asmDefault, disDefault, nil, nil, 5, runModeApplication, opDefault}, + {0xb2, "itxn_field", opItxnField, asmItxnField, disItxnField, oneAny, nil, 5, runModeApplication, stacky(typeTxField, "f")}, + {0xb3, "itxn_submit", opItxnSubmit, asmDefault, disDefault, nil, nil, 5, runModeApplication, opDefault}, {0xb4, "itxn", opItxn, asmItxn, disTxn, nil, oneAny, 5, runModeApplication, immediates("f")}, {0xb5, "itxna", opItxna, asmTxna, disTxna, nil, oneAny, 5, runModeApplication, immediates("f", "i")}, - {0xb6, "itxn_next", opTxNext, asmDefault, disDefault, nil, nil, 6, runModeApplication, opDefault}, + {0xb6, "itxn_next", opItxnNext, asmDefault, disDefault, nil, nil, 6, runModeApplication, opDefault}, {0xb7, "gitxn", opGitxn, asmGitxn, disGtxn, nil, oneAny, 6, runModeApplication, immediates("t", "f")}, {0xb8, "gitxna", opGitxna, asmGtxna, disGtxna, nil, oneAny, 6, runModeApplication, immediates("t", "f", "i")}, From 612df904ba744c8c74f72a1a691b7bf26e63ee05 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Thu, 24 Mar 2022 14:25:39 -0400 Subject: [PATCH 2/9] Streamline field handling. Simplifies adding a new field. Eliminates need for custom disassembler. Avoids hash lookup in opcode evaluation. Simplifies opdoc. --- cmd/opdoc/opdoc.go | 49 +- cmd/opdoc/tmLanguage.go | 15 +- data/transactions/logic/README.md | 6 +- data/transactions/logic/TEAL_opcodes.md | 26 +- data/transactions/logic/assembler.go | 417 ++++--------- data/transactions/logic/assembler_test.go | 33 +- data/transactions/logic/doc.go | 147 +---- data/transactions/logic/doc_test.go | 7 - data/transactions/logic/eval.go | 35 +- data/transactions/logic/evalStateful_test.go | 24 +- data/transactions/logic/eval_test.go | 13 +- data/transactions/logic/fields.go | 594 ++++++++++++------- data/transactions/logic/fields_string.go | 9 +- data/transactions/logic/fields_test.go | 2 +- data/transactions/logic/opcodes.go | 142 +++-- 15 files changed, 681 insertions(+), 838 deletions(-) diff --git a/cmd/opdoc/opdoc.go b/cmd/opdoc/opdoc.go index a457fbfb8b..e13a9ac612 100644 --- a/cmd/opdoc/opdoc.go +++ b/cmd/opdoc/opdoc.go @@ -77,7 +77,11 @@ func integerConstantsTableMarkdown(out io.Writer) { out.Write([]byte("\n")) } -func fieldSpecsMarkdown(out io.Writer, names []string, specs logic.FieldSpeccer) { +func fieldGroupMarkdown(out io.Writer, group logic.FieldGroup) { + fieldSpecsMarkdown(out, group.Names, group.Specs) +} + +func fieldSpecsMarkdown(out io.Writer, names []string, specs logic.FieldSpecMap) { showTypes := false showVers := false spec0 := specs.SpecByName(names[0]) @@ -246,10 +250,8 @@ func opToMarkdown(out io.Writer, op *logic.OpSpec) (err error) { appParamsFieldsMarkdown(out) case "acct_params_get": acctParamsFieldsMarkdown(out) - default: - if strings.HasPrefix(op.Name, "ecdsa") { - ecDsaCurvesMarkdown(out) - } + case "ecdsa_verify": + ecDsaCurvesMarkdown(out) } ode := logic.OpDocExtra(op.Name) if ode != "" { @@ -317,7 +319,7 @@ func typeString(types []logic.StackType) string { return string(out) } -func fieldsAndTypes(names []string, specs logic.FieldSpeccer) ([]string, string) { +func fieldsAndTypes(names []string, specs logic.FieldSpecMap) ([]string, string) { types := make([]logic.StackType, len(names)) for i, name := range names { types[i] = specs.SpecByName(name).Type() @@ -396,29 +398,18 @@ func main() { integerConstantsTableMarkdown(constants) constants.Close() - txnfields := create("txn_fields.md") - fieldSpecsMarkdown(txnfields, logic.TxnFieldNames[:], logic.TxnFieldSpecByName) - txnfields.Close() - - globalfields := create("global_fields.md") - fieldSpecsMarkdown(globalfields, logic.GlobalFieldNames[:], logic.GlobalFieldSpecByName) - globalfields.Close() - - assetholding := create("asset_holding_fields.md") - fieldSpecsMarkdown(assetholding, logic.AssetHoldingFieldNames[:], logic.AssetHoldingFieldSpecByName) - assetholding.Close() - - assetparams := create("asset_params_fields.md") - fieldSpecsMarkdown(assetparams, logic.AssetParamsFieldNames[:], logic.AssetParamsFieldSpecByName) - assetparams.Close() - - appparams := create("app_params_fields.md") - fieldSpecsMarkdown(appparams, logic.AppParamsFieldNames[:], logic.AppParamsFieldSpecByName) - appparams.Close() - - acctparams, _ := os.Create("acct_params_fields.md") - fieldSpecsMarkdown(acctparams, logic.AcctParamsFieldNames[:], logic.AcctParamsFieldSpecByName) - acctparams.Close() + written := make(map[string]bool) + opSpecs := logic.OpcodesByVersion(logic.LogicVersion) + for _, spec := range opSpecs { + for _, imm := range spec.Details.Immediates { + if imm.Group != nil && !written[imm.Group.Name] { + out := create(imm.Group.Name + "_fields.md") + fieldSpecsMarkdown(out, imm.Group.Names, imm.Group.Specs) + out.Close() + written[imm.Group.Name] = true + } + } + } langspecjs := create("langspec.json") enc := json.NewEncoder(langspecjs) diff --git a/cmd/opdoc/tmLanguage.go b/cmd/opdoc/tmLanguage.go index 06078e4547..d66e39e861 100644 --- a/cmd/opdoc/tmLanguage.go +++ b/cmd/opdoc/tmLanguage.go @@ -122,11 +122,18 @@ func buildSyntaxHighlight() *tmLanguage { }, } var allNamedFields []string - allNamedFields = append(allNamedFields, logic.TxnFieldNames[:]...) - allNamedFields = append(allNamedFields, logic.GlobalFieldNames[:]...) - allNamedFields = append(allNamedFields, logic.AssetHoldingFieldNames[:]...) - allNamedFields = append(allNamedFields, logic.AssetParamsFieldNames[:]...) + allNamedFields = append(allNamedFields, logic.TxnTypeNames[:]...) allNamedFields = append(allNamedFields, logic.OnCompletionNames[:]...) + accumulated := make(map[string]bool) + opSpecs := logic.OpcodesByVersion(logic.LogicVersion) + for _, spec := range opSpecs { + for _, imm := range spec.Details.Immediates { + if imm.Group != nil && !accumulated[imm.Group.Name] { + allNamedFields = append(allNamedFields, imm.Group.Names[:]...) + accumulated[imm.Group.Name] = true + } + } + } literals.Patterns = append(literals.Patterns, pattern{ Name: "variable.parameter.teal", diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 71d15cb466..880cae6479 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -436,7 +436,7 @@ Some of these have immediate data in the byte or bytes after the opcode. | 16 | TypeEnum | uint64 | | See table below | | 17 | XferAsset | uint64 | | Asset ID | | 18 | AssetAmount | uint64 | | value in Asset's units | -| 19 | AssetSender | []byte | | 32 byte address. Causes clawback of all value of asset from AssetSender if Sender is the Clawback address of the asset. | +| 19 | AssetSender | []byte | | 32 byte address. Moves asset from AssetSender if Sender is the Clawback address of the asset. | | 20 | AssetReceiver | []byte | | 32 byte address | | 21 | AssetCloseTo | []byte | | 32 byte address | | 22 | GroupIndex | uint64 | | Position of this transaction within an atomic transaction group. A stand-alone transaction is implicitly element 0 in a group of 1 | @@ -457,7 +457,7 @@ Some of these have immediate data in the byte or bytes after the opcode. | 37 | ConfigAssetUnitName | []byte | v2 | Unit name of the asset | | 38 | ConfigAssetName | []byte | v2 | The asset name | | 39 | ConfigAssetURL | []byte | v2 | URL | -| 40 | ConfigAssetMetadataHash | []byte | v2 | 32 byte commitment to some unspecified asset metadata | +| 40 | ConfigAssetMetadataHash | []byte | v2 | 32 byte commitment to unspecified asset metadata | | 41 | ConfigAssetManager | []byte | v2 | 32 byte address | | 42 | ConfigAssetReserve | []byte | v2 | 32 byte address | | 43 | ConfigAssetFreeze | []byte | v2 | 32 byte address | @@ -527,7 +527,7 @@ Asset fields include `AssetHolding` and `AssetParam` fields that are used in the | 4 | AssetName | []byte | | Asset name | | 5 | AssetURL | []byte | | URL with additional info about the asset | | 6 | AssetMetadataHash | []byte | | Arbitrary commitment | -| 7 | AssetManager | []byte | | Manager commitment | +| 7 | AssetManager | []byte | | Manager address | | 8 | AssetReserve | []byte | | Reserve address | | 9 | AssetFreeze | []byte | | Freeze address | | 10 | AssetClawback | []byte | | Clawback address | diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index b32b471547..d85809434c 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -57,8 +57,8 @@ The 32 byte public key is the last element on the stack, preceded by the 64 byte | Index | Name | In | Notes | | - | ------ | - | --------- | -| 0 | Secp256k1 | | secp256k1 curve | -| 1 | Secp256r1 | v7 | secp256r1 curve | +| 0 | Secp256k1 | | secp256k1 curve, used in Bitcoin | +| 1 | Secp256r1 | v7 | secp256r1 curve, NIST standard | The 32 byte Y-component of a public key is the last element on the stack, preceded by X-component of a pubkey, preceded by S and R components of a signature, preceded by the data that is fifth element on the stack. All values are big-endian encoded. The signed data must be 32 bytes long, and signatures in lower-S form are only accepted. @@ -71,14 +71,6 @@ The 32 byte Y-component of a public key is the last element on the stack, preced - **Cost**: Secp256k1=650 Secp256r1=2400 - Availability: v5 -`ECDSA` Curves: - -| Index | Name | In | Notes | -| - | ------ | - | --------- | -| 0 | Secp256k1 | | secp256k1 curve | -| 1 | Secp256r1 | v7 | secp256r1 curve | - - The 33 byte public key in a compressed form to be decompressed into X and Y (top) components. All values are big-endian encoded. ## ecdsa_pk_recover v @@ -89,14 +81,6 @@ The 33 byte public key in a compressed form to be decompressed into X and Y (top - **Cost**: 2000 - Availability: v5 -`ECDSA` Curves: - -| Index | Name | In | Notes | -| - | ------ | - | --------- | -| 0 | Secp256k1 | | secp256k1 curve | -| 1 | Secp256r1 | v7 | secp256r1 curve | - - S (top) and R elements of a signature, recovery id and data (bottom) are expected on the stack and used to deriver a public key. All values are big-endian encoded. The signed data must be 32 bytes long. ## + @@ -396,7 +380,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u | 16 | TypeEnum | uint64 | | See table below | | 17 | XferAsset | uint64 | | Asset ID | | 18 | AssetAmount | uint64 | | value in Asset's units | -| 19 | AssetSender | []byte | | 32 byte address. Causes clawback of all value of asset from AssetSender if Sender is the Clawback address of the asset. | +| 19 | AssetSender | []byte | | 32 byte address. Moves asset from AssetSender if Sender is the Clawback address of the asset. | | 20 | AssetReceiver | []byte | | 32 byte address | | 21 | AssetCloseTo | []byte | | 32 byte address | | 22 | GroupIndex | uint64 | | Position of this transaction within an atomic transaction group. A stand-alone transaction is implicitly element 0 in a group of 1 | @@ -417,7 +401,7 @@ The notation J,K indicates that two uint64 values J and K are interpreted as a u | 37 | ConfigAssetUnitName | []byte | v2 | Unit name of the asset | | 38 | ConfigAssetName | []byte | v2 | The asset name | | 39 | ConfigAssetURL | []byte | v2 | URL | -| 40 | ConfigAssetMetadataHash | []byte | v2 | 32 byte commitment to some unspecified asset metadata | +| 40 | ConfigAssetMetadataHash | []byte | v2 | 32 byte commitment to unspecified asset metadata | | 41 | ConfigAssetManager | []byte | v2 | 32 byte address | | 42 | ConfigAssetReserve | []byte | v2 | 32 byte address | | 43 | ConfigAssetFreeze | []byte | v2 | 32 byte address | @@ -933,7 +917,7 @@ params: Txn.Accounts offset (or, since v4, an _available_ address), asset id (or | 4 | AssetName | []byte | | Asset name | | 5 | AssetURL | []byte | | URL with additional info about the asset | | 6 | AssetMetadataHash | []byte | | Arbitrary commitment | -| 7 | AssetManager | []byte | | Manager commitment | +| 7 | AssetManager | []byte | | Manager address | | 8 | AssetReserve | []byte | | Reserve address | | 9 | AssetFreeze | []byte | | Freeze address | | 10 | AssetClawback | []byte | | Clawback address | diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index e01b1734bb..3d1d21d0d7 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -399,20 +399,14 @@ func asmInt(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { return ops.error("int needs one argument") } - // check friendly TypeEnum constants - te, isTypeEnum := txnTypeConstToUint64[args[0]] - if isTypeEnum { - ops.Uint(te) - return nil - } - // check raw transaction type strings - tt, isTypeStr := txnTypeIndexes[args[0]] - if isTypeStr { - ops.Uint(tt) + // check txn type constants + i, ok := txnTypeMap[args[0]] + if ok { + ops.Uint(i) return nil } // check OnCompetion constants - oc, isOCStr := onCompletionConstToUint64[args[0]] + oc, isOCStr := onCompletionMap[args[0]] if isOCStr { ops.Uint(oc) return nil @@ -2191,27 +2185,127 @@ func (dis *disassembleState) outputLabelIfNeeded() (err error) { type disFunc func(dis *disassembleState, spec *OpSpec) (string, error) -// Basic disasemble, and extra bytes of opcode are decoded as bytes integers. +// Basic disasemble. Immediates are decoded based on info in the OpSpec. func disDefault(dis *disassembleState, spec *OpSpec) (string, error) { - lastIdx := dis.pc + spec.Details.Size - 1 - if len(dis.program) <= lastIdx { - missing := lastIdx - len(dis.program) + 1 - return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing) - } - dis.nextpc = dis.pc + spec.Details.Size out := spec.Name - for s := 1; s < spec.Details.Size; s++ { - b := uint(dis.program[dis.pc+s]) - out += fmt.Sprintf(" %d", b) + pc := dis.pc + 1 + for _, imm := range spec.Details.Immediates { + out += " " + switch imm.kind { + case immByte: + if pc >= len(dis.program) { + return "", fmt.Errorf("program end while reading immediate %s for %s", + imm.Name, spec.Name) + } + b := dis.program[pc] + if imm.Group != nil { + if int(b) >= len(imm.Group.Names) { + return "", fmt.Errorf("invalid immediate %s for %s: %d", imm.Name, spec.Name, b) + } + out += imm.Group.Names[b] + } else { + out += fmt.Sprintf("%d", b) + } + if spec.Name == "intc" && int(b) < len(dis.intc) { + out += fmt.Sprintf(" // %d", dis.intc[b]) + } + if spec.Name == "bytec" && int(b) < len(dis.bytec) { + out += fmt.Sprintf(" // %s", guessByteFormat(dis.bytec[b])) + } + + pc++ + case immLabel: + offset := (uint(dis.program[pc]) << 8) | uint(dis.program[pc+1]) + target := int(offset) + pc + 2 + if target > 0xffff { + target -= 0x10000 + } + var label string + if dis.numericTargets { + label = fmt.Sprintf("%d", target) + } else { + if known, ok := dis.pendingLabels[target]; ok { + label = known + } else { + dis.labelCount++ + label = fmt.Sprintf("label%d", dis.labelCount) + dis.putLabel(label, target) + } + } + out += label + pc += 2 + case immInt: + val, bytesUsed := binary.Uvarint(dis.program[pc:]) + if bytesUsed <= 0 { + return "", fmt.Errorf("could not decode immediate %s for %s", imm.Name, spec.Name) + } + out += fmt.Sprintf("%d", val) + pc += bytesUsed + case immBytes: + length, bytesUsed := binary.Uvarint(dis.program[pc:]) + if bytesUsed <= 0 { + return "", fmt.Errorf("could not decode immediate %s for %s", imm.Name, spec.Name) + } + pc += bytesUsed + end := uint64(pc) + length + if end > uint64(len(dis.program)) || end < uint64(pc) { + return "", fmt.Errorf("could not decode immediate %s for %s", imm.Name, spec.Name) + } + bytes := dis.program[pc:end] + out += fmt.Sprintf("0x%s // %s", hex.EncodeToString(bytes), guessByteFormat(bytes)) + pc = int(end) + case immInts: + intc, nextpc, err := parseIntcblock(dis.program, pc) + if err != nil { + return "", err + } + + dis.intc = append(dis.intc, intc...) + for i, iv := range intc { + if i != 0 { + out += " " + } + out += fmt.Sprintf("%d", iv) + } + pc = nextpc + case immBytess: + bytec, nextpc, err := parseBytecBlock(dis.program, pc) + if err != nil { + return "", err + } + dis.bytec = append(dis.bytec, bytec...) + for i, bv := range bytec { + if i != 0 { + out += " " + } + out += fmt.Sprintf("0x%s", hex.EncodeToString(bv)) + } + pc = nextpc + default: + return "", fmt.Errorf("disDefault asked to disassemble complex immediate") + } + } + + if strings.HasPrefix(spec.Name, "intc_") { + b := spec.Name[len(spec.Name)-1] - byte('0') + if int(b) < len(dis.intc) { + out += fmt.Sprintf(" // %d", dis.intc[b]) + } + } + if strings.HasPrefix(spec.Name, "bytec_") { + b := spec.Name[len(spec.Name)-1] - byte('0') + if int(b) < len(dis.intc) { + out += fmt.Sprintf(" // %s", guessByteFormat(dis.bytec[b])) + } } + dis.nextpc = pc return out, nil } var errShortIntcblock = errors.New("intcblock ran past end of program") var errTooManyIntc = errors.New("intcblock with too many items") -func parseIntcblock(program []byte, pc int) (intc []uint64, nextpc int, err error) { - pos := pc + 1 +func parseIntcblock(program []byte, pos int) (intc []uint64, nextpc int, err error) { numInts, bytesUsed := binary.Uvarint(program[pos:]) if bytesUsed <= 0 { err = fmt.Errorf("could not decode intcblock size at pc=%d", pos) @@ -2267,8 +2361,7 @@ func checkIntConstBlock(cx *EvalContext) error { var errShortBytecblock = errors.New("bytecblock ran past end of program") var errTooManyItems = errors.New("bytecblock with too many items") -func parseBytecBlock(program []byte, pc int) (bytec [][]byte, nextpc int, err error) { - pos := pc + 1 +func parseBytecBlock(program []byte, pos int) (bytec [][]byte, nextpc int, err error) { numItems, bytesUsed := binary.Uvarint(program[pos:]) if bytesUsed <= 0 { err = fmt.Errorf("could not decode bytecblock size at pc=%d", pos) @@ -2341,20 +2434,6 @@ func checkByteConstBlock(cx *EvalContext) error { return nil } -func disIntcblock(dis *disassembleState, spec *OpSpec) (string, error) { - intc, nextpc, err := parseIntcblock(dis.program, dis.pc) - if err != nil { - return "", err - } - dis.nextpc = nextpc - out := spec.Name - for _, iv := range intc { - dis.intc = append(dis.intc, iv) - out += fmt.Sprintf(" %d", iv) - } - return out, nil -} - func disIntc(dis *disassembleState, spec *OpSpec) (string, error) { lastIdx := dis.pc + spec.Details.Size - 1 if len(dis.program) <= lastIdx { @@ -2389,20 +2468,6 @@ func disIntc(dis *disassembleState, spec *OpSpec) (string, error) { return fmt.Sprintf("intc%s", suffix), nil } -func disBytecblock(dis *disassembleState, spec *OpSpec) (string, error) { - bytec, nextpc, err := parseBytecBlock(dis.program, dis.pc) - if err != nil { - return "", err - } - dis.nextpc = nextpc - out := spec.Name - for _, bv := range bytec { - dis.bytec = append(dis.bytec, bv) - out += fmt.Sprintf(" 0x%s", hex.EncodeToString(bv)) - } - return out, nil -} - func allPrintableASCII(bytes []byte) bool { for _, b := range bytes { if b < 32 || b > 126 { @@ -2456,252 +2521,6 @@ func disBytec(dis *disassembleState, spec *OpSpec) (string, error) { return fmt.Sprintf("bytec%s", suffix), nil } -func disPushInt(dis *disassembleState, spec *OpSpec) (string, error) { - pos := dis.pc + 1 - val, bytesUsed := binary.Uvarint(dis.program[pos:]) - if bytesUsed <= 0 { - return "", fmt.Errorf("could not decode int at pc=%d", pos) - } - dis.nextpc = pos + bytesUsed - return fmt.Sprintf("%s %d", spec.Name, val), nil -} - -func disPushBytes(dis *disassembleState, spec *OpSpec) (string, error) { - pos := dis.pc + 1 - length, bytesUsed := binary.Uvarint(dis.program[pos:]) - if bytesUsed <= 0 { - return "", fmt.Errorf("could not decode bytes length at pc=%d", pos) - } - pos += bytesUsed - end := uint64(pos) + length - if end > uint64(len(dis.program)) || end < uint64(pos) { - return "", fmt.Errorf("pushbytes too long %d %d", end, pos) - } - bytes := dis.program[pos:end] - dis.nextpc = int(end) - return fmt.Sprintf("%s 0x%s // %s", spec.Name, hex.EncodeToString(bytes), guessByteFormat(bytes)), nil -} - -// This is also used to disassemble gtxns, gtxnsas, txnas, itxn, itxnas -func disTxn(dis *disassembleState, spec *OpSpec) (string, error) { - lastIdx := dis.pc + 1 - if len(dis.program) <= lastIdx { - missing := lastIdx - len(dis.program) + 1 - return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing) - } - dis.nextpc = dis.pc + 2 - txarg := dis.program[dis.pc+1] - if int(txarg) >= len(TxnFieldNames) { - return "", fmt.Errorf("invalid txn arg index %d at pc=%d", txarg, dis.pc) - } - return fmt.Sprintf("%s %s", spec.Name, TxnFieldNames[txarg]), nil -} - -// This is also used to disassemble gtxnsa -func disTxna(dis *disassembleState, spec *OpSpec) (string, error) { - lastIdx := dis.pc + 2 - if len(dis.program) <= lastIdx { - missing := lastIdx - len(dis.program) + 1 - return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing) - } - dis.nextpc = dis.pc + 3 - txarg := dis.program[dis.pc+1] - if int(txarg) >= len(TxnFieldNames) { - return "", fmt.Errorf("invalid txn arg index %d at pc=%d", txarg, dis.pc) - } - arrayFieldIdx := dis.program[dis.pc+2] - return fmt.Sprintf("%s %s %d", spec.Name, TxnFieldNames[txarg], arrayFieldIdx), nil -} - -// disGtxn is also used to disassemble gtxnas, gitxn, gitxnas -func disGtxn(dis *disassembleState, spec *OpSpec) (string, error) { - lastIdx := dis.pc + 2 - if len(dis.program) <= lastIdx { - missing := lastIdx - len(dis.program) + 1 - return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing) - } - dis.nextpc = dis.pc + 3 - gi := dis.program[dis.pc+1] - txarg := dis.program[dis.pc+2] - if int(txarg) >= len(TxnFieldNames) { - return "", fmt.Errorf("invalid txn arg index %d at pc=%d", txarg, dis.pc) - } - return fmt.Sprintf("%s %d %s", spec.Name, gi, TxnFieldNames[txarg]), nil -} - -// disGtxna is also used to disassemble gitxna -func disGtxna(dis *disassembleState, spec *OpSpec) (string, error) { - lastIdx := dis.pc + 3 - if len(dis.program) <= lastIdx { - missing := lastIdx - len(dis.program) + 1 - return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing) - } - dis.nextpc = dis.pc + 4 - gi := dis.program[dis.pc+1] - txarg := dis.program[dis.pc+2] - if int(txarg) >= len(TxnFieldNames) { - return "", fmt.Errorf("invalid txn arg index %d at pc=%d", txarg, dis.pc) - } - arrayFieldIdx := dis.program[dis.pc+3] - return fmt.Sprintf("%s %d %s %d", spec.Name, gi, TxnFieldNames[txarg], arrayFieldIdx), nil -} - -func disGlobal(dis *disassembleState, spec *OpSpec) (string, error) { - lastIdx := dis.pc + 1 - if len(dis.program) <= lastIdx { - missing := lastIdx - len(dis.program) + 1 - return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing) - } - dis.nextpc = dis.pc + 2 - garg := dis.program[dis.pc+1] - if int(garg) >= len(GlobalFieldNames) { - return "", fmt.Errorf("invalid global arg index %d at pc=%d", garg, dis.pc) - } - return fmt.Sprintf("%s %s", spec.Name, GlobalFieldNames[garg]), nil -} - -func disBranch(dis *disassembleState, spec *OpSpec) (string, error) { - lastIdx := dis.pc + 2 - if len(dis.program) <= lastIdx { - missing := lastIdx - len(dis.program) + 1 - return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing) - } - - dis.nextpc = dis.pc + 3 - offset := (uint(dis.program[dis.pc+1]) << 8) | uint(dis.program[dis.pc+2]) - target := int(offset) + dis.pc + 3 - if target > 0xffff { - target -= 0x10000 - } - var label string - if dis.numericTargets { - label = fmt.Sprintf("%d", target) - } else { - if known, ok := dis.pendingLabels[target]; ok { - label = known - } else { - dis.labelCount++ - label = fmt.Sprintf("label%d", dis.labelCount) - dis.putLabel(label, target) - } - } - return fmt.Sprintf("%s %s", spec.Name, label), nil -} - -func disAssetHolding(dis *disassembleState, spec *OpSpec) (string, error) { - lastIdx := dis.pc + 1 - if len(dis.program) <= lastIdx { - missing := lastIdx - len(dis.program) + 1 - return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing) - } - dis.nextpc = dis.pc + 2 - arg := dis.program[dis.pc+1] - if int(arg) >= len(AssetHoldingFieldNames) { - return "", fmt.Errorf("invalid asset holding arg index %d at pc=%d", arg, dis.pc) - } - return fmt.Sprintf("%s %s", spec.Name, AssetHoldingFieldNames[arg]), nil -} - -func disAssetParams(dis *disassembleState, spec *OpSpec) (string, error) { - lastIdx := dis.pc + 1 - if len(dis.program) <= lastIdx { - missing := lastIdx - len(dis.program) + 1 - return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing) - } - dis.nextpc = dis.pc + 2 - arg := dis.program[dis.pc+1] - if int(arg) >= len(AssetParamsFieldNames) { - return "", fmt.Errorf("invalid asset params arg index %d at pc=%d", arg, dis.pc) - } - return fmt.Sprintf("%s %s", spec.Name, AssetParamsFieldNames[arg]), nil -} - -func disAppParams(dis *disassembleState, spec *OpSpec) (string, error) { - lastIdx := dis.pc + 1 - if len(dis.program) <= lastIdx { - missing := lastIdx - len(dis.program) + 1 - return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing) - } - dis.nextpc = dis.pc + 2 - arg := dis.program[dis.pc+1] - if int(arg) >= len(AppParamsFieldNames) { - return "", fmt.Errorf("invalid app params arg index %d at pc=%d", arg, dis.pc) - } - return fmt.Sprintf("%s %s", spec.Name, AppParamsFieldNames[arg]), nil -} - -func disAcctParams(dis *disassembleState, spec *OpSpec) (string, error) { - lastIdx := dis.pc + 1 - if len(dis.program) <= lastIdx { - missing := lastIdx - len(dis.program) + 1 - return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing) - } - dis.nextpc = dis.pc + 2 - arg := dis.program[dis.pc+1] - if int(arg) >= len(AcctParamsFieldNames) { - return "", fmt.Errorf("invalid acct params arg index %d at pc=%d", arg, dis.pc) - } - return fmt.Sprintf("%s %s", spec.Name, AcctParamsFieldNames[arg]), nil -} - -func disItxnField(dis *disassembleState, spec *OpSpec) (string, error) { - lastIdx := dis.pc + 1 - if len(dis.program) <= lastIdx { - missing := lastIdx - len(dis.program) + 1 - return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing) - } - dis.nextpc = dis.pc + 2 - arg := dis.program[dis.pc+1] - if int(arg) >= len(TxnFieldNames) { - return "", fmt.Errorf("invalid %s arg index %d at pc=%d", spec.Name, arg, dis.pc) - } - return fmt.Sprintf("%s %s", spec.Name, TxnFieldNames[arg]), nil -} - -func disEcdsa(dis *disassembleState, spec *OpSpec) (string, error) { - lastIdx := dis.pc + 1 - if len(dis.program) <= lastIdx { - missing := lastIdx - len(dis.program) + 1 - return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing) - } - dis.nextpc = dis.pc + 2 - arg := dis.program[dis.pc+1] - if int(arg) >= len(EcdsaCurveNames) { - return "", fmt.Errorf("invalid curve arg index %d at pc=%d", arg, dis.pc) - } - return fmt.Sprintf("%s %s", spec.Name, EcdsaCurveNames[arg]), nil -} - -func disBase64Decode(dis *disassembleState, spec *OpSpec) (string, error) { - lastIdx := dis.pc + 1 - if len(dis.program) <= lastIdx { - missing := lastIdx - len(dis.program) + 1 - return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing) - } - dis.nextpc = dis.pc + 2 - b64dArg := dis.program[dis.pc+1] - if int(b64dArg) >= len(base64EncodingNames) { - return "", fmt.Errorf("invalid base64_decode arg index %d at pc=%d", b64dArg, dis.pc) - } - return fmt.Sprintf("%s %s", spec.Name, base64EncodingNames[b64dArg]), nil -} - -func disJSONRef(dis *disassembleState, spec *OpSpec) (string, error) { - lastIdx := dis.pc + 1 - if len(dis.program) <= lastIdx { - missing := lastIdx - len(dis.program) + 1 - return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing) - } - dis.nextpc = dis.pc + 2 - - jsonRefArg := dis.program[dis.pc+1] - if int(jsonRefArg) >= len(jsonRefSpecByName) { - return "", fmt.Errorf("invalid json_ref arg index %d at pc=%d", jsonRefArg, dis.pc) - } - - return fmt.Sprintf("%s %s", spec.Name, jsonRefTypeNames[jsonRefArg]), nil -} - type disInfo struct { pcOffset []PCOffset hasStatefulOps bool diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 29a7a1aff9..f366af65f5 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -1522,7 +1522,7 @@ func TestAssembleDisassembleErrors(t *testing.T) { ops.Program[2] = 0x50 // txn field _, err = Disassemble(ops.Program) require.Error(t, err) - require.Contains(t, err.Error(), "invalid txn arg index") + require.Contains(t, err.Error(), "invalid immediate f for txn") source = `txna Accounts 0` ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) @@ -1530,7 +1530,7 @@ func TestAssembleDisassembleErrors(t *testing.T) { ops.Program[2] = 0x50 // txn field _, err = Disassemble(ops.Program) require.Error(t, err) - require.Contains(t, err.Error(), "invalid txn arg index") + require.Contains(t, err.Error(), "invalid immediate f for txna") source = `gtxn 0 Sender` ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) @@ -1538,7 +1538,7 @@ func TestAssembleDisassembleErrors(t *testing.T) { ops.Program[3] = 0x50 // txn field _, err = Disassemble(ops.Program) require.Error(t, err) - require.Contains(t, err.Error(), "invalid txn arg index") + require.Contains(t, err.Error(), "invalid immediate f for gtxn") source = `gtxna 0 Accounts 0` ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) @@ -1546,7 +1546,7 @@ func TestAssembleDisassembleErrors(t *testing.T) { ops.Program[3] = 0x50 // txn field _, err = Disassemble(ops.Program) require.Error(t, err) - require.Contains(t, err.Error(), "invalid txn arg index") + require.Contains(t, err.Error(), "invalid immediate f for gtxna") source = `global MinTxnFee` ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) @@ -1554,7 +1554,7 @@ func TestAssembleDisassembleErrors(t *testing.T) { ops.Program[2] = 0x50 // txn field _, err = Disassemble(ops.Program) require.Error(t, err) - require.Contains(t, err.Error(), "invalid global arg index") + require.Contains(t, err.Error(), "invalid immediate f for global") ops.Program[0] = 0x11 // version out, err := Disassemble(ops.Program) @@ -1573,7 +1573,7 @@ func TestAssembleDisassembleErrors(t *testing.T) { ops.Program[7] = 0x50 // holding field _, err = Disassemble(ops.Program) require.Error(t, err) - require.Contains(t, err.Error(), "invalid asset holding arg index") + require.Contains(t, err.Error(), "invalid immediate f for asset_holding_get") source = "int 0\nasset_params_get AssetTotal" ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) @@ -1581,7 +1581,7 @@ func TestAssembleDisassembleErrors(t *testing.T) { ops.Program[4] = 0x50 // params field _, err = Disassemble(ops.Program) require.Error(t, err) - require.Contains(t, err.Error(), "invalid asset params arg index") + require.Contains(t, err.Error(), "invalid immediate f for asset_params_get") source = "int 0\nasset_params_get AssetTotal" ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) @@ -1591,17 +1591,22 @@ func TestAssembleDisassembleErrors(t *testing.T) { ops.Program = ops.Program[0 : len(ops.Program)-1] _, err = Disassemble(ops.Program) require.Error(t, err) - require.Contains(t, err.Error(), "unexpected asset_params_get opcode end: missing 1 bytes") + require.Contains(t, err.Error(), "program end while reading immediate f for asset_params_get") source = "gtxna 0 Accounts 0" ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) require.NoError(t, err) _, err = Disassemble(ops.Program) require.NoError(t, err) - ops.Program = ops.Program[0 : len(ops.Program)-2] - _, err = Disassemble(ops.Program) + _, err = Disassemble(ops.Program[0 : len(ops.Program)-1]) + require.Error(t, err) + require.Contains(t, err.Error(), "program end while reading immediate i for gtxna") + _, err = Disassemble(ops.Program[0 : len(ops.Program)-2]) + require.Error(t, err) + require.Contains(t, err.Error(), "program end while reading immediate f for gtxna") + _, err = Disassemble(ops.Program[0 : len(ops.Program)-3]) require.Error(t, err) - require.Contains(t, err.Error(), "unexpected gtxna opcode end: missing 2 bytes") + require.Contains(t, err.Error(), "program end while reading immediate t for gtxna") source = "txna Accounts 0" ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) @@ -1611,7 +1616,7 @@ func TestAssembleDisassembleErrors(t *testing.T) { ops.Program = ops.Program[0 : len(ops.Program)-1] _, err = Disassemble(ops.Program) require.Error(t, err) - require.Contains(t, err.Error(), "unexpected txna opcode end: missing 1 bytes") + require.Contains(t, err.Error(), "program end while reading immediate i for txna") source = "byte 0x4141\nsubstring 0 1" ops, err = AssembleStringWithVersion(source, AssemblerMaxVersion) @@ -1621,7 +1626,7 @@ func TestAssembleDisassembleErrors(t *testing.T) { ops.Program = ops.Program[0 : len(ops.Program)-1] _, err = Disassemble(ops.Program) require.Error(t, err) - require.Contains(t, err.Error(), "unexpected substring opcode end: missing 1 bytes") + require.Contains(t, err.Error(), "program end while reading immediate e for substring") } func TestAssembleVersions(t *testing.T) { @@ -2198,7 +2203,7 @@ func TestErrShortBytecblock(t *testing.T) { text := `intcblock 0x1234567812345678 0x1234567812345671 0x1234567812345672 0x1234567812345673 4 5 6 7 8` ops, err := AssembleStringWithVersion(text, 1) require.NoError(t, err) - _, _, err = parseIntcblock(ops.Program, 0) + _, _, err = parseIntcblock(ops.Program, 1) require.Equal(t, err, errShortIntcblock) var cx EvalContext diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 24e7bfe251..82a9414ffe 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -373,9 +373,9 @@ func OpAllCosts(opName string) []OpCost { // see the field in use. fakeProgram := make([]byte, 2) // brittle. these func are on single immediate opcodes right now. - group := spec.Details.Immediates[0].group - for _, name := range group.names { - fakeProgram[1] = group.spec.SpecByName(name).Field() + group := spec.Details.Immediates[0].Group + for _, name := range group.Names { + fakeProgram[1] = group.Specs.SpecByName(name).Field() fcost := spec.Details.costFunc(fakeProgram, 0) cost += fmt.Sprintf(" %s=%d", name, fcost) } @@ -424,99 +424,6 @@ func OnCompletionDescription(value uint64) string { // OnCompletionPreamble describes what the OnCompletion constants represent. const OnCompletionPreamble = "An application transaction must indicate the action to be taken following the execution of its approvalProgram or clearStateProgram. The constants below describe the available actions." -var txnFieldDocs = map[string]string{ - "Type": "Transaction type as bytes", - "TypeEnum": "See table below", - "Sender": "32 byte address", - "Fee": "microalgos", - "FirstValid": "round number", - "FirstValidTime": "Causes program to fail; reserved for future use", - "LastValid": "round number", - "Note": "Any data up to 1024 bytes", - "Lease": "32 byte lease value", - "RekeyTo": "32 byte Sender's new AuthAddr", - - "GroupIndex": "Position of this transaction within an atomic transaction group. A stand-alone transaction is implicitly element 0 in a group of 1", - "TxID": "The computed ID for this transaction. 32 bytes.", - - "Receiver": "32 byte address", - "Amount": "microalgos", - "CloseRemainderTo": "32 byte address", - - "VotePK": "32 byte address", - "SelectionPK": "32 byte address", - "StateProofPK": "64 byte state proof public key commitment", - "VoteFirst": "The first round that the participation key is valid.", - "VoteLast": "The last round that the participation key is valid.", - "VoteKeyDilution": "Dilution for the 2-level participation key", - "Nonparticipation": "Marks an account nonparticipating for rewards", - - "XferAsset": "Asset ID", - "AssetAmount": "value in Asset's units", - "AssetSender": "32 byte address. Causes clawback of all value of asset from AssetSender if Sender is the Clawback address of the asset.", - "AssetReceiver": "32 byte address", - "AssetCloseTo": "32 byte address", - - "ApplicationID": "ApplicationID from ApplicationCall transaction", - "OnCompletion": "ApplicationCall transaction on completion action", - "ApplicationArgs": "Arguments passed to the application in the ApplicationCall transaction", - "NumAppArgs": "Number of ApplicationArgs", - "Accounts": "Accounts listed in the ApplicationCall transaction", - "NumAccounts": "Number of Accounts", - "Assets": "Foreign Assets listed in the ApplicationCall transaction", - "NumAssets": "Number of Assets", - "Applications": "Foreign Apps listed in the ApplicationCall transaction", - "NumApplications": "Number of Applications", - "GlobalNumUint": "Number of global state integers in ApplicationCall", - "GlobalNumByteSlice": "Number of global state byteslices in ApplicationCall", - "LocalNumUint": "Number of local state integers in ApplicationCall", - "LocalNumByteSlice": "Number of local state byteslices in ApplicationCall", - "ApprovalProgram": "Approval program", - "ClearStateProgram": "Clear state program", - "ExtraProgramPages": "Number of additional pages for each of the application's approval and clear state programs. An ExtraProgramPages of 1 means 2048 more total bytes, or 1024 for each program.", - - "ConfigAsset": "Asset ID in asset config transaction", - "ConfigAssetTotal": "Total number of units of this asset created", - "ConfigAssetDecimals": "Number of digits to display after the decimal place when displaying the asset", - "ConfigAssetDefaultFrozen": "Whether the asset's slots are frozen by default or not, 0 or 1", - "ConfigAssetUnitName": "Unit name of the asset", - "ConfigAssetName": "The asset name", - "ConfigAssetURL": "URL", - "ConfigAssetMetadataHash": "32 byte commitment to some unspecified asset metadata", - "ConfigAssetManager": "32 byte address", - "ConfigAssetReserve": "32 byte address", - "ConfigAssetFreeze": "32 byte address", - "ConfigAssetClawback": "32 byte address", - - "FreezeAsset": "Asset ID being frozen or un-frozen", - "FreezeAssetAccount": "32 byte address of the account whose asset slot is being frozen or un-frozen", - "FreezeAssetFrozen": "The new frozen value, 0 or 1", - - "Logs": "Log messages emitted by an application call (only with `itxn` in v5)", - "NumLogs": "Number of Logs (only with `itxn` in v5)", - "LastLog": "The last message emitted. Empty bytes if none were emitted", - "CreatedAssetID": "Asset ID allocated by the creation of an ASA (only with `itxn` in v5)", - "CreatedApplicationID": "ApplicationID allocated by the creation of an application (only with `itxn` in v5)", -} - -var globalFieldDocs = map[string]string{ - "MinTxnFee": "microalgos", - "MinBalance": "microalgos", - "MaxTxnLife": "rounds", - "ZeroAddress": "32 byte address of all zero bytes", - "GroupSize": "Number of transactions in this atomic transaction group. At least 1", - "LogicSigVersion": "Maximum supported version", - "Round": "Current round number", - "LatestTimestamp": "Last confirmed block UNIX timestamp. Fails if negative", - "CurrentApplicationID": "ID of current application executing", - "CreatorAddress": "Address of the creator of the current application", - "CurrentApplicationAddress": "Address that the current application controls", - "GroupID": "ID of the transaction group. 32 zero bytes if the transaction is not part of a group.", - "OpcodeBudget": "The remaining cost that can be spent by opcodes in this program.", - "CallerApplicationID": "The application ID of the application that called this application. 0 if this application is at the top-level.", - "CallerApplicationAddress": "The application address of the application that called this application. ZeroAddress if this application is at the top-level.", -} - func addExtra(original string, extra string) string { if len(original) == 0 { return extra @@ -530,51 +437,3 @@ func addExtra(original string, extra string) string { } return original + sep + extra } - -// AssetHoldingFieldDocs are notes on fields available in `asset_holding_get` -var assetHoldingFieldDocs = map[string]string{ - "AssetBalance": "Amount of the asset unit held by this account", - "AssetFrozen": "Is the asset frozen or not", -} - -// assetParamsFieldDocs are notes on fields available in `asset_params_get` -var assetParamsFieldDocs = map[string]string{ - "AssetTotal": "Total number of units of this asset", - "AssetDecimals": "See AssetParams.Decimals", - "AssetDefaultFrozen": "Frozen by default or not", - "AssetUnitName": "Asset unit name", - "AssetName": "Asset name", - "AssetURL": "URL with additional info about the asset", - "AssetMetadataHash": "Arbitrary commitment", - "AssetManager": "Manager commitment", - "AssetReserve": "Reserve address", - "AssetFreeze": "Freeze address", - "AssetClawback": "Clawback address", - "AssetCreator": "Creator address", -} - -// appParamsFieldDocs are notes on fields available in `app_params_get` -var appParamsFieldDocs = map[string]string{ - "AppApprovalProgram": "Bytecode of Approval Program", - "AppClearStateProgram": "Bytecode of Clear State Program", - "AppGlobalNumUint": "Number of uint64 values allowed in Global State", - "AppGlobalNumByteSlice": "Number of byte array values allowed in Global State", - "AppLocalNumUint": "Number of uint64 values allowed in Local State", - "AppLocalNumByteSlice": "Number of byte array values allowed in Local State", - "AppExtraProgramPages": "Number of Extra Program Pages of code space", - "AppCreator": "Creator address", - "AppAddress": "Address for which this application has authority", -} - -// acctParamsFieldDocs are notes on fields available in `app_params_get` -var acctParamsFieldDocs = map[string]string{ - "AcctBalance": "Account balance in microalgos", - "AcctMinBalance": "Minimum required blance for account, in microalgos", - "AcctAuthAddr": "Address the account is rekeyed to.", -} - -// EcdsaCurveDocs are notes on curves available in `ecdsa_` opcodes -var EcdsaCurveDocs = map[string]string{ - "Secp256k1": "secp256k1 curve", - "Secp256r1": "secp256r1 curve", -} diff --git a/data/transactions/logic/doc_test.go b/data/transactions/logic/doc_test.go index 07ad37fdfc..a0bbaa384b 100644 --- a/data/transactions/logic/doc_test.go +++ b/data/transactions/logic/doc_test.go @@ -40,15 +40,8 @@ func TestOpDocs(t *testing.T) { assert.True(t, seen, "opDocByName is missing doc for %#v", op) } - require.Len(t, txnFieldDocs, len(TxnFieldNames)) require.Len(t, onCompletionDescriptions, len(OnCompletionNames)) - require.Len(t, globalFieldDocs, len(GlobalFieldNames)) - require.Len(t, assetHoldingFieldDocs, len(AssetHoldingFieldNames)) - require.Len(t, assetParamsFieldDocs, len(AssetParamsFieldNames)) - require.Len(t, appParamsFieldDocs, len(AppParamsFieldNames)) - require.Len(t, acctParamsFieldDocs, len(AcctParamsFieldNames)) require.Len(t, TypeNameDescriptions, len(TxnTypeNames)) - require.Len(t, EcdsaCurveDocs, len(EcdsaCurveNames)) } // TestDocStragglers confirms that we don't have any docs laying diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 0534e58630..8b4c226449 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1787,7 +1787,7 @@ func opBytesZero(cx *EvalContext) error { func opIntConstBlock(cx *EvalContext) error { var err error - cx.intc, cx.nextpc, err = parseIntcblock(cx.program, cx.pc) + cx.intc, cx.nextpc, err = parseIntcblock(cx.program, cx.pc+1) return err } @@ -1828,7 +1828,7 @@ func opPushInt(cx *EvalContext) error { func opByteConstBlock(cx *EvalContext) error { var err error - cx.bytec, cx.nextpc, err = parseBytecBlock(cx.program, cx.pc) + cx.bytec, cx.nextpc, err = parseBytecBlock(cx.program, cx.pc+1) return err } @@ -2147,7 +2147,10 @@ func TxnFieldToTealValue(txn *transactions.Transaction, groupIndex int, field Tx } var cx EvalContext stxnad := &transactions.SignedTxnWithAD{SignedTxn: transactions.SignedTxn{Txn: *txn}} - fs := txnFieldSpecByField[field] + fs, ok := txnFieldSpecByField(field) + if !ok { + return basics.TealValue{}, fmt.Errorf("invalid field %s", field) + } sv, err := cx.txnFieldToStack(stxnad, &fs, arrayFieldIdx, groupIndex, inner) return sv.toTealValue(), err } @@ -2229,7 +2232,7 @@ func (cx *EvalContext) txnFieldToStack(stxn *transactions.SignedTxnWithAD, fs *t case Type: sv.Bytes = []byte(txn.Type) case TypeEnum: - sv.Uint = txnTypeIndexes[string(txn.Type)] + sv.Uint = txnTypeMap[string(txn.Type)] case XferAsset: sv.Uint = uint64(txn.XferAsset) case AssetAmount: @@ -2372,7 +2375,7 @@ func (cx *EvalContext) txnFieldToStack(stxn *transactions.SignedTxnWithAD, fs *t } func (cx *EvalContext) fetchField(field TxnField, expectArray bool) (*txnFieldSpec, error) { - fs, ok := txnFieldSpecByField[field] + fs, ok := txnFieldSpecByField(field) if !ok || fs.version > cx.version { return nil, fmt.Errorf("invalid txn field %d", field) } @@ -2822,7 +2825,7 @@ func (cx *EvalContext) globalFieldToValue(fs globalFieldSpec) (sv stackValue, er func opGlobal(cx *EvalContext) error { globalField := GlobalField(cx.program[cx.pc+1]) - fs, ok := globalFieldSpecByField[globalField] + fs, ok := globalFieldSpecByField(globalField) if !ok || fs.version > cx.version { return fmt.Errorf("invalid global field %s", globalField) } @@ -2980,7 +2983,7 @@ var ecdsaVerifyCosts = map[byte]int{ func opEcdsaVerify(cx *EvalContext) error { ecdsaCurve := EcdsaCurve(cx.program[cx.pc+1]) - fs, ok := ecdsaCurveSpecByField[ecdsaCurve] + fs, ok := ecdsaCurveSpecByField(ecdsaCurve) if !ok || fs.version > cx.version { return fmt.Errorf("invalid curve %d", ecdsaCurve) } @@ -3041,7 +3044,7 @@ var ecdsaDecompressCosts = map[byte]int{ func opEcdsaPkDecompress(cx *EvalContext) error { ecdsaCurve := EcdsaCurve(cx.program[cx.pc+1]) - fs, ok := ecdsaCurveSpecByField[ecdsaCurve] + fs, ok := ecdsaCurveSpecByField(ecdsaCurve) if !ok || fs.version > cx.version { return fmt.Errorf("invalid curve %d", ecdsaCurve) } @@ -3085,7 +3088,7 @@ func opEcdsaPkDecompress(cx *EvalContext) error { func opEcdsaPkRecover(cx *EvalContext) error { ecdsaCurve := EcdsaCurve(cx.program[cx.pc+1]) - fs, ok := ecdsaCurveSpecByField[ecdsaCurve] + fs, ok := ecdsaCurveSpecByField(ecdsaCurve) if !ok || fs.version > cx.version { return fmt.Errorf("invalid curve %d", ecdsaCurve) } @@ -3930,7 +3933,7 @@ func opAssetHoldingGet(cx *EvalContext) error { prev := last - 1 // account holdingField := AssetHoldingField(cx.program[cx.pc+1]) - fs, ok := assetHoldingFieldSpecByField[holdingField] + fs, ok := assetHoldingFieldSpecByField(holdingField) if !ok || fs.version > cx.version { return fmt.Errorf("invalid asset_holding_get field %d", holdingField) } @@ -3965,7 +3968,7 @@ func opAssetParamsGet(cx *EvalContext) error { last := len(cx.stack) - 1 // asset paramField := AssetParamsField(cx.program[cx.pc+1]) - fs, ok := assetParamsFieldSpecByField[paramField] + fs, ok := assetParamsFieldSpecByField(paramField) if !ok || fs.version > cx.version { return fmt.Errorf("invalid asset_params_get field %d", paramField) } @@ -3995,7 +3998,7 @@ func opAppParamsGet(cx *EvalContext) error { last := len(cx.stack) - 1 // app paramField := AppParamsField(cx.program[cx.pc+1]) - fs, ok := appParamsFieldSpecByField[paramField] + fs, ok := appParamsFieldSpecByField(paramField) if !ok || fs.version > cx.version { return fmt.Errorf("invalid app_params_get field %d", paramField) } @@ -4039,7 +4042,7 @@ func opAcctParamsGet(cx *EvalContext) error { } paramField := AcctParamsField(cx.program[cx.pc+1]) - fs, ok := acctParamsFieldSpecByField[paramField] + fs, ok := acctParamsFieldSpecByField(paramField) if !ok || fs.version > cx.version { return fmt.Errorf("invalid acct_params_get field %d", paramField) } @@ -4470,7 +4473,7 @@ func opItxnField(cx *EvalContext) error { } last := len(cx.stack) - 1 field := TxnField(cx.program[cx.pc+1]) - fs, ok := txnFieldSpecByField[field] + fs, ok := txnFieldSpecByField(field) if !ok || fs.itxVersion == 0 || fs.itxVersion > cx.version { return fmt.Errorf("invalid itxn_field %s", field) } @@ -4649,7 +4652,7 @@ func base64Decode(encoded []byte, encoding *base64.Encoding) ([]byte, error) { func opBase64Decode(cx *EvalContext) error { last := len(cx.stack) - 1 encodingField := Base64Encoding(cx.program[cx.pc+1]) - fs, ok := base64EncodingSpecByField[encodingField] + fs, ok := base64EncodingSpecByField(encodingField) if !ok || fs.version > cx.version { return fmt.Errorf("invalid base64_decode encoding %d", encodingField) } @@ -4759,7 +4762,7 @@ func opJSONRef(cx *EvalContext) error { } stval.Bytes = parsed[key] default: - return fmt.Errorf("unsupported json_ref return type, should not have reached here") + return fmt.Errorf("unsupported json_ref return type %s", expectedType) } cx.stack[last] = stval return nil diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 86271a592d..56b0697714 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -2197,12 +2197,12 @@ func TestEnumFieldErrors(t *testing.T) { partitiontest.PartitionTest(t) source := `txn Amount` - origSpec := txnFieldSpecByField[Amount] + origSpec := txnFieldSpecs[Amount] changed := origSpec changed.ftype = StackBytes - txnFieldSpecByField[Amount] = changed + txnFieldSpecs[Amount] = changed defer func() { - txnFieldSpecByField[Amount] = origSpec + txnFieldSpecs[Amount] = origSpec }() testLogic(t, source, AssemblerMaxVersion, defaultEvalParams(nil), "Amount expected field type is []byte but got uint64") @@ -2210,12 +2210,12 @@ func TestEnumFieldErrors(t *testing.T) { source = `global MinTxnFee` - origMinTxnFs := globalFieldSpecByField[MinTxnFee] + origMinTxnFs := globalFieldSpecs[MinTxnFee] badMinTxnFs := origMinTxnFs badMinTxnFs.ftype = StackBytes - globalFieldSpecByField[MinTxnFee] = badMinTxnFs + globalFieldSpecs[MinTxnFee] = badMinTxnFs defer func() { - globalFieldSpecByField[MinTxnFee] = origMinTxnFs + globalFieldSpecs[MinTxnFee] = origMinTxnFs }() testLogic(t, source, AssemblerMaxVersion, defaultEvalParams(nil), "MinTxnFee expected field type is []byte but got uint64") @@ -2242,12 +2242,12 @@ int 55 asset_holding_get AssetBalance assert ` - origBalanceFs := assetHoldingFieldSpecByField[AssetBalance] + origBalanceFs := assetHoldingFieldSpecs[AssetBalance] badBalanceFs := origBalanceFs badBalanceFs.ftype = StackBytes - assetHoldingFieldSpecByField[AssetBalance] = badBalanceFs + assetHoldingFieldSpecs[AssetBalance] = badBalanceFs defer func() { - assetHoldingFieldSpecByField[AssetBalance] = origBalanceFs + assetHoldingFieldSpecs[AssetBalance] = origBalanceFs }() testApp(t, source, ep, "AssetBalance expected field type is []byte but got uint64") @@ -2256,12 +2256,12 @@ assert asset_params_get AssetTotal assert ` - origTotalFs := assetParamsFieldSpecByField[AssetTotal] + origTotalFs := assetParamsFieldSpecs[AssetTotal] badTotalFs := origTotalFs badTotalFs.ftype = StackBytes - assetParamsFieldSpecByField[AssetTotal] = badTotalFs + assetParamsFieldSpecs[AssetTotal] = badTotalFs defer func() { - assetParamsFieldSpecByField[AssetTotal] = origTotalFs + assetParamsFieldSpecs[AssetTotal] = origTotalFs }() testApp(t, source, ep, "AssetTotal expected field type is []byte but got uint64") diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 09913c2d9f..12499bc745 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1089,7 +1089,7 @@ func TestOnCompletionConstants(t *testing.T) { } require.Less(t, last, max, "too many OnCompletion constants, adjust max limit") require.Equal(t, int(invalidOnCompletionConst), last) - require.Equal(t, len(onCompletionConstToUint64), len(onCompletionDescriptions)) + require.Equal(t, len(onCompletionMap), len(onCompletionDescriptions)) require.Equal(t, len(OnCompletionNames), last) for v := NoOp; v < invalidOnCompletionConst; v++ { require.Equal(t, v.String(), OnCompletionNames[int(v)]) @@ -1099,8 +1099,8 @@ func TestOnCompletionConstants(t *testing.T) { for i := 0; i < last; i++ { oc := OnCompletionConstType(i) symbol := oc.String() - require.Contains(t, onCompletionConstToUint64, symbol) - require.Equal(t, uint64(i), onCompletionConstToUint64[symbol]) + require.Contains(t, onCompletionMap, symbol) + require.Equal(t, uint64(i), onCompletionMap[symbol]) t.Run(symbol, func(t *testing.T) { testAccepts(t, fmt.Sprintf("int %s; int %s; ==;", symbol, oc), 1) }) @@ -1543,7 +1543,7 @@ func TestTxn(t *testing.T) { } for i, txnField := range TxnFieldNames { - fs := txnFieldSpecByField[TxnField(i)] + fs := txnFieldSpecs[i] // Ensure that each field appears, starting in the version it was introduced for v := uint64(1); v <= uint64(LogicVersion); v++ { if v < fs.version { @@ -5128,3 +5128,8 @@ func TestOpJSONRef(t *testing.T) { } } + +func TestTypeComplaints(t *testing.T) { + t.Skip("Issue #3837") + testProg(t, "int 1; return; store 0", AssemblerMaxVersion) +} diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 226b76bb75..1fa97132fd 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -17,6 +17,8 @@ package logic import ( + "fmt" + "github.com/algorand/go-algorand/data/transactions" "github.com/algorand/go-algorand/protocol" ) @@ -165,7 +167,7 @@ const ( // StateProofPK Transaction.StateProofPK StateProofPK - invalidTxnField // fence for some setup that loops from Sender..invalidTxnField + invalidTxnField // compile-time constant for number of fields ) // FieldSpec unifies the various specs for presentation @@ -177,22 +179,28 @@ type FieldSpec interface { Version() uint64 } -// FieldSpeccer is something that yields a FieldSpec, given a name for the field -type FieldSpeccer interface { +// FieldSpecMap is something that yields a FieldSpec, given a name for the field +type FieldSpecMap interface { SpecByName(name string) FieldSpec } // FieldGroup binds all the info for a field (names, int value, spec access) so // they can be attached to opcodes and used by doc generation type FieldGroup struct { - names []string - spec FieldSpeccer + Name string + Names []string + Specs FieldSpecMap } // TxnFieldNames are arguments to the 'txn' family of opcodes. var TxnFieldNames [invalidTxnField]string -var txnFieldSpecByField map[TxnField]txnFieldSpec = make(map[TxnField]txnFieldSpec, len(TxnFieldNames)) +func txnFieldSpecByField(f TxnField) (txnFieldSpec, bool) { + if int(f) >= len(txnFieldSpecs) { + return txnFieldSpec{}, false + } + return txnFieldSpecs[f], true +} // TxnFieldSpecByName gives access to the field specs by field name var TxnFieldSpecByName tfNameSpecMap = make(map[string]txnFieldSpec, len(TxnFieldNames)) @@ -211,6 +219,7 @@ type txnFieldSpec struct { version uint64 // When this field become available to txn/gtxn. 0=always itxVersion uint64 // When this field become available to itxn_field. 0=never effects bool // Is this a field on the "effects"? That is, something in ApplyData + doc string } func (fs txnFieldSpec) Field() byte { @@ -230,82 +239,92 @@ func (fs txnFieldSpec) Version() uint64 { } func (fs txnFieldSpec) Note() string { - note := txnFieldDocs[fs.field.String()] + note := fs.doc if fs.effects { note = addExtra(note, "Application mode only") } return note } -var txnFieldSpecs = []txnFieldSpec{ - {Sender, StackBytes, false, 0, 5, false}, - {Fee, StackUint64, false, 0, 5, false}, - {FirstValid, StackUint64, false, 0, 0, false}, - {FirstValidTime, StackUint64, false, 0, 0, false}, - {LastValid, StackUint64, false, 0, 0, false}, - {Note, StackBytes, false, 0, 6, false}, - {Lease, StackBytes, false, 0, 0, false}, - {Receiver, StackBytes, false, 0, 5, false}, - {Amount, StackUint64, false, 0, 5, false}, - {CloseRemainderTo, StackBytes, false, 0, 5, false}, - {VotePK, StackBytes, false, 0, 6, false}, - {SelectionPK, StackBytes, false, 0, 6, false}, - {VoteFirst, StackUint64, false, 0, 6, false}, - {VoteLast, StackUint64, false, 0, 6, false}, - {VoteKeyDilution, StackUint64, false, 0, 6, false}, - {Type, StackBytes, false, 0, 5, false}, - {TypeEnum, StackUint64, false, 0, 5, false}, - {XferAsset, StackUint64, false, 0, 5, false}, - {AssetAmount, StackUint64, false, 0, 5, false}, - {AssetSender, StackBytes, false, 0, 5, false}, - {AssetReceiver, StackBytes, false, 0, 5, false}, - {AssetCloseTo, StackBytes, false, 0, 5, false}, - {GroupIndex, StackUint64, false, 0, 0, false}, - {TxID, StackBytes, false, 0, 0, false}, - {ApplicationID, StackUint64, false, 2, 6, false}, - {OnCompletion, StackUint64, false, 2, 6, false}, - {ApplicationArgs, StackBytes, true, 2, 6, false}, - {NumAppArgs, StackUint64, false, 2, 0, false}, - {Accounts, StackBytes, true, 2, 6, false}, - {NumAccounts, StackUint64, false, 2, 0, false}, - {ApprovalProgram, StackBytes, false, 2, 6, false}, - {ClearStateProgram, StackBytes, false, 2, 6, false}, - {RekeyTo, StackBytes, false, 2, 6, false}, - {ConfigAsset, StackUint64, false, 2, 5, false}, - {ConfigAssetTotal, StackUint64, false, 2, 5, false}, - {ConfigAssetDecimals, StackUint64, false, 2, 5, false}, - {ConfigAssetDefaultFrozen, StackUint64, false, 2, 5, false}, - {ConfigAssetUnitName, StackBytes, false, 2, 5, false}, - {ConfigAssetName, StackBytes, false, 2, 5, false}, - {ConfigAssetURL, StackBytes, false, 2, 5, false}, - {ConfigAssetMetadataHash, StackBytes, false, 2, 5, false}, - {ConfigAssetManager, StackBytes, false, 2, 5, false}, - {ConfigAssetReserve, StackBytes, false, 2, 5, false}, - {ConfigAssetFreeze, StackBytes, false, 2, 5, false}, - {ConfigAssetClawback, StackBytes, false, 2, 5, false}, - {FreezeAsset, StackUint64, false, 2, 5, false}, - {FreezeAssetAccount, StackBytes, false, 2, 5, false}, - {FreezeAssetFrozen, StackUint64, false, 2, 5, false}, - {Assets, StackUint64, true, 3, 6, false}, - {NumAssets, StackUint64, false, 3, 0, false}, - {Applications, StackUint64, true, 3, 6, false}, - {NumApplications, StackUint64, false, 3, 0, false}, - {GlobalNumUint, StackUint64, false, 3, 6, false}, - {GlobalNumByteSlice, StackUint64, false, 3, 6, false}, - {LocalNumUint, StackUint64, false, 3, 6, false}, - {LocalNumByteSlice, StackUint64, false, 3, 6, false}, - {ExtraProgramPages, StackUint64, false, 4, 6, false}, - {Nonparticipation, StackUint64, false, 5, 6, false}, +var txnFieldSpecs = [...]txnFieldSpec{ + {Sender, StackBytes, false, 0, 5, false, "32 byte address"}, + {Fee, StackUint64, false, 0, 5, false, "microalgos"}, + {FirstValid, StackUint64, false, 0, 0, false, "round number"}, + {FirstValidTime, StackUint64, false, 0, 0, false, "Causes program to fail; reserved for future use"}, + {LastValid, StackUint64, false, 0, 0, false, "round number"}, + {Note, StackBytes, false, 0, 6, false, "Any data up to 1024 bytes"}, + {Lease, StackBytes, false, 0, 0, false, "32 byte lease value"}, + {Receiver, StackBytes, false, 0, 5, false, "32 byte address"}, + {Amount, StackUint64, false, 0, 5, false, "microalgos"}, + {CloseRemainderTo, StackBytes, false, 0, 5, false, "32 byte address"}, + {VotePK, StackBytes, false, 0, 6, false, "32 byte address"}, + {SelectionPK, StackBytes, false, 0, 6, false, "32 byte address"}, + {VoteFirst, StackUint64, false, 0, 6, false, "The first round that the participation key is valid."}, + {VoteLast, StackUint64, false, 0, 6, false, "The last round that the participation key is valid."}, + {VoteKeyDilution, StackUint64, false, 0, 6, false, "Dilution for the 2-level participation key"}, + {Type, StackBytes, false, 0, 5, false, "Transaction type as bytes"}, + {TypeEnum, StackUint64, false, 0, 5, false, "See table below"}, + {XferAsset, StackUint64, false, 0, 5, false, "Asset ID"}, + {AssetAmount, StackUint64, false, 0, 5, false, "value in Asset's units"}, + {AssetSender, StackBytes, false, 0, 5, false, + "32 byte address. Moves asset from AssetSender if Sender is the Clawback address of the asset."}, + {AssetReceiver, StackBytes, false, 0, 5, false, "32 byte address"}, + {AssetCloseTo, StackBytes, false, 0, 5, false, "32 byte address"}, + {GroupIndex, StackUint64, false, 0, 0, false, + "Position of this transaction within an atomic transaction group. A stand-alone transaction is implicitly element 0 in a group of 1"}, + {TxID, StackBytes, false, 0, 0, false, "The computed ID for this transaction. 32 bytes."}, + {ApplicationID, StackUint64, false, 2, 6, false, "ApplicationID from ApplicationCall transaction"}, + {OnCompletion, StackUint64, false, 2, 6, false, "ApplicationCall transaction on completion action"}, + {ApplicationArgs, StackBytes, true, 2, 6, false, + "Arguments passed to the application in the ApplicationCall transaction"}, + {NumAppArgs, StackUint64, false, 2, 0, false, "Number of ApplicationArgs"}, + {Accounts, StackBytes, true, 2, 6, false, "Accounts listed in the ApplicationCall transaction"}, + {NumAccounts, StackUint64, false, 2, 0, false, "Number of Accounts"}, + {ApprovalProgram, StackBytes, false, 2, 6, false, "Approval program"}, + {ClearStateProgram, StackBytes, false, 2, 6, false, "Clear state program"}, + {RekeyTo, StackBytes, false, 2, 6, false, "32 byte Sender's new AuthAddr"}, + {ConfigAsset, StackUint64, false, 2, 5, false, "Asset ID in asset config transaction"}, + {ConfigAssetTotal, StackUint64, false, 2, 5, false, "Total number of units of this asset created"}, + {ConfigAssetDecimals, StackUint64, false, 2, 5, false, + "Number of digits to display after the decimal place when displaying the asset"}, + {ConfigAssetDefaultFrozen, StackUint64, false, 2, 5, false, + "Whether the asset's slots are frozen by default or not, 0 or 1"}, + {ConfigAssetUnitName, StackBytes, false, 2, 5, false, "Unit name of the asset"}, + {ConfigAssetName, StackBytes, false, 2, 5, false, "The asset name"}, + {ConfigAssetURL, StackBytes, false, 2, 5, false, "URL"}, + {ConfigAssetMetadataHash, StackBytes, false, 2, 5, false, + "32 byte commitment to unspecified asset metadata"}, + {ConfigAssetManager, StackBytes, false, 2, 5, false, "32 byte address"}, + {ConfigAssetReserve, StackBytes, false, 2, 5, false, "32 byte address"}, + {ConfigAssetFreeze, StackBytes, false, 2, 5, false, "32 byte address"}, + {ConfigAssetClawback, StackBytes, false, 2, 5, false, "32 byte address"}, + {FreezeAsset, StackUint64, false, 2, 5, false, "Asset ID being frozen or un-frozen"}, + {FreezeAssetAccount, StackBytes, false, 2, 5, false, + "32 byte address of the account whose asset slot is being frozen or un-frozen"}, + {FreezeAssetFrozen, StackUint64, false, 2, 5, false, "The new frozen value, 0 or 1"}, + {Assets, StackUint64, true, 3, 6, false, "Foreign Assets listed in the ApplicationCall transaction"}, + {NumAssets, StackUint64, false, 3, 0, false, "Number of Assets"}, + {Applications, StackUint64, true, 3, 6, false, "Foreign Apps listed in the ApplicationCall transaction"}, + {NumApplications, StackUint64, false, 3, 0, false, "Number of Applications"}, + {GlobalNumUint, StackUint64, false, 3, 6, false, "Number of global state integers in ApplicationCall"}, + {GlobalNumByteSlice, StackUint64, false, 3, 6, false, "Number of global state byteslices in ApplicationCall"}, + {LocalNumUint, StackUint64, false, 3, 6, false, "Number of local state integers in ApplicationCall"}, + {LocalNumByteSlice, StackUint64, false, 3, 6, false, "Number of local state byteslices in ApplicationCall"}, + {ExtraProgramPages, StackUint64, false, 4, 6, false, + "Number of additional pages for each of the application's approval and clear state programs. An ExtraProgramPages of 1 means 2048 more total bytes, or 1024 for each program."}, + {Nonparticipation, StackUint64, false, 5, 6, false, "Marks an account nonparticipating for rewards"}, // "Effects" Last two things are always going to: 0, true - {Logs, StackBytes, true, 5, 0, true}, - {NumLogs, StackUint64, false, 5, 0, true}, - {CreatedAssetID, StackUint64, false, 5, 0, true}, - {CreatedApplicationID, StackUint64, false, 5, 0, true}, - {LastLog, StackBytes, false, 6, 0, true}, + {Logs, StackBytes, true, 5, 0, true, "Log messages emitted by an application call (only with `itxn` in v5)"}, + {NumLogs, StackUint64, false, 5, 0, true, "Number of Logs (only with `itxn` in v5)"}, + {CreatedAssetID, StackUint64, false, 5, 0, true, + "Asset ID allocated by the creation of an ASA (only with `itxn` in v5)"}, + {CreatedApplicationID, StackUint64, false, 5, 0, true, + "ApplicationID allocated by the creation of an application (only with `itxn` in v5)"}, + {LastLog, StackBytes, false, 6, 0, true, "The last message emitted. Empty bytes if none were emitted"}, // Not an effect. Just added after the effects fields. - {StateProofPK, StackBytes, false, 6, 6, false}, + {StateProofPK, StackBytes, false, 6, 6, false, "64 byte state proof public key commitment"}, } // TxnaFieldNames are arguments to the 'txna' opcode @@ -320,6 +339,20 @@ func TxnaFieldNames() []string { return names } +var txnFields = FieldGroup{ + "txn", + TxnFieldNames[:], + TxnFieldSpecByName, +} + +/* +var txnaFields = FieldGroup{ + "txna", + TxnaFieldNames(), + TxnFieldSpecByName, +} +*/ + var innerTxnTypes = map[string]uint64{ string(protocol.PaymentTx): 5, string(protocol.KeyRegistrationTx): 6, @@ -330,7 +363,7 @@ var innerTxnTypes = map[string]uint64{ } // TxnTypeNames is the values of Txn.Type in enum order -var TxnTypeNames = []string{ +var TxnTypeNames = [...]string{ string(protocol.UnknownTx), string(protocol.PaymentTx), string(protocol.KeyRegistrationTx), @@ -340,37 +373,34 @@ var TxnTypeNames = []string{ string(protocol.ApplicationCallTx), } -// map TxnTypeName to its enum index, for `txn TypeEnum` -var txnTypeIndexes map[string]uint64 - -// map symbolic name to uint64 for assembleInt -var txnTypeConstToUint64 map[string]uint64 +// map txn type names (long and short) to index/enum value +var txnTypeMap map[string]uint64 = make(map[string]uint64) // OnCompletionConstType is the same as transactions.OnCompletion type OnCompletionConstType transactions.OnCompletion const ( // NoOp = transactions.NoOpOC - NoOp OnCompletionConstType = OnCompletionConstType(transactions.NoOpOC) + NoOp = OnCompletionConstType(transactions.NoOpOC) // OptIn = transactions.OptInOC - OptIn OnCompletionConstType = OnCompletionConstType(transactions.OptInOC) + OptIn = OnCompletionConstType(transactions.OptInOC) // CloseOut = transactions.CloseOutOC - CloseOut OnCompletionConstType = OnCompletionConstType(transactions.CloseOutOC) + CloseOut = OnCompletionConstType(transactions.CloseOutOC) // ClearState = transactions.ClearStateOC - ClearState OnCompletionConstType = OnCompletionConstType(transactions.ClearStateOC) + ClearState = OnCompletionConstType(transactions.ClearStateOC) // UpdateApplication = transactions.UpdateApplicationOC - UpdateApplication OnCompletionConstType = OnCompletionConstType(transactions.UpdateApplicationOC) + UpdateApplication = OnCompletionConstType(transactions.UpdateApplicationOC) // DeleteApplication = transactions.DeleteApplicationOC - DeleteApplication OnCompletionConstType = OnCompletionConstType(transactions.DeleteApplicationOC) + DeleteApplication = OnCompletionConstType(transactions.DeleteApplicationOC) // end of constants - invalidOnCompletionConst OnCompletionConstType = DeleteApplication + 1 + invalidOnCompletionConst = DeleteApplication + 1 ) // OnCompletionNames is the string names of Txn.OnCompletion, array index is the const value -var OnCompletionNames []string +var OnCompletionNames [invalidOnCompletionConst]string -// onCompletionConstToUint64 map symbolic name to uint64 for assembleInt -var onCompletionConstToUint64 map[string]uint64 +// onCompletionMap maps symbolic name to uint64 for assembleInt +var onCompletionMap map[string]uint64 // GlobalField is an enum for `global` opcode type GlobalField uint64 @@ -421,7 +451,7 @@ const ( // CallerApplicationAddress The Address of the caller app, else ZeroAddress CallerApplicationAddress - invalidGlobalField + invalidGlobalField // compile-time constant for number of fields ) // GlobalFieldNames are arguments to the 'global' opcode @@ -432,6 +462,7 @@ type globalFieldSpec struct { ftype StackType mode runMode version uint64 + doc string } func (fs globalFieldSpec) Field() byte { @@ -450,7 +481,7 @@ func (fs globalFieldSpec) Version() uint64 { return fs.version } func (fs globalFieldSpec) Note() string { - note := globalFieldDocs[fs.field.String()] + note := fs.doc if fs.mode == runModeApplication { note = addExtra(note, "Application mode only.") } @@ -458,26 +489,40 @@ func (fs globalFieldSpec) Note() string { return note } -var globalFieldSpecs = []globalFieldSpec{ - {MinTxnFee, StackUint64, modeAny, 0}, // version 0 is the same as TEAL v1 (initial TEAL release) - {MinBalance, StackUint64, modeAny, 0}, - {MaxTxnLife, StackUint64, modeAny, 0}, - {ZeroAddress, StackBytes, modeAny, 0}, - {GroupSize, StackUint64, modeAny, 0}, - {LogicSigVersion, StackUint64, modeAny, 2}, - {Round, StackUint64, runModeApplication, 2}, - {LatestTimestamp, StackUint64, runModeApplication, 2}, - {CurrentApplicationID, StackUint64, runModeApplication, 2}, - {CreatorAddress, StackBytes, runModeApplication, 3}, - {CurrentApplicationAddress, StackBytes, runModeApplication, 5}, - {GroupID, StackBytes, modeAny, 5}, - {OpcodeBudget, StackUint64, modeAny, 6}, - {CallerApplicationID, StackUint64, runModeApplication, 6}, - {CallerApplicationAddress, StackBytes, runModeApplication, 6}, +var globalFieldSpecs = [...]globalFieldSpec{ + // version 0 is the same as TEAL v1 (initial TEAL release) + {MinTxnFee, StackUint64, modeAny, 0, "microalgos"}, + {MinBalance, StackUint64, modeAny, 0, "microalgos"}, + {MaxTxnLife, StackUint64, modeAny, 0, "rounds"}, + {ZeroAddress, StackBytes, modeAny, 0, "32 byte address of all zero bytes"}, + {GroupSize, StackUint64, modeAny, 0, + "Number of transactions in this atomic transaction group. At least 1"}, + {LogicSigVersion, StackUint64, modeAny, 2, "Maximum supported version"}, + {Round, StackUint64, runModeApplication, 2, "Current round number"}, + {LatestTimestamp, StackUint64, runModeApplication, 2, + "Last confirmed block UNIX timestamp. Fails if negative"}, + {CurrentApplicationID, StackUint64, runModeApplication, 2, "ID of current application executing"}, + {CreatorAddress, StackBytes, runModeApplication, 3, + "Address of the creator of the current application"}, + {CurrentApplicationAddress, StackBytes, runModeApplication, 5, + "Address that the current application controls"}, + {GroupID, StackBytes, modeAny, 5, + "ID of the transaction group. 32 zero bytes if the transaction is not part of a group."}, + {OpcodeBudget, StackUint64, modeAny, 6, + "The remaining cost that can be spent by opcodes in this program."}, + {CallerApplicationID, StackUint64, runModeApplication, 6, + "The application ID of the application that called this application. 0 if this application is at the top-level."}, + {CallerApplicationAddress, StackBytes, runModeApplication, 6, + "The application address of the application that called this application. ZeroAddress if this application is at the top-level."}, +} + +func globalFieldSpecByField(f GlobalField) (globalFieldSpec, bool) { + if int(f) >= len(globalFieldSpecs) { + return globalFieldSpec{}, false + } + return globalFieldSpecs[f], true } -var globalFieldSpecByField map[GlobalField]globalFieldSpec = make(map[GlobalField]globalFieldSpec, len(GlobalFieldNames)) - // GlobalFieldSpecByName gives access to the field specs by field name var GlobalFieldSpecByName gfNameSpecMap = make(gfNameSpecMap, len(GlobalFieldNames)) @@ -487,6 +532,12 @@ func (s gfNameSpecMap) SpecByName(name string) FieldSpec { return s[name] } +var globalFields = FieldGroup{ + "global", + GlobalFieldNames[:], + GlobalFieldSpecByName, +} + // EcdsaCurve is an enum for `ecdsa_` opcodes type EcdsaCurve int @@ -495,7 +546,7 @@ const ( Secp256k1 EcdsaCurve = iota // Secp256r1 curve Secp256r1 - invalidEcdsaCurve + invalidEcdsaCurve // compile-time constant for number of fields ) // EcdsaCurveNames are arguments to the 'ecdsa_' opcode @@ -504,6 +555,7 @@ var EcdsaCurveNames [invalidEcdsaCurve]string type ecdsaCurveSpec struct { field EcdsaCurve version uint64 + doc string } func (fs ecdsaCurveSpec) Field() byte { @@ -523,16 +575,20 @@ func (fs ecdsaCurveSpec) Version() uint64 { } func (fs ecdsaCurveSpec) Note() string { - note := EcdsaCurveDocs[fs.field.String()] - return note + return fs.doc } -var ecdsaCurveSpecs = []ecdsaCurveSpec{ - {Secp256k1, 5}, - {Secp256r1, fidoVersion}, +var ecdsaCurveSpecs = [...]ecdsaCurveSpec{ + {Secp256k1, 5, "secp256k1 curve, used in Bitcoin"}, + {Secp256r1, fidoVersion, "secp256r1 curve, NIST standard"}, } -var ecdsaCurveSpecByField map[EcdsaCurve]ecdsaCurveSpec = make(map[EcdsaCurve]ecdsaCurveSpec, len(EcdsaCurveNames)) +func ecdsaCurveSpecByField(c EcdsaCurve) (ecdsaCurveSpec, bool) { + if int(c) >= len(ecdsaCurveSpecs) { + return ecdsaCurveSpec{}, false + } + return ecdsaCurveSpecs[c], true +} // EcdsaCurveSpecByName gives access to the field specs by field name var EcdsaCurveSpecByName ecDsaCurveNameSpecMap = make(ecDsaCurveNameSpecMap, len(EcdsaCurveNames)) @@ -544,8 +600,9 @@ func (s ecDsaCurveNameSpecMap) SpecByName(name string) FieldSpec { } var ecdsaCurves = FieldGroup{ - names: EcdsaCurveNames[:], - spec: EcdsaCurveSpecByName, + "ecdsa", + EcdsaCurveNames[:], + EcdsaCurveSpecByName, } // Base64Encoding is an enum for the `base64decode` opcode @@ -556,11 +613,10 @@ const ( URLEncoding Base64Encoding = iota // StdEncoding represents the standard encoding of the RFC StdEncoding - invalidBase64Alphabet + invalidBase64Encoding // compile-time constant for number of fields ) -// After running `go generate` these strings will be available: -var base64EncodingNames [2]string = [...]string{URLEncoding.String(), StdEncoding.String()} +var base64EncodingNames [invalidBase64Encoding]string type base64EncodingSpec struct { field Base64Encoding @@ -568,33 +624,53 @@ type base64EncodingSpec struct { version uint64 } -var base64EncodingSpecs = []base64EncodingSpec{ +var base64EncodingSpecs = [...]base64EncodingSpec{ {URLEncoding, StackBytes, 6}, {StdEncoding, StackBytes, 6}, } -var base64EncodingSpecByField map[Base64Encoding]base64EncodingSpec = make(map[Base64Encoding]base64EncodingSpec, len(base64EncodingNames)) +func base64EncodingSpecByField(e Base64Encoding) (base64EncodingSpec, bool) { + if int(e) >= len(base64EncodingSpecs) { + return base64EncodingSpec{}, false + } + return base64EncodingSpecs[e], true +} + var base64EncodingSpecByName base64EncodingSpecMap = make(base64EncodingSpecMap, len(base64EncodingNames)) type base64EncodingSpecMap map[string]base64EncodingSpec -func (fs *base64EncodingSpec) Type() StackType { +func (fs base64EncodingSpec) Field() byte { + return byte(fs.field) +} + +func (fs base64EncodingSpec) Type() StackType { return fs.ftype } -func (fs *base64EncodingSpec) OpVersion() uint64 { +func (fs base64EncodingSpec) OpVersion() uint64 { return 6 } -func (fs *base64EncodingSpec) Version() uint64 { +func (fs base64EncodingSpec) Version() uint64 { return fs.version } -func (fs *base64EncodingSpec) Note() string { +func (fs base64EncodingSpec) Note() string { note := "" // no doc list? return note } +func (s base64EncodingSpecMap) SpecByName(name string) FieldSpec { + return s[name] +} + +var base64Encodings = FieldGroup{ + "base64", + base64EncodingNames[:], + base64EncodingSpecByName, +} + // JSONRefType is an enum for the `json_ref` opcode type JSONRefType int @@ -605,11 +681,11 @@ const ( JSONUint64 // JSONObject represents json object JSONObject - invalidJSONRefType + invalidJSONRefType // compile-time constant for number of fields ) // After running `go generate` these strings will be available: -var jsonRefTypeNames [3]string = [...]string{JSONString.String(), JSONUint64.String(), JSONObject.String()} +var jsonRefTypeNames [invalidJSONRefType]string type jsonRefSpec struct { field JSONRefType @@ -617,17 +693,54 @@ type jsonRefSpec struct { version uint64 } -var jsonRefSpecs = []jsonRefSpec{ +var jsonRefSpecs = [...]jsonRefSpec{ {JSONString, StackBytes, fidoVersion}, {JSONUint64, StackUint64, fidoVersion}, {JSONObject, StackBytes, fidoVersion}, } -var jsonRefSpecByField map[JSONRefType]jsonRefSpec = make(map[JSONRefType]jsonRefSpec, len(jsonRefTypeNames)) +func jsonRefSpecByField(r JSONRefType) (jsonRefSpec, bool) { + if int(r) >= len(jsonRefSpecs) { + return jsonRefSpec{}, false + } + return jsonRefSpecs[r], true +} + var jsonRefSpecByName jsonRefSpecMap = make(jsonRefSpecMap, len(jsonRefTypeNames)) type jsonRefSpecMap map[string]jsonRefSpec +func (fs jsonRefSpec) Field() byte { + return byte(fs.field) +} + +func (fs jsonRefSpec) Type() StackType { + return fs.ftype +} + +func (fs jsonRefSpec) OpVersion() uint64 { + return fidoVersion +} + +func (fs jsonRefSpec) Version() uint64 { + return fs.version +} + +func (fs jsonRefSpec) Note() string { + note := "" // no doc list? + return note +} + +func (s jsonRefSpecMap) SpecByName(name string) FieldSpec { + return s[name] +} + +var jsonRefTypes = FieldGroup{ + "json_ref", + jsonRefTypeNames[:], + jsonRefSpecByName, +} + // AssetHoldingField is an enum for `asset_holding_get` opcode type AssetHoldingField int @@ -636,7 +749,7 @@ const ( AssetBalance AssetHoldingField = iota // AssetFrozen AssetHolding.Frozen AssetFrozen - invalidAssetHoldingField + invalidAssetHoldingField // compile-time constant for number of fields ) // AssetHoldingFieldNames are arguments to the 'asset_holding_get' opcode @@ -646,6 +759,7 @@ type assetHoldingFieldSpec struct { field AssetHoldingField ftype StackType version uint64 + doc string } func (fs assetHoldingFieldSpec) Field() byte { @@ -665,16 +779,20 @@ func (fs assetHoldingFieldSpec) Version() uint64 { } func (fs assetHoldingFieldSpec) Note() string { - note := assetHoldingFieldDocs[fs.field.String()] - return note + return fs.doc } -var assetHoldingFieldSpecs = []assetHoldingFieldSpec{ - {AssetBalance, StackUint64, 2}, - {AssetFrozen, StackUint64, 2}, +var assetHoldingFieldSpecs = [...]assetHoldingFieldSpec{ + {AssetBalance, StackUint64, 2, "Amount of the asset unit held by this account"}, + {AssetFrozen, StackUint64, 2, "Is the asset frozen or not"}, } -var assetHoldingFieldSpecByField map[AssetHoldingField]assetHoldingFieldSpec = make(map[AssetHoldingField]assetHoldingFieldSpec, len(AssetHoldingFieldNames)) +func assetHoldingFieldSpecByField(f AssetHoldingField) (assetHoldingFieldSpec, bool) { + if int(f) >= len(assetHoldingFieldSpecs) { + return assetHoldingFieldSpec{}, false + } + return assetHoldingFieldSpecs[f], true +} // AssetHoldingFieldSpecByName gives access to the field specs by field name var AssetHoldingFieldSpecByName ahfNameSpecMap = make(ahfNameSpecMap, len(AssetHoldingFieldNames)) @@ -685,6 +803,12 @@ func (s ahfNameSpecMap) SpecByName(name string) FieldSpec { return s[name] } +var assetHoldingFields = FieldGroup{ + "asset_holding", + AssetHoldingFieldNames[:], + AssetHoldingFieldSpecByName, +} + // AssetParamsField is an enum for `asset_params_get` opcode type AssetParamsField int @@ -715,7 +839,7 @@ const ( // AssetCreator is not *in* the Params, but it is uniquely determined. AssetCreator - invalidAssetParamsField + invalidAssetParamsField // compile-time constant for number of fields ) // AssetParamsFieldNames are arguments to the 'asset_params_get' opcode @@ -725,6 +849,7 @@ type assetParamsFieldSpec struct { field AssetParamsField ftype StackType version uint64 + doc string } func (fs assetParamsFieldSpec) Field() byte { @@ -744,27 +869,31 @@ func (fs assetParamsFieldSpec) Version() uint64 { } func (fs assetParamsFieldSpec) Note() string { - note := assetParamsFieldDocs[fs.field.String()] - return note -} - -var assetParamsFieldSpecs = []assetParamsFieldSpec{ - {AssetTotal, StackUint64, 2}, - {AssetDecimals, StackUint64, 2}, - {AssetDefaultFrozen, StackUint64, 2}, - {AssetUnitName, StackBytes, 2}, - {AssetName, StackBytes, 2}, - {AssetURL, StackBytes, 2}, - {AssetMetadataHash, StackBytes, 2}, - {AssetManager, StackBytes, 2}, - {AssetReserve, StackBytes, 2}, - {AssetFreeze, StackBytes, 2}, - {AssetClawback, StackBytes, 2}, - {AssetCreator, StackBytes, 5}, + return fs.doc +} + +var assetParamsFieldSpecs = [...]assetParamsFieldSpec{ + {AssetTotal, StackUint64, 2, "Total number of units of this asset"}, + {AssetDecimals, StackUint64, 2, "See AssetParams.Decimals"}, + {AssetDefaultFrozen, StackUint64, 2, "Frozen by default or not"}, + {AssetUnitName, StackBytes, 2, "Asset unit name"}, + {AssetName, StackBytes, 2, "Asset name"}, + {AssetURL, StackBytes, 2, "URL with additional info about the asset"}, + {AssetMetadataHash, StackBytes, 2, "Arbitrary commitment"}, + {AssetManager, StackBytes, 2, "Manager address"}, + {AssetReserve, StackBytes, 2, "Reserve address"}, + {AssetFreeze, StackBytes, 2, "Freeze address"}, + {AssetClawback, StackBytes, 2, "Clawback address"}, + {AssetCreator, StackBytes, 5, "Creator address"}, +} + +func assetParamsFieldSpecByField(f AssetParamsField) (assetParamsFieldSpec, bool) { + if int(f) >= len(assetParamsFieldSpecs) { + return assetParamsFieldSpec{}, false + } + return assetParamsFieldSpecs[f], true } -var assetParamsFieldSpecByField map[AssetParamsField]assetParamsFieldSpec = make(map[AssetParamsField]assetParamsFieldSpec, len(AssetParamsFieldNames)) - // AssetParamsFieldSpecByName gives access to the field specs by field name var AssetParamsFieldSpecByName apfNameSpecMap = make(apfNameSpecMap, len(AssetParamsFieldNames)) @@ -774,6 +903,12 @@ func (s apfNameSpecMap) SpecByName(name string) FieldSpec { return s[name] } +var assetParamsFields = FieldGroup{ + "asset_params", + AssetParamsFieldNames[:], + AssetParamsFieldSpecByName, +} + // AppParamsField is an enum for `app_params_get` opcode type AppParamsField int @@ -799,7 +934,7 @@ const ( // AppAddress is also not *in* the Params, but can be derived AppAddress - invalidAppParamsField + invalidAppParamsField // compile-time constant for number of fields ) // AppParamsFieldNames are arguments to the 'app_params_get' opcode @@ -809,6 +944,7 @@ type appParamsFieldSpec struct { field AppParamsField ftype StackType version uint64 + doc string } func (fs appParamsFieldSpec) Field() byte { @@ -828,23 +964,27 @@ func (fs appParamsFieldSpec) Version() uint64 { } func (fs appParamsFieldSpec) Note() string { - note := appParamsFieldDocs[fs.field.String()] - return note + return fs.doc } -var appParamsFieldSpecs = []appParamsFieldSpec{ - {AppApprovalProgram, StackBytes, 5}, - {AppClearStateProgram, StackBytes, 5}, - {AppGlobalNumUint, StackUint64, 5}, - {AppGlobalNumByteSlice, StackUint64, 5}, - {AppLocalNumUint, StackUint64, 5}, - {AppLocalNumByteSlice, StackUint64, 5}, - {AppExtraProgramPages, StackUint64, 5}, - {AppCreator, StackBytes, 5}, - {AppAddress, StackBytes, 5}, +var appParamsFieldSpecs = [...]appParamsFieldSpec{ + {AppApprovalProgram, StackBytes, 5, "Bytecode of Approval Program"}, + {AppClearStateProgram, StackBytes, 5, "Bytecode of Clear State Program"}, + {AppGlobalNumUint, StackUint64, 5, "Number of uint64 values allowed in Global State"}, + {AppGlobalNumByteSlice, StackUint64, 5, "Number of byte array values allowed in Global State"}, + {AppLocalNumUint, StackUint64, 5, "Number of uint64 values allowed in Local State"}, + {AppLocalNumByteSlice, StackUint64, 5, "Number of byte array values allowed in Local State"}, + {AppExtraProgramPages, StackUint64, 5, "Number of Extra Program Pages of code space"}, + {AppCreator, StackBytes, 5, "Creator address"}, + {AppAddress, StackBytes, 5, "Address for which this application has authority"}, } -var appParamsFieldSpecByField map[AppParamsField]appParamsFieldSpec = make(map[AppParamsField]appParamsFieldSpec, len(AppParamsFieldNames)) +func appParamsFieldSpecByField(f AppParamsField) (appParamsFieldSpec, bool) { + if int(f) >= len(appParamsFieldSpecs) { + return appParamsFieldSpec{}, false + } + return appParamsFieldSpecs[f], true +} // AppParamsFieldSpecByName gives access to the field specs by field name var AppParamsFieldSpecByName appNameSpecMap = make(appNameSpecMap, len(AppParamsFieldNames)) @@ -856,6 +996,12 @@ func (s appNameSpecMap) SpecByName(name string) FieldSpec { return s[name] } +var appParamsFields = FieldGroup{ + "app_params", + AppParamsFieldNames[:], + AppParamsFieldSpecByName, +} + // AcctParamsField is an enum for `acct_params_get` opcode type AcctParamsField int @@ -867,7 +1013,7 @@ const ( //AcctAuthAddr is the rekeyed address if any, else ZeroAddress AcctAuthAddr - invalidAcctParamsField + invalidAcctParamsField // compile-time constant for number of fields ) // AcctParamsFieldNames are arguments to the 'acct_params_get' opcode @@ -877,6 +1023,7 @@ type acctParamsFieldSpec struct { field AcctParamsField ftype StackType version uint64 + doc string } func (fs acctParamsFieldSpec) Field() byte { @@ -896,17 +1043,21 @@ func (fs acctParamsFieldSpec) Version() uint64 { } func (fs acctParamsFieldSpec) Note() string { - note := acctParamsFieldDocs[fs.field.String()] - return note + return fs.doc } -var acctParamsFieldSpecs = []acctParamsFieldSpec{ - {AcctBalance, StackUint64, 6}, - {AcctMinBalance, StackUint64, 6}, - {AcctAuthAddr, StackBytes, 6}, +var acctParamsFieldSpecs = [...]acctParamsFieldSpec{ + {AcctBalance, StackUint64, 6, "Account balance in microalgos"}, + {AcctMinBalance, StackUint64, 6, "Minimum required blance for account, in microalgos"}, + {AcctAuthAddr, StackBytes, 6, "Address the account is rekeyed to."}, } -var acctParamsFieldSpecByField map[AcctParamsField]acctParamsFieldSpec = make(map[AcctParamsField]acctParamsFieldSpec, len(AcctParamsFieldNames)) +func acctParamsFieldSpecByField(f AcctParamsField) (acctParamsFieldSpec, bool) { + if int(f) >= len(acctParamsFieldSpecs) { + return acctParamsFieldSpec{}, false + } + return acctParamsFieldSpecs[f], true +} // AcctParamsFieldSpecByName gives access to the field specs by field name var AcctParamsFieldSpecByName acctNameSpecMap = make(acctNameSpecMap, len(AcctParamsFieldNames)) @@ -918,102 +1069,95 @@ func (s acctNameSpecMap) SpecByName(name string) FieldSpec { return s[name] } +var acctParamsFields = FieldGroup{ + "acct_params", + AcctParamsFieldNames[:], + AcctParamsFieldSpecByName, +} + func init() { - for i, s := range txnFieldSpecs { - if int(s.field) != i { - panic("txnFieldSpecs disjoint with TxnField enum") + equal := func(x int, y int) { + if x != y { + panic(fmt.Sprintf("%d != %d", x, y)) } + } + + equal(len(txnFieldSpecs), len(TxnFieldNames)) + for i, s := range txnFieldSpecs { + equal(int(s.field), i) TxnFieldNames[s.field] = s.field.String() - txnFieldSpecByField[s.field] = s TxnFieldSpecByName[s.field.String()] = s } + equal(len(globalFieldSpecs), len(GlobalFieldNames)) for i, s := range globalFieldSpecs { - if int(s.field) != i { - panic("globalFieldSpecs disjoint with GlobalField enum") - } + equal(int(s.field), i) GlobalFieldNames[s.field] = s.field.String() - globalFieldSpecByField[s.field] = s GlobalFieldSpecByName[s.field.String()] = s } + equal(len(ecdsaCurveSpecs), len(EcdsaCurveNames)) for i, s := range ecdsaCurveSpecs { - if int(s.field) != i { - panic("ecdsaCurveSpecs disjoint with EcdsaCurve enum") - } + equal(int(s.field), i) EcdsaCurveNames[s.field] = s.field.String() - ecdsaCurveSpecByField[s.field] = s EcdsaCurveSpecByName[s.field.String()] = s } + equal(len(base64EncodingSpecs), len(base64EncodingNames)) for i, s := range base64EncodingSpecs { - if int(s.field) != i { - panic("base64EncodingSpecs disjoint with Base64Encoding enum") - } - base64EncodingSpecByField[s.field] = s + equal(int(s.field), i) + base64EncodingNames[i] = s.field.String() base64EncodingSpecByName[s.field.String()] = s } + equal(len(jsonRefSpecs), len(jsonRefTypeNames)) for i, s := range jsonRefSpecs { - if int(s.field) != i { - panic("jsonRefSpecs disjoint with JSONRefType enum") - } - jsonRefSpecByField[s.field] = s + equal(int(s.field), i) + jsonRefTypeNames[i] = s.field.String() jsonRefSpecByName[s.field.String()] = s } + equal(len(assetHoldingFieldSpecs), len(AssetHoldingFieldNames)) for i, s := range assetHoldingFieldSpecs { - if int(s.field) != i { - panic("assetHoldingFieldSpecs disjoint with AssetHoldingField enum") - } + equal(int(s.field), i) AssetHoldingFieldNames[i] = s.field.String() - assetHoldingFieldSpecByField[s.field] = s AssetHoldingFieldSpecByName[s.field.String()] = s } + equal(len(assetParamsFieldSpecs), len(AssetParamsFieldNames)) for i, s := range assetParamsFieldSpecs { - if int(s.field) != i { - panic("assetParamsFieldSpecs disjoint with AssetParamsField enum") - } + equal(int(s.field), i) AssetParamsFieldNames[i] = s.field.String() - assetParamsFieldSpecByField[s.field] = s AssetParamsFieldSpecByName[s.field.String()] = s } + equal(len(appParamsFieldSpecs), len(AppParamsFieldNames)) for i, s := range appParamsFieldSpecs { - if int(s.field) != i { - panic("appParamsFieldSpecs disjoint with AppParamsField enum") - } + equal(int(s.field), i) AppParamsFieldNames[i] = s.field.String() - appParamsFieldSpecByField[s.field] = s AppParamsFieldSpecByName[s.field.String()] = s } + equal(len(acctParamsFieldSpecs), len(AcctParamsFieldNames)) for i, s := range acctParamsFieldSpecs { - if int(s.field) != i { - panic("acctParamsFieldSpecs disjoint with AcctParamsField enum") - } + equal(int(s.field), i) AcctParamsFieldNames[i] = s.field.String() - acctParamsFieldSpecByField[s.field] = s AcctParamsFieldSpecByName[s.field.String()] = s } - txnTypeIndexes = make(map[string]uint64, len(TxnTypeNames)) + txnTypeMap = make(map[string]uint64) for i, tt := range TxnTypeNames { - txnTypeIndexes[tt] = uint64(i) + txnTypeMap[tt] = uint64(i) } - - txnTypeConstToUint64 = make(map[string]uint64, len(TxnTypeNames)) - for tt, v := range txnTypeIndexes { - symbol := TypeNameDescriptions[tt] - txnTypeConstToUint64[symbol] = v + for k, v := range TypeNameDescriptions { + txnTypeMap[v] = txnTypeMap[k] } - OnCompletionNames = make([]string, int(invalidOnCompletionConst)) - onCompletionConstToUint64 = make(map[string]uint64, len(OnCompletionNames)) + onCompletionMap = make(map[string]uint64, len(OnCompletionNames)) for oc := NoOp; oc < invalidOnCompletionConst; oc++ { symbol := oc.String() OnCompletionNames[oc] = symbol - onCompletionConstToUint64[symbol] = uint64(oc) + onCompletionMap[symbol] = uint64(oc) } + } diff --git a/data/transactions/logic/fields_string.go b/data/transactions/logic/fields_string.go index 4e3cdbc883..cd22984637 100644 --- a/data/transactions/logic/fields_string.go +++ b/data/transactions/logic/fields_string.go @@ -221,12 +221,11 @@ func _() { _ = x[ClearState-3] _ = x[UpdateApplication-4] _ = x[DeleteApplication-5] - _ = x[invalidOnCompletionConst-6] } -const _OnCompletionConstType_name = "NoOpOptInCloseOutClearStateUpdateApplicationDeleteApplicationinvalidOnCompletionConst" +const _OnCompletionConstType_name = "NoOpOptInCloseOutClearStateUpdateApplicationDeleteApplication" -var _OnCompletionConstType_index = [...]uint8{0, 4, 9, 17, 27, 44, 61, 85} +var _OnCompletionConstType_index = [...]uint8{0, 4, 9, 17, 27, 44, 61} func (i OnCompletionConstType) String() string { if i >= OnCompletionConstType(len(_OnCompletionConstType_index)-1) { @@ -259,10 +258,10 @@ func _() { var x [1]struct{} _ = x[URLEncoding-0] _ = x[StdEncoding-1] - _ = x[invalidBase64Alphabet-2] + _ = x[invalidBase64Encoding-2] } -const _Base64Encoding_name = "URLEncodingStdEncodinginvalidBase64Alphabet" +const _Base64Encoding_name = "URLEncodingStdEncodinginvalidBase64Encoding" var _Base64Encoding_index = [...]uint8{0, 11, 22, 43} diff --git a/data/transactions/logic/fields_test.go b/data/transactions/logic/fields_test.go index 46f98f4a38..4a8128c87e 100644 --- a/data/transactions/logic/fields_test.go +++ b/data/transactions/logic/fields_test.go @@ -176,7 +176,7 @@ func TestTxnEffectsAvailable(t *testing.T) { partitiontest.PartitionTest(t) t.Parallel() - for _, fs := range txnFieldSpecByField { + for _, fs := range txnFieldSpecs { if !fs.effects { continue } diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index da6f7dc51a..aec035e895 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -81,6 +81,12 @@ func costly(cost int) opDetails { return opDetails{cost, nil, 1, nil, nil, nil} } +func (d opDetails) costs(cost int) opDetails { + clone := d + clone.Cost = cost + return clone +} + func immediates(names ...string) opDetails { immediates := make([]immediate, len(names)) for i, name := range names { @@ -99,16 +105,27 @@ func sizeVaries(checker opCheckFunc, name string, kind immKind) opDetails { return opDetails{1, nil, 0, checker, []immediate{{name, kind, nil}}, nil} } -func costlyImm(cost int, names ...string) opDetails { - opd := immediates(names...) - opd.Cost = cost +// field is used to create an opDetails for an opcode with a single field +func field(immediate string, group *FieldGroup) opDetails { + opd := immediates(immediate) + opd.Immediates[0].Group = group return opd } +// field is used to annotate an existing immediate with group info +func (d opDetails) field(name string, group *FieldGroup) opDetails { + for i := range d.Immediates { + if d.Immediates[i].Name == name { + d.Immediates[i].Group = group + return d + } + } + panic(name) +} + func costByField(immediate string, group *FieldGroup, costs map[byte]int) opDetails { - opd := immediates(immediate) - opd.Immediates[0].group = group - opd.Cost = 0 + opd := immediates(immediate).costs(0) + opd.Immediates[0].Group = group opd.costFunc = func(program []byte, pc int) int { cost, ok := costs[program[pc+1]] if ok { @@ -134,7 +151,7 @@ const ( type immediate struct { Name string kind immKind - group *FieldGroup + Group *FieldGroup } // OpSpec defines an opcode @@ -186,9 +203,9 @@ var OpSpecs = []OpSpec{ {0x04, "ed25519verify", opEd25519Verify, asmDefault, disDefault, threeBytes, oneInt, 1, runModeSignature, costly(1900)}, {0x04, "ed25519verify", opEd25519Verify, asmDefault, disDefault, threeBytes, oneInt, 5, modeAny, costly(1900)}, - {0x05, "ecdsa_verify", opEcdsaVerify, asmEcdsa, disEcdsa, threeBytes.plus(twoBytes), oneInt, 5, modeAny, costByField("v", &ecdsaCurves, ecdsaVerifyCosts)}, - {0x06, "ecdsa_pk_decompress", opEcdsaPkDecompress, asmEcdsa, disEcdsa, oneBytes, twoBytes, 5, modeAny, costByField("v", &ecdsaCurves, ecdsaDecompressCosts)}, - {0x07, "ecdsa_pk_recover", opEcdsaPkRecover, asmEcdsa, disEcdsa, oneBytes.plus(oneInt).plus(twoBytes), twoBytes, 5, modeAny, costlyImm(2000, "v")}, + {0x05, "ecdsa_verify", opEcdsaVerify, asmEcdsa, disDefault, threeBytes.plus(twoBytes), oneInt, 5, modeAny, costByField("v", &ecdsaCurves, ecdsaVerifyCosts)}, + {0x06, "ecdsa_pk_decompress", opEcdsaPkDecompress, asmEcdsa, disDefault, oneBytes, twoBytes, 5, modeAny, costByField("v", &ecdsaCurves, ecdsaDecompressCosts)}, + {0x07, "ecdsa_pk_recover", opEcdsaPkRecover, asmEcdsa, disDefault, oneBytes.plus(oneInt).plus(twoBytes), twoBytes, 5, modeAny, field("v", &ecdsaCurves).costs(2000)}, {0x08, "+", opPlus, asmDefault, disDefault, twoInts, oneInt, 1, modeAny, opDefault}, {0x09, "-", opMinus, asmDefault, disDefault, twoInts, oneInt, 1, modeAny, opDefault}, @@ -215,38 +232,45 @@ var OpSpecs = []OpSpec{ {0x1e, "addw", opAddw, asmDefault, disDefault, twoInts, twoInts, 2, modeAny, opDefault}, {0x1f, "divmodw", opDivModw, asmDefault, disDefault, twoInts.plus(twoInts), twoInts.plus(twoInts), 4, modeAny, costly(20)}, - {0x20, "intcblock", opIntConstBlock, asmIntCBlock, disIntcblock, nil, nil, 1, modeAny, sizeVaries(checkIntConstBlock, "uint ...", immInts)}, - {0x21, "intc", opIntConstLoad, asmIntC, disIntc, nil, oneInt, 1, modeAny, immediates("i")}, - {0x22, "intc_0", opIntConst0, asmDefault, disIntc, nil, oneInt, 1, modeAny, opDefault}, - {0x23, "intc_1", opIntConst1, asmDefault, disIntc, nil, oneInt, 1, modeAny, opDefault}, - {0x24, "intc_2", opIntConst2, asmDefault, disIntc, nil, oneInt, 1, modeAny, opDefault}, - {0x25, "intc_3", opIntConst3, asmDefault, disIntc, nil, oneInt, 1, modeAny, opDefault}, - {0x26, "bytecblock", opByteConstBlock, asmByteCBlock, disBytecblock, nil, nil, 1, modeAny, sizeVaries(checkByteConstBlock, "bytes ...", immBytess)}, - {0x27, "bytec", opByteConstLoad, asmByteC, disBytec, nil, oneBytes, 1, modeAny, immediates("i")}, - {0x28, "bytec_0", opByteConst0, asmDefault, disBytec, nil, oneBytes, 1, modeAny, opDefault}, - {0x29, "bytec_1", opByteConst1, asmDefault, disBytec, nil, oneBytes, 1, modeAny, opDefault}, - {0x2a, "bytec_2", opByteConst2, asmDefault, disBytec, nil, oneBytes, 1, modeAny, opDefault}, - {0x2b, "bytec_3", opByteConst3, asmDefault, disBytec, nil, oneBytes, 1, modeAny, opDefault}, + {0x20, "intcblock", opIntConstBlock, asmIntCBlock, disDefault, nil, nil, 1, modeAny, sizeVaries(checkIntConstBlock, "uint ...", immInts)}, + {0x21, "intc", opIntConstLoad, asmIntC, disDefault, nil, oneInt, 1, modeAny, immediates("i")}, + {0x22, "intc_0", opIntConst0, asmDefault, disDefault, nil, oneInt, 1, modeAny, opDefault}, + {0x23, "intc_1", opIntConst1, asmDefault, disDefault, nil, oneInt, 1, modeAny, opDefault}, + {0x24, "intc_2", opIntConst2, asmDefault, disDefault, nil, oneInt, 1, modeAny, opDefault}, + {0x25, "intc_3", opIntConst3, asmDefault, disDefault, nil, oneInt, 1, modeAny, opDefault}, + {0x26, "bytecblock", opByteConstBlock, asmByteCBlock, disDefault, nil, nil, 1, modeAny, sizeVaries(checkByteConstBlock, "bytes ...", immBytess)}, + {0x27, "bytec", opByteConstLoad, asmByteC, disDefault, nil, oneBytes, 1, modeAny, immediates("i")}, + {0x28, "bytec_0", opByteConst0, asmDefault, disDefault, nil, oneBytes, 1, modeAny, opDefault}, + {0x29, "bytec_1", opByteConst1, asmDefault, disDefault, nil, oneBytes, 1, modeAny, opDefault}, + {0x2a, "bytec_2", opByteConst2, asmDefault, disDefault, nil, oneBytes, 1, modeAny, opDefault}, + {0x2b, "bytec_3", opByteConst3, asmDefault, disDefault, nil, oneBytes, 1, modeAny, opDefault}, {0x2c, "arg", opArg, asmArg, disDefault, nil, oneBytes, 1, runModeSignature, immediates("n")}, {0x2d, "arg_0", opArg0, asmDefault, disDefault, nil, oneBytes, 1, runModeSignature, opDefault}, {0x2e, "arg_1", opArg1, asmDefault, disDefault, nil, oneBytes, 1, runModeSignature, opDefault}, {0x2f, "arg_2", opArg2, asmDefault, disDefault, nil, oneBytes, 1, runModeSignature, opDefault}, {0x30, "arg_3", opArg3, asmDefault, disDefault, nil, oneBytes, 1, runModeSignature, opDefault}, - {0x31, "txn", opTxn, asmTxn, disTxn, nil, oneAny, 1, modeAny, immediates("f")}, + {0x31, "txn", opTxn, asmTxn, disDefault, nil, oneAny, 1, modeAny, field("f", &txnFields)}, // It is ok to have the same opcode for different TEAL versions. // This 'txn' asm command supports additional argument in version 2 and // generates 'txna' opcode in that particular case - {0x31, "txn", opTxn, asmTxn2, disTxn, nil, oneAny, 2, modeAny, immediates("f")}, - {0x32, "global", opGlobal, asmGlobal, disGlobal, nil, oneAny, 1, modeAny, immediates("f")}, - {0x33, "gtxn", opGtxn, asmGtxn, disGtxn, nil, oneAny, 1, modeAny, immediates("t", "f")}, - {0x33, "gtxn", opGtxn, asmGtxn2, disGtxn, nil, oneAny, 2, modeAny, immediates("t", "f")}, + {0x31, "txn", opTxn, asmTxn2, disDefault, nil, oneAny, 2, modeAny, field("f", &txnFields)}, + {0x32, "global", opGlobal, asmGlobal, disDefault, nil, oneAny, 1, modeAny, + field("f", &globalFields)}, + {0x33, "gtxn", opGtxn, asmGtxn, disDefault, nil, oneAny, 1, modeAny, + immediates("t", "f").field("f", &txnFields)}, + {0x33, "gtxn", opGtxn, asmGtxn2, disDefault, nil, oneAny, 2, modeAny, + immediates("t", "f").field("f", &txnFields)}, {0x34, "load", opLoad, asmDefault, disDefault, nil, oneAny, 1, modeAny, immediates("i")}, {0x35, "store", opStore, asmDefault, disDefault, oneAny, nil, 1, modeAny, immediates("i")}, - {0x36, "txna", opTxna, asmTxna, disTxna, nil, oneAny, 2, modeAny, immediates("f", "i")}, - {0x37, "gtxna", opGtxna, asmGtxna, disGtxna, nil, oneAny, 2, modeAny, immediates("t", "f", "i")}, + {0x36, "txna", opTxna, asmTxna, disDefault, nil, oneAny, 2, modeAny, + immediates("f", "i").field("f", &txnFields)}, + {0x37, "gtxna", opGtxna, asmGtxna, disDefault, nil, oneAny, 2, modeAny, + immediates("t", "f", "i").field("f", &txnFields)}, // Like gtxn, but gets txn index from stack, rather than immediate arg - {0x38, "gtxns", opGtxns, asmGtxns, disTxn, oneInt, oneAny, 3, modeAny, immediates("f")}, - {0x39, "gtxnsa", opGtxnsa, asmGtxns, disTxna, oneInt, oneAny, 3, modeAny, immediates("f", "i")}, + {0x38, "gtxns", opGtxns, asmGtxns, disDefault, oneInt, oneAny, 3, modeAny, + immediates("f").field("f", &txnFields)}, + {0x39, "gtxnsa", opGtxnsa, asmGtxns, disDefault, oneInt, oneAny, 3, modeAny, + immediates("f", "i").field("f", &txnFields)}, // Group scratch space access {0x3a, "gload", opGload, asmDefault, disDefault, nil, oneAny, 4, runModeApplication, immediates("t", "i")}, {0x3b, "gloads", opGloads, asmDefault, disDefault, oneInt, oneAny, 4, runModeApplication, immediates("i")}, @@ -258,9 +282,9 @@ var OpSpecs = []OpSpec{ {0x3e, "loads", opLoads, asmDefault, disDefault, oneInt, oneAny, 5, modeAny, opDefault}, {0x3f, "stores", opStores, asmDefault, disDefault, oneInt.plus(oneAny), nil, 5, modeAny, opDefault}, - {0x40, "bnz", opBnz, asmBranch, disBranch, oneInt, nil, 1, modeAny, opBranch}, - {0x41, "bz", opBz, asmBranch, disBranch, oneInt, nil, 2, modeAny, opBranch}, - {0x42, "b", opB, asmBranch, disBranch, nil, nil, 2, modeAny, opBranch}, + {0x40, "bnz", opBnz, asmBranch, disDefault, oneInt, nil, 1, modeAny, opBranch}, + {0x41, "bz", opBz, asmBranch, disDefault, oneInt, nil, 2, modeAny, opBranch}, + {0x42, "b", opB, asmBranch, disDefault, nil, nil, 2, modeAny, opBranch}, {0x43, "return", opReturn, asmDefault, disDefault, oneInt, nil, 2, modeAny, opDefault}, {0x44, "assert", opAssert, asmDefault, disDefault, oneInt, nil, 3, modeAny, opDefault}, {0x48, "pop", opPop, asmDefault, disDefault, oneAny, nil, 1, modeAny, opDefault}, @@ -287,8 +311,8 @@ var OpSpecs = []OpSpec{ {0x59, "extract_uint16", opExtract16Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, {0x5a, "extract_uint32", opExtract32Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, {0x5b, "extract_uint64", opExtract64Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, - {0x5c, "base64_decode", opBase64Decode, asmBase64Decode, disBase64Decode, oneBytes, oneBytes, fidoVersion, modeAny, costlyImm(25, "e")}, - {0x5d, "json_ref", opJSONRef, asmJSONRef, disJSONRef, twoBytes, oneAny, fidoVersion, modeAny, immediates("r")}, + {0x5c, "base64_decode", opBase64Decode, asmBase64Decode, disDefault, oneBytes, oneBytes, fidoVersion, modeAny, field("e", &base64Encodings).costs(25)}, + {0x5d, "json_ref", opJSONRef, asmJSONRef, disDefault, twoBytes, oneAny, fidoVersion, modeAny, field("r", &jsonRefTypes)}, {0x60, "balance", opBalance, asmDefault, disDefault, oneInt, oneInt, 2, runModeApplication, opDefault}, {0x60, "balance", opBalance, asmDefault, disDefault, oneAny, oneInt, directRefEnabledVersion, runModeApplication, opDefault}, @@ -307,23 +331,23 @@ var OpSpecs = []OpSpec{ {0x68, "app_local_del", opAppLocalDel, asmDefault, disDefault, oneAny.plus(oneBytes), nil, directRefEnabledVersion, runModeApplication, opDefault}, {0x69, "app_global_del", opAppGlobalDel, asmDefault, disDefault, oneBytes, nil, 2, runModeApplication, opDefault}, - {0x70, "asset_holding_get", opAssetHoldingGet, asmAssetHolding, disAssetHolding, twoInts, oneAny.plus(oneInt), 2, runModeApplication, immediates("f")}, - {0x70, "asset_holding_get", opAssetHoldingGet, asmAssetHolding, disAssetHolding, oneAny.plus(oneInt), oneAny.plus(oneInt), directRefEnabledVersion, runModeApplication, immediates("f")}, - {0x71, "asset_params_get", opAssetParamsGet, asmAssetParams, disAssetParams, oneInt, oneAny.plus(oneInt), 2, runModeApplication, immediates("f")}, - {0x72, "app_params_get", opAppParamsGet, asmAppParams, disAppParams, oneInt, oneAny.plus(oneInt), 5, runModeApplication, immediates("f")}, - {0x73, "acct_params_get", opAcctParamsGet, asmAcctParams, disAcctParams, oneAny, oneAny.plus(oneInt), 6, runModeApplication, immediates("f")}, + {0x70, "asset_holding_get", opAssetHoldingGet, asmAssetHolding, disDefault, twoInts, oneAny.plus(oneInt), 2, runModeApplication, field("f", &assetHoldingFields)}, + {0x70, "asset_holding_get", opAssetHoldingGet, asmAssetHolding, disDefault, oneAny.plus(oneInt), oneAny.plus(oneInt), directRefEnabledVersion, runModeApplication, field("f", &assetHoldingFields)}, + {0x71, "asset_params_get", opAssetParamsGet, asmAssetParams, disDefault, oneInt, oneAny.plus(oneInt), 2, runModeApplication, field("f", &assetParamsFields)}, + {0x72, "app_params_get", opAppParamsGet, asmAppParams, disDefault, oneInt, oneAny.plus(oneInt), 5, runModeApplication, field("f", &appParamsFields)}, + {0x73, "acct_params_get", opAcctParamsGet, asmAcctParams, disDefault, oneAny, oneAny.plus(oneInt), 6, runModeApplication, field("f", &acctParamsFields)}, {0x78, "min_balance", opMinBalance, asmDefault, disDefault, oneInt, oneInt, 3, runModeApplication, opDefault}, {0x78, "min_balance", opMinBalance, asmDefault, disDefault, oneAny, oneInt, directRefEnabledVersion, runModeApplication, opDefault}, // Immediate bytes and ints. Smaller code size for single use of constant. - {0x80, "pushbytes", opPushBytes, asmPushBytes, disPushBytes, nil, oneBytes, 3, modeAny, sizeVaries(opPushBytes, "bytes", immBytes)}, - {0x81, "pushint", opPushInt, asmPushInt, disPushInt, nil, oneInt, 3, modeAny, sizeVaries(opPushInt, "uint", immInt)}, + {0x80, "pushbytes", opPushBytes, asmPushBytes, disDefault, nil, oneBytes, 3, modeAny, sizeVaries(opPushBytes, "bytes", immBytes)}, + {0x81, "pushint", opPushInt, asmPushInt, disDefault, nil, oneInt, 3, modeAny, sizeVaries(opPushInt, "uint", immInt)}, {0x84, "ed25519verify_bare", opEd25519VerifyBare, asmDefault, disDefault, threeBytes, oneInt, 7, modeAny, costly(1900)}, // "Function oriented" - {0x88, "callsub", opCallSub, asmBranch, disBranch, nil, nil, 4, modeAny, opBranch}, + {0x88, "callsub", opCallSub, asmBranch, disDefault, nil, nil, 4, modeAny, opBranch}, {0x89, "retsub", opRetSub, asmDefault, disDefault, nil, nil, 4, modeAny, opDefault}, // Leave a little room for indirect function calls, or similar @@ -359,22 +383,32 @@ var OpSpecs = []OpSpec{ // AVM "effects" {0xb0, "log", opLog, asmDefault, disDefault, oneBytes, nil, 5, runModeApplication, opDefault}, {0xb1, "itxn_begin", opTxBegin, asmDefault, disDefault, nil, nil, 5, runModeApplication, opDefault}, - {0xb2, "itxn_field", opItxnField, asmItxnField, disItxnField, oneAny, nil, 5, runModeApplication, stacky(typeTxField, "f")}, + {0xb2, "itxn_field", opItxnField, asmItxnField, disDefault, oneAny, nil, 5, runModeApplication, + stacky(typeTxField, "f").field("f", &txnFields)}, {0xb3, "itxn_submit", opItxnSubmit, asmDefault, disDefault, nil, nil, 5, runModeApplication, opDefault}, - {0xb4, "itxn", opItxn, asmItxn, disTxn, nil, oneAny, 5, runModeApplication, immediates("f")}, - {0xb5, "itxna", opItxna, asmTxna, disTxna, nil, oneAny, 5, runModeApplication, immediates("f", "i")}, + {0xb4, "itxn", opItxn, asmItxn, disDefault, nil, oneAny, 5, runModeApplication, + field("f", &txnFields)}, + {0xb5, "itxna", opItxna, asmTxna, disDefault, nil, oneAny, 5, runModeApplication, + immediates("f", "i").field("f", &txnFields)}, {0xb6, "itxn_next", opItxnNext, asmDefault, disDefault, nil, nil, 6, runModeApplication, opDefault}, - {0xb7, "gitxn", opGitxn, asmGitxn, disGtxn, nil, oneAny, 6, runModeApplication, immediates("t", "f")}, - {0xb8, "gitxna", opGitxna, asmGtxna, disGtxna, nil, oneAny, 6, runModeApplication, immediates("t", "f", "i")}, + {0xb7, "gitxn", opGitxn, asmGitxn, disDefault, nil, oneAny, 6, runModeApplication, + immediates("t", "f").field("f", &txnFields)}, + {0xb8, "gitxna", opGitxna, asmGtxna, disDefault, nil, oneAny, 6, runModeApplication, + immediates("t", "f", "i").field("f", &txnFields)}, // Dynamic indexing - {0xc0, "txnas", opTxnas, asmTxnas, disTxn, oneInt, oneAny, 5, modeAny, immediates("f")}, - {0xc1, "gtxnas", opGtxnas, asmGtxnas, disGtxn, oneInt, oneAny, 5, modeAny, immediates("t", "f")}, - {0xc2, "gtxnsas", opGtxnsas, asmGtxnsas, disTxn, twoInts, oneAny, 5, modeAny, immediates("f")}, + {0xc0, "txnas", opTxnas, asmTxnas, disDefault, oneInt, oneAny, 5, modeAny, + field("f", &txnFields)}, + {0xc1, "gtxnas", opGtxnas, asmGtxnas, disDefault, oneInt, oneAny, 5, modeAny, + immediates("t", "f").field("f", &txnFields)}, + {0xc2, "gtxnsas", opGtxnsas, asmGtxnsas, disDefault, twoInts, oneAny, 5, modeAny, + field("f", &txnFields)}, {0xc3, "args", opArgs, asmDefault, disDefault, oneInt, oneBytes, 5, runModeSignature, opDefault}, {0xc4, "gloadss", opGloadss, asmDefault, disDefault, twoInts, oneAny, 6, runModeApplication, opDefault}, - {0xc5, "itxnas", opItxnas, asmTxnas, disTxn, oneInt, oneAny, 6, runModeApplication, immediates("f")}, - {0xc6, "gitxnas", opGitxnas, asmGtxnas, disGtxn, oneInt, oneAny, 6, runModeApplication, immediates("t", "f")}, + {0xc5, "itxnas", opItxnas, asmTxnas, disDefault, oneInt, oneAny, 6, runModeApplication, + field("f", &txnFields)}, + {0xc6, "gitxnas", opGitxnas, asmGtxnas, disDefault, oneInt, oneAny, 6, runModeApplication, + immediates("t", "f").field("f", &txnFields)}, } type sortByOpcode []OpSpec From 23cd197f0c25aaf9fe29b9c1789aee9f09908d9c Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Thu, 24 Mar 2022 15:36:01 -0400 Subject: [PATCH 3/9] Tidy up visibility of some field related vars --- cmd/opdoc/opdoc.go | 40 +++--- data/transactions/logic/assembler.go | 27 ++-- data/transactions/logic/evalStateful_test.go | 8 +- data/transactions/logic/fields.go | 144 +++++++++---------- data/transactions/logic/opcodes.go | 58 ++++---- 5 files changed, 136 insertions(+), 141 deletions(-) diff --git a/cmd/opdoc/opdoc.go b/cmd/opdoc/opdoc.go index e13a9ac612..3ae93a0edd 100644 --- a/cmd/opdoc/opdoc.go +++ b/cmd/opdoc/opdoc.go @@ -127,37 +127,37 @@ func fieldSpecsMarkdown(out io.Writer, names []string, specs logic.FieldSpecMap) func transactionFieldsMarkdown(out io.Writer) { fmt.Fprintf(out, "\n`txn` Fields (see [transaction reference](https://developer.algorand.org/docs/reference/transactions/)):\n\n") - fieldSpecsMarkdown(out, logic.TxnFieldNames[:], logic.TxnFieldSpecByName) + fieldGroupMarkdown(out, logic.TxnFields) } func globalFieldsMarkdown(out io.Writer) { fmt.Fprintf(out, "\n`global` Fields:\n\n") - fieldSpecsMarkdown(out, logic.GlobalFieldNames[:], logic.GlobalFieldSpecByName) + fieldGroupMarkdown(out, logic.GlobalFields) } func assetHoldingFieldsMarkdown(out io.Writer) { fmt.Fprintf(out, "\n`asset_holding_get` Fields:\n\n") - fieldSpecsMarkdown(out, logic.AssetHoldingFieldNames[:], logic.AssetHoldingFieldSpecByName) + fieldGroupMarkdown(out, logic.AssetHoldingFields) } func assetParamsFieldsMarkdown(out io.Writer) { fmt.Fprintf(out, "\n`asset_params_get` Fields:\n\n") - fieldSpecsMarkdown(out, logic.AssetParamsFieldNames[:], logic.AssetParamsFieldSpecByName) + fieldGroupMarkdown(out, logic.AssetParamsFields) } func appParamsFieldsMarkdown(out io.Writer) { fmt.Fprintf(out, "\n`app_params_get` Fields:\n\n") - fieldSpecsMarkdown(out, logic.AppParamsFieldNames[:], logic.AppParamsFieldSpecByName) + fieldGroupMarkdown(out, logic.AppParamsFields) } func acctParamsFieldsMarkdown(out io.Writer) { fmt.Fprintf(out, "\n`acct_params_get` Fields:\n\n") - fieldSpecsMarkdown(out, logic.AcctParamsFieldNames[:], logic.AcctParamsFieldSpecByName) + fieldGroupMarkdown(out, logic.AcctParamsFields) } func ecDsaCurvesMarkdown(out io.Writer) { fmt.Fprintf(out, "\n`ECDSA` Curves:\n\n") - fieldSpecsMarkdown(out, logic.EcdsaCurveNames[:], logic.EcdsaCurveSpecByName) + fieldGroupMarkdown(out, logic.EcdsaCurves) } func immediateMarkdown(op *logic.OpSpec) string { @@ -319,29 +319,33 @@ func typeString(types []logic.StackType) string { return string(out) } -func fieldsAndTypes(names []string, specs logic.FieldSpecMap) ([]string, string) { - types := make([]logic.StackType, len(names)) - for i, name := range names { - types[i] = specs.SpecByName(name).Type() +func fieldsAndTypes(group logic.FieldGroup) ([]string, string) { + // reminder: group.Names can be "sparse" See: logic.TxnaFields + fields := make([]string, 0, len(group.Names)) + types := make([]logic.StackType, 0, len(group.Names)) + for _, name := range group.Names { + if name != "" { + fields = append(fields, name) + types = append(types, group.Specs.SpecByName(name).Type()) + } } - return names, typeString(types) + return fields, typeString(types) } func argEnums(name string) (names []string, types string) { switch name { case "txn", "gtxn", "gtxns", "itxn", "gitxn", "itxn_field": - return fieldsAndTypes(logic.TxnFieldNames[:], logic.TxnFieldSpecByName) + return fieldsAndTypes(logic.TxnFields) case "global": return case "txna", "gtxna", "gtxnsa", "txnas", "gtxnas", "gtxnsas", "itxna", "gitxna": - // Map is the whole txn field spec map. That's fine, we only lookup the given names. - return fieldsAndTypes(logic.TxnaFieldNames(), logic.TxnFieldSpecByName) + return fieldsAndTypes(logic.TxnaFields) case "asset_holding_get": - return fieldsAndTypes(logic.AssetHoldingFieldNames[:], logic.AssetHoldingFieldSpecByName) + return fieldsAndTypes(logic.AssetHoldingFields) case "asset_params_get": - return fieldsAndTypes(logic.AssetParamsFieldNames[:], logic.AssetParamsFieldSpecByName) + return fieldsAndTypes(logic.AssetParamsFields) case "app_params_get": - return fieldsAndTypes(logic.AppParamsFieldNames[:], logic.AppParamsFieldSpecByName) + return fieldsAndTypes(logic.AppParamsFields) default: return nil, "" } diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 3d1d21d0d7..2b9b644396 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -781,7 +781,7 @@ func asmSubstring(ops *OpStream, spec *OpSpec, args []string) error { } func txnFieldImm(name string, expectArray bool, ops *OpStream) (*txnFieldSpec, error) { - fs, ok := TxnFieldSpecByName[name] + fs, ok := txnFieldSpecByName[name] if !ok { return nil, fmt.Errorf("unknown field: %#v", name) } @@ -1046,7 +1046,7 @@ func asmGlobal(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { return ops.errorf("%s expects one argument", spec.Name) } - fs, ok := GlobalFieldSpecByName[args[0]] + fs, ok := globalFieldSpecByName[args[0]] if !ok { return ops.errorf("%s unknown field: %#v", spec.Name, args[0]) } @@ -1067,7 +1067,7 @@ func asmAssetHolding(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { return ops.errorf("%s expects one argument", spec.Name) } - fs, ok := AssetHoldingFieldSpecByName[args[0]] + fs, ok := assetHoldingFieldSpecByName[args[0]] if !ok { return ops.errorf("%s unknown field: %#v", spec.Name, args[0]) } @@ -1088,7 +1088,7 @@ func asmAssetParams(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { return ops.errorf("%s expects one argument", spec.Name) } - fs, ok := AssetParamsFieldSpecByName[args[0]] + fs, ok := assetParamsFieldSpecByName[args[0]] if !ok { return ops.errorf("%s unknown field: %#v", spec.Name, args[0]) } @@ -1109,7 +1109,7 @@ func asmAppParams(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { return ops.errorf("%s expects one argument", spec.Name) } - fs, ok := AppParamsFieldSpecByName[args[0]] + fs, ok := appParamsFieldSpecByName[args[0]] if !ok { return ops.errorf("%s unknown field: %#v", spec.Name, args[0]) } @@ -1130,7 +1130,7 @@ func asmAcctParams(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { return ops.errorf("%s expects one argument", spec.Name) } - fs, ok := AcctParamsFieldSpecByName[args[0]] + fs, ok := acctParamsFieldSpecByName[args[0]] if !ok { return ops.errorf("%s unknown field: %#v", spec.Name, args[0]) } @@ -1151,15 +1151,17 @@ func asmItxnField(ops *OpStream, spec *OpSpec, args []string) error { if len(args) != 1 { return ops.errorf("%s expects one argument", spec.Name) } - fs, ok := TxnFieldSpecByName[args[0]] + fs, ok := txnFieldSpecByName[args[0]] if !ok { return ops.errorf("%s unknown field: %#v", spec.Name, args[0]) } if fs.itxVersion == 0 { - return ops.errorf("%s %#v is not allowed.", spec.Name, args[0]) + //nolint:errcheck // we continue to maintain typestack + ops.errorf("%s %#v is not allowed.", spec.Name, args[0]) } if fs.itxVersion > ops.Version { - return ops.errorf("%s %s field was introduced in TEAL v%d. Missed #pragma version?", spec.Name, args[0], fs.itxVersion) + //nolint:errcheck // we continue to maintain typestack + ops.errorf("%s %s field was introduced in TEAL v%d. Missed #pragma version?", spec.Name, args[0], fs.itxVersion) } ops.pending.WriteByte(spec.Opcode) ops.pending.WriteByte(uint8(fs.field)) @@ -1171,7 +1173,7 @@ func asmEcdsa(ops *OpStream, spec *OpSpec, args []string) error { return ops.errorf("%s expects one argument", spec.Name) } - cs, ok := EcdsaCurveSpecByName[args[0]] + cs, ok := ecdsaCurveSpecByName[args[0]] if !ok { return ops.errorf("%s unknown field: %#v", spec.Name, args[0]) } @@ -1218,7 +1220,8 @@ func asmJSONRef(ops *OpStream, spec *OpSpec, args []string) error { return ops.errorf("%s unsupported JSON value type: %#v", spec.Name, args[0]) } if jsonSpec.version > ops.Version { - return ops.errorf("%s %s field was introduced in TEAL v%d. Missed #pragma version?", spec.Name, args[0], jsonSpec.version) + //nolint:errcheck // we continue to maintain typestack + ops.errorf("%s %s field was introduced in TEAL v%d. Missed #pragma version?", spec.Name, args[0], jsonSpec.version) } valueType := jsonSpec.field @@ -1394,7 +1397,7 @@ func typeTxField(ops *OpStream, args []string) (StackTypes, StackTypes) { if len(args) != 1 { return oneAny, nil } - fs, ok := TxnFieldSpecByName[args[0]] + fs, ok := txnFieldSpecByName[args[0]] if !ok { return oneAny, nil } diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 56b0697714..b2b8bc6b08 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -905,14 +905,14 @@ func TestAssets(t *testing.T) { } func testAssetsByVersion(t *testing.T, assetsTestProgram string, version uint64) { - for _, field := range AssetHoldingFieldNames { - fs := AssetHoldingFieldSpecByName[field] + for _, field := range assetHoldingFieldNames { + fs := assetHoldingFieldSpecByName[field] if fs.version <= version && !strings.Contains(assetsTestProgram, field) { t.Errorf("TestAssets missing field %v", field) } } - for _, field := range AssetParamsFieldNames { - fs := AssetParamsFieldSpecByName[field] + for _, field := range assetParamsFieldNames { + fs := assetParamsFieldSpecByName[field] if fs.version <= version && !strings.Contains(assetsTestProgram, field) { t.Errorf("TestAssets missing field %v", field) } diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 1fa97132fd..0055fd7589 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -202,8 +202,7 @@ func txnFieldSpecByField(f TxnField) (txnFieldSpec, bool) { return txnFieldSpecs[f], true } -// TxnFieldSpecByName gives access to the field specs by field name -var TxnFieldSpecByName tfNameSpecMap = make(map[string]txnFieldSpec, len(TxnFieldNames)) +var txnFieldSpecByName = make(tfNameSpecMap, len(TxnFieldNames)) // simple interface used by doc generator for fields versioning type tfNameSpecMap map[string]txnFieldSpec @@ -327,31 +326,33 @@ var txnFieldSpecs = [...]txnFieldSpec{ {StateProofPK, StackBytes, false, 6, 6, false, "64 byte state proof public key commitment"}, } -// TxnaFieldNames are arguments to the 'txna' opcode -// It need not be fast, as it's only used for doc generation. -func TxnaFieldNames() []string { - var names []string - for _, fs := range txnFieldSpecs { +// TxnaFieldNames are txn field names that return arrays. Return value is a +// "sparse" slice, the names appear at their usual index, non-array alots are +// set to "". It need not be fast, as it's only called once. They laid out this +// way so that it is possible to get the name from the index value. +func txnaFieldNames() []string { + names := make([]string, len(txnFieldSpecs)) + for i, fs := range txnFieldSpecs { if fs.array { - names = append(names, fs.field.String()) + names[i] = fs.field.String() + } else { + names[i] = "" } } return names } -var txnFields = FieldGroup{ +var TxnFields = FieldGroup{ "txn", TxnFieldNames[:], - TxnFieldSpecByName, + txnFieldSpecByName, } -/* -var txnaFields = FieldGroup{ +var TxnaFields = FieldGroup{ "txna", - TxnaFieldNames(), - TxnFieldSpecByName, + txnaFieldNames(), + txnFieldSpecByName, } -*/ var innerTxnTypes = map[string]uint64{ string(protocol.PaymentTx): 5, @@ -374,7 +375,7 @@ var TxnTypeNames = [...]string{ } // map txn type names (long and short) to index/enum value -var txnTypeMap map[string]uint64 = make(map[string]uint64) +var txnTypeMap = make(map[string]uint64) // OnCompletionConstType is the same as transactions.OnCompletion type OnCompletionConstType transactions.OnCompletion @@ -523,8 +524,7 @@ func globalFieldSpecByField(f GlobalField) (globalFieldSpec, bool) { return globalFieldSpecs[f], true } -// GlobalFieldSpecByName gives access to the field specs by field name -var GlobalFieldSpecByName gfNameSpecMap = make(gfNameSpecMap, len(GlobalFieldNames)) +var globalFieldSpecByName = make(gfNameSpecMap, len(GlobalFieldNames)) type gfNameSpecMap map[string]globalFieldSpec @@ -532,10 +532,10 @@ func (s gfNameSpecMap) SpecByName(name string) FieldSpec { return s[name] } -var globalFields = FieldGroup{ +var GlobalFields = FieldGroup{ "global", GlobalFieldNames[:], - GlobalFieldSpecByName, + globalFieldSpecByName, } // EcdsaCurve is an enum for `ecdsa_` opcodes @@ -549,8 +549,7 @@ const ( invalidEcdsaCurve // compile-time constant for number of fields ) -// EcdsaCurveNames are arguments to the 'ecdsa_' opcode -var EcdsaCurveNames [invalidEcdsaCurve]string +var ecdsaCurveNames [invalidEcdsaCurve]string type ecdsaCurveSpec struct { field EcdsaCurve @@ -590,8 +589,7 @@ func ecdsaCurveSpecByField(c EcdsaCurve) (ecdsaCurveSpec, bool) { return ecdsaCurveSpecs[c], true } -// EcdsaCurveSpecByName gives access to the field specs by field name -var EcdsaCurveSpecByName ecDsaCurveNameSpecMap = make(ecDsaCurveNameSpecMap, len(EcdsaCurveNames)) +var ecdsaCurveSpecByName = make(ecDsaCurveNameSpecMap, len(ecdsaCurveNames)) type ecDsaCurveNameSpecMap map[string]ecdsaCurveSpec @@ -599,10 +597,10 @@ func (s ecDsaCurveNameSpecMap) SpecByName(name string) FieldSpec { return s[name] } -var ecdsaCurves = FieldGroup{ +var EcdsaCurves = FieldGroup{ "ecdsa", - EcdsaCurveNames[:], - EcdsaCurveSpecByName, + ecdsaCurveNames[:], + ecdsaCurveSpecByName, } // Base64Encoding is an enum for the `base64decode` opcode @@ -636,7 +634,7 @@ func base64EncodingSpecByField(e Base64Encoding) (base64EncodingSpec, bool) { return base64EncodingSpecs[e], true } -var base64EncodingSpecByName base64EncodingSpecMap = make(base64EncodingSpecMap, len(base64EncodingNames)) +var base64EncodingSpecByName = make(base64EncodingSpecMap, len(base64EncodingNames)) type base64EncodingSpecMap map[string]base64EncodingSpec @@ -665,7 +663,7 @@ func (s base64EncodingSpecMap) SpecByName(name string) FieldSpec { return s[name] } -var base64Encodings = FieldGroup{ +var Base64Encodings = FieldGroup{ "base64", base64EncodingNames[:], base64EncodingSpecByName, @@ -684,7 +682,6 @@ const ( invalidJSONRefType // compile-time constant for number of fields ) -// After running `go generate` these strings will be available: var jsonRefTypeNames [invalidJSONRefType]string type jsonRefSpec struct { @@ -706,7 +703,7 @@ func jsonRefSpecByField(r JSONRefType) (jsonRefSpec, bool) { return jsonRefSpecs[r], true } -var jsonRefSpecByName jsonRefSpecMap = make(jsonRefSpecMap, len(jsonRefTypeNames)) +var jsonRefSpecByName = make(jsonRefSpecMap, len(jsonRefTypeNames)) type jsonRefSpecMap map[string]jsonRefSpec @@ -735,7 +732,7 @@ func (s jsonRefSpecMap) SpecByName(name string) FieldSpec { return s[name] } -var jsonRefTypes = FieldGroup{ +var JsonRefTypes = FieldGroup{ "json_ref", jsonRefTypeNames[:], jsonRefSpecByName, @@ -752,8 +749,7 @@ const ( invalidAssetHoldingField // compile-time constant for number of fields ) -// AssetHoldingFieldNames are arguments to the 'asset_holding_get' opcode -var AssetHoldingFieldNames [invalidAssetHoldingField]string +var assetHoldingFieldNames [invalidAssetHoldingField]string type assetHoldingFieldSpec struct { field AssetHoldingField @@ -794,8 +790,7 @@ func assetHoldingFieldSpecByField(f AssetHoldingField) (assetHoldingFieldSpec, b return assetHoldingFieldSpecs[f], true } -// AssetHoldingFieldSpecByName gives access to the field specs by field name -var AssetHoldingFieldSpecByName ahfNameSpecMap = make(ahfNameSpecMap, len(AssetHoldingFieldNames)) +var assetHoldingFieldSpecByName = make(ahfNameSpecMap, len(assetHoldingFieldNames)) type ahfNameSpecMap map[string]assetHoldingFieldSpec @@ -803,10 +798,10 @@ func (s ahfNameSpecMap) SpecByName(name string) FieldSpec { return s[name] } -var assetHoldingFields = FieldGroup{ +var AssetHoldingFields = FieldGroup{ "asset_holding", - AssetHoldingFieldNames[:], - AssetHoldingFieldSpecByName, + assetHoldingFieldNames[:], + assetHoldingFieldSpecByName, } // AssetParamsField is an enum for `asset_params_get` opcode @@ -842,8 +837,7 @@ const ( invalidAssetParamsField // compile-time constant for number of fields ) -// AssetParamsFieldNames are arguments to the 'asset_params_get' opcode -var AssetParamsFieldNames [invalidAssetParamsField]string +var assetParamsFieldNames [invalidAssetParamsField]string type assetParamsFieldSpec struct { field AssetParamsField @@ -894,8 +888,7 @@ func assetParamsFieldSpecByField(f AssetParamsField) (assetParamsFieldSpec, bool return assetParamsFieldSpecs[f], true } -// AssetParamsFieldSpecByName gives access to the field specs by field name -var AssetParamsFieldSpecByName apfNameSpecMap = make(apfNameSpecMap, len(AssetParamsFieldNames)) +var assetParamsFieldSpecByName = make(apfNameSpecMap, len(assetParamsFieldNames)) type apfNameSpecMap map[string]assetParamsFieldSpec @@ -903,10 +896,10 @@ func (s apfNameSpecMap) SpecByName(name string) FieldSpec { return s[name] } -var assetParamsFields = FieldGroup{ +var AssetParamsFields = FieldGroup{ "asset_params", - AssetParamsFieldNames[:], - AssetParamsFieldSpecByName, + assetParamsFieldNames[:], + assetParamsFieldSpecByName, } // AppParamsField is an enum for `app_params_get` opcode @@ -937,8 +930,7 @@ const ( invalidAppParamsField // compile-time constant for number of fields ) -// AppParamsFieldNames are arguments to the 'app_params_get' opcode -var AppParamsFieldNames [invalidAppParamsField]string +var appParamsFieldNames [invalidAppParamsField]string type appParamsFieldSpec struct { field AppParamsField @@ -986,8 +978,7 @@ func appParamsFieldSpecByField(f AppParamsField) (appParamsFieldSpec, bool) { return appParamsFieldSpecs[f], true } -// AppParamsFieldSpecByName gives access to the field specs by field name -var AppParamsFieldSpecByName appNameSpecMap = make(appNameSpecMap, len(AppParamsFieldNames)) +var appParamsFieldSpecByName = make(appNameSpecMap, len(appParamsFieldNames)) // simple interface used by doc generator for fields versioning type appNameSpecMap map[string]appParamsFieldSpec @@ -996,10 +987,10 @@ func (s appNameSpecMap) SpecByName(name string) FieldSpec { return s[name] } -var appParamsFields = FieldGroup{ +var AppParamsFields = FieldGroup{ "app_params", - AppParamsFieldNames[:], - AppParamsFieldSpecByName, + appParamsFieldNames[:], + appParamsFieldSpecByName, } // AcctParamsField is an enum for `acct_params_get` opcode @@ -1016,8 +1007,7 @@ const ( invalidAcctParamsField // compile-time constant for number of fields ) -// AcctParamsFieldNames are arguments to the 'acct_params_get' opcode -var AcctParamsFieldNames [invalidAcctParamsField]string +var acctParamsFieldNames [invalidAcctParamsField]string type acctParamsFieldSpec struct { field AcctParamsField @@ -1059,20 +1049,18 @@ func acctParamsFieldSpecByField(f AcctParamsField) (acctParamsFieldSpec, bool) { return acctParamsFieldSpecs[f], true } -// AcctParamsFieldSpecByName gives access to the field specs by field name -var AcctParamsFieldSpecByName acctNameSpecMap = make(acctNameSpecMap, len(AcctParamsFieldNames)) +var acctParamsFieldSpecByName = make(acctNameSpecMap, len(acctParamsFieldNames)) -// simple interface used by doc generator for fields versioning type acctNameSpecMap map[string]acctParamsFieldSpec func (s acctNameSpecMap) SpecByName(name string) FieldSpec { return s[name] } -var acctParamsFields = FieldGroup{ +var AcctParamsFields = FieldGroup{ "acct_params", - AcctParamsFieldNames[:], - AcctParamsFieldSpecByName, + acctParamsFieldNames[:], + acctParamsFieldSpecByName, } func init() { @@ -1086,21 +1074,21 @@ func init() { for i, s := range txnFieldSpecs { equal(int(s.field), i) TxnFieldNames[s.field] = s.field.String() - TxnFieldSpecByName[s.field.String()] = s + txnFieldSpecByName[s.field.String()] = s } equal(len(globalFieldSpecs), len(GlobalFieldNames)) for i, s := range globalFieldSpecs { equal(int(s.field), i) GlobalFieldNames[s.field] = s.field.String() - GlobalFieldSpecByName[s.field.String()] = s + globalFieldSpecByName[s.field.String()] = s } - equal(len(ecdsaCurveSpecs), len(EcdsaCurveNames)) + equal(len(ecdsaCurveSpecs), len(ecdsaCurveNames)) for i, s := range ecdsaCurveSpecs { equal(int(s.field), i) - EcdsaCurveNames[s.field] = s.field.String() - EcdsaCurveSpecByName[s.field.String()] = s + ecdsaCurveNames[s.field] = s.field.String() + ecdsaCurveSpecByName[s.field.String()] = s } equal(len(base64EncodingSpecs), len(base64EncodingNames)) @@ -1117,32 +1105,32 @@ func init() { jsonRefSpecByName[s.field.String()] = s } - equal(len(assetHoldingFieldSpecs), len(AssetHoldingFieldNames)) + equal(len(assetHoldingFieldSpecs), len(assetHoldingFieldNames)) for i, s := range assetHoldingFieldSpecs { equal(int(s.field), i) - AssetHoldingFieldNames[i] = s.field.String() - AssetHoldingFieldSpecByName[s.field.String()] = s + assetHoldingFieldNames[i] = s.field.String() + assetHoldingFieldSpecByName[s.field.String()] = s } - equal(len(assetParamsFieldSpecs), len(AssetParamsFieldNames)) + equal(len(assetParamsFieldSpecs), len(assetParamsFieldNames)) for i, s := range assetParamsFieldSpecs { equal(int(s.field), i) - AssetParamsFieldNames[i] = s.field.String() - AssetParamsFieldSpecByName[s.field.String()] = s + assetParamsFieldNames[i] = s.field.String() + assetParamsFieldSpecByName[s.field.String()] = s } - equal(len(appParamsFieldSpecs), len(AppParamsFieldNames)) + equal(len(appParamsFieldSpecs), len(appParamsFieldNames)) for i, s := range appParamsFieldSpecs { equal(int(s.field), i) - AppParamsFieldNames[i] = s.field.String() - AppParamsFieldSpecByName[s.field.String()] = s + appParamsFieldNames[i] = s.field.String() + appParamsFieldSpecByName[s.field.String()] = s } - equal(len(acctParamsFieldSpecs), len(AcctParamsFieldNames)) + equal(len(acctParamsFieldSpecs), len(acctParamsFieldNames)) for i, s := range acctParamsFieldSpecs { equal(int(s.field), i) - AcctParamsFieldNames[i] = s.field.String() - AcctParamsFieldSpecByName[s.field.String()] = s + acctParamsFieldNames[i] = s.field.String() + acctParamsFieldSpecByName[s.field.String()] = s } txnTypeMap = make(map[string]uint64) diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index aec035e895..75ab0c4926 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -203,9 +203,9 @@ var OpSpecs = []OpSpec{ {0x04, "ed25519verify", opEd25519Verify, asmDefault, disDefault, threeBytes, oneInt, 1, runModeSignature, costly(1900)}, {0x04, "ed25519verify", opEd25519Verify, asmDefault, disDefault, threeBytes, oneInt, 5, modeAny, costly(1900)}, - {0x05, "ecdsa_verify", opEcdsaVerify, asmEcdsa, disDefault, threeBytes.plus(twoBytes), oneInt, 5, modeAny, costByField("v", &ecdsaCurves, ecdsaVerifyCosts)}, - {0x06, "ecdsa_pk_decompress", opEcdsaPkDecompress, asmEcdsa, disDefault, oneBytes, twoBytes, 5, modeAny, costByField("v", &ecdsaCurves, ecdsaDecompressCosts)}, - {0x07, "ecdsa_pk_recover", opEcdsaPkRecover, asmEcdsa, disDefault, oneBytes.plus(oneInt).plus(twoBytes), twoBytes, 5, modeAny, field("v", &ecdsaCurves).costs(2000)}, + {0x05, "ecdsa_verify", opEcdsaVerify, asmEcdsa, disDefault, threeBytes.plus(twoBytes), oneInt, 5, modeAny, costByField("v", &EcdsaCurves, ecdsaVerifyCosts)}, + {0x06, "ecdsa_pk_decompress", opEcdsaPkDecompress, asmEcdsa, disDefault, oneBytes, twoBytes, 5, modeAny, costByField("v", &EcdsaCurves, ecdsaDecompressCosts)}, + {0x07, "ecdsa_pk_recover", opEcdsaPkRecover, asmEcdsa, disDefault, oneBytes.plus(oneInt).plus(twoBytes), twoBytes, 5, modeAny, field("v", &EcdsaCurves).costs(2000)}, {0x08, "+", opPlus, asmDefault, disDefault, twoInts, oneInt, 1, modeAny, opDefault}, {0x09, "-", opMinus, asmDefault, disDefault, twoInts, oneInt, 1, modeAny, opDefault}, @@ -249,28 +249,28 @@ var OpSpecs = []OpSpec{ {0x2e, "arg_1", opArg1, asmDefault, disDefault, nil, oneBytes, 1, runModeSignature, opDefault}, {0x2f, "arg_2", opArg2, asmDefault, disDefault, nil, oneBytes, 1, runModeSignature, opDefault}, {0x30, "arg_3", opArg3, asmDefault, disDefault, nil, oneBytes, 1, runModeSignature, opDefault}, - {0x31, "txn", opTxn, asmTxn, disDefault, nil, oneAny, 1, modeAny, field("f", &txnFields)}, + {0x31, "txn", opTxn, asmTxn, disDefault, nil, oneAny, 1, modeAny, field("f", &TxnFields)}, // It is ok to have the same opcode for different TEAL versions. // This 'txn' asm command supports additional argument in version 2 and // generates 'txna' opcode in that particular case - {0x31, "txn", opTxn, asmTxn2, disDefault, nil, oneAny, 2, modeAny, field("f", &txnFields)}, + {0x31, "txn", opTxn, asmTxn2, disDefault, nil, oneAny, 2, modeAny, field("f", &TxnFields)}, {0x32, "global", opGlobal, asmGlobal, disDefault, nil, oneAny, 1, modeAny, - field("f", &globalFields)}, + field("f", &GlobalFields)}, {0x33, "gtxn", opGtxn, asmGtxn, disDefault, nil, oneAny, 1, modeAny, - immediates("t", "f").field("f", &txnFields)}, + immediates("t", "f").field("f", &TxnFields)}, {0x33, "gtxn", opGtxn, asmGtxn2, disDefault, nil, oneAny, 2, modeAny, - immediates("t", "f").field("f", &txnFields)}, + immediates("t", "f").field("f", &TxnFields)}, {0x34, "load", opLoad, asmDefault, disDefault, nil, oneAny, 1, modeAny, immediates("i")}, {0x35, "store", opStore, asmDefault, disDefault, oneAny, nil, 1, modeAny, immediates("i")}, {0x36, "txna", opTxna, asmTxna, disDefault, nil, oneAny, 2, modeAny, - immediates("f", "i").field("f", &txnFields)}, + immediates("f", "i").field("f", &TxnaFields)}, {0x37, "gtxna", opGtxna, asmGtxna, disDefault, nil, oneAny, 2, modeAny, - immediates("t", "f", "i").field("f", &txnFields)}, + immediates("t", "f", "i").field("f", &TxnFields)}, // Like gtxn, but gets txn index from stack, rather than immediate arg {0x38, "gtxns", opGtxns, asmGtxns, disDefault, oneInt, oneAny, 3, modeAny, - immediates("f").field("f", &txnFields)}, + immediates("f").field("f", &TxnFields)}, {0x39, "gtxnsa", opGtxnsa, asmGtxns, disDefault, oneInt, oneAny, 3, modeAny, - immediates("f", "i").field("f", &txnFields)}, + immediates("f", "i").field("f", &TxnFields)}, // Group scratch space access {0x3a, "gload", opGload, asmDefault, disDefault, nil, oneAny, 4, runModeApplication, immediates("t", "i")}, {0x3b, "gloads", opGloads, asmDefault, disDefault, oneInt, oneAny, 4, runModeApplication, immediates("i")}, @@ -311,8 +311,8 @@ var OpSpecs = []OpSpec{ {0x59, "extract_uint16", opExtract16Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, {0x5a, "extract_uint32", opExtract32Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, {0x5b, "extract_uint64", opExtract64Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, - {0x5c, "base64_decode", opBase64Decode, asmBase64Decode, disDefault, oneBytes, oneBytes, fidoVersion, modeAny, field("e", &base64Encodings).costs(25)}, - {0x5d, "json_ref", opJSONRef, asmJSONRef, disDefault, twoBytes, oneAny, fidoVersion, modeAny, field("r", &jsonRefTypes)}, + {0x5c, "base64_decode", opBase64Decode, asmBase64Decode, disDefault, oneBytes, oneBytes, fidoVersion, modeAny, field("e", &Base64Encodings).costs(25)}, + {0x5d, "json_ref", opJSONRef, asmJSONRef, disDefault, twoBytes, oneAny, fidoVersion, modeAny, field("r", &JsonRefTypes)}, {0x60, "balance", opBalance, asmDefault, disDefault, oneInt, oneInt, 2, runModeApplication, opDefault}, {0x60, "balance", opBalance, asmDefault, disDefault, oneAny, oneInt, directRefEnabledVersion, runModeApplication, opDefault}, @@ -331,11 +331,11 @@ var OpSpecs = []OpSpec{ {0x68, "app_local_del", opAppLocalDel, asmDefault, disDefault, oneAny.plus(oneBytes), nil, directRefEnabledVersion, runModeApplication, opDefault}, {0x69, "app_global_del", opAppGlobalDel, asmDefault, disDefault, oneBytes, nil, 2, runModeApplication, opDefault}, - {0x70, "asset_holding_get", opAssetHoldingGet, asmAssetHolding, disDefault, twoInts, oneAny.plus(oneInt), 2, runModeApplication, field("f", &assetHoldingFields)}, - {0x70, "asset_holding_get", opAssetHoldingGet, asmAssetHolding, disDefault, oneAny.plus(oneInt), oneAny.plus(oneInt), directRefEnabledVersion, runModeApplication, field("f", &assetHoldingFields)}, - {0x71, "asset_params_get", opAssetParamsGet, asmAssetParams, disDefault, oneInt, oneAny.plus(oneInt), 2, runModeApplication, field("f", &assetParamsFields)}, - {0x72, "app_params_get", opAppParamsGet, asmAppParams, disDefault, oneInt, oneAny.plus(oneInt), 5, runModeApplication, field("f", &appParamsFields)}, - {0x73, "acct_params_get", opAcctParamsGet, asmAcctParams, disDefault, oneAny, oneAny.plus(oneInt), 6, runModeApplication, field("f", &acctParamsFields)}, + {0x70, "asset_holding_get", opAssetHoldingGet, asmAssetHolding, disDefault, twoInts, oneAny.plus(oneInt), 2, runModeApplication, field("f", &AssetHoldingFields)}, + {0x70, "asset_holding_get", opAssetHoldingGet, asmAssetHolding, disDefault, oneAny.plus(oneInt), oneAny.plus(oneInt), directRefEnabledVersion, runModeApplication, field("f", &AssetHoldingFields)}, + {0x71, "asset_params_get", opAssetParamsGet, asmAssetParams, disDefault, oneInt, oneAny.plus(oneInt), 2, runModeApplication, field("f", &AssetParamsFields)}, + {0x72, "app_params_get", opAppParamsGet, asmAppParams, disDefault, oneInt, oneAny.plus(oneInt), 5, runModeApplication, field("f", &AppParamsFields)}, + {0x73, "acct_params_get", opAcctParamsGet, asmAcctParams, disDefault, oneAny, oneAny.plus(oneInt), 6, runModeApplication, field("f", &AcctParamsFields)}, {0x78, "min_balance", opMinBalance, asmDefault, disDefault, oneInt, oneInt, 3, runModeApplication, opDefault}, {0x78, "min_balance", opMinBalance, asmDefault, disDefault, oneAny, oneInt, directRefEnabledVersion, runModeApplication, opDefault}, @@ -384,31 +384,31 @@ var OpSpecs = []OpSpec{ {0xb0, "log", opLog, asmDefault, disDefault, oneBytes, nil, 5, runModeApplication, opDefault}, {0xb1, "itxn_begin", opTxBegin, asmDefault, disDefault, nil, nil, 5, runModeApplication, opDefault}, {0xb2, "itxn_field", opItxnField, asmItxnField, disDefault, oneAny, nil, 5, runModeApplication, - stacky(typeTxField, "f").field("f", &txnFields)}, + stacky(typeTxField, "f").field("f", &TxnFields)}, {0xb3, "itxn_submit", opItxnSubmit, asmDefault, disDefault, nil, nil, 5, runModeApplication, opDefault}, {0xb4, "itxn", opItxn, asmItxn, disDefault, nil, oneAny, 5, runModeApplication, - field("f", &txnFields)}, + field("f", &TxnFields)}, {0xb5, "itxna", opItxna, asmTxna, disDefault, nil, oneAny, 5, runModeApplication, - immediates("f", "i").field("f", &txnFields)}, + immediates("f", "i").field("f", &TxnFields)}, {0xb6, "itxn_next", opItxnNext, asmDefault, disDefault, nil, nil, 6, runModeApplication, opDefault}, {0xb7, "gitxn", opGitxn, asmGitxn, disDefault, nil, oneAny, 6, runModeApplication, - immediates("t", "f").field("f", &txnFields)}, + immediates("t", "f").field("f", &TxnFields)}, {0xb8, "gitxna", opGitxna, asmGtxna, disDefault, nil, oneAny, 6, runModeApplication, - immediates("t", "f", "i").field("f", &txnFields)}, + immediates("t", "f", "i").field("f", &TxnFields)}, // Dynamic indexing {0xc0, "txnas", opTxnas, asmTxnas, disDefault, oneInt, oneAny, 5, modeAny, - field("f", &txnFields)}, + field("f", &TxnFields)}, {0xc1, "gtxnas", opGtxnas, asmGtxnas, disDefault, oneInt, oneAny, 5, modeAny, - immediates("t", "f").field("f", &txnFields)}, + immediates("t", "f").field("f", &TxnFields)}, {0xc2, "gtxnsas", opGtxnsas, asmGtxnsas, disDefault, twoInts, oneAny, 5, modeAny, - field("f", &txnFields)}, + field("f", &TxnFields)}, {0xc3, "args", opArgs, asmDefault, disDefault, oneInt, oneBytes, 5, runModeSignature, opDefault}, {0xc4, "gloadss", opGloadss, asmDefault, disDefault, twoInts, oneAny, 6, runModeApplication, opDefault}, {0xc5, "itxnas", opItxnas, asmTxnas, disDefault, oneInt, oneAny, 6, runModeApplication, - field("f", &txnFields)}, + field("f", &TxnFields)}, {0xc6, "gitxnas", opGitxnas, asmGtxnas, disDefault, oneInt, oneAny, 6, runModeApplication, - immediates("t", "f").field("f", &txnFields)}, + immediates("t", "f").field("f", &TxnFields)}, } type sortByOpcode []OpSpec From ca7504fbdd3a5085140e7609fd5937df03e22f1a Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Thu, 24 Mar 2022 16:01:32 -0400 Subject: [PATCH 4/9] linting --- data/transactions/logic/fields.go | 12 +++++++++++- data/transactions/logic/opcodes.go | 2 +- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 0055fd7589..16600b3cb7 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -342,12 +342,14 @@ func txnaFieldNames() []string { return names } +// TxnFields contains info on the arguments to the txn* family of opcodes var TxnFields = FieldGroup{ "txn", TxnFieldNames[:], txnFieldSpecByName, } +// TxnaFields narows TxnFields to only have the names of array fetching opcodes var TxnaFields = FieldGroup{ "txna", txnaFieldNames(), @@ -532,6 +534,7 @@ func (s gfNameSpecMap) SpecByName(name string) FieldSpec { return s[name] } +// GlobalFields has info on the global opcode's immediate var GlobalFields = FieldGroup{ "global", GlobalFieldNames[:], @@ -597,6 +600,7 @@ func (s ecDsaCurveNameSpecMap) SpecByName(name string) FieldSpec { return s[name] } +// EcdsaCurves collects details about the constants used to describe EcdsaCurves var EcdsaCurves = FieldGroup{ "ecdsa", ecdsaCurveNames[:], @@ -663,6 +667,7 @@ func (s base64EncodingSpecMap) SpecByName(name string) FieldSpec { return s[name] } +// Base64Encodings describes the base64_encode immediate var Base64Encodings = FieldGroup{ "base64", base64EncodingNames[:], @@ -732,7 +737,8 @@ func (s jsonRefSpecMap) SpecByName(name string) FieldSpec { return s[name] } -var JsonRefTypes = FieldGroup{ +// JSONRefTypes describes the json_ref immediate +var JSONRefTypes = FieldGroup{ "json_ref", jsonRefTypeNames[:], jsonRefSpecByName, @@ -798,6 +804,7 @@ func (s ahfNameSpecMap) SpecByName(name string) FieldSpec { return s[name] } +// AssetHoldingFields describes asset_holding_get's immediates var AssetHoldingFields = FieldGroup{ "asset_holding", assetHoldingFieldNames[:], @@ -896,6 +903,7 @@ func (s apfNameSpecMap) SpecByName(name string) FieldSpec { return s[name] } +// AssetParamsFields describes asset_params_get's immediates var AssetParamsFields = FieldGroup{ "asset_params", assetParamsFieldNames[:], @@ -987,6 +995,7 @@ func (s appNameSpecMap) SpecByName(name string) FieldSpec { return s[name] } +// AppParamsFields describes app_params_get's immediates var AppParamsFields = FieldGroup{ "app_params", appParamsFieldNames[:], @@ -1057,6 +1066,7 @@ func (s acctNameSpecMap) SpecByName(name string) FieldSpec { return s[name] } +// AcctParamsFields describes acct_params_get's immediates var AcctParamsFields = FieldGroup{ "acct_params", acctParamsFieldNames[:], diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 75ab0c4926..3f9e4f4b07 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -312,7 +312,7 @@ var OpSpecs = []OpSpec{ {0x5a, "extract_uint32", opExtract32Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, {0x5b, "extract_uint64", opExtract64Bits, asmDefault, disDefault, byteInt, oneInt, 5, modeAny, opDefault}, {0x5c, "base64_decode", opBase64Decode, asmBase64Decode, disDefault, oneBytes, oneBytes, fidoVersion, modeAny, field("e", &Base64Encodings).costs(25)}, - {0x5d, "json_ref", opJSONRef, asmJSONRef, disDefault, twoBytes, oneAny, fidoVersion, modeAny, field("r", &JsonRefTypes)}, + {0x5d, "json_ref", opJSONRef, asmJSONRef, disDefault, twoBytes, oneAny, fidoVersion, modeAny, field("r", &JSONRefTypes)}, {0x60, "balance", opBalance, asmDefault, disDefault, oneInt, oneInt, 2, runModeApplication, opDefault}, {0x60, "balance", opBalance, asmDefault, disDefault, oneAny, oneInt, directRefEnabledVersion, runModeApplication, opDefault}, From d6b48e632e23bcd799a5b7df1c990a7566573445 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 25 Mar 2022 14:41:27 -0400 Subject: [PATCH 5/9] CR comments. Also, add some derived files so we can notice changes. --- cmd/opdoc/opdoc.go | 2 - data/transactions/logic/.gitignore | 2 - data/transactions/logic/assembler.go | 2 +- data/transactions/logic/fields.go | 6 +- data/transactions/logic/langspec.json | 1 + data/transactions/logic/teal.tmLanguage.json | 136 +++++++++++++++++++ 6 files changed, 141 insertions(+), 8 deletions(-) delete mode 100644 data/transactions/logic/.gitignore create mode 100644 data/transactions/logic/langspec.json create mode 100644 data/transactions/logic/teal.tmLanguage.json diff --git a/cmd/opdoc/opdoc.go b/cmd/opdoc/opdoc.go index 3ae93a0edd..f9243fba6f 100644 --- a/cmd/opdoc/opdoc.go +++ b/cmd/opdoc/opdoc.go @@ -278,7 +278,6 @@ type OpRecord struct { Name string Args string `json:",omitempty"` Returns string `json:",omitempty"` - Cost int Size int ArgEnum []string `json:",omitempty"` @@ -359,7 +358,6 @@ func buildLanguageSpec(opGroups map[string][]string) *LanguageSpec { records[i].Name = spec.Name records[i].Args = typeString(spec.Args) records[i].Returns = typeString(spec.Returns) - records[i].Cost = spec.Details.Cost records[i].Size = spec.Details.Size records[i].ArgEnum, records[i].ArgEnumTypes = argEnums(spec.Name) records[i].Doc = logic.OpDoc(spec.Name) diff --git a/data/transactions/logic/.gitignore b/data/transactions/logic/.gitignore deleted file mode 100644 index 24f8b4a361..0000000000 --- a/data/transactions/logic/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -langspec.json -teal.tmLanguage.json diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 2b9b644396..b38d3be54e 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -2285,7 +2285,7 @@ func disDefault(dis *disassembleState, spec *OpSpec) (string, error) { } pc = nextpc default: - return "", fmt.Errorf("disDefault asked to disassemble complex immediate") + return "", fmt.Errorf("unknown immKind %d", imm.kind) } } diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 16600b3cb7..c7a21efcb7 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -592,11 +592,11 @@ func ecdsaCurveSpecByField(c EcdsaCurve) (ecdsaCurveSpec, bool) { return ecdsaCurveSpecs[c], true } -var ecdsaCurveSpecByName = make(ecDsaCurveNameSpecMap, len(ecdsaCurveNames)) +var ecdsaCurveSpecByName = make(ecdsaCurveNameSpecMap, len(ecdsaCurveNames)) -type ecDsaCurveNameSpecMap map[string]ecdsaCurveSpec +type ecdsaCurveNameSpecMap map[string]ecdsaCurveSpec -func (s ecDsaCurveNameSpecMap) SpecByName(name string) FieldSpec { +func (s ecdsaCurveNameSpecMap) SpecByName(name string) FieldSpec { return s[name] } diff --git a/data/transactions/logic/langspec.json b/data/transactions/logic/langspec.json new file mode 100644 index 0000000000..99bec68c74 --- /dev/null +++ b/data/transactions/logic/langspec.json @@ -0,0 +1 @@ +{"EvalMaxVersion":7,"LogicSigVersion":6,"Ops":[{"Opcode":0,"Name":"err","Size":1,"Doc":"Fail immediately.","Groups":["Flow Control"]},{"Opcode":1,"Name":"sha256","Args":"B","Returns":"B","Size":1,"Doc":"SHA256 hash of value A, yields [32]byte","Groups":["Arithmetic"]},{"Opcode":2,"Name":"keccak256","Args":"B","Returns":"B","Size":1,"Doc":"Keccak256 hash of value A, yields [32]byte","Groups":["Arithmetic"]},{"Opcode":3,"Name":"sha512_256","Args":"B","Returns":"B","Size":1,"Doc":"SHA512_256 hash of value A, yields [32]byte","Groups":["Arithmetic"]},{"Opcode":4,"Name":"ed25519verify","Args":"BBB","Returns":"U","Size":1,"Doc":"for (data A, signature B, pubkey C) verify the signature of (\"ProgData\" || program_hash || data) against the pubkey =\u003e {0 or 1}","DocExtra":"The 32 byte public key is the last element on the stack, preceded by the 64 byte signature at the second-to-last element on the stack, preceded by the data which was signed at the third-to-last element on the stack.","Groups":["Arithmetic"]},{"Opcode":5,"Name":"ecdsa_verify","Args":"BBBBB","Returns":"U","Size":2,"Doc":"for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey =\u003e {0 or 1}","DocExtra":"The 32 byte Y-component of a public key is the last element on the stack, preceded by X-component of a pubkey, preceded by S and R components of a signature, preceded by the data that is fifth element on the stack. All values are big-endian encoded. The signed data must be 32 bytes long, and signatures in lower-S form are only accepted.","ImmediateNote":"{uint8 curve index}","Groups":["Arithmetic"]},{"Opcode":6,"Name":"ecdsa_pk_decompress","Args":"B","Returns":"BB","Size":2,"Doc":"decompress pubkey A into components X, Y","DocExtra":"The 33 byte public key in a compressed form to be decompressed into X and Y (top) components. All values are big-endian encoded.","ImmediateNote":"{uint8 curve index}","Groups":["Arithmetic"]},{"Opcode":7,"Name":"ecdsa_pk_recover","Args":"BUBB","Returns":"BB","Size":2,"Doc":"for (data A, recovery id B, signature C, D) recover a public key","DocExtra":"S (top) and R elements of a signature, recovery id and data (bottom) are expected on the stack and used to deriver a public key. All values are big-endian encoded. The signed data must be 32 bytes long.","ImmediateNote":"{uint8 curve index}","Groups":["Arithmetic"]},{"Opcode":8,"Name":"+","Args":"UU","Returns":"U","Size":1,"Doc":"A plus B. Fail on overflow.","DocExtra":"Overflow is an error condition which halts execution and fails the transaction. Full precision is available from `addw`.","Groups":["Arithmetic"]},{"Opcode":9,"Name":"-","Args":"UU","Returns":"U","Size":1,"Doc":"A minus B. Fail if B \u003e A.","Groups":["Arithmetic"]},{"Opcode":10,"Name":"/","Args":"UU","Returns":"U","Size":1,"Doc":"A divided by B (truncated division). Fail if B == 0.","DocExtra":"`divmodw` is available to divide the two-element values produced by `mulw` and `addw`.","Groups":["Arithmetic"]},{"Opcode":11,"Name":"*","Args":"UU","Returns":"U","Size":1,"Doc":"A times B. Fail on overflow.","DocExtra":"Overflow is an error condition which halts execution and fails the transaction. Full precision is available from `mulw`.","Groups":["Arithmetic"]},{"Opcode":12,"Name":"\u003c","Args":"UU","Returns":"U","Size":1,"Doc":"A less than B =\u003e {0 or 1}","Groups":["Arithmetic"]},{"Opcode":13,"Name":"\u003e","Args":"UU","Returns":"U","Size":1,"Doc":"A greater than B =\u003e {0 or 1}","Groups":["Arithmetic"]},{"Opcode":14,"Name":"\u003c=","Args":"UU","Returns":"U","Size":1,"Doc":"A less than or equal to B =\u003e {0 or 1}","Groups":["Arithmetic"]},{"Opcode":15,"Name":"\u003e=","Args":"UU","Returns":"U","Size":1,"Doc":"A greater than or equal to B =\u003e {0 or 1}","Groups":["Arithmetic"]},{"Opcode":16,"Name":"\u0026\u0026","Args":"UU","Returns":"U","Size":1,"Doc":"A is not zero and B is not zero =\u003e {0 or 1}","Groups":["Arithmetic"]},{"Opcode":17,"Name":"||","Args":"UU","Returns":"U","Size":1,"Doc":"A is not zero or B is not zero =\u003e {0 or 1}","Groups":["Arithmetic"]},{"Opcode":18,"Name":"==","Args":"..","Returns":"U","Size":1,"Doc":"A is equal to B =\u003e {0 or 1}","Groups":["Arithmetic"]},{"Opcode":19,"Name":"!=","Args":"..","Returns":"U","Size":1,"Doc":"A is not equal to B =\u003e {0 or 1}","Groups":["Arithmetic"]},{"Opcode":20,"Name":"!","Args":"U","Returns":"U","Size":1,"Doc":"A == 0 yields 1; else 0","Groups":["Arithmetic"]},{"Opcode":21,"Name":"len","Args":"B","Returns":"U","Size":1,"Doc":"yields length of byte value A","Groups":["Arithmetic"]},{"Opcode":22,"Name":"itob","Args":"U","Returns":"B","Size":1,"Doc":"converts uint64 A to big endian bytes","Groups":["Arithmetic"]},{"Opcode":23,"Name":"btoi","Args":"B","Returns":"U","Size":1,"Doc":"converts bytes A as big endian to uint64","DocExtra":"`btoi` fails if the input is longer than 8 bytes.","Groups":["Arithmetic"]},{"Opcode":24,"Name":"%","Args":"UU","Returns":"U","Size":1,"Doc":"A modulo B. Fail if B == 0.","Groups":["Arithmetic"]},{"Opcode":25,"Name":"|","Args":"UU","Returns":"U","Size":1,"Doc":"A bitwise-or B","Groups":["Arithmetic"]},{"Opcode":26,"Name":"\u0026","Args":"UU","Returns":"U","Size":1,"Doc":"A bitwise-and B","Groups":["Arithmetic"]},{"Opcode":27,"Name":"^","Args":"UU","Returns":"U","Size":1,"Doc":"A bitwise-xor B","Groups":["Arithmetic"]},{"Opcode":28,"Name":"~","Args":"U","Returns":"U","Size":1,"Doc":"bitwise invert value A","Groups":["Arithmetic"]},{"Opcode":29,"Name":"mulw","Args":"UU","Returns":"UU","Size":1,"Doc":"A times B as a 128-bit result in two uint64s. X is the high 64 bits, Y is the low","Groups":["Arithmetic"]},{"Opcode":30,"Name":"addw","Args":"UU","Returns":"UU","Size":1,"Doc":"A plus B as a 128-bit result. X is the carry-bit, Y is the low-order 64 bits.","Groups":["Arithmetic"]},{"Opcode":31,"Name":"divmodw","Args":"UUUU","Returns":"UUUU","Size":1,"Doc":"W,X = (A,B / C,D); Y,Z = (A,B modulo C,D)","DocExtra":"The notation J,K indicates that two uint64 values J and K are interpreted as a uint128 value, with J as the high uint64 and K the low.","Groups":["Arithmetic"]},{"Opcode":32,"Name":"intcblock","Size":0,"Doc":"prepare block of uint64 constants for use by intc","DocExtra":"`intcblock` loads following program bytes into an array of integer constants in the evaluator. These integer constants can be referred to by `intc` and `intc_*` which will push the value onto the stack. Subsequent calls to `intcblock` reset and replace the integer constants available to the script.","ImmediateNote":"{varuint length} [{varuint value}, ...]","Groups":["Loading Values"]},{"Opcode":33,"Name":"intc","Returns":"U","Size":2,"Doc":"Ith constant from intcblock","ImmediateNote":"{uint8 int constant index}","Groups":["Loading Values"]},{"Opcode":34,"Name":"intc_0","Returns":"U","Size":1,"Doc":"constant 0 from intcblock","Groups":["Loading Values"]},{"Opcode":35,"Name":"intc_1","Returns":"U","Size":1,"Doc":"constant 1 from intcblock","Groups":["Loading Values"]},{"Opcode":36,"Name":"intc_2","Returns":"U","Size":1,"Doc":"constant 2 from intcblock","Groups":["Loading Values"]},{"Opcode":37,"Name":"intc_3","Returns":"U","Size":1,"Doc":"constant 3 from intcblock","Groups":["Loading Values"]},{"Opcode":38,"Name":"bytecblock","Size":0,"Doc":"prepare block of byte-array constants for use by bytec","DocExtra":"`bytecblock` loads the following program bytes into an array of byte-array constants in the evaluator. These constants can be referred to by `bytec` and `bytec_*` which will push the value onto the stack. Subsequent calls to `bytecblock` reset and replace the bytes constants available to the script.","ImmediateNote":"{varuint length} [({varuint value length} bytes), ...]","Groups":["Loading Values"]},{"Opcode":39,"Name":"bytec","Returns":"B","Size":2,"Doc":"Ith constant from bytecblock","ImmediateNote":"{uint8 byte constant index}","Groups":["Loading Values"]},{"Opcode":40,"Name":"bytec_0","Returns":"B","Size":1,"Doc":"constant 0 from bytecblock","Groups":["Loading Values"]},{"Opcode":41,"Name":"bytec_1","Returns":"B","Size":1,"Doc":"constant 1 from bytecblock","Groups":["Loading Values"]},{"Opcode":42,"Name":"bytec_2","Returns":"B","Size":1,"Doc":"constant 2 from bytecblock","Groups":["Loading Values"]},{"Opcode":43,"Name":"bytec_3","Returns":"B","Size":1,"Doc":"constant 3 from bytecblock","Groups":["Loading Values"]},{"Opcode":44,"Name":"arg","Returns":"B","Size":2,"Doc":"Nth LogicSig argument","ImmediateNote":"{uint8 arg index N}","Groups":["Loading Values"]},{"Opcode":45,"Name":"arg_0","Returns":"B","Size":1,"Doc":"LogicSig argument 0","Groups":["Loading Values"]},{"Opcode":46,"Name":"arg_1","Returns":"B","Size":1,"Doc":"LogicSig argument 1","Groups":["Loading Values"]},{"Opcode":47,"Name":"arg_2","Returns":"B","Size":1,"Doc":"LogicSig argument 2","Groups":["Loading Values"]},{"Opcode":48,"Name":"arg_3","Returns":"B","Size":1,"Doc":"LogicSig argument 3","Groups":["Loading Values"]},{"Opcode":49,"Name":"txn","Returns":".","Size":2,"ArgEnum":["Sender","Fee","FirstValid","FirstValidTime","LastValid","Note","Lease","Receiver","Amount","CloseRemainderTo","VotePK","SelectionPK","VoteFirst","VoteLast","VoteKeyDilution","Type","TypeEnum","XferAsset","AssetAmount","AssetSender","AssetReceiver","AssetCloseTo","GroupIndex","TxID","ApplicationID","OnCompletion","ApplicationArgs","NumAppArgs","Accounts","NumAccounts","ApprovalProgram","ClearStateProgram","RekeyTo","ConfigAsset","ConfigAssetTotal","ConfigAssetDecimals","ConfigAssetDefaultFrozen","ConfigAssetUnitName","ConfigAssetName","ConfigAssetURL","ConfigAssetMetadataHash","ConfigAssetManager","ConfigAssetReserve","ConfigAssetFreeze","ConfigAssetClawback","FreezeAsset","FreezeAssetAccount","FreezeAssetFrozen","Assets","NumAssets","Applications","NumApplications","GlobalNumUint","GlobalNumByteSlice","LocalNumUint","LocalNumByteSlice","ExtraProgramPages","Nonparticipation","Logs","NumLogs","CreatedAssetID","CreatedApplicationID","LastLog","StateProofPK"],"ArgEnumTypes":"BUUUUBBBUBBBUUUBUUUBBBUBUUBUBUBBBUUUUBBBBBBBBUBUUUUUUUUUUUBUUUBB","Doc":"field F of current transaction","DocExtra":"FirstValidTime causes the program to fail. The field is reserved for future use.","ImmediateNote":"{uint8 transaction field index}","Groups":["Loading Values"]},{"Opcode":50,"Name":"global","Returns":".","Size":2,"Doc":"global field F","ImmediateNote":"{uint8 global field index}","Groups":["Loading Values"]},{"Opcode":51,"Name":"gtxn","Returns":".","Size":3,"ArgEnum":["Sender","Fee","FirstValid","FirstValidTime","LastValid","Note","Lease","Receiver","Amount","CloseRemainderTo","VotePK","SelectionPK","VoteFirst","VoteLast","VoteKeyDilution","Type","TypeEnum","XferAsset","AssetAmount","AssetSender","AssetReceiver","AssetCloseTo","GroupIndex","TxID","ApplicationID","OnCompletion","ApplicationArgs","NumAppArgs","Accounts","NumAccounts","ApprovalProgram","ClearStateProgram","RekeyTo","ConfigAsset","ConfigAssetTotal","ConfigAssetDecimals","ConfigAssetDefaultFrozen","ConfigAssetUnitName","ConfigAssetName","ConfigAssetURL","ConfigAssetMetadataHash","ConfigAssetManager","ConfigAssetReserve","ConfigAssetFreeze","ConfigAssetClawback","FreezeAsset","FreezeAssetAccount","FreezeAssetFrozen","Assets","NumAssets","Applications","NumApplications","GlobalNumUint","GlobalNumByteSlice","LocalNumUint","LocalNumByteSlice","ExtraProgramPages","Nonparticipation","Logs","NumLogs","CreatedAssetID","CreatedApplicationID","LastLog","StateProofPK"],"ArgEnumTypes":"BUUUUBBBUBBBUUUBUUUBBBUBUUBUBUBBBUUUUBBBBBBBBUBUUUUUUUUUUUBUUUBB","Doc":"field F of the Tth transaction in the current group","DocExtra":"for notes on transaction fields available, see `txn`. If this transaction is _i_ in the group, `gtxn i field` is equivalent to `txn field`.","ImmediateNote":"{uint8 transaction group index} {uint8 transaction field index}","Groups":["Loading Values"]},{"Opcode":52,"Name":"load","Returns":".","Size":2,"Doc":"Ith scratch space value. All scratch spaces are 0 at program start.","ImmediateNote":"{uint8 position in scratch space to load from}","Groups":["Loading Values"]},{"Opcode":53,"Name":"store","Args":".","Size":2,"Doc":"store A to the Ith scratch space","ImmediateNote":"{uint8 position in scratch space to store to}","Groups":["Loading Values"]},{"Opcode":54,"Name":"txna","Returns":".","Size":3,"ArgEnum":["ApplicationArgs","Accounts","Assets","Applications","Logs"],"ArgEnumTypes":"BBUUB","Doc":"Ith value of the array field F of the current transaction","ImmediateNote":"{uint8 transaction field index} {uint8 transaction field array index}","Groups":["Loading Values"]},{"Opcode":55,"Name":"gtxna","Returns":".","Size":4,"ArgEnum":["ApplicationArgs","Accounts","Assets","Applications","Logs"],"ArgEnumTypes":"BBUUB","Doc":"Ith value of the array field F from the Tth transaction in the current group","ImmediateNote":"{uint8 transaction group index} {uint8 transaction field index} {uint8 transaction field array index}","Groups":["Loading Values"]},{"Opcode":56,"Name":"gtxns","Args":"U","Returns":".","Size":2,"ArgEnum":["Sender","Fee","FirstValid","FirstValidTime","LastValid","Note","Lease","Receiver","Amount","CloseRemainderTo","VotePK","SelectionPK","VoteFirst","VoteLast","VoteKeyDilution","Type","TypeEnum","XferAsset","AssetAmount","AssetSender","AssetReceiver","AssetCloseTo","GroupIndex","TxID","ApplicationID","OnCompletion","ApplicationArgs","NumAppArgs","Accounts","NumAccounts","ApprovalProgram","ClearStateProgram","RekeyTo","ConfigAsset","ConfigAssetTotal","ConfigAssetDecimals","ConfigAssetDefaultFrozen","ConfigAssetUnitName","ConfigAssetName","ConfigAssetURL","ConfigAssetMetadataHash","ConfigAssetManager","ConfigAssetReserve","ConfigAssetFreeze","ConfigAssetClawback","FreezeAsset","FreezeAssetAccount","FreezeAssetFrozen","Assets","NumAssets","Applications","NumApplications","GlobalNumUint","GlobalNumByteSlice","LocalNumUint","LocalNumByteSlice","ExtraProgramPages","Nonparticipation","Logs","NumLogs","CreatedAssetID","CreatedApplicationID","LastLog","StateProofPK"],"ArgEnumTypes":"BUUUUBBBUBBBUUUBUUUBBBUBUUBUBUBBBUUUUBBBBBBBBUBUUUUUUUUUUUBUUUBB","Doc":"field F of the Ath transaction in the current group","DocExtra":"for notes on transaction fields available, see `txn`. If top of stack is _i_, `gtxns field` is equivalent to `gtxn _i_ field`. gtxns exists so that _i_ can be calculated, often based on the index of the current transaction.","ImmediateNote":"{uint8 transaction field index}","Groups":["Loading Values"]},{"Opcode":57,"Name":"gtxnsa","Args":"U","Returns":".","Size":3,"ArgEnum":["ApplicationArgs","Accounts","Assets","Applications","Logs"],"ArgEnumTypes":"BBUUB","Doc":"Ith value of the array field F from the Ath transaction in the current group","ImmediateNote":"{uint8 transaction field index} {uint8 transaction field array index}","Groups":["Loading Values"]},{"Opcode":58,"Name":"gload","Returns":".","Size":3,"Doc":"Ith scratch space value of the Tth transaction in the current group","DocExtra":"`gload` fails unless the requested transaction is an ApplicationCall and T \u003c GroupIndex.","ImmediateNote":"{uint8 transaction group index} {uint8 position in scratch space to load from}","Groups":["Loading Values"]},{"Opcode":59,"Name":"gloads","Args":"U","Returns":".","Size":2,"Doc":"Ith scratch space value of the Ath transaction in the current group","DocExtra":"`gloads` fails unless the requested transaction is an ApplicationCall and A \u003c GroupIndex.","ImmediateNote":"{uint8 position in scratch space to load from}","Groups":["Loading Values"]},{"Opcode":60,"Name":"gaid","Returns":"U","Size":2,"Doc":"ID of the asset or application created in the Tth transaction of the current group","DocExtra":"`gaid` fails unless the requested transaction created an asset or application and T \u003c GroupIndex.","ImmediateNote":"{uint8 transaction group index}","Groups":["Loading Values"]},{"Opcode":61,"Name":"gaids","Args":"U","Returns":"U","Size":1,"Doc":"ID of the asset or application created in the Ath transaction of the current group","DocExtra":"`gaids` fails unless the requested transaction created an asset or application and A \u003c GroupIndex.","Groups":["Loading Values"]},{"Opcode":62,"Name":"loads","Args":"U","Returns":".","Size":1,"Doc":"Ath scratch space value. All scratch spaces are 0 at program start.","Groups":["Loading Values"]},{"Opcode":63,"Name":"stores","Args":"U.","Size":1,"Doc":"store B to the Ath scratch space","Groups":["Loading Values"]},{"Opcode":64,"Name":"bnz","Args":"U","Size":3,"Doc":"branch to TARGET if value A is not zero","DocExtra":"The `bnz` instruction opcode 0x40 is followed by two immediate data bytes which are a high byte first and low byte second which together form a 16 bit offset which the instruction may branch to. For a bnz instruction at `pc`, if the last element of the stack is not zero then branch to instruction at `pc + 3 + N`, else proceed to next instruction at `pc + 3`. Branch targets must be aligned instructions. (e.g. Branching to the second byte of a 2 byte op will be rejected.) Starting at v4, the offset is treated as a signed 16 bit integer allowing for backward branches and looping. In prior version (v1 to v3), branch offsets are limited to forward branches only, 0-0x7fff.\n\nAt v2 it became allowed to branch to the end of the program exactly after the last instruction: bnz to byte N (with 0-indexing) was illegal for a TEAL program with N bytes before v2, and is legal after it. This change eliminates the need for a last instruction of no-op as a branch target at the end. (Branching beyond the end--in other words, to a byte larger than N--is still illegal and will cause the program to fail.)","ImmediateNote":"{int16 branch offset, big endian}","Groups":["Flow Control"]},{"Opcode":65,"Name":"bz","Args":"U","Size":3,"Doc":"branch to TARGET if value A is zero","DocExtra":"See `bnz` for details on how branches work. `bz` inverts the behavior of `bnz`.","ImmediateNote":"{int16 branch offset, big endian}","Groups":["Flow Control"]},{"Opcode":66,"Name":"b","Size":3,"Doc":"branch unconditionally to TARGET","DocExtra":"See `bnz` for details on how branches work. `b` always jumps to the offset.","ImmediateNote":"{int16 branch offset, big endian}","Groups":["Flow Control"]},{"Opcode":67,"Name":"return","Args":"U","Size":1,"Doc":"use A as success value; end","Groups":["Flow Control"]},{"Opcode":68,"Name":"assert","Args":"U","Size":1,"Doc":"immediately fail unless A is a non-zero number","Groups":["Flow Control"]},{"Opcode":72,"Name":"pop","Args":".","Size":1,"Doc":"discard A","Groups":["Flow Control"]},{"Opcode":73,"Name":"dup","Args":".","Returns":"..","Size":1,"Doc":"duplicate A","Groups":["Flow Control"]},{"Opcode":74,"Name":"dup2","Args":"..","Returns":"....","Size":1,"Doc":"duplicate A and B","Groups":["Flow Control"]},{"Opcode":75,"Name":"dig","Args":".","Returns":"..","Size":2,"Doc":"Nth value from the top of the stack. dig 0 is equivalent to dup","ImmediateNote":"{uint8 depth}","Groups":["Flow Control"]},{"Opcode":76,"Name":"swap","Args":"..","Returns":"..","Size":1,"Doc":"swaps A and B on stack","Groups":["Flow Control"]},{"Opcode":77,"Name":"select","Args":"..U","Returns":".","Size":1,"Doc":"selects one of two values based on top-of-stack: B if C != 0, else A","Groups":["Flow Control"]},{"Opcode":78,"Name":"cover","Args":".","Returns":".","Size":2,"Doc":"remove top of stack, and place it deeper in the stack such that N elements are above it. Fails if stack depth \u003c= N.","ImmediateNote":"{uint8 depth}","Groups":["Flow Control"]},{"Opcode":79,"Name":"uncover","Args":".","Returns":".","Size":2,"Doc":"remove the value at depth N in the stack and shift above items down so the Nth deep value is on top of the stack. Fails if stack depth \u003c= N.","ImmediateNote":"{uint8 depth}","Groups":["Flow Control"]},{"Opcode":80,"Name":"concat","Args":"BB","Returns":"B","Size":1,"Doc":"join A and B","DocExtra":"`concat` fails if the result would be greater than 4096 bytes.","Groups":["Arithmetic"]},{"Opcode":81,"Name":"substring","Args":"B","Returns":"B","Size":3,"Doc":"A range of bytes from A starting at S up to but not including E. If E \u003c S, or either is larger than the array length, the program fails","ImmediateNote":"{uint8 start position} {uint8 end position}","Groups":["Byte Array Manipulation"]},{"Opcode":82,"Name":"substring3","Args":"BUU","Returns":"B","Size":1,"Doc":"A range of bytes from A starting at B up to but not including C. If C \u003c B, or either is larger than the array length, the program fails","Groups":["Byte Array Manipulation"]},{"Opcode":83,"Name":"getbit","Args":".U","Returns":"U","Size":1,"Doc":"Bth bit of (byte-array or integer) A. If B is greater than or equal to the bit length of the value (8*byte length), the program fails","DocExtra":"see explanation of bit ordering in setbit","Groups":["Arithmetic"]},{"Opcode":84,"Name":"setbit","Args":".UU","Returns":".","Size":1,"Doc":"Copy of (byte-array or integer) A, with the Bth bit set to (0 or 1) C. If B is greater than or equal to the bit length of the value (8*byte length), the program fails","DocExtra":"When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on the integer 0 yields 8, or 2^3. When A is a byte array, index 0 is the leftmost bit of the leftmost byte. Setting bits 0 through 11 to 1 in a 4-byte-array of 0s yields the byte array 0xfff00000. Setting bit 3 to 1 on the 1-byte-array 0x00 yields the byte array 0x10.","Groups":["Arithmetic"]},{"Opcode":85,"Name":"getbyte","Args":"BU","Returns":"U","Size":1,"Doc":"Bth byte of A, as an integer. If B is greater than or equal to the array length, the program fails","Groups":["Arithmetic"]},{"Opcode":86,"Name":"setbyte","Args":"BUU","Returns":"B","Size":1,"Doc":"Copy of A with the Bth byte set to small integer (between 0..255) C. If B is greater than or equal to the array length, the program fails","Groups":["Arithmetic"]},{"Opcode":87,"Name":"extract","Args":"B","Returns":"B","Size":3,"Doc":"A range of bytes from A starting at S up to but not including S+L. If L is 0, then extract to the end of the string. If S or S+L is larger than the array length, the program fails","ImmediateNote":"{uint8 start position} {uint8 length}","Groups":["Byte Array Manipulation"]},{"Opcode":88,"Name":"extract3","Args":"BUU","Returns":"B","Size":1,"Doc":"A range of bytes from A starting at B up to but not including B+C. If B+C is larger than the array length, the program fails","Groups":["Byte Array Manipulation"]},{"Opcode":89,"Name":"extract_uint16","Args":"BU","Returns":"U","Size":1,"Doc":"A uint16 formed from a range of big-endian bytes from A starting at B up to but not including B+2. If B+2 is larger than the array length, the program fails","Groups":["Byte Array Manipulation"]},{"Opcode":90,"Name":"extract_uint32","Args":"BU","Returns":"U","Size":1,"Doc":"A uint32 formed from a range of big-endian bytes from A starting at B up to but not including B+4. If B+4 is larger than the array length, the program fails","Groups":["Byte Array Manipulation"]},{"Opcode":91,"Name":"extract_uint64","Args":"BU","Returns":"U","Size":1,"Doc":"A uint64 formed from a range of big-endian bytes from A starting at B up to but not including B+8. If B+8 is larger than the array length, the program fails","Groups":["Byte Array Manipulation"]},{"Opcode":92,"Name":"base64_decode","Args":"B","Returns":"B","Size":2,"Doc":"decode A which was base64-encoded using _encoding_ E. Fail if A is not base64 encoded with encoding E","DocExtra":"Decodes A using the base64 encoding E. Specify the encoding with an immediate arg either as URL and Filename Safe (`URLEncoding`) or Standard (`StdEncoding`). See \u003ca href=\"https://rfc-editor.org/rfc/rfc4648.html#section-4\"\u003eRFC 4648\u003c/a\u003e (sections 4 and 5). It is assumed that the encoding ends with the exact number of `=` padding characters as required by the RFC. When padding occurs, any unused pad bits in the encoding must be set to zero or the decoding will fail. The special cases of `\\n` and `\\r` are allowed but completely ignored. An error will result when attempting to decode a string with a character that is not in the encoding alphabet or not one of `=`, `\\r`, or `\\n`.","ImmediateNote":"{uint8 encoding index}","Groups":["Byte Array Manipulation"]},{"Opcode":93,"Name":"json_ref","Args":"BB","Returns":".","Size":2,"Doc":"return key B's value from a [valid](jsonspec.md) utf-8 encoded json object A","DocExtra":"specify the return type with an immediate arg either as JSONUint64 or JSONString or JSONObject.","ImmediateNote":"{string return type}","Groups":["Byte Array Manipulation"]},{"Opcode":96,"Name":"balance","Args":".","Returns":"U","Size":1,"Doc":"get balance for account A, in microalgos. The balance is observed after the effects of previous transactions in the group, and after the fee for the current transaction is deducted.","DocExtra":"params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: value.","Groups":["State Access"]},{"Opcode":97,"Name":"app_opted_in","Args":".U","Returns":"U","Size":1,"Doc":"1 if account A is opted in to application B, else 0","DocExtra":"params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: 1 if opted in and 0 otherwise.","Groups":["State Access"]},{"Opcode":98,"Name":"app_local_get","Args":".B","Returns":".","Size":1,"Doc":"local state of the key B in the current application in account A","DocExtra":"params: Txn.Accounts offset (or, since v4, an _available_ account address), state key. Return: value. The value is zero (of type uint64) if the key does not exist.","Groups":["State Access"]},{"Opcode":99,"Name":"app_local_get_ex","Args":".UB","Returns":".U","Size":1,"Doc":"X is the local state of application B, key C in account A. Y is 1 if key existed, else 0","DocExtra":"params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset), state key. Return: did_exist flag (top of the stack, 1 if the application and key existed and 0 otherwise), value. The value is zero (of type uint64) if the key does not exist.","Groups":["State Access"]},{"Opcode":100,"Name":"app_global_get","Args":"B","Returns":".","Size":1,"Doc":"global state of the key A in the current application","DocExtra":"params: state key. Return: value. The value is zero (of type uint64) if the key does not exist.","Groups":["State Access"]},{"Opcode":101,"Name":"app_global_get_ex","Args":"UB","Returns":".U","Size":1,"Doc":"X is the global state of application A, key B. Y is 1 if key existed, else 0","DocExtra":"params: Txn.ForeignApps offset (or, since v4, an _available_ application id), state key. Return: did_exist flag (top of the stack, 1 if the application and key existed and 0 otherwise), value. The value is zero (of type uint64) if the key does not exist.","Groups":["State Access"]},{"Opcode":102,"Name":"app_local_put","Args":".B.","Size":1,"Doc":"write C to key B in account A's local state of the current application","DocExtra":"params: Txn.Accounts offset (or, since v4, an _available_ account address), state key, value.","Groups":["State Access"]},{"Opcode":103,"Name":"app_global_put","Args":"B.","Size":1,"Doc":"write B to key A in the global state of the current application","Groups":["State Access"]},{"Opcode":104,"Name":"app_local_del","Args":".B","Size":1,"Doc":"delete key B from account A's local state of the current application","DocExtra":"params: Txn.Accounts offset (or, since v4, an _available_ account address), state key.\n\nDeleting a key which is already absent has no effect on the application local state. (In particular, it does _not_ cause the program to fail.)","Groups":["State Access"]},{"Opcode":105,"Name":"app_global_del","Args":"B","Size":1,"Doc":"delete key A from the global state of the current application","DocExtra":"params: state key.\n\nDeleting a key which is already absent has no effect on the application global state. (In particular, it does _not_ cause the program to fail.)","Groups":["State Access"]},{"Opcode":112,"Name":"asset_holding_get","Args":".U","Returns":".U","Size":2,"ArgEnum":["AssetBalance","AssetFrozen"],"ArgEnumTypes":"UU","Doc":"X is field F from account A's holding of asset B. Y is 1 if A is opted into B, else 0","DocExtra":"params: Txn.Accounts offset (or, since v4, an _available_ address), asset id (or, since v4, a Txn.ForeignAssets offset). Return: did_exist flag (1 if the asset existed and 0 otherwise), value.","ImmediateNote":"{uint8 asset holding field index}","Groups":["State Access"]},{"Opcode":113,"Name":"asset_params_get","Args":"U","Returns":".U","Size":2,"ArgEnum":["AssetTotal","AssetDecimals","AssetDefaultFrozen","AssetUnitName","AssetName","AssetURL","AssetMetadataHash","AssetManager","AssetReserve","AssetFreeze","AssetClawback","AssetCreator"],"ArgEnumTypes":"UUUBBBBBBBBB","Doc":"X is field F from asset A. Y is 1 if A exists, else 0","DocExtra":"params: Txn.ForeignAssets offset (or, since v4, an _available_ asset id. Return: did_exist flag (1 if the asset existed and 0 otherwise), value.","ImmediateNote":"{uint8 asset params field index}","Groups":["State Access"]},{"Opcode":114,"Name":"app_params_get","Args":"U","Returns":".U","Size":2,"ArgEnum":["AppApprovalProgram","AppClearStateProgram","AppGlobalNumUint","AppGlobalNumByteSlice","AppLocalNumUint","AppLocalNumByteSlice","AppExtraProgramPages","AppCreator","AppAddress"],"ArgEnumTypes":"BBUUUUUBB","Doc":"X is field F from app A. Y is 1 if A exists, else 0","DocExtra":"params: Txn.ForeignApps offset or an _available_ app id. Return: did_exist flag (1 if the application existed and 0 otherwise), value.","ImmediateNote":"{uint8 app params field index}","Groups":["State Access"]},{"Opcode":115,"Name":"acct_params_get","Args":".","Returns":".U","Size":2,"Doc":"X is field F from account A. Y is 1 if A owns positive algos, else 0","ImmediateNote":"{uint8 account params field index}","Groups":["State Access"]},{"Opcode":120,"Name":"min_balance","Args":".","Returns":"U","Size":1,"Doc":"get minimum required balance for account A, in microalgos. Required balance is affected by [ASA](https://developer.algorand.org/docs/features/asa/#assets-overview) and [App](https://developer.algorand.org/docs/features/asc1/stateful/#minimum-balance-requirement-for-a-smart-contract) usage. When creating or opting into an app, the minimum balance grows before the app code runs, therefore the increase is visible there. When deleting or closing out, the minimum balance decreases after the app executes.","DocExtra":"params: Txn.Accounts offset (or, since v4, an _available_ account address), _available_ application id (or, since v4, a Txn.ForeignApps offset). Return: value.","Groups":["State Access"]},{"Opcode":128,"Name":"pushbytes","Returns":"B","Size":0,"Doc":"immediate BYTES","DocExtra":"pushbytes args are not added to the bytecblock during assembly processes","ImmediateNote":"{varuint length} {bytes}","Groups":["Loading Values"]},{"Opcode":129,"Name":"pushint","Returns":"U","Size":0,"Doc":"immediate UINT","DocExtra":"pushint args are not added to the intcblock during assembly processes","ImmediateNote":"{varuint int}","Groups":["Loading Values"]},{"Opcode":132,"Name":"ed25519verify_bare","Args":"BBB","Returns":"U","Size":1,"Doc":"for (data A, signature B, pubkey C) verify the signature of the data against the pubkey =\u003e {0 or 1}","Groups":["Arithmetic"]},{"Opcode":136,"Name":"callsub","Size":3,"Doc":"branch unconditionally to TARGET, saving the next instruction on the call stack","DocExtra":"The call stack is separate from the data stack. Only `callsub` and `retsub` manipulate it.","ImmediateNote":"{int16 branch offset, big endian}","Groups":["Flow Control"]},{"Opcode":137,"Name":"retsub","Size":1,"Doc":"pop the top instruction from the call stack and branch to it","DocExtra":"The call stack is separate from the data stack. Only `callsub` and `retsub` manipulate it.","Groups":["Flow Control"]},{"Opcode":144,"Name":"shl","Args":"UU","Returns":"U","Size":1,"Doc":"A times 2^B, modulo 2^64","Groups":["Arithmetic"]},{"Opcode":145,"Name":"shr","Args":"UU","Returns":"U","Size":1,"Doc":"A divided by 2^B","Groups":["Arithmetic"]},{"Opcode":146,"Name":"sqrt","Args":"U","Returns":"U","Size":1,"Doc":"The largest integer I such that I^2 \u003c= A","Groups":["Arithmetic"]},{"Opcode":147,"Name":"bitlen","Args":".","Returns":"U","Size":1,"Doc":"The highest set bit in A. If A is a byte-array, it is interpreted as a big-endian unsigned integer. bitlen of 0 is 0, bitlen of 8 is 4","DocExtra":"bitlen interprets arrays as big-endian integers, unlike setbit/getbit","Groups":["Arithmetic"]},{"Opcode":148,"Name":"exp","Args":"UU","Returns":"U","Size":1,"Doc":"A raised to the Bth power. Fail if A == B == 0 and on overflow","Groups":["Arithmetic"]},{"Opcode":149,"Name":"expw","Args":"UU","Returns":"UU","Size":1,"Doc":"A raised to the Bth power as a 128-bit result in two uint64s. X is the high 64 bits, Y is the low. Fail if A == B == 0 or if the results exceeds 2^128-1","Groups":["Arithmetic"]},{"Opcode":150,"Name":"bsqrt","Args":"B","Returns":"B","Size":1,"Doc":"The largest integer I such that I^2 \u003c= A. A and I are interpreted as big-endian unsigned integers","Groups":["Byte Array Arithmetic"]},{"Opcode":151,"Name":"divw","Args":"UUU","Returns":"U","Size":1,"Doc":"A,B / C. Fail if C == 0 or if result overflows.","DocExtra":"The notation A,B indicates that A and B are interpreted as a uint128 value, with A as the high uint64 and B the low.","Groups":["Arithmetic"]},{"Opcode":152,"Name":"sha3_256","Args":"B","Returns":"B","Size":1,"Doc":"SHA3_256 hash of value A, yields [32]byte","Groups":["Arithmetic"]},{"Opcode":160,"Name":"b+","Args":"BB","Returns":"B","Size":1,"Doc":"A plus B. A and B are interpreted as big-endian unsigned integers","Groups":["Byte Array Arithmetic"]},{"Opcode":161,"Name":"b-","Args":"BB","Returns":"B","Size":1,"Doc":"A minus B. A and B are interpreted as big-endian unsigned integers. Fail on underflow.","Groups":["Byte Array Arithmetic"]},{"Opcode":162,"Name":"b/","Args":"BB","Returns":"B","Size":1,"Doc":"A divided by B (truncated division). A and B are interpreted as big-endian unsigned integers. Fail if B is zero.","Groups":["Byte Array Arithmetic"]},{"Opcode":163,"Name":"b*","Args":"BB","Returns":"B","Size":1,"Doc":"A times B. A and B are interpreted as big-endian unsigned integers.","Groups":["Byte Array Arithmetic"]},{"Opcode":164,"Name":"b\u003c","Args":"BB","Returns":"U","Size":1,"Doc":"1 if A is less than B, else 0. A and B are interpreted as big-endian unsigned integers","Groups":["Byte Array Arithmetic"]},{"Opcode":165,"Name":"b\u003e","Args":"BB","Returns":"U","Size":1,"Doc":"1 if A is greater than B, else 0. A and B are interpreted as big-endian unsigned integers","Groups":["Byte Array Arithmetic"]},{"Opcode":166,"Name":"b\u003c=","Args":"BB","Returns":"U","Size":1,"Doc":"1 if A is less than or equal to B, else 0. A and B are interpreted as big-endian unsigned integers","Groups":["Byte Array Arithmetic"]},{"Opcode":167,"Name":"b\u003e=","Args":"BB","Returns":"U","Size":1,"Doc":"1 if A is greater than or equal to B, else 0. A and B are interpreted as big-endian unsigned integers","Groups":["Byte Array Arithmetic"]},{"Opcode":168,"Name":"b==","Args":"BB","Returns":"U","Size":1,"Doc":"1 if A is equal to B, else 0. A and B are interpreted as big-endian unsigned integers","Groups":["Byte Array Arithmetic"]},{"Opcode":169,"Name":"b!=","Args":"BB","Returns":"U","Size":1,"Doc":"0 if A is equal to B, else 1. A and B are interpreted as big-endian unsigned integers","Groups":["Byte Array Arithmetic"]},{"Opcode":170,"Name":"b%","Args":"BB","Returns":"B","Size":1,"Doc":"A modulo B. A and B are interpreted as big-endian unsigned integers. Fail if B is zero.","Groups":["Byte Array Arithmetic"]},{"Opcode":171,"Name":"b|","Args":"BB","Returns":"B","Size":1,"Doc":"A bitwise-or B. A and B are zero-left extended to the greater of their lengths","Groups":["Byte Array Logic"]},{"Opcode":172,"Name":"b\u0026","Args":"BB","Returns":"B","Size":1,"Doc":"A bitwise-and B. A and B are zero-left extended to the greater of their lengths","Groups":["Byte Array Logic"]},{"Opcode":173,"Name":"b^","Args":"BB","Returns":"B","Size":1,"Doc":"A bitwise-xor B. A and B are zero-left extended to the greater of their lengths","Groups":["Byte Array Logic"]},{"Opcode":174,"Name":"b~","Args":"B","Returns":"B","Size":1,"Doc":"A with all bits inverted","Groups":["Byte Array Logic"]},{"Opcode":175,"Name":"bzero","Args":"U","Returns":"B","Size":1,"Doc":"zero filled byte-array of length A","Groups":["Loading Values"]},{"Opcode":176,"Name":"log","Args":"B","Size":1,"Doc":"write A to log state of the current application","DocExtra":"`log` fails if called more than MaxLogCalls times in a program, or if the sum of logged bytes exceeds 1024 bytes.","Groups":["State Access"]},{"Opcode":177,"Name":"itxn_begin","Size":1,"Doc":"begin preparation of a new inner transaction in a new transaction group","DocExtra":"`itxn_begin` initializes Sender to the application address; Fee to the minimum allowable, taking into account MinTxnFee and credit from overpaying in earlier transactions; FirstValid/LastValid to the values in the invoking transaction, and all other fields to zero or empty values.","Groups":["Inner Transactions"]},{"Opcode":178,"Name":"itxn_field","Args":".","Size":2,"ArgEnum":["Sender","Fee","FirstValid","FirstValidTime","LastValid","Note","Lease","Receiver","Amount","CloseRemainderTo","VotePK","SelectionPK","VoteFirst","VoteLast","VoteKeyDilution","Type","TypeEnum","XferAsset","AssetAmount","AssetSender","AssetReceiver","AssetCloseTo","GroupIndex","TxID","ApplicationID","OnCompletion","ApplicationArgs","NumAppArgs","Accounts","NumAccounts","ApprovalProgram","ClearStateProgram","RekeyTo","ConfigAsset","ConfigAssetTotal","ConfigAssetDecimals","ConfigAssetDefaultFrozen","ConfigAssetUnitName","ConfigAssetName","ConfigAssetURL","ConfigAssetMetadataHash","ConfigAssetManager","ConfigAssetReserve","ConfigAssetFreeze","ConfigAssetClawback","FreezeAsset","FreezeAssetAccount","FreezeAssetFrozen","Assets","NumAssets","Applications","NumApplications","GlobalNumUint","GlobalNumByteSlice","LocalNumUint","LocalNumByteSlice","ExtraProgramPages","Nonparticipation","Logs","NumLogs","CreatedAssetID","CreatedApplicationID","LastLog","StateProofPK"],"ArgEnumTypes":"BUUUUBBBUBBBUUUBUUUBBBUBUUBUBUBBBUUUUBBBBBBBBUBUUUUUUUUUUUBUUUBB","Doc":"set field F of the current inner transaction to A","DocExtra":"`itxn_field` fails if A is of the wrong type for F, including a byte array of the wrong size for use as an address when F is an address field. `itxn_field` also fails if A is an account, asset, or app that is not _available_, or an attempt is made extend an array field beyond the limit imposed by consensus parameters. (Addresses set into asset params of acfg transactions need not be _available_.)","ImmediateNote":"{uint8 transaction field index}","Groups":["Inner Transactions"]},{"Opcode":179,"Name":"itxn_submit","Size":1,"Doc":"execute the current inner transaction group. Fail if executing this group would exceed the inner transaction limit, or if any transaction in the group fails.","DocExtra":"`itxn_submit` resets the current transaction so that it can not be resubmitted. A new `itxn_begin` is required to prepare another inner transaction.","Groups":["Inner Transactions"]},{"Opcode":180,"Name":"itxn","Returns":".","Size":2,"ArgEnum":["Sender","Fee","FirstValid","FirstValidTime","LastValid","Note","Lease","Receiver","Amount","CloseRemainderTo","VotePK","SelectionPK","VoteFirst","VoteLast","VoteKeyDilution","Type","TypeEnum","XferAsset","AssetAmount","AssetSender","AssetReceiver","AssetCloseTo","GroupIndex","TxID","ApplicationID","OnCompletion","ApplicationArgs","NumAppArgs","Accounts","NumAccounts","ApprovalProgram","ClearStateProgram","RekeyTo","ConfigAsset","ConfigAssetTotal","ConfigAssetDecimals","ConfigAssetDefaultFrozen","ConfigAssetUnitName","ConfigAssetName","ConfigAssetURL","ConfigAssetMetadataHash","ConfigAssetManager","ConfigAssetReserve","ConfigAssetFreeze","ConfigAssetClawback","FreezeAsset","FreezeAssetAccount","FreezeAssetFrozen","Assets","NumAssets","Applications","NumApplications","GlobalNumUint","GlobalNumByteSlice","LocalNumUint","LocalNumByteSlice","ExtraProgramPages","Nonparticipation","Logs","NumLogs","CreatedAssetID","CreatedApplicationID","LastLog","StateProofPK"],"ArgEnumTypes":"BUUUUBBBUBBBUUUBUUUBBBUBUUBUBUBBBUUUUBBBBBBBBUBUUUUUUUUUUUBUUUBB","Doc":"field F of the last inner transaction","ImmediateNote":"{uint8 transaction field index}","Groups":["Inner Transactions"]},{"Opcode":181,"Name":"itxna","Returns":".","Size":3,"ArgEnum":["ApplicationArgs","Accounts","Assets","Applications","Logs"],"ArgEnumTypes":"BBUUB","Doc":"Ith value of the array field F of the last inner transaction","ImmediateNote":"{uint8 transaction field index} {uint8 transaction field array index}","Groups":["Inner Transactions"]},{"Opcode":182,"Name":"itxn_next","Size":1,"Doc":"begin preparation of a new inner transaction in the same transaction group","DocExtra":"`itxn_next` initializes the transaction exactly as `itxn_begin` does","Groups":["Inner Transactions"]},{"Opcode":183,"Name":"gitxn","Returns":".","Size":3,"ArgEnum":["Sender","Fee","FirstValid","FirstValidTime","LastValid","Note","Lease","Receiver","Amount","CloseRemainderTo","VotePK","SelectionPK","VoteFirst","VoteLast","VoteKeyDilution","Type","TypeEnum","XferAsset","AssetAmount","AssetSender","AssetReceiver","AssetCloseTo","GroupIndex","TxID","ApplicationID","OnCompletion","ApplicationArgs","NumAppArgs","Accounts","NumAccounts","ApprovalProgram","ClearStateProgram","RekeyTo","ConfigAsset","ConfigAssetTotal","ConfigAssetDecimals","ConfigAssetDefaultFrozen","ConfigAssetUnitName","ConfigAssetName","ConfigAssetURL","ConfigAssetMetadataHash","ConfigAssetManager","ConfigAssetReserve","ConfigAssetFreeze","ConfigAssetClawback","FreezeAsset","FreezeAssetAccount","FreezeAssetFrozen","Assets","NumAssets","Applications","NumApplications","GlobalNumUint","GlobalNumByteSlice","LocalNumUint","LocalNumByteSlice","ExtraProgramPages","Nonparticipation","Logs","NumLogs","CreatedAssetID","CreatedApplicationID","LastLog","StateProofPK"],"ArgEnumTypes":"BUUUUBBBUBBBUUUBUUUBBBUBUUBUBUBBBUUUUBBBBBBBBUBUUUUUUUUUUUBUUUBB","Doc":"field F of the Tth transaction in the last inner group submitted","ImmediateNote":"{uint8 transaction group index} {uint8 transaction field index}","Groups":["Inner Transactions"]},{"Opcode":184,"Name":"gitxna","Returns":".","Size":4,"ArgEnum":["ApplicationArgs","Accounts","Assets","Applications","Logs"],"ArgEnumTypes":"BBUUB","Doc":"Ith value of the array field F from the Tth transaction in the last inner group submitted","ImmediateNote":"{uint8 transaction group index} {uint8 transaction field index} {uint8 transaction field array index}","Groups":["Inner Transactions"]},{"Opcode":192,"Name":"txnas","Args":"U","Returns":".","Size":2,"ArgEnum":["ApplicationArgs","Accounts","Assets","Applications","Logs"],"ArgEnumTypes":"BBUUB","Doc":"Ath value of the array field F of the current transaction","ImmediateNote":"{uint8 transaction field index}","Groups":["Loading Values"]},{"Opcode":193,"Name":"gtxnas","Args":"U","Returns":".","Size":3,"ArgEnum":["ApplicationArgs","Accounts","Assets","Applications","Logs"],"ArgEnumTypes":"BBUUB","Doc":"Ath value of the array field F from the Tth transaction in the current group","ImmediateNote":"{uint8 transaction group index} {uint8 transaction field index}","Groups":["Loading Values"]},{"Opcode":194,"Name":"gtxnsas","Args":"UU","Returns":".","Size":2,"ArgEnum":["ApplicationArgs","Accounts","Assets","Applications","Logs"],"ArgEnumTypes":"BBUUB","Doc":"Bth value of the array field F from the Ath transaction in the current group","ImmediateNote":"{uint8 transaction field index}","Groups":["Loading Values"]},{"Opcode":195,"Name":"args","Args":"U","Returns":"B","Size":1,"Doc":"Ath LogicSig argument","Groups":["Loading Values"]},{"Opcode":196,"Name":"gloadss","Args":"UU","Returns":".","Size":1,"Doc":"Bth scratch space value of the Ath transaction in the current group","Groups":["Loading Values"]},{"Opcode":197,"Name":"itxnas","Args":"U","Returns":".","Size":2,"Doc":"Ath value of the array field F of the last inner transaction","ImmediateNote":"{uint8 transaction field index}","Groups":["Inner Transactions"]},{"Opcode":198,"Name":"gitxnas","Args":"U","Returns":".","Size":3,"Doc":"Ath value of the array field F from the Tth transaction in the last inner group submitted","ImmediateNote":"{uint8 transaction group index} {uint8 transaction field index}","Groups":["Inner Transactions"]}]} diff --git a/data/transactions/logic/teal.tmLanguage.json b/data/transactions/logic/teal.tmLanguage.json new file mode 100644 index 0000000000..8502f868dd --- /dev/null +++ b/data/transactions/logic/teal.tmLanguage.json @@ -0,0 +1,136 @@ +{ + "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", + "name": "Algorand TEAL", + "patterns": [ + { + "include": "#invalid" + }, + { + "include": "#comments" + }, + { + "include": "#strings" + }, + { + "include": "#literals" + }, + { + "include": "#labels" + }, + { + "include": "#keywords" + }, + { + "include": "#pragmas" + } + ], + "repository": { + "comments": { + "name": "comment.line.double-slash.teal", + "begin": "//", + "end": "$" + }, + "invalid": { + "patterns": [ + { + "name": "invalid.illegal.teal", + "match": "^\\s+.*$" + } + ] + }, + "keywords": { + "patterns": [ + { + "match": "\\b(base64|b64|base32|b32)(?:\\(|\\s+)([a-zA-Z0-9\\+\\/\\=]+)(?:\\)|\\s?|$)", + "captures": { + "1": { + "name": "support.class.teal" + }, + "2": { + "name": "string.quoted.triple.teal" + } + } + }, + { + "match": "^(addr)\\s+([A-Z2-7\\=]+)", + "captures": { + "1": { + "name": "keyword.other.teal" + }, + "2": { + "name": "string.unquoted.teal" + } + } + }, + { + "name": "keyword.other.teal", + "match": "^(int|byte|addr|intcblock|intc|intc_0|intc_1|intc_2|intc_3|pushint|bytecblock|bytec|bytec_0|bytec_1|bytec_2|bytec_3|pushbytes|bzero|arg|arg_0|arg_1|arg_2|arg_3|args|txn|gtxn|txna|txnas|gtxna|gtxnas|gtxns|gtxnsa|gtxnsas|global|load|loads|store|stores|gload|gloads|gloadss|gaid|gaids)\\b" + }, + { + "name": "keyword.control.teal", + "match": "^(err|bnz|bz|b|return|pop|dup|dup2|dig|cover|uncover|swap|select|assert|callsub|retsub)\\b" + }, + { + "name": "keyword.other.unit.teal", + "match": "^(balance|min_balance|app_opted_in|app_local_get|app_local_get_ex|app_global_get|app_global_get_ex|app_local_put|app_global_put|app_local_del|app_global_del|asset_holding_get|asset_params_get|app_params_get|acct_params_get|log)\\b" + }, + { + "name": "keyword.operator.teal", + "match": "^(b\\||b\u0026|b\\^|b\\~|itxn_begin|itxn_next|itxn_field|itxn_submit|itxn|itxna|itxnas|gitxn|gitxna|gitxnas|sha256|keccak256|sha512_256|sha3_256|ed25519verify|ed25519verify_bare|ecdsa_verify|ecdsa_pk_recover|ecdsa_pk_decompress|\\+|\\-|/|\\*|\\\u003c|\\\u003e|\\\u003c\\=|\\\u003e\\=|\u0026\u0026|\\|\\||shl|shr|sqrt|bitlen|exp|\\=\\=|\\!\\=|\\!|len|itob|btoi|%|\\||\u0026|\\^|\\~|mulw|addw|divw|divmodw|expw|getbit|setbit|getbyte|setbyte|concat|substring|substring3|extract|extract3|extract_uint16|extract_uint32|extract_uint64|base64_decode|json_ref|b\\+|b\\-|b/|b\\*|b\\\u003c|b\\\u003e|b\\\u003c\\=|b\\\u003e\\=|b\\=\\=|b\\!\\=|b%|bsqrt)\\b" + } + ] + }, + "labels": { + "patterns": [ + { + "name": "support.variable.teal", + "match": "^\\w+:.*$" + }, + { + "match": "\\b(?\u003c=b|bz|bnz)\\s+(\\w+)\\b", + "captures": { + "1": { + "name": "support.variable.teal" + } + } + } + ] + }, + "literals": { + "patterns": [ + { + "name": "constant.numeric.teal", + "match": "\\b([0-9]+)\\b" + }, + { + "name": "constant.numeric.teal", + "match": "\\b(?\u003c=int\\s+)(0x[0-9]+)\\b" + }, + { + "name": "string.quoted.double.teal", + "match": "\\b(?\u003c=byte\\s+)(0x[0-9]+)\\b" + }, + { + "name": "variable.parameter.teal", + "match": "\\b(unknown|pay|keyreg|acfg|axfer|afrz|appl|NoOp|OptIn|CloseOut|ClearState|UpdateApplication|DeleteApplication|Secp256k1|Secp256r1|Sender|Fee|FirstValid|FirstValidTime|LastValid|Note|Lease|Receiver|Amount|CloseRemainderTo|VotePK|SelectionPK|VoteFirst|VoteLast|VoteKeyDilution|Type|TypeEnum|XferAsset|AssetAmount|AssetSender|AssetReceiver|AssetCloseTo|GroupIndex|TxID|ApplicationID|OnCompletion|ApplicationArgs|NumAppArgs|Accounts|NumAccounts|ApprovalProgram|ClearStateProgram|RekeyTo|ConfigAsset|ConfigAssetTotal|ConfigAssetDecimals|ConfigAssetDefaultFrozen|ConfigAssetUnitName|ConfigAssetName|ConfigAssetURL|ConfigAssetMetadataHash|ConfigAssetManager|ConfigAssetReserve|ConfigAssetFreeze|ConfigAssetClawback|FreezeAsset|FreezeAssetAccount|FreezeAssetFrozen|Assets|NumAssets|Applications|NumApplications|GlobalNumUint|GlobalNumByteSlice|LocalNumUint|LocalNumByteSlice|ExtraProgramPages|Nonparticipation|Logs|NumLogs|CreatedAssetID|CreatedApplicationID|LastLog|StateProofPK|MinTxnFee|MinBalance|MaxTxnLife|ZeroAddress|GroupSize|LogicSigVersion|Round|LatestTimestamp|CurrentApplicationID|CreatorAddress|CurrentApplicationAddress|GroupID|OpcodeBudget|CallerApplicationID|CallerApplicationAddress|||||||||||||||||||||||||||ApplicationArgs||Accounts||||||||||||||||||||Assets||Applications||||||||Logs||||||URLEncoding|StdEncoding|JSONString|JSONUint64|JSONObject|AssetBalance|AssetFrozen|AssetTotal|AssetDecimals|AssetDefaultFrozen|AssetUnitName|AssetName|AssetURL|AssetMetadataHash|AssetManager|AssetReserve|AssetFreeze|AssetClawback|AssetCreator|AppApprovalProgram|AppClearStateProgram|AppGlobalNumUint|AppGlobalNumByteSlice|AppLocalNumUint|AppLocalNumByteSlice|AppExtraProgramPages|AppCreator|AppAddress|AcctBalance|AcctMinBalance|AcctAuthAddr)\\b" + } + ] + }, + "pragmas": { + "name": "support.function.teal", + "match": "^#pragma\\b.*$" + }, + "strings": { + "name": "string.quoted.double.teal", + "begin": "\"", + "end": "\"", + "patterns": [ + { + "name": "constant.character.escape.teal", + "match": "\\\\(x[0-9A-Fa-f]{2}|.|$)" + } + ] + } + }, + "scopeName": "source.teal" +} From d90e192ab593b205056007ef61dfcc686b7f353e Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 25 Mar 2022 14:59:19 -0400 Subject: [PATCH 6/9] CR tidying --- data/transactions/logic/assembler.go | 70 +--------------------------- 1 file changed, 2 insertions(+), 68 deletions(-) diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index b38d3be54e..925433c3f6 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -2254,8 +2254,8 @@ func disDefault(dis *disassembleState, spec *OpSpec) (string, error) { if end > uint64(len(dis.program)) || end < uint64(pc) { return "", fmt.Errorf("could not decode immediate %s for %s", imm.Name, spec.Name) } - bytes := dis.program[pc:end] - out += fmt.Sprintf("0x%s // %s", hex.EncodeToString(bytes), guessByteFormat(bytes)) + constant := dis.program[pc:end] + out += fmt.Sprintf("0x%s // %s", hex.EncodeToString(constant), guessByteFormat(constant)) pc = int(end) case immInts: intc, nextpc, err := parseIntcblock(dis.program, pc) @@ -2437,40 +2437,6 @@ func checkByteConstBlock(cx *EvalContext) error { return nil } -func disIntc(dis *disassembleState, spec *OpSpec) (string, error) { - lastIdx := dis.pc + spec.Details.Size - 1 - if len(dis.program) <= lastIdx { - missing := lastIdx - len(dis.program) + 1 - return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing) - } - dis.nextpc = dis.pc + spec.Details.Size - var suffix string - var b int - switch spec.Opcode { - case 0x22: - suffix = "_0" - b = 0 - case 0x23: - suffix = "_1" - b = 1 - case 0x24: - suffix = "_2" - b = 2 - case 0x25: - suffix = "_3" - b = 3 - case 0x21: - b = int(dis.program[dis.pc+1]) - suffix = fmt.Sprintf(" %d", b) - default: - return "", fmt.Errorf("disIntc on %v", spec) - } - if b < len(dis.intc) { - return fmt.Sprintf("intc%s // %d", suffix, dis.intc[b]), nil - } - return fmt.Sprintf("intc%s", suffix), nil -} - func allPrintableASCII(bytes []byte) bool { for _, b := range bytes { if b < 32 || b > 126 { @@ -2492,38 +2458,6 @@ func guessByteFormat(bytes []byte) string { return "0x" + hex.EncodeToString(bytes) } -func disBytec(dis *disassembleState, spec *OpSpec) (string, error) { - lastIdx := dis.pc + spec.Details.Size - 1 - if len(dis.program) <= lastIdx { - missing := lastIdx - len(dis.program) + 1 - return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing) - } - dis.nextpc = dis.pc + spec.Details.Size - var suffix string - var b int - switch spec.Opcode { - case 0x28: - suffix = "_0" - b = 0 - case 0x29: - suffix = "_1" - b = 1 - case 0x2a: - suffix = "_2" - b = 2 - case 0x2b: - suffix = "_3" - b = 3 - case 0x27: - b = int(dis.program[dis.pc+1]) - suffix = fmt.Sprintf(" %d", b) - } - if b < len(dis.bytec) { - return fmt.Sprintf("bytec%s // %s", suffix, guessByteFormat(dis.bytec[b])), nil - } - return fmt.Sprintf("bytec%s", suffix), nil -} - type disInfo struct { pcOffset []PCOffset hasStatefulOps bool From 13b24999a6930ccdfdd35b2cf126df9e023a18d1 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 25 Mar 2022 15:12:03 -0400 Subject: [PATCH 7/9] Never, ever, allow 0 cost opcodes. --- data/transactions/logic/eval.go | 3 +++ data/transactions/logic/opcodes.go | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 8b4c226449..6a03c30f9f 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -909,6 +909,9 @@ func (cx *EvalContext) step() error { if opcost == 0 { opcost = deets.costFunc(cx.program, cx.pc) } + if opcost == 0 { + return fmt.Errorf("%3d %s returned 0 cost", cx.pc, spec.Name) + } cx.cost += opcost if cx.PooledApplicationBudget != nil { *cx.PooledApplicationBudget -= opcost diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 3f9e4f4b07..d7fe9e2e54 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -131,7 +131,7 @@ func costByField(immediate string, group *FieldGroup, costs map[byte]int) opDeta if ok { return cost } - return 1 + return 0 // Will panic the AVM - implies a bad field is in the bytecode } return opd } From 2aef60ea50625922134b440ebed858325525e9b0 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 25 Mar 2022 17:24:12 -0400 Subject: [PATCH 8/9] deterministic tmlanguage --- cmd/opdoc/tmLanguage.go | 11 ++++++++++- data/transactions/logic/teal.tmLanguage.json | 12 ++++++------ 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/cmd/opdoc/tmLanguage.go b/cmd/opdoc/tmLanguage.go index d66e39e861..1ba6764b21 100644 --- a/cmd/opdoc/tmLanguage.go +++ b/cmd/opdoc/tmLanguage.go @@ -18,6 +18,7 @@ package main import ( "fmt" + "sort" "strings" "github.com/algorand/go-algorand/data/transactions/logic" @@ -160,7 +161,15 @@ func buildSyntaxHighlight() *tmLanguage { }, } var allArithmetics []string - for grp, names := range logic.OpGroups { + + var keys []string + for key := range logic.OpGroups { + keys = append(keys, key) + } + sort.Strings(keys) + for _, grp := range keys { + names := logic.OpGroups[grp] + sort.Strings(names) switch grp { case "Flow Control": keywords.Patterns = append(keywords.Patterns, pattern{ diff --git a/data/transactions/logic/teal.tmLanguage.json b/data/transactions/logic/teal.tmLanguage.json index 8502f868dd..5d5ee634ac 100644 --- a/data/transactions/logic/teal.tmLanguage.json +++ b/data/transactions/logic/teal.tmLanguage.json @@ -63,20 +63,20 @@ } }, { - "name": "keyword.other.teal", - "match": "^(int|byte|addr|intcblock|intc|intc_0|intc_1|intc_2|intc_3|pushint|bytecblock|bytec|bytec_0|bytec_1|bytec_2|bytec_3|pushbytes|bzero|arg|arg_0|arg_1|arg_2|arg_3|args|txn|gtxn|txna|txnas|gtxna|gtxnas|gtxns|gtxnsa|gtxnsas|global|load|loads|store|stores|gload|gloads|gloadss|gaid|gaids)\\b" + "name": "keyword.control.teal", + "match": "^(assert|b|bnz|bz|callsub|cover|dig|dup|dup2|err|pop|retsub|return|select|swap|uncover)\\b" }, { - "name": "keyword.control.teal", - "match": "^(err|bnz|bz|b|return|pop|dup|dup2|dig|cover|uncover|swap|select|assert|callsub|retsub)\\b" + "name": "keyword.other.teal", + "match": "^(int|byte|addr|arg|arg_0|arg_1|arg_2|arg_3|args|bytec|bytec_0|bytec_1|bytec_2|bytec_3|bytecblock|bzero|gaid|gaids|gload|gloads|gloadss|global|gtxn|gtxna|gtxnas|gtxns|gtxnsa|gtxnsas|intc|intc_0|intc_1|intc_2|intc_3|intcblock|load|loads|pushbytes|pushint|store|stores|txn|txna|txnas)\\b" }, { "name": "keyword.other.unit.teal", - "match": "^(balance|min_balance|app_opted_in|app_local_get|app_local_get_ex|app_global_get|app_global_get_ex|app_local_put|app_global_put|app_local_del|app_global_del|asset_holding_get|asset_params_get|app_params_get|acct_params_get|log)\\b" + "match": "^(acct_params_get|app_global_del|app_global_get|app_global_get_ex|app_global_put|app_local_del|app_local_get|app_local_get_ex|app_local_put|app_opted_in|app_params_get|asset_holding_get|asset_params_get|balance|log|min_balance)\\b" }, { "name": "keyword.operator.teal", - "match": "^(b\\||b\u0026|b\\^|b\\~|itxn_begin|itxn_next|itxn_field|itxn_submit|itxn|itxna|itxnas|gitxn|gitxna|gitxnas|sha256|keccak256|sha512_256|sha3_256|ed25519verify|ed25519verify_bare|ecdsa_verify|ecdsa_pk_recover|ecdsa_pk_decompress|\\+|\\-|/|\\*|\\\u003c|\\\u003e|\\\u003c\\=|\\\u003e\\=|\u0026\u0026|\\|\\||shl|shr|sqrt|bitlen|exp|\\=\\=|\\!\\=|\\!|len|itob|btoi|%|\\||\u0026|\\^|\\~|mulw|addw|divw|divmodw|expw|getbit|setbit|getbyte|setbyte|concat|substring|substring3|extract|extract3|extract_uint16|extract_uint32|extract_uint64|base64_decode|json_ref|b\\+|b\\-|b/|b\\*|b\\\u003c|b\\\u003e|b\\\u003c\\=|b\\\u003e\\=|b\\=\\=|b\\!\\=|b%|bsqrt)\\b" + "match": "^(\\!|\\!\\=|%|\u0026|\u0026\u0026|\\*|\\+|\\-|/|\\\u003c|\\\u003c\\=|\\=\\=|\\\u003e|\\\u003e\\=|\\^|addw|bitlen|btoi|concat|divmodw|divw|ecdsa_pk_decompress|ecdsa_pk_recover|ecdsa_verify|ed25519verify|ed25519verify_bare|exp|expw|getbit|getbyte|itob|keccak256|len|mulw|setbit|setbyte|sha256|sha3_256|sha512_256|shl|shr|sqrt|\\||\\|\\||\\~|b\\!\\=|b%|b\\*|b\\+|b\\-|b/|b\\\u003c|b\\\u003c\\=|b\\=\\=|b\\\u003e|b\\\u003e\\=|bsqrt|b\u0026|b\\^|b\\||b\\~|base64_decode|extract|extract3|extract_uint16|extract_uint32|extract_uint64|json_ref|substring|substring3|gitxn|gitxna|gitxnas|itxn|itxn_begin|itxn_field|itxn_next|itxn_submit|itxna|itxnas)\\b" } ] }, From 8256400034635c50456ef3891fa25f244d3d007b Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Fri, 25 Mar 2022 21:25:22 -0400 Subject: [PATCH 9/9] Comment on Cost vs costFunc --- data/transactions/logic/eval.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 6a03c30f9f..731d48de5c 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -907,6 +907,10 @@ func (cx *EvalContext) step() error { opcost := deets.Cost if opcost == 0 { + // We use a constant (deets.Cost) to avoid the cost of an indirect + // function call in most cases. Consider getting the cost details into + // OpSpec so that a single (inlinable?) function can compute the cost, + // rather than a per opcode function pointer. opcost = deets.costFunc(cx.program, cx.pc) } if opcost == 0 {