diff --git a/cmd/opdoc/opdoc.go b/cmd/opdoc/opdoc.go index 2516e8ef1b..000f30ffee 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 d7802ca479..6ded5904ed 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 @@ -390,7 +390,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 32747e61ef..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 (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,7 +1240,7 @@ 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/assembler.go b/data/transactions/logic/assembler.go index ca953ee36c..74ae5874bd 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -2552,7 +2552,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/doc.go b/data/transactions/logic/doc.go index 760c6a75c0..b668b089a7 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.", "txnas": "push Xth value of the array field F of the current transaction", "gtxnas": "push Xth value of the array field F from the Tth transaction in the current group", diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 507c13e719..b9572be317 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -2040,7 +2040,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) @@ -2060,7 +2060,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) @@ -2110,7 +2110,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) @@ -2123,7 +2123,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 { @@ -2143,7 +2143,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) @@ -2203,7 +2203,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) @@ -2216,7 +2216,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 { @@ -2237,7 +2237,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) @@ -3415,7 +3415,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") } @@ -3440,31 +3440,40 @@ 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 + } + // i != 0 is so that the error reports 0 instead of Unknown + if i != 0 && i < uint64(len(TxnTypeNames)) { + 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) } - 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 @@ -3475,22 +3484,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 @@ -3499,9 +3507,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 tx_field %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..bac19afd7f 100644 --- a/data/transactions/logic/evalAppTxn_test.go +++ b/data/transactions/logic/evalAppTxn_test.go @@ -29,22 +29,24 @@ 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") - 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) @@ -308,6 +310,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 tx_field RekeyTo") +} + func TestNumInner(t *testing.T) { pay := ` tx_begin 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) diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 00f7fe9966..69c9de58e1 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 available 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 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: