algorand-python-testing 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- algopy/__init__.py +42 -0
- algopy/arc4.py +1 -0
- algopy/gtxn.py +1 -0
- algopy/itxn.py +1 -0
- algopy/op.py +1 -0
- algopy/py.typed +0 -0
- algopy_testing/__init__.py +47 -0
- algopy_testing/arc4.py +1222 -0
- algopy_testing/constants.py +17 -0
- algopy_testing/context.py +769 -0
- algopy_testing/decorators/__init__.py +0 -0
- algopy_testing/decorators/abimethod.py +146 -0
- algopy_testing/decorators/subroutine.py +9 -0
- algopy_testing/enums.py +39 -0
- algopy_testing/gtxn.py +239 -0
- algopy_testing/itxn.py +353 -0
- algopy_testing/models/__init__.py +23 -0
- algopy_testing/models/account.py +128 -0
- algopy_testing/models/application.py +72 -0
- algopy_testing/models/asset.py +109 -0
- algopy_testing/models/contract.py +69 -0
- algopy_testing/models/global_values.py +67 -0
- algopy_testing/models/gtxn.py +40 -0
- algopy_testing/models/itxn.py +34 -0
- algopy_testing/models/transactions.py +158 -0
- algopy_testing/models/txn.py +111 -0
- algopy_testing/models/unsigned_builtins.py +15 -0
- algopy_testing/op.py +639 -0
- algopy_testing/primitives/__init__.py +6 -0
- algopy_testing/primitives/biguint.py +147 -0
- algopy_testing/primitives/bytes.py +173 -0
- algopy_testing/primitives/string.py +67 -0
- algopy_testing/primitives/uint64.py +210 -0
- algopy_testing/py.typed +0 -0
- algopy_testing/state/__init__.py +4 -0
- algopy_testing/state/global_state.py +73 -0
- algopy_testing/state/local_state.py +54 -0
- algopy_testing/utils.py +156 -0
- algorand_python_testing-0.1.0.dist-info/METADATA +29 -0
- algorand_python_testing-0.1.0.dist-info/RECORD +41 -0
- algorand_python_testing-0.1.0.dist-info/WHEEL +4 -0
|
File without changes
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import functools
|
|
4
|
+
import typing
|
|
5
|
+
|
|
6
|
+
from algopy_testing.constants import UINT64_SIZE
|
|
7
|
+
from algopy_testing.utils import int_to_bytes
|
|
8
|
+
|
|
9
|
+
if typing.TYPE_CHECKING:
|
|
10
|
+
import algopy
|
|
11
|
+
|
|
12
|
+
from algopy_testing.context import AlgopyTestContext
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
if typing.TYPE_CHECKING:
|
|
16
|
+
import algopy
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
_P = typing.ParamSpec("_P")
|
|
20
|
+
_R = typing.TypeVar("_R")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _extract_refs_from_args(
|
|
24
|
+
args: tuple[typing.Any, ...], kwargs: dict[str, typing.Any], ref_type: type
|
|
25
|
+
) -> list[typing.Any]:
|
|
26
|
+
import algopy
|
|
27
|
+
|
|
28
|
+
refs: list[typing.Any] = []
|
|
29
|
+
for arg in list(args) + list(kwargs.values()):
|
|
30
|
+
match arg:
|
|
31
|
+
case algopy.gtxn.TransactionBase() if ref_type is algopy.gtxn.TransactionBase:
|
|
32
|
+
refs.append(arg)
|
|
33
|
+
case algopy.Account() if ref_type is algopy.Account:
|
|
34
|
+
refs.append(arg)
|
|
35
|
+
case algopy.Asset() if ref_type is algopy.Asset:
|
|
36
|
+
refs.append(arg)
|
|
37
|
+
case algopy.Application() if ref_type is algopy.Application:
|
|
38
|
+
refs.append(arg)
|
|
39
|
+
case (
|
|
40
|
+
algopy.Bytes() | algopy.String() | algopy.BigUInt() | algopy.UInt64() | int()
|
|
41
|
+
) if ref_type is algopy.Bytes:
|
|
42
|
+
refs.append(_extract_bytes(arg))
|
|
43
|
+
case _:
|
|
44
|
+
continue
|
|
45
|
+
return refs
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _extract_bytes(value: object) -> algopy.Bytes:
|
|
49
|
+
import algopy
|
|
50
|
+
|
|
51
|
+
if isinstance(value, algopy.Bytes):
|
|
52
|
+
return value
|
|
53
|
+
if isinstance(value, (algopy.String | algopy.BigUInt)):
|
|
54
|
+
return value.bytes
|
|
55
|
+
if isinstance(value, (algopy.UInt64 | int)):
|
|
56
|
+
return algopy.Bytes(int_to_bytes(int(value), UINT64_SIZE))
|
|
57
|
+
raise ValueError(f"Unsupported type: {type(value).__name__}")
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _extract_and_append_txn_to_context(
|
|
61
|
+
context: AlgopyTestContext, args: tuple[typing.Any, ...], kwargs: dict[str, typing.Any]
|
|
62
|
+
) -> None:
|
|
63
|
+
import algopy
|
|
64
|
+
|
|
65
|
+
context.add_transactions(_extract_refs_from_args(args, kwargs, algopy.gtxn.TransactionBase))
|
|
66
|
+
existing_indexes = {int(txn.group_index) for txn in context.get_transaction_group()}
|
|
67
|
+
new_group_txn_index = max(existing_indexes, default=-1) + 1
|
|
68
|
+
|
|
69
|
+
context.add_transactions(
|
|
70
|
+
[
|
|
71
|
+
context.any_app_call_txn(
|
|
72
|
+
group_index=1,
|
|
73
|
+
sender=context.default_creator,
|
|
74
|
+
app_id=context.default_application,
|
|
75
|
+
accounts=_extract_refs_from_args(args, kwargs, algopy.Account),
|
|
76
|
+
assets=_extract_refs_from_args(args, kwargs, algopy.Asset),
|
|
77
|
+
apps=_extract_refs_from_args(args, kwargs, algopy.Application),
|
|
78
|
+
app_args=_extract_refs_from_args(args, kwargs, algopy.Bytes),
|
|
79
|
+
approval_program_pages=_extract_refs_from_args(args, kwargs, algopy.Bytes),
|
|
80
|
+
clear_state_program_pages=_extract_refs_from_args(args, kwargs, algopy.Bytes),
|
|
81
|
+
),
|
|
82
|
+
]
|
|
83
|
+
)
|
|
84
|
+
context.set_active_transaction_index(new_group_txn_index)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
@typing.overload
|
|
88
|
+
def abimethod(fn: typing.Callable[_P, _R], /) -> typing.Callable[_P, _R]: ...
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@typing.overload
|
|
92
|
+
def abimethod(
|
|
93
|
+
*,
|
|
94
|
+
name: str | None = None,
|
|
95
|
+
create: typing.Literal["allow", "require", "disallow"] = "disallow",
|
|
96
|
+
allow_actions: typing.Sequence[
|
|
97
|
+
algopy.OnCompleteAction
|
|
98
|
+
| typing.Literal[
|
|
99
|
+
"NoOp",
|
|
100
|
+
"OptIn",
|
|
101
|
+
"CloseOut",
|
|
102
|
+
# ClearState has its own program, so is not considered as part of ARC4 routing
|
|
103
|
+
"UpdateApplication",
|
|
104
|
+
"DeleteApplication",
|
|
105
|
+
]
|
|
106
|
+
] = ("NoOp",),
|
|
107
|
+
readonly: bool = False,
|
|
108
|
+
default_args: typing.Mapping[str, str | object] = {},
|
|
109
|
+
) -> typing.Callable[[typing.Callable[_P, _R]], typing.Callable[_P, _R]]: ...
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def abimethod( # noqa: PLR0913
|
|
113
|
+
fn: typing.Callable[_P, _R] | None = None,
|
|
114
|
+
*,
|
|
115
|
+
name: str | None = None, # noqa: ARG001
|
|
116
|
+
create: typing.Literal["allow", "require", "disallow"] = "disallow", # noqa: ARG001
|
|
117
|
+
allow_actions: typing.Sequence[ # noqa: ARG001
|
|
118
|
+
algopy.OnCompleteAction
|
|
119
|
+
| typing.Literal[
|
|
120
|
+
"NoOp",
|
|
121
|
+
"OptIn",
|
|
122
|
+
"CloseOut",
|
|
123
|
+
"UpdateApplication",
|
|
124
|
+
"DeleteApplication",
|
|
125
|
+
]
|
|
126
|
+
] = ("NoOp",),
|
|
127
|
+
readonly: bool = False, # noqa: ARG001
|
|
128
|
+
default_args: typing.Mapping[str, str | object] | None = None, # noqa: ARG001
|
|
129
|
+
) -> typing.Callable[[typing.Callable[_P, _R]], typing.Callable[_P, _R]] | typing.Callable[_P, _R]:
|
|
130
|
+
def decorator(fn: typing.Callable[_P, _R]) -> typing.Callable[_P, _R]:
|
|
131
|
+
@functools.wraps(fn)
|
|
132
|
+
def wrapper(*args: _P.args, **kwargs: _P.kwargs) -> _R:
|
|
133
|
+
from algopy_testing import get_test_context
|
|
134
|
+
|
|
135
|
+
context = get_test_context()
|
|
136
|
+
if context is None or context._active_transaction_index is not None:
|
|
137
|
+
return fn(*args, **kwargs)
|
|
138
|
+
|
|
139
|
+
_extract_and_append_txn_to_context(context, args, kwargs)
|
|
140
|
+
return fn(*args, **kwargs)
|
|
141
|
+
|
|
142
|
+
return wrapper
|
|
143
|
+
|
|
144
|
+
if fn is not None:
|
|
145
|
+
return decorator(fn)
|
|
146
|
+
return decorator
|
algopy_testing/enums.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from enum import Enum, IntEnum
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class OnCompleteAction(Enum):
|
|
5
|
+
NoOp = 0
|
|
6
|
+
OptIn = 1
|
|
7
|
+
CloseOut = 2
|
|
8
|
+
ClearState = 3
|
|
9
|
+
UpdateApplication = 4
|
|
10
|
+
DeleteApplication = 5
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class TransactionType(IntEnum):
|
|
14
|
+
Payment = 0
|
|
15
|
+
KeyRegistration = 1
|
|
16
|
+
AssetConfig = 2
|
|
17
|
+
AssetTransfer = 3
|
|
18
|
+
AssetFreeze = 4
|
|
19
|
+
ApplicationCall = 5
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ECDSA(Enum):
|
|
23
|
+
Secp256k1 = 0
|
|
24
|
+
Secp256r1 = 1
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class VrfVerify(Enum):
|
|
28
|
+
VrfAlgorand = 0
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Base64(Enum):
|
|
32
|
+
URLEncoding = 0
|
|
33
|
+
StdEncoding = 1
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"OnCompleteAction",
|
|
38
|
+
"TransactionType",
|
|
39
|
+
]
|
algopy_testing/gtxn.py
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
from dataclasses import dataclass, field
|
|
5
|
+
from typing import TYPE_CHECKING
|
|
6
|
+
|
|
7
|
+
from algopy_testing.models.transactions import (
|
|
8
|
+
ApplicationCallFields,
|
|
9
|
+
AssetConfigFields,
|
|
10
|
+
AssetFreezeFields,
|
|
11
|
+
AssetTransferFields,
|
|
12
|
+
KeyRegistrationFields,
|
|
13
|
+
PaymentFields,
|
|
14
|
+
_ApplicationCallBaseFields,
|
|
15
|
+
_AssetConfigBaseFields,
|
|
16
|
+
_AssetFreezeBaseFields,
|
|
17
|
+
_AssetTransferBaseFields,
|
|
18
|
+
_KeyRegistrationBaseFields,
|
|
19
|
+
_PaymentBaseFields,
|
|
20
|
+
_TransactionBaseFields,
|
|
21
|
+
)
|
|
22
|
+
from algopy_testing.utils import txn_type_to_bytes
|
|
23
|
+
|
|
24
|
+
if TYPE_CHECKING:
|
|
25
|
+
import algopy
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class _GroupTransaction:
|
|
30
|
+
_fields: dict[str, typing.Any] = field(default_factory=dict)
|
|
31
|
+
|
|
32
|
+
group_index: algopy.UInt64 | int = field(default=0)
|
|
33
|
+
|
|
34
|
+
def __init__(self, group_index: algopy.UInt64 | int) -> None:
|
|
35
|
+
self.group_index = group_index
|
|
36
|
+
self._fields = {}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class TransactionFields(
|
|
40
|
+
_TransactionBaseFields,
|
|
41
|
+
_AssetTransferBaseFields,
|
|
42
|
+
_PaymentBaseFields,
|
|
43
|
+
_ApplicationCallBaseFields,
|
|
44
|
+
_KeyRegistrationBaseFields,
|
|
45
|
+
_AssetConfigBaseFields,
|
|
46
|
+
_AssetFreezeBaseFields,
|
|
47
|
+
total=False,
|
|
48
|
+
):
|
|
49
|
+
pass
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class TransactionBase(_GroupTransaction):
|
|
54
|
+
def __init__(
|
|
55
|
+
self, group_index: algopy.UInt64 | int, **kwargs: typing.Unpack[_TransactionBaseFields]
|
|
56
|
+
):
|
|
57
|
+
super().__init__(group_index=group_index)
|
|
58
|
+
self._fields.update(kwargs)
|
|
59
|
+
|
|
60
|
+
def set(self, **kwargs: typing.Unpack[_TransactionBaseFields]) -> None:
|
|
61
|
+
"""Updates inner transaction parameter values"""
|
|
62
|
+
self._fields.update(kwargs)
|
|
63
|
+
|
|
64
|
+
def __getattr__(self, name: str) -> object:
|
|
65
|
+
if name in _TransactionBaseFields.__annotations__:
|
|
66
|
+
return self._fields.get(name)
|
|
67
|
+
|
|
68
|
+
raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class AssetTransferTransaction(TransactionBase):
|
|
73
|
+
def __init__(
|
|
74
|
+
self, group_index: algopy.UInt64 | int, **kwargs: typing.Unpack[AssetTransferFields]
|
|
75
|
+
):
|
|
76
|
+
super().__init__(group_index=group_index)
|
|
77
|
+
self._fields.update(kwargs)
|
|
78
|
+
|
|
79
|
+
def set(self, **kwargs: typing.Unpack[AssetTransferFields]) -> None:
|
|
80
|
+
"""Updates inner transaction parameter values"""
|
|
81
|
+
self._fields.update(kwargs)
|
|
82
|
+
|
|
83
|
+
def __getattr__(self, name: str) -> typing.Any:
|
|
84
|
+
if name in AssetTransferFields.__annotations__:
|
|
85
|
+
return self._fields.get(name)
|
|
86
|
+
|
|
87
|
+
raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
@dataclass
|
|
91
|
+
class PaymentTransaction(TransactionBase):
|
|
92
|
+
def __init__(self, group_index: algopy.UInt64 | int, **kwargs: typing.Unpack[PaymentFields]):
|
|
93
|
+
import algopy
|
|
94
|
+
|
|
95
|
+
super().__init__(group_index=group_index)
|
|
96
|
+
self._fields.update(
|
|
97
|
+
{
|
|
98
|
+
"type": algopy.TransactionType.Payment,
|
|
99
|
+
"type_bytes": txn_type_to_bytes(int(algopy.TransactionType.Payment)),
|
|
100
|
+
**kwargs,
|
|
101
|
+
}
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def __getattr__(self, name: str) -> typing.Any:
|
|
105
|
+
if name in PaymentFields.__annotations__:
|
|
106
|
+
return self._fields.get(name)
|
|
107
|
+
|
|
108
|
+
raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@dataclass
|
|
112
|
+
class ApplicationCallTransaction(TransactionBase):
|
|
113
|
+
def __init__(
|
|
114
|
+
self, group_index: algopy.UInt64 | int, **kwargs: typing.Unpack[ApplicationCallFields]
|
|
115
|
+
):
|
|
116
|
+
import algopy
|
|
117
|
+
|
|
118
|
+
super().__init__(group_index=group_index)
|
|
119
|
+
self._fields.update(
|
|
120
|
+
{
|
|
121
|
+
"type": algopy.TransactionType.ApplicationCall,
|
|
122
|
+
"type_bytes": txn_type_to_bytes(int(algopy.TransactionType.ApplicationCall)),
|
|
123
|
+
**kwargs,
|
|
124
|
+
}
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def __getattr__(self, name: str) -> typing.Any:
|
|
128
|
+
if name in ApplicationCallFields.__annotations__:
|
|
129
|
+
return self._fields.get(name)
|
|
130
|
+
|
|
131
|
+
raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class KeyRegistrationTransaction(TransactionBase):
|
|
135
|
+
def __init__(
|
|
136
|
+
self, group_index: algopy.UInt64 | int, **kwargs: typing.Unpack[KeyRegistrationFields]
|
|
137
|
+
):
|
|
138
|
+
import algopy
|
|
139
|
+
|
|
140
|
+
super().__init__(group_index=group_index)
|
|
141
|
+
self._fields.update(
|
|
142
|
+
{
|
|
143
|
+
"type": algopy.TransactionType.KeyRegistration,
|
|
144
|
+
"type_bytes": txn_type_to_bytes(int(algopy.TransactionType.KeyRegistration)),
|
|
145
|
+
**kwargs,
|
|
146
|
+
}
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def __getattr__(self, name: str) -> typing.Any:
|
|
150
|
+
if name in KeyRegistrationFields.__annotations__:
|
|
151
|
+
return self._fields.get(name)
|
|
152
|
+
|
|
153
|
+
raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
@dataclass
|
|
157
|
+
class AssetConfigTransaction(TransactionBase):
|
|
158
|
+
def __init__(
|
|
159
|
+
self, group_index: algopy.UInt64 | int, **kwargs: typing.Unpack[AssetConfigFields]
|
|
160
|
+
):
|
|
161
|
+
import algopy
|
|
162
|
+
|
|
163
|
+
super().__init__(group_index=group_index)
|
|
164
|
+
self._fields.update(
|
|
165
|
+
{
|
|
166
|
+
"type": algopy.TransactionType.AssetConfig,
|
|
167
|
+
"type_bytes": txn_type_to_bytes(int(algopy.TransactionType.AssetConfig)),
|
|
168
|
+
**kwargs,
|
|
169
|
+
}
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
def __getattr__(self, name: str) -> typing.Any:
|
|
173
|
+
if name in AssetConfigFields.__annotations__:
|
|
174
|
+
return self._fields.get(name)
|
|
175
|
+
|
|
176
|
+
raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
@dataclass
|
|
180
|
+
class AssetFreezeTransaction(TransactionBase):
|
|
181
|
+
def __init__(
|
|
182
|
+
self,
|
|
183
|
+
group_index: algopy.UInt64 | int,
|
|
184
|
+
**kwargs: typing.Unpack[AssetFreezeFields],
|
|
185
|
+
):
|
|
186
|
+
import algopy
|
|
187
|
+
|
|
188
|
+
super().__init__(group_index=group_index)
|
|
189
|
+
self._fields.update(
|
|
190
|
+
{
|
|
191
|
+
"type": algopy.TransactionType.AssetFreeze,
|
|
192
|
+
"type_bytes": txn_type_to_bytes(int(algopy.TransactionType.AssetFreeze)),
|
|
193
|
+
**kwargs,
|
|
194
|
+
}
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
def __getattr__(self, name: str) -> typing.Any:
|
|
198
|
+
if name in AssetFreezeFields.__annotations__:
|
|
199
|
+
return self._fields.get(name)
|
|
200
|
+
|
|
201
|
+
raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
@dataclass
|
|
205
|
+
class Transaction(TransactionBase):
|
|
206
|
+
def __init__(
|
|
207
|
+
self,
|
|
208
|
+
group_index: algopy.UInt64 | int,
|
|
209
|
+
**kwargs: typing.Unpack[TransactionFields],
|
|
210
|
+
):
|
|
211
|
+
if "type" not in kwargs:
|
|
212
|
+
raise ValueError("Transaction 'type' field is required")
|
|
213
|
+
|
|
214
|
+
super().__init__(group_index=group_index)
|
|
215
|
+
self._fields.update(kwargs)
|
|
216
|
+
|
|
217
|
+
def __getattr__(self, name: str) -> typing.Any:
|
|
218
|
+
if name in TransactionFields.__annotations__:
|
|
219
|
+
return self._fields.get(name)
|
|
220
|
+
|
|
221
|
+
raise AttributeError(f"'{type(self)}' object has no attribute '{name}'")
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
__all__ = [
|
|
225
|
+
"TransactionBase",
|
|
226
|
+
"AssetTransferTransaction",
|
|
227
|
+
"PaymentTransaction",
|
|
228
|
+
"ApplicationCallTransaction",
|
|
229
|
+
"KeyRegistrationTransaction",
|
|
230
|
+
"AssetConfigTransaction",
|
|
231
|
+
"AssetFreezeTransaction",
|
|
232
|
+
"Transaction",
|
|
233
|
+
"PaymentFields",
|
|
234
|
+
"AssetTransferFields",
|
|
235
|
+
"ApplicationCallFields",
|
|
236
|
+
"KeyRegistrationFields",
|
|
237
|
+
"AssetConfigFields",
|
|
238
|
+
"AssetFreezeFields",
|
|
239
|
+
]
|