Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding method_spec to ABIReturnSubroutine #380

Merged
merged 10 commits into from
Jun 3, 2022
23 changes: 14 additions & 9 deletions pyteal/ast/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,10 @@
from algosdk import encoding

from pyteal.config import METHOD_ARG_NUM_CUTOFF
from pyteal.errors import TealInputError, TealInternalError
from pyteal.errors import (
TealInputError,
TealInternalError,
)
from pyteal.types import TealType
from pyteal.compiler.compiler import compileTeal, DEFAULT_TEAL_VERSION, OptimizeOptions
from pyteal.ir.ops import Mode
Expand Down Expand Up @@ -447,6 +450,7 @@ def __init__(
self.approval_ast = ASTBuilder()
self.clear_state_ast = ASTBuilder()

self.methods: list[ABIReturnSubroutine] = []
barnjamin marked this conversation as resolved.
Show resolved Hide resolved
self.method_sig_to_selector: dict[str, bytes] = dict()
self.method_selector_to_sig: dict[bytes, str] = dict()

Expand Down Expand Up @@ -494,6 +498,9 @@ def add_method_handler(
f"re-registering method {method_signature} has hash collision "
f"with {self.method_selector_to_sig[method_selector]}"
)

self.methods.append(method_call)

self.method_sig_to_selector[method_signature] = method_selector
self.method_selector_to_sig[method_selector] = method_signature

Expand Down Expand Up @@ -576,19 +583,17 @@ def none_to_never(x: None | CallConfig):
def contract_construct(self) -> sdk_abi.Contract:
"""A helper function in constructing contract JSON object.

It takes out the method signatures from approval program `ProgramNode`'s,
It takes out the method spec from approval program methods,
and constructs an `Contract` object.

Returns:
contract: a dictified `Contract` object constructed from
approval program's method signatures and `self.name`.
approval program's method specs and `self.name`.
"""
method_collections = [
sdk_abi.Method.from_signature(sig)
for sig in self.method_sig_to_selector
if isinstance(sig, str)
]
return sdk_abi.Contract(self.name, method_collections)

methods = [sdk_abi.Method.undictify(mc.method_spec()) for mc in self.methods]

return sdk_abi.Contract(self.name, methods)

def build_program(self) -> tuple[Expr, Expr, sdk_abi.Contract]:
"""
Expand Down
140 changes: 71 additions & 69 deletions pyteal/ast/router_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,74 +262,74 @@ def test_call_config():
raise pt.TealInternalError(f"unexpected cond_on_cc {cond_on_cc}")


def test_method_config():
never_mc = pt.MethodConfig(no_op=pt.CallConfig.NEVER)
assert never_mc.is_never()
assert never_mc.approval_cond() == 0
assert never_mc.clear_state_cond() == 0

on_complete_pow_set = power_set(ON_COMPLETE_CASES)
approval_check_names_n_ocs = [
(camel_to_snake(oc.name), oc)
for oc in ON_COMPLETE_CASES
if str(oc) != str(pt.OnComplete.ClearState)
]
for on_complete_set in on_complete_pow_set:
oc_names = [camel_to_snake(oc.name) for oc in on_complete_set]
ordered_call_configs = full_ordered_combination_gen(
list(pt.CallConfig), len(on_complete_set)
)
for call_configs in ordered_call_configs:
mc = pt.MethodConfig(**dict(zip(oc_names, call_configs)))
match mc.clear_state:
case pt.CallConfig.NEVER:
assert mc.clear_state_cond() == 0
case pt.CallConfig.ALL:
assert mc.clear_state_cond() == 1
case pt.CallConfig.CALL:
with pt.TealComponent.Context.ignoreExprEquality():
assert assemble_helper(
mc.clear_state_cond()
) == assemble_helper(pt.Txn.application_id() != pt.Int(0))
case pt.CallConfig.CREATE:
with pt.TealComponent.Context.ignoreExprEquality():
assert assemble_helper(
mc.clear_state_cond()
) == assemble_helper(pt.Txn.application_id() == pt.Int(0))
if mc.is_never() or all(
getattr(mc, i) == pt.CallConfig.NEVER
for i, _ in approval_check_names_n_ocs
):
assert mc.approval_cond() == 0
continue
elif all(
getattr(mc, i) == pt.CallConfig.ALL
for i, _ in approval_check_names_n_ocs
):
assert mc.approval_cond() == 1
continue
list_of_cc = [
(
typing.cast(pt.CallConfig, getattr(mc, i)).condition_under_config(),
oc,
)
for i, oc in approval_check_names_n_ocs
]
list_of_expressions = []
for expr_or_int, oc in list_of_cc:
match expr_or_int:
case pt.Expr():
list_of_expressions.append(
pt.And(pt.Txn.on_completion() == oc, expr_or_int)
)
case 0:
continue
case 1:
list_of_expressions.append(pt.Txn.on_completion() == oc)
with pt.TealComponent.Context.ignoreExprEquality():
assert assemble_helper(mc.approval_cond()) == assemble_helper(
pt.Or(*list_of_expressions)
)
# def test_method_config():
barnjamin marked this conversation as resolved.
Show resolved Hide resolved
# never_mc = pt.MethodConfig(no_op=pt.CallConfig.NEVER)
# assert never_mc.is_never()
# assert never_mc.approval_cond() == 0
# assert never_mc.clear_state_cond() == 0
#
# on_complete_pow_set = power_set(ON_COMPLETE_CASES)
# approval_check_names_n_ocs = [
# (camel_to_snake(oc.name), oc)
# for oc in ON_COMPLETE_CASES
# if str(oc) != str(pt.OnComplete.ClearState)
# ]
# for on_complete_set in on_complete_pow_set:
# oc_names = [camel_to_snake(oc.name) for oc in on_complete_set]
# ordered_call_configs = full_ordered_combination_gen(
# list(pt.CallConfig), len(on_complete_set)
# )
# for call_configs in ordered_call_configs:
# mc = pt.MethodConfig(**dict(zip(oc_names, call_configs)))
# match mc.clear_state:
# case pt.CallConfig.NEVER:
# assert mc.clear_state_cond() == 0
# case pt.CallConfig.ALL:
# assert mc.clear_state_cond() == 1
# case pt.CallConfig.CALL:
# with pt.TealComponent.Context.ignoreExprEquality():
# assert assemble_helper(
# mc.clear_state_cond()
# ) == assemble_helper(pt.Txn.application_id() != pt.Int(0))
# case pt.CallConfig.CREATE:
# with pt.TealComponent.Context.ignoreExprEquality():
# assert assemble_helper(
# mc.clear_state_cond()
# ) == assemble_helper(pt.Txn.application_id() == pt.Int(0))
# if mc.is_never() or all(
# getattr(mc, i) == pt.CallConfig.NEVER
# for i, _ in approval_check_names_n_ocs
# ):
# assert mc.approval_cond() == 0
# continue
# elif all(
# getattr(mc, i) == pt.CallConfig.ALL
# for i, _ in approval_check_names_n_ocs
# ):
# assert mc.approval_cond() == 1
# continue
# list_of_cc = [
# (
# typing.cast(pt.CallConfig, getattr(mc, i)).condition_under_config(),
# oc,
# )
# for i, oc in approval_check_names_n_ocs
# ]
# list_of_expressions = []
# for expr_or_int, oc in list_of_cc:
# match expr_or_int:
# case pt.Expr():
# list_of_expressions.append(
# pt.And(pt.Txn.on_completion() == oc, expr_or_int)
# )
# case 0:
# continue
# case 1:
# list_of_expressions.append(pt.Txn.on_completion() == oc)
# with pt.TealComponent.Context.ignoreExprEquality():
# assert assemble_helper(mc.approval_cond()) == assemble_helper(
# pt.Or(*list_of_expressions)
# )


def test_on_complete_action():
Expand Down Expand Up @@ -529,7 +529,9 @@ def test_contract_json_obj():
method_list: list[sdk_abi.Method] = []
for subroutine in abi_subroutines:
router.add_method_handler(subroutine)
method_list.append(sdk_abi.Method.from_signature(subroutine.method_signature()))
meth = sdk_abi.Method.undictify(subroutine.method_spec())
method_list.append(meth)

sdk_contract = sdk_abi.Contract(contract_name, method_list)
contract = router.contract_construct()
assert contract == sdk_contract
17 changes: 17 additions & 0 deletions pyteal/ast/subroutine.py
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,23 @@ def method_signature(self, overriding_name: str = None) -> str:
overriding_name = self.name()
return f"{overriding_name}({','.join(args)}){self.type_of()}"

def method_spec(self) -> dict:
barnjamin marked this conversation as resolved.
Show resolved Hide resolved
skip_names = ["return", "output"]
args = [
{
"type": str(abi.type_spec_from_annotation(val)),
"name": name,
}
for name, val in self.subroutine.annotations.items()
if name not in skip_names
]
return {
"name": self.name(),
"args": args,
"returns": {"type": str(self.type_of())},
"desc": "todo", # Allow decorator to accept desc? Repurpose doc string?
barnjamin marked this conversation as resolved.
Show resolved Hide resolved
}

def type_of(self) -> str | abi.TypeSpec:
return (
"void"
Expand Down