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

Add overriding name to method spec so it comes through in contract json #550

Merged
merged 9 commits into from
Oct 7, 2022
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Fixed
* Erroring on constructing an odd length hex string. ([#539](https://github.com/algorand/pyteal/pull/539))
* Incorrect behavior when overriding a method name ([#550](https://github.com/algorand/pyteal/pull/550))

# 0.18.1

Expand Down
15 changes: 15 additions & 0 deletions docs/abi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -682,6 +682,21 @@ Notice that even though the original :code:`get_account_status` function returns

The only exception to this transformation is if the subroutine has no return value. Without a return value, a :code:`ComputedValue` is unnecessary and the subroutine will still return an :code:`Expr` to the caller. In this case, the :code:`@ABIReturnSubroutine` decorator acts identically the :code:`@Subroutine` decorator.

The name of the subroutine constructed by the :code:`@ABIReturnSubroutine` decorator is by default the function name. In order to override the default subroutine name, the decorator :any:`ABIReturnSubroutine.name_override <ABIReturnSubroutine.name_override>` is introduced to construct a subroutine with its name overridden. An example is below:

.. code-block:: python

from pyteal import *

@ABIReturnSubroutine.name_override("increment")
def add_by_one(prev: abi.Uint32, *, output: abi.Uint32) -> Expr:
return output.set(prev.get() + Int(1))

# NOTE! In this case, the `ABIReturnSubroutine` is initialized with a name "increment"
# overriding its original name "add_by_one"
assert add_by_one.method_spec().dictify()["name"] == "increment"


Creating an ARC-4 Program
----------------------------------------------------

Expand Down
2 changes: 1 addition & 1 deletion pyteal/ast/router.py
Original file line number Diff line number Diff line change
Expand Up @@ -647,7 +647,7 @@ def method(
# - CallConfig.Never
# both cases evaluate to False in if statement.
def wrap(_func) -> ABIReturnSubroutine:
wrapped_subroutine = ABIReturnSubroutine(_func)
wrapped_subroutine = ABIReturnSubroutine(_func, overriding_name=name)
call_configs: MethodConfig
if (
no_op is None
Expand Down
37 changes: 37 additions & 0 deletions pyteal/ast/router_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -801,3 +801,40 @@ def clear_state_method():
assemble_helper(actual_clear_state_with_method)
== expected_clear_state_with_method
)


def test_override_names():
michaeldiamant marked this conversation as resolved.
Show resolved Hide resolved
r1 = pt.Router("test")

@r1.method(name="handle")
def handle_asa(deposit: pt.abi.AssetTransferTransaction):
"""handles the deposit where the input is an asset transfer"""
return pt.Assert(deposit.get().asset_amount() > pt.Int(0))

@r1.method(name="handle")
def handle_algo(deposit: pt.abi.PaymentTransaction):
"""handles the deposit where the input is a payment"""
return pt.Assert(deposit.get().amount() > pt.Int(0))

ap1, cs1, c1 = r1.compile_program(version=pt.compiler.MAX_PROGRAM_VERSION)
assert len(c1.methods) == 2
for meth in c1.methods:
dmeth = meth.dictify()
assert dmeth["name"] == "handle"

# Confirm an equivalent router definition _without_ `name` overrides produces the same output.
r2 = pt.Router("test")

@r2.method()
def handle(deposit: pt.abi.AssetTransferTransaction):
"""handles the deposit where the input is an asset transfer"""
return pt.Assert(deposit.get().asset_amount() > pt.Int(0))

@r2.method()
def handle(deposit: pt.abi.PaymentTransaction): # noqa: F811
"""handles the deposit where the input is a payment"""
return pt.Assert(deposit.get().amount() > pt.Int(0))

ap2, cs2, c2 = r2.compile_program(version=pt.compiler.MAX_PROGRAM_VERSION)

assert (ap1, cs1, c1) == (ap2, cs2, c2)
11 changes: 11 additions & 0 deletions pyteal/ast/subroutine.py
Original file line number Diff line number Diff line change
Expand Up @@ -519,16 +519,27 @@ def abi_sum(toSum: abi.DynamicArray[abi.Uint64], *, output: abi.Uint64) -> Expr:
def __init__(
self,
fn_implementation: Callable[..., Expr],
/,
*,
overriding_name: Optional[str] = None,
) -> None:
self.output_kwarg_info: Optional[OutputKwArgInfo] = self._get_output_kwarg_info(
fn_implementation
)
self.subroutine = SubroutineDefinition(
fn_implementation,
return_type=TealType.none,
name_str=overriding_name,
has_abi_output=self.output_kwarg_info is not None,
)

@staticmethod
def name_override(name: str) -> Callable[..., "ABIReturnSubroutine"]:
ahangsu marked this conversation as resolved.
Show resolved Hide resolved
def wrapper(fn_impl: Callable[..., Expr]) -> ABIReturnSubroutine:
return ABIReturnSubroutine(fn_impl, overriding_name=name)

return wrapper

@classmethod
def _get_output_kwarg_info(
cls, fn_implementation: Callable[..., Expr]
Expand Down
18 changes: 18 additions & 0 deletions pyteal/ast/subroutine_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1464,3 +1464,21 @@ def withdraw(amount: pt.abi.Uint64, recipient: pt.abi.Account):
== "An account who will receive the withdrawn Algos. This may or may not be the same as the method call sender."
)
assert "desc" not in mspec_dict["returns"]


def test_override_abi_method_name():
def abi_meth(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64):
return output.set(a.get() + b.get())

mspec = ABIReturnSubroutine(abi_meth).method_spec().dictify()
assert mspec["name"] == "abi_meth"

mspec = ABIReturnSubroutine(abi_meth, overriding_name="add").method_spec().dictify()
assert mspec["name"] == "add"

@ABIReturnSubroutine.name_override("overriden_add")
def abi_meth_2(a: pt.abi.Uint64, b: pt.abi.Uint64, *, output: pt.abi.Uint64):
return output.set(a.get() + b.get())

mspec = abi_meth_2.method_spec().dictify()
assert mspec["name"] == "overriden_add"