From adbfd50e8d1796c6158980b803e7aee1695a37f6 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 7 Sep 2021 13:38:37 -0400 Subject: [PATCH 1/5] Spec improvements, and table driven txfield versions --- cmd/opdoc/opdoc.go | 6 +- data/transactions/logic/README.md | 16 +-- data/transactions/logic/TEAL_opcodes.md | 22 ++-- data/transactions/logic/doc.go | 16 +-- data/transactions/logic/eval.go | 89 +++++++------ data/transactions/logic/evalAppTxn_test.go | 30 ++++- data/transactions/logic/fields.go | 138 +++++++++++---------- 7 files changed, 182 insertions(+), 135 deletions(-) diff --git a/cmd/opdoc/opdoc.go b/cmd/opdoc/opdoc.go index 334942a324..a2086edf93 100644 --- a/cmd/opdoc/opdoc.go +++ b/cmd/opdoc/opdoc.go @@ -159,7 +159,11 @@ func opToMarkdown(out io.Writer, op *logic.OpSpec) (err error) { if cost.From == cost.To { fmt.Fprintf(out, " - %d (LogicSigVersion = %d)\n", cost.Cost, cost.To) } else { - fmt.Fprintf(out, " - %d (%d <= LogicSigVersion <= %d)\n", cost.Cost, cost.From, cost.To) + if cost.To < logic.LogicVersion { + fmt.Fprintf(out, " - %d (%d <= LogicSigVersion <= %d)\n", cost.Cost, cost.From, cost.To) + } else { + fmt.Fprintf(out, " - %d (LogicSigVersion => %d)\n", cost.Cost, cost.From) + } } } } else { diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index cf01278b45..120732e136 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -148,10 +148,10 @@ various sizes. | `substring s e` | pop a byte-array A. For immediate values in 0..255 S and E: extract a range of bytes from A starting at S up to but not including E, push the substring result. If E < S, or either is larger than the array length, the program fails | | `substring3` | pop a byte-array A and two integers B and C. Extract a range of bytes from A starting at B up to but not including C, push the substring result. If C < B, or either is larger than the array length, the program fails | | `extract s l` | pop a byte-array A. For immediate values in 0..255 S and L: extract a range of bytes from A starting at S up to but not including S+L, push the substring result. 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 | -| `extract3` | pop a byte-array A and two integers B and C. Extract a range of bytes from A starting at B up to but not including B+C, push the substring result. If B or B+C is larger than the array length, the program fails | -| `extract16bits` | pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+2, convert bytes as big endian and push the uint64 result. If B or B+2 is larger than the array length, the program fails | -| `extract32bits` | pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+4, convert bytes as big endian and push the uint64 result. If B or B+4 is larger than the array length, the program fails | -| `extract64bits` | pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+8, convert bytes as big endian and push the uint64 result. If B or B+8 is larger than the array length, the program fails | +| `extract3` | pop a byte-array A and two integers B and C. Extract a range of bytes from A starting at B up to but not including B+C, push the substring result. If B+C is larger than the array length, the program fails | +| `extract16bits` | pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+2, convert bytes as big endian and push the uint64 result. If B+2 is larger than the array length, the program fails | +| `extract32bits` | pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+4, convert bytes as big endian and push the uint64 result. If B+4 is larger than the array length, the program fails | +| `extract64bits` | pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+8, convert bytes as big endian and push the uint64 result. If B+8 is larger than the array length, the program fails | These opcodes take byte-array values that are interpreted as big-endian unsigned integers. For mathematical operators, the @@ -197,9 +197,9 @@ The following opcodes allow for the construction and submission of | Op | Description | | --- | --- | -| `tx_begin` | Prepare a new application action | -| `tx_field f` | Set field F of the current application action | -| `tx_submit` | Execute the current application action. Panic on any failure. | +| `tx_begin` | Begin preparation of a new inner transaction | +| `tx_field f` | Set field F of the current inner transaction to X | +| `tx_submit` | Execute the current inner transaction. Panic on any failure. | ### Loading Values @@ -386,7 +386,7 @@ App fields used in the `app_params_get` opcode. | `dup` | duplicate last value on stack | | `dup2` | duplicate two last values on stack: A, B -> A, B, A, B | | `dig n` | push the Nth value from the top of the stack. dig 0 is equivalent to dup | -| `cover n` | remove top of stack, and place it down the stack such that N elements are above it | +| `cover n` | remove top of stack, and place it deeper in the stack such that N elements are above it | | `uncover n` | 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 | | `swap` | swaps two last values on stack: A, B -> B, A | | `select` | selects one of two values based on top-of-stack: A, B, C -> (if C != 0 then B else A) | diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index 5e3ff238e0..cb4450efca 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -18,7 +18,7 @@ Ops have a 'cost' of 1 unless otherwise specified. - SHA256 hash of value X, yields [32]byte - **Cost**: - 7 (LogicSigVersion = 1) - - 35 (2 <= LogicSigVersion <= 5) + - 35 (LogicSigVersion => 2) ## keccak256 @@ -28,7 +28,7 @@ Ops have a 'cost' of 1 unless otherwise specified. - Keccak256 hash of value X, yields [32]byte - **Cost**: - 26 (LogicSigVersion = 1) - - 130 (2 <= LogicSigVersion <= 5) + - 130 (LogicSigVersion => 2) ## sha512_256 @@ -38,7 +38,7 @@ Ops have a 'cost' of 1 unless otherwise specified. - SHA512_256 hash of value X, yields [32]byte - **Cost**: - 9 (LogicSigVersion = 1) - - 45 (2 <= LogicSigVersion <= 5) + - 45 (LogicSigVersion => 2) ## ed25519verify @@ -667,7 +667,7 @@ See `bnz` for details on how branches work. `b` always jumps to the offset. - Opcode: 0x4e {uint8 depth} - Pops: *... stack*, any - Pushes: any -- remove top of stack, and place it down the stack such that N elements are above it +- remove top of stack, and place it deeper in the stack such that N elements are above it - LogicSigVersion >= 5 ## uncover n @@ -753,7 +753,7 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on - Opcode: 0x58 - Pops: *... stack*, {[]byte A}, {uint64 B}, {uint64 C} - Pushes: []byte -- pop a byte-array A and two integers B and C. Extract a range of bytes from A starting at B up to but not including B+C, push the substring result. If B or B+C is larger than the array length, the program fails +- pop a byte-array A and two integers B and C. Extract a range of bytes from A starting at B up to but not including B+C, push the substring result. If B+C is larger than the array length, the program fails - LogicSigVersion >= 5 ## extract16bits @@ -761,7 +761,7 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on - Opcode: 0x59 - Pops: *... stack*, {[]byte A}, {uint64 B} - Pushes: uint64 -- pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+2, convert bytes as big endian and push the uint64 result. If B or B+2 is larger than the array length, the program fails +- pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+2, convert bytes as big endian and push the uint64 result. If B+2 is larger than the array length, the program fails - LogicSigVersion >= 5 ## extract32bits @@ -769,7 +769,7 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on - Opcode: 0x5a - Pops: *... stack*, {[]byte A}, {uint64 B} - Pushes: uint64 -- pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+4, convert bytes as big endian and push the uint64 result. If B or B+4 is larger than the array length, the program fails +- pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+4, convert bytes as big endian and push the uint64 result. If B+4 is larger than the array length, the program fails - LogicSigVersion >= 5 ## extract64bits @@ -777,7 +777,7 @@ When A is a uint64, index 0 is the least significant bit. Setting bit 3 to 1 on - Opcode: 0x5b - Pops: *... stack*, {[]byte A}, {uint64 B} - Pushes: uint64 -- pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+8, convert bytes as big endian and push the uint64 result. If B or B+8 is larger than the array length, the program fails +- pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+8, convert bytes as big endian and push the uint64 result. If B+8 is larger than the array length, the program fails - LogicSigVersion >= 5 ## balance @@ -1222,7 +1222,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - Opcode: 0xb1 - Pops: _None_ - Pushes: _None_ -- Prepare a new application action +- Begin preparation of a new inner transaction - LogicSigVersion >= 5 - Mode: Application @@ -1231,7 +1231,7 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - Opcode: 0xb2 {uint8 transaction field index} - Pops: *... stack*, any - Pushes: _None_ -- Set field F of the current application action +- Set field F of the current inner transaction to X - LogicSigVersion >= 5 - Mode: Application @@ -1240,6 +1240,6 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit - Opcode: 0xb3 - Pops: _None_ - Pushes: _None_ -- Execute the current application action. Panic on any failure. +- Execute the current inner transaction. Panic on any failure. - LogicSigVersion >= 5 - Mode: Application diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index da437559e6..904d3475d7 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -100,7 +100,7 @@ var opDocByName = map[string]string{ "dup": "duplicate last value on stack", "dup2": "duplicate two last values on stack: A, B -> A, B, A, B", "dig": "push the Nth value from the top of the stack. dig 0 is equivalent to dup", - "cover": "remove top of stack, and place it down the stack such that N elements are above it", + "cover": "remove top of stack, and place it deeper in the stack such that N elements are above it", "uncover": "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", "swap": "swaps two last values on stack: A, B -> B, A", "select": "selects one of two values based on top-of-stack: A, B, C -> (if C != 0 then B else A)", @@ -112,10 +112,10 @@ var opDocByName = map[string]string{ "getbyte": "pop a byte-array A and integer B. Extract the Bth byte of A and push it as an integer", "setbyte": "pop a byte-array A, integer B, and small integer C (between 0..255). Set the Bth byte of A to C, and push the result", "extract": "pop a byte-array A. For immediate values in 0..255 S and L: extract a range of bytes from A starting at S up to but not including S+L, push the substring result. 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", - "extract3": "pop a byte-array A and two integers B and C. Extract a range of bytes from A starting at B up to but not including B+C, push the substring result. If B or B+C is larger than the array length, the program fails", - "extract16bits": "pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+2, convert bytes as big endian and push the uint64 result. If B or B+2 is larger than the array length, the program fails", - "extract32bits": "pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+4, convert bytes as big endian and push the uint64 result. If B or B+4 is larger than the array length, the program fails", - "extract64bits": "pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+8, convert bytes as big endian and push the uint64 result. If B or B+8 is larger than the array length, the program fails", + "extract3": "pop a byte-array A and two integers B and C. Extract a range of bytes from A starting at B up to but not including B+C, push the substring result. If B+C is larger than the array length, the program fails", + "extract16bits": "pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+2, convert bytes as big endian and push the uint64 result. If B+2 is larger than the array length, the program fails", + "extract32bits": "pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+4, convert bytes as big endian and push the uint64 result. If B+4 is larger than the array length, the program fails", + "extract64bits": "pop a byte-array A and integer B. Extract a range of bytes from A starting at B up to but not including B+8, convert bytes as big endian and push the uint64 result. If B+8 is larger than the array length, the program fails", "balance": "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.", "min_balance": "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.", @@ -152,9 +152,9 @@ var opDocByName = map[string]string{ "b~": "X with all bits inverted", "log": "write bytes to log state of the current application", - "tx_begin": "Prepare a new application action", - "tx_field": "Set field F of the current application action", - "tx_submit": "Execute the current application action. Panic on any failure.", + "tx_begin": "Begin preparation of a new inner transaction", + "tx_field": "Set field F of the current inner transaction to X", + "tx_submit": "Execute the current inner transaction. Panic on any failure.", } // OpDoc returns a description of the op diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index d754e09181..a688149aa4 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -2033,7 +2033,7 @@ func (cx *EvalContext) txnFieldToStack(txn *transactions.Transaction, field TxnF } func opTxn(cx *EvalContext) { - field := TxnField(uint64(cx.program[cx.pc+1])) + field := TxnField(cx.program[cx.pc+1]) fs, ok := txnFieldSpecByField[field] if !ok || fs.version > cx.version { cx.err = fmt.Errorf("invalid txn field %d", field) @@ -2053,7 +2053,7 @@ func opTxn(cx *EvalContext) { } func opTxna(cx *EvalContext) { - field := TxnField(uint64(cx.program[cx.pc+1])) + field := TxnField(cx.program[cx.pc+1]) fs, ok := txnFieldSpecByField[field] if !ok || fs.version > cx.version { cx.err = fmt.Errorf("invalid txn field %d", field) @@ -2080,7 +2080,7 @@ func opGtxn(cx *EvalContext) { return } tx := &cx.TxnGroup[gtxid].Txn - field := TxnField(uint64(cx.program[cx.pc+2])) + field := TxnField(cx.program[cx.pc+2]) fs, ok := txnFieldSpecByField[field] if !ok || fs.version > cx.version { cx.err = fmt.Errorf("invalid txn field %d", field) @@ -2093,7 +2093,7 @@ func opGtxn(cx *EvalContext) { } var sv stackValue var err error - if TxnField(field) == GroupIndex { + if field == GroupIndex { // GroupIndex; asking this when we just specified it is _dumb_, but oh well sv.Uint = uint64(gtxid) } else { @@ -2113,7 +2113,7 @@ func opGtxna(cx *EvalContext) { return } tx := &cx.TxnGroup[gtxid].Txn - field := TxnField(uint64(cx.program[cx.pc+2])) + field := TxnField(cx.program[cx.pc+2]) fs, ok := txnFieldSpecByField[field] if !ok || fs.version > cx.version { cx.err = fmt.Errorf("invalid txn field %d", field) @@ -2141,7 +2141,7 @@ func opGtxns(cx *EvalContext) { return } tx := &cx.TxnGroup[gtxid].Txn - field := TxnField(uint64(cx.program[cx.pc+1])) + field := TxnField(cx.program[cx.pc+1]) fs, ok := txnFieldSpecByField[field] if !ok || fs.version > cx.version { cx.err = fmt.Errorf("invalid txn field %d", field) @@ -2154,7 +2154,7 @@ func opGtxns(cx *EvalContext) { } var sv stackValue var err error - if TxnField(field) == GroupIndex { + if field == GroupIndex { // GroupIndex; asking this when we just specified it is _dumb_, but oh well sv.Uint = uint64(gtxid) } else { @@ -2175,7 +2175,7 @@ func opGtxnsa(cx *EvalContext) { return } tx := &cx.TxnGroup[gtxid].Txn - field := TxnField(uint64(cx.program[cx.pc+1])) + field := TxnField(cx.program[cx.pc+1]) fs, ok := txnFieldSpecByField[field] if !ok || fs.version > cx.version { cx.err = fmt.Errorf("invalid txn field %d", field) @@ -3319,7 +3319,7 @@ func opTxBegin(cx *EvalContext) { // that don't need (or want!) to allow low numbers to represent the account at // that index in Accounts array. func (cx *EvalContext) availableAccount(sv stackValue) (basics.Address, error) { - if sv.argType() != StackBytes { + if sv.argType() != StackBytes || len(sv.Bytes) != crypto.DigestSize { return basics.Address{}, fmt.Errorf("not an address") } @@ -3344,31 +3344,34 @@ func (cx *EvalContext) availableAsset(sv stackValue) (basics.AssetIndex, error) return basics.AssetIndex(0), fmt.Errorf("invalid Asset reference %d", aid) } -func opTxField(cx *EvalContext) { - if cx.subtxn == nil { - cx.err = errors.New("tx_field without tx_begin") - return - } - last := len(cx.stack) - 1 - field := TxnField(uint64(cx.program[cx.pc+1])) - sv := cx.stack[last] - switch field { +func (cx *EvalContext) stackIntoTxnField(sv stackValue, fs txnFieldSpec, txn *transactions.Transaction) (err error) { + switch fs.field { case Type: if sv.Bytes == nil { - cx.err = fmt.Errorf("Type arg not a byte array") + err = fmt.Errorf("Type arg not a byte array") + return + } + txType, ok := innerTxnTypes[string(sv.Bytes)] + if ok { + txn.Type = txType + } else { + err = fmt.Errorf("%s is not a valid Type for tx_field", sv.Bytes) } - cx.subtxn.Txn.Type = protocol.TxType(sv.Bytes) case TypeEnum: var i uint64 - i, cx.err = sv.uint() - if i < uint64(len(TxnTypeNames)) { - cx.subtxn.Txn.Type = protocol.TxType(TxnTypeNames[i]) + i, err = sv.uint() + if err != nil { + return + } + if i != 0 && i < uint64(len(TxnTypeNames)) { + txn.Type = protocol.TxType(TxnTypeNames[i]) + } else { + err = fmt.Errorf("%d is not a valid TypeEnum", i) } - case Sender: - cx.subtxn.Txn.Sender, cx.err = cx.availableAccount(sv) + txn.Sender, err = cx.availableAccount(sv) case Fee: - cx.subtxn.Txn.Fee.Raw, cx.err = sv.uint() + txn.Fee.Raw, err = sv.uint() // FirstValid, LastValid unsettable: no motivation // Note unsettable: would be strange, as this "Note" would not end up "chain-visible" // GenesisID, GenesisHash unsettable: surely makes no sense @@ -3379,22 +3382,21 @@ func opTxField(cx *EvalContext) { // KeyReg not allowed yet, so no fields settable case Receiver: - cx.subtxn.Txn.Receiver, cx.err = cx.availableAccount(sv) + txn.Receiver, err = cx.availableAccount(sv) case Amount: - cx.subtxn.Txn.Amount.Raw, cx.err = sv.uint() + txn.Amount.Raw, err = sv.uint() case CloseRemainderTo: - cx.subtxn.Txn.CloseRemainderTo, cx.err = cx.availableAccount(sv) - + txn.CloseRemainderTo, err = cx.availableAccount(sv) case XferAsset: - cx.subtxn.Txn.XferAsset, cx.err = cx.availableAsset(sv) + txn.XferAsset, err = cx.availableAsset(sv) case AssetAmount: - cx.subtxn.Txn.AssetAmount, cx.err = sv.uint() + txn.AssetAmount, err = sv.uint() case AssetSender: - cx.subtxn.Txn.AssetSender, cx.err = cx.availableAccount(sv) + txn.AssetSender, err = cx.availableAccount(sv) case AssetReceiver: - cx.subtxn.Txn.AssetReceiver, cx.err = cx.availableAccount(sv) + txn.AssetReceiver, err = cx.availableAccount(sv) case AssetCloseTo: - cx.subtxn.Txn.AssetCloseTo, cx.err = cx.availableAccount(sv) + txn.AssetCloseTo, err = cx.availableAccount(sv) // acfg likely next @@ -3403,9 +3405,24 @@ func opTxField(cx *EvalContext) { // appl needs to wait. Can't call AVM from AVM. default: - cx.err = fmt.Errorf("invalid txfield %s", field) + return fmt.Errorf("invalid txfield %s", fs.field) } + return +} +func opTxField(cx *EvalContext) { + if cx.subtxn == nil { + cx.err = errors.New("tx_field without tx_begin") + return + } + last := len(cx.stack) - 1 + field := TxnField(cx.program[cx.pc+1]) + fs, ok := txnFieldSpecByField[field] + if !ok || fs.itxVersion == 0 || fs.itxVersion > cx.version { + cx.err = fmt.Errorf("invalid tx_field field %d", field) + } + sv := cx.stack[last] + cx.err = cx.stackIntoTxnField(sv, fs, &cx.subtxn.Txn) cx.stack = cx.stack[:last] // pop } diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 03001f1962..0e7dbabede 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -29,17 +29,17 @@ func TestActionTypes(t *testing.T) { testApp(t, "int pay; tx_field TypeEnum; tx_submit; int 1;", ep, "tx_field without tx_begin") testApp(t, "tx_begin; tx_submit; int 1;", ep, "Invalid inner transaction type") // bad type - testApp(t, "tx_begin; byte \"pya\"; tx_field Type; tx_submit; int 1;", ep, "Invalid inner transaction type") + testApp(t, "tx_begin; byte \"pya\"; tx_field Type; tx_submit; int 1;", ep, "pya is not a valid Type") // mixed up the int form for the byte form testApp(t, obfuscate("tx_begin; int pay; tx_field Type; tx_submit; int 1;"), ep, "Type arg not a byte array") // or vice versa testApp(t, obfuscate("tx_begin; byte \"pay\"; tx_field TypeEnum; tx_submit; int 1;"), ep, "not a uint64") // good types, not alllowed yet - testApp(t, "tx_begin; byte \"keyreg\"; tx_field Type; tx_submit; int 1;", ep, "Invalid inner transaction type") - testApp(t, "tx_begin; byte \"acfg\"; tx_field Type; tx_submit; int 1;", ep, "Invalid inner transaction type") - testApp(t, "tx_begin; byte \"afrz\"; tx_field Type; tx_submit; int 1;", ep, "Invalid inner transaction type") - testApp(t, "tx_begin; byte \"appl\"; tx_field Type; tx_submit; int 1;", ep, "Invalid inner transaction type") + testApp(t, "tx_begin; byte \"keyreg\"; tx_field Type; tx_submit; int 1;", ep, "keyreg is not a valid Type for tx_field") + testApp(t, "tx_begin; byte \"acfg\"; tx_field Type; tx_submit; int 1;", ep, "acfg is not a valid Type for tx_field") + testApp(t, "tx_begin; byte \"afrz\"; tx_field Type; tx_submit; int 1;", ep, "afrz is not a valid Type for tx_field") + testApp(t, "tx_begin; byte \"appl\"; tx_field Type; tx_submit; int 1;", ep, "appl is not a valid Type for tx_field") // same, as enums testApp(t, "tx_begin; int keyreg; tx_field TypeEnum; tx_submit; int 1;", ep, "Invalid inner transaction type") testApp(t, "tx_begin; int acfg; tx_field TypeEnum; tx_submit; int 1;", ep, "Invalid inner transaction type") @@ -308,6 +308,26 @@ func TestExtraFields(t *testing.T) { "non-zero fields for type axfer") } +func TestBadField(t *testing.T) { + pay := ` + tx_begin + int 7; tx_field AssetAmount; + tx_field Amount + tx_field Receiver + tx_field Sender + int pay + tx_field TypeEnum + txn Receiver + tx_field RekeyTo // NOT ALLOWED + tx_submit +` + + ep, ledger := makeSampleEnv() + ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) + testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep, + "invalid txfield RekeyTo") +} + func TestNumInner(t *testing.T) { pay := ` tx_begin diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 00f7fe9966..0358e71796 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -172,77 +172,78 @@ func (s tfNameSpecMap) getExtraFor(name string) (extra string) { } type txnFieldSpec struct { - field TxnField - ftype StackType - version uint64 + field TxnField + ftype StackType + version uint64 // When this field become availabe to txn/gtxn. 0=always + itxVersion uint64 // When this field become available to tx_field. 0=never } var txnFieldSpecs = []txnFieldSpec{ - {Sender, StackBytes, 0}, - {Fee, StackUint64, 0}, - {FirstValid, StackUint64, 0}, - {FirstValidTime, StackUint64, 0}, - {LastValid, StackUint64, 0}, - {Note, StackBytes, 0}, - {Lease, StackBytes, 0}, - {Receiver, StackBytes, 0}, - {Amount, StackUint64, 0}, - {CloseRemainderTo, StackBytes, 0}, - {VotePK, StackBytes, 0}, - {SelectionPK, StackBytes, 0}, - {VoteFirst, StackUint64, 0}, - {VoteLast, StackUint64, 0}, - {VoteKeyDilution, StackUint64, 0}, - {Type, StackBytes, 0}, - {TypeEnum, StackUint64, 0}, - {XferAsset, StackUint64, 0}, - {AssetAmount, StackUint64, 0}, - {AssetSender, StackBytes, 0}, - {AssetReceiver, StackBytes, 0}, - {AssetCloseTo, StackBytes, 0}, - {GroupIndex, StackUint64, 0}, - {TxID, StackBytes, 0}, - {ApplicationID, StackUint64, 2}, - {OnCompletion, StackUint64, 2}, - {ApplicationArgs, StackBytes, 2}, - {NumAppArgs, StackUint64, 2}, - {Accounts, StackBytes, 2}, - {NumAccounts, StackUint64, 2}, - {ApprovalProgram, StackBytes, 2}, - {ClearStateProgram, StackBytes, 2}, - {RekeyTo, StackBytes, 2}, - {ConfigAsset, StackUint64, 2}, - {ConfigAssetTotal, StackUint64, 2}, - {ConfigAssetDecimals, StackUint64, 2}, - {ConfigAssetDefaultFrozen, StackUint64, 2}, - {ConfigAssetUnitName, StackBytes, 2}, - {ConfigAssetName, StackBytes, 2}, - {ConfigAssetURL, StackBytes, 2}, - {ConfigAssetMetadataHash, StackBytes, 2}, - {ConfigAssetManager, StackBytes, 2}, - {ConfigAssetReserve, StackBytes, 2}, - {ConfigAssetFreeze, StackBytes, 2}, - {ConfigAssetClawback, StackBytes, 2}, - {FreezeAsset, StackUint64, 2}, - {FreezeAssetAccount, StackBytes, 2}, - {FreezeAssetFrozen, StackUint64, 2}, - {Assets, StackUint64, 3}, - {NumAssets, StackUint64, 3}, - {Applications, StackUint64, 3}, - {NumApplications, StackUint64, 3}, - {GlobalNumUint, StackUint64, 3}, - {GlobalNumByteSlice, StackUint64, 3}, - {LocalNumUint, StackUint64, 3}, - {LocalNumByteSlice, StackUint64, 3}, - {ExtraProgramPages, StackUint64, 4}, - {Nonparticipation, StackUint64, 5}, + {Sender, StackBytes, 0, 5}, + {Fee, StackUint64, 0, 5}, + {FirstValid, StackUint64, 0, 0}, + {FirstValidTime, StackUint64, 0, 0}, + {LastValid, StackUint64, 0, 0}, + {Note, StackBytes, 0, 0}, + {Lease, StackBytes, 0, 0}, + {Receiver, StackBytes, 0, 5}, + {Amount, StackUint64, 0, 5}, + {CloseRemainderTo, StackBytes, 0, 5}, + {VotePK, StackBytes, 0, 0}, + {SelectionPK, StackBytes, 0, 0}, + {VoteFirst, StackUint64, 0, 0}, + {VoteLast, StackUint64, 0, 0}, + {VoteKeyDilution, StackUint64, 0, 0}, + {Type, StackBytes, 0, 5}, + {TypeEnum, StackUint64, 0, 5}, + {XferAsset, StackUint64, 0, 5}, + {AssetAmount, StackUint64, 0, 5}, + {AssetSender, StackBytes, 0, 5}, + {AssetReceiver, StackBytes, 0, 5}, + {AssetCloseTo, StackBytes, 0, 5}, + {GroupIndex, StackUint64, 0, 0}, + {TxID, StackBytes, 0, 0}, + {ApplicationID, StackUint64, 2, 0}, + {OnCompletion, StackUint64, 2, 0}, + {ApplicationArgs, StackBytes, 2, 0}, + {NumAppArgs, StackUint64, 2, 0}, + {Accounts, StackBytes, 2, 0}, + {NumAccounts, StackUint64, 2, 0}, + {ApprovalProgram, StackBytes, 2, 0}, + {ClearStateProgram, StackBytes, 2, 0}, + {RekeyTo, StackBytes, 2, 0}, + {ConfigAsset, StackUint64, 2, 0}, + {ConfigAssetTotal, StackUint64, 2, 0}, + {ConfigAssetDecimals, StackUint64, 2, 0}, + {ConfigAssetDefaultFrozen, StackUint64, 2, 0}, + {ConfigAssetUnitName, StackBytes, 2, 0}, + {ConfigAssetName, StackBytes, 2, 0}, + {ConfigAssetURL, StackBytes, 2, 0}, + {ConfigAssetMetadataHash, StackBytes, 2, 0}, + {ConfigAssetManager, StackBytes, 2, 0}, + {ConfigAssetReserve, StackBytes, 2, 0}, + {ConfigAssetFreeze, StackBytes, 2, 0}, + {ConfigAssetClawback, StackBytes, 2, 0}, + {FreezeAsset, StackUint64, 2, 0}, + {FreezeAssetAccount, StackBytes, 2, 0}, + {FreezeAssetFrozen, StackUint64, 2, 0}, + {Assets, StackUint64, 3, 0}, + {NumAssets, StackUint64, 3, 0}, + {Applications, StackUint64, 3, 0}, + {NumApplications, StackUint64, 3, 0}, + {GlobalNumUint, StackUint64, 3, 0}, + {GlobalNumByteSlice, StackUint64, 3, 0}, + {LocalNumUint, StackUint64, 3, 0}, + {LocalNumByteSlice, StackUint64, 3, 0}, + {ExtraProgramPages, StackUint64, 4, 0}, + {Nonparticipation, StackUint64, 5, 0}, } // TxnaFieldNames are arguments to the 'txna' opcode // It is a subset of txn transaction fields so initialized here in-place var TxnaFieldNames = []string{ApplicationArgs.String(), Accounts.String(), Assets.String(), Applications.String()} -// TxnaFieldTypes is StackBytes or StackUint64 parallel to TxnFieldNames +// TxnaFieldTypes is StackBytes or StackUint64 parallel to TxnaFieldNames var TxnaFieldTypes = []StackType{ txnaFieldSpecByField[ApplicationArgs].ftype, txnaFieldSpecByField[Accounts].ftype, @@ -251,10 +252,15 @@ var TxnaFieldTypes = []StackType{ } var txnaFieldSpecByField = map[TxnField]txnFieldSpec{ - ApplicationArgs: {ApplicationArgs, StackBytes, 2}, - Accounts: {Accounts, StackBytes, 2}, - Assets: {Assets, StackUint64, 3}, - Applications: {Applications, StackUint64, 3}, + ApplicationArgs: {ApplicationArgs, StackBytes, 2, 0}, + Accounts: {Accounts, StackBytes, 2, 0}, + Assets: {Assets, StackUint64, 3, 0}, + Applications: {Applications, StackUint64, 3, 0}, +} + +var innerTxnTypes = map[string]protocol.TxType{ + string(protocol.PaymentTx): protocol.PaymentTx, + string(protocol.AssetTransferTx): protocol.AssetTransferTx, } // TxnTypeNames is the values of Txn.Type in enum order From 04c6b6f7b6ab5910d7f1de7c6e02a6469e150d22 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 7 Sep 2021 14:54:33 -0400 Subject: [PATCH 2/5] more checking on tx_field type setting --- data/transactions/logic/assembler.go | 2 +- data/transactions/logic/eval.go | 10 ++++++++-- data/transactions/logic/evalAppTxn_test.go | 12 +++++++----- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 28f6ae29c1..5de1ee4047 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -2476,7 +2476,7 @@ func disTxField(dis *disassembleState, spec *OpSpec) (string, error) { dis.nextpc = dis.pc + 2 arg := dis.program[dis.pc+1] if int(arg) >= len(TxnFieldNames) { - return "", fmt.Errorf("invalid txfield arg index %d at pc=%d", arg, dis.pc) + 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 } diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index a688149aa4..9c4d1d57ef 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -3363,8 +3363,14 @@ func (cx *EvalContext) stackIntoTxnField(sv stackValue, fs txnFieldSpec, txn *tr if err != nil { return } + // i != 0 is so that the error reports 0 instead of Unknown if i != 0 && i < uint64(len(TxnTypeNames)) { - txn.Type = protocol.TxType(TxnTypeNames[i]) + txType, ok := innerTxnTypes[TxnTypeNames[i]] + if ok { + txn.Type = txType + } else { + err = fmt.Errorf("%s is not a valid Type for tx_field", TxnTypeNames[i]) + } } else { err = fmt.Errorf("%d is not a valid TypeEnum", i) } @@ -3405,7 +3411,7 @@ func (cx *EvalContext) stackIntoTxnField(sv stackValue, fs txnFieldSpec, txn *tr // appl needs to wait. Can't call AVM from AVM. default: - return fmt.Errorf("invalid txfield %s", fs.field) + return fmt.Errorf("invalid tx_field %s", fs.field) } return } diff --git a/data/transactions/logic/evalAppTxn_test.go b/data/transactions/logic/evalAppTxn_test.go index 0e7dbabede..bac19afd7f 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -41,10 +41,12 @@ func TestActionTypes(t *testing.T) { testApp(t, "tx_begin; byte \"afrz\"; tx_field Type; tx_submit; int 1;", ep, "afrz is not a valid Type for tx_field") testApp(t, "tx_begin; byte \"appl\"; tx_field Type; tx_submit; int 1;", ep, "appl is not a valid Type for tx_field") // same, as enums - testApp(t, "tx_begin; int keyreg; tx_field TypeEnum; tx_submit; int 1;", ep, "Invalid inner transaction type") - testApp(t, "tx_begin; int acfg; tx_field TypeEnum; tx_submit; int 1;", ep, "Invalid inner transaction type") - testApp(t, "tx_begin; int afrz; tx_field TypeEnum; tx_submit; int 1;", ep, "Invalid inner transaction type") - testApp(t, "tx_begin; int appl; tx_field TypeEnum; tx_submit; int 1;", ep, "Invalid inner transaction type") + testApp(t, "tx_begin; int keyreg; tx_field TypeEnum; tx_submit; int 1;", ep, "keyreg is not a valid Type for tx_field") + testApp(t, "tx_begin; int acfg; tx_field TypeEnum; tx_submit; int 1;", ep, "acfg is not a valid Type for tx_field") + testApp(t, "tx_begin; int afrz; tx_field TypeEnum; tx_submit; int 1;", ep, "afrz is not a valid Type for tx_field") + testApp(t, "tx_begin; int appl; tx_field TypeEnum; tx_submit; int 1;", ep, "appl is not a valid Type for tx_field") + testApp(t, "tx_begin; int 42; tx_field TypeEnum; tx_submit; int 1;", ep, "42 is not a valid TypeEnum") + testApp(t, "tx_begin; int 0; tx_field TypeEnum; tx_submit; int 1;", ep, "0 is not a valid TypeEnum") // "insufficient balance" because app account is charged fee // (defaults make these 0 pay|axfer to zero address, from app account) @@ -325,7 +327,7 @@ func TestBadField(t *testing.T) { ep, ledger := makeSampleEnv() ledger.NewApp(ep.Txn.Txn.Receiver, 888, basics.AppParams{}) testApp(t, "global CurrentApplicationAddress; txn Accounts 1; int 100"+pay, ep, - "invalid txfield RekeyTo") + "invalid tx_field RekeyTo") } func TestNumInner(t *testing.T) { From ac729a99a4d8a4a052b8e70c7bbc7ff04b641206 Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Tue, 7 Sep 2021 15:06:44 -0400 Subject: [PATCH 3/5] Spelling "available" --- data/transactions/logic/fields.go | 2 +- rpcs/blockService.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 0358e71796..69c9de58e1 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -174,7 +174,7 @@ func (s tfNameSpecMap) getExtraFor(name string) (extra string) { type txnFieldSpec struct { field TxnField ftype StackType - version uint64 // When this field become availabe to txn/gtxn. 0=always + version uint64 // When this field become available to txn/gtxn. 0=always itxVersion uint64 // When this field become available to tx_field. 0=never } diff --git a/rpcs/blockService.go b/rpcs/blockService.go index c5b3668d70..2df26223c8 100644 --- a/rpcs/blockService.go +++ b/rpcs/blockService.go @@ -252,7 +252,7 @@ func (bs *BlockService) listenForCatchupReq(reqs <-chan network.IncomingMessage, const noRoundNumberErrMsg = "can't find the round number" const noDataTypeErrMsg = "can't find the data-type" const roundNumberParseErrMsg = "unable to parse round number" -const blockNotAvailabeErrMsg = "requested block is not available" +const blockNotAvailableErrMsg = "requested block is not available" const datatypeUnsupportedErrMsg = "requested data type is unsupported" // a blocking function for handling a catchup request @@ -360,7 +360,7 @@ func topicBlockBytes(log logging.Logger, dataLedger *data.Ledger, round basics.R log.Infof("BlockService topicBlockBytes: %s", err) } return network.Topics{ - network.MakeTopic(network.ErrorKey, []byte(blockNotAvailabeErrMsg))} + network.MakeTopic(network.ErrorKey, []byte(blockNotAvailableErrMsg))} } switch requestType { case BlockAndCertValue: From 85089f8bdc3d581c93937e1dbc9b661f06c7f90e Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Wed, 8 Sep 2021 15:18:42 -0400 Subject: [PATCH 4/5] coverage additions --- data/transactions/logic/eval_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 9c9b4edbfd..9807c6be9b 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -4401,6 +4401,7 @@ func TestBits(t *testing.T) { testPanics(t, "int 1; int 64; getbit; int 0; ==", 3) testAccepts(t, "int 0; int 3; int 1; setbit; int 8; ==", 3) + testPanics(t, "int 0; int 3; int 2; setbit; pop; int 1", 3) testAccepts(t, "int 8; int 3; getbit; int 1; ==", 3) testAccepts(t, "int 15; int 3; int 0; setbit; int 7; ==", 3) @@ -4442,6 +4443,7 @@ func TestBytes(t *testing.T) { testPanics(t, `byte "john"; int 4; getbyte; int 1; ==`, 3) // past end testAccepts(t, `byte "john"; int 2; int 105; setbyte; byte "join"; ==`, 3) + testPanics(t, `byte "john"; int 2; int 256; setbyte; pop; int 1;`, 3) testPanics(t, `global ZeroAddress; dup; concat; int 64; int 7; setbyte; int 1; return`, 3) testAccepts(t, `global ZeroAddress; dup; concat; int 63; int 7; setbyte; int 1; return`, 3) @@ -4784,6 +4786,8 @@ func TestBytesCompare(t *testing.T) { testAccepts(t, "byte 0x10; byte 0x10; b<; !", 4) testAccepts(t, "byte 0x10; byte 0x10; b<=", 4) + testAccepts(t, "byte 0x10; int 64; bzero; b>", 4) + testPanics(t, "byte 0x10; int 65; bzero; b>", 4) testAccepts(t, "byte 0x11; byte 0x10; b>", 4) testAccepts(t, "byte 0x11; byte 0x0010; b>", 4) @@ -4794,6 +4798,8 @@ func TestBytesCompare(t *testing.T) { testAccepts(t, "byte 0x11; byte 0x11; b==", 4) testAccepts(t, "byte 0x0011; byte 0x11; b==", 4) testAccepts(t, "byte 0x11; byte 0x00000000000011; b==", 4) + testAccepts(t, "byte 0x00; int 64; bzero; b==", 4) + testPanics(t, "byte 0x00; int 65; bzero; b==", 4) testAccepts(t, "byte 0x11; byte 0x00; b!=", 4) testAccepts(t, "byte 0x0011; byte 0x1100; b!=", 4) From 553b63ec8bf02ef7458686e722586a97e2fd6eba Mon Sep 17 00:00:00 2001 From: John Jannotti Date: Wed, 8 Sep 2021 15:24:42 -0400 Subject: [PATCH 5/5] >=, not => --- cmd/opdoc/opdoc.go | 2 +- data/transactions/logic/TEAL_opcodes.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/opdoc/opdoc.go b/cmd/opdoc/opdoc.go index c9099a3188..000f30ffee 100644 --- a/cmd/opdoc/opdoc.go +++ b/cmd/opdoc/opdoc.go @@ -162,7 +162,7 @@ func opToMarkdown(out io.Writer, op *logic.OpSpec) (err error) { if cost.To < logic.LogicVersion { fmt.Fprintf(out, " - %d (%d <= LogicSigVersion <= %d)\n", cost.Cost, cost.From, cost.To) } else { - fmt.Fprintf(out, " - %d (LogicSigVersion => %d)\n", cost.Cost, cost.From) + fmt.Fprintf(out, " - %d (LogicSigVersion >= %d)\n", cost.Cost, cost.From) } } } diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index 5f52f279b5..9589bdb477 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -18,7 +18,7 @@ Ops have a 'cost' of 1 unless otherwise specified. - SHA256 hash of value X, yields [32]byte - **Cost**: - 7 (LogicSigVersion = 1) - - 35 (LogicSigVersion => 2) + - 35 (LogicSigVersion >= 2) ## keccak256 @@ -28,7 +28,7 @@ Ops have a 'cost' of 1 unless otherwise specified. - Keccak256 hash of value X, yields [32]byte - **Cost**: - 26 (LogicSigVersion = 1) - - 130 (LogicSigVersion => 2) + - 130 (LogicSigVersion >= 2) ## sha512_256 @@ -38,7 +38,7 @@ Ops have a 'cost' of 1 unless otherwise specified. - SHA512_256 hash of value X, yields [32]byte - **Cost**: - 9 (LogicSigVersion = 1) - - 45 (LogicSigVersion => 2) + - 45 (LogicSigVersion >= 2) ## ed25519verify