summaryrefslogtreecommitdiff
path: root/data/transactions/logic/assembler.go
diff options
context:
space:
mode:
Diffstat (limited to 'data/transactions/logic/assembler.go')
-rw-r--r--data/transactions/logic/assembler.go300
1 files changed, 268 insertions, 32 deletions
diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go
index c5e38d632..58e597ecd 100644
--- a/data/transactions/logic/assembler.go
+++ b/data/transactions/logic/assembler.go
@@ -386,7 +386,7 @@ func (ops *OpStream) ByteLiteral(val []byte) {
found := false
var constIndex uint
for i, cv := range ops.bytec {
- if bytes.Compare(cv, val) == 0 {
+ if bytes.Equal(cv, val) {
found = true
constIndex = uint(i)
break
@@ -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 {
@@ -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")
@@ -853,16 +875,16 @@ func assembleGtxn(ops *OpStream, spec *OpSpec, args []string) error {
return ops.error(err)
}
if slot > 255 {
- return ops.errorf("gtxn transaction index beyond 255: %d", slot)
+ return ops.errorf("%s transaction index beyond 255: %d", spec.Name, slot)
}
fs, ok := txnFieldSpecByName[args[1]]
if !ok {
- return ops.errorf("gtxn unknown field: %#v", args[1])
+ return ops.errorf("%s unknown field: %#v", spec.Name, args[1])
}
_, ok = txnaFieldSpecByField[fs.field]
if ok {
- return ops.errorf("found array field %#v in gtxn op", args[1])
+ return ops.errorf("found array field %#v in %s op", args[1], spec.Name)
}
if fs.version > ops.Version {
return ops.errorf("field %#v available in version %d. Missed #pragma version?", args[1], fs.version)
@@ -883,38 +905,38 @@ func assembleGtxn2(ops *OpStream, spec *OpSpec, args []string) error {
gtxna := OpsByName[ops.Version]["gtxna"]
return assembleGtxna(ops, &gtxna, args)
}
- return ops.error("gtxn expects two or three arguments")
+ return ops.errorf("%s expects two or three arguments", spec.Name)
}
func assembleGtxna(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) != 3 {
- return ops.error("gtxna expects three arguments")
+ return ops.errorf("%s expects three arguments", spec.Name)
}
slot, err := strconv.ParseUint(args[0], 0, 64)
if err != nil {
return ops.error(err)
}
if slot > 255 {
- return ops.errorf("gtxna group index beyond 255: %d", slot)
+ return ops.errorf("%s group index beyond 255: %d", spec.Name, slot)
}
fs, ok := txnFieldSpecByName[args[1]]
if !ok {
- return ops.errorf("gtxna unknown field: %#v", args[1])
+ return ops.errorf("%s unknown field: %#v", spec.Name, args[1])
}
_, ok = txnaFieldSpecByField[fs.field]
if !ok {
- return ops.errorf("gtxna unknown field: %#v", args[1])
+ return ops.errorf("%s unknown field: %#v", spec.Name, args[1])
}
if fs.version > ops.Version {
- return ops.errorf("gtxna %#v available in version %d. Missed #pragma version?", args[1], fs.version)
+ return ops.errorf("%s %#v available in version %d. Missed #pragma version?", spec.Name, args[1], fs.version)
}
arrayFieldIdx, err := strconv.ParseUint(args[2], 0, 64)
if err != nil {
return ops.error(err)
}
if arrayFieldIdx > 255 {
- return ops.errorf("gtxna array index beyond 255: %d", arrayFieldIdx)
+ return ops.errorf("%s array index beyond 255: %d", spec.Name, arrayFieldIdx)
}
ops.pending.WriteByte(spec.Opcode)
@@ -925,17 +947,49 @@ 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.errorf("%s expects two immediate arguments", spec.Name)
+ }
+
+ slot, err := strconv.ParseUint(args[0], 0, 64)
+ if err != nil {
+ return ops.error(err)
+ }
+ if slot > 255 {
+ return ops.errorf("%s group index beyond 255: %d", spec.Name, slot)
+ }
+
+ fs, ok := txnFieldSpecByName[args[1]]
+ if !ok {
+ return ops.errorf("%s unknown field: %#v", spec.Name, args[1])
+ }
+ _, ok = txnaFieldSpecByField[fs.field]
+ if !ok {
+ return ops.errorf("%s unknown field: %#v", spec.Name, args[1])
+ }
+ if fs.version > ops.Version {
+ return ops.errorf("%s %#v available in version %d. Missed #pragma version?", spec.Name, 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"]
return assembleGtxnsa(ops, &gtxnsa, args)
}
if len(args) != 1 {
- return ops.error("gtxns expects one or two immediate arguments")
+ return ops.errorf("%s expects one or two immediate arguments", spec.Name)
}
fs, ok := txnFieldSpecByName[args[0]]
if !ok {
- return ops.errorf("gtxns unknown field: %#v", args[0])
+ return ops.errorf("%s unknown field: %#v", spec.Name, args[0])
}
_, ok = txnaFieldSpecByField[fs.field]
if ok {
@@ -953,26 +1007,110 @@ func assembleGtxns(ops *OpStream, spec *OpSpec, args []string) error {
func assembleGtxnsa(ops *OpStream, spec *OpSpec, args []string) error {
if len(args) != 2 {
- return ops.error("gtxnsa expects two immediate arguments")
+ return ops.errorf("%s expects two immediate arguments", spec.Name)
+ }
+ fs, ok := txnFieldSpecByName[args[0]]
+ if !ok {
+ return ops.errorf("%s unknown field: %#v", spec.Name, args[0])
+ }
+ _, ok = txnaFieldSpecByField[fs.field]
+ if !ok {
+ return ops.errorf("%s unknown field: %#v", spec.Name, args[0])
+ }
+ if fs.version > ops.Version {
+ return ops.errorf("%s %#v available in version %d. Missed #pragma version?", spec.Name, args[0], fs.version)
+ }
+ arrayFieldIdx, err := strconv.ParseUint(args[1], 0, 64)
+ if err != nil {
+ return ops.error(err)
+ }
+ if arrayFieldIdx > 255 {
+ return ops.errorf("%s array index beyond 255: %d", spec.Name, arrayFieldIdx)
+ }
+ ops.pending.WriteByte(spec.Opcode)
+ ops.pending.WriteByte(uint8(fs.field))
+ ops.pending.WriteByte(uint8(arrayFieldIdx))
+ ops.returns(fs.ftype)
+ return nil
+}
+
+func assembleGtxnsas(ops *OpStream, spec *OpSpec, args []string) error {
+ if len(args) != 1 {
+ return ops.errorf("%s expects one immediate argument", spec.Name)
+ }
+ fs, ok := txnFieldSpecByName[args[0]]
+ if !ok {
+ return ops.errorf("%s unknown field: %#v", spec.Name, args[0])
+ }
+ _, ok = txnaFieldSpecByField[fs.field]
+ if !ok {
+ return ops.errorf("%s unknown field: %#v", spec.Name, args[0])
+ }
+ if fs.version > ops.Version {
+ return ops.errorf("%s %#v available in version %d. Missed #pragma version?", spec.Name, args[0], fs.version)
+ }
+ ops.pending.WriteByte(spec.Opcode)
+ ops.pending.WriteByte(uint8(fs.field))
+ ops.returns(fs.ftype)
+ return nil
+}
+
+// asmItxn delegates to asmItxnOnly or asmItxna depending on number of operands
+func asmItxn(ops *OpStream, spec *OpSpec, args []string) error {
+ if len(args) == 1 {
+ return asmItxnOnly(ops, spec, args)
+ }
+ if len(args) == 2 {
+ itxna := OpsByName[ops.Version]["itxna"]
+ return asmItxna(ops, &itxna, args)
+ }
+ return ops.errorf("%s expects one or two arguments", spec.Name)
+}
+
+func asmItxnOnly(ops *OpStream, spec *OpSpec, args []string) error {
+ if len(args) != 1 {
+ return ops.errorf("%s expects one argument", spec.Name)
+ }
+ fs, ok := txnFieldSpecByName[args[0]]
+ if !ok {
+ return ops.errorf("%s unknown field: %#v", spec.Name, args[0])
+ }
+ _, ok = txnaFieldSpecByField[fs.field]
+ if ok {
+ return ops.errorf("found array field %#v in %s op", args[0], spec.Name)
+ }
+ if fs.version > ops.Version {
+ return ops.errorf("field %#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 asmItxna(ops *OpStream, spec *OpSpec, args []string) error {
+ if len(args) != 2 {
+ return ops.errorf("%s expects two immediate arguments", spec.Name)
}
fs, ok := txnFieldSpecByName[args[0]]
if !ok {
- return ops.errorf("gtxnsa unknown field: %#v", args[0])
+ return ops.errorf("%s unknown field: %#v", spec.Name, args[0])
}
_, ok = txnaFieldSpecByField[fs.field]
if !ok {
- return ops.errorf("gtxnsa unknown field: %#v", args[0])
+ return ops.errorf("%s unknown field: %#v", spec.Name, args[0])
}
if fs.version > ops.Version {
- return ops.errorf("gtxnsa %#v available in version %d. Missed #pragma version?", args[0], fs.version)
+ return ops.errorf("%s %#v available in version %d. Missed #pragma version?", spec.Name, args[0], fs.version)
}
arrayFieldIdx, err := strconv.ParseUint(args[1], 0, 64)
if err != nil {
return ops.error(err)
}
if arrayFieldIdx > 255 {
- return ops.errorf("gtxnsa array index beyond 255: %d", arrayFieldIdx)
+ return ops.errorf("%s array index beyond 255: %d", spec.Name, arrayFieldIdx)
}
+
ops.pending.WriteByte(spec.Opcode)
ops.pending.WriteByte(uint8(fs.field))
ops.pending.WriteByte(uint8(arrayFieldIdx))
@@ -1064,6 +1202,43 @@ func assembleAppParams(ops *OpStream, spec *OpSpec, args []string) error {
return nil
}
+func asmTxField(ops *OpStream, spec *OpSpec, args []string) error {
+ if len(args) != 1 {
+ return ops.errorf("%s expects one argument", spec.Name)
+ }
+ fs, ok := txnFieldSpecByName[args[0]]
+ if !ok {
+ return ops.errorf("txn unknown field: %#v", args[0])
+ }
+ _, ok = txnaFieldSpecByField[fs.field]
+ if ok {
+ return ops.errorf("found array field %#v in %s op", args[0], spec.Name)
+ }
+ ops.pending.WriteByte(spec.Opcode)
+ ops.pending.WriteByte(uint8(fs.field))
+ return nil
+}
+
+func assembleEcdsa(ops *OpStream, spec *OpSpec, args []string) error {
+ if len(args) != 1 {
+ return ops.errorf("%s expects one argument", spec.Name)
+ }
+
+ cs, ok := ecdsaCurveSpecByName[args[0]]
+ if !ok {
+ return ops.errorf("%s unknown field: %#v", spec.Name, args[0])
+ }
+ if cs.version > ops.Version {
+ //nolint:errcheck // we continue to maintain typestack
+ ops.errorf("%s %s available in version %d. Missed #pragma version?", spec.Name, args[0], cs.version)
+ }
+
+ val := cs.field
+ ops.pending.WriteByte(spec.Opcode)
+ ops.pending.WriteByte(uint8(val))
+ return nil
+}
+
type assembleFunc func(*OpStream, *OpSpec, []string) error
// Basic assembly. Any extra bytes of opcode are encoded as byte immediates.
@@ -1228,6 +1403,17 @@ func typeUncover(ops *OpStream, args []string) (StackTypes, StackTypes) {
return anys, returns
}
+func typeTxField(ops *OpStream, args []string) (StackTypes, StackTypes) {
+ if len(args) != 1 {
+ return oneAny, nil
+ }
+ fs, ok := txnFieldSpecByName[args[0]]
+ if !ok {
+ return oneAny, nil
+ }
+ return StackTypes{fs.ftype}, nil
+}
+
// keywords handle parsing and assembling special asm language constructs like 'addr'
// We use OpSpec here, but somewhat degenerate, since they don't have opcodes or eval functions
var keywords = map[string]OpSpec{
@@ -1960,8 +2146,16 @@ type disassembleState struct {
labelCount int
pendingLabels map[int]string
+ // If we find a (back) jump to a label we did not generate
+ // (because we didn't know about it yet), rerun is set to
+ // true, and we make a second attempt to assemble once the
+ // first attempt is done. The second attempt retains all the
+ // labels found in the first pass. In effect, the first
+ // attempt to assemble becomes a first-pass in a two-pass
+ // assembly process that simply collects jump target labels.
+ rerun bool
+
nextpc int
- err error
intc []uint64
bytec [][]byte
@@ -1972,6 +2166,9 @@ func (dis *disassembleState) putLabel(label string, target int) {
dis.pendingLabels = make(map[int]string)
}
dis.pendingLabels[target] = label
+ if target <= dis.pc {
+ dis.rerun = true
+ }
}
func (dis *disassembleState) outputLabelIfNeeded() (err error) {
@@ -2031,7 +2228,7 @@ func parseIntcblock(program []byte, pc int) (intc []uint64, nextpc int, err erro
return
}
-func checkIntConstBlock(cx *evalContext) error {
+func checkIntConstBlock(cx *EvalContext) error {
pos := cx.pc + 1
numInts, bytesUsed := binary.Uvarint(cx.program[pos:])
if bytesUsed <= 0 {
@@ -2099,7 +2296,7 @@ func parseBytecBlock(program []byte, pc int) (bytec [][]byte, nextpc int, err er
return
}
-func checkByteConstBlock(cx *evalContext) error {
+func checkByteConstBlock(cx *EvalContext) error {
pos := cx.pc + 1
numItems, bytesUsed := binary.Uvarint(cx.program[pos:])
if bytesUsed <= 0 {
@@ -2257,7 +2454,7 @@ func disPushInt(dis *disassembleState, spec *OpSpec) (string, error) {
dis.nextpc = pos + bytesUsed
return fmt.Sprintf("%s %d", spec.Name, val), nil
}
-func checkPushInt(cx *evalContext) error {
+func checkPushInt(cx *EvalContext) error {
opPushInt(cx)
return cx.err
}
@@ -2277,12 +2474,12 @@ func disPushBytes(dis *disassembleState, spec *OpSpec) (string, error) {
dis.nextpc = int(end)
return fmt.Sprintf("%s 0x%s // %s", spec.Name, hex.EncodeToString(bytes), guessByteFormat(bytes)), nil
}
-func checkPushBytes(cx *evalContext) error {
+func checkPushBytes(cx *EvalContext) error {
opPushBytes(cx)
return cx.err
}
-// This is also used to disassemble gtxns
+// This is also used to disassemble gtxns, gtxnsas, txnas, itxn
func disTxn(dis *disassembleState, spec *OpSpec) (string, error) {
lastIdx := dis.pc + 1
if len(dis.program) <= lastIdx {
@@ -2313,6 +2510,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 {
@@ -2325,7 +2523,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) {
@@ -2428,16 +2626,46 @@ func disAppParams(dis *disassembleState, spec *OpSpec) (string, error) {
return fmt.Sprintf("%s %s", spec.Name, AppParamsFieldNames[arg]), nil
}
+func disTxField(dis *disassembleState, spec *OpSpec) (string, error) {
+ lastIdx := dis.pc + 1
+ if len(dis.program) <= lastIdx {
+ missing := lastIdx - len(dis.program) + 1
+ return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing)
+ }
+ dis.nextpc = dis.pc + 2
+ arg := dis.program[dis.pc+1]
+ if int(arg) >= len(TxnFieldNames) {
+ return "", fmt.Errorf("invalid %s arg index %d at pc=%d", spec.Name, arg, dis.pc)
+ }
+ return fmt.Sprintf("%s %s", spec.Name, TxnFieldNames[arg]), nil
+}
+
+func disEcdsa(dis *disassembleState, spec *OpSpec) (string, error) {
+ lastIdx := dis.pc + 1
+ if len(dis.program) <= lastIdx {
+ missing := lastIdx - len(dis.program) + 1
+ return "", fmt.Errorf("unexpected %s opcode end: missing %d bytes", spec.Name, missing)
+ }
+ dis.nextpc = dis.pc + 2
+ arg := dis.program[dis.pc+1]
+ if int(arg) >= len(EcdsaCurveNames) {
+ return "", fmt.Errorf("invalid curve arg index %d at pc=%d", arg, dis.pc)
+ }
+ return fmt.Sprintf("%s %s", spec.Name, EcdsaCurveNames[arg]), nil
+}
+
type disInfo struct {
pcOffset []PCOffset
hasStatefulOps bool
}
-// disassembleInstrumented is like Disassemble, but additionally returns where
-// each program counter value maps in the disassembly
-func disassembleInstrumented(program []byte) (text string, ds disInfo, err error) {
+// disassembleInstrumented is like Disassemble, but additionally
+// returns where each program counter value maps in the
+// disassembly. If the labels names are known, they may be passed in.
+// When doing so, labels for all jump targets must be provided.
+func disassembleInstrumented(program []byte, labels map[int]string) (text string, ds disInfo, err error) {
out := strings.Builder{}
- dis := disassembleState{program: program, out: &out}
+ dis := disassembleState{program: program, out: &out, pendingLabels: labels}
version, vlen := binary.Uvarint(program)
if vlen <= 0 {
fmt.Fprintf(dis.out, "// invalid version\n")
@@ -2489,18 +2717,26 @@ func disassembleInstrumented(program []byte) (text string, ds disInfo, err error
}
text = out.String()
+
+ if dis.rerun {
+ if labels != nil {
+ err = errors.New("rerun even though we had labels")
+ return
+ }
+ return disassembleInstrumented(program, dis.pendingLabels)
+ }
return
}
// Disassemble produces a text form of program bytes.
// AssembleString(Disassemble()) should result in the same program bytes.
func Disassemble(program []byte) (text string, err error) {
- text, _, err = disassembleInstrumented(program)
+ text, _, err = disassembleInstrumented(program, nil)
return
}
// HasStatefulOps checks if the program has stateful opcodes
func HasStatefulOps(program []byte) (bool, error) {
- _, ds, err := disassembleInstrumented(program)
+ _, ds, err := disassembleInstrumented(program, nil)
return ds.hasStatefulOps, err
}