Esempio n. 1
0
    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)
Esempio n. 2
0
    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
Esempio n. 3
0
    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,
            )
Esempio n. 4
0
    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
Esempio n. 5
0
    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)
Esempio n. 6
0
    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)
Esempio n. 7
0
    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))
Esempio n. 8
0
    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))
Esempio n. 9
0
    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)
Esempio n. 10
0
 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)
Esempio n. 11
0
    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
        )
Esempio n. 12
0
    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))
Esempio n. 13
0
    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)
Esempio n. 14
0
    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))
Esempio n. 15
0
    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)
Esempio n. 16
0
    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)
Esempio n. 17
0
    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))
Esempio n. 18
0
    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))
Esempio n. 19
0
    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
Esempio n. 20
0
    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
Esempio n. 21
0
    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
Esempio n. 22
0
    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)
Esempio n. 23
0
    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])
Esempio n. 24
0
 def __teal__(self, options: "CompileOptions"):
     op = TealOp(self, Op.err)
     return TealBlock.FromOp(options, op)
Esempio n. 25
0
 def __teal__(self, options: "CompileOptions"):
     return TealBlock.FromOp(options, TealOp(self, self.field.get_op()),
                             *self.args)
Esempio n. 26
0
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
Esempio n. 27
0
 def __teal__(self, options: "CompileOptions"):
     return TealBlock.FromOp(options, TealOp(self, Op.return_),
                             self.success)
Esempio n. 28
0
    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)
Esempio n. 29
0
 def __teal__(
         self,
         options: "CompileOptions") -> Tuple[TealBlock, TealSimpleBlock]:
     op = TealOp(self, Op.comment, self.comment)
     return TealBlock.FromOp(options, op)
Esempio n. 30
0
 def __teal__(self, options: "CompileOptions"):
     op = TealOp(self, self.op, self.name)
     return TealBlock.FromOp(options, op)