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
algopy_testing/itxn.py
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import logging
|
|
4
|
+
import typing
|
|
5
|
+
from copy import deepcopy
|
|
6
|
+
|
|
7
|
+
import algosdk
|
|
8
|
+
|
|
9
|
+
from algopy_testing.constants import MAX_UINT64
|
|
10
|
+
from algopy_testing.context import get_test_context
|
|
11
|
+
from algopy_testing.models.transactions import (
|
|
12
|
+
PaymentFields,
|
|
13
|
+
_ApplicationCallBaseFields,
|
|
14
|
+
_AssetConfigBaseFields,
|
|
15
|
+
_AssetFreezeBaseFields,
|
|
16
|
+
_AssetTransferBaseFields,
|
|
17
|
+
_KeyRegistrationBaseFields,
|
|
18
|
+
_PaymentBaseFields,
|
|
19
|
+
_TransactionCoreFields,
|
|
20
|
+
_TransactionFields,
|
|
21
|
+
)
|
|
22
|
+
from algopy_testing.utils import dummy_transaction_id, txn_type_to_bytes
|
|
23
|
+
|
|
24
|
+
logger = logging.getLogger(__name__)
|
|
25
|
+
|
|
26
|
+
if typing.TYPE_CHECKING:
|
|
27
|
+
import algopy
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class _BaseInnerTransaction:
|
|
31
|
+
fields: dict[str, typing.Any]
|
|
32
|
+
|
|
33
|
+
def submit(self) -> object:
|
|
34
|
+
import algopy.itxn
|
|
35
|
+
|
|
36
|
+
context = get_test_context()
|
|
37
|
+
|
|
38
|
+
if not context:
|
|
39
|
+
raise RuntimeError("No test context found")
|
|
40
|
+
|
|
41
|
+
result = algopy.itxn.InnerTransactionResult(**self.fields)
|
|
42
|
+
context._append_inner_transaction_group([result])
|
|
43
|
+
return result
|
|
44
|
+
|
|
45
|
+
def copy(self) -> typing.Self:
|
|
46
|
+
return deepcopy(self)
|
|
47
|
+
|
|
48
|
+
def get_field(self, type_dict: object, name: str) -> typing.Any:
|
|
49
|
+
if name in type_dict.__annotations__:
|
|
50
|
+
return self.fields.get(name)
|
|
51
|
+
|
|
52
|
+
raise AttributeError(f"{type(self).__name__!r} object has no attribute {name!r}")
|
|
53
|
+
|
|
54
|
+
def __eq__(self, other: object) -> bool:
|
|
55
|
+
if isinstance(other, _BaseInnerTransaction):
|
|
56
|
+
return bool(self.fields == other.fields)
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
def __hash__(self) -> int:
|
|
60
|
+
return hash(self.fields)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class InnerTransaction(_BaseInnerTransaction):
|
|
64
|
+
def __init__(
|
|
65
|
+
self,
|
|
66
|
+
*,
|
|
67
|
+
type: algopy.TransactionType, # noqa: A002
|
|
68
|
+
**kwargs: typing.Unpack[_TransactionCoreFields],
|
|
69
|
+
):
|
|
70
|
+
self.fields = {
|
|
71
|
+
"type": type,
|
|
72
|
+
"type_bytes": txn_type_to_bytes(int(type)),
|
|
73
|
+
**kwargs,
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
def set(self, **kwargs: typing.Unpack[_TransactionFields]) -> None:
|
|
77
|
+
"""Updates inner transaction parameter values"""
|
|
78
|
+
self.fields.update(kwargs)
|
|
79
|
+
|
|
80
|
+
def __getattr__(self, name: str) -> object:
|
|
81
|
+
return self.get_field(_TransactionFields, name)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class Payment(_BaseInnerTransaction):
|
|
85
|
+
def __init__(self, **kwargs: typing.Unpack[_PaymentBaseFields]):
|
|
86
|
+
import algopy
|
|
87
|
+
|
|
88
|
+
self.fields = {**kwargs, "type": algopy.TransactionType.Payment}
|
|
89
|
+
|
|
90
|
+
def set(self, **kwargs: typing.Unpack[_PaymentBaseFields]) -> None:
|
|
91
|
+
"""Updates inner transaction parameter values"""
|
|
92
|
+
import algopy
|
|
93
|
+
|
|
94
|
+
self.fields.update({**kwargs, "type": algopy.TransactionType.Payment})
|
|
95
|
+
|
|
96
|
+
def __getattr__(self, name: str) -> object:
|
|
97
|
+
return self.get_field(_PaymentBaseFields, name)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
class KeyRegistration(_BaseInnerTransaction):
|
|
101
|
+
def __init__(self, **kwargs: typing.Unpack[_KeyRegistrationBaseFields]):
|
|
102
|
+
import algopy
|
|
103
|
+
|
|
104
|
+
self.fields = {**kwargs, "type": algopy.TransactionType.KeyRegistration}
|
|
105
|
+
|
|
106
|
+
def set(self, **kwargs: typing.Unpack[_KeyRegistrationBaseFields]) -> None:
|
|
107
|
+
"""Updates inner transaction parameter values"""
|
|
108
|
+
import algopy
|
|
109
|
+
|
|
110
|
+
self.fields.update({**kwargs, "type": algopy.TransactionType.KeyRegistration})
|
|
111
|
+
|
|
112
|
+
def __getattr__(self, name: str) -> object:
|
|
113
|
+
return self.get_field(_KeyRegistrationBaseFields, name)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class AssetConfig(_BaseInnerTransaction):
|
|
117
|
+
def __init__(self, **kwargs: typing.Unpack[_AssetConfigBaseFields]):
|
|
118
|
+
import algopy
|
|
119
|
+
|
|
120
|
+
self.fields = {**kwargs, "type": algopy.TransactionType.AssetConfig}
|
|
121
|
+
|
|
122
|
+
def set(self, **kwargs: typing.Unpack[_AssetConfigBaseFields]) -> None:
|
|
123
|
+
"""Updates inner transaction parameter values"""
|
|
124
|
+
import algopy
|
|
125
|
+
|
|
126
|
+
self.fields.update({**kwargs, "type": algopy.TransactionType.AssetConfig})
|
|
127
|
+
|
|
128
|
+
def __getattr__(self, name: str) -> object:
|
|
129
|
+
return self.get_field(_AssetConfigBaseFields, name)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class AssetTransfer(_BaseInnerTransaction):
|
|
133
|
+
def __init__(self, **kwargs: typing.Unpack[_AssetTransferBaseFields]):
|
|
134
|
+
import algopy
|
|
135
|
+
|
|
136
|
+
from algopy_testing import get_test_context
|
|
137
|
+
|
|
138
|
+
context = get_test_context()
|
|
139
|
+
self.fields = {
|
|
140
|
+
"type": algopy.TransactionType.AssetTransfer,
|
|
141
|
+
"asset_sender": context.default_application.address if context else None,
|
|
142
|
+
"amount": 0,
|
|
143
|
+
**kwargs,
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
def set(self, **kwargs: typing.Unpack[_AssetTransferBaseFields]) -> None:
|
|
147
|
+
"""Updates inner transaction parameter values"""
|
|
148
|
+
import algopy
|
|
149
|
+
|
|
150
|
+
self.fields.update({**kwargs, "type": algopy.TransactionType.AssetTransfer})
|
|
151
|
+
|
|
152
|
+
def __getattr__(self, name: str) -> object:
|
|
153
|
+
return self.get_field(_AssetTransferBaseFields, name)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class AssetFreeze(_BaseInnerTransaction):
|
|
157
|
+
def __init__(self, **kwargs: typing.Unpack[_AssetFreezeBaseFields]):
|
|
158
|
+
import algopy
|
|
159
|
+
|
|
160
|
+
self.fields = {**kwargs, "type": algopy.TransactionType.AssetFreeze}
|
|
161
|
+
|
|
162
|
+
def set(self, **kwargs: typing.Unpack[_AssetFreezeBaseFields]) -> None:
|
|
163
|
+
"""Updates inner transaction parameter values"""
|
|
164
|
+
import algopy
|
|
165
|
+
|
|
166
|
+
self.fields.update({**kwargs, "type": algopy.TransactionType.AssetFreeze})
|
|
167
|
+
|
|
168
|
+
def __getattr__(self, name: str) -> object:
|
|
169
|
+
return self.get_field(_AssetFreezeBaseFields, name)
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class ApplicationCall(_BaseInnerTransaction):
|
|
173
|
+
def __init__(self, **kwargs: typing.Unpack[_ApplicationCallBaseFields]):
|
|
174
|
+
import algopy
|
|
175
|
+
|
|
176
|
+
self.fields = {**kwargs, "type": algopy.TransactionType.ApplicationCall}
|
|
177
|
+
|
|
178
|
+
def set(self, **kwargs: typing.Unpack[_ApplicationCallBaseFields]) -> None:
|
|
179
|
+
"""Updates inner transaction parameter values"""
|
|
180
|
+
import algopy
|
|
181
|
+
|
|
182
|
+
self.fields.update({**kwargs, "type": algopy.TransactionType.ApplicationCall})
|
|
183
|
+
|
|
184
|
+
def __getattr__(self, name: str) -> object:
|
|
185
|
+
return self.get_field(_ApplicationCallBaseFields, name)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# ==== Inner Transaction Results ====
|
|
189
|
+
# These are used to represent finalized transactions submitted to the network
|
|
190
|
+
# and are created by the `submit` method of each inner transaction class
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
class _BaseInnerTransactionResult:
|
|
194
|
+
fields: dict[str, typing.Any]
|
|
195
|
+
|
|
196
|
+
@typing.overload
|
|
197
|
+
def __init__(
|
|
198
|
+
self,
|
|
199
|
+
txn_type: algopy.TransactionType,
|
|
200
|
+
**kwargs: typing.Unpack[_TransactionFields],
|
|
201
|
+
): ...
|
|
202
|
+
|
|
203
|
+
@typing.overload
|
|
204
|
+
def __init__(
|
|
205
|
+
self,
|
|
206
|
+
**kwargs: typing.Unpack[_TransactionFields],
|
|
207
|
+
): ...
|
|
208
|
+
|
|
209
|
+
def __init__(
|
|
210
|
+
self,
|
|
211
|
+
txn_type: algopy.TransactionType | None = None,
|
|
212
|
+
**kwargs: typing.Unpack[_TransactionFields],
|
|
213
|
+
):
|
|
214
|
+
import algopy
|
|
215
|
+
|
|
216
|
+
if txn_type is None and kwargs.get("type") is None:
|
|
217
|
+
raise ValueError("No transaction type provided to `algopy.itxn.InnerTransaction`")
|
|
218
|
+
|
|
219
|
+
txn_type = txn_type if txn_type is not None else kwargs.get("type")
|
|
220
|
+
txn_type_bytes = txn_type_to_bytes(int(txn_type)) # type: ignore[arg-type]
|
|
221
|
+
|
|
222
|
+
self.fields = {
|
|
223
|
+
"type": txn_type,
|
|
224
|
+
"type_bytes": txn_type_bytes,
|
|
225
|
+
"first_valid": algopy.UInt64(0),
|
|
226
|
+
"first_valid_time": algopy.UInt64(0),
|
|
227
|
+
"last_valid": algopy.UInt64(MAX_UINT64),
|
|
228
|
+
"note": algopy.Bytes(b""),
|
|
229
|
+
"lease": algopy.Bytes(bytes(algosdk.constants.ZERO_ADDRESS, encoding="utf-8")),
|
|
230
|
+
"close_remainder_to": algopy.Bytes(
|
|
231
|
+
bytes(algosdk.constants.ZERO_ADDRESS, encoding="utf-8")
|
|
232
|
+
),
|
|
233
|
+
"txn_id": algopy.Bytes(dummy_transaction_id()),
|
|
234
|
+
**kwargs,
|
|
235
|
+
}
|
|
236
|
+
self._parse_covariant_types()
|
|
237
|
+
|
|
238
|
+
def get_field(self, type_dict: object, name: str) -> typing.Any:
|
|
239
|
+
if name in type_dict.__annotations__:
|
|
240
|
+
return self.fields.get(name)
|
|
241
|
+
|
|
242
|
+
raise AttributeError(f"{type(self).__name__!r} object has no attribute {name!r}")
|
|
243
|
+
|
|
244
|
+
def _parse_covariant_types(
|
|
245
|
+
self,
|
|
246
|
+
) -> None:
|
|
247
|
+
import algopy
|
|
248
|
+
|
|
249
|
+
for name, value in self.fields.items():
|
|
250
|
+
if isinstance(value, int):
|
|
251
|
+
self.fields[name] = algopy.UInt64(value)
|
|
252
|
+
if isinstance(value, bytes):
|
|
253
|
+
self.fields[name] = algopy.Bytes(value)
|
|
254
|
+
if isinstance(value, str):
|
|
255
|
+
self.fields[name] = algopy.Bytes(value.encode("utf-8"))
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
class PaymentInnerTransaction(_BaseInnerTransactionResult):
|
|
259
|
+
def __init__(self, **kwargs: typing.Unpack[PaymentFields]):
|
|
260
|
+
import algopy
|
|
261
|
+
|
|
262
|
+
super().__init__(algopy.TransactionType.Payment, **kwargs)
|
|
263
|
+
|
|
264
|
+
def __getattr__(self, name: str) -> object:
|
|
265
|
+
return self.get_field(PaymentFields, name)
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
class KeyRegistrationInnerTransaction(_BaseInnerTransactionResult):
|
|
269
|
+
def __init__(self, **kwargs: typing.Unpack[_KeyRegistrationBaseFields]):
|
|
270
|
+
import algopy
|
|
271
|
+
|
|
272
|
+
super().__init__(algopy.TransactionType.KeyRegistration, **kwargs)
|
|
273
|
+
|
|
274
|
+
def __getattr__(self, name: str) -> object:
|
|
275
|
+
return self.get_field(_KeyRegistrationBaseFields, name)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class AssetConfigInnerTransaction(_BaseInnerTransactionResult):
|
|
279
|
+
def __init__(self, **kwargs: typing.Unpack[_AssetConfigBaseFields]):
|
|
280
|
+
import algopy
|
|
281
|
+
|
|
282
|
+
super().__init__(algopy.TransactionType.AssetConfig, **kwargs)
|
|
283
|
+
|
|
284
|
+
def __getattr__(self, name: str) -> object:
|
|
285
|
+
return self.get_field(_AssetConfigBaseFields, name)
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class AssetTransferInnerTransaction(_BaseInnerTransactionResult):
|
|
289
|
+
def __init__(self, **kwargs: typing.Unpack[_AssetTransferBaseFields]):
|
|
290
|
+
import algopy
|
|
291
|
+
|
|
292
|
+
super().__init__(algopy.TransactionType.AssetTransfer, **kwargs)
|
|
293
|
+
|
|
294
|
+
def __getattr__(self, name: str) -> object:
|
|
295
|
+
return self.get_field(_AssetTransferBaseFields, name)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
class AssetFreezeInnerTransaction(_BaseInnerTransactionResult):
|
|
299
|
+
def __init__(self, **kwargs: typing.Unpack[_AssetFreezeBaseFields]):
|
|
300
|
+
import algopy
|
|
301
|
+
|
|
302
|
+
super().__init__(algopy.TransactionType.AssetFreeze, **kwargs)
|
|
303
|
+
|
|
304
|
+
def __getattr__(self, name: str) -> object:
|
|
305
|
+
return self.get_field(_AssetFreezeBaseFields, name)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
class ApplicationCallInnerTransaction(_BaseInnerTransactionResult):
|
|
309
|
+
def __init__(self, **kwargs: typing.Unpack[_ApplicationCallBaseFields]):
|
|
310
|
+
import algopy
|
|
311
|
+
|
|
312
|
+
super().__init__(algopy.TransactionType.ApplicationCall, **kwargs)
|
|
313
|
+
|
|
314
|
+
def __getattr__(self, name: str) -> object:
|
|
315
|
+
return self.get_field(_ApplicationCallBaseFields, name)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
class InnerTransactionResult(_BaseInnerTransactionResult):
|
|
319
|
+
def __init__(self, **kwargs: typing.Unpack[_TransactionFields]):
|
|
320
|
+
super().__init__(**kwargs)
|
|
321
|
+
|
|
322
|
+
def __getattr__(self, name: str) -> object:
|
|
323
|
+
return self.get_field(_TransactionFields, name)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
_InnerTransactionsType = (
|
|
327
|
+
InnerTransactionResult
|
|
328
|
+
| PaymentInnerTransaction
|
|
329
|
+
| KeyRegistrationInnerTransaction
|
|
330
|
+
| AssetConfigInnerTransaction
|
|
331
|
+
| AssetTransferInnerTransaction
|
|
332
|
+
| AssetFreezeInnerTransaction
|
|
333
|
+
| ApplicationCallInnerTransaction
|
|
334
|
+
)
|
|
335
|
+
|
|
336
|
+
__all__ = [
|
|
337
|
+
"_BaseInnerTransaction",
|
|
338
|
+
"_InnerTransactionsType",
|
|
339
|
+
"InnerTransaction",
|
|
340
|
+
"Payment",
|
|
341
|
+
"KeyRegistration",
|
|
342
|
+
"AssetConfig",
|
|
343
|
+
"AssetTransfer",
|
|
344
|
+
"AssetFreeze",
|
|
345
|
+
"ApplicationCall",
|
|
346
|
+
"PaymentInnerTransaction",
|
|
347
|
+
"KeyRegistrationInnerTransaction",
|
|
348
|
+
"AssetConfigInnerTransaction",
|
|
349
|
+
"AssetTransferInnerTransaction",
|
|
350
|
+
"AssetFreezeInnerTransaction",
|
|
351
|
+
"ApplicationCallInnerTransaction",
|
|
352
|
+
"InnerTransactionResult",
|
|
353
|
+
]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from algopy_testing.models.account import Account
|
|
2
|
+
from algopy_testing.models.application import Application
|
|
3
|
+
from algopy_testing.models.asset import Asset
|
|
4
|
+
from algopy_testing.models.contract import ARC4Contract, Contract
|
|
5
|
+
from algopy_testing.models.global_values import Global
|
|
6
|
+
from algopy_testing.models.gtxn import GTxn
|
|
7
|
+
from algopy_testing.models.itxn import ITxn
|
|
8
|
+
from algopy_testing.models.txn import Txn
|
|
9
|
+
from algopy_testing.models.unsigned_builtins import uenumerate, urange
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"ARC4Contract",
|
|
13
|
+
"Account",
|
|
14
|
+
"Application",
|
|
15
|
+
"Asset",
|
|
16
|
+
"Contract",
|
|
17
|
+
"Global",
|
|
18
|
+
"GTxn",
|
|
19
|
+
"ITxn",
|
|
20
|
+
"Txn",
|
|
21
|
+
"uenumerate",
|
|
22
|
+
"urange",
|
|
23
|
+
]
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import TYPE_CHECKING, Self, TypedDict, TypeVar
|
|
5
|
+
|
|
6
|
+
import algosdk
|
|
7
|
+
|
|
8
|
+
from algopy_testing.primitives.bytes import Bytes
|
|
9
|
+
from algopy_testing.utils import as_bytes
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
import algopy
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
T = TypeVar("T")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class AccountFields(TypedDict, total=False):
|
|
19
|
+
balance: algopy.UInt64
|
|
20
|
+
min_balance: algopy.UInt64
|
|
21
|
+
auth_address: algopy.Account
|
|
22
|
+
total_num_uint: algopy.UInt64
|
|
23
|
+
total_num_byte_slice: algopy.Bytes
|
|
24
|
+
total_extra_app_pages: algopy.UInt64
|
|
25
|
+
total_apps_created: algopy.UInt64
|
|
26
|
+
total_apps_opted_in: algopy.UInt64
|
|
27
|
+
total_assets_created: algopy.UInt64
|
|
28
|
+
total_assets: algopy.UInt64
|
|
29
|
+
total_boxes: algopy.UInt64
|
|
30
|
+
total_box_bytes: algopy.UInt64
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass()
|
|
34
|
+
class Account:
|
|
35
|
+
_public_key: bytes
|
|
36
|
+
|
|
37
|
+
def __init__(self, value: str | Bytes = algosdk.constants.ZERO_ADDRESS, /):
|
|
38
|
+
if not isinstance(value, (str | Bytes)):
|
|
39
|
+
raise TypeError("Invalid value for Account")
|
|
40
|
+
|
|
41
|
+
public_key = (
|
|
42
|
+
algosdk.encoding.decode_address(value) if isinstance(value, str) else value.value
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
self._public_key = public_key
|
|
46
|
+
|
|
47
|
+
def is_opted_in(self, asset_or_app: algopy.Asset | algopy.Application, /) -> bool:
|
|
48
|
+
from algopy import Application, Asset
|
|
49
|
+
|
|
50
|
+
from algopy_testing import get_test_context
|
|
51
|
+
|
|
52
|
+
context = get_test_context()
|
|
53
|
+
opted_apps = context._account_data[str(self)].opted_apps
|
|
54
|
+
opted_asset_balances = context._account_data[str(self)].opted_asset_balances
|
|
55
|
+
|
|
56
|
+
if not context:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
"Test context is not initialized! Use `with algopy_testing_context()` to access "
|
|
59
|
+
"the context manager."
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
if isinstance(asset_or_app, Asset):
|
|
63
|
+
return asset_or_app.id in opted_asset_balances
|
|
64
|
+
elif isinstance(asset_or_app, Application):
|
|
65
|
+
return asset_or_app.id in opted_apps
|
|
66
|
+
|
|
67
|
+
raise TypeError(
|
|
68
|
+
"Invalid `asset_or_app` argument type. Must be an `algopy.Asset` or "
|
|
69
|
+
"`algopy.Application` instance."
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
@classmethod
|
|
73
|
+
def from_bytes(cls, value: algopy.Bytes | bytes) -> Self:
|
|
74
|
+
# NOTE: AVM does not perform any validation beyond type.
|
|
75
|
+
validated_value = as_bytes(value)
|
|
76
|
+
return cls(Bytes(validated_value))
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def bytes(self) -> Bytes:
|
|
80
|
+
return Bytes(self._public_key)
|
|
81
|
+
|
|
82
|
+
def __getattr__(self, name: str) -> object:
|
|
83
|
+
from algopy_testing.context import get_test_context
|
|
84
|
+
|
|
85
|
+
context = get_test_context()
|
|
86
|
+
if not context:
|
|
87
|
+
raise ValueError(
|
|
88
|
+
"Test context is not initialized! Use `with algopy_testing_context()` to access "
|
|
89
|
+
"the context manager."
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
if str(self) not in context._account_data:
|
|
93
|
+
raise ValueError(
|
|
94
|
+
"`algopy.Account` is not present in the test context! "
|
|
95
|
+
"Use `context.add_account()` or `context.any_account()` to add the account "
|
|
96
|
+
"to your test setup."
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
return_value = context._account_data[str(self)].fields.get(name)
|
|
100
|
+
if return_value is None:
|
|
101
|
+
raise AttributeError(
|
|
102
|
+
f"The value for '{name}' in the test context is None. "
|
|
103
|
+
f"Make sure to patch the global field '{name}' using your `AlgopyTestContext` "
|
|
104
|
+
"instance."
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
return return_value
|
|
108
|
+
|
|
109
|
+
def __repr__(self) -> str:
|
|
110
|
+
return str(algosdk.encoding.encode_address(self._public_key))
|
|
111
|
+
|
|
112
|
+
def __str__(self) -> str:
|
|
113
|
+
return str(algosdk.encoding.encode_address(self._public_key))
|
|
114
|
+
|
|
115
|
+
def __eq__(self, other: object) -> bool:
|
|
116
|
+
if not isinstance(other, Account | str):
|
|
117
|
+
raise TypeError("Invalid value for Account")
|
|
118
|
+
if isinstance(other, Account):
|
|
119
|
+
return self._public_key == other._public_key
|
|
120
|
+
return self._public_key == as_bytes(other)
|
|
121
|
+
|
|
122
|
+
def __bool__(self) -> bool:
|
|
123
|
+
return bool(self._public_key) and self._public_key != algosdk.encoding.decode_address(
|
|
124
|
+
algosdk.constants.ZERO_ADDRESS
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def __hash__(self) -> int:
|
|
128
|
+
return hash(self._public_key)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import typing
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from typing import TYPE_CHECKING, TypedDict, TypeVar
|
|
6
|
+
|
|
7
|
+
from algopy_testing.utils import as_string
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
import algopy
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
T = TypeVar("T")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ApplicationFields(TypedDict, total=False):
|
|
17
|
+
approval_program: algopy.Bytes
|
|
18
|
+
clear_state_program: algopy.Bytes
|
|
19
|
+
global_num_uint: algopy.UInt64
|
|
20
|
+
global_num_bytes: algopy.UInt64
|
|
21
|
+
local_num_uint: algopy.UInt64
|
|
22
|
+
local_num_bytes: algopy.UInt64
|
|
23
|
+
extra_program_pages: algopy.UInt64
|
|
24
|
+
creator: algopy.Account
|
|
25
|
+
address: algopy.Account
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass()
|
|
29
|
+
class Application:
|
|
30
|
+
id: algopy.UInt64
|
|
31
|
+
|
|
32
|
+
def __init__(self, application_id: algopy.UInt64 | int = 0, /):
|
|
33
|
+
from algopy import UInt64
|
|
34
|
+
|
|
35
|
+
self.id = application_id if isinstance(application_id, UInt64) else UInt64(application_id)
|
|
36
|
+
|
|
37
|
+
def __getattr__(self, name: str) -> typing.Any:
|
|
38
|
+
from algopy_testing.context import get_test_context
|
|
39
|
+
|
|
40
|
+
context = get_test_context()
|
|
41
|
+
if not context:
|
|
42
|
+
raise ValueError(
|
|
43
|
+
"Test context is not initialized! Use `with algopy_testing_context()` to access "
|
|
44
|
+
"the context manager."
|
|
45
|
+
)
|
|
46
|
+
if int(self.id) not in context._application_data:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
"`algopy.Application` is not present in the test context! "
|
|
49
|
+
"Use `context.add_application()` or `context.any_application()` to add the "
|
|
50
|
+
"application to your test setup."
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
return_value = context._application_data[int(self.id)].get(name)
|
|
54
|
+
if return_value is None:
|
|
55
|
+
raise AttributeError(
|
|
56
|
+
f"The value for '{name}' in the test context is None. "
|
|
57
|
+
f"Make sure to patch the global field '{name}' using your `AlgopyTestContext` "
|
|
58
|
+
"instance."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
return return_value
|
|
62
|
+
|
|
63
|
+
def __eq__(self, other: object) -> bool:
|
|
64
|
+
if isinstance(other, Application):
|
|
65
|
+
return self.id == other.id
|
|
66
|
+
return self.id == as_string(other)
|
|
67
|
+
|
|
68
|
+
def __bool__(self) -> bool:
|
|
69
|
+
return self.id != 0
|
|
70
|
+
|
|
71
|
+
def __hash__(self) -> int:
|
|
72
|
+
return hash(self.id)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import TYPE_CHECKING, TypedDict, TypeVar
|
|
5
|
+
|
|
6
|
+
if TYPE_CHECKING:
|
|
7
|
+
import algopy
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
T = TypeVar("T")
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class AssetFields(TypedDict, total=False):
|
|
14
|
+
total: algopy.UInt64
|
|
15
|
+
decimals: algopy.UInt64
|
|
16
|
+
default_frozen: bool
|
|
17
|
+
unit_name: algopy.Bytes
|
|
18
|
+
name: algopy.Bytes
|
|
19
|
+
url: algopy.Bytes
|
|
20
|
+
metadata_hash: algopy.Bytes
|
|
21
|
+
manager: algopy.Account
|
|
22
|
+
reserve: algopy.Account
|
|
23
|
+
freeze: algopy.Account
|
|
24
|
+
clawback: algopy.Account
|
|
25
|
+
creator: algopy.Account
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass
|
|
29
|
+
class Asset:
|
|
30
|
+
id: algopy.UInt64
|
|
31
|
+
|
|
32
|
+
def __init__(self, asset_id: algopy.UInt64 | int = 0):
|
|
33
|
+
from algopy import UInt64
|
|
34
|
+
|
|
35
|
+
self.id = asset_id if isinstance(asset_id, UInt64) else UInt64(asset_id)
|
|
36
|
+
|
|
37
|
+
def balance(self, account: algopy.Account) -> algopy.UInt64:
|
|
38
|
+
from algopy_testing.context import get_test_context
|
|
39
|
+
|
|
40
|
+
context = get_test_context()
|
|
41
|
+
if not context:
|
|
42
|
+
raise ValueError(
|
|
43
|
+
"Test context is not initialized! Use `with algopy_testing_context()` to access "
|
|
44
|
+
"the context manager."
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
if account not in context._account_data:
|
|
48
|
+
raise ValueError(
|
|
49
|
+
"The account is not present in the test context! "
|
|
50
|
+
"Use `context.add_account()` or `context.any_account()` to add the account to "
|
|
51
|
+
"your test setup."
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
account_data = context._account_data.get(str(account), None)
|
|
55
|
+
|
|
56
|
+
if not account_data:
|
|
57
|
+
raise ValueError("Account not found in testing context!")
|
|
58
|
+
|
|
59
|
+
if int(self.id) not in account_data.opted_asset_balances:
|
|
60
|
+
raise ValueError(
|
|
61
|
+
"The asset is not opted into the account! "
|
|
62
|
+
"Use `account.opt_in()` to opt the asset into the account."
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
return account_data.opted_asset_balances[self.id]
|
|
66
|
+
|
|
67
|
+
def frozen(self, _account: algopy.Account) -> bool:
|
|
68
|
+
raise NotImplementedError(
|
|
69
|
+
"The 'frozen' method is being executed in a python testing context. "
|
|
70
|
+
"Please mock this method using your python testing framework of choice."
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
def __getattr__(self, name: str) -> object:
|
|
74
|
+
from algopy_testing.context import get_test_context
|
|
75
|
+
|
|
76
|
+
context = get_test_context()
|
|
77
|
+
if not context:
|
|
78
|
+
raise ValueError(
|
|
79
|
+
"Test context is not initialized! Use `with algopy_testing_context()` to access "
|
|
80
|
+
"the context manager."
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if int(self.id) not in context._asset_data:
|
|
84
|
+
raise ValueError(
|
|
85
|
+
"`algopy.Asset` is not present in the test context! "
|
|
86
|
+
"Use `context.add_asset()` or `context.any_asset()` to add the asset to "
|
|
87
|
+
"your test setup."
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
return_value = context._asset_data[int(self.id)].get(name)
|
|
91
|
+
if return_value is None:
|
|
92
|
+
raise AttributeError(
|
|
93
|
+
f"The value for '{name}' in the test context is None. "
|
|
94
|
+
f"Make sure to patch the global field '{name}' using your `AlgopyTestContext` "
|
|
95
|
+
"instance."
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
return return_value
|
|
99
|
+
|
|
100
|
+
def __eq__(self, other: object) -> bool:
|
|
101
|
+
if isinstance(other, Asset):
|
|
102
|
+
return self.id == other.id
|
|
103
|
+
return self.id == other
|
|
104
|
+
|
|
105
|
+
def __bool__(self) -> bool:
|
|
106
|
+
return self.id != 0
|
|
107
|
+
|
|
108
|
+
def __hash__(self) -> int:
|
|
109
|
+
return hash(self.id)
|