Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add opcodes for dynamically indexing into Txn array fields #2847

Merged
merged 8 commits into from
Sep 7, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions cmd/opdoc/opdoc.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ func argEnum(name string) []string {
if name == "global" {
return logic.GlobalFieldNames
}
if name == "txna" || name == "gtxna" || name == "gtxnsa" {
if name == "txna" || name == "gtxna" || name == "gtxnsa" || name == "txnas" || name == "gtxnas" {
return logic.TxnaFieldNames
}
if name == "asset_holding_get" {
Expand Down Expand Up @@ -282,7 +282,7 @@ func argEnumTypes(name string) string {
if name == "global" {
return typeString(logic.GlobalFieldTypes)
}
if name == "txna" || name == "gtxna" || name == "gtxnsa" {
if name == "txna" || name == "gtxna" || name == "gtxnsa" || name == "txnas" || name == "gtxnas" {
return typeString(logic.TxnaFieldTypes)
}
if name == "asset_holding_get" {
Expand Down
3 changes: 3 additions & 0 deletions data/transactions/logic/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,9 @@ Some of these have immediate data in the byte or bytes after the opcode.
| `gloads i` | push Ith scratch space index of the Xth transaction in the current group |
| `gaid t` | push the ID of the asset or application created in the Tth transaction of the current group |
| `gaids` | push the ID of the asset or application created in the Xth transaction of the current group |
| `txnas f` | pop an index A. push Ath value of the array field F of the current transaction |
| `gtxnas t f` | pop an index A. push Ath value of the array field F from the Tth transaction in the current group |
| `gtxnsas f` | pop an index A and an index B. push Bth value of the array field F from the Ath transaction in the current group |

**Transaction Fields**

Expand Down
24 changes: 24 additions & 0 deletions data/transactions/logic/TEAL_opcodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -1213,3 +1213,27 @@ bitlen interprets arrays as big-endian integers, unlike setbit/getbit
- Mode: Application

`log` can be called up to MaxLogCalls times in a program, and log up to a total of 1k bytes.

## txnas f

- Opcode: 0xc0 {uint8 transaction field index}
- Pops: *... stack*, uint64
- Pushes: any
- pop an index A. push Ath value of the array field F of the current transaction
- LogicSigVersion >= 5

## gtxnas t f

- Opcode: 0xc1 {uint8 transaction group index} {uint8 transaction field index}
- Pops: *... stack*, uint64
- Pushes: any
- pop an index A. push Ath value of the array field F from the Tth transaction in the current group
- LogicSigVersion >= 5

## gtxnsas f

- Opcode: 0xc2 {uint8 transaction field index}
- Pops: *... stack*, {uint64 A}, {uint64 B}
- Pushes: any
- pop an index A and an index B. push Bth value of the array field F from the Ath transaction in the current group
- LogicSigVersion >= 5
82 changes: 79 additions & 3 deletions data/transactions/logic/assembler.go
Original file line number Diff line number Diff line change
Expand Up @@ -816,7 +816,7 @@ func assembleTxn2(ops *OpStream, spec *OpSpec, args []string) error {

func assembleTxna(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) != 2 {
return ops.error("txna expects two arguments")
return ops.error("txna expects two immediate arguments")
}
fs, ok := txnFieldSpecByName[args[0]]
if !ok {
Expand Down Expand Up @@ -844,6 +844,28 @@ func assembleTxna(ops *OpStream, spec *OpSpec, args []string) error {
return nil
}

func assembleTxnas(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) != 1 {
return ops.error("txnas expects one immediate argument")
}
fs, ok := txnFieldSpecByName[args[0]]
if !ok {
return ops.errorf("txnas unknown field: %#v", args[0])
}
_, ok = txnaFieldSpecByField[fs.field]
if !ok {
return ops.errorf("txnas unknown field: %#v", args[0])
}
if fs.version > ops.Version {
return ops.errorf("txnas %#v available in version %d. Missed #pragma version?", args[0], fs.version)
}

ops.pending.WriteByte(spec.Opcode)
ops.pending.WriteByte(uint8(fs.field))
ops.returns(fs.ftype)
return nil
}

func assembleGtxn(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) != 2 {
return ops.error("gtxn expects two arguments")
Expand Down Expand Up @@ -925,6 +947,38 @@ func assembleGtxna(ops *OpStream, spec *OpSpec, args []string) error {
return nil
}

func assembleGtxnas(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) != 2 {
return ops.error("gtxnas expects two immediate arguments")
}

slot, err := strconv.ParseUint(args[0], 0, 64)
if err != nil {
return ops.error(err)
}
if slot > 255 {
return ops.errorf("gtxnas group index beyond 255: %d", slot)
}

fs, ok := txnFieldSpecByName[args[1]]
if !ok {
return ops.errorf("gtxnas unknown field: %#v", args[1])
}
_, ok = txnaFieldSpecByField[fs.field]
if !ok {
return ops.errorf("gtxnas unknown field: %#v", args[1])
}
if fs.version > ops.Version {
return ops.errorf("gtxnas %#v available in version %d. Missed #pragma version?", args[1], fs.version)
}

ops.pending.WriteByte(spec.Opcode)
ops.pending.WriteByte(uint8(slot))
ops.pending.WriteByte(uint8(fs.field))
ops.returns(fs.ftype)
return nil
}

func assembleGtxns(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) == 2 {
gtxnsa := OpsByName[ops.Version]["gtxnsa"]
Expand Down Expand Up @@ -980,6 +1034,27 @@ func assembleGtxnsa(ops *OpStream, spec *OpSpec, args []string) error {
return nil
}

func assembleGtxnsas(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) != 1 {
return ops.error("gtxnsas expects one immediate argument")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's try to use %s and spec.Name as often as possible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed - I think we can potentially save ourselves time in the future by doing this.

}
fs, ok := txnFieldSpecByName[args[0]]
if !ok {
return ops.errorf("gtxnsas unknown field: %#v", args[0])
}
_, ok = txnaFieldSpecByField[fs.field]
if !ok {
return ops.errorf("gtxnsas unknown field: %#v", args[0])
}
if fs.version > ops.Version {
return ops.errorf("gtxnsas %#v available in version %d. Missed #pragma version?", args[0], fs.version)
}
ops.pending.WriteByte(spec.Opcode)
ops.pending.WriteByte(uint8(fs.field))
ops.returns(fs.ftype)
return nil
}

func assembleGlobal(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) != 1 {
return ops.errorf("%s expects one argument", spec.Name)
Expand Down Expand Up @@ -2294,7 +2369,7 @@ func checkPushBytes(cx *evalContext) error {
return cx.err
}

// This is also used to disassemble gtxns
// This is also used to disassemble gtxns, gtxnsas and txnas
func disTxn(dis *disassembleState, spec *OpSpec) (string, error) {
lastIdx := dis.pc + 1
if len(dis.program) <= lastIdx {
Expand Down Expand Up @@ -2325,6 +2400,7 @@ func disTxna(dis *disassembleState, spec *OpSpec) (string, error) {
return fmt.Sprintf("%s %s %d", spec.Name, TxnFieldNames[txarg], arrayFieldIdx), nil
}

// This is also used to disassemble gtxnas
func disGtxn(dis *disassembleState, spec *OpSpec) (string, error) {
lastIdx := dis.pc + 2
if len(dis.program) <= lastIdx {
Expand All @@ -2337,7 +2413,7 @@ func disGtxn(dis *disassembleState, spec *OpSpec) (string, error) {
if int(txarg) >= len(TxnFieldNames) {
return "", fmt.Errorf("invalid txn arg index %d at pc=%d", txarg, dis.pc)
}
return fmt.Sprintf("gtxn %d %s", gi, TxnFieldNames[txarg]), nil
return fmt.Sprintf("%s %d %s", spec.Name, gi, TxnFieldNames[txarg]), nil
}

func disGtxna(dis *disassembleState, spec *OpSpec) (string, error) {
Expand Down
13 changes: 11 additions & 2 deletions data/transactions/logic/assembler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,13 @@ extract16bits
log
txn Nonparticipation
gtxn 0 Nonparticipation
int 1
txnas ApplicationArgs
int 0
gtxnas 0 ApplicationArgs
int 0
int 0
gtxnsas ApplicationArgs
`

var nonsense = map[uint64]string{
Expand All @@ -327,7 +334,7 @@ var compiled = map[uint64]string{
2: "022008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f23102311231223132314181b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f",
3: "032008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f23102311231223132314181b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f4478222105531421055427042106552105082106564c4d4b02210538212106391c0081e80780046a6f686e",
4: "042004010200b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228236628226724286828692422700048482471004848361c0037001a0031183119311b311d311e311f312024221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2355220823564c4d4b0222382123391c0081e80780046a6f686e2281d00f24231f880003420001892223902291922394239593a0a1a2a3a4a5a6a7a8a9aaabacadae23af3a00003b003c003d8164",
5: "052004010200b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228236628226724286828692422700048482471004848361c0037001a0031183119311b311d311e311f312024221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2355220823564c4d4b0222382123391c0081e80780046a6f686e2281d00f24231f880003420001892223902291922394239593a0a1a2a3a4a5a6a7a8a9aaabacadae23af3a00003b003c003d816472064e014f0180070123456789abcd57000824810858245b245a2459b03139330039",
5: "052004010002b7a60c26040242420c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d047465737400320032013202320380021234292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e01022581f8acd19181cf959a1281f8acd19181cf951a81f8acd19181cf1581f8acd191810f082209240a220b230c240d250e230f23102311231223132314181b1c28171615400003290349483403350222231d4a484848482a50512a632223524100034200004322602261222b634848222862482864286548482228246628226723286828692322700048482371004848361c0037001a0031183119311b311d311e311f312023221e312131223123312431253126312731283129312a312b312c312d312e312f44782522531422542b2455220824564c4d4b0222382124391c0081e80780046a6f686e2281d00f23241f880003420001892224902291922494249593a0a1a2a3a4a5a6a7a8a9aaabacadae24af3a00003b003c003d816472064e014f0180070123456789abcd57000823810858235b235a2359b0313933003922c01a23c1001a2323c21a",
}

func pseudoOp(opcode string) bool {
Expand Down Expand Up @@ -494,13 +501,15 @@ func TestAssembleTxna(t *testing.T) {
testLine(t, "gtxna 0 Sender 256", AssemblerMaxVersion, "gtxna unknown field: \"Sender\"")
testLine(t, "txn Accounts 0", 1, "txn expects one argument")
testLine(t, "txn Accounts 0 1", 2, "txn expects one or two arguments")
testLine(t, "txna Accounts 0 1", AssemblerMaxVersion, "txna expects two arguments")
testLine(t, "txna Accounts 0 1", AssemblerMaxVersion, "txna expects two immediate arguments")
testLine(t, "txnas Accounts 1", AssemblerMaxVersion, "txnas expects one immediate argument")
testLine(t, "txna Accounts a", AssemblerMaxVersion, "strconv.ParseUint...")
testLine(t, "gtxn 0 Sender 0", 1, "gtxn expects two arguments")
testLine(t, "gtxn 0 Sender 1 2", 2, "gtxn expects two or three arguments")
testLine(t, "gtxna 0 Accounts 1 2", AssemblerMaxVersion, "gtxna expects three arguments")
testLine(t, "gtxna a Accounts 0", AssemblerMaxVersion, "strconv.ParseUint...")
testLine(t, "gtxna 0 Accounts a", AssemblerMaxVersion, "strconv.ParseUint...")
testLine(t, "gtxnas Accounts 1 2", AssemblerMaxVersion, "gtxnas expects two immediate arguments")
testLine(t, "txn ABC", 2, "txn unknown field: \"ABC\"")
testLine(t, "gtxn 0 ABC", 2, "gtxn unknown field: \"ABC\"")
testLine(t, "gtxn a ABC", 2, "strconv.ParseUint...")
Expand Down
9 changes: 8 additions & 1 deletion data/transactions/logic/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,10 @@ var opDocByName = map[string]string{
"b~": "X with all bits inverted",

"log": "write bytes to log state of the current application",

"txnas": "pop an index A. push Ath value of the array field F of the current transaction",
"gtxnas": "pop an index A. push Ath value of the array field F from the Tth transaction in the current group",
"gtxnsas": "pop an index A and an index B. push Bth value of the array field F from the Ath transaction in the current group",
}

// OpDoc returns a description of the op
Expand Down Expand Up @@ -191,6 +195,9 @@ var opcodeImmediateNotes = map[string]string{
"asset_holding_get": "{uint8 asset holding field index}",
"asset_params_get": "{uint8 asset params field index}",
"app_params_get": "{uint8 app params field index}",
"txnas": "{uint8 transaction field index}",
"gtxnas": "{uint8 transaction group index} {uint8 transaction field index}",
"gtxnsas": "{uint8 transaction field index}",
}

// OpImmediateNote returns a short string about immediate data which follows the op byte
Expand Down Expand Up @@ -252,7 +259,7 @@ var OpGroups = map[string][]string{
"Byte Array Slicing": {"substring", "substring3", "extract", "extract3", "extract16bits", "extract32bits", "extract64bits"},
"Byteslice Arithmetic": {"b+", "b-", "b/", "b*", "b<", "b>", "b<=", "b>=", "b==", "b!=", "b%"},
"Byteslice Logic": {"b|", "b&", "b^", "b~"},
"Loading Values": {"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", "txn", "gtxn", "txna", "gtxna", "gtxns", "gtxnsa", "global", "load", "store", "gload", "gloads", "gaid", "gaids"},
"Loading Values": {"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", "txn", "gtxn", "txna", "gtxna", "gtxns", "gtxnsa", "global", "load", "store", "gload", "gloads", "gaid", "gaids", "txnas", "gtxnas", "gtxnsas"},
"Flow Control": {"err", "bnz", "bz", "b", "return", "pop", "dup", "dup2", "dig", "cover", "uncover", "swap", "select", "assert", "callsub", "retsub"},
"State Access": {"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", "log"},
}
Expand Down
83 changes: 83 additions & 0 deletions data/transactions/logic/eval.go
Original file line number Diff line number Diff line change
Expand Up @@ -2062,6 +2062,29 @@ func opTxna(cx *evalContext) {
cx.stack = append(cx.stack, sv)
}

func opTxnas(cx *evalContext) {
last := len(cx.stack) - 1

field := TxnField(uint64(cx.program[cx.pc+1]))
fs, ok := txnFieldSpecByField[field]
if !ok || fs.version > cx.version {
cx.err = fmt.Errorf("invalid txn field %d", field)
return
}
_, ok = txnaFieldSpecByField[field]
if !ok {
cx.err = fmt.Errorf("txnas unsupported field %d", field)
return
}
arrayFieldIdx := cx.stack[last].Uint
sv, err := cx.txnFieldToStack(&cx.Txn.Txn, field, arrayFieldIdx, cx.GroupIndex)
if err != nil {
cx.err = err
return
}
cx.stack[last] = sv
}

func opGtxn(cx *evalContext) {
gtxid := int(uint(cx.program[cx.pc+1]))
if gtxid >= len(cx.TxnGroup) {
Expand Down Expand Up @@ -2122,6 +2145,35 @@ func opGtxna(cx *evalContext) {
cx.stack = append(cx.stack, sv)
}

func opGtxnas(cx *evalContext) {
last := len(cx.stack) - 1

gtxid := int(uint(cx.program[cx.pc+1]))
if gtxid >= len(cx.TxnGroup) {
cx.err = fmt.Errorf("gtxnas lookup TxnGroup[%d] but it only has %d", gtxid, len(cx.TxnGroup))
return
}
tx := &cx.TxnGroup[gtxid].Txn
field := TxnField(uint64(cx.program[cx.pc+2]))
fs, ok := txnFieldSpecByField[field]
if !ok || fs.version > cx.version {
cx.err = fmt.Errorf("invalid txn field %d", field)
return
}
_, ok = txnaFieldSpecByField[field]
if !ok {
cx.err = fmt.Errorf("gtxnas unsupported field %d", field)
return
}
arrayFieldIdx := cx.stack[last].Uint
sv, err := cx.txnFieldToStack(tx, field, arrayFieldIdx, gtxid)
if err != nil {
cx.err = err
return
}
cx.stack[last] = sv
}

func opGtxns(cx *evalContext) {
last := len(cx.stack) - 1
gtxid := int(cx.stack[last].Uint)
Expand Down Expand Up @@ -2184,6 +2236,37 @@ func opGtxnsa(cx *evalContext) {
cx.stack[last] = sv
}

func opGtxnsas(cx *evalContext) {
last := len(cx.stack) - 1
prev := last - 1

gtxid := int(cx.stack[prev].Uint)
if gtxid >= len(cx.TxnGroup) {
cx.err = fmt.Errorf("gtxnsa lookup TxnGroup[%d] but it only has %d", gtxid, len(cx.TxnGroup))
return
}
tx := &cx.TxnGroup[gtxid].Txn
field := TxnField(uint64(cx.program[cx.pc+1]))
fs, ok := txnFieldSpecByField[field]
if !ok || fs.version > cx.version {
cx.err = fmt.Errorf("invalid txn field %d", field)
return
}
_, ok = txnaFieldSpecByField[field]
if !ok {
cx.err = fmt.Errorf("gtxnsa unsupported field %d", field)
return
}
arrayFieldIdx := cx.stack[last].Uint
sv, err := cx.txnFieldToStack(tx, field, arrayFieldIdx, gtxid)
if err != nil {
cx.err = err
return
}
cx.stack[prev] = sv
cx.stack = cx.stack[:last]
}

func opGaidImpl(cx *evalContext, groupIdx int, opName string) (sv stackValue, err error) {
if groupIdx >= len(cx.TxnGroup) {
err = fmt.Errorf("%s lookup TxnGroup[%d] but it only has %d", opName, groupIdx, len(cx.TxnGroup))
Expand Down
3 changes: 3 additions & 0 deletions data/transactions/logic/evalStateful_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2843,6 +2843,9 @@ func TestReturnTypes(t *testing.T) {
"pushbytes": `pushbytes "jojogoodgorilla"`,
"app_params_get": "app_params_get AppGlobalNumUint",
"extract": "extract 0 2",
"txnas": "txnas ApplicationArgs",
"gtxnas": "gtxnas 0 ApplicationArgs",
"gtxnsas": "pop; pop; int 0; int 0; gtxnsas ApplicationArgs",
}

byName := OpsByName[LogicVersion]
Expand Down
Loading