Skip to content

Commit

Permalink
add ExecuteMethod helper (#501)
Browse files Browse the repository at this point in the history
* add ExecuteMethodCall helper
  • Loading branch information
barnjamin committed Aug 11, 2022
1 parent d3f688b commit a93c9db
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 237 deletions.
63 changes: 60 additions & 3 deletions pyteal/ast/itxn.py
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ def Execute(cls, fields: dict[TxnField, Expr | list[Expr]]) -> Expr:
InnerTxnBuilder.Begin()
InnerTxnBuilder.SetFields(fields)
InnerTxnBuilder.End()
InnerTxnBuilder.Submit()
Requires program version 5 or higher. This operation is only permitted in application mode.
Expand Down Expand Up @@ -249,14 +249,71 @@ def SetFields(cls, fields: dict[TxnField, Expr | list[Expr]]) -> Expr:
fieldsToSet = [cls.SetField(field, value) for field, value in fields.items()]
return Seq(fieldsToSet)

@classmethod
def ExecuteMethodCall(
cls,
*,
app_id: Expr,
method_signature: str,
args: list[abi.BaseType | Expr | dict[TxnField, Expr | list[Expr]]],
extra_fields: dict[TxnField, Expr | list[Expr]] = None,
) -> Expr:
"""Performs a single app call transaction formatted as an ABI method call.
A convenience method that accepts fields to submit a single inner transaction, which is equivalent to:
.. code-block:: python
InnerTxnBuilder.Begin()
InnerTxnBuilder.MethodCall(
app_id=app_id,
method_signature=method_signature,
args=args,
extra_fields=extra_fields,
),
InnerTxnBuilder.Submit()
Requires program version 5 or higher. This operation is only permitted in application mode.
Args:
app_id: An expression that evaluates to a `TealType.uint64` corresponding to the application being called.
method_signature: A string representing the method signature of the method we're calling. This is used to do
type checking on the arguments passed and to create the method selector passed as the first argument.
args: A list of arguments to pass to the application. The values in this list depend on the kind of argument you wish to pass:
- For basic ABI arguments (not Reference or Transaction types):
If an ABI type is passed it **MUST** match the type specified in the `method_signature`. If an Expr is passed it must evaluate to `TealType.bytes` but beyond that no type checking is performed.
- For Reference arguments:
Either the Reference type or an Expr that returns the type corresponding to the reference type are allowed.
(i.e. Asset is TealType.uint64, Application is TealType.uint64, Account is TealType.bytes)
- For Transaction arguments:
A dictionary containing TxnField to Expr that describe Transactions to be pre-pended to the transaction group being constructed. The `TxnField.type_enum` key MUST be set and MUST match the expected transaction type specified in the `method_signature`.
extra_fields (optional): A dictionary whose keys are fields to set and whose values are the value each
field should take. Each value must evaluate to a type that is compatible with the
field being set. These fields are set on the ApplicationCallTransaction being constructed
"""
return Seq(
cls.Begin(),
cls.MethodCall(
app_id=app_id,
method_signature=method_signature,
args=args,
extra_fields=extra_fields,
),
cls.Submit(),
)

@classmethod
def MethodCall(
cls,
*,
app_id: Expr,
method_signature: str,
args: list[abi.BaseType | Expr | dict[TxnField, Expr | list[Expr]]],
extra_fields: dict[TxnField, Expr | list[Expr]] = {},
extra_fields: dict[TxnField, Expr | list[Expr]] = None,
) -> Expr:
"""Adds an ABI method call transaction to the current inner transaction group.
Expand Down Expand Up @@ -425,7 +482,7 @@ def MethodCall(
# Set the fields for the app call in app args and foreign arrays
*fields_to_set,
# Add any remaining fields specified by the user
InnerTxnBuilder.SetFields(extra_fields),
InnerTxnBuilder.SetFields({} if extra_fields is None else extra_fields),
)


Expand Down
264 changes: 30 additions & 234 deletions pyteal/ast/itxn_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -435,8 +435,16 @@ def test_InnerTxnBuilder_method_call(
args=args,
extra_fields=extra_fields,
)
with pytest.raises(expected_error):
pt.InnerTxnBuilder.ExecuteMethodCall(
app_id=app_id,
method_signature=sig,
args=args,
extra_fields=extra_fields,
)
return

# First run the test with MethodCall
expr: pt.Expr = pt.InnerTxnBuilder.MethodCall(
app_id=app_id, method_signature=sig, args=args, extra_fields=extra_fields
)
Expand All @@ -458,241 +466,29 @@ def test_InnerTxnBuilder_method_call(
pt.TealBlock.GetReferencedScratchSlots(expected),
)

# Now run the same test with ExecuteMethodCall
expr = pt.InnerTxnBuilder.ExecuteMethodCall(
app_id=app_id, method_signature=sig, args=args, extra_fields=extra_fields
)
assert expr.type_of() == pt.TealType.none
assert not expr.has_return()

ITXN_METHOD_CASES = (
(
pt.Int(1),
"add(uint64,uint64)void",
[t1_1 := pt.Itob(pt.Int(1)), t1_2 := pt.Itob(pt.Int(1))],
{TxnField.fee: pt.Int(0)},
pt.Seq(
pt.InnerTxnBuilder.SetFields(
{
pt.TxnField.type_enum: TxnType.ApplicationCall,
pt.TxnField.application_id: pt.Int(1),
pt.TxnField.application_args: [
pt.MethodSignature("add(uint64,uint64)void"),
t1_1,
t1_2,
],
pt.TxnField.fee: pt.Int(0),
}
),
),
None,
),
(
pt.Int(1),
"add(uint64,uint64)void",
[t2_1 := pt.abi.Uint64(), t2_2 := pt.abi.Uint64()],
{TxnField.fee: pt.Int(0)},
pt.Seq(
pt.InnerTxnBuilder.SetFields(
{
pt.TxnField.type_enum: TxnType.ApplicationCall,
pt.TxnField.application_id: pt.Int(1),
pt.TxnField.application_args: [
pt.MethodSignature("add(uint64,uint64)void"),
t2_1.encode(),
t2_2.encode(),
],
pt.TxnField.fee: pt.Int(0),
}
),
),
None,
),
(
pt.Int(1),
"add(application,account,asset)void",
[
t3_1 := pt.abi.Application(),
t3_2 := pt.abi.Account(),
t3_3 := pt.abi.Asset(),
],
{TxnField.fee: pt.Int(0)},
pt.Seq(
pt.InnerTxnBuilder.SetFields(
{
pt.TxnField.type_enum: TxnType.ApplicationCall,
pt.TxnField.application_id: pt.Int(1),
pt.TxnField.accounts: [t3_2.address()],
pt.TxnField.applications: [t3_1.application_id()],
pt.TxnField.assets: [t3_3.asset_id()],
pt.TxnField.application_args: [
pt.MethodSignature("add(application,account,asset)void"),
pt.Bytes(b"\x01"),
pt.Bytes(b"\x01"),
pt.Bytes(b"\x00"),
],
pt.TxnField.fee: pt.Int(0),
}
),
),
None,
),
(
pt.Int(1),
"add(application,account,asset)void",
[
t4_1 := pt.Int(1),
t4_2 := pt.Global.zero_address(),
t4_3 := pt.Int(2),
],
{TxnField.fee: pt.Int(0)},
pt.Seq(
pt.InnerTxnBuilder.SetFields(
{
pt.TxnField.type_enum: TxnType.ApplicationCall,
pt.TxnField.application_id: pt.Int(1),
pt.TxnField.accounts: [t4_2],
pt.TxnField.applications: [t4_1],
pt.TxnField.assets: [t4_3],
pt.TxnField.application_args: [
pt.MethodSignature("add(application,account,asset)void"),
pt.Bytes(b"\x01"),
pt.Bytes(b"\x01"),
pt.Bytes(b"\x00"),
],
pt.TxnField.fee: pt.Int(0),
}
),
),
None,
),
(
pt.Int(1),
"add(pay,txn,appl)void",
[
t5_1 := {TxnField.type_enum: TxnType.Payment},
t5_2 := {TxnField.type_enum: TxnType.AssetTransfer},
t5_3 := {TxnField.type_enum: TxnType.ApplicationCall},
],
{TxnField.fee: pt.Int(0)},
pt.Seq(
pt.InnerTxnBuilder.SetFields(t5_1), # type: ignore
pt.InnerTxnBuilder.Next(),
pt.InnerTxnBuilder.SetFields(t5_2), # type: ignore
pt.InnerTxnBuilder.Next(),
pt.InnerTxnBuilder.SetFields(t5_3), # type: ignore
pt.InnerTxnBuilder.Next(),
pt.InnerTxnBuilder.SetFields(
{
pt.TxnField.type_enum: TxnType.ApplicationCall,
pt.TxnField.application_id: pt.Int(1),
pt.TxnField.application_args: [
pt.MethodSignature("add(pay,txn,appl)void"),
],
pt.TxnField.fee: pt.Int(0),
}
),
),
None,
),
# Error cases
(
pt.Int(1),
"add(pay,txn,appl)void",
[
{},
{TxnField.type_enum: TxnType.AssetTransfer},
{TxnField.type_enum: TxnType.ApplicationCall},
],
None,
None,
pt.TealInputError,
),
(
pt.Int(1),
"add(pay,txn,appl)void",
[
{TxnField.type_enum: pt.Int(10)},
{TxnField.type_enum: TxnType.AssetTransfer},
{TxnField.type_enum: TxnType.ApplicationCall},
],
None,
None,
pt.TealTypeError,
),
(
pt.Int(1),
"add(pay,txn,appl)void",
[
{TxnField.type_enum: TxnType.ApplicationCall},
{TxnField.type_enum: TxnType.AssetTransfer},
{TxnField.type_enum: TxnType.ApplicationCall},
],
None,
None,
pt.TealInputError,
),
(
pt.Int(1),
"add(application,account,asset)void",
[
pt.abi.Asset(),
pt.abi.Account(),
pt.abi.Asset(),
],
None,
None,
pt.TealTypeError,
),
(
pt.Int(1),
"add(application)void",
[
pt.Bytes(""),
],
None,
None,
pt.TealTypeError,
),
(
pt.Int(1),
"add(asset)void",
[
pt.Bytes(""),
],
None,
None,
pt.TealTypeError,
),
(
pt.Int(1),
"add(account)void",
[
pt.Int(1),
],
None,
None,
pt.TealTypeError,
),
(
pt.Int(1),
"add(uint64,uint64)void",
[pt.abi.String(), pt.abi.Uint64()],
None,
None,
pt.TealTypeError,
),
(
pt.Int(1),
"add(uint64,uint64)void",
[pt.abi.Uint64()],
None,
None,
pt.TealInputError,
),
(
pt.Int(1),
"add(uint64,uint64)void",
[pt.abi.Uint64(), pt.abi.Uint64(), pt.abi.Uint64()],
None,
None,
pt.TealInputError,
),
)
expected, _ = pt.Seq(
pt.InnerTxnBuilder.Begin(), expected_expr, pt.InnerTxnBuilder.Submit()
).__teal__(avm6Options)
expected.addIncoming()
expected = pt.TealBlock.NormalizeBlocks(expected)

actual, _ = expr.__teal__(avm6Options)
actual.addIncoming()
actual = pt.TealBlock.NormalizeBlocks(actual)

with pt.TealComponent.Context.ignoreScratchSlotEquality(), pt.TealComponent.Context.ignoreExprEquality():
assert actual == expected
assert pt.TealBlock.MatchScratchSlotReferences(
pt.TealBlock.GetReferencedScratchSlots(actual),
pt.TealBlock.GetReferencedScratchSlots(expected),
)


# txn_test.py performs additional testing

0 comments on commit a93c9db

Please sign in to comment.