def __teal__(self, options: "CompileOptions"): if not isinstance(self.start, Int): return TernaryExpr( Op.replace3, (TealType.bytes, TealType.uint64, TealType.bytes), TealType.bytes, self.original, self.start, self.replacement, ).__teal__(options) op = self.__get_op(options) verifyProgramVersion( op.min_version, options.version, "Program version too low to use op {}".format(op), ) s = cast(Int, self.start).value if op == Op.replace2: return TealBlock.FromOp(options, TealOp(self, op, s), self.original, self.replacement) elif op == Op.replace3: return TealBlock.FromOp(options, TealOp(self, op), self.original, self.start, self.replacement)
def __teal__(self, options: "CompileOptions"): op = self.__get_op(options) verifyProgramVersion( op.min_version, options.version, "Program version too low to use op {}".format(op), ) if op == Op.extract: # if possible, exploit optimization in the extract opcode that takes the suffix # when the length argument is 0 return TealBlock.FromOp( options, TealOp(self, op, cast(Int, self.startArg).value, 0), self.stringArg, ) elif op == Op.substring3: strBlockStart, strBlockEnd = self.stringArg.__teal__(options) nextBlockStart, nextBlockEnd = self.startArg.__teal__(options) strBlockEnd.setNextBlock(nextBlockStart) finalBlock = TealSimpleBlock([ TealOp(self, Op.dig, 1), TealOp(self, Op.len), TealOp(self, Op.substring3), ]) nextBlockEnd.setNextBlock(finalBlock) return strBlockStart, finalBlock
def __teal__(self, options: "CompileOptions"): if not isinstance(self.startArg, Int) or not isinstance( self.lenArg, Int): return TernaryExpr( Op.extract3, (TealType.bytes, TealType.uint64, TealType.uint64), TealType.bytes, self.stringArg, self.startArg, self.lenArg, ).__teal__(options) op = self.__get_op(options) verifyProgramVersion( op.min_version, options.version, "Program version too low to use op {}".format(op), ) s, l = cast(Int, self.startArg).value, cast(Int, self.lenArg).value if op == Op.extract: return TealBlock.FromOp(options, TealOp(self, op, s, l), self.stringArg) elif op == Op.extract3: return TealBlock.FromOp( options, TealOp(self, op), self.stringArg, self.startArg, self.lenArg, )
def __teal__(self, options: "CompileOptions"): if len(self.cond) > 1: asserts: list[Expr] = [] for cond in self.cond: asrt = Assert(cond, comment=self.comment) asrt.trace = cond.trace asserts.append(asrt) return Seq(*asserts).__teal__(options) if options.version >= Op.assert_.min_version: # use assert op if available conds: list[Expr] = [self.cond[0]] if self.comment is not None: conds.append(Comment(self.comment)) return TealBlock.FromOp(options, TealOp(self, Op.assert_), *conds) # if assert op is not available, use branches and err condStart, condEnd = self.cond[0].__teal__(options) end = TealSimpleBlock([]) errBlock = TealSimpleBlock([TealOp(self, Op.err)]) branchBlock = TealConditionalBlock([]) branchBlock.setTrueBlock(end) branchBlock.setFalseBlock(errBlock) condEnd.setNextBlock(branchBlock) return condStart, end
def __teal__(self, options: "CompileOptions"): from pyteal.ir import TealOp, Op, TealBlock if self.index_expression is not None: op = TealOp(self, Op.loads) return TealBlock.FromOp(options, op, self.index_expression) s = cast(ScratchSlot, self.slot) op = TealOp(self, Op.load, s) return TealBlock.FromOp(options, op)
def __teal__(self, options: "CompileOptions"): from pyteal.ir import TealOp, Op, TealBlock if self.index_expression is not None: op = TealOp(self, Op.stores) return TealBlock.FromOp(options, op, self.index_expression, self.value) if not isinstance(self.slot, ScratchSlot): raise TealInternalError( "cannot handle slot of type {}".format(type(self.slot)) ) op = TealOp(self, Op.store, self.slot) return TealBlock.FromOp(options, op, self.value)
def __teal__(self, options: "CompileOptions"): verifyProgramVersion( Op.gaid.min_version, options.version, "Program version too low to use Gaid expression", ) if type(self.txnIndex) is int: op = TealOp(self, Op.gaid, self.txnIndex) return TealBlock.FromOp(options, op) op = TealOp(self, Op.gaids) return TealBlock.FromOp(options, op, cast(Expr, self.txnIndex))
def __teal__(self, options: "CompileOptions"): if type(self.index) is int: op = TealOp(self, Op.arg, self.index) return TealBlock.FromOp(options, op) verifyProgramVersion( Op.args.min_version, options.version, "Program version too low to use dynamic indexes with Arg", ) op = TealOp(self, Op.args) return TealBlock.FromOp(options, op, cast(Expr, self.index))
def __teal__(self, options: "CompileOptions"): verifyProgramVersion( self.op.min_version, options.version, "Program version too low to use op {}".format(self.op), ) return TealBlock.FromOp(options, TealOp(self, self.op), self.arg)
def __teal__(self, options: "CompileOptions"): if self.base == "utf8": payload = self.byte_str elif self.base == "base16": payload = "0x" + self.byte_str else: payload = "{}({})".format(self.base, self.byte_str) op = TealOp(self, Op.byte, payload) return TealBlock.FromOp(options, op)
def __teal__(self, options: "CompileOptions"): verifyProgramVersion( Op.itxn_field.min_version, options.version, "Program version too low to create inner transactions", ) return TealBlock.FromOp( options, TealOp(self, Op.itxn_field, self.field.arg_name), self.value )
def __teal__(self, options: "CompileOptions"): op = self.action.value verifyProgramVersion( op.min_version, options.version, "Program version too low to create inner transactions", ) return TealBlock.FromOp(options, TealOp(self, op))
def __teal__(self, options: "CompileOptions"): verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version) verifyProgramVersion( self.op.min_version, options.version, "Program version too low to use op {}".format(self.op), ) op = TealOp(self, self.op, self.field.arg_name) return TealBlock.FromOp(options, op)
def __teal__(self, options: "CompileOptions"): verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version) opToUse = self.staticOp if type(self.index) is int else self.dynamicOp if opToUse is None: raise TealCompileError("Dynamic array indexing not supported", self) verifyProgramVersion( opToUse.min_version, options.version, "Program version too low to use op {}".format(opToUse), ) if type(self.index) is int: op = TealOp(self, opToUse, self.field.arg_name, self.index) return TealBlock.FromOp(options, op) op = TealOp(self, opToUse, self.field.arg_name) return TealBlock.FromOp(options, op, cast(Expr, self.index))
def __teal__(self, options: "CompileOptions"): verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version) verifyProgramVersion( Op.gitxn.min_version, options.version, "Program version too low to use gitxn", ) op = TealOp(self, Op.gitxn, self.txnIndex, self.field.arg_name) return TealBlock.FromOp(options, op)
def __teal__(self, options: "CompileOptions"): verifyProgramVersion( Op.json_ref.min_version, options.version, "Program version too low to use op json_ref", ) verifyFieldVersion(self.type.arg_name, self.type.min_version, options.version) op = TealOp(self, Op.json_ref, self.type.arg_name) return TealBlock.FromOp(options, op, self.json_obj, self.key)
def __teal__(self, options: "CompileOptions"): verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version) if type(self.index) is int: opToUse = Op.gitxna else: opToUse = Op.gitxnas verifyProgramVersion( opToUse.min_version, options.version, "Program version too low to use op {}".format(opToUse), ) if type(self.index) is int: op = TealOp(self, opToUse, self.txnIndex, self.field.arg_name, self.index) return TealBlock.FromOp(options, op) op = TealOp(self, opToUse, self.txnIndex, self.field.arg_name) return TealBlock.FromOp(options, op, cast(Expr, self.index))
def __teal__(self, options: "CompileOptions"): verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version) if type(self.txnIndex) is int: verifyProgramVersion( Op.gtxn.min_version, options.version, "Program version too low to use gtxn", ) op = TealOp(self, Op.gtxn, self.txnIndex, self.field.arg_name) return TealBlock.FromOp(options, op) verifyProgramVersion( Op.gtxns.min_version, options.version, "Program version too low to index Gtxn with dynamic values", ) op = TealOp(self, Op.gtxns, self.field.arg_name) return TealBlock.FromOp(options, op, cast(Expr, self.txnIndex))
def __teal__(self, options: "CompileOptions"): if options.version < Op.cover.min_version: raise TealCompileError( "WideRatio requires program version {} or higher".format( Op.cover.min_version ), self, ) numStart, numEnd = multiplyFactors(self, self.numeratorFactors, options) denomStart, denomEnd = multiplyFactors(self, self.denominatorFactors, options) numEnd.setNextBlock(denomStart) combine = TealSimpleBlock( [ TealOp(self, Op.divmodw), TealOp(self, Op.pop), # pop remainder low word TealOp(self, Op.pop), # pop remainder high word TealOp(self, Op.swap), # swap quotient high and low words TealOp(self, Op.logic_not), TealOp(self, Op.assert_), # assert quotient high word is 0 # end with quotient low word remaining on the stack ] ) denomEnd.setNextBlock(combine) return numStart, combine
def __teal__(self, options: "CompileOptions"): start = None end = None for i, arg in enumerate(self.args): argStart, argEnd = arg.__teal__(options) if i == 0: start = argStart end = argEnd else: cast(TealSimpleBlock, end).setNextBlock(argStart) opBlock = TealSimpleBlock([TealOp(self, self.op)]) argEnd.setNextBlock(opBlock) end = opBlock return start, end
def __teal__(self, options: "CompileOptions"): self.compile_check(options) tealOp = TealOp(self, self.op, *self.immediate_args) callStart, callEnd = TealBlock.FromOp(options, tealOp, *self.args) curEnd = callEnd # the list is reversed in order to preserve the ordering of the opcode's returned # values. ie the output to stack [A, B, C] should correspond to C->output_slots[2] # B->output_slots[1], and A->output_slots[0]. for slot in reversed(self.output_slots): store = slot.store() storeStart, storeEnd = store.__teal__(options) curEnd.setNextBlock(storeStart) curEnd = storeEnd return callStart, curEnd
def __teal__(self, options: "CompileOptions"): if options.currentSubroutine is not None: verifyProgramVersion( Op.retsub.min_version, options.version, "Program version too low to use subroutines", ) returnType = options.currentSubroutine.return_type if returnType == TealType.none: if self.value is not None: raise TealCompileError( "Cannot return a value from a subroutine with return type TealType.none", self, ) else: if self.value is None: raise TealCompileError( "A subroutine declares it returns a value, but no value is being returned", self, ) actualType = self.value.type_of() if not types_match(actualType, returnType): raise TealCompileError( "Incompatible return type from subroutine, expected {} but got {}" .format(returnType, actualType), self, ) op = Op.retsub else: if self.value is None: raise TealCompileError( "Return from main program must have an argument", self) actualType = self.value.type_of() if not types_match(actualType, TealType.uint64): raise TealCompileError( "Incompatible return type from main program, expected {} but got {}" .format(TealType.uint64, actualType), self, ) op = Op.return_ args = [] if self.value is None else [self.value] return TealBlock.FromOp(options, TealOp(self, op), *args)
def __teal__(self, options: "CompileOptions"): """ Generate the subroutine's start and end teal blocks. The subroutine's arguments are pushed on the stack to be picked up into local scratch variables. There are 4 cases to consider for the pushed arg expression: 1. (by-value) In the case of typical arguments of type Expr, the expression ITSELF is evaluated for the stack and will be stored in a local ScratchVar for subroutine evaluation 2. (by-reference) In the case of a by-reference argument of type ScratchVar, its SLOT INDEX is put on the stack and will be stored in a local DynamicScratchVar for subroutine evaluation 3. (ABI, or a special case in by-value) In this case, the storage of an ABI value are loaded to the stack and will be stored in a local ABI value for subroutine evaluation 4. (ABI output keyword argument) In this case, we do not place ABI values (encoding) on the stack. This is an *output-only* argument: in `evaluate_subroutine` an ABI typed instance for subroutine evaluation will be generated, and gets in to construct the subroutine implementation. """ verifyProgramVersion( Op.callsub.min_version, options.version, "Program version too low to use SubroutineCall expression", ) def handle_arg(arg: Expr | ScratchVar | abi.BaseType) -> Expr: if isinstance(arg, ScratchVar): return arg.index() elif isinstance(arg, Expr): return arg elif isinstance(arg, abi.BaseType): return arg.stored_value.load() else: raise TealInputError( f"cannot handle current arg: {arg} to put it on stack" ) op = TealOp(self, Op.callsub, self.subroutine) return TealBlock.FromOp(options, op, *[handle_arg(x) for x in self.args])
def __teal__(self, options: "CompileOptions"): op = TealOp(self, Op.err) return TealBlock.FromOp(options, op)
def __teal__(self, options: "CompileOptions"): return TealBlock.FromOp(options, TealOp(self, self.field.get_op()), *self.args)
def spillLocalSlotsDuringRecursion( version: int, subroutineMapping: Dict[Optional[SubroutineDefinition], List[TealComponent]], subroutineGraph: Dict[SubroutineDefinition, Set[SubroutineDefinition]], localSlots: Dict[Optional[SubroutineDefinition], Set[int]], ) -> None: """In order to prevent recursion from modifying the local scratch slots a subroutine uses, subroutines must "spill" their local slots to the stack before calling any other subroutine which may invoke the calling subroutine. "Spill to stack" means loading all local slots onto the stack, invoking the subroutine which may result in recursion, then restoring all local slots from the stack. This prevents the local slots from being modifying by a new recursive invocation of the current subroutine. Args: version: The current program version being assembled. subroutineMapping: A dictionary containing a list of TealComponents for every subroutine in a program. The key None is taken to indicate the main program routine. This input may be modified by this function in order to spill subroutine slots. subroutineGraph: A graph of subroutines. Each key is a subroutine (the main routine should not be present), which represents a node in the graph. Each value is a set of all subroutines that specific subroutine calls, which represent directional edges in the graph. localSlots: The output from the function `assignScratchSlotsToSubroutines`, which indicates the local slots which must be spilled for each subroutine. """ recursivePoints = findRecursionPoints(subroutineGraph) recursive_byref = None for k, v in recursivePoints.items(): if v and k.by_ref_args: recursive_byref = k break if recursive_byref: msg = "ScratchVar arguments not allowed in recursive subroutines, but a recursive call-path was detected: {}()" raise TealInputError( msg.format("()-->".join(f.name() for f in find_recursive_path( subroutineGraph, recursive_byref)))) coverAvailable = version >= Op.cover.min_version for subroutine, reentryPoints in recursivePoints.items(): slots = list(sorted(slot for slot in localSlots[subroutine])) if len(reentryPoints) == 0 or len(slots) == 0: # no need to spill slots continue ops = subroutineMapping[subroutine] newOps: List[TealComponent] = [] for stmt in ops: before: List[TealComponent] = [] after: List[TealComponent] = [] calledSubroutines = stmt.getSubroutines() # the only opcode that references subroutines is callsub, and it should only ever # reference one subroutine at a time assert ( len(calledSubroutines) <= 1 ), "Multiple subroutines are called from the same TealComponent" reentrySubroutineCalls = list( reentryPoints.intersection(calledSubroutines)) if len(reentrySubroutineCalls) != 0: # A subroutine is being called which may reenter the current subroutine, so insert # ops to spill local slots to the stack before calling the subroutine and also to # restore the local slots after returning from the subroutine. This prevents a # reentry into the current subroutine from modifying variables we are currently # using. # reentrySubroutineCalls should have a length of 1, since calledSubroutines has a # maximum length of 1 reentrySubroutineCall = reentrySubroutineCalls[0] numArgs = reentrySubroutineCall.argument_count() digArgs = True coverSpilledSlots = False uncoverArgs = False if coverAvailable: digArgs = False if len(slots) < numArgs: coverSpilledSlots = True else: uncoverArgs = True for slot in slots: # spill local slots to the stack before.append(TealOp(None, Op.load, slot)) if coverSpilledSlots: # numArgs is guaranteed to be at least 2 here (since numArgs > len(slots) # and len(slots) must be at least 1 for the code to get this far), so no # need to replace this with swap if numArgs is 1 before.append(TealOp(None, Op.cover, numArgs)) for _ in range(numArgs): # pull the subroutine arguments to the top of the stack, above the just spilled # local slots, if needed stackDistance = len(slots) + numArgs - 1 if uncoverArgs: if stackDistance == 1: before.append(TealOp(None, Op.swap)) else: before.append( TealOp(None, Op.uncover, stackDistance)) if digArgs: before.append(TealOp( None, Op.dig, stackDistance, )) # because we are stuck using dig instead of uncover in AVM 4, we'll need to # pop all of the dug up arguments after the function returns hideReturnValueInFirstSlot = False if subroutine.return_type != TealType.none: # if the subroutine returns a value on the stack, we need to preserve this after # restoring all local slots. if len(slots) == 1: after.append(TealOp(None, Op.swap)) elif coverAvailable: after.append(TealOp(None, Op.cover, len(slots))) else: # Store the return value into slots[0] temporarily hideReturnValueInFirstSlot = True after.append(TealOp(None, Op.store, slots[0])) for slot in slots[::-1]: # restore slots, iterating in reverse because slots[-1] is at the top of the stack if hideReturnValueInFirstSlot and slot is slots[0]: # time to restore the return value to the top of the stack # slots[0] is being used to store the return value, so load it again after.append(TealOp(None, Op.load, slot)) # swap the return value with the actual value of slot[0] on the stack after.append(TealOp(None, Op.swap)) after.append(TealOp(None, Op.store, slot)) if digArgs: for _ in range(numArgs): # clear out the duplicate arguments that were dug up previously, since dig # does not pop the dug values -- once we use cover/uncover to properly set up # the spilled slots, this will no longer be necessary if subroutine.return_type != TealType.none: # if there is a return value on top of the stack, we need to preserve # it, so swap it with the subroutine argument that's below it on the # stack after.append(TealOp(None, Op.swap)) after.append(TealOp(None, Op.pop)) newOps += before newOps.append(stmt) newOps += after subroutineMapping[subroutine] = newOps
def __teal__(self, options: "CompileOptions"): return TealBlock.FromOp(options, TealOp(self, Op.return_), self.success)
def __teal__(self, options: "CompileOptions"): verifyFieldVersion(self.field.arg_name, self.field.min_version, options.version) op = TealOp(self, Op.global_, self.field.arg_name) return TealBlock.FromOp(options, op)
def __teal__( self, options: "CompileOptions") -> Tuple[TealBlock, TealSimpleBlock]: op = TealOp(self, Op.comment, self.comment) return TealBlock.FromOp(options, op)
def __teal__(self, options: "CompileOptions"): op = TealOp(self, self.op, self.name) return TealBlock.FromOp(options, op)