Source code for beaker.decorators

from collections.abc import Callable
from functools import wraps
from typing import ParamSpec, TypeVar

from pyteal import (
    And,
    App,
    Assert,
    AssetHolding,
    Expr,
    Global,
    Int,
    Seq,
    SubroutineFnWrapper,
    TealType,
    Txn,
)
from pyteal.types import require_type

__all__ = [
    "Authorize",
    "authorize",
    "AuthCallable",
]


AuthCallable = Callable[[Expr], Expr]
"""A function that takes Txn.sender() and returns a condition to assert"""


# TODO: refactor this to be more of an Expr builder so it becomes composable
class Authorize:
    """
    Authorize contains methods that may be used as values to
    the `authorize` keyword of the `handle` decorator
    """

    @classmethod
    def only_creator(cls) -> AuthCallable:
        """require that the sender of the app call match exactly the address of the app's creator"""
        return cls.only(Global.creator_address())

[docs] @staticmethod def only(addr: Expr) -> AuthCallable: """require that the sender of the app call match exactly the address passed""" require_type(addr, TealType.bytes) return lambda sender: sender == addr
[docs] @staticmethod def holds_token(asset_id: Expr) -> AuthCallable: """require that the sender of the app call holds >0 of the asset id passed""" require_type(asset_id, TealType.uint64) return lambda sender: Seq( bal := AssetHolding.balance(sender, asset_id), And(bal.hasValue(), bal.value() > Int(0)), )
[docs] @staticmethod def opted_in( app_id: Expr = Global.current_application_id(), # noqa: B008 ) -> AuthCallable: """require that the sender of the app call has already opted-in to a given app id""" require_type(app_id, TealType.uint64) return lambda sender: App.optedIn(sender, app_id)
HandlerReturn = TypeVar("HandlerReturn", bound=Expr) HandlerParams = ParamSpec("HandlerParams") def authorize( allowed: AuthCallable | SubroutineFnWrapper, ) -> Callable[[Callable[HandlerParams, HandlerReturn]], Callable[HandlerParams, Expr]]: def decorator( fn: Callable[HandlerParams, HandlerReturn] ) -> Callable[HandlerParams, Expr]: @wraps(fn) def wrapped(*args: HandlerParams.args, **kwargs: HandlerParams.kwargs) -> Expr: return Seq( Assert(allowed(Txn.sender()), comment="unauthorized"), fn(*args, **kwargs), ) return wrapped return decorator