Skip to content

Commit df9199e

Browse files
authored
Merge pull request #56 from algorandfoundation/refactor/decorators
refactor: add @public as alias of @abimethod and relax requirement of @subroutine
2 parents a35f340 + b14c635 commit df9199e

File tree

18 files changed

+101
-106
lines changed

18 files changed

+101
-106
lines changed

docs/coverage.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ See which `algorand-python` stubs are implemented by the `algorand-python-testin
4040
| algopy.ensure_budget | Emulated |
4141
| algopy.log | Emulated |
4242
| algopy.logicsig | Emulated |
43+
| algopy.public | Emulated |
4344
| algopy.size_of | Emulated |
4445
| algopy.subroutine | Native |
4546
| algopy.uenumerate | Native |

docs/index.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class VotingContract(algopy.ARC4Contract):
6262
)
6363
self.voted = algopy.LocalState(algopy.UInt64, key="voted", description="Tracks if an account has voted")
6464
65-
@arc4.abimethod
65+
@algopy.public
6666
def set_topic(self, topic: arc4.String) -> None:
6767
self.topic.value = topic.bytes
6868
@@ -79,7 +79,7 @@ class VotingContract(algopy.ARC4Contract):
7979
self.voted[algopy.Txn.sender] = algopy.UInt64(1)
8080
return arc4.Bool(True)
8181
82-
@arc4.abimethod(readonly=True)
82+
@algopy.public(readonly=True)
8383
def get_votes(self) -> arc4.UInt64:
8484
return arc4.UInt64(self.votes.value)
8585
@@ -141,9 +141,9 @@ This example demonstrates key aspects of testing with `algorand-python-testing`
141141
1. ARC4 Contract Features:
142142

143143
- Use of `algopy.ARC4Contract` as the base class for the contract.
144-
- ABI methods defined using the `@arc4.abimethod` decorator.
144+
- ABI methods defined using the `@arc4.abimethod`, or its alias `@algopy.public`, decorator.
145145
- Use of ARC4-specific types like `arc4.String`, `arc4.Bool`, and `arc4.UInt64`.
146-
- Readonly method annotation with `@arc4.abimethod(readonly=True)`.
146+
- Readonly method annotation with `@arc4.abimethod(readonly=True)` or `@algopy.public(readonly=True)` .
147147

148148
2. Testing ARC4 Contracts:
149149

docs/testing-guide/concepts.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,5 +61,5 @@ For a full list of all public `algopy` types and their corresponding implementat
6161

6262
## Data Validation
6363

64-
Algorand Python and the puya compiler have functionality to perform validation of transaction inputs via the `--validate-abi-args`, `--validate-abi-return` CLI arguments, `arc4.abimethod(validate_encoding=...)` decorator and `.validate()` methods.
65-
The Algorand Python Testing library does *NOT* implement this validation behaviour, as you should test invalid inputs using an integrated test against a real Algorand network.
64+
Algorand Python and the puya compiler have functionality to perform validation of transaction inputs via the `--validate-abi-args`, `--validate-abi-return` CLI arguments, `arc4.abimethod(validate_encoding=...)` decorator (or its alias, `algopy.public`) and `.validate()` methods.
65+
The Algorand Python Testing library does _NOT_ implement this validation behaviour, as you should test invalid inputs using an integrated test against a real Algorand network.

docs/testing-guide/contract-testing.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Smart Contract Testing
22

3-
This guide provides an overview of how to test smart contracts using the Algorand Python SDK (`algopy`). It covers the basics of testing `ARC4Contract` and `Contract` classes, focusing on the `abimethod` and `baremethod` decorators.
3+
This guide provides an overview of how to test smart contracts using the Algorand Python SDK (`algopy`). It covers the basics of testing `ARC4Contract` and `Contract` classes, focusing on the `baremethod`, `abimethod` and `public` (an alias of `abimethod`) decorators.
44

55
![](https://mermaid.ink/img/pako:eNqVkrFugzAQhl_Fujnp1ImhEiJrJNREWeoOV9sNVsFG9iEVBd69R5w0JE2llsk2n7-7_-AAymsDGewDtpXYrqQT_GyKFwl5vfcBnRZlT5V3IjYYSCjvKKAiCa-JzXfrObyzgTqsxRpVZZ25YOX2nnRrIomCneZzpszLkllktu0f8ratrUKyjFsXCZ1K2gTH7i01_8dGUjOT_55YeLdUFVr3zRunf5b6R5hZoFnBq9cX72_Br_Cj8bl4vJCHaVucvowYxHk5Xg_sfPkY6SbbphDL5dMgQZu29n0U5DMJwzTVGyApySKZKFSNMXKVxPJYYAGNCQ1azX_VYboqgSrTcAcZLzWGDwnSjcxhR37TOwUZhc4sIPhuX0H2jnXkXddqrrCyyKNpTqfjF5m74B8?type=png)
66

@@ -24,7 +24,7 @@ context = ctx_manager.__enter__()
2424

2525
Subclasses of `algopy.ARC4Contract` are **required** to be instantiated with an active test context. As part of instantiation, the test context will automatically create a matching `algopy.Application` object instance.
2626

27-
Within the class implementation, methods decorated with `algopy.arc4.abimethod` and `algopy.arc4.baremethod` will automatically assemble an `algopy.gtxn.ApplicationCallTransaction` to emulate the AVM application call. This behaviour can be overridden by setting the transaction group manually as part of test setup; this is done via implicit invocation of the `algopy_testing.context.any_application()` _value generator_ (refer to the [API](../api.md) for more details).
27+
Within the class implementation, methods decorated with `algopy.arc4.abimethod` (or its alias, `algopy.public`) and `algopy.arc4.baremethod` will automatically assemble an `algopy.gtxn.ApplicationCallTransaction` to emulate the AVM application call. This behaviour can be overridden by setting the transaction group manually as part of test setup; this is done via implicit invocation of the `algopy_testing.context.any_application()` _value generator_ (refer to the [API](../api.md) for more details).
2828

2929
```{testcode}
3030
class SimpleVotingContract(algopy.ARC4Contract):
@@ -42,14 +42,14 @@ class SimpleVotingContract(algopy.ARC4Contract):
4242
self.topic.value = initial_topic
4343
self.votes.value = algopy.UInt64(0)
4444
45-
@algopy.arc4.abimethod
45+
@algopy.public
4646
def vote(self) -> algopy.UInt64:
4747
assert self.voted[algopy.Txn.sender] == algopy.UInt64(0), "Account has already voted"
4848
self.votes.value += algopy.UInt64(1)
4949
self.voted[algopy.Txn.sender] = algopy.UInt64(1)
5050
return self.votes.value
5151
52-
@algopy.arc4.abimethod(readonly=True)
52+
@algopy.public(readonly=True)
5353
def get_votes(self) -> algopy.UInt64:
5454
return self.votes.value
5555
@@ -74,7 +74,7 @@ assert contract.topic.value == initial_topic
7474
assert contract.votes.value == algopy.UInt64(0)
7575
7676
# Act - Vote
77-
# The method `.vote()` is decorated with `algopy.arc4.abimethod`, which means it will assemble a transaction to emulate the AVM application call
77+
# The method `.vote()` is decorated with `algopy.public`, an alias of `algopy.arc4.abimethod`, which means it will assemble a transaction to emulate the AVM application call
7878
result = contract.vote()
7979
8080
# Assert - you can access the corresponding auto generated application call transaction via test context

docs/testing-guide/subroutines.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ context = ctx_manager.__enter__()
1818

1919
The `@algopy.subroutine` decorator exposes contract methods for isolated testing within the Algorand Python Testing framework. This enables focused validation of core business logic without the overhead of full application deployment and execution.
2020

21+
`@algopy.subroutine` decorator is optional for the methods in a contract which are not callable externally.
22+
2123
## Usage
2224

2325
1. Decorate internal methods with `@algopy.subroutine`:
@@ -26,7 +28,7 @@ The `@algopy.subroutine` decorator exposes contract methods for isolated testing
2628
from algopy import subroutine, UInt64
2729
2830
class MyContract:
29-
@subroutine
31+
@subroutine # optional
3032
def calculate_value(self, input: UInt64) -> UInt64:
3133
return input * UInt64(2)
3234
```

docs/testing-guide/transactions.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ When testing smart contracts, to stay consistent with AVM, the framework _does n
143143

144144
```{testcode}
145145
class MyContract(algopy.ARC4Contract):
146-
@algopy.arc4.abimethod
146+
@algopy.public
147147
def pay_via_itxn(self, asset: algopy.Asset) -> None:
148148
algopy.itxn.Payment(
149149
receiver=algopy.Txn.sender,
@@ -180,7 +180,7 @@ first_payment_txn = first_itxn_group.payment(0)
180180

181181
In this example, we define a contract method `pay_via_itxn` that creates and submits an inner payment transaction. The test context automatically captures and stores the inner transactions submitted by the contract method.
182182

183-
Note that we don't need to wrap the execution in a `create_group` context manager because the method is decorated with `@algopy.arc4.abimethod`, which automatically creates a transaction group for the method. The `create_group` context manager is only needed when you want to create more complex transaction groups or patch transaction fields for various transaction-related opcodes in AVM.
183+
Note that we don't need to wrap the execution in a `create_group` context manager because the method is decorated with `@algopy.public`, an alias of `@algopy.arc4.abimethod`, which automatically creates a transaction group for the method. The `create_group` context manager is only needed when you want to create more complex transaction groups or patch transaction fields for various transaction-related opcodes in AVM.
184184

185185
To access the submitted inner transactions:
186186

examples/marketplace/contract.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
gtxn,
1010
itxn,
1111
op,
12-
subroutine,
1312
)
1413
from algopy.arc4 import abimethod
1514

@@ -32,7 +31,6 @@ class DigitalMarketplace(ARC4Contract):
3231
def __init__(self) -> None:
3332
self.listings = BoxMap(ListingKey, ListingValue)
3433

35-
@subroutine
3634
def listings_box_mbr(self) -> UInt64:
3735
return (
3836
2_500
@@ -55,7 +53,6 @@ def listings_box_mbr(self) -> UInt64:
5553
* 400
5654
)
5755

58-
@subroutine
5956
def quantity_price(self, quantity: UInt64, price: UInt64, asset_decimals: UInt64) -> UInt64:
6057
amount_not_scaled_high, amount_not_scaled_low = op.mulw(price, quantity)
6158
scaling_factor_high, scaling_factor_low = op.expw(10, asset_decimals)

examples/proof_of_attendance/contract.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@ def __init__(self) -> None:
88
self.total_attendees = algopy.UInt64(0)
99
self.box_map = algopy.BoxMap(algopy.Bytes, algopy.UInt64)
1010

11-
@algopy.arc4.abimethod(create="require")
11+
@algopy.public(create="require")
1212
def init(self, max_attendees: algopy.UInt64) -> None:
1313
assert algopy.Txn.sender == algopy.Global.creator_address, "Only creator can initialize"
1414
self.max_attendees = max_attendees
1515

16-
@algopy.arc4.abimethod()
16+
@algopy.public()
1717
def confirm_attendance(self) -> None:
1818
assert self.total_attendees < self.max_attendees, "Max attendees reached"
1919

@@ -25,7 +25,7 @@ def confirm_attendance(self) -> None:
2525

2626
algopy.op.Box.put(algopy.Txn.sender.bytes, algopy.op.itob(minted_asset.id))
2727

28-
@algopy.arc4.abimethod()
28+
@algopy.public()
2929
def confirm_attendance_with_box(self) -> None:
3030
assert self.total_attendees < self.max_attendees, "Max attendees reached"
3131

@@ -38,7 +38,7 @@ def confirm_attendance_with_box(self) -> None:
3838

3939
box.value = minted_asset.id
4040

41-
@algopy.arc4.abimethod()
41+
@algopy.public()
4242
def confirm_attendance_with_box_ref(self) -> None:
4343
assert self.total_attendees < self.max_attendees, "Max attendees reached"
4444

@@ -51,7 +51,7 @@ def confirm_attendance_with_box_ref(self) -> None:
5151

5252
box_ref.value = algopy.op.itob(minted_asset.id)
5353

54-
@algopy.arc4.abimethod()
54+
@algopy.public()
5555
def confirm_attendance_with_box_map(self) -> None:
5656
assert self.total_attendees < self.max_attendees, "Max attendees reached"
5757

@@ -63,33 +63,33 @@ def confirm_attendance_with_box_map(self) -> None:
6363

6464
self.box_map[algopy.Txn.sender.bytes] = minted_asset.id
6565

66-
@algopy.arc4.abimethod(readonly=True)
66+
@algopy.public(readonly=True)
6767
def get_poa_id(self) -> algopy.UInt64:
6868
poa_id, exists = algopy.op.Box.get(algopy.Txn.sender.bytes)
6969
assert exists, "POA not found"
7070
return algopy.op.btoi(poa_id)
7171

72-
@algopy.arc4.abimethod(readonly=True)
72+
@algopy.public(readonly=True)
7373
def get_poa_id_with_box(self) -> algopy.UInt64:
7474
box = algopy.Box(algopy.UInt64, key=algopy.Txn.sender.bytes)
7575
poa_id, exists = box.maybe()
7676
assert exists, "POA not found"
7777
return poa_id
7878

79-
@algopy.arc4.abimethod(readonly=True)
79+
@algopy.public(readonly=True)
8080
def get_poa_id_with_box_ref(self) -> algopy.UInt64:
8181
box_ref = algopy.Box(algopy.Bytes, key=algopy.Txn.sender.bytes)
8282
poa_id, exists = box_ref.maybe()
8383
assert exists, "POA not found"
8484
return algopy.op.btoi(poa_id)
8585

86-
@algopy.arc4.abimethod(readonly=True)
86+
@algopy.public(readonly=True)
8787
def get_poa_id_with_box_map(self) -> algopy.UInt64:
8888
poa_id, exists = self.box_map.maybe(algopy.Txn.sender.bytes)
8989
assert exists, "POA not found"
9090
return poa_id
9191

92-
@algopy.arc4.abimethod()
92+
@algopy.public()
9393
def claim_poa(self, opt_in_txn: algopy.gtxn.AssetTransferTransaction) -> None:
9494
poa_id, exists = algopy.op.Box.get(algopy.Txn.sender.bytes)
9595
assert exists, "POA not found, attendance validation failed!"
@@ -108,7 +108,7 @@ def claim_poa(self, opt_in_txn: algopy.gtxn.AssetTransferTransaction) -> None:
108108
algopy.op.btoi(poa_id),
109109
)
110110

111-
@algopy.arc4.abimethod()
111+
@algopy.public()
112112
def claim_poa_with_box(self, opt_in_txn: algopy.gtxn.AssetTransferTransaction) -> None:
113113
box = algopy.Box(algopy.UInt64, key=algopy.Txn.sender.bytes)
114114
poa_id, exists = box.maybe()
@@ -128,7 +128,7 @@ def claim_poa_with_box(self, opt_in_txn: algopy.gtxn.AssetTransferTransaction) -
128128
poa_id,
129129
)
130130

131-
@algopy.arc4.abimethod()
131+
@algopy.public()
132132
def claim_poa_with_box_ref(self, opt_in_txn: algopy.gtxn.AssetTransferTransaction) -> None:
133133
box_ref = algopy.Box(algopy.Bytes, key=algopy.Txn.sender.bytes)
134134
poa_id, exists = box_ref.maybe()
@@ -148,7 +148,7 @@ def claim_poa_with_box_ref(self, opt_in_txn: algopy.gtxn.AssetTransferTransactio
148148
algopy.op.btoi(poa_id),
149149
)
150150

151-
@algopy.arc4.abimethod()
151+
@algopy.public()
152152
def claim_poa_with_box_map(self, opt_in_txn: algopy.gtxn.AssetTransferTransaction) -> None:
153153
poa_id, exists = self.box_map.maybe(algopy.Txn.sender.bytes)
154154
assert exists, "POA not found, attendance validation failed!"

examples/simple_voting/contract.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
Txn,
99
UInt64,
1010
op,
11-
subroutine,
1211
)
1312

1413
VOTE_PRICE = 10_000
@@ -24,11 +23,9 @@ def __init__(self) -> None:
2423
)
2524
self.voted = LocalState(UInt64, key="voted", description="Tracks if an account has voted")
2625

27-
@subroutine
2826
def set_topic(self, topic: Bytes) -> None:
2927
self.topic.value = topic
3028

31-
@subroutine
3229
def vote(self, voter: Account) -> bool:
3330
assert op.Global.group_size == UInt64(2)
3431
assert op.GTxn.amount(1) == UInt64(VOTE_PRICE)
@@ -40,7 +37,6 @@ def vote(self, voter: Account) -> bool:
4037
self.voted[voter] = UInt64(1)
4138
return True
4239

43-
@subroutine
4440
def get_votes(self) -> UInt64:
4541
return self.votes.value
4642

src/_algopy_testing/__init__.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# ruff: noqa: I001
12
from _algopy_testing import arc4, gtxn, itxn
23
from _algopy_testing.context import AlgopyTestContext
34
from _algopy_testing.context_helpers.context_storage import algopy_testing_context
@@ -24,6 +25,9 @@
2425
from _algopy_testing.value_generators.arc4 import ARC4ValueGenerator
2526
from _algopy_testing.value_generators.avm import AVMValueGenerator
2627
from _algopy_testing.value_generators.txn import TxnValueGenerator
28+
from _algopy_testing.decorators.arc4 import (
29+
abimethod as public,
30+
)
2731

2832
__all__ = [
2933
"ARC4Contract",
@@ -60,6 +64,7 @@
6064
"gtxn",
6165
"itxn",
6266
"logicsig",
67+
"public",
6368
"subroutine",
6469
"uenumerate",
6570
"urange",

0 commit comments

Comments
 (0)