From a08e8da64ddd3f3d338b6d4026bc48149e76bbbb Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Mon, 17 May 2021 11:50:16 -0400 Subject: [PATCH 01/35] Add mechanism for accessing previous `EvalContext`s from within a txn group * Add `Cx` and `CxGroup` properties to the `EvalParams` struct * Expose (and rename) `evalContext` to `EvalContext` --- data/transactions/logic/assembler.go | 8 +- data/transactions/logic/assembler_test.go | 2 +- data/transactions/logic/debugger.go | 4 +- data/transactions/logic/eval.go | 259 ++++++++++--------- data/transactions/logic/evalStateful_test.go | 2 +- data/transactions/logic/eval_test.go | 6 +- ledger/eval.go | 30 ++- 7 files changed, 164 insertions(+), 147 deletions(-) diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 5c4d864d01..82820b4eeb 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -1383,7 +1383,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 { @@ -1451,7 +1451,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 { @@ -1609,7 +1609,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 } @@ -1629,7 +1629,7 @@ func disPushBytes(dis *disassembleState, spec *OpSpec) (string, error) { dis.nextpc = int(end) return fmt.Sprintf("%s 0x%s", spec.Name, hex.EncodeToString(bytes)), nil } -func checkPushBytes(cx *evalContext) error { +func checkPushBytes(cx *EvalContext) error { opPushBytes(cx) return cx.err } diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 1fca9c8494..e6273f03bf 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -1650,7 +1650,7 @@ func TestErrShortBytecblock(t *testing.T) { _, _, err = parseIntcblock(ops.Program, 0) require.Equal(t, err, errShortIntcblock) - var cx evalContext + var cx EvalContext cx.program = ops.Program err = checkIntConstBlock(&cx) require.Equal(t, err, errShortIntcblock) diff --git a/data/transactions/logic/debugger.go b/data/transactions/logic/debugger.go index 021a53faf1..3c49b57cc8 100644 --- a/data/transactions/logic/debugger.go +++ b/data/transactions/logic/debugger.go @@ -86,7 +86,7 @@ func GetProgramID(program []byte) string { return hex.EncodeToString(hash[:]) } -func makeDebugState(cx *evalContext) DebugState { +func makeDebugState(cx *EvalContext) DebugState { disasm, dsInfo, err := disassembleInstrumented(cx.program) if err != nil { // Report disassembly error as program text @@ -194,7 +194,7 @@ func valueDeltaToValueDelta(vd *basics.ValueDelta) basics.ValueDelta { } } -func (cx *evalContext) refreshDebugState() *DebugState { +func (cx *EvalContext) refreshDebugState() *DebugState { ds := &cx.debugState // Update pc, line, error, stack, and scratch space diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 91557d3bc0..75dd678796 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -149,12 +149,18 @@ type EvalParams struct { // the transaction being evaluated Txn *transactions.SignedTxn + // evaluation context + Cx *EvalContext + Proto *config.ConsensusParams Trace io.Writer TxnGroup []transactions.SignedTxn + // for accessing evaluation contexts of other txns + CxGroup []*EvalContext + // GroupIndex should point to Txn within TxnGroup GroupIndex int @@ -174,8 +180,8 @@ type EvalParams struct { runModeFlags runMode } -type opEvalFunc func(cx *evalContext) -type opCheckFunc func(cx *evalContext) error +type opEvalFunc func(cx *EvalContext) +type opCheckFunc func(cx *EvalContext) error type runMode uint64 @@ -221,7 +227,7 @@ func (ep EvalParams) log() logging.Logger { return logging.Base() } -type evalContext struct { +type EvalContext struct { EvalParams stack []stackValue @@ -306,24 +312,22 @@ var errTooManyArgs = errors.New("LogicSig has too many arguments") // EvalStateful executes stateful TEAL program func EvalStateful(program []byte, params EvalParams) (pass bool, err error) { - var cx evalContext - cx.EvalParams = params + cx := params.Cx cx.runModeFlags = runModeApplication - return eval(program, &cx) + return eval(program, cx) } // Eval checks to see if a transaction passes logic // A program passes successfully if it finishes with one int element on the stack that is non-zero. func Eval(program []byte, params EvalParams) (pass bool, err error) { - var cx evalContext - cx.EvalParams = params + cx := params.Cx cx.runModeFlags = runModeSignature - return eval(program, &cx) + return eval(program, cx) } // eval implementation // A program passes successfully if it finishes with one int element on the stack that is non-zero. -func eval(program []byte, cx *evalContext) (pass bool, err error) { +func eval(program []byte, cx *EvalContext) (pass bool, err error) { defer func() { if x := recover(); x != nil { buf := make([]byte, 16*1024) @@ -488,10 +492,9 @@ func check(program []byte, params EvalParams) (err error) { return fmt.Errorf("program version must be >= %d for this transaction group, but have version %d", minVersion, version) } - var cx evalContext + cx := params.Cx cx.version = version cx.pc = vlen - cx.EvalParams = params cx.program = program cx.branchTargets = make(map[int]bool) cx.instructionStarts = make(map[int]bool) @@ -546,7 +549,7 @@ func boolToUint(x bool) uint64 { // MaxStackDepth should move to consensus params const MaxStackDepth = 1000 -func (cx *evalContext) step() { +func (cx *EvalContext) step() { opcode := cx.program[cx.pc] spec := &opsByOpcode[cx.version][opcode] @@ -647,7 +650,7 @@ func (cx *evalContext) step() { } } -func (cx *evalContext) checkStep() (int, error) { +func (cx *EvalContext) checkStep() (int, error) { cx.instructionStarts[cx.pc] = true opcode := cx.program[cx.pc] spec := &opsByOpcode[cx.version][opcode] @@ -689,11 +692,11 @@ func (cx *evalContext) checkStep() (int, error) { return deets.Cost, nil } -func opErr(cx *evalContext) { +func opErr(cx *EvalContext) { cx.err = errors.New("TEAL runtime encountered err opcode") } -func opReturn(cx *evalContext) { +func opReturn(cx *EvalContext) { // Achieve the end condition: // Take the last element on the stack and make it the return value (only element on the stack) // Move the pc to the end of the program @@ -703,7 +706,7 @@ func opReturn(cx *evalContext) { cx.nextpc = len(cx.program) } -func opAssert(cx *evalContext) { +func opAssert(cx *EvalContext) { last := len(cx.stack) - 1 if cx.stack[last].Uint != 0 { cx.stack = cx.stack[:last] @@ -712,13 +715,13 @@ func opAssert(cx *evalContext) { cx.err = errors.New("assert failed") } -func opSwap(cx *evalContext) { +func opSwap(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 cx.stack[last], cx.stack[prev] = cx.stack[prev], cx.stack[last] } -func opSelect(cx *evalContext) { +func opSelect(cx *EvalContext) { last := len(cx.stack) - 1 // condition on top prev := last - 1 // true is one down pprev := prev - 1 // false below that @@ -729,14 +732,14 @@ func opSelect(cx *evalContext) { cx.stack = cx.stack[:prev] } -func opSHA256(cx *evalContext) { +func opSHA256(cx *EvalContext) { last := len(cx.stack) - 1 hash := sha256.Sum256(cx.stack[last].Bytes) cx.stack[last].Bytes = hash[:] } // The Keccak256 variant of SHA-3 is implemented for compatibility with Ethereum -func opKeccak256(cx *evalContext) { +func opKeccak256(cx *EvalContext) { last := len(cx.stack) - 1 hasher := sha3.NewLegacyKeccak256() hasher.Write(cx.stack[last].Bytes) @@ -751,13 +754,13 @@ func opKeccak256(cx *evalContext) { // stability and portability in case the rest of Algorand ever moves // to a different default hash. For stability of this language, at // that time a new opcode should be made with the new hash. -func opSHA512_256(cx *evalContext) { +func opSHA512_256(cx *EvalContext) { last := len(cx.stack) - 1 hash := sha512.Sum512_256(cx.stack[last].Bytes) cx.stack[last].Bytes = hash[:] } -func opPlus(cx *evalContext) { +func opPlus(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 cx.stack[prev].Uint += cx.stack[last].Uint @@ -776,7 +779,7 @@ func opAddwImpl(x, y uint64) (carry uint64, sum uint64) { return } -func opAddw(cx *evalContext) { +func opAddw(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 carry, sum := opAddwImpl(cx.stack[prev].Uint, cx.stack[last].Uint) @@ -802,7 +805,7 @@ func opDivwImpl(hiNum, loNum, hiDen, loDen uint64) (hiQuo uint64, loQuo uint64, rem.Uint64() } -func opDivw(cx *evalContext) { +func opDivw(cx *EvalContext) { loDen := len(cx.stack) - 1 hiDen := loDen - 1 if cx.stack[loDen].Uint == 0 && cx.stack[hiDen].Uint == 0 { @@ -819,7 +822,7 @@ func opDivw(cx *evalContext) { cx.stack[loDen].Uint = loRem } -func opMinus(cx *evalContext) { +func opMinus(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 if cx.stack[last].Uint > cx.stack[prev].Uint { @@ -830,7 +833,7 @@ func opMinus(cx *evalContext) { cx.stack = cx.stack[:last] } -func opDiv(cx *evalContext) { +func opDiv(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 if cx.stack[last].Uint == 0 { @@ -841,7 +844,7 @@ func opDiv(cx *evalContext) { cx.stack = cx.stack[:last] } -func opModulo(cx *evalContext) { +func opModulo(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 if cx.stack[last].Uint == 0 { @@ -852,7 +855,7 @@ func opModulo(cx *evalContext) { cx.stack = cx.stack[:last] } -func opMul(cx *evalContext) { +func opMul(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 a := cx.stack[prev].Uint @@ -886,7 +889,7 @@ func opMulwImpl(x, y uint64) (high64 uint64, low64 uint64, err error) { return } -func opMulw(cx *evalContext) { +func opMulw(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 high, low, err := opMulwImpl(cx.stack[prev].Uint, cx.stack[last].Uint) @@ -898,7 +901,7 @@ func opMulw(cx *evalContext) { cx.stack[last].Uint = low } -func opLt(cx *evalContext) { +func opLt(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 cond := cx.stack[prev].Uint < cx.stack[last].Uint @@ -910,7 +913,7 @@ func opLt(cx *evalContext) { cx.stack = cx.stack[:last] } -func opGt(cx *evalContext) { +func opGt(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 cond := cx.stack[prev].Uint > cx.stack[last].Uint @@ -922,7 +925,7 @@ func opGt(cx *evalContext) { cx.stack = cx.stack[:last] } -func opLe(cx *evalContext) { +func opLe(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 cond := cx.stack[prev].Uint <= cx.stack[last].Uint @@ -934,7 +937,7 @@ func opLe(cx *evalContext) { cx.stack = cx.stack[:last] } -func opGe(cx *evalContext) { +func opGe(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 cond := cx.stack[prev].Uint >= cx.stack[last].Uint @@ -946,7 +949,7 @@ func opGe(cx *evalContext) { cx.stack = cx.stack[:last] } -func opAnd(cx *evalContext) { +func opAnd(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 cond := (cx.stack[prev].Uint != 0) && (cx.stack[last].Uint != 0) @@ -958,7 +961,7 @@ func opAnd(cx *evalContext) { cx.stack = cx.stack[:last] } -func opOr(cx *evalContext) { +func opOr(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 cond := (cx.stack[prev].Uint != 0) || (cx.stack[last].Uint != 0) @@ -970,7 +973,7 @@ func opOr(cx *evalContext) { cx.stack = cx.stack[:last] } -func opEq(cx *evalContext) { +func opEq(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 ta := cx.stack[prev].argType() @@ -994,7 +997,7 @@ func opEq(cx *evalContext) { cx.stack = cx.stack[:last] } -func opNeq(cx *evalContext) { +func opNeq(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 ta := cx.stack[prev].argType() @@ -1018,7 +1021,7 @@ func opNeq(cx *evalContext) { cx.stack = cx.stack[:last] } -func opNot(cx *evalContext) { +func opNot(cx *EvalContext) { last := len(cx.stack) - 1 cond := cx.stack[last].Uint == 0 if cond { @@ -1028,13 +1031,13 @@ func opNot(cx *evalContext) { } } -func opLen(cx *evalContext) { +func opLen(cx *EvalContext) { last := len(cx.stack) - 1 cx.stack[last].Uint = uint64(len(cx.stack[last].Bytes)) cx.stack[last].Bytes = nil } -func opItob(cx *evalContext) { +func opItob(cx *EvalContext) { last := len(cx.stack) - 1 ibytes := make([]byte, 8) binary.BigEndian.PutUint64(ibytes, cx.stack[last].Uint) @@ -1043,7 +1046,7 @@ func opItob(cx *evalContext) { cx.stack[last].Bytes = ibytes } -func opBtoi(cx *evalContext) { +func opBtoi(cx *EvalContext) { last := len(cx.stack) - 1 ibytes := cx.stack[last].Bytes if len(ibytes) > 8 { @@ -1059,61 +1062,61 @@ func opBtoi(cx *evalContext) { cx.stack[last].Bytes = nil } -func opBitOr(cx *evalContext) { +func opBitOr(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 cx.stack[prev].Uint = cx.stack[prev].Uint | cx.stack[last].Uint cx.stack = cx.stack[:last] } -func opBitAnd(cx *evalContext) { +func opBitAnd(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 cx.stack[prev].Uint = cx.stack[prev].Uint & cx.stack[last].Uint cx.stack = cx.stack[:last] } -func opBitXor(cx *evalContext) { +func opBitXor(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 cx.stack[prev].Uint = cx.stack[prev].Uint ^ cx.stack[last].Uint cx.stack = cx.stack[:last] } -func opBitNot(cx *evalContext) { +func opBitNot(cx *EvalContext) { last := len(cx.stack) - 1 cx.stack[last].Uint = cx.stack[last].Uint ^ 0xffffffffffffffff } -func opIntConstBlock(cx *evalContext) { +func opIntConstBlock(cx *EvalContext) { cx.intc, cx.nextpc, cx.err = parseIntcblock(cx.program, cx.pc) } -func opIntConstN(cx *evalContext, n uint) { +func opIntConstN(cx *EvalContext, n uint) { if n >= uint(len(cx.intc)) { cx.err = fmt.Errorf("intc [%d] beyond %d constants", n, len(cx.intc)) return } cx.stack = append(cx.stack, stackValue{Uint: cx.intc[n]}) } -func opIntConstLoad(cx *evalContext) { +func opIntConstLoad(cx *EvalContext) { n := uint(cx.program[cx.pc+1]) opIntConstN(cx, n) } -func opIntConst0(cx *evalContext) { +func opIntConst0(cx *EvalContext) { opIntConstN(cx, 0) } -func opIntConst1(cx *evalContext) { +func opIntConst1(cx *EvalContext) { opIntConstN(cx, 1) } -func opIntConst2(cx *evalContext) { +func opIntConst2(cx *EvalContext) { opIntConstN(cx, 2) } -func opIntConst3(cx *evalContext) { +func opIntConst3(cx *EvalContext) { opIntConstN(cx, 3) } -func opPushInt(cx *evalContext) { +func opPushInt(cx *EvalContext) { val, bytesUsed := binary.Uvarint(cx.program[cx.pc+1:]) if bytesUsed <= 0 { cx.err = fmt.Errorf("could not decode int at pc=%d", cx.pc+1) @@ -1124,35 +1127,35 @@ func opPushInt(cx *evalContext) { cx.nextpc = cx.pc + 1 + bytesUsed } -func opByteConstBlock(cx *evalContext) { +func opByteConstBlock(cx *EvalContext) { cx.bytec, cx.nextpc, cx.err = parseBytecBlock(cx.program, cx.pc) } -func opByteConstN(cx *evalContext, n uint) { +func opByteConstN(cx *EvalContext, n uint) { if n >= uint(len(cx.bytec)) { cx.err = fmt.Errorf("bytec [%d] beyond %d constants", n, len(cx.bytec)) return } cx.stack = append(cx.stack, stackValue{Bytes: cx.bytec[n]}) } -func opByteConstLoad(cx *evalContext) { +func opByteConstLoad(cx *EvalContext) { n := uint(cx.program[cx.pc+1]) opByteConstN(cx, n) } -func opByteConst0(cx *evalContext) { +func opByteConst0(cx *EvalContext) { opByteConstN(cx, 0) } -func opByteConst1(cx *evalContext) { +func opByteConst1(cx *EvalContext) { opByteConstN(cx, 1) } -func opByteConst2(cx *evalContext) { +func opByteConst2(cx *EvalContext) { opByteConstN(cx, 2) } -func opByteConst3(cx *evalContext) { +func opByteConst3(cx *EvalContext) { opByteConstN(cx, 3) } -func opPushBytes(cx *evalContext) { +func opPushBytes(cx *EvalContext) { pos := cx.pc + 1 length, bytesUsed := binary.Uvarint(cx.program[pos:]) if bytesUsed <= 0 { @@ -1170,7 +1173,7 @@ func opPushBytes(cx *evalContext) { cx.nextpc = int(end) } -func opArgN(cx *evalContext, n uint64) { +func opArgN(cx *EvalContext, n uint64) { if n >= uint64(len(cx.Txn.Lsig.Args)) { cx.err = fmt.Errorf("cannot load arg[%d] of %d", n, len(cx.Txn.Lsig.Args)) return @@ -1179,24 +1182,24 @@ func opArgN(cx *evalContext, n uint64) { cx.stack = append(cx.stack, stackValue{Bytes: val}) } -func opArg(cx *evalContext) { +func opArg(cx *EvalContext) { n := uint64(cx.program[cx.pc+1]) opArgN(cx, n) } -func opArg0(cx *evalContext) { +func opArg0(cx *EvalContext) { opArgN(cx, 0) } -func opArg1(cx *evalContext) { +func opArg1(cx *EvalContext) { opArgN(cx, 1) } -func opArg2(cx *evalContext) { +func opArg2(cx *EvalContext) { opArgN(cx, 2) } -func opArg3(cx *evalContext) { +func opArg3(cx *EvalContext) { opArgN(cx, 3) } -func branchTarget(cx *evalContext) (int, error) { +func branchTarget(cx *EvalContext) (int, error) { offset := int16(uint16(cx.program[cx.pc+1])<<8 | uint16(cx.program[cx.pc+2])) if offset < 0 && cx.version < backBranchEnabledVersion { return 0, fmt.Errorf("negative branch offset %x", offset) @@ -1217,7 +1220,7 @@ func branchTarget(cx *evalContext) (int, error) { } // checks any branch that is {op} {int16 be offset} -func checkBranch(cx *evalContext) error { +func checkBranch(cx *EvalContext) error { cx.nextpc = cx.pc + 3 target, err := branchTarget(cx) if err != nil { @@ -1232,7 +1235,7 @@ func checkBranch(cx *evalContext) error { cx.branchTargets[target] = true return nil } -func opBnz(cx *evalContext) { +func opBnz(cx *EvalContext) { last := len(cx.stack) - 1 cx.nextpc = cx.pc + 3 isNonZero := cx.stack[last].Uint != 0 @@ -1247,7 +1250,7 @@ func opBnz(cx *evalContext) { } } -func opBz(cx *evalContext) { +func opBz(cx *EvalContext) { last := len(cx.stack) - 1 cx.nextpc = cx.pc + 3 isZero := cx.stack[last].Uint == 0 @@ -1262,7 +1265,7 @@ func opBz(cx *evalContext) { } } -func opB(cx *evalContext) { +func opB(cx *EvalContext) { target, err := branchTarget(cx) if err != nil { cx.err = err @@ -1271,12 +1274,12 @@ func opB(cx *evalContext) { cx.nextpc = target } -func opCallSub(cx *evalContext) { +func opCallSub(cx *EvalContext) { cx.callstack = append(cx.callstack, cx.pc+3) opB(cx) } -func opRetSub(cx *evalContext) { +func opRetSub(cx *EvalContext) { top := len(cx.callstack) - 1 if top < 0 { cx.err = errors.New("retsub with empty callstack") @@ -1287,24 +1290,24 @@ func opRetSub(cx *evalContext) { cx.nextpc = target } -func opPop(cx *evalContext) { +func opPop(cx *EvalContext) { last := len(cx.stack) - 1 cx.stack = cx.stack[:last] } -func opDup(cx *evalContext) { +func opDup(cx *EvalContext) { last := len(cx.stack) - 1 sv := cx.stack[last] cx.stack = append(cx.stack, sv) } -func opDup2(cx *evalContext) { +func opDup2(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 cx.stack = append(cx.stack, cx.stack[prev:]...) } -func opDig(cx *evalContext) { +func opDig(cx *EvalContext) { depth := int(uint(cx.program[cx.pc+1])) idx := len(cx.stack) - 1 - depth // Need to check stack size explicitly here because checkArgs() doesn't understand dig @@ -1317,7 +1320,7 @@ func opDig(cx *evalContext) { cx.stack = append(cx.stack, sv) } -func (cx *evalContext) assetHoldingEnumToValue(holding *basics.AssetHolding, field uint64) (sv stackValue, err error) { +func (cx *EvalContext) assetHoldingEnumToValue(holding *basics.AssetHolding, field uint64) (sv stackValue, err error) { switch AssetHoldingField(field) { case AssetBalance: sv.Uint = holding.Amount @@ -1336,7 +1339,7 @@ func (cx *evalContext) assetHoldingEnumToValue(holding *basics.AssetHolding, fie return } -func (cx *evalContext) assetParamsEnumToValue(params *basics.AssetParams, field uint64) (sv stackValue, err error) { +func (cx *EvalContext) assetParamsEnumToValue(params *basics.AssetParams, field uint64) (sv stackValue, err error) { switch AssetParamsField(field) { case AssetTotal: sv.Uint = params.Total @@ -1375,12 +1378,12 @@ func (cx *evalContext) assetParamsEnumToValue(params *basics.AssetParams, field // TxnFieldToTealValue is a thin wrapper for txnFieldToStack for external use func TxnFieldToTealValue(txn *transactions.Transaction, groupIndex int, field TxnField, arrayFieldIdx uint64) (basics.TealValue, error) { - cx := evalContext{EvalParams: EvalParams{GroupIndex: groupIndex}} + cx := EvalContext{EvalParams: EvalParams{GroupIndex: groupIndex}} sv, err := cx.txnFieldToStack(txn, field, arrayFieldIdx, groupIndex) return sv.toTealValue(), err } -func (cx *evalContext) getTxID(txn *transactions.Transaction, groupIndex int) transactions.Txid { +func (cx *EvalContext) getTxID(txn *transactions.Transaction, groupIndex int) transactions.Txid { // Initialize txidCache if necessary if cx.txidCache == nil { cx.txidCache = make(map[int]transactions.Txid, len(cx.TxnGroup)) @@ -1396,7 +1399,7 @@ func (cx *evalContext) getTxID(txn *transactions.Transaction, groupIndex int) tr return txid } -func (cx *evalContext) txnFieldToStack(txn *transactions.Transaction, field TxnField, arrayFieldIdx uint64, groupIndex int) (sv stackValue, err error) { +func (cx *EvalContext) txnFieldToStack(txn *transactions.Transaction, field TxnField, arrayFieldIdx uint64, groupIndex int) (sv stackValue, err error) { err = nil switch field { case Sender: @@ -1556,7 +1559,7 @@ func (cx *evalContext) txnFieldToStack(txn *transactions.Transaction, field TxnF return } -func opTxn(cx *evalContext) { +func opTxn(cx *EvalContext) { field := TxnField(uint64(cx.program[cx.pc+1])) fs, ok := txnFieldSpecByField[field] if !ok || fs.version > cx.version { @@ -1578,7 +1581,7 @@ func opTxn(cx *evalContext) { cx.stack = append(cx.stack, sv) } -func opTxna(cx *evalContext) { +func opTxna(cx *EvalContext) { field := TxnField(uint64(cx.program[cx.pc+1])) fs, ok := txnFieldSpecByField[field] if !ok || fs.version > cx.version { @@ -1601,7 +1604,7 @@ func opTxna(cx *evalContext) { cx.stack = append(cx.stack, sv) } -func opGtxn(cx *evalContext) { +func opGtxn(cx *EvalContext) { gtxid := int(uint(cx.program[cx.pc+1])) if gtxid >= len(cx.TxnGroup) { cx.err = fmt.Errorf("gtxn lookup TxnGroup[%d] but it only has %d", gtxid, len(cx.TxnGroup)) @@ -1634,7 +1637,7 @@ func opGtxn(cx *evalContext) { cx.stack = append(cx.stack, sv) } -func opGtxna(cx *evalContext) { +func opGtxna(cx *EvalContext) { gtxid := int(uint(cx.program[cx.pc+1])) if gtxid >= len(cx.TxnGroup) { cx.err = fmt.Errorf("gtxna lookup TxnGroup[%d] but it only has %d", gtxid, len(cx.TxnGroup)) @@ -1663,7 +1666,7 @@ func opGtxna(cx *evalContext) { cx.stack = append(cx.stack, sv) } -func opGtxns(cx *evalContext) { +func opGtxns(cx *EvalContext) { last := len(cx.stack) - 1 gtxid := int(cx.stack[last].Uint) if gtxid >= len(cx.TxnGroup) { @@ -1697,7 +1700,7 @@ func opGtxns(cx *evalContext) { cx.stack[last] = sv } -func opGtxnsa(cx *evalContext) { +func opGtxnsa(cx *EvalContext) { last := len(cx.stack) - 1 gtxid := int(cx.stack[last].Uint) if gtxid >= len(cx.TxnGroup) { @@ -1727,7 +1730,7 @@ func opGtxnsa(cx *evalContext) { cx.stack[last] = sv } -func (cx *evalContext) getRound() (rnd uint64, err error) { +func (cx *EvalContext) getRound() (rnd uint64, err error) { if cx.Ledger == nil { err = fmt.Errorf("ledger not available") return @@ -1735,7 +1738,7 @@ func (cx *evalContext) getRound() (rnd uint64, err error) { return uint64(cx.Ledger.Round()), nil } -func (cx *evalContext) getLatestTimestamp() (timestamp uint64, err error) { +func (cx *EvalContext) getLatestTimestamp() (timestamp uint64, err error) { if cx.Ledger == nil { err = fmt.Errorf("ledger not available") return @@ -1748,7 +1751,7 @@ func (cx *evalContext) getLatestTimestamp() (timestamp uint64, err error) { return uint64(ts), nil } -func (cx *evalContext) getApplicationID() (rnd uint64, err error) { +func (cx *EvalContext) getApplicationID() (rnd uint64, err error) { if cx.Ledger == nil { err = fmt.Errorf("ledger not available") return @@ -1756,7 +1759,7 @@ func (cx *evalContext) getApplicationID() (rnd uint64, err error) { return uint64(cx.Ledger.ApplicationID()), nil } -func (cx *evalContext) getCreatorAddress() ([]byte, error) { +func (cx *EvalContext) getCreatorAddress() ([]byte, error) { if cx.Ledger == nil { return nil, fmt.Errorf("ledger not available") } @@ -1766,7 +1769,7 @@ func (cx *evalContext) getCreatorAddress() ([]byte, error) { var zeroAddress basics.Address -func (cx *evalContext) globalFieldToStack(field GlobalField) (sv stackValue, err error) { +func (cx *EvalContext) globalFieldToStack(field GlobalField) (sv stackValue, err error) { switch field { case MinTxnFee: sv.Uint = cx.Proto.MinTxnFee @@ -1794,7 +1797,7 @@ func (cx *evalContext) globalFieldToStack(field GlobalField) (sv stackValue, err return sv, err } -func opGlobal(cx *evalContext) { +func opGlobal(cx *EvalContext) { gindex := uint64(cx.program[cx.pc+1]) globalField := GlobalField(gindex) fs, ok := globalFieldSpecByField[globalField] @@ -1836,14 +1839,14 @@ func (msg Msg) ToBeHashed() (protocol.HashID, []byte) { } // programHash lets us lazily compute H(cx.program) -func (cx *evalContext) programHash() crypto.Digest { +func (cx *EvalContext) programHash() crypto.Digest { if cx.programHashCached == (crypto.Digest{}) { cx.programHashCached = crypto.HashObj(Program(cx.program)) } return cx.programHashCached } -func opEd25519verify(cx *evalContext) { +func opEd25519verify(cx *EvalContext) { last := len(cx.stack) - 1 // index of PK prev := last - 1 // index of signature pprev := prev - 1 // index of data @@ -1872,19 +1875,19 @@ func opEd25519verify(cx *evalContext) { cx.stack = cx.stack[:prev] } -func opLoad(cx *evalContext) { +func opLoad(cx *EvalContext) { gindex := int(uint(cx.program[cx.pc+1])) cx.stack = append(cx.stack, cx.scratch[gindex]) } -func opStore(cx *evalContext) { +func opStore(cx *EvalContext) { gindex := int(uint(cx.program[cx.pc+1])) last := len(cx.stack) - 1 cx.scratch[gindex] = cx.stack[last] cx.stack = cx.stack[:last] } -func opConcat(cx *evalContext) { +func opConcat(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 a := cx.stack[prev].Bytes @@ -1916,14 +1919,14 @@ func substring(x []byte, start, end int) (out []byte, err error) { return } -func opSubstring(cx *evalContext) { +func opSubstring(cx *EvalContext) { last := len(cx.stack) - 1 start := cx.program[cx.pc+1] end := cx.program[cx.pc+2] cx.stack[last].Bytes, cx.err = substring(cx.stack[last].Bytes, int(start), int(end)) } -func opSubstring3(cx *evalContext) { +func opSubstring3(cx *EvalContext) { last := len(cx.stack) - 1 // end prev := last - 1 // start pprev := prev - 1 // bytes @@ -1937,7 +1940,7 @@ func opSubstring3(cx *evalContext) { cx.stack = cx.stack[:prev] } -func opGetBit(cx *evalContext) { +func opGetBit(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 idx := cx.stack[last].Uint @@ -1974,7 +1977,7 @@ func opGetBit(cx *evalContext) { cx.stack = cx.stack[:last] } -func opSetBit(cx *evalContext) { +func opSetBit(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 pprev := prev - 1 @@ -2026,7 +2029,7 @@ func opSetBit(cx *evalContext) { cx.stack = cx.stack[:prev] } -func opGetByte(cx *evalContext) { +func opGetByte(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 @@ -2042,7 +2045,7 @@ func opGetByte(cx *evalContext) { cx.stack = cx.stack[:last] } -func opSetByte(cx *evalContext) { +func opSetByte(cx *EvalContext) { last := len(cx.stack) - 1 prev := last - 1 pprev := prev - 1 @@ -2060,7 +2063,7 @@ func opSetByte(cx *evalContext) { cx.stack = cx.stack[:prev] } -func opBalance(cx *evalContext) { +func opBalance(cx *EvalContext) { last := len(cx.stack) - 1 // account offset accountIdx := cx.stack[last].Uint @@ -2085,7 +2088,7 @@ func opBalance(cx *evalContext) { cx.stack[last].Uint = microAlgos.Raw } -func opMinBalance(cx *evalContext) { +func opMinBalance(cx *EvalContext) { last := len(cx.stack) - 1 // account offset accountIdx := cx.stack[last].Uint @@ -2110,7 +2113,7 @@ func opMinBalance(cx *evalContext) { cx.stack[last].Uint = microAlgos.Raw } -func opAppCheckOptedIn(cx *evalContext) { +func opAppCheckOptedIn(cx *EvalContext) { last := len(cx.stack) - 1 // app id prev := last - 1 // account offset @@ -2143,7 +2146,7 @@ func opAppCheckOptedIn(cx *evalContext) { cx.stack = cx.stack[:last] } -func (cx *evalContext) appReadLocalKey(appIdx uint64, accountIdx uint64, key string) (basics.TealValue, bool, error) { +func (cx *EvalContext) appReadLocalKey(appIdx uint64, accountIdx uint64, key string) (basics.TealValue, bool, error) { // Convert the account offset to an address addr, err := cx.Txn.Txn.AddressByIndex(accountIdx, cx.Txn.Txn.Sender) if err != nil { @@ -2153,7 +2156,7 @@ func (cx *evalContext) appReadLocalKey(appIdx uint64, accountIdx uint64, key str } // appWriteLocalKey writes value to local key/value cow -func (cx *evalContext) appWriteLocalKey(accountIdx uint64, key string, tv basics.TealValue) error { +func (cx *EvalContext) appWriteLocalKey(accountIdx uint64, key string, tv basics.TealValue) error { // Convert the account offset to an address addr, err := cx.Txn.Txn.AddressByIndex(accountIdx, cx.Txn.Txn.Sender) if err != nil { @@ -2163,7 +2166,7 @@ func (cx *evalContext) appWriteLocalKey(accountIdx uint64, key string, tv basics } // appDeleteLocalKey deletes a value from the key/value cow -func (cx *evalContext) appDeleteLocalKey(accountIdx uint64, key string) error { +func (cx *EvalContext) appDeleteLocalKey(accountIdx uint64, key string) error { // Convert the account offset to an address addr, err := cx.Txn.Txn.AddressByIndex(accountIdx, cx.Txn.Txn.Sender) if err != nil { @@ -2172,7 +2175,7 @@ func (cx *evalContext) appDeleteLocalKey(accountIdx uint64, key string) error { return cx.Ledger.DelLocal(addr, key, accountIdx) } -func (cx *evalContext) appReadGlobalKey(foreignAppsIndex uint64, key string) (basics.TealValue, bool, error) { +func (cx *EvalContext) appReadGlobalKey(foreignAppsIndex uint64, key string) (basics.TealValue, bool, error) { if foreignAppsIndex > uint64(len(cx.Txn.Txn.ForeignApps)) { err := fmt.Errorf("invalid ForeignApps index %d", foreignAppsIndex) return basics.TealValue{}, false, err @@ -2186,15 +2189,15 @@ func (cx *evalContext) appReadGlobalKey(foreignAppsIndex uint64, key string) (ba return cx.Ledger.GetGlobal(appIdx, key) } -func (cx *evalContext) appWriteGlobalKey(key string, tv basics.TealValue) error { +func (cx *EvalContext) appWriteGlobalKey(key string, tv basics.TealValue) error { return cx.Ledger.SetGlobal(key, tv) } -func (cx *evalContext) appDeleteGlobalKey(key string) error { +func (cx *EvalContext) appDeleteGlobalKey(key string) error { return cx.Ledger.DelGlobal(key) } -func opAppGetLocalState(cx *evalContext) { +func opAppGetLocalState(cx *EvalContext) { last := len(cx.stack) - 1 // state key prev := last - 1 // account offset @@ -2212,7 +2215,7 @@ func opAppGetLocalState(cx *evalContext) { cx.stack = cx.stack[:last] } -func opAppGetLocalStateEx(cx *evalContext) { +func opAppGetLocalStateEx(cx *EvalContext) { last := len(cx.stack) - 1 // state key prev := last - 1 // app id pprev := prev - 1 // account offset @@ -2237,7 +2240,7 @@ func opAppGetLocalStateEx(cx *evalContext) { cx.stack = cx.stack[:last] } -func opAppGetLocalStateImpl(cx *evalContext, appID uint64, key []byte, accountIdx uint64) (result stackValue, ok bool, err error) { +func opAppGetLocalStateImpl(cx *EvalContext, appID uint64, key []byte, accountIdx uint64) (result stackValue, ok bool, err error) { if cx.Ledger == nil { err = fmt.Errorf("ledger not available") return @@ -2255,7 +2258,7 @@ func opAppGetLocalStateImpl(cx *evalContext, appID uint64, key []byte, accountId return } -func opAppGetGlobalStateImpl(cx *evalContext, appIndex uint64, key []byte) (result stackValue, ok bool, err error) { +func opAppGetGlobalStateImpl(cx *EvalContext, appIndex uint64, key []byte) (result stackValue, ok bool, err error) { if cx.Ledger == nil { err = fmt.Errorf("ledger not available") return @@ -2272,7 +2275,7 @@ func opAppGetGlobalStateImpl(cx *evalContext, appIndex uint64, key []byte) (resu return } -func opAppGetGlobalState(cx *evalContext) { +func opAppGetGlobalState(cx *EvalContext) { last := len(cx.stack) - 1 // state key key := cx.stack[last].Bytes @@ -2287,7 +2290,7 @@ func opAppGetGlobalState(cx *evalContext) { cx.stack[last] = result } -func opAppGetGlobalStateEx(cx *evalContext) { +func opAppGetGlobalStateEx(cx *EvalContext) { last := len(cx.stack) - 1 // state key prev := last - 1 @@ -2309,7 +2312,7 @@ func opAppGetGlobalStateEx(cx *evalContext) { cx.stack[last] = isOk } -func opAppPutLocalState(cx *evalContext) { +func opAppPutLocalState(cx *EvalContext) { last := len(cx.stack) - 1 // value prev := last - 1 // state key pprev := prev - 1 // account offset @@ -2332,7 +2335,7 @@ func opAppPutLocalState(cx *evalContext) { cx.stack = cx.stack[:pprev] } -func opAppPutGlobalState(cx *evalContext) { +func opAppPutGlobalState(cx *EvalContext) { last := len(cx.stack) - 1 // value prev := last - 1 // state key @@ -2353,7 +2356,7 @@ func opAppPutGlobalState(cx *evalContext) { cx.stack = cx.stack[:prev] } -func opAppDeleteLocalState(cx *evalContext) { +func opAppDeleteLocalState(cx *EvalContext) { last := len(cx.stack) - 1 // key prev := last - 1 // account offset @@ -2374,7 +2377,7 @@ func opAppDeleteLocalState(cx *evalContext) { cx.stack = cx.stack[:prev] } -func opAppDeleteGlobalState(cx *evalContext) { +func opAppDeleteGlobalState(cx *EvalContext) { last := len(cx.stack) - 1 // key key := string(cx.stack[last].Bytes) @@ -2392,7 +2395,7 @@ func opAppDeleteGlobalState(cx *evalContext) { cx.stack = cx.stack[:last] } -func opAssetHoldingGet(cx *evalContext) { +func opAssetHoldingGet(cx *EvalContext) { last := len(cx.stack) - 1 // asset id prev := last - 1 // account offset @@ -2427,7 +2430,7 @@ func opAssetHoldingGet(cx *evalContext) { cx.stack[last].Uint = exist } -func opAssetParamsGet(cx *evalContext) { +func opAssetParamsGet(cx *EvalContext) { last := len(cx.stack) - 1 // foreign asset id foreignAssetsIndex := cx.stack[last].Uint diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 98c791a1e6..1a9b7c82e1 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -2734,7 +2734,7 @@ func TestReturnTypes(t *testing.T) { source := sb.String() ops := testProg(t, source, AssemblerMaxVersion) - var cx evalContext + var cx EvalContext cx.EvalParams = ep cx.runModeFlags = m diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 74dcc251fc..d5afc06b01 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -2657,10 +2657,10 @@ func TestShortBytecblock2(t *testing.T) { const panicString = "out of memory, buffer overrun, stack overflow, divide by zero, halt and catch fire" -func opPanic(cx *evalContext) { +func opPanic(cx *EvalContext) { panic(panicString) } -func checkPanic(cx *evalContext) error { +func checkPanic(cx *EvalContext) error { panic(panicString) } @@ -3464,7 +3464,7 @@ intc_0 opsByOpcode[LogicVersion][spec.Opcode] = origSpec }() - spec.op = func(cx *evalContext) { + spec.op = func(cx *EvalContext) { // overflow cx.stack = make([]stackValue, 2000) } diff --git a/ledger/eval.go b/ledger/eval.go index 100d17df0b..7dbb43c8df 100644 --- a/ledger/eval.go +++ b/ledger/eval.go @@ -667,7 +667,8 @@ func (eval *BlockEvaluator) TransactionGroup(txads []transactions.SignedTxnWithA // prepareEvalParams creates a logic.EvalParams for each ApplicationCall // transaction in the group func (eval *BlockEvaluator) prepareEvalParams(txgroup []transactions.SignedTxnWithAD) (res []*logic.EvalParams) { - var groupNoAD []transactions.SignedTxn + var txnGroupNoAD []transactions.SignedTxn + var contextGroup []*logic.EvalContext var minTealVersion uint64 res = make([]*logic.EvalParams, len(txgroup)) for i, txn := range txgroup { @@ -676,22 +677,35 @@ func (eval *BlockEvaluator) prepareEvalParams(txgroup []transactions.SignedTxnWi continue } - // Initialize group without ApplyData lazily - if groupNoAD == nil { - groupNoAD = make([]transactions.SignedTxn, len(txgroup)) + // Initialize context group pointers and transaction group without ApplyData lazily + if txnGroupNoAD == nil { + txnGroupNoAD = make([]transactions.SignedTxn, len(txgroup)) for j := range txgroup { - groupNoAD[j] = txgroup[j].SignedTxn + txnGroupNoAD[j] = txgroup[j].SignedTxn } - minTealVersion = logic.ComputeMinTealVersion(groupNoAD) + + contextGroup = make([]*logic.EvalContext, len(txgroup)) + for j, txn := range txgroup { + // Only allocate space for ApplicationCall transactions + if txn.SignedTxn.Txn.Type == protocol.ApplicationCallTx { + contextGroup[j] = new(logic.EvalContext) + } + } + + minTealVersion = logic.ComputeMinTealVersion(txnGroupNoAD) } res[i] = &logic.EvalParams{ - Txn: &groupNoAD[i], + Txn: &txnGroupNoAD[i], + Cx: contextGroup[i], Proto: &eval.proto, - TxnGroup: groupNoAD, + TxnGroup: txnGroupNoAD, + CxGroup: contextGroup, GroupIndex: i, MinTealVersion: &minTealVersion, } + + contextGroup[i].EvalParams = *res[i] } return } From 3a861a30b90377736e6a5138322c50a704ca17e8 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Tue, 18 May 2021 11:36:04 -0400 Subject: [PATCH 02/35] Set up tests * Test that `prepareEvalParams` correctly sets up `EvalContext` pointers * Add `Scratch` to `TxnField` and `txnFieldSpecs` * Set up a test to confirm `gtxna` works with the `Scratch` field --- data/transactions/logic/eval_test.go | 77 ++++++++++++++++++++++++ data/transactions/logic/fields.go | 6 ++ data/transactions/logic/fields_string.go | 7 ++- ledger/eval_test.go | 18 ++++++ 4 files changed, 105 insertions(+), 3 deletions(-) diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index d5afc06b01..6a84147f43 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -87,6 +87,7 @@ func defaultEvalParamsWithVersion(sb *strings.Builder, txn *transactions.SignedT ep := EvalParams{} ep.Proto = &proto ep.Txn = pt + ep.Cx = new(EvalContext) if sb != nil { // have to do this since go's nil semantics: https://golang.org/doc/faq#nil_error ep.Trace = sb } @@ -1806,6 +1807,82 @@ func testLogic(t *testing.T, program string, v uint64, ep EvalParams, problems . } } +func TestGtxnaScratch(t *testing.T) { + t.Parallel() + + case1 := []string{ + `int 314 +store 0 +int 1`, + `gtxna 0 Scratch 0 +int 314 +== +`, + } + case2 := []string{ + `byte "txn 1" +store 0 +int 1`, + `byte "txn 2" +store 1 +int 1`, + `gtxna 0 Scratch 0 +byte "txn 1" +== +gtxna 1 Scratch 1 +byte "txn 2" +== +&& +`, + } + cases := [][]string{ + case1, case2, + } + + for i, testCase := range cases { + t.Run(fmt.Sprintf("i=%d", i), func(t *testing.T) { + // Assemble ops + opsList := make([]*OpStream, len(testCase)) + for j, source := range testCase { + ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) + require.NoError(t, err) + opsList[j] = ops + } + + // Initialize txgroup and cxgroup + txgroup := make([]transactions.SignedTxn, len(testCase)) + for j := range txgroup { + txgroup[j] = transactions.SignedTxn{} + } + cxgroup := make([]*EvalContext, len(testCase)) + for j := range cxgroup { + cxgroup[j] = new(EvalContext) + } + + // Construct EvalParams + proto := defaultEvalProtoWithVersion(LogicVersion) + epList := make([]EvalParams, len(testCase)) + for j := range testCase { + epList[j] = EvalParams{ + Proto: &proto, + Txn: &txgroup[j], + Cx: cxgroup[j], + TxnGroup: txgroup, + CxGroup: cxgroup, + } + cxgroup[j].EvalParams = epList[j] + } + + // Evaluate app calls + for j, ops := range opsList { + pass, err := Eval(ops.Program, epList[j]) + require.NoError(t, err) + require.True(t, pass) + } + }) + } +} + func TestTxna(t *testing.T) { t.Parallel() source := `txna Accounts 1 diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 41aecb4b9e..15ae21d7e7 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -143,6 +143,9 @@ const ( // LocalNumByteSlice uint64 LocalNumByteSlice + // Scratch [256]stackValue + Scratch + invalidTxnField // fence for some setup that loops from Sender..invalidTxnField ) @@ -228,6 +231,7 @@ var txnFieldSpecs = []txnFieldSpec{ {GlobalNumByteSlice, StackUint64, 3}, {LocalNumUint, StackUint64, 3}, {LocalNumByteSlice, StackUint64, 3}, + {Scratch, StackBytes, 4}, } // TxnaFieldNames are arguments to the 'txna' opcode @@ -240,6 +244,7 @@ var TxnaFieldTypes = []StackType{ txnaFieldSpecByField[Accounts].ftype, txnaFieldSpecByField[Assets].ftype, txnaFieldSpecByField[Applications].ftype, + txnaFieldSpecByField[Scratch].ftype, } var txnaFieldSpecByField = map[TxnField]txnFieldSpec{ @@ -247,6 +252,7 @@ var txnaFieldSpecByField = map[TxnField]txnFieldSpec{ Accounts: {Accounts, StackBytes, 2}, Assets: {Assets, StackUint64, 3}, Applications: {Applications, StackUint64, 3}, + Scratch: {Scratch, StackBytes, 4}, } // TxnTypeNames is the values of Txn.Type in enum order diff --git a/data/transactions/logic/fields_string.go b/data/transactions/logic/fields_string.go index c06df6944e..f2f906fd96 100644 --- a/data/transactions/logic/fields_string.go +++ b/data/transactions/logic/fields_string.go @@ -64,12 +64,13 @@ func _() { _ = x[GlobalNumByteSlice-53] _ = x[LocalNumUint-54] _ = x[LocalNumByteSlice-55] - _ = x[invalidTxnField-56] + _ = x[Scratch-56] + _ = x[invalidTxnField-57] } -const _TxnField_name = "SenderFeeFirstValidFirstValidTimeLastValidNoteLeaseReceiverAmountCloseRemainderToVotePKSelectionPKVoteFirstVoteLastVoteKeyDilutionTypeTypeEnumXferAssetAssetAmountAssetSenderAssetReceiverAssetCloseToGroupIndexTxIDApplicationIDOnCompletionApplicationArgsNumAppArgsAccountsNumAccountsApprovalProgramClearStateProgramRekeyToConfigAssetConfigAssetTotalConfigAssetDecimalsConfigAssetDefaultFrozenConfigAssetUnitNameConfigAssetNameConfigAssetURLConfigAssetMetadataHashConfigAssetManagerConfigAssetReserveConfigAssetFreezeConfigAssetClawbackFreezeAssetFreezeAssetAccountFreezeAssetFrozenAssetsNumAssetsApplicationsNumApplicationsGlobalNumUintGlobalNumByteSliceLocalNumUintLocalNumByteSliceinvalidTxnField" +const _TxnField_name = "SenderFeeFirstValidFirstValidTimeLastValidNoteLeaseReceiverAmountCloseRemainderToVotePKSelectionPKVoteFirstVoteLastVoteKeyDilutionTypeTypeEnumXferAssetAssetAmountAssetSenderAssetReceiverAssetCloseToGroupIndexTxIDApplicationIDOnCompletionApplicationArgsNumAppArgsAccountsNumAccountsApprovalProgramClearStateProgramRekeyToConfigAssetConfigAssetTotalConfigAssetDecimalsConfigAssetDefaultFrozenConfigAssetUnitNameConfigAssetNameConfigAssetURLConfigAssetMetadataHashConfigAssetManagerConfigAssetReserveConfigAssetFreezeConfigAssetClawbackFreezeAssetFreezeAssetAccountFreezeAssetFrozenAssetsNumAssetsApplicationsNumApplicationsGlobalNumUintGlobalNumByteSliceLocalNumUintLocalNumByteSliceScratchinvalidTxnField" -var _TxnField_index = [...]uint16{0, 6, 9, 19, 33, 42, 46, 51, 59, 65, 81, 87, 98, 107, 115, 130, 134, 142, 151, 162, 173, 186, 198, 208, 212, 225, 237, 252, 262, 270, 281, 296, 313, 320, 331, 347, 366, 390, 409, 424, 438, 461, 479, 497, 514, 533, 544, 562, 579, 585, 594, 606, 621, 634, 652, 664, 681, 696} +var _TxnField_index = [...]uint16{0, 6, 9, 19, 33, 42, 46, 51, 59, 65, 81, 87, 98, 107, 115, 130, 134, 142, 151, 162, 173, 186, 198, 208, 212, 225, 237, 252, 262, 270, 281, 296, 313, 320, 331, 347, 366, 390, 409, 424, 438, 461, 479, 497, 514, 533, 544, 562, 579, 585, 594, 606, 621, 634, 652, 664, 681, 688, 703} func (i TxnField) String() string { if i < 0 || i >= TxnField(len(_TxnField_index)-1) { diff --git a/ledger/eval_test.go b/ledger/eval_test.go index 1035c6ef9a..e896d8ab57 100644 --- a/ledger/eval_test.go +++ b/ledger/eval_test.go @@ -418,6 +418,24 @@ func TestPrepareEvalParams(t *testing.T) { } }) } + + // Test evalContext pointers were set up correctly + appCallsGroup := cases[6].group + epList := eval.prepareEvalParams(appCallsGroup) + + for i := range appCallsGroup { + for j := range epList { + require.Equal(t, epList[j].Cx, epList[i].CxGroup[j]) + } + } + + // And confirm that mutating an evalContext mutates that one in the group + original := epList[0].Cx + pointer := epList[1].CxGroup[0] + newVal := 1 + require.NotEqual(t, newVal, original.GroupIndex) + original.GroupIndex = newVal + require.Equal(t, newVal, pointer.GroupIndex) } func testLedgerCleanup(l *Ledger, dbName string, inMem bool) { From 1b68083d3fa4df66cfc9a7af0559793f5a2da760 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Tue, 18 May 2021 12:17:32 -0400 Subject: [PATCH 03/35] Implement `Scratch` gtxna field --- data/transactions/logic/eval.go | 9 ++++++++- data/transactions/logic/fields.go | 4 ++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 75dd678796..1997f17435 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1546,6 +1546,13 @@ func (cx *EvalContext) txnFieldToStack(txn *transactions.Transaction, field TxnF sv.Bytes = txn.FreezeAccount[:] case FreezeAssetFrozen: sv.Uint = boolToUint(txn.AssetFrozen) + case Scratch: + val := cx.CxGroup[groupIndex].scratch[arrayFieldIdx] + if val.Bytes != nil { + sv.Bytes = val.Bytes + } else { + sv.Uint = val.Uint + } default: err = fmt.Errorf("invalid txn field %d", field) return @@ -1553,7 +1560,7 @@ func (cx *EvalContext) txnFieldToStack(txn *transactions.Transaction, field TxnF txnField := TxnField(field) txnFieldType := TxnFieldTypes[txnField] - if txnFieldType != sv.argType() { + if txnFieldType != sv.argType() && txnFieldType != StackAny { err = fmt.Errorf("%s expected field type is %s but got %s", txnField.String(), txnFieldType.String(), sv.argType().String()) } return diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 15ae21d7e7..2fc9b070b5 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -231,7 +231,7 @@ var txnFieldSpecs = []txnFieldSpec{ {GlobalNumByteSlice, StackUint64, 3}, {LocalNumUint, StackUint64, 3}, {LocalNumByteSlice, StackUint64, 3}, - {Scratch, StackBytes, 4}, + {Scratch, StackAny, 4}, } // TxnaFieldNames are arguments to the 'txna' opcode @@ -252,7 +252,7 @@ var txnaFieldSpecByField = map[TxnField]txnFieldSpec{ Accounts: {Accounts, StackBytes, 2}, Assets: {Assets, StackUint64, 3}, Applications: {Applications, StackUint64, 3}, - Scratch: {Scratch, StackBytes, 4}, + Scratch: {Scratch, StackAny, 4}, } // TxnTypeNames is the values of Txn.Type in enum order From a7f64c218b36b29c7f6adb73ff31b986eac51069 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Tue, 18 May 2021 12:56:07 -0400 Subject: [PATCH 04/35] Add error conditions * Error if scratch space index is out of bounds * Error if trying to get a Scratch space from self or a future transaction --- data/transactions/logic/eval.go | 10 +++ data/transactions/logic/eval_test.go | 97 +++++++++++++++++++++------- 2 files changed, 84 insertions(+), 23 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 1997f17435..aa21798cc6 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1547,6 +1547,16 @@ func (cx *EvalContext) txnFieldToStack(txn *transactions.Transaction, field TxnF case FreezeAssetFrozen: sv.Uint = boolToUint(txn.AssetFrozen) case Scratch: + if arrayFieldIdx >= 256 { + err = fmt.Errorf("invalid Scratch index %d", arrayFieldIdx) + return + } else if groupIndex == cx.GroupIndex { + err = fmt.Errorf("can't use Scratch txn field on self, use load instead") + return + } else if groupIndex > cx.GroupIndex { + err = fmt.Errorf("can't get future Scratch from txn with index %d", groupIndex) + return + } val := cx.CxGroup[groupIndex].scratch[arrayFieldIdx] if val.Bytes != nil { sv.Bytes = val.Bytes diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 6a84147f43..44fe169a33 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1810,23 +1810,32 @@ func testLogic(t *testing.T, program string, v uint64, ep EvalParams, problems . func TestGtxnaScratch(t *testing.T) { t.Parallel() - case1 := []string{ - `int 314 + type scratchTestCase struct { + tealSources []string + errContains string + } + + simpleCase := scratchTestCase{ + tealSources: []string{ + `int 2 store 0 int 1`, - `gtxna 0 Scratch 0 -int 314 + `gtxna 0 Scratch 0 +int 2 == `, + }, } - case2 := []string{ - `byte "txn 1" + + multipleTxnCase := scratchTestCase{ + tealSources: []string{ + `byte "txn 1" store 0 int 1`, - `byte "txn 2" + `byte "txn 2" store 1 int 1`, - `gtxna 0 Scratch 0 + `gtxna 0 Scratch 0 byte "txn 1" == gtxna 1 Scratch 1 @@ -1834,51 +1843,93 @@ byte "txn 2" == && `, + }, + } + + selfCase := scratchTestCase{ + tealSources: []string{ + `gtxna 0 Scratch 0 +int 2 +store 0 +int 1 +`, + }, + errContains: "can't use Scratch txn field on self, use load instead", + } + + laterTxnSlotCase := scratchTestCase{ + tealSources: []string{ + `gtxna 1 Scratch 0 +int 2 +== +`, + `int 2 +store 0 +int 1`, + }, + errContains: "can't get future Scratch from txn with index 1", } - cases := [][]string{ - case1, case2, + + cases := []scratchTestCase{ + simpleCase, multipleTxnCase, selfCase, laterTxnSlotCase, } for i, testCase := range cases { t.Run(fmt.Sprintf("i=%d", i), func(t *testing.T) { + sources := testCase.tealSources // Assemble ops - opsList := make([]*OpStream, len(testCase)) - for j, source := range testCase { + opsList := make([]*OpStream, len(sources)) + for j, source := range sources { ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) require.NoError(t, err) opsList[j] = ops } // Initialize txgroup and cxgroup - txgroup := make([]transactions.SignedTxn, len(testCase)) + txgroup := make([]transactions.SignedTxn, len(sources)) for j := range txgroup { txgroup[j] = transactions.SignedTxn{} } - cxgroup := make([]*EvalContext, len(testCase)) + cxgroup := make([]*EvalContext, len(sources)) for j := range cxgroup { cxgroup[j] = new(EvalContext) } // Construct EvalParams proto := defaultEvalProtoWithVersion(LogicVersion) - epList := make([]EvalParams, len(testCase)) - for j := range testCase { + epList := make([]EvalParams, len(sources)) + for j := range sources { epList[j] = EvalParams{ - Proto: &proto, - Txn: &txgroup[j], - Cx: cxgroup[j], - TxnGroup: txgroup, - CxGroup: cxgroup, + Proto: &proto, + Txn: &txgroup[j], + Cx: cxgroup[j], + TxnGroup: txgroup, + CxGroup: cxgroup, + GroupIndex: j, } cxgroup[j].EvalParams = epList[j] } // Evaluate app calls + shouldErr := testCase.errContains != "" + didPass := true for j, ops := range opsList { pass, err := Eval(ops.Program, epList[j]) - require.NoError(t, err) - require.True(t, pass) + + // Confirm it errors or that the error message is the expected one + if !shouldErr { + require.NoError(t, err) + } else if shouldErr && err != nil { + require.Error(t, err) + require.Contains(t, err.Error(), testCase.errContains) + } + + if !pass { + didPass = false + } } + + require.Equal(t, !shouldErr, didPass) }) } } From f952852e2650a15973782c76fd2090bf8e770552 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Tue, 18 May 2021 14:33:04 -0400 Subject: [PATCH 05/35] Add Scratch field description --- data/transactions/logic/doc.go | 1 + 1 file changed, 1 insertion(+) diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index a64f92511a..d7b0dc9872 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -305,6 +305,7 @@ var txnFieldDocs = map[string]string{ "GlobalNumByteSlice": "Number of global state byteslices in ApplicationCall", "LocalNumUint": "Number of local state integers in ApplicationCall", "LocalNumByteSlice": "Number of local state byteslices in ApplicationCall", + "Scratch": "Scratch space of previous app call transaction", "ApprovalProgram": "Approval program", "ClearStateProgram": "Clear state program", "RekeyTo": "32 byte Sender's new AuthAddr", From d375f564d556cded3b96415f3e7a25f98024344d Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Tue, 18 May 2021 15:09:32 -0400 Subject: [PATCH 06/35] Error if accessing Scratch space of non-app call --- data/transactions/logic/eval.go | 5 ++- data/transactions/logic/eval_test.go | 49 ++++++++++++++++++++++++++-- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index aa21798cc6..57e447bf15 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1547,7 +1547,10 @@ func (cx *EvalContext) txnFieldToStack(txn *transactions.Transaction, field TxnF case FreezeAssetFrozen: sv.Uint = boolToUint(txn.AssetFrozen) case Scratch: - if arrayFieldIdx >= 256 { + if txn.Type != protocol.ApplicationCallTx { + err = fmt.Errorf("can't use Scratch txn field on non-app call txn with index %d", groupIndex) + return + } else if arrayFieldIdx >= 256 { err = fmt.Errorf("invalid Scratch index %d", arrayFieldIdx) return } else if groupIndex == cx.GroupIndex { diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 44fe169a33..548fd363c1 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1873,6 +1873,7 @@ int 1`, cases := []scratchTestCase{ simpleCase, multipleTxnCase, selfCase, laterTxnSlotCase, } + proto := defaultEvalProtoWithVersion(LogicVersion) for i, testCase := range cases { t.Run(fmt.Sprintf("i=%d", i), func(t *testing.T) { @@ -1888,7 +1889,11 @@ int 1`, // Initialize txgroup and cxgroup txgroup := make([]transactions.SignedTxn, len(sources)) for j := range txgroup { - txgroup[j] = transactions.SignedTxn{} + txgroup[j] = transactions.SignedTxn{ + Txn: transactions.Transaction{ + Type: protocol.ApplicationCallTx, + }, + } } cxgroup := make([]*EvalContext, len(sources)) for j := range cxgroup { @@ -1896,7 +1901,6 @@ int 1`, } // Construct EvalParams - proto := defaultEvalProtoWithVersion(LogicVersion) epList := make([]EvalParams, len(sources)) for j := range sources { epList[j] = EvalParams{ @@ -1932,6 +1936,47 @@ int 1`, require.Equal(t, !shouldErr, didPass) }) } + + // Test failure on non-app call + t.Run("should fail on non-app call", func(t *testing.T) { + source := "gtxna 0 Scratch 0" + + // Assemble ops + ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) + require.NoError(t, err) + + // Initialize txgroup and cxgroup + txgroup := make([]transactions.SignedTxn, 2) + txgroup[0] = transactions.SignedTxn{ + Txn: transactions.Transaction{ + Type: protocol.PaymentTx, + }, + } + txgroup[1] = transactions.SignedTxn{} + cxgroup := make([]*EvalContext, 2) + for j := range cxgroup { + cxgroup[j] = new(EvalContext) + } + + // Construct EvalParams + epList := make([]EvalParams, 2) + for j := range epList { + epList[j] = EvalParams{ + Proto: &proto, + Txn: &txgroup[j], + Cx: cxgroup[j], + TxnGroup: txgroup, + CxGroup: cxgroup, + GroupIndex: j, + } + cxgroup[j].EvalParams = epList[j] + } + + // Evaluate app call + _, err = Eval(ops.Program, epList[1]) + require.Error(t, err) + require.Contains(t, err.Error(), "can't use Scratch txn field on non-app call txn with index 0") + }) } func TestTxna(t *testing.T) { From 4406baead4987f4ed6b816c4a1d99c3fc9863695 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Tue, 18 May 2021 16:51:03 -0400 Subject: [PATCH 07/35] Revert "Add mechanism for accessing previous `EvalContext`s from within a txn group" This reverts commit 92e05b7a802e07f6f4ecaec3013993692e6a72c0. --- data/transactions/logic/assembler.go | 8 +- data/transactions/logic/assembler_test.go | 2 +- data/transactions/logic/debugger.go | 4 +- data/transactions/logic/eval.go | 259 +++++++++---------- data/transactions/logic/evalStateful_test.go | 2 +- data/transactions/logic/eval_test.go | 6 +- ledger/eval.go | 30 +-- 7 files changed, 147 insertions(+), 164 deletions(-) diff --git a/data/transactions/logic/assembler.go b/data/transactions/logic/assembler.go index 82820b4eeb..5c4d864d01 100644 --- a/data/transactions/logic/assembler.go +++ b/data/transactions/logic/assembler.go @@ -1383,7 +1383,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 { @@ -1451,7 +1451,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 { @@ -1609,7 +1609,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 } @@ -1629,7 +1629,7 @@ func disPushBytes(dis *disassembleState, spec *OpSpec) (string, error) { dis.nextpc = int(end) return fmt.Sprintf("%s 0x%s", spec.Name, hex.EncodeToString(bytes)), nil } -func checkPushBytes(cx *EvalContext) error { +func checkPushBytes(cx *evalContext) error { opPushBytes(cx) return cx.err } diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index e6273f03bf..1fca9c8494 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -1650,7 +1650,7 @@ func TestErrShortBytecblock(t *testing.T) { _, _, err = parseIntcblock(ops.Program, 0) require.Equal(t, err, errShortIntcblock) - var cx EvalContext + var cx evalContext cx.program = ops.Program err = checkIntConstBlock(&cx) require.Equal(t, err, errShortIntcblock) diff --git a/data/transactions/logic/debugger.go b/data/transactions/logic/debugger.go index 3c49b57cc8..021a53faf1 100644 --- a/data/transactions/logic/debugger.go +++ b/data/transactions/logic/debugger.go @@ -86,7 +86,7 @@ func GetProgramID(program []byte) string { return hex.EncodeToString(hash[:]) } -func makeDebugState(cx *EvalContext) DebugState { +func makeDebugState(cx *evalContext) DebugState { disasm, dsInfo, err := disassembleInstrumented(cx.program) if err != nil { // Report disassembly error as program text @@ -194,7 +194,7 @@ func valueDeltaToValueDelta(vd *basics.ValueDelta) basics.ValueDelta { } } -func (cx *EvalContext) refreshDebugState() *DebugState { +func (cx *evalContext) refreshDebugState() *DebugState { ds := &cx.debugState // Update pc, line, error, stack, and scratch space diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 57e447bf15..85d5fb314f 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -149,18 +149,12 @@ type EvalParams struct { // the transaction being evaluated Txn *transactions.SignedTxn - // evaluation context - Cx *EvalContext - Proto *config.ConsensusParams Trace io.Writer TxnGroup []transactions.SignedTxn - // for accessing evaluation contexts of other txns - CxGroup []*EvalContext - // GroupIndex should point to Txn within TxnGroup GroupIndex int @@ -180,8 +174,8 @@ type EvalParams struct { runModeFlags runMode } -type opEvalFunc func(cx *EvalContext) -type opCheckFunc func(cx *EvalContext) error +type opEvalFunc func(cx *evalContext) +type opCheckFunc func(cx *evalContext) error type runMode uint64 @@ -227,7 +221,7 @@ func (ep EvalParams) log() logging.Logger { return logging.Base() } -type EvalContext struct { +type evalContext struct { EvalParams stack []stackValue @@ -312,22 +306,24 @@ var errTooManyArgs = errors.New("LogicSig has too many arguments") // EvalStateful executes stateful TEAL program func EvalStateful(program []byte, params EvalParams) (pass bool, err error) { - cx := params.Cx + var cx evalContext + cx.EvalParams = params cx.runModeFlags = runModeApplication - return eval(program, cx) + return eval(program, &cx) } // Eval checks to see if a transaction passes logic // A program passes successfully if it finishes with one int element on the stack that is non-zero. func Eval(program []byte, params EvalParams) (pass bool, err error) { - cx := params.Cx + var cx evalContext + cx.EvalParams = params cx.runModeFlags = runModeSignature - return eval(program, cx) + return eval(program, &cx) } // eval implementation // A program passes successfully if it finishes with one int element on the stack that is non-zero. -func eval(program []byte, cx *EvalContext) (pass bool, err error) { +func eval(program []byte, cx *evalContext) (pass bool, err error) { defer func() { if x := recover(); x != nil { buf := make([]byte, 16*1024) @@ -492,9 +488,10 @@ func check(program []byte, params EvalParams) (err error) { return fmt.Errorf("program version must be >= %d for this transaction group, but have version %d", minVersion, version) } - cx := params.Cx + var cx evalContext cx.version = version cx.pc = vlen + cx.EvalParams = params cx.program = program cx.branchTargets = make(map[int]bool) cx.instructionStarts = make(map[int]bool) @@ -549,7 +546,7 @@ func boolToUint(x bool) uint64 { // MaxStackDepth should move to consensus params const MaxStackDepth = 1000 -func (cx *EvalContext) step() { +func (cx *evalContext) step() { opcode := cx.program[cx.pc] spec := &opsByOpcode[cx.version][opcode] @@ -650,7 +647,7 @@ func (cx *EvalContext) step() { } } -func (cx *EvalContext) checkStep() (int, error) { +func (cx *evalContext) checkStep() (int, error) { cx.instructionStarts[cx.pc] = true opcode := cx.program[cx.pc] spec := &opsByOpcode[cx.version][opcode] @@ -692,11 +689,11 @@ func (cx *EvalContext) checkStep() (int, error) { return deets.Cost, nil } -func opErr(cx *EvalContext) { +func opErr(cx *evalContext) { cx.err = errors.New("TEAL runtime encountered err opcode") } -func opReturn(cx *EvalContext) { +func opReturn(cx *evalContext) { // Achieve the end condition: // Take the last element on the stack and make it the return value (only element on the stack) // Move the pc to the end of the program @@ -706,7 +703,7 @@ func opReturn(cx *EvalContext) { cx.nextpc = len(cx.program) } -func opAssert(cx *EvalContext) { +func opAssert(cx *evalContext) { last := len(cx.stack) - 1 if cx.stack[last].Uint != 0 { cx.stack = cx.stack[:last] @@ -715,13 +712,13 @@ func opAssert(cx *EvalContext) { cx.err = errors.New("assert failed") } -func opSwap(cx *EvalContext) { +func opSwap(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 cx.stack[last], cx.stack[prev] = cx.stack[prev], cx.stack[last] } -func opSelect(cx *EvalContext) { +func opSelect(cx *evalContext) { last := len(cx.stack) - 1 // condition on top prev := last - 1 // true is one down pprev := prev - 1 // false below that @@ -732,14 +729,14 @@ func opSelect(cx *EvalContext) { cx.stack = cx.stack[:prev] } -func opSHA256(cx *EvalContext) { +func opSHA256(cx *evalContext) { last := len(cx.stack) - 1 hash := sha256.Sum256(cx.stack[last].Bytes) cx.stack[last].Bytes = hash[:] } // The Keccak256 variant of SHA-3 is implemented for compatibility with Ethereum -func opKeccak256(cx *EvalContext) { +func opKeccak256(cx *evalContext) { last := len(cx.stack) - 1 hasher := sha3.NewLegacyKeccak256() hasher.Write(cx.stack[last].Bytes) @@ -754,13 +751,13 @@ func opKeccak256(cx *EvalContext) { // stability and portability in case the rest of Algorand ever moves // to a different default hash. For stability of this language, at // that time a new opcode should be made with the new hash. -func opSHA512_256(cx *EvalContext) { +func opSHA512_256(cx *evalContext) { last := len(cx.stack) - 1 hash := sha512.Sum512_256(cx.stack[last].Bytes) cx.stack[last].Bytes = hash[:] } -func opPlus(cx *EvalContext) { +func opPlus(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 cx.stack[prev].Uint += cx.stack[last].Uint @@ -779,7 +776,7 @@ func opAddwImpl(x, y uint64) (carry uint64, sum uint64) { return } -func opAddw(cx *EvalContext) { +func opAddw(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 carry, sum := opAddwImpl(cx.stack[prev].Uint, cx.stack[last].Uint) @@ -805,7 +802,7 @@ func opDivwImpl(hiNum, loNum, hiDen, loDen uint64) (hiQuo uint64, loQuo uint64, rem.Uint64() } -func opDivw(cx *EvalContext) { +func opDivw(cx *evalContext) { loDen := len(cx.stack) - 1 hiDen := loDen - 1 if cx.stack[loDen].Uint == 0 && cx.stack[hiDen].Uint == 0 { @@ -822,7 +819,7 @@ func opDivw(cx *EvalContext) { cx.stack[loDen].Uint = loRem } -func opMinus(cx *EvalContext) { +func opMinus(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 if cx.stack[last].Uint > cx.stack[prev].Uint { @@ -833,7 +830,7 @@ func opMinus(cx *EvalContext) { cx.stack = cx.stack[:last] } -func opDiv(cx *EvalContext) { +func opDiv(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 if cx.stack[last].Uint == 0 { @@ -844,7 +841,7 @@ func opDiv(cx *EvalContext) { cx.stack = cx.stack[:last] } -func opModulo(cx *EvalContext) { +func opModulo(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 if cx.stack[last].Uint == 0 { @@ -855,7 +852,7 @@ func opModulo(cx *EvalContext) { cx.stack = cx.stack[:last] } -func opMul(cx *EvalContext) { +func opMul(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 a := cx.stack[prev].Uint @@ -889,7 +886,7 @@ func opMulwImpl(x, y uint64) (high64 uint64, low64 uint64, err error) { return } -func opMulw(cx *EvalContext) { +func opMulw(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 high, low, err := opMulwImpl(cx.stack[prev].Uint, cx.stack[last].Uint) @@ -901,7 +898,7 @@ func opMulw(cx *EvalContext) { cx.stack[last].Uint = low } -func opLt(cx *EvalContext) { +func opLt(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 cond := cx.stack[prev].Uint < cx.stack[last].Uint @@ -913,7 +910,7 @@ func opLt(cx *EvalContext) { cx.stack = cx.stack[:last] } -func opGt(cx *EvalContext) { +func opGt(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 cond := cx.stack[prev].Uint > cx.stack[last].Uint @@ -925,7 +922,7 @@ func opGt(cx *EvalContext) { cx.stack = cx.stack[:last] } -func opLe(cx *EvalContext) { +func opLe(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 cond := cx.stack[prev].Uint <= cx.stack[last].Uint @@ -937,7 +934,7 @@ func opLe(cx *EvalContext) { cx.stack = cx.stack[:last] } -func opGe(cx *EvalContext) { +func opGe(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 cond := cx.stack[prev].Uint >= cx.stack[last].Uint @@ -949,7 +946,7 @@ func opGe(cx *EvalContext) { cx.stack = cx.stack[:last] } -func opAnd(cx *EvalContext) { +func opAnd(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 cond := (cx.stack[prev].Uint != 0) && (cx.stack[last].Uint != 0) @@ -961,7 +958,7 @@ func opAnd(cx *EvalContext) { cx.stack = cx.stack[:last] } -func opOr(cx *EvalContext) { +func opOr(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 cond := (cx.stack[prev].Uint != 0) || (cx.stack[last].Uint != 0) @@ -973,7 +970,7 @@ func opOr(cx *EvalContext) { cx.stack = cx.stack[:last] } -func opEq(cx *EvalContext) { +func opEq(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 ta := cx.stack[prev].argType() @@ -997,7 +994,7 @@ func opEq(cx *EvalContext) { cx.stack = cx.stack[:last] } -func opNeq(cx *EvalContext) { +func opNeq(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 ta := cx.stack[prev].argType() @@ -1021,7 +1018,7 @@ func opNeq(cx *EvalContext) { cx.stack = cx.stack[:last] } -func opNot(cx *EvalContext) { +func opNot(cx *evalContext) { last := len(cx.stack) - 1 cond := cx.stack[last].Uint == 0 if cond { @@ -1031,13 +1028,13 @@ func opNot(cx *EvalContext) { } } -func opLen(cx *EvalContext) { +func opLen(cx *evalContext) { last := len(cx.stack) - 1 cx.stack[last].Uint = uint64(len(cx.stack[last].Bytes)) cx.stack[last].Bytes = nil } -func opItob(cx *EvalContext) { +func opItob(cx *evalContext) { last := len(cx.stack) - 1 ibytes := make([]byte, 8) binary.BigEndian.PutUint64(ibytes, cx.stack[last].Uint) @@ -1046,7 +1043,7 @@ func opItob(cx *EvalContext) { cx.stack[last].Bytes = ibytes } -func opBtoi(cx *EvalContext) { +func opBtoi(cx *evalContext) { last := len(cx.stack) - 1 ibytes := cx.stack[last].Bytes if len(ibytes) > 8 { @@ -1062,61 +1059,61 @@ func opBtoi(cx *EvalContext) { cx.stack[last].Bytes = nil } -func opBitOr(cx *EvalContext) { +func opBitOr(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 cx.stack[prev].Uint = cx.stack[prev].Uint | cx.stack[last].Uint cx.stack = cx.stack[:last] } -func opBitAnd(cx *EvalContext) { +func opBitAnd(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 cx.stack[prev].Uint = cx.stack[prev].Uint & cx.stack[last].Uint cx.stack = cx.stack[:last] } -func opBitXor(cx *EvalContext) { +func opBitXor(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 cx.stack[prev].Uint = cx.stack[prev].Uint ^ cx.stack[last].Uint cx.stack = cx.stack[:last] } -func opBitNot(cx *EvalContext) { +func opBitNot(cx *evalContext) { last := len(cx.stack) - 1 cx.stack[last].Uint = cx.stack[last].Uint ^ 0xffffffffffffffff } -func opIntConstBlock(cx *EvalContext) { +func opIntConstBlock(cx *evalContext) { cx.intc, cx.nextpc, cx.err = parseIntcblock(cx.program, cx.pc) } -func opIntConstN(cx *EvalContext, n uint) { +func opIntConstN(cx *evalContext, n uint) { if n >= uint(len(cx.intc)) { cx.err = fmt.Errorf("intc [%d] beyond %d constants", n, len(cx.intc)) return } cx.stack = append(cx.stack, stackValue{Uint: cx.intc[n]}) } -func opIntConstLoad(cx *EvalContext) { +func opIntConstLoad(cx *evalContext) { n := uint(cx.program[cx.pc+1]) opIntConstN(cx, n) } -func opIntConst0(cx *EvalContext) { +func opIntConst0(cx *evalContext) { opIntConstN(cx, 0) } -func opIntConst1(cx *EvalContext) { +func opIntConst1(cx *evalContext) { opIntConstN(cx, 1) } -func opIntConst2(cx *EvalContext) { +func opIntConst2(cx *evalContext) { opIntConstN(cx, 2) } -func opIntConst3(cx *EvalContext) { +func opIntConst3(cx *evalContext) { opIntConstN(cx, 3) } -func opPushInt(cx *EvalContext) { +func opPushInt(cx *evalContext) { val, bytesUsed := binary.Uvarint(cx.program[cx.pc+1:]) if bytesUsed <= 0 { cx.err = fmt.Errorf("could not decode int at pc=%d", cx.pc+1) @@ -1127,35 +1124,35 @@ func opPushInt(cx *EvalContext) { cx.nextpc = cx.pc + 1 + bytesUsed } -func opByteConstBlock(cx *EvalContext) { +func opByteConstBlock(cx *evalContext) { cx.bytec, cx.nextpc, cx.err = parseBytecBlock(cx.program, cx.pc) } -func opByteConstN(cx *EvalContext, n uint) { +func opByteConstN(cx *evalContext, n uint) { if n >= uint(len(cx.bytec)) { cx.err = fmt.Errorf("bytec [%d] beyond %d constants", n, len(cx.bytec)) return } cx.stack = append(cx.stack, stackValue{Bytes: cx.bytec[n]}) } -func opByteConstLoad(cx *EvalContext) { +func opByteConstLoad(cx *evalContext) { n := uint(cx.program[cx.pc+1]) opByteConstN(cx, n) } -func opByteConst0(cx *EvalContext) { +func opByteConst0(cx *evalContext) { opByteConstN(cx, 0) } -func opByteConst1(cx *EvalContext) { +func opByteConst1(cx *evalContext) { opByteConstN(cx, 1) } -func opByteConst2(cx *EvalContext) { +func opByteConst2(cx *evalContext) { opByteConstN(cx, 2) } -func opByteConst3(cx *EvalContext) { +func opByteConst3(cx *evalContext) { opByteConstN(cx, 3) } -func opPushBytes(cx *EvalContext) { +func opPushBytes(cx *evalContext) { pos := cx.pc + 1 length, bytesUsed := binary.Uvarint(cx.program[pos:]) if bytesUsed <= 0 { @@ -1173,7 +1170,7 @@ func opPushBytes(cx *EvalContext) { cx.nextpc = int(end) } -func opArgN(cx *EvalContext, n uint64) { +func opArgN(cx *evalContext, n uint64) { if n >= uint64(len(cx.Txn.Lsig.Args)) { cx.err = fmt.Errorf("cannot load arg[%d] of %d", n, len(cx.Txn.Lsig.Args)) return @@ -1182,24 +1179,24 @@ func opArgN(cx *EvalContext, n uint64) { cx.stack = append(cx.stack, stackValue{Bytes: val}) } -func opArg(cx *EvalContext) { +func opArg(cx *evalContext) { n := uint64(cx.program[cx.pc+1]) opArgN(cx, n) } -func opArg0(cx *EvalContext) { +func opArg0(cx *evalContext) { opArgN(cx, 0) } -func opArg1(cx *EvalContext) { +func opArg1(cx *evalContext) { opArgN(cx, 1) } -func opArg2(cx *EvalContext) { +func opArg2(cx *evalContext) { opArgN(cx, 2) } -func opArg3(cx *EvalContext) { +func opArg3(cx *evalContext) { opArgN(cx, 3) } -func branchTarget(cx *EvalContext) (int, error) { +func branchTarget(cx *evalContext) (int, error) { offset := int16(uint16(cx.program[cx.pc+1])<<8 | uint16(cx.program[cx.pc+2])) if offset < 0 && cx.version < backBranchEnabledVersion { return 0, fmt.Errorf("negative branch offset %x", offset) @@ -1220,7 +1217,7 @@ func branchTarget(cx *EvalContext) (int, error) { } // checks any branch that is {op} {int16 be offset} -func checkBranch(cx *EvalContext) error { +func checkBranch(cx *evalContext) error { cx.nextpc = cx.pc + 3 target, err := branchTarget(cx) if err != nil { @@ -1235,7 +1232,7 @@ func checkBranch(cx *EvalContext) error { cx.branchTargets[target] = true return nil } -func opBnz(cx *EvalContext) { +func opBnz(cx *evalContext) { last := len(cx.stack) - 1 cx.nextpc = cx.pc + 3 isNonZero := cx.stack[last].Uint != 0 @@ -1250,7 +1247,7 @@ func opBnz(cx *EvalContext) { } } -func opBz(cx *EvalContext) { +func opBz(cx *evalContext) { last := len(cx.stack) - 1 cx.nextpc = cx.pc + 3 isZero := cx.stack[last].Uint == 0 @@ -1265,7 +1262,7 @@ func opBz(cx *EvalContext) { } } -func opB(cx *EvalContext) { +func opB(cx *evalContext) { target, err := branchTarget(cx) if err != nil { cx.err = err @@ -1274,12 +1271,12 @@ func opB(cx *EvalContext) { cx.nextpc = target } -func opCallSub(cx *EvalContext) { +func opCallSub(cx *evalContext) { cx.callstack = append(cx.callstack, cx.pc+3) opB(cx) } -func opRetSub(cx *EvalContext) { +func opRetSub(cx *evalContext) { top := len(cx.callstack) - 1 if top < 0 { cx.err = errors.New("retsub with empty callstack") @@ -1290,24 +1287,24 @@ func opRetSub(cx *EvalContext) { cx.nextpc = target } -func opPop(cx *EvalContext) { +func opPop(cx *evalContext) { last := len(cx.stack) - 1 cx.stack = cx.stack[:last] } -func opDup(cx *EvalContext) { +func opDup(cx *evalContext) { last := len(cx.stack) - 1 sv := cx.stack[last] cx.stack = append(cx.stack, sv) } -func opDup2(cx *EvalContext) { +func opDup2(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 cx.stack = append(cx.stack, cx.stack[prev:]...) } -func opDig(cx *EvalContext) { +func opDig(cx *evalContext) { depth := int(uint(cx.program[cx.pc+1])) idx := len(cx.stack) - 1 - depth // Need to check stack size explicitly here because checkArgs() doesn't understand dig @@ -1320,7 +1317,7 @@ func opDig(cx *EvalContext) { cx.stack = append(cx.stack, sv) } -func (cx *EvalContext) assetHoldingEnumToValue(holding *basics.AssetHolding, field uint64) (sv stackValue, err error) { +func (cx *evalContext) assetHoldingEnumToValue(holding *basics.AssetHolding, field uint64) (sv stackValue, err error) { switch AssetHoldingField(field) { case AssetBalance: sv.Uint = holding.Amount @@ -1339,7 +1336,7 @@ func (cx *EvalContext) assetHoldingEnumToValue(holding *basics.AssetHolding, fie return } -func (cx *EvalContext) assetParamsEnumToValue(params *basics.AssetParams, field uint64) (sv stackValue, err error) { +func (cx *evalContext) assetParamsEnumToValue(params *basics.AssetParams, field uint64) (sv stackValue, err error) { switch AssetParamsField(field) { case AssetTotal: sv.Uint = params.Total @@ -1378,12 +1375,12 @@ func (cx *EvalContext) assetParamsEnumToValue(params *basics.AssetParams, field // TxnFieldToTealValue is a thin wrapper for txnFieldToStack for external use func TxnFieldToTealValue(txn *transactions.Transaction, groupIndex int, field TxnField, arrayFieldIdx uint64) (basics.TealValue, error) { - cx := EvalContext{EvalParams: EvalParams{GroupIndex: groupIndex}} + cx := evalContext{EvalParams: EvalParams{GroupIndex: groupIndex}} sv, err := cx.txnFieldToStack(txn, field, arrayFieldIdx, groupIndex) return sv.toTealValue(), err } -func (cx *EvalContext) getTxID(txn *transactions.Transaction, groupIndex int) transactions.Txid { +func (cx *evalContext) getTxID(txn *transactions.Transaction, groupIndex int) transactions.Txid { // Initialize txidCache if necessary if cx.txidCache == nil { cx.txidCache = make(map[int]transactions.Txid, len(cx.TxnGroup)) @@ -1399,7 +1396,7 @@ func (cx *EvalContext) getTxID(txn *transactions.Transaction, groupIndex int) tr return txid } -func (cx *EvalContext) txnFieldToStack(txn *transactions.Transaction, field TxnField, arrayFieldIdx uint64, groupIndex int) (sv stackValue, err error) { +func (cx *evalContext) txnFieldToStack(txn *transactions.Transaction, field TxnField, arrayFieldIdx uint64, groupIndex int) (sv stackValue, err error) { err = nil switch field { case Sender: @@ -1579,7 +1576,7 @@ func (cx *EvalContext) txnFieldToStack(txn *transactions.Transaction, field TxnF return } -func opTxn(cx *EvalContext) { +func opTxn(cx *evalContext) { field := TxnField(uint64(cx.program[cx.pc+1])) fs, ok := txnFieldSpecByField[field] if !ok || fs.version > cx.version { @@ -1601,7 +1598,7 @@ func opTxn(cx *EvalContext) { cx.stack = append(cx.stack, sv) } -func opTxna(cx *EvalContext) { +func opTxna(cx *evalContext) { field := TxnField(uint64(cx.program[cx.pc+1])) fs, ok := txnFieldSpecByField[field] if !ok || fs.version > cx.version { @@ -1624,7 +1621,7 @@ func opTxna(cx *EvalContext) { cx.stack = append(cx.stack, sv) } -func opGtxn(cx *EvalContext) { +func opGtxn(cx *evalContext) { gtxid := int(uint(cx.program[cx.pc+1])) if gtxid >= len(cx.TxnGroup) { cx.err = fmt.Errorf("gtxn lookup TxnGroup[%d] but it only has %d", gtxid, len(cx.TxnGroup)) @@ -1657,7 +1654,7 @@ func opGtxn(cx *EvalContext) { cx.stack = append(cx.stack, sv) } -func opGtxna(cx *EvalContext) { +func opGtxna(cx *evalContext) { gtxid := int(uint(cx.program[cx.pc+1])) if gtxid >= len(cx.TxnGroup) { cx.err = fmt.Errorf("gtxna lookup TxnGroup[%d] but it only has %d", gtxid, len(cx.TxnGroup)) @@ -1686,7 +1683,7 @@ func opGtxna(cx *EvalContext) { cx.stack = append(cx.stack, sv) } -func opGtxns(cx *EvalContext) { +func opGtxns(cx *evalContext) { last := len(cx.stack) - 1 gtxid := int(cx.stack[last].Uint) if gtxid >= len(cx.TxnGroup) { @@ -1720,7 +1717,7 @@ func opGtxns(cx *EvalContext) { cx.stack[last] = sv } -func opGtxnsa(cx *EvalContext) { +func opGtxnsa(cx *evalContext) { last := len(cx.stack) - 1 gtxid := int(cx.stack[last].Uint) if gtxid >= len(cx.TxnGroup) { @@ -1750,7 +1747,7 @@ func opGtxnsa(cx *EvalContext) { cx.stack[last] = sv } -func (cx *EvalContext) getRound() (rnd uint64, err error) { +func (cx *evalContext) getRound() (rnd uint64, err error) { if cx.Ledger == nil { err = fmt.Errorf("ledger not available") return @@ -1758,7 +1755,7 @@ func (cx *EvalContext) getRound() (rnd uint64, err error) { return uint64(cx.Ledger.Round()), nil } -func (cx *EvalContext) getLatestTimestamp() (timestamp uint64, err error) { +func (cx *evalContext) getLatestTimestamp() (timestamp uint64, err error) { if cx.Ledger == nil { err = fmt.Errorf("ledger not available") return @@ -1771,7 +1768,7 @@ func (cx *EvalContext) getLatestTimestamp() (timestamp uint64, err error) { return uint64(ts), nil } -func (cx *EvalContext) getApplicationID() (rnd uint64, err error) { +func (cx *evalContext) getApplicationID() (rnd uint64, err error) { if cx.Ledger == nil { err = fmt.Errorf("ledger not available") return @@ -1779,7 +1776,7 @@ func (cx *EvalContext) getApplicationID() (rnd uint64, err error) { return uint64(cx.Ledger.ApplicationID()), nil } -func (cx *EvalContext) getCreatorAddress() ([]byte, error) { +func (cx *evalContext) getCreatorAddress() ([]byte, error) { if cx.Ledger == nil { return nil, fmt.Errorf("ledger not available") } @@ -1789,7 +1786,7 @@ func (cx *EvalContext) getCreatorAddress() ([]byte, error) { var zeroAddress basics.Address -func (cx *EvalContext) globalFieldToStack(field GlobalField) (sv stackValue, err error) { +func (cx *evalContext) globalFieldToStack(field GlobalField) (sv stackValue, err error) { switch field { case MinTxnFee: sv.Uint = cx.Proto.MinTxnFee @@ -1817,7 +1814,7 @@ func (cx *EvalContext) globalFieldToStack(field GlobalField) (sv stackValue, err return sv, err } -func opGlobal(cx *EvalContext) { +func opGlobal(cx *evalContext) { gindex := uint64(cx.program[cx.pc+1]) globalField := GlobalField(gindex) fs, ok := globalFieldSpecByField[globalField] @@ -1859,14 +1856,14 @@ func (msg Msg) ToBeHashed() (protocol.HashID, []byte) { } // programHash lets us lazily compute H(cx.program) -func (cx *EvalContext) programHash() crypto.Digest { +func (cx *evalContext) programHash() crypto.Digest { if cx.programHashCached == (crypto.Digest{}) { cx.programHashCached = crypto.HashObj(Program(cx.program)) } return cx.programHashCached } -func opEd25519verify(cx *EvalContext) { +func opEd25519verify(cx *evalContext) { last := len(cx.stack) - 1 // index of PK prev := last - 1 // index of signature pprev := prev - 1 // index of data @@ -1895,19 +1892,19 @@ func opEd25519verify(cx *EvalContext) { cx.stack = cx.stack[:prev] } -func opLoad(cx *EvalContext) { +func opLoad(cx *evalContext) { gindex := int(uint(cx.program[cx.pc+1])) cx.stack = append(cx.stack, cx.scratch[gindex]) } -func opStore(cx *EvalContext) { +func opStore(cx *evalContext) { gindex := int(uint(cx.program[cx.pc+1])) last := len(cx.stack) - 1 cx.scratch[gindex] = cx.stack[last] cx.stack = cx.stack[:last] } -func opConcat(cx *EvalContext) { +func opConcat(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 a := cx.stack[prev].Bytes @@ -1939,14 +1936,14 @@ func substring(x []byte, start, end int) (out []byte, err error) { return } -func opSubstring(cx *EvalContext) { +func opSubstring(cx *evalContext) { last := len(cx.stack) - 1 start := cx.program[cx.pc+1] end := cx.program[cx.pc+2] cx.stack[last].Bytes, cx.err = substring(cx.stack[last].Bytes, int(start), int(end)) } -func opSubstring3(cx *EvalContext) { +func opSubstring3(cx *evalContext) { last := len(cx.stack) - 1 // end prev := last - 1 // start pprev := prev - 1 // bytes @@ -1960,7 +1957,7 @@ func opSubstring3(cx *EvalContext) { cx.stack = cx.stack[:prev] } -func opGetBit(cx *EvalContext) { +func opGetBit(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 idx := cx.stack[last].Uint @@ -1997,7 +1994,7 @@ func opGetBit(cx *EvalContext) { cx.stack = cx.stack[:last] } -func opSetBit(cx *EvalContext) { +func opSetBit(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 pprev := prev - 1 @@ -2049,7 +2046,7 @@ func opSetBit(cx *EvalContext) { cx.stack = cx.stack[:prev] } -func opGetByte(cx *EvalContext) { +func opGetByte(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 @@ -2065,7 +2062,7 @@ func opGetByte(cx *EvalContext) { cx.stack = cx.stack[:last] } -func opSetByte(cx *EvalContext) { +func opSetByte(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 pprev := prev - 1 @@ -2083,7 +2080,7 @@ func opSetByte(cx *EvalContext) { cx.stack = cx.stack[:prev] } -func opBalance(cx *EvalContext) { +func opBalance(cx *evalContext) { last := len(cx.stack) - 1 // account offset accountIdx := cx.stack[last].Uint @@ -2108,7 +2105,7 @@ func opBalance(cx *EvalContext) { cx.stack[last].Uint = microAlgos.Raw } -func opMinBalance(cx *EvalContext) { +func opMinBalance(cx *evalContext) { last := len(cx.stack) - 1 // account offset accountIdx := cx.stack[last].Uint @@ -2133,7 +2130,7 @@ func opMinBalance(cx *EvalContext) { cx.stack[last].Uint = microAlgos.Raw } -func opAppCheckOptedIn(cx *EvalContext) { +func opAppCheckOptedIn(cx *evalContext) { last := len(cx.stack) - 1 // app id prev := last - 1 // account offset @@ -2166,7 +2163,7 @@ func opAppCheckOptedIn(cx *EvalContext) { cx.stack = cx.stack[:last] } -func (cx *EvalContext) appReadLocalKey(appIdx uint64, accountIdx uint64, key string) (basics.TealValue, bool, error) { +func (cx *evalContext) appReadLocalKey(appIdx uint64, accountIdx uint64, key string) (basics.TealValue, bool, error) { // Convert the account offset to an address addr, err := cx.Txn.Txn.AddressByIndex(accountIdx, cx.Txn.Txn.Sender) if err != nil { @@ -2176,7 +2173,7 @@ func (cx *EvalContext) appReadLocalKey(appIdx uint64, accountIdx uint64, key str } // appWriteLocalKey writes value to local key/value cow -func (cx *EvalContext) appWriteLocalKey(accountIdx uint64, key string, tv basics.TealValue) error { +func (cx *evalContext) appWriteLocalKey(accountIdx uint64, key string, tv basics.TealValue) error { // Convert the account offset to an address addr, err := cx.Txn.Txn.AddressByIndex(accountIdx, cx.Txn.Txn.Sender) if err != nil { @@ -2186,7 +2183,7 @@ func (cx *EvalContext) appWriteLocalKey(accountIdx uint64, key string, tv basics } // appDeleteLocalKey deletes a value from the key/value cow -func (cx *EvalContext) appDeleteLocalKey(accountIdx uint64, key string) error { +func (cx *evalContext) appDeleteLocalKey(accountIdx uint64, key string) error { // Convert the account offset to an address addr, err := cx.Txn.Txn.AddressByIndex(accountIdx, cx.Txn.Txn.Sender) if err != nil { @@ -2195,7 +2192,7 @@ func (cx *EvalContext) appDeleteLocalKey(accountIdx uint64, key string) error { return cx.Ledger.DelLocal(addr, key, accountIdx) } -func (cx *EvalContext) appReadGlobalKey(foreignAppsIndex uint64, key string) (basics.TealValue, bool, error) { +func (cx *evalContext) appReadGlobalKey(foreignAppsIndex uint64, key string) (basics.TealValue, bool, error) { if foreignAppsIndex > uint64(len(cx.Txn.Txn.ForeignApps)) { err := fmt.Errorf("invalid ForeignApps index %d", foreignAppsIndex) return basics.TealValue{}, false, err @@ -2209,15 +2206,15 @@ func (cx *EvalContext) appReadGlobalKey(foreignAppsIndex uint64, key string) (ba return cx.Ledger.GetGlobal(appIdx, key) } -func (cx *EvalContext) appWriteGlobalKey(key string, tv basics.TealValue) error { +func (cx *evalContext) appWriteGlobalKey(key string, tv basics.TealValue) error { return cx.Ledger.SetGlobal(key, tv) } -func (cx *EvalContext) appDeleteGlobalKey(key string) error { +func (cx *evalContext) appDeleteGlobalKey(key string) error { return cx.Ledger.DelGlobal(key) } -func opAppGetLocalState(cx *EvalContext) { +func opAppGetLocalState(cx *evalContext) { last := len(cx.stack) - 1 // state key prev := last - 1 // account offset @@ -2235,7 +2232,7 @@ func opAppGetLocalState(cx *EvalContext) { cx.stack = cx.stack[:last] } -func opAppGetLocalStateEx(cx *EvalContext) { +func opAppGetLocalStateEx(cx *evalContext) { last := len(cx.stack) - 1 // state key prev := last - 1 // app id pprev := prev - 1 // account offset @@ -2260,7 +2257,7 @@ func opAppGetLocalStateEx(cx *EvalContext) { cx.stack = cx.stack[:last] } -func opAppGetLocalStateImpl(cx *EvalContext, appID uint64, key []byte, accountIdx uint64) (result stackValue, ok bool, err error) { +func opAppGetLocalStateImpl(cx *evalContext, appID uint64, key []byte, accountIdx uint64) (result stackValue, ok bool, err error) { if cx.Ledger == nil { err = fmt.Errorf("ledger not available") return @@ -2278,7 +2275,7 @@ func opAppGetLocalStateImpl(cx *EvalContext, appID uint64, key []byte, accountId return } -func opAppGetGlobalStateImpl(cx *EvalContext, appIndex uint64, key []byte) (result stackValue, ok bool, err error) { +func opAppGetGlobalStateImpl(cx *evalContext, appIndex uint64, key []byte) (result stackValue, ok bool, err error) { if cx.Ledger == nil { err = fmt.Errorf("ledger not available") return @@ -2295,7 +2292,7 @@ func opAppGetGlobalStateImpl(cx *EvalContext, appIndex uint64, key []byte) (resu return } -func opAppGetGlobalState(cx *EvalContext) { +func opAppGetGlobalState(cx *evalContext) { last := len(cx.stack) - 1 // state key key := cx.stack[last].Bytes @@ -2310,7 +2307,7 @@ func opAppGetGlobalState(cx *EvalContext) { cx.stack[last] = result } -func opAppGetGlobalStateEx(cx *EvalContext) { +func opAppGetGlobalStateEx(cx *evalContext) { last := len(cx.stack) - 1 // state key prev := last - 1 @@ -2332,7 +2329,7 @@ func opAppGetGlobalStateEx(cx *EvalContext) { cx.stack[last] = isOk } -func opAppPutLocalState(cx *EvalContext) { +func opAppPutLocalState(cx *evalContext) { last := len(cx.stack) - 1 // value prev := last - 1 // state key pprev := prev - 1 // account offset @@ -2355,7 +2352,7 @@ func opAppPutLocalState(cx *EvalContext) { cx.stack = cx.stack[:pprev] } -func opAppPutGlobalState(cx *EvalContext) { +func opAppPutGlobalState(cx *evalContext) { last := len(cx.stack) - 1 // value prev := last - 1 // state key @@ -2376,7 +2373,7 @@ func opAppPutGlobalState(cx *EvalContext) { cx.stack = cx.stack[:prev] } -func opAppDeleteLocalState(cx *EvalContext) { +func opAppDeleteLocalState(cx *evalContext) { last := len(cx.stack) - 1 // key prev := last - 1 // account offset @@ -2397,7 +2394,7 @@ func opAppDeleteLocalState(cx *EvalContext) { cx.stack = cx.stack[:prev] } -func opAppDeleteGlobalState(cx *EvalContext) { +func opAppDeleteGlobalState(cx *evalContext) { last := len(cx.stack) - 1 // key key := string(cx.stack[last].Bytes) @@ -2415,7 +2412,7 @@ func opAppDeleteGlobalState(cx *EvalContext) { cx.stack = cx.stack[:last] } -func opAssetHoldingGet(cx *EvalContext) { +func opAssetHoldingGet(cx *evalContext) { last := len(cx.stack) - 1 // asset id prev := last - 1 // account offset @@ -2450,7 +2447,7 @@ func opAssetHoldingGet(cx *EvalContext) { cx.stack[last].Uint = exist } -func opAssetParamsGet(cx *EvalContext) { +func opAssetParamsGet(cx *evalContext) { last := len(cx.stack) - 1 // foreign asset id foreignAssetsIndex := cx.stack[last].Uint diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 1a9b7c82e1..98c791a1e6 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -2734,7 +2734,7 @@ func TestReturnTypes(t *testing.T) { source := sb.String() ops := testProg(t, source, AssemblerMaxVersion) - var cx EvalContext + var cx evalContext cx.EvalParams = ep cx.runModeFlags = m diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 548fd363c1..6027d14016 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -2830,10 +2830,10 @@ func TestShortBytecblock2(t *testing.T) { const panicString = "out of memory, buffer overrun, stack overflow, divide by zero, halt and catch fire" -func opPanic(cx *EvalContext) { +func opPanic(cx *evalContext) { panic(panicString) } -func checkPanic(cx *EvalContext) error { +func checkPanic(cx *evalContext) error { panic(panicString) } @@ -3637,7 +3637,7 @@ intc_0 opsByOpcode[LogicVersion][spec.Opcode] = origSpec }() - spec.op = func(cx *EvalContext) { + spec.op = func(cx *evalContext) { // overflow cx.stack = make([]stackValue, 2000) } diff --git a/ledger/eval.go b/ledger/eval.go index 7dbb43c8df..100d17df0b 100644 --- a/ledger/eval.go +++ b/ledger/eval.go @@ -667,8 +667,7 @@ func (eval *BlockEvaluator) TransactionGroup(txads []transactions.SignedTxnWithA // prepareEvalParams creates a logic.EvalParams for each ApplicationCall // transaction in the group func (eval *BlockEvaluator) prepareEvalParams(txgroup []transactions.SignedTxnWithAD) (res []*logic.EvalParams) { - var txnGroupNoAD []transactions.SignedTxn - var contextGroup []*logic.EvalContext + var groupNoAD []transactions.SignedTxn var minTealVersion uint64 res = make([]*logic.EvalParams, len(txgroup)) for i, txn := range txgroup { @@ -677,35 +676,22 @@ func (eval *BlockEvaluator) prepareEvalParams(txgroup []transactions.SignedTxnWi continue } - // Initialize context group pointers and transaction group without ApplyData lazily - if txnGroupNoAD == nil { - txnGroupNoAD = make([]transactions.SignedTxn, len(txgroup)) + // Initialize group without ApplyData lazily + if groupNoAD == nil { + groupNoAD = make([]transactions.SignedTxn, len(txgroup)) for j := range txgroup { - txnGroupNoAD[j] = txgroup[j].SignedTxn + groupNoAD[j] = txgroup[j].SignedTxn } - - contextGroup = make([]*logic.EvalContext, len(txgroup)) - for j, txn := range txgroup { - // Only allocate space for ApplicationCall transactions - if txn.SignedTxn.Txn.Type == protocol.ApplicationCallTx { - contextGroup[j] = new(logic.EvalContext) - } - } - - minTealVersion = logic.ComputeMinTealVersion(txnGroupNoAD) + minTealVersion = logic.ComputeMinTealVersion(groupNoAD) } res[i] = &logic.EvalParams{ - Txn: &txnGroupNoAD[i], - Cx: contextGroup[i], + Txn: &groupNoAD[i], Proto: &eval.proto, - TxnGroup: txnGroupNoAD, - CxGroup: contextGroup, + TxnGroup: groupNoAD, GroupIndex: i, MinTealVersion: &minTealVersion, } - - contextGroup[i].EvalParams = *res[i] } return } From be4b38c3fb56374b4d1511c776016147368e9025 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Wed, 19 May 2021 15:52:33 -0400 Subject: [PATCH 08/35] Use `EvalSideEffects` struct in `EvalParams` * Add `clone` method to `stackValue` struct to protect against malicious byteslice editing --- data/transactions/logic/eval.go | 39 ++++++++++++++++++++++++++++++++- ledger/eval.go | 7 ++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 85d5fb314f..28abb3d6ab 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -71,6 +71,17 @@ func (sv *stackValue) typeName() string { return "uint64" } +func (sv *stackValue) clone() stackValue { + if sv.Bytes != nil { + // clone stack value if Bytes + bytesClone := make([]byte, len(sv.Bytes)) + copy(bytesClone, sv.Bytes) + return stackValue{Bytes: bytesClone} + } + // otherwise no cloning is needed if Uint + return stackValue{Uint: sv.Uint} +} + func (sv *stackValue) String() string { if sv.Bytes != nil { return hex.EncodeToString(sv.Bytes) @@ -144,6 +155,22 @@ type LedgerForLogic interface { GetDelta(txn *transactions.Transaction) (evalDelta basics.EvalDelta, err error) } +// EvalSideEffects contains data returned from evaluation +type EvalSideEffects struct { + scratchSpace scratchSpace +} + +// GetScratchValue loads and clones a stackValue +// The value is cloned so the original bytes are protected from changes +func (se *EvalSideEffects) GetScratchValue(stackPos uint8) stackValue { + return se.scratchSpace[stackPos].clone() +} + +// SetScratchSpace stores the scratch space +func (se *EvalSideEffects) SetScratchSpace(scratch scratchSpace) { + se.scratchSpace = scratch +} + // EvalParams contains data that comes into condition evaluation. type EvalParams struct { // the transaction being evaluated @@ -158,6 +185,10 @@ type EvalParams struct { // GroupIndex should point to Txn within TxnGroup GroupIndex int + SideEffects *EvalSideEffects + + PastSideEffects []EvalSideEffects + Logger logging.Logger Ledger LedgerForLogic @@ -221,6 +252,8 @@ func (ep EvalParams) log() logging.Logger { return logging.Base() } +type scratchSpace = [256]stackValue + type evalContext struct { EvalParams @@ -233,7 +266,7 @@ type evalContext struct { intc []uint64 bytec [][]byte version uint64 - scratch [256]stackValue + scratch scratchSpace cost int // cost incurred so far @@ -428,6 +461,10 @@ func eval(program []byte, cx *evalContext) (pass bool, err error) { if cx.stack[0].Bytes != nil { return false, errors.New("stack finished with bytes not int") } + + // set side effects + cx.SideEffects.SetScratchSpace(cx.scratch) + return cx.stack[0].Uint != 0, nil } diff --git a/ledger/eval.go b/ledger/eval.go index 100d17df0b..dfccb5ca17 100644 --- a/ledger/eval.go +++ b/ledger/eval.go @@ -723,6 +723,13 @@ func (eval *BlockEvaluator) transactionGroup(txgroup []transactions.SignedTxnWit for gi, txad := range txgroup { var txib transactions.SignedTxnInBlock + // Append the last txn's side effects to the existing list of past side effects + if gi != 0 { + accumulatedSideEffects := evalParams[gi-1].PastSideEffects + lastSideEffects := *evalParams[gi-1].SideEffects + evalParams[gi].PastSideEffects = append(accumulatedSideEffects, lastSideEffects) + } + err := eval.transaction(txad.SignedTxn, evalParams[gi], txad.ApplyData, cow, &txib) if err != nil { return err From 065de3c27106ead1838b342a9ebe5b74a7124d50 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Wed, 19 May 2021 15:55:54 -0400 Subject: [PATCH 09/35] Remove artifacts from previous attempts --- data/transactions/logic/eval.go | 2 +- data/transactions/logic/eval_test.go | 15 --------------- ledger/eval_test.go | 18 ------------------ 3 files changed, 1 insertion(+), 34 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 28abb3d6ab..a1b8a8515c 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1594,7 +1594,7 @@ func (cx *evalContext) txnFieldToStack(txn *transactions.Transaction, field TxnF err = fmt.Errorf("can't get future Scratch from txn with index %d", groupIndex) return } - val := cx.CxGroup[groupIndex].scratch[arrayFieldIdx] + val := cx.PastSideEffects[groupIndex].GetScratchValue(uint8(arrayFieldIdx)) if val.Bytes != nil { sv.Bytes = val.Bytes } else { diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 6027d14016..0994065eb2 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -87,7 +87,6 @@ func defaultEvalParamsWithVersion(sb *strings.Builder, txn *transactions.SignedT ep := EvalParams{} ep.Proto = &proto ep.Txn = pt - ep.Cx = new(EvalContext) if sb != nil { // have to do this since go's nil semantics: https://golang.org/doc/faq#nil_error ep.Trace = sb } @@ -1895,10 +1894,6 @@ int 1`, }, } } - cxgroup := make([]*EvalContext, len(sources)) - for j := range cxgroup { - cxgroup[j] = new(EvalContext) - } // Construct EvalParams epList := make([]EvalParams, len(sources)) @@ -1906,12 +1901,9 @@ int 1`, epList[j] = EvalParams{ Proto: &proto, Txn: &txgroup[j], - Cx: cxgroup[j], TxnGroup: txgroup, - CxGroup: cxgroup, GroupIndex: j, } - cxgroup[j].EvalParams = epList[j] } // Evaluate app calls @@ -1953,10 +1945,6 @@ int 1`, }, } txgroup[1] = transactions.SignedTxn{} - cxgroup := make([]*EvalContext, 2) - for j := range cxgroup { - cxgroup[j] = new(EvalContext) - } // Construct EvalParams epList := make([]EvalParams, 2) @@ -1964,12 +1952,9 @@ int 1`, epList[j] = EvalParams{ Proto: &proto, Txn: &txgroup[j], - Cx: cxgroup[j], TxnGroup: txgroup, - CxGroup: cxgroup, GroupIndex: j, } - cxgroup[j].EvalParams = epList[j] } // Evaluate app call diff --git a/ledger/eval_test.go b/ledger/eval_test.go index e896d8ab57..1035c6ef9a 100644 --- a/ledger/eval_test.go +++ b/ledger/eval_test.go @@ -418,24 +418,6 @@ func TestPrepareEvalParams(t *testing.T) { } }) } - - // Test evalContext pointers were set up correctly - appCallsGroup := cases[6].group - epList := eval.prepareEvalParams(appCallsGroup) - - for i := range appCallsGroup { - for j := range epList { - require.Equal(t, epList[j].Cx, epList[i].CxGroup[j]) - } - } - - // And confirm that mutating an evalContext mutates that one in the group - original := epList[0].Cx - pointer := epList[1].CxGroup[0] - newVal := 1 - require.NotEqual(t, newVal, original.GroupIndex) - original.GroupIndex = newVal - require.Equal(t, newVal, pointer.GroupIndex) } func testLedgerCleanup(l *Ledger, dbName string, inMem bool) { From 6b508b4c85d792dc4bd6dc5d95760879604f6976 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 20 May 2021 10:40:42 -0400 Subject: [PATCH 10/35] Add helper method `accumulatePastSideEffects` * Expose `StackValue` for testing --- data/transactions/logic/debugger.go | 8 +-- data/transactions/logic/eval.go | 76 ++++++++++++++-------------- data/transactions/logic/eval_test.go | 19 ++++--- ledger/eval.go | 18 ++++--- ledger/eval_test.go | 26 ++++++++++ 5 files changed, 92 insertions(+), 55 deletions(-) diff --git a/data/transactions/logic/debugger.go b/data/transactions/logic/debugger.go index 021a53faf1..6405c1726a 100644 --- a/data/transactions/logic/debugger.go +++ b/data/transactions/logic/debugger.go @@ -107,7 +107,7 @@ func makeDebugState(cx *evalContext) DebugState { for fieldIdx := range GlobalFieldNames { sv, err := cx.globalFieldToStack(GlobalField(fieldIdx)) if err != nil { - sv = stackValue{Bytes: []byte(err.Error())} + sv = StackValue{Bytes: []byte(err.Error())} } globals[fieldIdx] = stackValueToTealValue(&sv) } @@ -117,7 +117,7 @@ func makeDebugState(cx *evalContext) DebugState { if (cx.runModeFlags & runModeApplication) != 0 { ds.EvalDelta, err = cx.Ledger.GetDelta(&cx.Txn.Txn) if err != nil { - sv := stackValue{Bytes: []byte(err.Error())} + sv := StackValue{Bytes: []byte(err.Error())} tv := stackValueToTealValue(&sv) vd := tv.ToValueDelta() ds.EvalDelta.GlobalDelta = basics.StateDelta{"error": vd} @@ -176,7 +176,7 @@ func (d *DebugState) PCToLine(pc int) int { return len(strings.Split(d.Disassembly[:offset], "\n")) - one } -func stackValueToTealValue(sv *stackValue) basics.TealValue { +func stackValueToTealValue(sv *StackValue) basics.TealValue { tv := sv.toTealValue() return basics.TealValue{ Type: tv.Type, @@ -221,7 +221,7 @@ func (cx *evalContext) refreshDebugState() *DebugState { var err error ds.EvalDelta, err = cx.Ledger.GetDelta(&cx.Txn.Txn) if err != nil { - sv := stackValue{Bytes: []byte(err.Error())} + sv := StackValue{Bytes: []byte(err.Error())} tv := stackValueToTealValue(&sv) vd := tv.ToValueDelta() ds.EvalDelta.GlobalDelta = basics.StateDelta{"error": vd} diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index a1b8a8515c..f4c8be27a3 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -49,47 +49,47 @@ const EvalMaxScratchSize = 255 // MaxStringSize is the limit of byte strings created by `concat` const MaxStringSize = 4096 -// stackValue is the type for the operand stack. -// Each stackValue is either a valid []byte value or a uint64 value. -// If (.Bytes != nil) the stackValue is a []byte value, otherwise uint64 value. -type stackValue struct { +// StackValue is the type for the operand stack. +// Each StackValue is either a valid []byte value or a uint64 value. +// If (.Bytes != nil) the StackValue is a []byte value, otherwise uint64 value. +type StackValue struct { Uint uint64 Bytes []byte } -func (sv *stackValue) argType() StackType { +func (sv *StackValue) argType() StackType { if sv.Bytes != nil { return StackBytes } return StackUint64 } -func (sv *stackValue) typeName() string { +func (sv *StackValue) typeName() string { if sv.Bytes != nil { return "[]byte" } return "uint64" } -func (sv *stackValue) clone() stackValue { +func (sv *StackValue) clone() StackValue { if sv.Bytes != nil { // clone stack value if Bytes bytesClone := make([]byte, len(sv.Bytes)) copy(bytesClone, sv.Bytes) - return stackValue{Bytes: bytesClone} + return StackValue{Bytes: bytesClone} } // otherwise no cloning is needed if Uint - return stackValue{Uint: sv.Uint} + return StackValue{Uint: sv.Uint} } -func (sv *stackValue) String() string { +func (sv *StackValue) String() string { if sv.Bytes != nil { return hex.EncodeToString(sv.Bytes) } return fmt.Sprintf("%d 0x%x", sv.Uint, sv.Uint) } -func stackValueFromTealValue(tv *basics.TealValue) (sv stackValue, err error) { +func stackValueFromTealValue(tv *basics.TealValue) (sv StackValue, err error) { switch tv.Type { case basics.TealBytesType: sv.Bytes = []byte(tv.Bytes) @@ -124,7 +124,7 @@ func ComputeMinTealVersion(group []transactions.SignedTxn) uint64 { return minVersion } -func (sv *stackValue) toTealValue() (tv basics.TealValue) { +func (sv *StackValue) toTealValue() (tv basics.TealValue) { if sv.argType() == StackBytes { return basics.TealValue{Type: basics.TealBytesType, Bytes: string(sv.Bytes)} } @@ -162,7 +162,7 @@ type EvalSideEffects struct { // GetScratchValue loads and clones a stackValue // The value is cloned so the original bytes are protected from changes -func (se *EvalSideEffects) GetScratchValue(stackPos uint8) stackValue { +func (se *EvalSideEffects) GetScratchValue(stackPos uint8) StackValue { return se.scratchSpace[stackPos].clone() } @@ -252,12 +252,12 @@ func (ep EvalParams) log() logging.Logger { return logging.Base() } -type scratchSpace = [256]stackValue +type scratchSpace = [256]StackValue type evalContext struct { EvalParams - stack []stackValue + stack []StackValue callstack []int program []byte // txn.Lsig.Logic ? pc int @@ -423,7 +423,7 @@ func eval(program []byte, cx *evalContext) (pass bool, err error) { cx.version = version cx.pc = vlen - cx.stack = make([]stackValue, 0, 10) + cx.stack = make([]StackValue, 0, 10) cx.program = program if cx.Debugger != nil { @@ -1131,7 +1131,7 @@ func opIntConstN(cx *evalContext, n uint) { cx.err = fmt.Errorf("intc [%d] beyond %d constants", n, len(cx.intc)) return } - cx.stack = append(cx.stack, stackValue{Uint: cx.intc[n]}) + cx.stack = append(cx.stack, StackValue{Uint: cx.intc[n]}) } func opIntConstLoad(cx *evalContext) { n := uint(cx.program[cx.pc+1]) @@ -1156,7 +1156,7 @@ func opPushInt(cx *evalContext) { cx.err = fmt.Errorf("could not decode int at pc=%d", cx.pc+1) return } - sv := stackValue{Uint: val} + sv := StackValue{Uint: val} cx.stack = append(cx.stack, sv) cx.nextpc = cx.pc + 1 + bytesUsed } @@ -1170,7 +1170,7 @@ func opByteConstN(cx *evalContext, n uint) { cx.err = fmt.Errorf("bytec [%d] beyond %d constants", n, len(cx.bytec)) return } - cx.stack = append(cx.stack, stackValue{Bytes: cx.bytec[n]}) + cx.stack = append(cx.stack, StackValue{Bytes: cx.bytec[n]}) } func opByteConstLoad(cx *evalContext) { n := uint(cx.program[cx.pc+1]) @@ -1202,7 +1202,7 @@ func opPushBytes(cx *evalContext) { cx.err = fmt.Errorf("pushbytes too long at pc=%d", pos) return } - sv := stackValue{Bytes: cx.program[pos:end]} + sv := StackValue{Bytes: cx.program[pos:end]} cx.stack = append(cx.stack, sv) cx.nextpc = int(end) } @@ -1213,7 +1213,7 @@ func opArgN(cx *evalContext, n uint64) { return } val := nilToEmpty(cx.Txn.Lsig.Args[n]) - cx.stack = append(cx.stack, stackValue{Bytes: val}) + cx.stack = append(cx.stack, StackValue{Bytes: val}) } func opArg(cx *evalContext) { @@ -1354,7 +1354,7 @@ func opDig(cx *evalContext) { cx.stack = append(cx.stack, sv) } -func (cx *evalContext) assetHoldingEnumToValue(holding *basics.AssetHolding, field uint64) (sv stackValue, err error) { +func (cx *evalContext) assetHoldingEnumToValue(holding *basics.AssetHolding, field uint64) (sv StackValue, err error) { switch AssetHoldingField(field) { case AssetBalance: sv.Uint = holding.Amount @@ -1373,7 +1373,7 @@ func (cx *evalContext) assetHoldingEnumToValue(holding *basics.AssetHolding, fie return } -func (cx *evalContext) assetParamsEnumToValue(params *basics.AssetParams, field uint64) (sv stackValue, err error) { +func (cx *evalContext) assetParamsEnumToValue(params *basics.AssetParams, field uint64) (sv StackValue, err error) { switch AssetParamsField(field) { case AssetTotal: sv.Uint = params.Total @@ -1433,7 +1433,7 @@ func (cx *evalContext) getTxID(txn *transactions.Transaction, groupIndex int) tr return txid } -func (cx *evalContext) txnFieldToStack(txn *transactions.Transaction, field TxnField, arrayFieldIdx uint64, groupIndex int) (sv stackValue, err error) { +func (cx *evalContext) txnFieldToStack(txn *transactions.Transaction, field TxnField, arrayFieldIdx uint64, groupIndex int) (sv StackValue, err error) { err = nil switch field { case Sender: @@ -1625,7 +1625,7 @@ func opTxn(cx *evalContext) { cx.err = fmt.Errorf("invalid txn field %d", field) return } - var sv stackValue + var sv StackValue var err error sv, err = cx.txnFieldToStack(&cx.Txn.Txn, field, 0, cx.GroupIndex) if err != nil { @@ -1647,7 +1647,7 @@ func opTxna(cx *evalContext) { cx.err = fmt.Errorf("txna unsupported field %d", field) return } - var sv stackValue + var sv StackValue var err error arrayFieldIdx := uint64(cx.program[cx.pc+2]) sv, err = cx.txnFieldToStack(&cx.Txn.Txn, field, arrayFieldIdx, cx.GroupIndex) @@ -1676,7 +1676,7 @@ func opGtxn(cx *evalContext) { cx.err = fmt.Errorf("invalid txn field %d", field) return } - var sv stackValue + var sv StackValue var err error if TxnField(field) == GroupIndex { // GroupIndex; asking this when we just specified it is _dumb_, but oh well @@ -1709,7 +1709,7 @@ func opGtxna(cx *evalContext) { cx.err = fmt.Errorf("gtxna unsupported field %d", field) return } - var sv stackValue + var sv StackValue var err error arrayFieldIdx := uint64(cx.program[cx.pc+3]) sv, err = cx.txnFieldToStack(tx, field, arrayFieldIdx, gtxid) @@ -1739,7 +1739,7 @@ func opGtxns(cx *evalContext) { cx.err = fmt.Errorf("invalid txn field %d", field) return } - var sv stackValue + var sv StackValue var err error if TxnField(field) == GroupIndex { // GroupIndex; asking this when we just specified it is _dumb_, but oh well @@ -1773,7 +1773,7 @@ func opGtxnsa(cx *evalContext) { cx.err = fmt.Errorf("gtxnsa unsupported field %d", field) return } - var sv stackValue + var sv StackValue var err error arrayFieldIdx := uint64(cx.program[cx.pc+2]) sv, err = cx.txnFieldToStack(tx, field, arrayFieldIdx, gtxid) @@ -1823,7 +1823,7 @@ func (cx *evalContext) getCreatorAddress() ([]byte, error) { var zeroAddress basics.Address -func (cx *evalContext) globalFieldToStack(field GlobalField) (sv stackValue, err error) { +func (cx *evalContext) globalFieldToStack(field GlobalField) (sv StackValue, err error) { switch field { case MinTxnFee: sv.Uint = cx.Proto.MinTxnFee @@ -2284,7 +2284,7 @@ func opAppGetLocalStateEx(cx *evalContext) { return } - var isOk stackValue + var isOk StackValue if ok { isOk.Uint = 1 } @@ -2294,7 +2294,7 @@ func opAppGetLocalStateEx(cx *evalContext) { cx.stack = cx.stack[:last] } -func opAppGetLocalStateImpl(cx *evalContext, appID uint64, key []byte, accountIdx uint64) (result stackValue, ok bool, err error) { +func opAppGetLocalStateImpl(cx *evalContext, appID uint64, key []byte, accountIdx uint64) (result StackValue, ok bool, err error) { if cx.Ledger == nil { err = fmt.Errorf("ledger not available") return @@ -2312,7 +2312,7 @@ func opAppGetLocalStateImpl(cx *evalContext, appID uint64, key []byte, accountId return } -func opAppGetGlobalStateImpl(cx *evalContext, appIndex uint64, key []byte) (result stackValue, ok bool, err error) { +func opAppGetGlobalStateImpl(cx *evalContext, appIndex uint64, key []byte) (result StackValue, ok bool, err error) { if cx.Ledger == nil { err = fmt.Errorf("ledger not available") return @@ -2357,7 +2357,7 @@ func opAppGetGlobalStateEx(cx *evalContext) { return } - var isOk stackValue + var isOk StackValue if ok { isOk.Uint = 1 } @@ -2469,7 +2469,7 @@ func opAssetHoldingGet(cx *evalContext) { } var exist uint64 = 0 - var value stackValue + var value StackValue if holding, err := cx.Ledger.AssetHolding(addr, basics.AssetIndex(assetID)); err == nil { // the holding exist, read the value exist = 1 @@ -2502,7 +2502,7 @@ func opAssetParamsGet(cx *evalContext) { assetID := cx.Txn.Txn.ForeignAssets[foreignAssetsIndex] var exist uint64 = 0 - var value stackValue + var value StackValue if params, err := cx.Ledger.AssetParams(basics.AssetIndex(assetID)); err == nil { // params exist, read the value exist = 1 @@ -2514,5 +2514,5 @@ func opAssetParamsGet(cx *evalContext) { } cx.stack[last] = value - cx.stack = append(cx.stack, stackValue{Uint: exist}) + cx.stack = append(cx.stack, StackValue{Uint: exist}) } diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 0994065eb2..cfea03784b 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1899,10 +1899,11 @@ int 1`, epList := make([]EvalParams, len(sources)) for j := range sources { epList[j] = EvalParams{ - Proto: &proto, - Txn: &txgroup[j], - TxnGroup: txgroup, - GroupIndex: j, + Proto: &proto, + Txn: &txgroup[j], + TxnGroup: txgroup, + GroupIndex: j, + SideEffects: &EvalSideEffects{}, } } @@ -1910,6 +1911,12 @@ int 1`, shouldErr := testCase.errContains != "" didPass := true for j, ops := range opsList { + if j != 0 { + accumulatedSideEffects := epList[j-1].PastSideEffects + lastSideEffects := *epList[j-1].SideEffects + epList[j].PastSideEffects = append(accumulatedSideEffects, lastSideEffects) + } + pass, err := Eval(ops.Program, epList[j]) // Confirm it errors or that the error message is the expected one @@ -3624,7 +3631,7 @@ intc_0 spec.op = func(cx *evalContext) { // overflow - cx.stack = make([]stackValue, 2000) + cx.stack = make([]StackValue, 2000) } opsByOpcode[LogicVersion][spec.Opcode] = spec _, err = Eval(ops.Program, ep) @@ -3731,7 +3738,7 @@ byte 0x // empty byte constant func TestArgType(t *testing.T) { t.Parallel() - var sv stackValue + var sv StackValue require.Equal(t, StackUint64, sv.argType()) sv.Bytes = []byte("") require.Equal(t, StackBytes, sv.argType()) diff --git a/ledger/eval.go b/ledger/eval.go index dfccb5ca17..d5092b735b 100644 --- a/ledger/eval.go +++ b/ledger/eval.go @@ -690,12 +690,22 @@ func (eval *BlockEvaluator) prepareEvalParams(txgroup []transactions.SignedTxnWi Proto: &eval.proto, TxnGroup: groupNoAD, GroupIndex: i, + SideEffects: &logic.EvalSideEffects{}, MinTealVersion: &minTealVersion, } } return } +// appendSideEffects appends the last txn's side effects to the list of past side effects +func (eval *BlockEvaluator) accumulatePastSideEffects(gi int, evalParams []*logic.EvalParams) { + if gi != 0 { + accumulatedSideEffects := evalParams[gi-1].PastSideEffects + lastSideEffects := *evalParams[gi-1].SideEffects + evalParams[gi].PastSideEffects = append(accumulatedSideEffects, lastSideEffects) + } +} + // transactionGroup tentatively executes a group of transactions as part of this block evaluation. // If the transaction group cannot be added to the block without violating some constraints, // an error is returned and the block evaluator state is unchanged. @@ -723,13 +733,7 @@ func (eval *BlockEvaluator) transactionGroup(txgroup []transactions.SignedTxnWit for gi, txad := range txgroup { var txib transactions.SignedTxnInBlock - // Append the last txn's side effects to the existing list of past side effects - if gi != 0 { - accumulatedSideEffects := evalParams[gi-1].PastSideEffects - lastSideEffects := *evalParams[gi-1].SideEffects - evalParams[gi].PastSideEffects = append(accumulatedSideEffects, lastSideEffects) - } - + eval.accumulatePastSideEffects(gi, evalParams) err := eval.transaction(txad.SignedTxn, evalParams[gi], txad.ApplyData, cow, &txib) if err != nil { return err diff --git a/ledger/eval_test.go b/ledger/eval_test.go index 1035c6ef9a..ce137a2a73 100644 --- a/ledger/eval_test.go +++ b/ledger/eval_test.go @@ -408,6 +408,7 @@ func TestPrepareEvalParams(t *testing.T) { for j, present := range testCase.expected { if present { require.NotNil(t, res[j]) + require.NotNil(t, res[j].SideEffects) require.Equal(t, res[j].GroupIndex, j) require.Equal(t, res[j].TxnGroup, expGroupNoAD) require.Equal(t, *res[j].Proto, eval.proto) @@ -420,6 +421,31 @@ func TestPrepareEvalParams(t *testing.T) { } } +func TestAccumulatePastSideEffects(t *testing.T) { + eval := BlockEvaluator{} + + evalParams := make([]*logic.EvalParams, 2) + for i := range evalParams { + evalParams[i] = &logic.EvalParams{ + SideEffects: &logic.EvalSideEffects{}, + } + } + + var scratchSpace [256]logic.StackValue + scratchSpace[0] = logic.StackValue{Uint: 1} + + eval.accumulatePastSideEffects(0, evalParams) + evalParams[0].SideEffects.SetScratchSpace(scratchSpace) + require.Empty(t, evalParams[1].PastSideEffects) + eval.accumulatePastSideEffects(1, evalParams) + require.NotEmpty(t, evalParams[1].PastSideEffects) + require.Equal( + t, + uint64(1), + evalParams[1].PastSideEffects[0].GetScratchValue(0).Uint, + ) +} + func testLedgerCleanup(l *Ledger, dbName string, inMem bool) { l.Close() if !inMem { From 918accce976a16ae8c7746f9579c3e66a761198c Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 20 May 2021 09:53:10 -0400 Subject: [PATCH 11/35] Error if using Scratch field from within a LogicSig --- data/transactions/logic/eval.go | 3 ++ data/transactions/logic/eval_test.go | 81 ++++++++++++++++++---------- 2 files changed, 57 insertions(+), 27 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index f4c8be27a3..d59315e58e 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1584,6 +1584,9 @@ func (cx *evalContext) txnFieldToStack(txn *transactions.Transaction, field TxnF if txn.Type != protocol.ApplicationCallTx { err = fmt.Errorf("can't use Scratch txn field on non-app call txn with index %d", groupIndex) return + } else if cx.Txn.Lsig.Logic != nil { + err = fmt.Errorf("can't use Scratch txn field from within a LogicSig") + return } else if arrayFieldIdx >= 256 { err = fmt.Errorf("invalid Scratch index %d", arrayFieldIdx) return diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index cfea03784b..3c1a7ce1d8 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1936,39 +1936,66 @@ int 1`, }) } - // Test failure on non-app call - t.Run("should fail on non-app call", func(t *testing.T) { - source := "gtxna 0 Scratch 0" - - // Assemble ops - ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) + type failureCase struct { + firstTxn transactions.SignedTxn + secondTxn transactions.SignedTxn + errContains string + } - // Initialize txgroup and cxgroup - txgroup := make([]transactions.SignedTxn, 2) - txgroup[0] = transactions.SignedTxn{ + nonAppCall := failureCase{ + firstTxn: transactions.SignedTxn{ Txn: transactions.Transaction{ Type: protocol.PaymentTx, }, - } - txgroup[1] = transactions.SignedTxn{} - - // Construct EvalParams - epList := make([]EvalParams, 2) - for j := range epList { - epList[j] = EvalParams{ - Proto: &proto, - Txn: &txgroup[j], - TxnGroup: txgroup, - GroupIndex: j, + }, + errContains: "can't use Scratch txn field on non-app call txn with index 0", + } + + logicSigCall := failureCase{ + firstTxn: transactions.SignedTxn{ + Txn: transactions.Transaction{ + Type: protocol.ApplicationCallTx, + }, + }, + secondTxn: transactions.SignedTxn{ + Lsig: transactions.LogicSig{ + Logic: []byte{1}, + }, + }, + errContains: "can't use Scratch txn field from within a LogicSig", + } + + failCases := []failureCase{nonAppCall, logicSigCall} + for j, failCase := range failCases { + t.Run(fmt.Sprintf("j=%d", j), func(t *testing.T) { + source := "gtxna 0 Scratch 0" + + // Assemble ops + ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) + require.NoError(t, err) + + // Initialize txgroup and cxgroup + txgroup := make([]transactions.SignedTxn, 2) + txgroup[0] = failCase.firstTxn + txgroup[1] = failCase.secondTxn + + // Construct EvalParams + epList := make([]EvalParams, 2) + for j := range epList { + epList[j] = EvalParams{ + Proto: &proto, + Txn: &txgroup[j], + TxnGroup: txgroup, + GroupIndex: j, + } } - } - // Evaluate app call - _, err = Eval(ops.Program, epList[1]) - require.Error(t, err) - require.Contains(t, err.Error(), "can't use Scratch txn field on non-app call txn with index 0") - }) + // Evaluate app call + _, err = Eval(ops.Program, epList[1]) + require.Error(t, err) + require.Contains(t, err.Error(), failCase.errContains) + }) + } } func TestTxna(t *testing.T) { From 3f810a2a2ce777f68885f122748fb31605a32152 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 20 May 2021 10:27:14 -0400 Subject: [PATCH 12/35] Use `testProg` instead of `AssembleString` --- data/transactions/logic/eval_test.go | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 3c1a7ce1d8..9629024bb8 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1880,8 +1880,7 @@ int 1`, // Assemble ops opsList := make([]*OpStream, len(sources)) for j, source := range sources { - ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) + ops := testProg(t, source, AssemblerMaxVersion) opsList[j] = ops } @@ -1969,10 +1968,7 @@ int 1`, for j, failCase := range failCases { t.Run(fmt.Sprintf("j=%d", j), func(t *testing.T) { source := "gtxna 0 Scratch 0" - - // Assemble ops - ops, err := AssembleStringWithVersion(source, AssemblerMaxVersion) - require.NoError(t, err) + ops := testProg(t, source, AssemblerMaxVersion) // Initialize txgroup and cxgroup txgroup := make([]transactions.SignedTxn, 2) @@ -1991,7 +1987,7 @@ int 1`, } // Evaluate app call - _, err = Eval(ops.Program, epList[1]) + _, err := Eval(ops.Program, epList[1]) require.Error(t, err) require.Contains(t, err.Error(), failCase.errContains) }) From 0ec9658aa3842a0b85c7170c238bb92b501152ac Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Fri, 21 May 2021 09:54:13 -0400 Subject: [PATCH 13/35] Remove unnecessary `SideEffects` property to better contain logic --- data/transactions/logic/eval.go | 6 ++---- data/transactions/logic/eval_test.go | 20 +++++++++----------- ledger/eval.go | 27 ++++++++++----------------- ledger/eval_test.go | 27 +-------------------------- 4 files changed, 22 insertions(+), 58 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index d59315e58e..73a3180748 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -185,9 +185,7 @@ type EvalParams struct { // GroupIndex should point to Txn within TxnGroup GroupIndex int - SideEffects *EvalSideEffects - - PastSideEffects []EvalSideEffects + PastSideEffects []*EvalSideEffects Logger logging.Logger @@ -463,7 +461,7 @@ func eval(program []byte, cx *evalContext) (pass bool, err error) { } // set side effects - cx.SideEffects.SetScratchSpace(cx.scratch) + cx.PastSideEffects[cx.GroupIndex].SetScratchSpace(cx.scratch) return cx.stack[0].Uint != 0, nil } diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 9629024bb8..9c235e3655 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1895,14 +1895,18 @@ int 1`, } // Construct EvalParams + pastSideEffects := make([]*EvalSideEffects, len(sources)) + for j := range pastSideEffects { + pastSideEffects[j] = new(EvalSideEffects) + } epList := make([]EvalParams, len(sources)) for j := range sources { epList[j] = EvalParams{ - Proto: &proto, - Txn: &txgroup[j], - TxnGroup: txgroup, - GroupIndex: j, - SideEffects: &EvalSideEffects{}, + Proto: &proto, + Txn: &txgroup[j], + TxnGroup: txgroup, + GroupIndex: j, + PastSideEffects: pastSideEffects, } } @@ -1910,12 +1914,6 @@ int 1`, shouldErr := testCase.errContains != "" didPass := true for j, ops := range opsList { - if j != 0 { - accumulatedSideEffects := epList[j-1].PastSideEffects - lastSideEffects := *epList[j-1].SideEffects - epList[j].PastSideEffects = append(accumulatedSideEffects, lastSideEffects) - } - pass, err := Eval(ops.Program, epList[j]) // Confirm it errors or that the error message is the expected one diff --git a/ledger/eval.go b/ledger/eval.go index d5092b735b..c4dc36479e 100644 --- a/ledger/eval.go +++ b/ledger/eval.go @@ -668,6 +668,7 @@ func (eval *BlockEvaluator) TransactionGroup(txads []transactions.SignedTxnWithA // transaction in the group func (eval *BlockEvaluator) prepareEvalParams(txgroup []transactions.SignedTxnWithAD) (res []*logic.EvalParams) { var groupNoAD []transactions.SignedTxn + var pastSideEffects []*logic.EvalSideEffects var minTealVersion uint64 res = make([]*logic.EvalParams, len(txgroup)) for i, txn := range txgroup { @@ -676,36 +677,29 @@ func (eval *BlockEvaluator) prepareEvalParams(txgroup []transactions.SignedTxnWi continue } - // Initialize group without ApplyData lazily + // Initialize side effects and group without ApplyData lazily if groupNoAD == nil { groupNoAD = make([]transactions.SignedTxn, len(txgroup)) + pastSideEffects = make([]*logic.EvalSideEffects, len(txgroup)) for j := range txgroup { groupNoAD[j] = txgroup[j].SignedTxn + pastSideEffects[j] = new(logic.EvalSideEffects) } minTealVersion = logic.ComputeMinTealVersion(groupNoAD) } res[i] = &logic.EvalParams{ - Txn: &groupNoAD[i], - Proto: &eval.proto, - TxnGroup: groupNoAD, - GroupIndex: i, - SideEffects: &logic.EvalSideEffects{}, - MinTealVersion: &minTealVersion, + Txn: &groupNoAD[i], + Proto: &eval.proto, + TxnGroup: groupNoAD, + GroupIndex: i, + PastSideEffects: pastSideEffects, + MinTealVersion: &minTealVersion, } } return } -// appendSideEffects appends the last txn's side effects to the list of past side effects -func (eval *BlockEvaluator) accumulatePastSideEffects(gi int, evalParams []*logic.EvalParams) { - if gi != 0 { - accumulatedSideEffects := evalParams[gi-1].PastSideEffects - lastSideEffects := *evalParams[gi-1].SideEffects - evalParams[gi].PastSideEffects = append(accumulatedSideEffects, lastSideEffects) - } -} - // transactionGroup tentatively executes a group of transactions as part of this block evaluation. // If the transaction group cannot be added to the block without violating some constraints, // an error is returned and the block evaluator state is unchanged. @@ -733,7 +727,6 @@ func (eval *BlockEvaluator) transactionGroup(txgroup []transactions.SignedTxnWit for gi, txad := range txgroup { var txib transactions.SignedTxnInBlock - eval.accumulatePastSideEffects(gi, evalParams) err := eval.transaction(txad.SignedTxn, evalParams[gi], txad.ApplyData, cow, &txib) if err != nil { return err diff --git a/ledger/eval_test.go b/ledger/eval_test.go index ce137a2a73..2ea3a39392 100644 --- a/ledger/eval_test.go +++ b/ledger/eval_test.go @@ -408,7 +408,7 @@ func TestPrepareEvalParams(t *testing.T) { for j, present := range testCase.expected { if present { require.NotNil(t, res[j]) - require.NotNil(t, res[j].SideEffects) + require.NotNil(t, res[j].PastSideEffects) require.Equal(t, res[j].GroupIndex, j) require.Equal(t, res[j].TxnGroup, expGroupNoAD) require.Equal(t, *res[j].Proto, eval.proto) @@ -421,31 +421,6 @@ func TestPrepareEvalParams(t *testing.T) { } } -func TestAccumulatePastSideEffects(t *testing.T) { - eval := BlockEvaluator{} - - evalParams := make([]*logic.EvalParams, 2) - for i := range evalParams { - evalParams[i] = &logic.EvalParams{ - SideEffects: &logic.EvalSideEffects{}, - } - } - - var scratchSpace [256]logic.StackValue - scratchSpace[0] = logic.StackValue{Uint: 1} - - eval.accumulatePastSideEffects(0, evalParams) - evalParams[0].SideEffects.SetScratchSpace(scratchSpace) - require.Empty(t, evalParams[1].PastSideEffects) - eval.accumulatePastSideEffects(1, evalParams) - require.NotEmpty(t, evalParams[1].PastSideEffects) - require.Equal( - t, - uint64(1), - evalParams[1].PastSideEffects[0].GetScratchValue(0).Uint, - ) -} - func testLedgerCleanup(l *Ledger, dbName string, inMem bool) { l.Close() if !inMem { From 76da67e36b3d55e6690bba76c3f5dc12511eb3ed Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Fri, 21 May 2021 10:21:05 -0400 Subject: [PATCH 14/35] Address review feedback * Use `runMode` to check stateful/logicsig * Simplify code where possible --- data/transactions/logic/eval.go | 13 +++---------- data/transactions/logic/eval_test.go | 22 +++++++++++++--------- 2 files changed, 16 insertions(+), 19 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 73a3180748..d61f8af23f 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1582,7 +1582,7 @@ func (cx *evalContext) txnFieldToStack(txn *transactions.Transaction, field TxnF if txn.Type != protocol.ApplicationCallTx { err = fmt.Errorf("can't use Scratch txn field on non-app call txn with index %d", groupIndex) return - } else if cx.Txn.Lsig.Logic != nil { + } else if cx.runModeFlags == runModeSignature { err = fmt.Errorf("can't use Scratch txn field from within a LogicSig") return } else if arrayFieldIdx >= 256 { @@ -1595,12 +1595,7 @@ func (cx *evalContext) txnFieldToStack(txn *transactions.Transaction, field TxnF err = fmt.Errorf("can't get future Scratch from txn with index %d", groupIndex) return } - val := cx.PastSideEffects[groupIndex].GetScratchValue(uint8(arrayFieldIdx)) - if val.Bytes != nil { - sv.Bytes = val.Bytes - } else { - sv.Uint = val.Uint - } + sv = cx.PastSideEffects[groupIndex].GetScratchValue(uint8(arrayFieldIdx)) default: err = fmt.Errorf("invalid txn field %d", field) return @@ -1626,9 +1621,7 @@ func opTxn(cx *evalContext) { cx.err = fmt.Errorf("invalid txn field %d", field) return } - var sv StackValue - var err error - sv, err = cx.txnFieldToStack(&cx.Txn.Txn, field, 0, cx.GroupIndex) + sv, err := cx.txnFieldToStack(&cx.Txn.Txn, field, 0, cx.GroupIndex) if err != nil { cx.err = err return diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 9c235e3655..45aae3630f 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1914,7 +1914,7 @@ int 1`, shouldErr := testCase.errContains != "" didPass := true for j, ops := range opsList { - pass, err := Eval(ops.Program, epList[j]) + pass, err := EvalStateful(ops.Program, epList[j]) // Confirm it errors or that the error message is the expected one if !shouldErr { @@ -1935,7 +1935,7 @@ int 1`, type failureCase struct { firstTxn transactions.SignedTxn - secondTxn transactions.SignedTxn + runMode runMode errContains string } @@ -1945,6 +1945,7 @@ int 1`, Type: protocol.PaymentTx, }, }, + runMode: runModeApplication, errContains: "can't use Scratch txn field on non-app call txn with index 0", } @@ -1954,11 +1955,7 @@ int 1`, Type: protocol.ApplicationCallTx, }, }, - secondTxn: transactions.SignedTxn{ - Lsig: transactions.LogicSig{ - Logic: []byte{1}, - }, - }, + runMode: runModeSignature, errContains: "can't use Scratch txn field from within a LogicSig", } @@ -1971,7 +1968,7 @@ int 1`, // Initialize txgroup and cxgroup txgroup := make([]transactions.SignedTxn, 2) txgroup[0] = failCase.firstTxn - txgroup[1] = failCase.secondTxn + txgroup[1] = transactions.SignedTxn{} // Construct EvalParams epList := make([]EvalParams, 2) @@ -1985,7 +1982,14 @@ int 1`, } // Evaluate app call - _, err := Eval(ops.Program, epList[1]) + var err error + switch failCase.runMode { + case runModeApplication: + _, err = EvalStateful(ops.Program, epList[1]) + default: + _, err = Eval(ops.Program, epList[1]) + } + require.Error(t, err) require.Contains(t, err.Error(), failCase.errContains) }) From 64442f2998220a7b43ccd5b7dacc33daf47421eb Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Fri, 21 May 2021 10:29:48 -0400 Subject: [PATCH 15/35] Use `typecheck` for consistent type checking --- data/transactions/logic/eval.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index d61f8af23f..efe0f7497a 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1365,7 +1365,7 @@ func (cx *evalContext) assetHoldingEnumToValue(holding *basics.AssetHolding, fie assetHoldingField := AssetHoldingField(field) assetHoldingFieldType := AssetHoldingFieldTypes[assetHoldingField] - if assetHoldingFieldType != sv.argType() { + if !typecheck(assetHoldingFieldType, sv.argType()) { err = fmt.Errorf("%s expected field type is %s but got %s", assetHoldingField.String(), assetHoldingFieldType.String(), sv.argType().String()) } return @@ -1402,7 +1402,7 @@ func (cx *evalContext) assetParamsEnumToValue(params *basics.AssetParams, field assetParamsField := AssetParamsField(field) assetParamsFieldType := AssetParamsFieldTypes[assetParamsField] - if assetParamsFieldType != sv.argType() { + if !typecheck(assetParamsFieldType, sv.argType()) { err = fmt.Errorf("%s expected field type is %s but got %s", assetParamsField.String(), assetParamsFieldType.String(), sv.argType().String()) } return @@ -1603,7 +1603,7 @@ func (cx *evalContext) txnFieldToStack(txn *transactions.Transaction, field TxnF txnField := TxnField(field) txnFieldType := TxnFieldTypes[txnField] - if txnFieldType != sv.argType() && txnFieldType != StackAny { + if !typecheck(txnFieldType, sv.argType()) { err = fmt.Errorf("%s expected field type is %s but got %s", txnField.String(), txnFieldType.String(), sv.argType().String()) } return @@ -1865,7 +1865,7 @@ func opGlobal(cx *evalContext) { } globalFieldType := GlobalFieldTypes[globalField] - if globalFieldType != sv.argType() { + if !typecheck(globalFieldType, sv.argType()) { cx.err = fmt.Errorf("%s expected field type is %s but got %s", globalField.String(), globalFieldType.String(), sv.argType().String()) return } From 0eb199882dd265891e06380e4c30ccd1caa1bbb4 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Fri, 21 May 2021 11:56:40 -0400 Subject: [PATCH 16/35] Revert exposing `stackValue` --- data/transactions/logic/debugger.go | 8 +-- data/transactions/logic/eval.go | 74 ++++++++++++++-------------- data/transactions/logic/eval_test.go | 4 +- 3 files changed, 43 insertions(+), 43 deletions(-) diff --git a/data/transactions/logic/debugger.go b/data/transactions/logic/debugger.go index 6405c1726a..021a53faf1 100644 --- a/data/transactions/logic/debugger.go +++ b/data/transactions/logic/debugger.go @@ -107,7 +107,7 @@ func makeDebugState(cx *evalContext) DebugState { for fieldIdx := range GlobalFieldNames { sv, err := cx.globalFieldToStack(GlobalField(fieldIdx)) if err != nil { - sv = StackValue{Bytes: []byte(err.Error())} + sv = stackValue{Bytes: []byte(err.Error())} } globals[fieldIdx] = stackValueToTealValue(&sv) } @@ -117,7 +117,7 @@ func makeDebugState(cx *evalContext) DebugState { if (cx.runModeFlags & runModeApplication) != 0 { ds.EvalDelta, err = cx.Ledger.GetDelta(&cx.Txn.Txn) if err != nil { - sv := StackValue{Bytes: []byte(err.Error())} + sv := stackValue{Bytes: []byte(err.Error())} tv := stackValueToTealValue(&sv) vd := tv.ToValueDelta() ds.EvalDelta.GlobalDelta = basics.StateDelta{"error": vd} @@ -176,7 +176,7 @@ func (d *DebugState) PCToLine(pc int) int { return len(strings.Split(d.Disassembly[:offset], "\n")) - one } -func stackValueToTealValue(sv *StackValue) basics.TealValue { +func stackValueToTealValue(sv *stackValue) basics.TealValue { tv := sv.toTealValue() return basics.TealValue{ Type: tv.Type, @@ -221,7 +221,7 @@ func (cx *evalContext) refreshDebugState() *DebugState { var err error ds.EvalDelta, err = cx.Ledger.GetDelta(&cx.Txn.Txn) if err != nil { - sv := StackValue{Bytes: []byte(err.Error())} + sv := stackValue{Bytes: []byte(err.Error())} tv := stackValueToTealValue(&sv) vd := tv.ToValueDelta() ds.EvalDelta.GlobalDelta = basics.StateDelta{"error": vd} diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index efe0f7497a..1b01eba026 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -49,47 +49,47 @@ const EvalMaxScratchSize = 255 // MaxStringSize is the limit of byte strings created by `concat` const MaxStringSize = 4096 -// StackValue is the type for the operand stack. -// Each StackValue is either a valid []byte value or a uint64 value. -// If (.Bytes != nil) the StackValue is a []byte value, otherwise uint64 value. -type StackValue struct { +// stackValue is the type for the operand stack. +// Each stackValue is either a valid []byte value or a uint64 value. +// If (.Bytes != nil) the stackValue is a []byte value, otherwise uint64 value. +type stackValue struct { Uint uint64 Bytes []byte } -func (sv *StackValue) argType() StackType { +func (sv *stackValue) argType() StackType { if sv.Bytes != nil { return StackBytes } return StackUint64 } -func (sv *StackValue) typeName() string { +func (sv *stackValue) typeName() string { if sv.Bytes != nil { return "[]byte" } return "uint64" } -func (sv *StackValue) clone() StackValue { +func (sv *stackValue) clone() stackValue { if sv.Bytes != nil { // clone stack value if Bytes bytesClone := make([]byte, len(sv.Bytes)) copy(bytesClone, sv.Bytes) - return StackValue{Bytes: bytesClone} + return stackValue{Bytes: bytesClone} } // otherwise no cloning is needed if Uint - return StackValue{Uint: sv.Uint} + return stackValue{Uint: sv.Uint} } -func (sv *StackValue) String() string { +func (sv *stackValue) String() string { if sv.Bytes != nil { return hex.EncodeToString(sv.Bytes) } return fmt.Sprintf("%d 0x%x", sv.Uint, sv.Uint) } -func stackValueFromTealValue(tv *basics.TealValue) (sv StackValue, err error) { +func stackValueFromTealValue(tv *basics.TealValue) (sv stackValue, err error) { switch tv.Type { case basics.TealBytesType: sv.Bytes = []byte(tv.Bytes) @@ -124,7 +124,7 @@ func ComputeMinTealVersion(group []transactions.SignedTxn) uint64 { return minVersion } -func (sv *StackValue) toTealValue() (tv basics.TealValue) { +func (sv *stackValue) toTealValue() (tv basics.TealValue) { if sv.argType() == StackBytes { return basics.TealValue{Type: basics.TealBytesType, Bytes: string(sv.Bytes)} } @@ -162,7 +162,7 @@ type EvalSideEffects struct { // GetScratchValue loads and clones a stackValue // The value is cloned so the original bytes are protected from changes -func (se *EvalSideEffects) GetScratchValue(stackPos uint8) StackValue { +func (se *EvalSideEffects) GetScratchValue(stackPos uint8) stackValue { return se.scratchSpace[stackPos].clone() } @@ -250,12 +250,12 @@ func (ep EvalParams) log() logging.Logger { return logging.Base() } -type scratchSpace = [256]StackValue +type scratchSpace = [256]stackValue type evalContext struct { EvalParams - stack []StackValue + stack []stackValue callstack []int program []byte // txn.Lsig.Logic ? pc int @@ -421,7 +421,7 @@ func eval(program []byte, cx *evalContext) (pass bool, err error) { cx.version = version cx.pc = vlen - cx.stack = make([]StackValue, 0, 10) + cx.stack = make([]stackValue, 0, 10) cx.program = program if cx.Debugger != nil { @@ -1129,7 +1129,7 @@ func opIntConstN(cx *evalContext, n uint) { cx.err = fmt.Errorf("intc [%d] beyond %d constants", n, len(cx.intc)) return } - cx.stack = append(cx.stack, StackValue{Uint: cx.intc[n]}) + cx.stack = append(cx.stack, stackValue{Uint: cx.intc[n]}) } func opIntConstLoad(cx *evalContext) { n := uint(cx.program[cx.pc+1]) @@ -1154,7 +1154,7 @@ func opPushInt(cx *evalContext) { cx.err = fmt.Errorf("could not decode int at pc=%d", cx.pc+1) return } - sv := StackValue{Uint: val} + sv := stackValue{Uint: val} cx.stack = append(cx.stack, sv) cx.nextpc = cx.pc + 1 + bytesUsed } @@ -1168,7 +1168,7 @@ func opByteConstN(cx *evalContext, n uint) { cx.err = fmt.Errorf("bytec [%d] beyond %d constants", n, len(cx.bytec)) return } - cx.stack = append(cx.stack, StackValue{Bytes: cx.bytec[n]}) + cx.stack = append(cx.stack, stackValue{Bytes: cx.bytec[n]}) } func opByteConstLoad(cx *evalContext) { n := uint(cx.program[cx.pc+1]) @@ -1200,7 +1200,7 @@ func opPushBytes(cx *evalContext) { cx.err = fmt.Errorf("pushbytes too long at pc=%d", pos) return } - sv := StackValue{Bytes: cx.program[pos:end]} + sv := stackValue{Bytes: cx.program[pos:end]} cx.stack = append(cx.stack, sv) cx.nextpc = int(end) } @@ -1211,7 +1211,7 @@ func opArgN(cx *evalContext, n uint64) { return } val := nilToEmpty(cx.Txn.Lsig.Args[n]) - cx.stack = append(cx.stack, StackValue{Bytes: val}) + cx.stack = append(cx.stack, stackValue{Bytes: val}) } func opArg(cx *evalContext) { @@ -1352,7 +1352,7 @@ func opDig(cx *evalContext) { cx.stack = append(cx.stack, sv) } -func (cx *evalContext) assetHoldingEnumToValue(holding *basics.AssetHolding, field uint64) (sv StackValue, err error) { +func (cx *evalContext) assetHoldingEnumToValue(holding *basics.AssetHolding, field uint64) (sv stackValue, err error) { switch AssetHoldingField(field) { case AssetBalance: sv.Uint = holding.Amount @@ -1371,7 +1371,7 @@ func (cx *evalContext) assetHoldingEnumToValue(holding *basics.AssetHolding, fie return } -func (cx *evalContext) assetParamsEnumToValue(params *basics.AssetParams, field uint64) (sv StackValue, err error) { +func (cx *evalContext) assetParamsEnumToValue(params *basics.AssetParams, field uint64) (sv stackValue, err error) { switch AssetParamsField(field) { case AssetTotal: sv.Uint = params.Total @@ -1431,7 +1431,7 @@ func (cx *evalContext) getTxID(txn *transactions.Transaction, groupIndex int) tr return txid } -func (cx *evalContext) txnFieldToStack(txn *transactions.Transaction, field TxnField, arrayFieldIdx uint64, groupIndex int) (sv StackValue, err error) { +func (cx *evalContext) txnFieldToStack(txn *transactions.Transaction, field TxnField, arrayFieldIdx uint64, groupIndex int) (sv stackValue, err error) { err = nil switch field { case Sender: @@ -1641,7 +1641,7 @@ func opTxna(cx *evalContext) { cx.err = fmt.Errorf("txna unsupported field %d", field) return } - var sv StackValue + var sv stackValue var err error arrayFieldIdx := uint64(cx.program[cx.pc+2]) sv, err = cx.txnFieldToStack(&cx.Txn.Txn, field, arrayFieldIdx, cx.GroupIndex) @@ -1670,7 +1670,7 @@ func opGtxn(cx *evalContext) { cx.err = fmt.Errorf("invalid txn field %d", field) return } - var sv StackValue + var sv stackValue var err error if TxnField(field) == GroupIndex { // GroupIndex; asking this when we just specified it is _dumb_, but oh well @@ -1703,7 +1703,7 @@ func opGtxna(cx *evalContext) { cx.err = fmt.Errorf("gtxna unsupported field %d", field) return } - var sv StackValue + var sv stackValue var err error arrayFieldIdx := uint64(cx.program[cx.pc+3]) sv, err = cx.txnFieldToStack(tx, field, arrayFieldIdx, gtxid) @@ -1733,7 +1733,7 @@ func opGtxns(cx *evalContext) { cx.err = fmt.Errorf("invalid txn field %d", field) return } - var sv StackValue + var sv stackValue var err error if TxnField(field) == GroupIndex { // GroupIndex; asking this when we just specified it is _dumb_, but oh well @@ -1767,7 +1767,7 @@ func opGtxnsa(cx *evalContext) { cx.err = fmt.Errorf("gtxnsa unsupported field %d", field) return } - var sv StackValue + var sv stackValue var err error arrayFieldIdx := uint64(cx.program[cx.pc+2]) sv, err = cx.txnFieldToStack(tx, field, arrayFieldIdx, gtxid) @@ -1817,7 +1817,7 @@ func (cx *evalContext) getCreatorAddress() ([]byte, error) { var zeroAddress basics.Address -func (cx *evalContext) globalFieldToStack(field GlobalField) (sv StackValue, err error) { +func (cx *evalContext) globalFieldToStack(field GlobalField) (sv stackValue, err error) { switch field { case MinTxnFee: sv.Uint = cx.Proto.MinTxnFee @@ -2278,7 +2278,7 @@ func opAppGetLocalStateEx(cx *evalContext) { return } - var isOk StackValue + var isOk stackValue if ok { isOk.Uint = 1 } @@ -2288,7 +2288,7 @@ func opAppGetLocalStateEx(cx *evalContext) { cx.stack = cx.stack[:last] } -func opAppGetLocalStateImpl(cx *evalContext, appID uint64, key []byte, accountIdx uint64) (result StackValue, ok bool, err error) { +func opAppGetLocalStateImpl(cx *evalContext, appID uint64, key []byte, accountIdx uint64) (result stackValue, ok bool, err error) { if cx.Ledger == nil { err = fmt.Errorf("ledger not available") return @@ -2306,7 +2306,7 @@ func opAppGetLocalStateImpl(cx *evalContext, appID uint64, key []byte, accountId return } -func opAppGetGlobalStateImpl(cx *evalContext, appIndex uint64, key []byte) (result StackValue, ok bool, err error) { +func opAppGetGlobalStateImpl(cx *evalContext, appIndex uint64, key []byte) (result stackValue, ok bool, err error) { if cx.Ledger == nil { err = fmt.Errorf("ledger not available") return @@ -2351,7 +2351,7 @@ func opAppGetGlobalStateEx(cx *evalContext) { return } - var isOk StackValue + var isOk stackValue if ok { isOk.Uint = 1 } @@ -2463,7 +2463,7 @@ func opAssetHoldingGet(cx *evalContext) { } var exist uint64 = 0 - var value StackValue + var value stackValue if holding, err := cx.Ledger.AssetHolding(addr, basics.AssetIndex(assetID)); err == nil { // the holding exist, read the value exist = 1 @@ -2496,7 +2496,7 @@ func opAssetParamsGet(cx *evalContext) { assetID := cx.Txn.Txn.ForeignAssets[foreignAssetsIndex] var exist uint64 = 0 - var value StackValue + var value stackValue if params, err := cx.Ledger.AssetParams(basics.AssetIndex(assetID)); err == nil { // params exist, read the value exist = 1 @@ -2508,5 +2508,5 @@ func opAssetParamsGet(cx *evalContext) { } cx.stack[last] = value - cx.stack = append(cx.stack, StackValue{Uint: exist}) + cx.stack = append(cx.stack, stackValue{Uint: exist}) } diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 45aae3630f..7c48620e15 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -3656,7 +3656,7 @@ intc_0 spec.op = func(cx *evalContext) { // overflow - cx.stack = make([]StackValue, 2000) + cx.stack = make([]stackValue, 2000) } opsByOpcode[LogicVersion][spec.Opcode] = spec _, err = Eval(ops.Program, ep) @@ -3763,7 +3763,7 @@ byte 0x // empty byte constant func TestArgType(t *testing.T) { t.Parallel() - var sv StackValue + var sv stackValue require.Equal(t, StackUint64, sv.argType()) sv.Bytes = []byte("") require.Equal(t, StackBytes, sv.argType()) From 57c9c06fd536b14a7bb1138223410fa41e297b88 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Fri, 21 May 2021 14:49:04 -0400 Subject: [PATCH 17/35] `stackPos` typo --- data/transactions/logic/eval.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 1b01eba026..25f43801b9 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -162,8 +162,8 @@ type EvalSideEffects struct { // GetScratchValue loads and clones a stackValue // The value is cloned so the original bytes are protected from changes -func (se *EvalSideEffects) GetScratchValue(stackPos uint8) stackValue { - return se.scratchSpace[stackPos].clone() +func (se *EvalSideEffects) GetScratchValue(scratchPos uint8) stackValue { + return se.scratchSpace[scratchPos].clone() } // SetScratchSpace stores the scratch space From 6780633d64213b16e7e1c48a26257b8a793ded2b Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Fri, 21 May 2021 14:50:20 -0400 Subject: [PATCH 18/35] Remove unnecessary variable declarations from `opTxn` and friends --- data/transactions/logic/eval.go | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 25f43801b9..e0e27c208c 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1641,10 +1641,8 @@ func opTxna(cx *evalContext) { cx.err = fmt.Errorf("txna unsupported field %d", field) return } - var sv stackValue - var err error arrayFieldIdx := uint64(cx.program[cx.pc+2]) - sv, err = cx.txnFieldToStack(&cx.Txn.Txn, field, arrayFieldIdx, cx.GroupIndex) + sv, err := cx.txnFieldToStack(&cx.Txn.Txn, field, arrayFieldIdx, cx.GroupIndex) if err != nil { cx.err = err return @@ -1703,10 +1701,8 @@ func opGtxna(cx *evalContext) { cx.err = fmt.Errorf("gtxna unsupported field %d", field) return } - var sv stackValue - var err error arrayFieldIdx := uint64(cx.program[cx.pc+3]) - sv, err = cx.txnFieldToStack(tx, field, arrayFieldIdx, gtxid) + sv, err := cx.txnFieldToStack(tx, field, arrayFieldIdx, gtxid) if err != nil { cx.err = err return @@ -1767,10 +1763,8 @@ func opGtxnsa(cx *evalContext) { cx.err = fmt.Errorf("gtxnsa unsupported field %d", field) return } - var sv stackValue - var err error arrayFieldIdx := uint64(cx.program[cx.pc+2]) - sv, err = cx.txnFieldToStack(tx, field, arrayFieldIdx, gtxid) + sv, err := cx.txnFieldToStack(tx, field, arrayFieldIdx, gtxid) if err != nil { cx.err = err return From 98eb20fbf6055ab47df227a6faf6d08789b4add2 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Mon, 24 May 2021 14:31:49 -0400 Subject: [PATCH 19/35] Remove `Scratch` field from transaction field specs --- data/transactions/logic/doc.go | 1 - data/transactions/logic/eval.go | 18 ------------------ data/transactions/logic/fields.go | 6 ------ data/transactions/logic/fields_string.go | 7 +++---- 4 files changed, 3 insertions(+), 29 deletions(-) diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index d7b0dc9872..a64f92511a 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -305,7 +305,6 @@ var txnFieldDocs = map[string]string{ "GlobalNumByteSlice": "Number of global state byteslices in ApplicationCall", "LocalNumUint": "Number of local state integers in ApplicationCall", "LocalNumByteSlice": "Number of local state byteslices in ApplicationCall", - "Scratch": "Scratch space of previous app call transaction", "ApprovalProgram": "Approval program", "ClearStateProgram": "Clear state program", "RekeyTo": "32 byte Sender's new AuthAddr", diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index e0e27c208c..b205c782cd 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1578,24 +1578,6 @@ func (cx *evalContext) txnFieldToStack(txn *transactions.Transaction, field TxnF sv.Bytes = txn.FreezeAccount[:] case FreezeAssetFrozen: sv.Uint = boolToUint(txn.AssetFrozen) - case Scratch: - if txn.Type != protocol.ApplicationCallTx { - err = fmt.Errorf("can't use Scratch txn field on non-app call txn with index %d", groupIndex) - return - } else if cx.runModeFlags == runModeSignature { - err = fmt.Errorf("can't use Scratch txn field from within a LogicSig") - return - } else if arrayFieldIdx >= 256 { - err = fmt.Errorf("invalid Scratch index %d", arrayFieldIdx) - return - } else if groupIndex == cx.GroupIndex { - err = fmt.Errorf("can't use Scratch txn field on self, use load instead") - return - } else if groupIndex > cx.GroupIndex { - err = fmt.Errorf("can't get future Scratch from txn with index %d", groupIndex) - return - } - sv = cx.PastSideEffects[groupIndex].GetScratchValue(uint8(arrayFieldIdx)) default: err = fmt.Errorf("invalid txn field %d", field) return diff --git a/data/transactions/logic/fields.go b/data/transactions/logic/fields.go index 2fc9b070b5..41aecb4b9e 100644 --- a/data/transactions/logic/fields.go +++ b/data/transactions/logic/fields.go @@ -143,9 +143,6 @@ const ( // LocalNumByteSlice uint64 LocalNumByteSlice - // Scratch [256]stackValue - Scratch - invalidTxnField // fence for some setup that loops from Sender..invalidTxnField ) @@ -231,7 +228,6 @@ var txnFieldSpecs = []txnFieldSpec{ {GlobalNumByteSlice, StackUint64, 3}, {LocalNumUint, StackUint64, 3}, {LocalNumByteSlice, StackUint64, 3}, - {Scratch, StackAny, 4}, } // TxnaFieldNames are arguments to the 'txna' opcode @@ -244,7 +240,6 @@ var TxnaFieldTypes = []StackType{ txnaFieldSpecByField[Accounts].ftype, txnaFieldSpecByField[Assets].ftype, txnaFieldSpecByField[Applications].ftype, - txnaFieldSpecByField[Scratch].ftype, } var txnaFieldSpecByField = map[TxnField]txnFieldSpec{ @@ -252,7 +247,6 @@ var txnaFieldSpecByField = map[TxnField]txnFieldSpec{ Accounts: {Accounts, StackBytes, 2}, Assets: {Assets, StackUint64, 3}, Applications: {Applications, StackUint64, 3}, - Scratch: {Scratch, StackAny, 4}, } // TxnTypeNames is the values of Txn.Type in enum order diff --git a/data/transactions/logic/fields_string.go b/data/transactions/logic/fields_string.go index f2f906fd96..c06df6944e 100644 --- a/data/transactions/logic/fields_string.go +++ b/data/transactions/logic/fields_string.go @@ -64,13 +64,12 @@ func _() { _ = x[GlobalNumByteSlice-53] _ = x[LocalNumUint-54] _ = x[LocalNumByteSlice-55] - _ = x[Scratch-56] - _ = x[invalidTxnField-57] + _ = x[invalidTxnField-56] } -const _TxnField_name = "SenderFeeFirstValidFirstValidTimeLastValidNoteLeaseReceiverAmountCloseRemainderToVotePKSelectionPKVoteFirstVoteLastVoteKeyDilutionTypeTypeEnumXferAssetAssetAmountAssetSenderAssetReceiverAssetCloseToGroupIndexTxIDApplicationIDOnCompletionApplicationArgsNumAppArgsAccountsNumAccountsApprovalProgramClearStateProgramRekeyToConfigAssetConfigAssetTotalConfigAssetDecimalsConfigAssetDefaultFrozenConfigAssetUnitNameConfigAssetNameConfigAssetURLConfigAssetMetadataHashConfigAssetManagerConfigAssetReserveConfigAssetFreezeConfigAssetClawbackFreezeAssetFreezeAssetAccountFreezeAssetFrozenAssetsNumAssetsApplicationsNumApplicationsGlobalNumUintGlobalNumByteSliceLocalNumUintLocalNumByteSliceScratchinvalidTxnField" +const _TxnField_name = "SenderFeeFirstValidFirstValidTimeLastValidNoteLeaseReceiverAmountCloseRemainderToVotePKSelectionPKVoteFirstVoteLastVoteKeyDilutionTypeTypeEnumXferAssetAssetAmountAssetSenderAssetReceiverAssetCloseToGroupIndexTxIDApplicationIDOnCompletionApplicationArgsNumAppArgsAccountsNumAccountsApprovalProgramClearStateProgramRekeyToConfigAssetConfigAssetTotalConfigAssetDecimalsConfigAssetDefaultFrozenConfigAssetUnitNameConfigAssetNameConfigAssetURLConfigAssetMetadataHashConfigAssetManagerConfigAssetReserveConfigAssetFreezeConfigAssetClawbackFreezeAssetFreezeAssetAccountFreezeAssetFrozenAssetsNumAssetsApplicationsNumApplicationsGlobalNumUintGlobalNumByteSliceLocalNumUintLocalNumByteSliceinvalidTxnField" -var _TxnField_index = [...]uint16{0, 6, 9, 19, 33, 42, 46, 51, 59, 65, 81, 87, 98, 107, 115, 130, 134, 142, 151, 162, 173, 186, 198, 208, 212, 225, 237, 252, 262, 270, 281, 296, 313, 320, 331, 347, 366, 390, 409, 424, 438, 461, 479, 497, 514, 533, 544, 562, 579, 585, 594, 606, 621, 634, 652, 664, 681, 688, 703} +var _TxnField_index = [...]uint16{0, 6, 9, 19, 33, 42, 46, 51, 59, 65, 81, 87, 98, 107, 115, 130, 134, 142, 151, 162, 173, 186, 198, 208, 212, 225, 237, 252, 262, 270, 281, 296, 313, 320, 331, 347, 366, 390, 409, 424, 438, 461, 479, 497, 514, 533, 544, 562, 579, 585, 594, 606, 621, 634, 652, 664, 681, 696} func (i TxnField) String() string { if i < 0 || i >= TxnField(len(_TxnField_index)-1) { From f39ecdbacbf6ab8e4b063fa3a4ca878d13471186 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Mon, 24 May 2021 14:39:49 -0400 Subject: [PATCH 20/35] Convert from Scratch test to `gload` test --- data/transactions/logic/eval_test.go | 382 ++++++++++++++------------- 1 file changed, 192 insertions(+), 190 deletions(-) diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 7c48620e15..89371768da 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1806,196 +1806,6 @@ func testLogic(t *testing.T, program string, v uint64, ep EvalParams, problems . } } -func TestGtxnaScratch(t *testing.T) { - t.Parallel() - - type scratchTestCase struct { - tealSources []string - errContains string - } - - simpleCase := scratchTestCase{ - tealSources: []string{ - `int 2 -store 0 -int 1`, - `gtxna 0 Scratch 0 -int 2 -== -`, - }, - } - - multipleTxnCase := scratchTestCase{ - tealSources: []string{ - `byte "txn 1" -store 0 -int 1`, - `byte "txn 2" -store 1 -int 1`, - `gtxna 0 Scratch 0 -byte "txn 1" -== -gtxna 1 Scratch 1 -byte "txn 2" -== -&& -`, - }, - } - - selfCase := scratchTestCase{ - tealSources: []string{ - `gtxna 0 Scratch 0 -int 2 -store 0 -int 1 -`, - }, - errContains: "can't use Scratch txn field on self, use load instead", - } - - laterTxnSlotCase := scratchTestCase{ - tealSources: []string{ - `gtxna 1 Scratch 0 -int 2 -== -`, - `int 2 -store 0 -int 1`, - }, - errContains: "can't get future Scratch from txn with index 1", - } - - cases := []scratchTestCase{ - simpleCase, multipleTxnCase, selfCase, laterTxnSlotCase, - } - proto := defaultEvalProtoWithVersion(LogicVersion) - - for i, testCase := range cases { - t.Run(fmt.Sprintf("i=%d", i), func(t *testing.T) { - sources := testCase.tealSources - // Assemble ops - opsList := make([]*OpStream, len(sources)) - for j, source := range sources { - ops := testProg(t, source, AssemblerMaxVersion) - opsList[j] = ops - } - - // Initialize txgroup and cxgroup - txgroup := make([]transactions.SignedTxn, len(sources)) - for j := range txgroup { - txgroup[j] = transactions.SignedTxn{ - Txn: transactions.Transaction{ - Type: protocol.ApplicationCallTx, - }, - } - } - - // Construct EvalParams - pastSideEffects := make([]*EvalSideEffects, len(sources)) - for j := range pastSideEffects { - pastSideEffects[j] = new(EvalSideEffects) - } - epList := make([]EvalParams, len(sources)) - for j := range sources { - epList[j] = EvalParams{ - Proto: &proto, - Txn: &txgroup[j], - TxnGroup: txgroup, - GroupIndex: j, - PastSideEffects: pastSideEffects, - } - } - - // Evaluate app calls - shouldErr := testCase.errContains != "" - didPass := true - for j, ops := range opsList { - pass, err := EvalStateful(ops.Program, epList[j]) - - // Confirm it errors or that the error message is the expected one - if !shouldErr { - require.NoError(t, err) - } else if shouldErr && err != nil { - require.Error(t, err) - require.Contains(t, err.Error(), testCase.errContains) - } - - if !pass { - didPass = false - } - } - - require.Equal(t, !shouldErr, didPass) - }) - } - - type failureCase struct { - firstTxn transactions.SignedTxn - runMode runMode - errContains string - } - - nonAppCall := failureCase{ - firstTxn: transactions.SignedTxn{ - Txn: transactions.Transaction{ - Type: protocol.PaymentTx, - }, - }, - runMode: runModeApplication, - errContains: "can't use Scratch txn field on non-app call txn with index 0", - } - - logicSigCall := failureCase{ - firstTxn: transactions.SignedTxn{ - Txn: transactions.Transaction{ - Type: protocol.ApplicationCallTx, - }, - }, - runMode: runModeSignature, - errContains: "can't use Scratch txn field from within a LogicSig", - } - - failCases := []failureCase{nonAppCall, logicSigCall} - for j, failCase := range failCases { - t.Run(fmt.Sprintf("j=%d", j), func(t *testing.T) { - source := "gtxna 0 Scratch 0" - ops := testProg(t, source, AssemblerMaxVersion) - - // Initialize txgroup and cxgroup - txgroup := make([]transactions.SignedTxn, 2) - txgroup[0] = failCase.firstTxn - txgroup[1] = transactions.SignedTxn{} - - // Construct EvalParams - epList := make([]EvalParams, 2) - for j := range epList { - epList[j] = EvalParams{ - Proto: &proto, - Txn: &txgroup[j], - TxnGroup: txgroup, - GroupIndex: j, - } - } - - // Evaluate app call - var err error - switch failCase.runMode { - case runModeApplication: - _, err = EvalStateful(ops.Program, epList[1]) - default: - _, err = Eval(ops.Program, epList[1]) - } - - require.Error(t, err) - require.Contains(t, err.Error(), failCase.errContains) - }) - } -} - func TestTxna(t *testing.T) { t.Parallel() source := `txna Accounts 1 @@ -2417,6 +2227,198 @@ int 5 } } +func TestGload(t *testing.T) { + t.Parallel() + + // for simple app-call-only transaction groups + type scratchTestCase struct { + tealSources []string + errContains string + } + + simpleCase := scratchTestCase{ + tealSources: []string{ + `int 2 +store 0 +int 1`, + `gload 0 0 +int 2 +== +`, + }, + } + + multipleTxnCase := scratchTestCase{ + tealSources: []string{ + `byte "txn 1" +store 0 +int 1`, + `byte "txn 2" +store 1 +int 1`, + `gload 0 0 +byte "txn 1" +== +gload 1 1 +byte "txn 2" +== +&& +`, + }, + } + + selfCase := scratchTestCase{ + tealSources: []string{ + `gload 0 0 +int 2 +store 0 +int 1 +`, + }, + errContains: "can't use gload on self, use load instead", + } + + laterTxnSlotCase := scratchTestCase{ + tealSources: []string{ + `gload 1 0 +int 2 +== +`, + `int 2 +store 0 +int 1`, + }, + errContains: "gload can't get future scratch space from txn with index 1", + } + + cases := []scratchTestCase{ + simpleCase, multipleTxnCase, selfCase, laterTxnSlotCase, + } + proto := defaultEvalProtoWithVersion(LogicVersion) + + for i, testCase := range cases { + t.Run(fmt.Sprintf("i=%d", i), func(t *testing.T) { + sources := testCase.tealSources + // Assemble ops + opsList := make([]*OpStream, len(sources)) + for j, source := range sources { + ops := testProg(t, source, AssemblerMaxVersion) + opsList[j] = ops + } + + // Initialize txgroup and cxgroup + txgroup := make([]transactions.SignedTxn, len(sources)) + for j := range txgroup { + txgroup[j] = transactions.SignedTxn{ + Txn: transactions.Transaction{ + Type: protocol.ApplicationCallTx, + }, + } + } + + // Construct EvalParams + pastSideEffects := make([]*EvalSideEffects, len(sources)) + for j := range pastSideEffects { + pastSideEffects[j] = new(EvalSideEffects) + } + epList := make([]EvalParams, len(sources)) + for j := range sources { + epList[j] = EvalParams{ + Proto: &proto, + Txn: &txgroup[j], + TxnGroup: txgroup, + GroupIndex: j, + PastSideEffects: pastSideEffects, + } + } + + // Evaluate app calls + shouldErr := testCase.errContains != "" + didPass := true + for j, ops := range opsList { + pass, err := EvalStateful(ops.Program, epList[j]) + + // Confirm it errors or that the error message is the expected one + if !shouldErr { + require.NoError(t, err) + } else if shouldErr && err != nil { + require.Error(t, err) + require.Contains(t, err.Error(), testCase.errContains) + } + + if !pass { + didPass = false + } + } + + require.Equal(t, !shouldErr, didPass) + }) + } + + // for more complex group transaction cases + type failureCase struct { + firstTxn transactions.SignedTxn + runMode runMode + errContains string + } + + nonAppCall := failureCase{ + firstTxn: transactions.SignedTxn{ + Txn: transactions.Transaction{ + Type: protocol.PaymentTx, + }, + }, + runMode: runModeApplication, + errContains: "can't use gload on non-app call txn with index 0", + } + + logicSigCall := failureCase{ + firstTxn: transactions.SignedTxn{ + Txn: transactions.Transaction{ + Type: protocol.ApplicationCallTx, + }, + }, + runMode: runModeSignature, + errContains: "can't use gload from within a LogicSig", + } + + failCases := []failureCase{nonAppCall, logicSigCall} + for j, failCase := range failCases { + t.Run(fmt.Sprintf("j=%d", j), func(t *testing.T) { + source := "gload 0 0" + ops := testProg(t, source, AssemblerMaxVersion) + + // Initialize txgroup and cxgroup + txgroup := make([]transactions.SignedTxn, 2) + txgroup[0] = failCase.firstTxn + txgroup[1] = transactions.SignedTxn{} + + // Construct EvalParams + epList := make([]EvalParams, 2) + for j := range epList { + epList[j] = EvalParams{ + Proto: &proto, + Txn: &txgroup[j], + TxnGroup: txgroup, + GroupIndex: j, + } + } + + // Evaluate app call + var err error + switch failCase.runMode { + case runModeApplication: + _, err = EvalStateful(ops.Program, epList[1]) + default: + _, err = Eval(ops.Program, epList[1]) + } + + require.Error(t, err) + require.Contains(t, err.Error(), failCase.errContains) + }) + } +} + const testCompareProgramText = `int 35 int 16 > From 169886dc865a0142c453c3ad271e46dc048f0656 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Mon, 24 May 2021 16:31:05 -0400 Subject: [PATCH 21/35] `gload` opcode --- data/transactions/logic/README.md | 3 ++- data/transactions/logic/TEAL_opcodes.md | 8 +++++++ data/transactions/logic/doc.go | 4 +++- data/transactions/logic/eval.go | 28 +++++++++++++++++++++++++ data/transactions/logic/opcodes.go | 3 +++ 5 files changed, 44 insertions(+), 2 deletions(-) diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 96331db349..36afd6eb6c 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -27,7 +27,7 @@ A program can either authorize some delegated action on a normal private key sig * If the account has signed the program (an ed25519 signature on "Program" concatenated with the program bytes) then if the program returns true the transaction is authorized as if the account had signed it. This allows an account to hand out a signed program so that other users can carry out delegated actions which are approved by the program. * If the SHA512_256 hash of the program (prefixed by "Program") is equal to the transaction Sender address then this is a contract account wholly controlled by the program. No other signature is necessary or possible. The only way to execute a transaction against the contract account is for the program to approve it. -The TEAL bytecode plus the length of any Args must add up to less than 1000 bytes (consensus parameter LogicSigMaxSize). Each TEAL op has an associated cost and the program cost must total less than 20000 (consensus parameter LogicSigMaxCost). Most ops have a cost of 1, but a few slow crypto ops are much higher. Prior to v4, program costs was estimated as the static sum of all opcode costs in a program (ignoring conditionals that might skip some code). Beginning with v4, a program's cost is tracked dynamically, while being evaluated. If the program exceeds its budget, it fails. +The TEAL bytecode plus the length of any Args must add up to less than 1000 bytes (consensus parameter LogicSigMaxSize). Each TEAL op has an associated cost and the program cost must total less than 20000 (consensus parameter LogicSigMaxCost). Most ops have a cost of 1, but a few slow crypto ops are much higher. Prior to v4, program costs was estimated as the static sum of all opcode costs in a program (ignoring conditionals that might skip some code). Beginning with v4, a program's cost is tracked dynamically, while being evaluated. If the program exceeds its budget, it fails. ## Execution modes @@ -169,6 +169,7 @@ Some of these have immediate data in the byte or bytes after the opcode. | `global f` | push value from globals to stack | | `load i` | copy a value from scratch space to the stack | | `store i` | pop a value from the stack and store to scratch space | +| `gload t i` | push Ith scratch space index of the Tth transaction in the current group | **Transaction Fields** diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index 4768c53e66..b17cfefd4a 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -881,3 +881,11 @@ The call stack is separate from the data stack. Only `callsub` and `retsub` mani - LogicSigVersion >= 4 The call stack is separate from the data stack. Only `callsub` and `retsub` manipulate it.` + +## gload t i + +- Opcode: 0xb0 {uint8 transaction group index} {uint8 position in scratch space to store to} +- Pops: _None_ +- Pushes: any +- push Ith scratch space index of the Tth transaction in the current group +- LogicSigVersion >= 2 diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index a64f92511a..6af4ca4054 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -81,6 +81,7 @@ var opDocByName = map[string]string{ "global": "push value from globals to stack", "load": "copy a value from scratch space to the stack", "store": "pop a value from the stack and store to scratch space", + "gload": "push Ith scratch space index of the Tth transaction in the current group", "bnz": "branch to TARGET if value X is not zero", "bz": "branch to TARGET if value X is zero", "b": "branch unconditionally to TARGET", @@ -141,6 +142,7 @@ var opcodeImmediateNotes = map[string]string{ "b": "{int16 branch offset, big endian. (negative offsets are illegal before v4)}", "load": "{uint8 position in scratch space to load from}", "store": "{uint8 position in scratch space to store to}", + "gload": "{uint8 transaction group index} {uint8 position in scratch space to store to}", "substring": "{uint8 start position} {uint8 end position}", "dig": "{uint8 depth}", "asset_holding_get": "{uint8 asset holding field index}", @@ -201,7 +203,7 @@ type OpGroup struct { // OpGroupList is groupings of ops for documentation purposes. var OpGroupList = []OpGroup{ {"Arithmetic", []string{"sha256", "keccak256", "sha512_256", "ed25519verify", "+", "-", "/", "*", "<", ">", "<=", ">=", "&&", "||", "==", "!=", "!", "len", "itob", "btoi", "%", "|", "&", "^", "~", "mulw", "addw", "divw", "getbit", "setbit", "getbyte", "setbyte", "concat", "substring", "substring3"}}, - {"Loading Values", []string{"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "txn", "gtxn", "txna", "gtxna", "gtxns", "gtxnsa", "global", "load", "store"}}, + {"Loading Values", []string{"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "txn", "gtxn", "txna", "gtxna", "gtxns", "gtxnsa", "global", "load", "store", "gload"}}, {"Flow Control", []string{"err", "bnz", "bz", "b", "return", "pop", "dup", "dup2", "dig", "swap", "select", "assert", "callsub", "retsub"}}, {"State Access", []string{"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"}}, } diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index b205c782cd..439fe3f58b 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1911,6 +1911,34 @@ func opStore(cx *evalContext) { cx.stack = cx.stack[:last] } +func opGload(cx *evalContext) { + gtxid := int(uint(cx.program[cx.pc+1])) + if gtxid >= len(cx.TxnGroup) { + cx.err = fmt.Errorf("gload lookup TxnGroup[%d] but it only has %d", gtxid, len(cx.TxnGroup)) + return + } + txn := cx.TxnGroup[gtxid].Txn + gindex := int(uint(cx.program[cx.pc+2])) + if gindex >= 256 { + cx.err = fmt.Errorf("invalid Scratch index %d", gindex) + return + } else if txn.Type != protocol.ApplicationCallTx { + cx.err = fmt.Errorf("can't use gload on non-app call txn with index %d", gtxid) + return + } else if cx.runModeFlags == runModeSignature { + cx.err = fmt.Errorf("can't use gload from within a LogicSig") + return + } else if gtxid == cx.GroupIndex { + cx.err = fmt.Errorf("can't use gload on self, use load instead") + return + } else if gtxid > cx.GroupIndex { + cx.err = fmt.Errorf("gload can't get future scratch space from txn with index %d", gtxid) + return + } + scratchValue := cx.PastSideEffects[gtxid].GetScratchValue(uint8(gindex)) + cx.stack = append(cx.stack, scratchValue) +} + func opConcat(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index c9822a2026..55b95208bc 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -241,6 +241,9 @@ var OpSpecs = []OpSpec{ // shl, shr // divw, modw convenience // expmod + + // Group scratch space access + {0xb0, "gload", opGload, asmDefault, disDefault, nil, oneAny, 2, modeAny, immediates("t", "i")}, } type sortByOpcode []OpSpec From e7cefb49e29844f6fcb5d1d17ae77f8306816cc5 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Mon, 24 May 2021 16:58:48 -0400 Subject: [PATCH 22/35] Add `gloads` unit test --- data/transactions/logic/eval_test.go | 64 ++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 89371768da..09a38f9bae 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -2419,6 +2419,70 @@ int 1`, } } +func TestGloads(t *testing.T) { + t.Parallel() + + // Multiple app calls + source1 := `byte "txn 1" +store 0 +int 1` + source2 := `byte "txn 2" +store 1 +int 1` + source3 := `int 0 +gloads 0 +byte "txn 1" +== +int 1 +gloads 1 +byte "txn 2" +== +&&` + + sources := []string{source1, source2, source3} + proto := defaultEvalProtoWithVersion(LogicVersion) + + // Assemble ops + opsList := make([]*OpStream, len(sources)) + for j, source := range sources { + ops := testProg(t, source, AssemblerMaxVersion) + opsList[j] = ops + } + + // Initialize txgroup and cxgroup + txgroup := make([]transactions.SignedTxn, len(sources)) + for j := range txgroup { + txgroup[j] = transactions.SignedTxn{ + Txn: transactions.Transaction{ + Type: protocol.ApplicationCallTx, + }, + } + } + + // Construct EvalParams + pastSideEffects := make([]*EvalSideEffects, len(sources)) + for j := range pastSideEffects { + pastSideEffects[j] = new(EvalSideEffects) + } + epList := make([]EvalParams, len(sources)) + for j := range sources { + epList[j] = EvalParams{ + Proto: &proto, + Txn: &txgroup[j], + TxnGroup: txgroup, + GroupIndex: j, + PastSideEffects: pastSideEffects, + } + } + + // Evaluate app calls + for j, ops := range opsList { + pass, err := EvalStateful(ops.Program, epList[j]) + require.NoError(t, err) + require.True(t, pass) + } +} + const testCompareProgramText = `int 35 int 16 > From 47bdbf864406165f2398dfc0fc077f13bae095a7 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Mon, 24 May 2021 17:12:16 -0400 Subject: [PATCH 23/35] `gloads` opcode * Fix incorrect opcode details * Refactor `gload` implementation into shared method --- data/transactions/logic/README.md | 1 + data/transactions/logic/TEAL_opcodes.md | 26 ++++++++---- data/transactions/logic/doc.go | 6 ++- data/transactions/logic/eval.go | 56 +++++++++++++++++-------- data/transactions/logic/opcodes.go | 6 +-- 5 files changed, 65 insertions(+), 30 deletions(-) diff --git a/data/transactions/logic/README.md b/data/transactions/logic/README.md index 36afd6eb6c..f55f16360f 100644 --- a/data/transactions/logic/README.md +++ b/data/transactions/logic/README.md @@ -170,6 +170,7 @@ Some of these have immediate data in the byte or bytes after the opcode. | `load i` | copy a value from scratch space to the stack | | `store i` | pop a value from the stack and store to scratch space | | `gload t i` | push Ith scratch space index of the Tth transaction in the current group | +| `gloads i` | push Ith scratch space index of the Ath transaction in the current group | **Transaction Fields** diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index b17cfefd4a..8274b9ec24 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -521,6 +521,24 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g - push Ith value of the array field F from the Ath transaction in the current group - LogicSigVersion >= 3 +## gload t i + +- Opcode: 0x3a {uint8 transaction group index} {uint8 position in scratch space to load from} +- Pops: _None_ +- Pushes: any +- push Ith scratch space index of the Tth transaction in the current group +- LogicSigVersion >= 4 +- Mode: Application + +## gloads i + +- Opcode: 0x3b {uint8 position in scratch space to load from} +- Pops: *... stack*, uint64 +- Pushes: any +- push Ith scratch space index of the Ath transaction in the current group +- LogicSigVersion >= 4 +- Mode: Application + ## bnz target - Opcode: 0x40 {int16 branch offset, big endian. (negative offsets are illegal before v4)} @@ -881,11 +899,3 @@ The call stack is separate from the data stack. Only `callsub` and `retsub` mani - LogicSigVersion >= 4 The call stack is separate from the data stack. Only `callsub` and `retsub` manipulate it.` - -## gload t i - -- Opcode: 0xb0 {uint8 transaction group index} {uint8 position in scratch space to store to} -- Pops: _None_ -- Pushes: any -- push Ith scratch space index of the Tth transaction in the current group -- LogicSigVersion >= 2 diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index 6af4ca4054..e67ab3e6d0 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -82,6 +82,7 @@ var opDocByName = map[string]string{ "load": "copy a value from scratch space to the stack", "store": "pop a value from the stack and store to scratch space", "gload": "push Ith scratch space index of the Tth transaction in the current group", + "gloads": "push Ith scratch space index of the Ath transaction in the current group", "bnz": "branch to TARGET if value X is not zero", "bz": "branch to TARGET if value X is zero", "b": "branch unconditionally to TARGET", @@ -142,7 +143,8 @@ var opcodeImmediateNotes = map[string]string{ "b": "{int16 branch offset, big endian. (negative offsets are illegal before v4)}", "load": "{uint8 position in scratch space to load from}", "store": "{uint8 position in scratch space to store to}", - "gload": "{uint8 transaction group index} {uint8 position in scratch space to store to}", + "gload": "{uint8 transaction group index} {uint8 position in scratch space to load from}", + "gloads": "{uint8 position in scratch space to load from}", "substring": "{uint8 start position} {uint8 end position}", "dig": "{uint8 depth}", "asset_holding_get": "{uint8 asset holding field index}", @@ -203,7 +205,7 @@ type OpGroup struct { // OpGroupList is groupings of ops for documentation purposes. var OpGroupList = []OpGroup{ {"Arithmetic", []string{"sha256", "keccak256", "sha512_256", "ed25519verify", "+", "-", "/", "*", "<", ">", "<=", ">=", "&&", "||", "==", "!=", "!", "len", "itob", "btoi", "%", "|", "&", "^", "~", "mulw", "addw", "divw", "getbit", "setbit", "getbyte", "setbyte", "concat", "substring", "substring3"}}, - {"Loading Values", []string{"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "txn", "gtxn", "txna", "gtxna", "gtxns", "gtxnsa", "global", "load", "store", "gload"}}, + {"Loading Values", []string{"intcblock", "intc", "intc_0", "intc_1", "intc_2", "intc_3", "pushint", "bytecblock", "bytec", "bytec_0", "bytec_1", "bytec_2", "bytec_3", "pushbytes", "arg", "arg_0", "arg_1", "arg_2", "arg_3", "txn", "gtxn", "txna", "gtxna", "gtxns", "gtxnsa", "global", "load", "store", "gload", "gloads"}}, {"Flow Control", []string{"err", "bnz", "bz", "b", "return", "pop", "dup", "dup2", "dig", "swap", "select", "assert", "callsub", "retsub"}}, {"State Access", []string{"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"}}, } diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 439fe3f58b..22d498af3f 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1911,34 +1911,56 @@ func opStore(cx *evalContext) { cx.stack = cx.stack[:last] } -func opGload(cx *evalContext) { - gtxid := int(uint(cx.program[cx.pc+1])) - if gtxid >= len(cx.TxnGroup) { - cx.err = fmt.Errorf("gload lookup TxnGroup[%d] but it only has %d", gtxid, len(cx.TxnGroup)) +func opGloadImpl(cx *evalContext, groupIdx int, scratchIdx int) (err error, scratchValue stackValue) { + if groupIdx >= len(cx.TxnGroup) { + err = fmt.Errorf("gload lookup TxnGroup[%d] but it only has %d", groupIdx, len(cx.TxnGroup)) return - } - txn := cx.TxnGroup[gtxid].Txn - gindex := int(uint(cx.program[cx.pc+2])) - if gindex >= 256 { - cx.err = fmt.Errorf("invalid Scratch index %d", gindex) + } else if scratchIdx >= 256 { + err = fmt.Errorf("invalid Scratch index %d", scratchIdx) return - } else if txn.Type != protocol.ApplicationCallTx { - cx.err = fmt.Errorf("can't use gload on non-app call txn with index %d", gtxid) + } else if txn := cx.TxnGroup[groupIdx].Txn; txn.Type != protocol.ApplicationCallTx { + err = fmt.Errorf("can't use gload on non-app call txn with index %d", groupIdx) return } else if cx.runModeFlags == runModeSignature { - cx.err = fmt.Errorf("can't use gload from within a LogicSig") + err = fmt.Errorf("can't use gload from within a LogicSig") + return + } else if groupIdx == cx.GroupIndex { + err = fmt.Errorf("can't use gload on self, use load instead") return - } else if gtxid == cx.GroupIndex { - cx.err = fmt.Errorf("can't use gload on self, use load instead") + } else if groupIdx > cx.GroupIndex { + err = fmt.Errorf("gload can't get future scratch space from txn with index %d", groupIdx) return - } else if gtxid > cx.GroupIndex { - cx.err = fmt.Errorf("gload can't get future scratch space from txn with index %d", gtxid) + } + + scratchValue = cx.PastSideEffects[groupIdx].GetScratchValue(uint8(scratchIdx)) + return +} + +func opGload(cx *evalContext) { + groupIdx := int(uint(cx.program[cx.pc+1])) + scratchIdx := int(uint(cx.program[cx.pc+2])) + err, scratchValue := opGloadImpl(cx, groupIdx, scratchIdx) + if err != nil { + cx.err = err return } - scratchValue := cx.PastSideEffects[gtxid].GetScratchValue(uint8(gindex)) + cx.stack = append(cx.stack, scratchValue) } +func opGloads(cx *evalContext) { + last := len(cx.stack) - 1 + groupIdx := int(cx.stack[last].Uint) + scratchIdx := int(uint(cx.program[cx.pc+1])) + err, scratchValue := opGloadImpl(cx, groupIdx, scratchIdx) + if err != nil { + cx.err = err + return + } + + cx.stack[last] = scratchValue +} + func opConcat(cx *evalContext) { last := len(cx.stack) - 1 prev := last - 1 diff --git a/data/transactions/logic/opcodes.go b/data/transactions/logic/opcodes.go index 55b95208bc..3dbdac8986 100644 --- a/data/transactions/logic/opcodes.go +++ b/data/transactions/logic/opcodes.go @@ -189,6 +189,9 @@ var OpSpecs = []OpSpec{ // Like gtxn, but gets txn index from stack, rather than immediate arg {0x38, "gtxns", opGtxns, assembleGtxns, disTxn, oneInt, oneAny, 3, modeAny, immediates("f")}, {0x39, "gtxnsa", opGtxnsa, assembleGtxns, disTxna, oneInt, oneAny, 3, modeAny, immediates("f", "i")}, + // Group scratch space access + {0x3a, "gload", opGload, asmDefault, disDefault, nil, oneAny, 4, runModeApplication, immediates("t", "i")}, + {0x3b, "gloads", opGloads, asmDefault, disDefault, oneInt, oneAny, 4, runModeApplication, immediates("i")}, {0x40, "bnz", opBnz, assembleBranch, disBranch, oneInt, nil, 1, modeAny, opBranch}, {0x41, "bz", opBz, assembleBranch, disBranch, oneInt, nil, 2, modeAny, opBranch}, @@ -241,9 +244,6 @@ var OpSpecs = []OpSpec{ // shl, shr // divw, modw convenience // expmod - - // Group scratch space access - {0xb0, "gload", opGload, asmDefault, disDefault, nil, oneAny, 2, modeAny, immediates("t", "i")}, } type sortByOpcode []OpSpec From badb635364e21119e967c5760c6d537bb1cb6410 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Tue, 25 May 2021 11:23:13 -0400 Subject: [PATCH 24/35] Fix failing tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove pointless check for `runModeSignature` * Add `gload` opcodes to nonsense test * Populate evalParams constructed during tests with gload’s expected environment --- data/transactions/logic/assembler_test.go | 4 +++- data/transactions/logic/eval.go | 3 --- data/transactions/logic/evalStateful_test.go | 5 +++++ data/transactions/logic/eval_test.go | 16 +++++++++++++++- 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/data/transactions/logic/assembler_test.go b/data/transactions/logic/assembler_test.go index 1fca9c8494..1166184eec 100644 --- a/data/transactions/logic/assembler_test.go +++ b/data/transactions/logic/assembler_test.go @@ -258,6 +258,8 @@ stuff: retsub next: int 1 +gload 0 0 +gloads 0 ` var nonsense = map[uint64]string{ @@ -271,7 +273,7 @@ var compiled = map[uint64]string{ 1: "012008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f23102311231223132314181b1c2b1716154000032903494", 2: "022008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f23102311231223132314181b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f", 3: "032008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f23102311231223132314181b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f4478222105531421055427042106552105082106564c4d4b02210538212106391c0081e80780046a6f686e", - 4: "042008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f23102311231223132314181b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f4478222105531421055427042106552105082106564c4d4b02210538212106391c0081e80780046a6f686e210581d00f210721061f880003420001892105", + 4: "042008b7a60cf8acd19181cf959a12f8acd19181cf951af8acd19181cf15f8acd191810f01020026050212340c68656c6c6f20776f726c6421208dae2087fbba51304eb02b91f656948397a7946390e8cb70fc9ea4d95f92251d024242047465737400320032013202320328292929292a0431003101310231043105310731083109310a310b310c310d310e310f3111311231133114311533000033000133000233000433000533000733000833000933000a33000b33000c33000d33000e33000f3300113300123300133300143300152d2e0102222324252104082209240a220b230c240d250e230f23102311231223132314181b1c2b171615400003290349483403350222231d4a484848482a50512a63222352410003420000432105602105612105270463484821052b62482b642b65484821052b2106662b21056721072b682b692107210570004848210771004848361c0037001a0031183119311b311d311e311f3120210721051e312131223123312431253126312731283129312a312b312c312d312e312f4478222105531421055427042106552105082106564c4d4b02210538212106391c0081e80780046a6f686e210581d00f210721061f8800034200018921053a00003b00", } func pseudoOp(opcode string) bool { diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 22d498af3f..9ec0858dfe 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1921,9 +1921,6 @@ func opGloadImpl(cx *evalContext, groupIdx int, scratchIdx int) (err error, scra } else if txn := cx.TxnGroup[groupIdx].Txn; txn.Type != protocol.ApplicationCallTx { err = fmt.Errorf("can't use gload on non-app call txn with index %d", groupIdx) return - } else if cx.runModeFlags == runModeSignature { - err = fmt.Errorf("can't use gload from within a LogicSig") - return } else if groupIdx == cx.GroupIndex { err = fmt.Errorf("can't use gload on self, use load instead") return diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index 98c791a1e6..7b6401de54 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -2644,12 +2644,15 @@ func TestReturnTypes(t *testing.T) { } ep := defaultEvalParams(nil, nil) txn := makeSampleTxn() + txn.Txn.Type = protocol.ApplicationCallTx txgroup := makeSampleTxnGroup(txn) ep.Txn = &txn ep.TxnGroup = txgroup ep.Txn.Txn.ApplicationID = 1 ep.Txn.Txn.ForeignApps = []basics.AppIndex{txn.Txn.ApplicationID} ep.Txn.Txn.ForeignAssets = []basics.AssetIndex{basics.AssetIndex(1), basics.AssetIndex(1)} + ep.GroupIndex = 1 + ep.PastSideEffects = makeSamplePastSideEffects(len(txgroup)) txn.Lsig.Args = [][]byte{ []byte("aoeu"), []byte("aoeu"), @@ -2693,6 +2696,8 @@ func TestReturnTypes(t *testing.T) { "arg": "arg 0", "load": "load 0", "store": "store 0", + "gload": "gload 0 0", + "gloads": "gloads 0", "dig": "dig 0", "intc": "intcblock 0; intc 0", "intc_0": "intcblock 0; intc_0", diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 09a38f9bae..190358cb79 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -87,6 +87,7 @@ func defaultEvalParamsWithVersion(sb *strings.Builder, txn *transactions.SignedT ep := EvalParams{} ep.Proto = &proto ep.Txn = pt + ep.PastSideEffects = makeSamplePastSideEffects(5) if sb != nil { // have to do this since go's nil semantics: https://golang.org/doc/faq#nil_error ep.Trace = sb } @@ -1540,6 +1541,19 @@ func makeSampleTxnGroup(txn transactions.SignedTxn) []transactions.SignedTxn { return txgroup } +func makeSamplePastSideEffects(groupSize int) []*EvalSideEffects { + sampleScratch := scratchSpace{ + stackValue{Uint: 1}, + } + sideEffects := make([]*EvalSideEffects, groupSize) + for i := range sideEffects { + sideEffects[i] = &EvalSideEffects{ + scratchSpace: sampleScratch, + } + } + return sideEffects +} + func TestTxn(t *testing.T) { t.Parallel() for _, txnField := range TxnFieldNames { @@ -2379,7 +2393,7 @@ int 1`, }, }, runMode: runModeSignature, - errContains: "can't use gload from within a LogicSig", + errContains: "gload not allowed in current mode", } failCases := []failureCase{nonAppCall, logicSigCall} From 94ee198376e91532e53298565ae124c9978dc223 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Tue, 25 May 2021 11:31:32 -0400 Subject: [PATCH 25/35] Document only previous app calls limitation --- data/transactions/logic/TEAL_opcodes.md | 4 ++++ data/transactions/logic/doc.go | 2 ++ 2 files changed, 6 insertions(+) diff --git a/data/transactions/logic/TEAL_opcodes.md b/data/transactions/logic/TEAL_opcodes.md index 8274b9ec24..9cd73b879b 100644 --- a/data/transactions/logic/TEAL_opcodes.md +++ b/data/transactions/logic/TEAL_opcodes.md @@ -530,6 +530,8 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g - LogicSigVersion >= 4 - Mode: Application +The `gload` opcode can only access scratch spaces of previous app calls contained in the current group. + ## gloads i - Opcode: 0x3b {uint8 position in scratch space to load from} @@ -539,6 +541,8 @@ for notes on transaction fields available, see `txn`. If top of stack is _i_, `g - LogicSigVersion >= 4 - Mode: Application +The `gloads` opcode can only access scratch spaces of previous app calls contained in the current group. + ## bnz target - Opcode: 0x40 {int16 branch offset, big endian. (negative offsets are illegal before v4)} diff --git a/data/transactions/logic/doc.go b/data/transactions/logic/doc.go index e67ab3e6d0..a35d453d8e 100644 --- a/data/transactions/logic/doc.go +++ b/data/transactions/logic/doc.go @@ -172,6 +172,8 @@ var opDocExtras = map[string]string{ "txn": "FirstValidTime causes the program to fail. The field is reserved for future use.", "gtxn": "for notes on transaction fields available, see `txn`. If this transaction is _i_ in the group, `gtxn i field` is equivalent to `txn field`.", "gtxns": "for notes on transaction fields available, see `txn`. If top of stack is _i_, `gtxns field` is equivalent to `gtxn _i_ field`. gtxns exists so that _i_ can be calculated, often based on the index of the current transaction.", + "gload": "The `gload` opcode can only access scratch spaces of previous app calls contained in the current group.", + "gloads": "The `gloads` opcode can only access scratch spaces of previous app calls contained in the current group.", "btoi": "`btoi` panics if the input is longer than 8 bytes.", "concat": "`concat` panics if the result would be greater than 4096 bytes.", "pushbytes": "pushbytes args are not added to the bytecblock during assembly processes", From 867218272bede6a21d0610f63fc98916ef754929 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Wed, 26 May 2021 14:55:12 -0400 Subject: [PATCH 26/35] `gloads` integration test --- .../e2e_subs/access-previous-scratch.sh | 38 +++++++++++++++ .../e2e_subs/tealprogs/approve-all.teal | 2 + .../e2e_subs/tealprogs/scratch-rw.teal | 46 +++++++++++++++++++ 3 files changed, 86 insertions(+) create mode 100755 test/scripts/e2e_subs/access-previous-scratch.sh create mode 100644 test/scripts/e2e_subs/tealprogs/approve-all.teal create mode 100644 test/scripts/e2e_subs/tealprogs/scratch-rw.teal diff --git a/test/scripts/e2e_subs/access-previous-scratch.sh b/test/scripts/e2e_subs/access-previous-scratch.sh new file mode 100755 index 0000000000..7c519ec43d --- /dev/null +++ b/test/scripts/e2e_subs/access-previous-scratch.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +filename=$(basename "$0") +scriptname="${filename%.*}" +date "+${scriptname} start %Y%m%d_%H%M%S" + +set -e +set -x +set -o pipefail +export SHELLOPTS + +WALLET=$1 + +TEAL=test/scripts/e2e_subs/tealprogs + +gcmd="goal -w ${WALLET}" + +ACCOUNT=$(${gcmd} account list|awk '{ print $3 }') + +APPID=$(${gcmd} app create --creator "${ACCOUNT}" --approval-prog=${TEAL}/scratch-rw.teal --global-byteslices 0 --global-ints 0 --local-byteslices 0 --local-ints 0 --clear-prog=${TEAL}/approve-all.teal | grep Created | awk '{ print $6 }') + +# Create app calls +function create_app_call { + ${gcmd} app call --app-id="$APPID" --from="$ACCOUNT" --app-arg=str:"$1" --app-arg=int:"$2" --app-arg=int:"$3" --out "$TEMPDIR/$4" +} + +create_app_call write 0 1 unsigned_scratch_write.txn +create_app_call check 0 1 unsigned_scratch_check.txn + +# Group transactions +cat "$TEMPDIR/unsigned_scratch_write.txn" "$TEMPDIR/unsigned_scratch_check.txn" > "$TEMPDIR/combined_transactions.txn" +${gcmd} clerk group -i "$TEMPDIR/combined_transactions.txn" -o "$TEMPDIR/grouped_transactions.txn" + +# Sign and send +${gcmd} clerk sign -i "$TEMPDIR/grouped_transactions.txn" -o "$TEMPDIR/signed.txn" +${gcmd} clerk rawsend -f "$TEMPDIR/signed.txn" + +date "+${scriptname} OK %Y%m%d_%H%M%S" \ No newline at end of file diff --git a/test/scripts/e2e_subs/tealprogs/approve-all.teal b/test/scripts/e2e_subs/tealprogs/approve-all.teal new file mode 100644 index 0000000000..790222107f --- /dev/null +++ b/test/scripts/e2e_subs/tealprogs/approve-all.teal @@ -0,0 +1,2 @@ +#pragma version 2 + int 1 diff --git a/test/scripts/e2e_subs/tealprogs/scratch-rw.teal b/test/scripts/e2e_subs/tealprogs/scratch-rw.teal new file mode 100644 index 0000000000..b0b9c8089b --- /dev/null +++ b/test/scripts/e2e_subs/tealprogs/scratch-rw.teal @@ -0,0 +1,46 @@ +#pragma version 4 + txn ApplicationID + bz skip // skip code on creation + + int OptIn + txn OnCompletion + == + bnz skip // skip code on opt-in + +main: + // This program allows the reading (checking) and writing of scratch spaces. + // ARGS = "write|check", groupIndex, value (or expected value if checking) + // If arg0 is "write", the value is written to scratch space in the first slot, and groupIndex is ignored + // If arg0 is "check", the first slot of scratch space is copied from of the app call with index groupIndex + // -> succeeds if it matches the expected value + + txn ApplicationArgs 2 // value + txn ApplicationArgs 1 // groupIndex + + txn ApplicationArgs 0 + byte "write" + == + bnz write + + txn ApplicationArgs 0 + byte "check" + == + bnz check + + err + +write: + pop // ignore groupIndex + store 0 + int 1 + return + +check: + btoi + gloads 0 + == + return + +skip: + int 1 + return From 68fac4ee4736e1b564a6de809d36a093325bac5f Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Wed, 26 May 2021 14:57:36 -0400 Subject: [PATCH 27/35] `opGloadImpl` improvements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Swap `scratchValue` and `err` order when returning to conform to Go’s code style * Provide better errors by including an accurate op name `gload` or `gloads` when throwing errors --- data/transactions/logic/eval.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 9ec0858dfe..334f858cf3 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -1911,21 +1911,21 @@ func opStore(cx *evalContext) { cx.stack = cx.stack[:last] } -func opGloadImpl(cx *evalContext, groupIdx int, scratchIdx int) (err error, scratchValue stackValue) { +func opGloadImpl(cx *evalContext, groupIdx int, scratchIdx int, opName string) (scratchValue stackValue, err error) { if groupIdx >= len(cx.TxnGroup) { - err = fmt.Errorf("gload lookup TxnGroup[%d] but it only has %d", groupIdx, len(cx.TxnGroup)) + err = fmt.Errorf("%s lookup TxnGroup[%d] but it only has %d", opName, groupIdx, len(cx.TxnGroup)) return } else if scratchIdx >= 256 { err = fmt.Errorf("invalid Scratch index %d", scratchIdx) return } else if txn := cx.TxnGroup[groupIdx].Txn; txn.Type != protocol.ApplicationCallTx { - err = fmt.Errorf("can't use gload on non-app call txn with index %d", groupIdx) + err = fmt.Errorf("can't use %s on non-app call txn with index %d", opName, groupIdx) return } else if groupIdx == cx.GroupIndex { - err = fmt.Errorf("can't use gload on self, use load instead") + err = fmt.Errorf("can't use %s on self, use load instead", opName) return } else if groupIdx > cx.GroupIndex { - err = fmt.Errorf("gload can't get future scratch space from txn with index %d", groupIdx) + err = fmt.Errorf("%s can't get future scratch space from txn with index %d", opName, groupIdx) return } @@ -1936,7 +1936,7 @@ func opGloadImpl(cx *evalContext, groupIdx int, scratchIdx int) (err error, scra func opGload(cx *evalContext) { groupIdx := int(uint(cx.program[cx.pc+1])) scratchIdx := int(uint(cx.program[cx.pc+2])) - err, scratchValue := opGloadImpl(cx, groupIdx, scratchIdx) + scratchValue, err := opGloadImpl(cx, groupIdx, scratchIdx, "gload") if err != nil { cx.err = err return @@ -1949,7 +1949,7 @@ func opGloads(cx *evalContext) { last := len(cx.stack) - 1 groupIdx := int(cx.stack[last].Uint) scratchIdx := int(uint(cx.program[cx.pc+1])) - err, scratchValue := opGloadImpl(cx, groupIdx, scratchIdx) + scratchValue, err := opGloadImpl(cx, groupIdx, scratchIdx, "gloads") if err != nil { cx.err = err return From 1aa1bfa9082d3ccb43a0e327fad4e3d60ea8e4e0 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Wed, 26 May 2021 15:43:33 -0400 Subject: [PATCH 28/35] Remove useless pointer slice types --- data/transactions/logic/eval.go | 2 +- data/transactions/logic/eval_test.go | 46 +++++++++++++--------------- ledger/eval.go | 6 ++-- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 968ba71947..ce2157909f 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -189,7 +189,7 @@ type EvalParams struct { // GroupIndex should point to Txn within TxnGroup GroupIndex int - PastSideEffects []*EvalSideEffects + PastSideEffects []EvalSideEffects Logger logging.Logger diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 676a72f838..d8fa1797e6 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1344,13 +1344,13 @@ func makeSampleTxnGroup(txn transactions.SignedTxn) []transactions.SignedTxn { return txgroup } -func makeSamplePastSideEffects(groupSize int) []*EvalSideEffects { +func makeSamplePastSideEffects(groupSize int) []EvalSideEffects { sampleScratch := scratchSpace{ stackValue{Uint: 1}, } - sideEffects := make([]*EvalSideEffects, groupSize) + sideEffects := make([]EvalSideEffects, groupSize) for i := range sideEffects { - sideEffects[i] = &EvalSideEffects{ + sideEffects[i] = EvalSideEffects{ scratchSpace: sampleScratch, } } @@ -2012,9 +2012,9 @@ int 1`, } // Construct EvalParams - pastSideEffects := make([]*EvalSideEffects, len(sources)) + pastSideEffects := make([]EvalSideEffects, len(sources)) for j := range pastSideEffects { - pastSideEffects[j] = new(EvalSideEffects) + pastSideEffects[j] = EvalSideEffects{} } epList := make([]EvalParams, len(sources)) for j := range sources { @@ -2155,9 +2155,9 @@ byte "txn 2" } // Construct EvalParams - pastSideEffects := make([]*EvalSideEffects, len(sources)) + pastSideEffects := make([]EvalSideEffects, len(sources)) for j := range pastSideEffects { - pastSideEffects[j] = new(EvalSideEffects) + pastSideEffects[j] = EvalSideEffects{} } epList := make([]EvalParams, len(sources)) for j := range sources { @@ -3114,7 +3114,7 @@ func benchmarkExpensiveProgram(b *testing.B, source string) { // during the "operation". They are presumed to be fast (15/ns), so // the idea is that you can subtract that out from the reported speed func benchmarkOperation(b *testing.B, prefix string, operation string, suffix string) { - runs := 1 + b.N / 2000 + runs := 1 + b.N/2000 inst := strings.Count(operation, ";") + strings.Count(operation, "\n") source := prefix + ";" + strings.Repeat(operation+";", 2000) + ";" + suffix source = strings.ReplaceAll(source, ";", "\n") @@ -3140,7 +3140,7 @@ func BenchmarkUintMath(b *testing.B) { } for _, bench := range benches { b.Run(bench[0], func(b *testing.B) { - benchmarkOperation(b, bench[1], bench[2], bench[3]); + benchmarkOperation(b, bench[1], bench[2], bench[3]) }) } } @@ -3149,7 +3149,7 @@ func BenchmarkUintCmp(b *testing.B) { ops := []string{"==", "!=", "<", "<=", ">", ">="} for _, op := range ops { b.Run(op, func(b *testing.B) { - benchmarkOperation(b, "", "int 7263; int 273834; "+op+"; pop", "int 1"); + benchmarkOperation(b, "", "int 7263; int 273834; "+op+"; pop", "int 1") }) } } @@ -3157,7 +3157,7 @@ func BenchmarkBigLogic(b *testing.B) { benches := [][]string{ {"b&", "byte 0x01234576", "byte 0x01ffffffffffffff; b&", "pop; int 1"}, {"b|", "byte 0x0ffff1234576", "byte 0x1202; b|", "pop; int 1"}, - {"b^", "byte 0x01234576", "byte 0x0223627389; b^", "pop; int 1"}, + {"b^", "byte 0x01234576", "byte 0x0223627389; b^", "pop; int 1"}, {"b~", "byte 0x0123457673624736", "b~", "pop; int 1"}, {"b&big", @@ -3168,7 +3168,7 @@ func BenchmarkBigLogic(b *testing.B) { "byte 0x0123457601234576012345760123457601234576012345760123457601234576", "byte 0xffffff01ffffffffffffff01234576012345760123457601234576; b|", "pop; int 1"}, - {"b^big", "", // u256*u256 + {"b^big", "", // u256*u256 `byte 0x123457601234576012345760123457601234576012345760123457601234576a byte 0xf123457601234576012345760123457601234576012345760123457601234576; b^; pop`, "int 1"}, @@ -3178,7 +3178,7 @@ func BenchmarkBigLogic(b *testing.B) { } for _, bench := range benches { b.Run(bench[0], func(b *testing.B) { - benchmarkOperation(b, bench[1], bench[2], bench[3]); + benchmarkOperation(b, bench[1], bench[2], bench[3]) }) } } @@ -3193,30 +3193,30 @@ func BenchmarkBigMath(b *testing.B) { {"b/", "", "byte 0x0123457673624736; byte 0x0223627389; b/; pop", "int 1"}, {"b%", "", "byte 0x0123457673624736; byte 0x0223627389; b/; pop", "int 1"}, - {"b+big", // u256 + u256 + {"b+big", // u256 + u256 "byte 0x0123457601234576012345760123457601234576012345760123457601234576", "byte 0x01ffffffffffffff01ffffffffffffff01234576012345760123457601234576; b+", "pop; int 1"}, - {"b-big", // second is a bit small, so we can subtract it over and over + {"b-big", // second is a bit small, so we can subtract it over and over "byte 0x0123457601234576012345760123457601234576012345760123457601234576", "byte 0xffffff01ffffffffffffff01234576012345760123457601234576; b-", "pop; int 1"}, - {"b*big", "", // u256*u256 + {"b*big", "", // u256*u256 `byte 0xa123457601234576012345760123457601234576012345760123457601234576 byte 0xf123457601234576012345760123457601234576012345760123457601234576; b*; pop`, "int 1"}, - {"b/big", "", // u256 / u128 (half sized divisor seems pessimal) + {"b/big", "", // u256 / u128 (half sized divisor seems pessimal) `byte 0xa123457601234576012345760123457601234576012345760123457601234576 byte 0x34576012345760123457601234576312; b/; pop`, "int 1"}, - {"b%big", "", // u256 / u128 (half sized divisor seems pessimal) + {"b%big", "", // u256 / u128 (half sized divisor seems pessimal) `byte 0xa123457601234576012345760123457601234576012345760123457601234576 byte 0x34576012345760123457601234576312; b/; pop`, "int 1"}, } for _, bench := range benches { b.Run(bench[0], func(b *testing.B) { - benchmarkOperation(b, bench[1], bench[2], bench[3]); + benchmarkOperation(b, bench[1], bench[2], bench[3]) }) } } @@ -3226,24 +3226,23 @@ func BenchmarkHash(b *testing.B) { for _, hash := range hashes { b.Run(hash+"-small", func(b *testing.B) { // hash 32 bytes benchmarkOperation(b, "byte 0x1234567890", hash, - "pop; int 1"); + "pop; int 1") }) } for _, hash := range hashes { b.Run(hash+"-med", func(b *testing.B) { // hash 128 bytes benchmarkOperation(b, "byte 0x1234567890", - hash+"; dup; concat; dup; concat", "pop; int 1"); + hash+"; dup; concat; dup; concat", "pop; int 1") }) } for _, hash := range hashes { b.Run(hash+"-big", func(b *testing.B) { // hash 512 bytes benchmarkOperation(b, "byte 0x1234567890", - hash+"; dup; concat; dup; concat; dup; concat; dup; concat", "pop; int 1"); + hash+"; dup; concat; dup; concat; dup; concat; dup; concat", "pop; int 1") }) } } - func BenchmarkAddx64(b *testing.B) { progs := [][]string{ {"add long stack", addBenchmarkSource}, @@ -3325,7 +3324,6 @@ ed25519verify`, pkStr), v) } } - func BenchmarkEd25519Verifyx1(b *testing.B) { //benchmark setup var data [][32]byte diff --git a/ledger/eval.go b/ledger/eval.go index c4dc36479e..afc8332f5a 100644 --- a/ledger/eval.go +++ b/ledger/eval.go @@ -668,7 +668,7 @@ func (eval *BlockEvaluator) TransactionGroup(txads []transactions.SignedTxnWithA // transaction in the group func (eval *BlockEvaluator) prepareEvalParams(txgroup []transactions.SignedTxnWithAD) (res []*logic.EvalParams) { var groupNoAD []transactions.SignedTxn - var pastSideEffects []*logic.EvalSideEffects + var pastSideEffects []logic.EvalSideEffects var minTealVersion uint64 res = make([]*logic.EvalParams, len(txgroup)) for i, txn := range txgroup { @@ -680,10 +680,10 @@ func (eval *BlockEvaluator) prepareEvalParams(txgroup []transactions.SignedTxnWi // Initialize side effects and group without ApplyData lazily if groupNoAD == nil { groupNoAD = make([]transactions.SignedTxn, len(txgroup)) - pastSideEffects = make([]*logic.EvalSideEffects, len(txgroup)) + pastSideEffects = make([]logic.EvalSideEffects, len(txgroup)) for j := range txgroup { groupNoAD[j] = txgroup[j].SignedTxn - pastSideEffects[j] = new(logic.EvalSideEffects) + pastSideEffects[j] = logic.EvalSideEffects{} } minTealVersion = logic.ComputeMinTealVersion(groupNoAD) } From e0963eed72935c04f00bd9aa6dd431b77af257d6 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Wed, 26 May 2021 15:54:05 -0400 Subject: [PATCH 29/35] Wrap first line to improve TEAL readability --- data/transactions/logic/eval_test.go | 36 ++++++++++++++++++---------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index d8fa1797e6..e954a0842c 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1933,10 +1933,12 @@ func TestGload(t *testing.T) { simpleCase := scratchTestCase{ tealSources: []string{ - `int 2 + ` +int 2 store 0 int 1`, - `gload 0 0 + ` +gload 0 0 int 2 == `, @@ -1945,13 +1947,16 @@ int 2 multipleTxnCase := scratchTestCase{ tealSources: []string{ - `byte "txn 1" + ` +byte "txn 1" store 0 int 1`, - `byte "txn 2" + ` +byte "txn 2" store 1 int 1`, - `gload 0 0 + ` +gload 0 0 byte "txn 1" == gload 1 1 @@ -1964,7 +1969,8 @@ byte "txn 2" selfCase := scratchTestCase{ tealSources: []string{ - `gload 0 0 + ` +gload 0 0 int 2 store 0 int 1 @@ -1975,11 +1981,12 @@ int 1 laterTxnSlotCase := scratchTestCase{ tealSources: []string{ - `gload 1 0 + ` +gload 1 0 +int 2 +==`, + ` int 2 -== -`, - `int 2 store 0 int 1`, }, @@ -2118,13 +2125,16 @@ func TestGloads(t *testing.T) { t.Parallel() // Multiple app calls - source1 := `byte "txn 1" + source1 := ` +byte "txn 1" store 0 int 1` - source2 := `byte "txn 2" + source2 := ` +byte "txn 2" store 1 int 1` - source3 := `int 0 + source3 := ` +int 0 gloads 0 byte "txn 1" == From 554b6749d95eeb1e9c8847cb3e2e0ab099589c91 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Wed, 26 May 2021 15:57:51 -0400 Subject: [PATCH 30/35] Reduce ambiguity between `txnIdx` and `scratchIdx` --- data/transactions/logic/eval_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index e954a0842c..f5f0a9aa37 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -1953,13 +1953,13 @@ store 0 int 1`, ` byte "txn 2" -store 1 +store 2 int 1`, ` gload 0 0 byte "txn 1" == -gload 1 1 +gload 1 2 byte "txn 2" == && From b1ff62ac0750dd23cbe4047fca60573d15c80f84 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Wed, 26 May 2021 18:11:23 -0400 Subject: [PATCH 31/35] Unexpose internal scratch space methods --- data/transactions/logic/eval.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index ce2157909f..5e4bec4012 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -164,14 +164,14 @@ type EvalSideEffects struct { scratchSpace scratchSpace } -// GetScratchValue loads and clones a stackValue +// getScratchValue loads and clones a stackValue // The value is cloned so the original bytes are protected from changes -func (se *EvalSideEffects) GetScratchValue(scratchPos uint8) stackValue { +func (se *EvalSideEffects) getScratchValue(scratchPos uint8) stackValue { return se.scratchSpace[scratchPos].clone() } -// SetScratchSpace stores the scratch space -func (se *EvalSideEffects) SetScratchSpace(scratch scratchSpace) { +// setScratchSpace stores the scratch space +func (se *EvalSideEffects) setScratchSpace(scratch scratchSpace) { se.scratchSpace = scratch } @@ -465,7 +465,7 @@ func eval(program []byte, cx *evalContext) (pass bool, err error) { } // set side effects - cx.PastSideEffects[cx.GroupIndex].SetScratchSpace(cx.scratch) + cx.PastSideEffects[cx.GroupIndex].setScratchSpace(cx.scratch) return cx.stack[0].Uint != 0, nil } @@ -2265,7 +2265,7 @@ func opGloadImpl(cx *evalContext, groupIdx int, scratchIdx int, opName string) ( return } - scratchValue = cx.PastSideEffects[groupIdx].GetScratchValue(uint8(scratchIdx)) + scratchValue = cx.PastSideEffects[groupIdx].getScratchValue(uint8(scratchIdx)) return } From 665c0bdc097204e75f49d7db9adf165dc1fa4429 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 27 May 2021 09:30:26 -0400 Subject: [PATCH 32/35] Add utility method for initializing past side effect slices --- data/transactions/logic/eval.go | 9 +++++++++ data/transactions/logic/eval_test.go | 10 ++-------- ledger/eval.go | 3 +-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index 5e4bec4012..d46ae026d7 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -164,6 +164,15 @@ type EvalSideEffects struct { scratchSpace scratchSpace } +// MakePastSideEffects allocates and initializes a slice of EvalSideEffects of length `size` +func MakePastSideEffects(size int) (pastSideEffects []EvalSideEffects) { + pastSideEffects = make([]EvalSideEffects, size) + for j := range pastSideEffects { + pastSideEffects[j] = EvalSideEffects{} + } + return +} + // getScratchValue loads and clones a stackValue // The value is cloned so the original bytes are protected from changes func (se *EvalSideEffects) getScratchValue(scratchPos uint8) stackValue { diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index f5f0a9aa37..92f0b8177e 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -2019,10 +2019,7 @@ int 1`, } // Construct EvalParams - pastSideEffects := make([]EvalSideEffects, len(sources)) - for j := range pastSideEffects { - pastSideEffects[j] = EvalSideEffects{} - } + pastSideEffects := MakePastSideEffects(len(sources)) epList := make([]EvalParams, len(sources)) for j := range sources { epList[j] = EvalParams{ @@ -2165,10 +2162,7 @@ byte "txn 2" } // Construct EvalParams - pastSideEffects := make([]EvalSideEffects, len(sources)) - for j := range pastSideEffects { - pastSideEffects[j] = EvalSideEffects{} - } + pastSideEffects := MakePastSideEffects(len(sources)) epList := make([]EvalParams, len(sources)) for j := range sources { epList[j] = EvalParams{ diff --git a/ledger/eval.go b/ledger/eval.go index afc8332f5a..aa1952d078 100644 --- a/ledger/eval.go +++ b/ledger/eval.go @@ -680,11 +680,10 @@ func (eval *BlockEvaluator) prepareEvalParams(txgroup []transactions.SignedTxnWi // Initialize side effects and group without ApplyData lazily if groupNoAD == nil { groupNoAD = make([]transactions.SignedTxn, len(txgroup)) - pastSideEffects = make([]logic.EvalSideEffects, len(txgroup)) for j := range txgroup { groupNoAD[j] = txgroup[j].SignedTxn - pastSideEffects[j] = logic.EvalSideEffects{} } + pastSideEffects = logic.MakePastSideEffects(len(txgroup)) minTealVersion = logic.ComputeMinTealVersion(groupNoAD) } From a1fb497e0fa7735bf824a491b5510b0badb1b842 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 27 May 2021 09:58:09 -0400 Subject: [PATCH 33/35] Add check to only set side effects during an application call --- data/transactions/logic/eval.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index d46ae026d7..cfd0fe10e8 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -474,7 +474,9 @@ func eval(program []byte, cx *evalContext) (pass bool, err error) { } // set side effects - cx.PastSideEffects[cx.GroupIndex].setScratchSpace(cx.scratch) + if cx.runModeFlags == runModeApplication { + cx.PastSideEffects[cx.GroupIndex].setScratchSpace(cx.scratch) + } return cx.stack[0].Uint != 0, nil } From 96584f1715f574807c140f51eab9f1ff08ef5d0b Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 27 May 2021 11:24:02 -0400 Subject: [PATCH 34/35] Move side effect logic into `EvalStateful` function * Remove `makeSamplePastSideEffects` function now that there is the `MakePastSideEffects` function --- data/transactions/logic/eval.go | 11 ++++----- data/transactions/logic/evalStateful_test.go | 2 +- data/transactions/logic/eval_test.go | 25 ++++++-------------- 3 files changed, 13 insertions(+), 25 deletions(-) diff --git a/data/transactions/logic/eval.go b/data/transactions/logic/eval.go index cfd0fe10e8..1a2633824e 100644 --- a/data/transactions/logic/eval.go +++ b/data/transactions/logic/eval.go @@ -353,7 +353,11 @@ func EvalStateful(program []byte, params EvalParams) (pass bool, err error) { var cx evalContext cx.EvalParams = params cx.runModeFlags = runModeApplication - return eval(program, &cx) + pass, err = eval(program, &cx) + + // set side effects + cx.PastSideEffects[cx.GroupIndex].setScratchSpace(cx.scratch) + return } // Eval checks to see if a transaction passes logic @@ -473,11 +477,6 @@ func eval(program []byte, cx *evalContext) (pass bool, err error) { return false, errors.New("stack finished with bytes not int") } - // set side effects - if cx.runModeFlags == runModeApplication { - cx.PastSideEffects[cx.GroupIndex].setScratchSpace(cx.scratch) - } - return cx.stack[0].Uint != 0, nil } diff --git a/data/transactions/logic/evalStateful_test.go b/data/transactions/logic/evalStateful_test.go index d1ceb208a6..f743242ba9 100644 --- a/data/transactions/logic/evalStateful_test.go +++ b/data/transactions/logic/evalStateful_test.go @@ -2652,7 +2652,7 @@ func TestReturnTypes(t *testing.T) { ep.Txn.Txn.ForeignApps = []basics.AppIndex{txn.Txn.ApplicationID} ep.Txn.Txn.ForeignAssets = []basics.AssetIndex{basics.AssetIndex(1), basics.AssetIndex(1)} ep.GroupIndex = 1 - ep.PastSideEffects = makeSamplePastSideEffects(len(txgroup)) + ep.PastSideEffects = MakePastSideEffects(len(txgroup)) txn.Lsig.Args = [][]byte{ []byte("aoeu"), []byte("aoeu"), diff --git a/data/transactions/logic/eval_test.go b/data/transactions/logic/eval_test.go index 92f0b8177e..9d60c0b3a6 100644 --- a/data/transactions/logic/eval_test.go +++ b/data/transactions/logic/eval_test.go @@ -93,7 +93,7 @@ func defaultEvalParamsWithVersion(sb *strings.Builder, txn *transactions.SignedT ep := EvalParams{} ep.Proto = &proto ep.Txn = pt - ep.PastSideEffects = makeSamplePastSideEffects(5) + ep.PastSideEffects = MakePastSideEffects(5) if sb != nil { // have to do this since go's nil semantics: https://golang.org/doc/faq#nil_error ep.Trace = sb } @@ -1344,19 +1344,6 @@ func makeSampleTxnGroup(txn transactions.SignedTxn) []transactions.SignedTxn { return txgroup } -func makeSamplePastSideEffects(groupSize int) []EvalSideEffects { - sampleScratch := scratchSpace{ - stackValue{Uint: 1}, - } - sideEffects := make([]EvalSideEffects, groupSize) - for i := range sideEffects { - sideEffects[i] = EvalSideEffects{ - scratchSpace: sampleScratch, - } - } - return sideEffects -} - func TestTxn(t *testing.T) { t.Parallel() for _, txnField := range TxnFieldNames { @@ -2093,13 +2080,15 @@ int 1`, txgroup[1] = transactions.SignedTxn{} // Construct EvalParams + pastSideEffects := MakePastSideEffects(2) epList := make([]EvalParams, 2) for j := range epList { epList[j] = EvalParams{ - Proto: &proto, - Txn: &txgroup[j], - TxnGroup: txgroup, - GroupIndex: j, + Proto: &proto, + Txn: &txgroup[j], + TxnGroup: txgroup, + GroupIndex: j, + PastSideEffects: pastSideEffects, } } From dee9b0886270bc0ff1fffc7035172216d3b15cf4 Mon Sep 17 00:00:00 2001 From: Jacob Daitzman Date: Thu, 27 May 2021 16:47:29 -0400 Subject: [PATCH 35/35] Populate `EvalParams` from test suites with past side effect slices --- cmd/tealdbg/local.go | 79 +++++++++++++++++----------- cmd/tealdbg/localLedger_test.go | 10 ++-- cmd/tealdbg/server.go | 2 + daemon/algod/api/server/v2/dryrun.go | 10 ++-- 4 files changed, 62 insertions(+), 39 deletions(-) diff --git a/cmd/tealdbg/local.go b/cmd/tealdbg/local.go index f8edad06d7..baebcea046 100644 --- a/cmd/tealdbg/local.go +++ b/cmd/tealdbg/local.go @@ -205,16 +205,17 @@ const ( // evaluation is a description of a single debugger run type evaluation struct { - program []byte - source string - offsetToLine map[int]int - name string - groupIndex int - mode modeType - aidx basics.AppIndex - ba apply.Balances - result evalResult - states AppState + program []byte + source string + offsetToLine map[int]int + name string + groupIndex int + pastSideEffects []logic.EvalSideEffects + mode modeType + aidx basics.AppIndex + ba apply.Balances + result evalResult + states AppState } func (e *evaluation) eval(ep logic.EvalParams) (pass bool, err error) { @@ -339,6 +340,17 @@ func (r *LocalRunner) Setup(dp *DebugParams) (err error) { dp.LatestTimestamp = int64(ddr.LatestTimestamp) } + if dp.PastSideEffects == nil { + dp.PastSideEffects = logic.MakePastSideEffects(len(r.txnGroup)) + } else if len(dp.PastSideEffects) != len(r.txnGroup) { + err = fmt.Errorf( + "invalid past side effects slice with length %d should match group length of %d txns", + len(dp.PastSideEffects), + len(r.txnGroup), + ) + return + } + // if program(s) specified then run from it if len(dp.ProgramBlobs) > 0 { if len(r.txnGroup) == 1 && dp.GroupIndex != 0 { @@ -376,6 +388,7 @@ func (r *LocalRunner) Setup(dp *DebugParams) (err error) { } } r.runs[i].groupIndex = dp.GroupIndex + r.runs[i].pastSideEffects = dp.PastSideEffects r.runs[i].name = dp.ProgramNames[i] var mode modeType @@ -438,12 +451,13 @@ func (r *LocalRunner) Setup(dp *DebugParams) (err error) { return } run := evaluation{ - program: stxn.Txn.ApprovalProgram, - groupIndex: gi, - mode: modeStateful, - aidx: appIdx, - ba: b, - states: states, + program: stxn.Txn.ApprovalProgram, + groupIndex: gi, + pastSideEffects: dp.PastSideEffects, + mode: modeStateful, + aidx: appIdx, + ba: b, + states: states, } r.runs = append(r.runs, run) } @@ -473,12 +487,13 @@ func (r *LocalRunner) Setup(dp *DebugParams) (err error) { return } run := evaluation{ - program: program, - groupIndex: gi, - mode: modeStateful, - aidx: appIdx, - ba: b, - states: states, + program: program, + groupIndex: gi, + pastSideEffects: dp.PastSideEffects, + mode: modeStateful, + aidx: appIdx, + ba: b, + states: states, } r.runs = append(r.runs, run) found = true @@ -513,11 +528,12 @@ func (r *LocalRunner) RunAll() error { r.debugger.SaveProgram(run.name, run.program, run.source, run.offsetToLine, run.states) ep := logic.EvalParams{ - Proto: &r.proto, - Debugger: r.debugger, - Txn: &r.txnGroup[groupIndex], - TxnGroup: r.txnGroup, - GroupIndex: run.groupIndex, + Proto: &r.proto, + Debugger: r.debugger, + Txn: &r.txnGroup[groupIndex], + TxnGroup: r.txnGroup, + GroupIndex: run.groupIndex, + PastSideEffects: run.pastSideEffects, } run.result.pass, run.result.err = run.eval(ep) @@ -541,10 +557,11 @@ func (r *LocalRunner) Run() (bool, error) { run := r.runs[0] ep := logic.EvalParams{ - Proto: &r.proto, - Txn: &r.txnGroup[groupIndex], - TxnGroup: r.txnGroup, - GroupIndex: run.groupIndex, + Proto: &r.proto, + Txn: &r.txnGroup[groupIndex], + TxnGroup: r.txnGroup, + GroupIndex: run.groupIndex, + PastSideEffects: run.pastSideEffects, } // Workaround for Go's nil/empty interfaces nil check after nil assignment, i.e. diff --git a/cmd/tealdbg/localLedger_test.go b/cmd/tealdbg/localLedger_test.go index c766ac1ce4..ae941723a2 100644 --- a/cmd/tealdbg/localLedger_test.go +++ b/cmd/tealdbg/localLedger_test.go @@ -107,11 +107,13 @@ int 2 a.NoError(err) proto := config.Consensus[protocol.ConsensusCurrentVersion] + pse := logic.MakePastSideEffects(1) ep := logic.EvalParams{ - Txn: &txn, - Proto: &proto, - TxnGroup: []transactions.SignedTxn{txn}, - GroupIndex: 0, + Txn: &txn, + Proto: &proto, + TxnGroup: []transactions.SignedTxn{txn}, + GroupIndex: 0, + PastSideEffects: pse, } pass, delta, err := ba.StatefulEval(ep, appIdx, program) a.NoError(err) diff --git a/cmd/tealdbg/server.go b/cmd/tealdbg/server.go index f207285948..43335921fc 100644 --- a/cmd/tealdbg/server.go +++ b/cmd/tealdbg/server.go @@ -25,6 +25,7 @@ import ( "strings" "time" + "github.com/algorand/go-algorand/data/transactions/logic" "github.com/algorand/websocket" "github.com/gorilla/mux" ) @@ -72,6 +73,7 @@ type DebugParams struct { Proto string TxnBlob []byte GroupIndex int + PastSideEffects []logic.EvalSideEffects BalanceBlob []byte DdrBlob []byte IndexerURL string diff --git a/daemon/algod/api/server/v2/dryrun.go b/daemon/algod/api/server/v2/dryrun.go index 0f0c64955a..27f52e6bcd 100644 --- a/daemon/algod/api/server/v2/dryrun.go +++ b/daemon/algod/api/server/v2/dryrun.go @@ -400,11 +400,13 @@ func doDryrunRequest(dr *DryrunRequest, response *generated.DryrunResponse) { response.Txns = make([]generated.DryrunTxnResult, len(dr.Txns)) for ti, stxn := range dr.Txns { + pse := logic.MakePastSideEffects(1) ep := logic.EvalParams{ - Txn: &stxn, - Proto: &proto, - TxnGroup: dr.Txns, - GroupIndex: ti, + Txn: &stxn, + Proto: &proto, + TxnGroup: dr.Txns, + GroupIndex: ti, + PastSideEffects: pse, } var result generated.DryrunTxnResult if len(stxn.Lsig.Logic) > 0 {