Skip to content

Commit

Permalink
minor polish; fixing links in algopy testing docs; simplifying auctio…
Browse files Browse the repository at this point in the history
…n tests (#259)
  • Loading branch information
aorumbayev authored Jun 26, 2024
1 parent f19425d commit 0b6a087
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 82 deletions.
38 changes: 16 additions & 22 deletions algopy_testing/examples/auction/test_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,43 +36,39 @@ def test_start_auction(
context: AlgopyTestContext,
) -> None:
# Arrange
account = context.any_account()
app_account = context.any_account()
latest_timestamp = context.any_uint64(1, 1000)
auction_price = context.any_uint64(1, 100)
auction_duration = context.any_uint64(1000, 3600)
starting_price = context.any_uint64()
auction_duration = context.any_uint64(100, 1000)
axfer_txn = context.any_asset_transfer_transaction(
asset_receiver=app_account,
asset_amount=auction_price,
asset_receiver=context.default_application.address,
asset_amount=starting_price,
)
contract = AuctionContract()
contract.asa_amount = auction_price
contract.asa_amount = starting_price
context.patch_global_fields(
creator_address=account,
current_application_address=app_account,
latest_timestamp=latest_timestamp,
)
context.patch_txn_fields(sender=account)
context.patch_txn_fields(sender=context.default_creator)

# Act
contract.start_auction(
auction_price,
starting_price,
auction_duration,
axfer_txn,
)

# Assert
assert contract.auction_end == latest_timestamp + auction_duration
assert contract.previous_bid == auction_price
assert contract.asa_amount == auction_price
assert contract.previous_bid == starting_price
assert contract.asa_amount == starting_price


def test_bid(context: AlgopyTestContext) -> None:
# Arrange
account = context.default_creator
auction_end = context.any_uint64(min_value=int(time.time()) + 10_000)
previous_bid = context.any_uint64(1, 100)
pay_amount = context.any_uint64(100, 200)
pay_amount = context.any_uint64()

contract = AuctionContract()
contract.auction_end = auction_end
Expand All @@ -95,10 +91,10 @@ def test_claim_bids(
account = context.any_account()
context.patch_txn_fields(sender=account)
contract = AuctionContract()
claimable_amount = context.any_uint64(100, 300)
claimable_amount = context.any_uint64()
contract.claimable_amount[account] = claimable_amount
contract.previous_bidder = account
previous_bid = context.any_uint64(1, 100)
previous_bid = context.any_uint64(max_value=int(claimable_amount))
contract.previous_bid = previous_bid

# Act
Expand All @@ -115,12 +111,10 @@ def test_claim_bids(

def test_claim_asset(context: AlgopyTestContext) -> None:
# Arrange
account = context.any_account()
latest_timestamp = context.any_uint64(1000, 2000)
context.patch_global_fields(latest_timestamp=latest_timestamp)
context.patch_global_fields(latest_timestamp=context.any_uint64())
contract = AuctionContract()
contract.auction_end = context.any_uint64(1, 100)
contract.previous_bidder = account
contract.previous_bidder = context.default_creator
asa_amount = context.any_uint64(1000, 2000)
contract.asa_amount = asa_amount
asset = context.any_asset()
Expand All @@ -131,8 +125,8 @@ def test_claim_asset(context: AlgopyTestContext) -> None:
# Assert
last_inner_txn = context.last_submitted_itxn.asset_transfer
assert last_inner_txn.xfer_asset == asset
assert last_inner_txn.asset_close_to == account
assert last_inner_txn.asset_receiver == account
assert last_inner_txn.asset_close_to == context.default_creator
assert last_inner_txn.asset_receiver == context.default_creator
assert last_inner_txn.asset_amount == asa_amount


Expand Down
2 changes: 1 addition & 1 deletion algopy_testing/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "hatchling.build"

[project]
name = "algorand-python-testing"
version = "0.2.0"
version = "0.2.1"
description = 'Algorand Python testing library'
readme = "README.md"
requires-python = ">=3.12"
Expand Down
45 changes: 27 additions & 18 deletions algopy_testing/src/algopy_testing/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,21 +159,30 @@ def txn_type_to_bytes(txn_type: int) -> algopy.Bytes:


def is_instance(obj: object, class_or_tuple: type | UnionType) -> bool:
if isinstance(class_or_tuple, UnionType):
return any(is_instance(obj, arg) for arg in get_args(class_or_tuple))

if isinstance(obj, typing._ProtocolMeta): # type: ignore[type-check, unused-ignore]
return (
(
any(
f"{obj.__module__}.{obj.__name__}"
== f"{class_or_tuple.__module__}.{class_or_tuple.__name__}" # type: ignore[union-attr, unused-ignore]
for class_or_tuple in get_args(class_or_tuple)
)
)
if isinstance(class_or_tuple, UnionType)
else (
f"{obj.__module__}.{obj.__name__}"
== f"{class_or_tuple.__module__}.{class_or_tuple.__name__}" # type: ignore[union-attr, unused-ignore]
)
f"{obj.__module__}.{obj.__name__}"
== f"{class_or_tuple.__module__}.{class_or_tuple.__name__}" # type: ignore[union-attr, unused-ignore]
)

# Manual comparison by module and name
if (
hasattr(obj, "__module__")
and hasattr(obj, "__name__")
and (
obj.__module__,
obj.__name__, # type: ignore[attr-defined, unused-ignore]
)
== (
class_or_tuple.__module__,
class_or_tuple.__name__,
)
):
return True

return isinstance(obj, class_or_tuple)


Expand All @@ -190,21 +199,21 @@ def abi_type_name_for_arg( # noqa: PLR0912, C901, PLR0911
return "string"
if is_instance(arg, algopy.arc4.Bool | bool):
return "bool"
if isinstance(arg, algopy.BigUInt):
if is_instance(arg, algopy.BigUInt):
return "uint512"
if isinstance(arg, algopy.UInt64):
if is_instance(arg, algopy.UInt64):
return "uint64"
if isinstance(arg, int):
return "uint64" if arg <= MAX_UINT64 else "uint512"
if isinstance(arg, algopy.Bytes | bytes):
if is_instance(arg, algopy.Bytes | bytes):
return "byte[]"
if is_instance(arg, algopy.arc4.Address):
return "address"
if isinstance(arg, algopy.Asset):
if is_instance(arg, algopy.Asset):
return "uint64" if is_return_type else "asset"
if isinstance(arg, algopy.Account):
if is_instance(arg, algopy.Account):
return "uint64" if is_return_type else "account"
if isinstance(arg, algopy.Application):
if is_instance(arg, algopy.Application):
return "uint64" if is_return_type else "application"
if is_instance(arg, algopy.arc4.UIntN):
return "uint" + str(arg._bit_size) # type: ignore[attr-defined]
Expand Down
70 changes: 37 additions & 33 deletions docs/algopy_testing/coverage.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,40 @@
# Coverage

See which stubs are implemented completely (pure functions), which are mockable (via context manager), and which are not.
See which `algorand-python` stubs are implemented by the `algorand-python-testing` library. There are 3 general categories:

| Name | Implementation Status | Mockable Representation |
| ------------------------------------------- | --------------------- | ----------------------- |
| Primitives (UInt64, BigUInt, Bytes, String) | Fully Implemented | Yes |
| Asset | Partially Implemented | Yes |
| Account | Partially Implemented | Yes |
| Application | Partially Implemented | Yes |
| urange | Fully Implemented | Yes |
| subroutine | Fully Implemented | Yes |
| op. (most operations) | Fully Implemented | Yes |
| Txn, GTxn, ITxn | Fully Implemented | Yes |
| Global | Fully Implemented | Yes |
| Box | Fully Implemented | Yes |
| Block | Fully Implemented | Yes |
| logicsig | Fully Implemented | Yes |
| log | Fully Implemented | Yes |
| itxn. (inner transactions) | Fully Implemented | Yes |
| gtxn. (group transactions) | Fully Implemented | Yes |
| ensure_budget | Fully Implemented | Yes |
| arc4. (most ARC4 features) | Fully Implemented | Yes |
| uenumerate | Not Implemented | Yes |
| op.ITxnCreate | Not Implemented | Yes |
| op.EllipticCurve | Not Implemented | Yes |
| op.AssetParamsGet | Not Implemented | Yes |
| op.AppParamsGet | Not Implemented | Yes |
| op.AppLocal | Not Implemented | Yes |
| op.AppGlobal | Not Implemented | Yes |
| op.AcctParamsGet | Not Implemented | Yes |
| itxn.ApplicationCallInnerTransaction | Not Implemented | Yes |
| arc4.UFixedNxM | Not Implemented | Yes |
| arc4.BigUFixedNxM | Not Implemented | Yes |
| arc4.ARC4Contract | Not Implemented | Yes |
| StateTotals | Not Implemented | Yes |
1. **Implemented**: Full native Python equivalent matching AVM computation. For example, `algopy.op.sha256` and other cryptographic operations behave identically in AVM and unit tests written with this library.

2. **Emulated**: Implemented with the aid of the `AlgopyTestContext` manager, which mimics major AVM behavior to allow this abstraction to function as expected in a test context. For example, when you call `Box.put` on an `algopy.Box` object within a test context, it won't interact with the real Algorand network. Instead, it will store the data in the test context manager behind the scenes, while still providing the same interface as the real `Box` class.

3. **Mockable**: No implementation provided, but can be easily mocked or patched to inject intended behavior. For example, `algopy.abi_call` can be mocked to return or act as needed; otherwise, it will raise a "not implemented" exception. Mockable types are exceptional cases where behavior or functionality does not make sense within a unit testing context or would require an unnecessary amount of complexity without significant benefit to the end user (a developer writing offline unit tests).

> Note, below table not exhaustive yet, but will be expanded along with initial stable release.
| Name | Implementation Status |
| ------------------------------------------------------------ | --------------------- |
| Primitives (UInt64, BigUInt, Bytes, String) | Implemented |
| urange | Implemented |
| All crypto ops in op.\* namespace (to be expanded in detail) | Implemented |
| Txn, GTxn, ITxn | Implemented |
| arc4.\* namespace (to be expanded in detail) | Implemented |
| uenumerate | Implemented |
| op.ITxnCreate | Implemented |
| StateTotals | Implemented |
| Asset | Emulated |
| Account | Emulated |
| Application | Emulated |
| subroutine | Emulated |
| Global | Emulated |
| Box | Emulated |
| Block | Emulated |
| logicsig | Emulated |
| log | Emulated |
| itxn.\* namespace (inner transactions) | Emulated |
| gtxn.\* namespace (group transactions) | Emulated |
| ensure_budget | Mockable |
| op.EllipticCurve | Mockable |
| op.AssetParamsGet | Mockable |
| op.AppParamsGet | Mockable |
| op.AppLocal | Mockable |
| op.AppGlobal | Mockable |
| op.AcctParamsGet | Mockable |
18 changes: 10 additions & 8 deletions docs/algopy_testing/examples.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
# Examples
# Example

| Contract Name | Description | Link to contract.py |
| ------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| Auction | Implements a basic auction system | [auction/contract.py](algopy_testing/examples/auction/contract.py) |
| Proof of Attendance | Manages proof of attendance for events | [proof_of_attendance/contract.py](algopy_testing/examples/proof_of_attendance/contract.py) |
| Simple Voting | Implements a basic voting system | [simple_voting/contract.py](algopy_testing/examples/simple_voting/contract.py) |
| ZK Whitelist | Implements a whitelist using zero-knowledge proofs | [zk_whitelist/contract.py](algopy_testing/examples/zk_whitelist/contract.py) |
| HTLC LogicSig | Implements a Hashed Time-Locked Contract using LogicSig | [htlc_logicsig/signature.py](algopy_testing/examples/htlc_logicsig/signature.py) |
Below is a showcase of various examples of unit testing real and sample Algorand Python smart contracts using `algorand-python-testing`.

| Contract Name | Test File | Key Features Demonstrated | Test versions of Algopy Abstractions used |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
| Auction | [test_contract.py](https://github.com/algorandfoundation/puya/blob/main/algopy_testing/examples/auction/test_contract.py) | - Use of algopy_testing_context<br>- Mocking of global state and transaction fields<br>- Testing of ARC4 contract methods<br>- Emulation of asset creation and transfers<br>- Verification of inner transactions | - **ARC4Contract**<br>- **Global**<br>- **Txn**<br>- **Asset**<br>- **Account** |
| Proof of Attendance | [test_contract.py](https://github.com/algorandfoundation/puya/blob/main/algopy_testing/examples/proof_of_attendance/test_contract.py) | - Creation and management of dummy assets<br>- Testing of box storage operations<br>- Verification of inner transactions for asset transfers<br>- Use of any\_\* methods for generating test data | - **Contract**<br>- **Box**<br>- **Asset**<br>- **Account**<br>- **op** (for various operations) |
| Simple Voting | [test_contract.py](https://github.com/algorandfoundation/puya/blob/main/algopy_testing/examples/simple_voting/test_contract.py) | - Testing of global and local state operations<br>- Verification of transaction group operations<br>- Mocking of payment transactions | - **Contract**<br>- **GlobalState**<br>- **LocalState**<br>- **Txn**<br>- **GTxn** (group transactions) |
| ZK Whitelist | [test_contract.py](https://github.com/algorandfoundation/puya/blob/main/algopy_testing/examples/zk_whitelist/test_contract.py) | - Testing of zero-knowledge proof verification<br>- Mocking of external application calls<br>- Use of ARC4 types and methods | - **ARC4Contract**<br>- **arc4 types** (Address, DynamicArray, StaticArray, etc.)<br>- Application logs |
| HTLC LogicSig | [test_signature.py](https://github.com/algorandfoundation/puya/blob/main/algopy_testing/examples/htlc_logicsig/test_signature.py) | - Testing of LogicSig contracts<br>- Verification of time-based conditions<br>- Mocking of transaction parameters | - **LogicSig**<br>- **Txn**<br>- **Global** |
12 changes: 12 additions & 0 deletions docs/algopy_testing/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

The Algorand Python Testing framework provides a powerful set of tools for testing your Algorand Python smart contracts within Python interpeter. This section will continue delving into the main features and concepts of the framework.

> Please note that this framework is in preview, detailed documentation is to follow, if you are interested in specific op code or having trouble implementing a test, reach out to us on #algokit channel on official Algorand discord.
## Test Context Manager

The core of the testing framework is the `algopy_testing_context` [context manager](https://docs.python.org/3/library/contextlib.html#contextlib.ContextDecorator). This creates a simulated Algorand environment for your tests that mimics major behaviour of AVM in Python interpreter.
Expand Down Expand Up @@ -218,12 +220,22 @@ ctx.patch_global_fields(latest_timestamp=algopy.UInt64(1000))

### Global State

To be documented...

### Local State

To be documented...

### Scratch Space

To be documented...

### Boxes

To be documented...

> NOTE: Higher level Boxes interface introduce in v2.1.0 is not supported yet, however all low level Box 'op' calls are available.
## Smart Signatures

Test logic signatures (also known as smart signatures):
Expand Down

0 comments on commit 0b6a087

Please sign in to comment.