algorand-python-testing 0.0.0b1__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.
Files changed (52) hide show
  1. algopy/__init__.py +58 -0
  2. algopy/arc4.py +1 -0
  3. algopy/gtxn.py +1 -0
  4. algopy/itxn.py +1 -0
  5. algopy/op.py +1 -0
  6. algopy/py.typed +0 -0
  7. algopy_testing/__init__.py +55 -0
  8. algopy_testing/arc4.py +1533 -0
  9. algopy_testing/constants.py +22 -0
  10. algopy_testing/context.py +1194 -0
  11. algopy_testing/decorators/__init__.py +0 -0
  12. algopy_testing/decorators/abimethod.py +204 -0
  13. algopy_testing/decorators/baremethod.py +83 -0
  14. algopy_testing/decorators/subroutine.py +9 -0
  15. algopy_testing/enums.py +42 -0
  16. algopy_testing/gtxn.py +261 -0
  17. algopy_testing/itxn.py +665 -0
  18. algopy_testing/models/__init__.py +31 -0
  19. algopy_testing/models/account.py +128 -0
  20. algopy_testing/models/application.py +72 -0
  21. algopy_testing/models/asset.py +109 -0
  22. algopy_testing/models/block.py +34 -0
  23. algopy_testing/models/box.py +158 -0
  24. algopy_testing/models/contract.py +82 -0
  25. algopy_testing/models/gitxn.py +42 -0
  26. algopy_testing/models/global_values.py +72 -0
  27. algopy_testing/models/gtxn.py +56 -0
  28. algopy_testing/models/itxn.py +85 -0
  29. algopy_testing/models/logicsig.py +44 -0
  30. algopy_testing/models/template_variable.py +23 -0
  31. algopy_testing/models/transactions.py +158 -0
  32. algopy_testing/models/txn.py +113 -0
  33. algopy_testing/models/unsigned_builtins.py +36 -0
  34. algopy_testing/op.py +1098 -0
  35. algopy_testing/primitives/__init__.py +6 -0
  36. algopy_testing/primitives/biguint.py +148 -0
  37. algopy_testing/primitives/bytes.py +174 -0
  38. algopy_testing/primitives/string.py +68 -0
  39. algopy_testing/primitives/uint64.py +213 -0
  40. algopy_testing/protocols.py +18 -0
  41. algopy_testing/py.typed +0 -0
  42. algopy_testing/state/__init__.py +4 -0
  43. algopy_testing/state/global_state.py +73 -0
  44. algopy_testing/state/local_state.py +54 -0
  45. algopy_testing/utilities/__init__.py +3 -0
  46. algopy_testing/utilities/budget.py +23 -0
  47. algopy_testing/utilities/log.py +55 -0
  48. algopy_testing/utils.py +249 -0
  49. algorand_python_testing-0.0.0b1.dist-info/METADATA +81 -0
  50. algorand_python_testing-0.0.0b1.dist-info/RECORD +52 -0
  51. algorand_python_testing-0.0.0b1.dist-info/WHEEL +4 -0
  52. algorand_python_testing-0.0.0b1.dist-info/licenses/LICENSE +14 -0
algopy_testing/arc4.py ADDED
@@ -0,0 +1,1533 @@
1
+ from __future__ import annotations
2
+
3
+ import copy
4
+ import decimal
5
+ import types
6
+ import typing
7
+ from collections import ChainMap
8
+ from collections.abc import Iterable, Reversible
9
+
10
+ import algosdk
11
+ from Cryptodome.Hash import SHA512
12
+
13
+ from algopy_testing.constants import (
14
+ ARC4_RETURN_PREFIX,
15
+ BITS_IN_BYTE,
16
+ MAX_UINT64,
17
+ UINT64_SIZE,
18
+ UINT512_SIZE,
19
+ )
20
+ from algopy_testing.decorators.abimethod import abimethod
21
+ from algopy_testing.decorators.baremethod import baremethod
22
+ from algopy_testing.models import Account
23
+ from algopy_testing.protocols import BytesBacked
24
+ from algopy_testing.utils import (
25
+ abi_type_name_for_arg,
26
+ as_bytes,
27
+ as_int,
28
+ as_int16,
29
+ as_int64,
30
+ as_int512,
31
+ as_string,
32
+ int_to_bytes,
33
+ )
34
+
35
+ if typing.TYPE_CHECKING:
36
+ import algopy
37
+
38
+ _ABI_LENGTH_SIZE = 2
39
+ _TBitSize = typing.TypeVar("_TBitSize", bound=int)
40
+
41
+
42
+ class _ABIEncoded(BytesBacked, typing.Protocol):
43
+ @classmethod
44
+ def from_log(cls, log: algopy.Bytes, /) -> typing.Self:
45
+ """Load an ABI type from application logs,
46
+ checking for the ABI return prefix `0x151f7c75`"""
47
+ ...
48
+
49
+
50
+ def arc4_signature(signature: str, /) -> algopy.Bytes:
51
+ """Convert a signature to ARC4 bytes"""
52
+ import algopy
53
+
54
+ hashed_signature = SHA512.new(truncate="256")
55
+ hashed_signature.update(signature.encode("utf-8"))
56
+ return_value = hashed_signature.digest()[:4]
57
+ return algopy.Bytes(return_value)
58
+
59
+
60
+ class String(_ABIEncoded):
61
+ """An ARC4 sequence of bytes containing a UTF8 string"""
62
+
63
+ _value: bytes
64
+
65
+ def __init__(self, value: algopy.String | str = "", /) -> None:
66
+ import algopy
67
+
68
+ match value:
69
+ case algopy.String():
70
+ bytes_value = as_bytes(value.bytes)
71
+ case str(value):
72
+ bytes_value = value.encode("utf-8")
73
+ case _:
74
+ raise TypeError(
75
+ f"value must be a string or String type, not {type(value).__name__!r}"
76
+ )
77
+
78
+ self._value = len(bytes_value).to_bytes(_ABI_LENGTH_SIZE) + bytes_value
79
+
80
+ @property
81
+ def native(self) -> algopy.String:
82
+ """Return the String representation of the UTF8 string after ARC4 decoding"""
83
+ import algopy
84
+
85
+ return algopy.String.from_bytes(self._value[_ABI_LENGTH_SIZE:])
86
+
87
+ def __add__(self, other: String | str) -> String:
88
+ return String(self.native + as_string(other))
89
+
90
+ def __radd__(self, other: String | str) -> String:
91
+ return String(as_string(other) + self.native)
92
+
93
+ def __eq__(self, other: String | str) -> bool: # type: ignore[override]
94
+ return self.native == as_string(other)
95
+
96
+ def __bool__(self) -> bool:
97
+ """Returns `True` if length is not zero"""
98
+ return bool(self.native)
99
+
100
+ @classmethod
101
+ def from_bytes(cls, value: algopy.Bytes | bytes, /) -> typing.Self:
102
+ """Construct an instance from the underlying bytes (no validation)"""
103
+ result = cls()
104
+ result._value = as_bytes(value)
105
+ return result
106
+
107
+ @property
108
+ def bytes(self) -> algopy.Bytes:
109
+ """Get the underlying Bytes"""
110
+ import algopy
111
+
112
+ return algopy.Bytes(self._value)
113
+
114
+ @classmethod
115
+ def from_log(cls, log: algopy.Bytes, /) -> typing.Self:
116
+ """Load an ABI type from application logs,
117
+ checking for the ABI return prefix `0x151f7c75`"""
118
+ import algopy
119
+
120
+ if log[:4] == algopy.Bytes(ARC4_RETURN_PREFIX):
121
+ return cls.from_bytes(log[4:])
122
+ raise ValueError("ABI return prefix not found")
123
+
124
+
125
+ # https://stackoverflow.com/a/75395800
126
+ class _UIntNMeta(type(_ABIEncoded), typing.Generic[_TBitSize]): # type: ignore[misc]
127
+ __concrete__: typing.ClassVar[dict[type, type]] = {}
128
+
129
+ def __getitem__(cls, key_t: type[_TBitSize]) -> type:
130
+ cache = cls.__concrete__
131
+ if c := cache.get(key_t, None):
132
+ return c
133
+ cache[key_t] = c = types.new_class(
134
+ f"{cls.__name__}[{key_t.__name__}]", (cls,), {}, lambda ns: ns.update(_t=key_t)
135
+ )
136
+ return c
137
+
138
+
139
+ class _UIntN(_ABIEncoded, typing.Generic[_TBitSize], metaclass=_UIntNMeta):
140
+ _t: type[_TBitSize]
141
+ _bit_size: int
142
+ _max_bits_len: int
143
+ _max_bytes_len: int
144
+ _max_int: int
145
+ _value: bytes # underlying 'bytes' value representing the UIntN
146
+
147
+ def __init__(self, value: algopy.BigUInt | algopy.UInt64 | int = 0, /) -> None:
148
+ self._bit_size = as_int(typing.get_args(self._t)[0], max=self._max_bits_len)
149
+ self._max_int = 2**self._bit_size - 1
150
+ self._max_bytes_len = self._bit_size // BITS_IN_BYTE
151
+
152
+ value = as_int(value, max=self._max_int)
153
+ bytes_value = int_to_bytes(value, self._max_bytes_len)
154
+ self._value = as_bytes(bytes_value, max_size=self._max_bytes_len)
155
+
156
+ # ~~~ https://docs.python.org/3/reference/datamodel.html#basic-customization ~~~
157
+ # TODO: mypy suggests due to Liskov below should be other: object
158
+ # need to consider ramifications here, ignoring it for now
159
+ def __eq__( # type: ignore[override]
160
+ self,
161
+ other: UIntN[_TBitSize] | BigUIntN[_TBitSize] | algopy.UInt64 | algopy.BigUInt | int,
162
+ ) -> bool:
163
+ raise NotImplementedError
164
+
165
+ def __ne__( # type: ignore[override]
166
+ self,
167
+ other: UIntN[_TBitSize] | BigUIntN[_TBitSize] | algopy.UInt64 | algopy.BigUInt | int,
168
+ ) -> bool:
169
+ raise NotImplementedError
170
+
171
+ def __le__(
172
+ self,
173
+ other: UIntN[_TBitSize] | BigUIntN[_TBitSize] | algopy.UInt64 | algopy.BigUInt | int,
174
+ ) -> bool:
175
+ raise NotImplementedError
176
+
177
+ def __lt__(
178
+ self,
179
+ other: UIntN[_TBitSize] | BigUIntN[_TBitSize] | algopy.UInt64 | algopy.BigUInt | int,
180
+ ) -> bool:
181
+ raise NotImplementedError
182
+
183
+ def __ge__(
184
+ self,
185
+ other: UIntN[_TBitSize] | BigUIntN[_TBitSize] | algopy.UInt64 | algopy.BigUInt | int,
186
+ ) -> bool:
187
+ raise NotImplementedError
188
+
189
+ def __gt__(
190
+ self,
191
+ other: UIntN[_TBitSize] | BigUIntN[_TBitSize] | algopy.UInt64 | algopy.BigUInt | int,
192
+ ) -> bool:
193
+ raise NotImplementedError
194
+
195
+ def __bool__(self) -> bool:
196
+ """Returns `True` if not equal to zero"""
197
+ raise NotImplementedError
198
+
199
+ @classmethod
200
+ def from_bytes(cls, value: algopy.Bytes | bytes, /) -> typing.Self:
201
+ """Construct an instance from the underlying bytes (no validation)"""
202
+
203
+ value = as_bytes(value)
204
+ result = cls()
205
+ result._value = value
206
+ return result
207
+
208
+ @property
209
+ def bytes(self) -> algopy.Bytes:
210
+ """Get the underlying Bytes"""
211
+ import algopy
212
+
213
+ return algopy.Bytes(self._value)
214
+
215
+ @classmethod
216
+ def from_log(cls, log: algopy.Bytes, /) -> typing.Self:
217
+ """Load an ABI type from application logs,
218
+ checking for the ABI return prefix `0x151f7c75`"""
219
+ import algopy
220
+
221
+ if log[:4] == algopy.Bytes(ARC4_RETURN_PREFIX):
222
+ return cls.from_bytes(log[4:])
223
+ raise ValueError("ABI return prefix not found")
224
+
225
+
226
+ class UIntN(_UIntN[_TBitSize], typing.Generic[_TBitSize]):
227
+ """An ARC4 UInt consisting of the number of bits specified.
228
+
229
+ Max Size: 64 bits"""
230
+
231
+ _max_bits_len = UINT64_SIZE
232
+
233
+ @property
234
+ def native(self) -> algopy.UInt64:
235
+ """Return the UInt64 representation of the value after ARC4 decoding"""
236
+ import algopy
237
+
238
+ return algopy.UInt64(int.from_bytes(self._value))
239
+
240
+ def __eq__(self, other: object) -> bool:
241
+ return as_int64(self.native) == as_int(other, max=None)
242
+
243
+ def __ne__(self, other: object) -> bool:
244
+ return as_int64(self.native) != as_int(other, max=None)
245
+
246
+ def __le__(self, other: object) -> bool:
247
+ return as_int64(self.native) <= as_int(other, max=None)
248
+
249
+ def __lt__(self, other: object) -> bool:
250
+ return as_int64(self.native) < as_int(other, max=None)
251
+
252
+ def __ge__(self, other: object) -> bool:
253
+ return as_int64(self.native) >= as_int(other, max=None)
254
+
255
+ def __gt__(self, other: object) -> bool:
256
+ return as_int64(self.native) > as_int(other, max=None)
257
+
258
+ def __bool__(self) -> bool:
259
+ return bool(self.native)
260
+
261
+
262
+ class BigUIntN(_UIntN[_TBitSize], typing.Generic[_TBitSize]):
263
+ """An ARC4 UInt consisting of the number of bits specified.
264
+
265
+ Max size: 512 bits"""
266
+
267
+ _max_bits_len = UINT512_SIZE
268
+
269
+ @property
270
+ def native(self) -> algopy.BigUInt:
271
+ """Return the UInt64 representation of the value after ARC4 decoding"""
272
+ import algopy
273
+
274
+ return algopy.BigUInt.from_bytes(self._value)
275
+
276
+ def __eq__(self, other: object) -> bool:
277
+ return as_int512(self.native) == as_int(other, max=None)
278
+
279
+ def __ne__(self, other: object) -> bool:
280
+ return as_int512(self.native) != as_int(other, max=None)
281
+
282
+ def __le__(self, other: object) -> bool:
283
+ return as_int512(self.native) <= as_int(other, max=None)
284
+
285
+ def __lt__(self, other: object) -> bool:
286
+ return as_int512(self.native) < as_int(other, max=None)
287
+
288
+ def __ge__(self, other: object) -> bool:
289
+ return as_int512(self.native) >= as_int(other, max=None)
290
+
291
+ def __gt__(self, other: object) -> bool:
292
+ return as_int512(self.native) > as_int(other, max=None)
293
+
294
+ def __bool__(self) -> bool:
295
+ return bool(self.native)
296
+
297
+
298
+ _TDecimalPlaces = typing.TypeVar("_TDecimalPlaces", bound=int)
299
+ _MAX_M_SIZE = 160
300
+
301
+
302
+ class _UFixedNxMMeta(type(_ABIEncoded), typing.Generic[_TBitSize, _TDecimalPlaces]): # type: ignore[misc]
303
+ __concrete__: typing.ClassVar[dict[tuple[type, type], type]] = {}
304
+
305
+ def __getitem__(cls, key_t: tuple[type[_TBitSize], type[_TDecimalPlaces]]) -> type:
306
+ cache = cls.__concrete__
307
+ if c := cache.get(key_t, None):
308
+ return c
309
+
310
+ size_t, decimal_t = key_t
311
+ cache[key_t] = c = types.new_class(
312
+ f"{cls.__name__}[{size_t.__name__}, {decimal_t.__name__}]",
313
+ (cls,),
314
+ {},
315
+ lambda ns: ns.update(_size_t=size_t, _decimal_t=decimal_t),
316
+ )
317
+ return c
318
+
319
+
320
+ class _UFixedNxM(
321
+ _ABIEncoded, typing.Generic[_TBitSize, _TDecimalPlaces], metaclass=_UFixedNxMMeta
322
+ ):
323
+ _size_t: type[_TBitSize]
324
+ _decimal_t: type[_TDecimalPlaces]
325
+ _n: int
326
+ _m: int
327
+ _max_bits_len: int
328
+ _max_bytes_len: int
329
+ _value: bytes # underlying 'bytes' value representing the UFixedNxM
330
+
331
+ def __init__(self, value: str = "0.0", /):
332
+ """
333
+ Construct an instance of UFixedNxM where value (v) is determined from the original
334
+ decimal value (d) by the formula v = round(d * (10^M))
335
+ """
336
+ self._n = as_int(typing.get_args(self._size_t)[0], max=self._max_bits_len)
337
+ self._m = as_int(typing.get_args(self._decimal_t)[0], max=_MAX_M_SIZE)
338
+ self._max_int = 2**self._n - 1
339
+ self._max_bytes_len = self._n // BITS_IN_BYTE
340
+
341
+ value = as_string(value)
342
+ with decimal.localcontext(
343
+ decimal.Context(
344
+ prec=160,
345
+ traps=[
346
+ decimal.Rounded,
347
+ decimal.InvalidOperation,
348
+ decimal.Overflow,
349
+ decimal.DivisionByZero,
350
+ ],
351
+ )
352
+ ):
353
+ try:
354
+ d = decimal.Decimal(value)
355
+ except ArithmeticError as ex:
356
+ raise ValueError(f"Invalid decimal literal: {value}") from ex
357
+ if d < 0:
358
+ raise ValueError("Negative numbers not allowed")
359
+ try:
360
+ q = d.quantize(decimal.Decimal(f"1e-{self._m}"))
361
+ except ArithmeticError as ex:
362
+ raise ValueError(f"Too many decimals, expected max of {self._m}") from ex
363
+
364
+ int_value = round(q * (10**self._m))
365
+ int_value = as_int(int_value, max=self._max_int)
366
+ bytes_value = int_to_bytes(int_value, self._max_bytes_len)
367
+ self._value = as_bytes(bytes_value, max_size=self._max_bytes_len)
368
+
369
+ def __bool__(self) -> bool:
370
+ """Returns `True` if not equal to zero"""
371
+ return bool(int.from_bytes(self._value))
372
+
373
+ @classmethod
374
+ def from_bytes(cls, value: algopy.Bytes | bytes, /) -> typing.Self:
375
+ """Construct an instance from the underlying bytes (no validation)"""
376
+ value = as_bytes(value)
377
+ result = cls()
378
+ result._value = value
379
+ return result
380
+
381
+ @property
382
+ def bytes(self) -> algopy.Bytes:
383
+ """Get the underlying Bytes"""
384
+ import algopy
385
+
386
+ return algopy.Bytes(self._value)
387
+
388
+ @classmethod
389
+ def from_log(cls, log: algopy.Bytes, /) -> typing.Self:
390
+ """Load an ABI type from application logs,
391
+ checking for the ABI return prefix `0x151f7c75`"""
392
+ import algopy
393
+
394
+ if log[:4] == algopy.Bytes(ARC4_RETURN_PREFIX):
395
+ return cls.from_bytes(log[4:])
396
+ raise ValueError("ABI return prefix not found")
397
+
398
+
399
+ class UFixedNxM(
400
+ _UFixedNxM[_TBitSize, _TDecimalPlaces], typing.Generic[_TBitSize, _TDecimalPlaces]
401
+ ):
402
+ """An ARC4 UFixed representing a decimal with the number of bits and precision specified.
403
+
404
+ Max size: 64 bits"""
405
+
406
+ _max_bits_len: int = UINT64_SIZE
407
+
408
+
409
+ class BigUFixedNxM(
410
+ _UFixedNxM[_TBitSize, _TDecimalPlaces], typing.Generic[_TBitSize, _TDecimalPlaces]
411
+ ):
412
+ """An ARC4 UFixed representing a decimal with the number of bits and precision specified.
413
+
414
+ Max size: 512 bits"""
415
+
416
+ _max_bits_len: int = UINT512_SIZE
417
+
418
+
419
+ class Byte(UIntN[typing.Literal[8]]):
420
+ """An ARC4 alias for a UInt8"""
421
+
422
+
423
+ UInt8: typing.TypeAlias = UIntN[typing.Literal[8]]
424
+ """An ARC4 UInt8"""
425
+
426
+ UInt16: typing.TypeAlias = UIntN[typing.Literal[16]]
427
+ """An ARC4 UInt16"""
428
+
429
+ UInt32: typing.TypeAlias = UIntN[typing.Literal[32]]
430
+ """An ARC4 UInt32"""
431
+
432
+ UInt64: typing.TypeAlias = UIntN[typing.Literal[64]]
433
+ """An ARC4 UInt64"""
434
+
435
+ UInt128: typing.TypeAlias = BigUIntN[typing.Literal[128]]
436
+ """An ARC4 UInt128"""
437
+
438
+ UInt256: typing.TypeAlias = BigUIntN[typing.Literal[256]]
439
+ """An ARC4 UInt256"""
440
+
441
+ UInt512: typing.TypeAlias = BigUIntN[typing.Literal[512]]
442
+ """An ARC4 UInt512"""
443
+
444
+
445
+ class Bool(_ABIEncoded):
446
+ """An ARC4 encoded bool"""
447
+
448
+ _value: bytes
449
+
450
+ # True value is encoded as having a 1 on the most significant bit (0x80 = 128)
451
+ _true_int_value = 128
452
+ _false_int_value = 0
453
+
454
+ def __init__(self, value: bool = False, /) -> None: # noqa: FBT001, FBT002
455
+ self._value = int_to_bytes(self._true_int_value if value else self._false_int_value, 1)
456
+
457
+ def __bool__(self) -> bool:
458
+ """Allow Bool to be used in boolean contexts"""
459
+ return self.native
460
+
461
+ @property
462
+ def native(self) -> bool:
463
+ """Return the bool representation of the value after ARC4 decoding"""
464
+ int_value = int.from_bytes(self._value)
465
+ return int_value == self._true_int_value
466
+
467
+ @classmethod
468
+ def from_bytes(cls, value: algopy.Bytes | bytes, /) -> typing.Self:
469
+ """Construct an instance from the underlying bytes (no validation)"""
470
+ result = cls()
471
+ result._value = as_bytes(value)
472
+ return result
473
+
474
+ @property
475
+ def bytes(self) -> algopy.Bytes:
476
+ """Get the underlying Bytes"""
477
+ import algopy
478
+
479
+ return algopy.Bytes(self._value)
480
+
481
+ @classmethod
482
+ def from_log(cls, log: algopy.Bytes, /) -> typing.Self:
483
+ """Load an ABI type from application logs,
484
+ checking for the ABI return prefix `0x151f7c75`"""
485
+ import algopy
486
+
487
+ if log[:4] == algopy.Bytes(ARC4_RETURN_PREFIX):
488
+ return cls.from_bytes(log[4:])
489
+ raise ValueError("ABI return prefix not found")
490
+
491
+
492
+ _TArrayItem = typing.TypeVar("_TArrayItem")
493
+ _TArrayLength = typing.TypeVar("_TArrayLength", bound=int)
494
+
495
+
496
+ class _StaticArrayMeta(type(_ABIEncoded), typing.Generic[_TArrayItem, _TArrayLength]): # type: ignore # noqa: PGH003
497
+ __concrete__: typing.ClassVar[dict[tuple[type, type], type]] = {}
498
+
499
+ def __getitem__(cls, key_t: tuple[type[_TArrayItem], type[_TArrayLength]]) -> type:
500
+ cache = cls.__concrete__
501
+ if c := cache.get(key_t, None):
502
+ return c
503
+
504
+ cache[key_t] = c = types.new_class(
505
+ f"{cls.__name__}[{','.join([k.__name__ for k in key_t])}]",
506
+ (cls,),
507
+ {},
508
+ lambda ns: ns.update(
509
+ _child_types=[_TypeInfo(key_t[0])]
510
+ * (typing.get_args(key_t[1])[0] if len(typing.get_args(key_t[1])) > 0 else 0),
511
+ _array_item_t=key_t[0],
512
+ ),
513
+ )
514
+ return c
515
+
516
+
517
+ class _TypeInfo:
518
+ value: type
519
+ child_types: list[_TypeInfo] | None
520
+
521
+ def __init__(self, value: type, child_types: list[_TypeInfo] | None = None):
522
+ self.value = value
523
+ self.child_types = child_types or (
524
+ value._child_types if hasattr(value, "_child_types") else child_types
525
+ )
526
+
527
+
528
+ class StaticArray(
529
+ _ABIEncoded,
530
+ typing.Generic[_TArrayItem, _TArrayLength],
531
+ Reversible[_TArrayItem],
532
+ metaclass=_StaticArrayMeta,
533
+ ):
534
+ """A fixed length ARC4 Array of the specified type and length"""
535
+
536
+ _array_item_t: type[_TArrayItem]
537
+ _child_types: list[_TypeInfo]
538
+
539
+ _value: bytes
540
+
541
+ def __init__(self, *items: _TArrayItem):
542
+ self._value = _encode(items)
543
+ if items:
544
+ self._array_item_t = type(items[0])
545
+ self._child_types = [self._get_type_info(item) for item in items]
546
+
547
+ # ensure these two variables are set as instance variables instead of class variables
548
+ # to avoid sharing state between instances created by copy operation
549
+ if hasattr(self, "_array_item_t"):
550
+ self._array_item_t = self._array_item_t
551
+ self._child_types = self._child_types or [] if hasattr(self, "_child_types") else []
552
+
553
+ def _get_type_info(self, item: _TArrayItem) -> _TypeInfo:
554
+ return _TypeInfo(
555
+ self._array_item_t,
556
+ item._child_types if hasattr(item, "_child_types") else None,
557
+ )
558
+
559
+ def __iter__(self) -> typing.Iterator[_TArrayItem]:
560
+ # """Returns an iterator for the items in the array"""
561
+ return iter(self._list())
562
+
563
+ def __reversed__(self) -> typing.Iterator[_TArrayItem]:
564
+ # """Returns an iterator for the items in the array, in reverse order"""
565
+ return reversed(self._list())
566
+
567
+ @property
568
+ def length(self) -> algopy.UInt64:
569
+ # """Returns the current length of the array"""
570
+ import algopy
571
+
572
+ return algopy.UInt64(len(self._list()))
573
+
574
+ @typing.overload
575
+ def __getitem__(self, value: algopy.UInt64 | int, /) -> _TArrayItem: ...
576
+ @typing.overload
577
+ def __getitem__(self, value: slice, /) -> list[_TArrayItem]: ...
578
+ def __getitem__(self, index: algopy.UInt64 | int | slice) -> _TArrayItem | list[_TArrayItem]:
579
+ if isinstance(index, slice):
580
+ return self._list()[index]
581
+ return self._list()[index]
582
+
583
+ def append(self, item: _TArrayItem, /) -> None:
584
+ """Append items to this array"""
585
+ if not issubclass(type(item), self._array_item_t):
586
+ expected_type = self._array_item_t.__name__
587
+ actual_type = type(item).__name__
588
+ raise TypeError(f"item must be of type {expected_type!r}, not {actual_type!r}")
589
+ x = self._list()
590
+ x.append(item)
591
+ self._child_types.append(self._get_type_info(item))
592
+ self._value = _encode(x)
593
+
594
+ def extend(self, other: Iterable[_TArrayItem], /) -> None:
595
+ """Extend this array with the contents of another array"""
596
+ if any(not issubclass(type(x), self._array_item_t) for x in other):
597
+ raise TypeError(f"items must be of type {self._array_item_t.__name__!r}")
598
+ x = self._list()
599
+ x.extend(other)
600
+ self._child_types.extend([self._get_type_info(x) for x in other])
601
+ self._value = _encode(x)
602
+
603
+ def __setitem__(self, index: algopy.UInt64 | int, value: _TArrayItem) -> _TArrayItem:
604
+ if not issubclass(type(value), self._array_item_t):
605
+ expected_type = self._array_item_t.__name__
606
+ actual_type = type(value).__name__
607
+ raise TypeError(f"item must be of type {expected_type!r}, not {actual_type!r}")
608
+ x = self._list()
609
+ x[index] = value
610
+ self._value = _encode(x)
611
+ return value
612
+
613
+ def copy(self) -> typing.Self:
614
+ # """Create a copy of this array"""
615
+ return copy.deepcopy(self)
616
+
617
+ def _list(self) -> list[_TArrayItem]:
618
+ return _decode(self._value, self._child_types)
619
+
620
+ @classmethod
621
+ def from_bytes(cls, value: algopy.Bytes | bytes, /) -> typing.Self:
622
+ """Construct an instance from the underlying bytes (no validation)"""
623
+ value = as_bytes(value)
624
+ result = cls()
625
+ result._value = value
626
+ return result
627
+
628
+ @property
629
+ def bytes(self) -> algopy.Bytes:
630
+ """Get the underlying Bytes"""
631
+ import algopy
632
+
633
+ return algopy.Bytes(self._value)
634
+
635
+ @classmethod
636
+ def from_log(cls, log: algopy.Bytes, /) -> typing.Self:
637
+ """Load an ABI type from application logs,
638
+ checking for the ABI return prefix `0x151f7c75`"""
639
+ import algopy
640
+
641
+ if log[:4] == algopy.Bytes(ARC4_RETURN_PREFIX):
642
+ return cls.from_bytes(log[4:])
643
+ raise ValueError("ABI return prefix not found")
644
+
645
+
646
+ class Address(StaticArray[Byte, typing.Literal[32]]):
647
+ """An alias for an array containing 32 bytes representing an Algorand address"""
648
+
649
+ def __init__(self, value: Account | str | algopy.Bytes = algosdk.constants.ZERO_ADDRESS):
650
+ """
651
+ If `value` is a string, it should be a 58 character base32 string,
652
+ ie a base32 string-encoded 32 bytes public key + 4 bytes checksum.
653
+ If `value` is a Bytes, it's length checked to be 32 bytes - to avoid this
654
+ check, use `Address.from_bytes(...)` instead.
655
+ Defaults to the zero-address.
656
+ """
657
+ if isinstance(value, str):
658
+ try:
659
+ bytes_value = algosdk.encoding.decode_address(value)
660
+ except Exception as e:
661
+ raise ValueError(f"cannot encode the following address: {value!r}") from e
662
+ else:
663
+ bytes_value = (
664
+ value.bytes.value if isinstance(value, Account) else as_bytes(value, max_size=32)
665
+ )
666
+ if len(bytes_value) != 32:
667
+ raise ValueError(f"expected 32 bytes, got: {len(bytes_value)}")
668
+ self._value = bytes_value
669
+
670
+ @property
671
+ def native(self) -> Account:
672
+ # """Return the Account representation of the address after ARC4 decoding"""
673
+ return Account(self.bytes)
674
+
675
+ def __bool__(self) -> bool:
676
+ # """Returns `True` if not equal to the zero address"""
677
+ zero_bytes = algosdk.encoding.decode_address(algosdk.constants.ZERO_ADDRESS)
678
+ return self.bytes != zero_bytes if isinstance(zero_bytes, bytes) else False
679
+
680
+ def __eq__(self, other: Address | Account | str) -> bool: # type: ignore[override]
681
+ """Address equality is determined by the address of another
682
+ `arc4.Address`, `Account` or `str`"""
683
+ if isinstance(other, Address | Account):
684
+ return self.bytes == other.bytes
685
+ other_bytes = algosdk.encoding.decode_address(other)
686
+ return self.bytes == other_bytes if isinstance(other_bytes, bytes) else False
687
+
688
+
689
+ class _DynamicArrayMeta(type(_ABIEncoded), typing.Generic[_TArrayItem, _TArrayLength]): # type: ignore # noqa: PGH003
690
+ __concrete__: typing.ClassVar[dict[type, type]] = {}
691
+
692
+ def __getitem__(cls, key_t: type[_TArrayItem]) -> type:
693
+ cache = cls.__concrete__
694
+ if c := cache.get(key_t, None):
695
+ return c
696
+
697
+ cache[key_t] = c = types.new_class(
698
+ f"{cls.__name__}[{key_t.__name__}]",
699
+ (cls,),
700
+ {},
701
+ lambda ns: ns.update(
702
+ _child_types=[],
703
+ _array_item_t=key_t,
704
+ ),
705
+ )
706
+ return c
707
+
708
+
709
+ class DynamicArray(
710
+ _ABIEncoded,
711
+ typing.Generic[_TArrayItem],
712
+ Reversible[_TArrayItem],
713
+ metaclass=_DynamicArrayMeta,
714
+ ):
715
+ """A dynamically sized ARC4 Array of the specified type"""
716
+
717
+ _array_item_t: type[_TArrayItem]
718
+ _child_types: list[_TypeInfo]
719
+
720
+ _value: bytes
721
+
722
+ def __init__(self, *items: _TArrayItem):
723
+ self._value = self._encode_with_length(items)
724
+ if items:
725
+ self._array_item_t = type(items[0])
726
+ self._child_types = [self._get_type_info(item) for item in items]
727
+
728
+ # ensure these two variables are set as instance variables instead of class variables
729
+ # to avoid sharing state between instances created by copy operation
730
+ if hasattr(self, "_array_item_t"):
731
+ self._array_item_t = self._array_item_t
732
+ self._child_types = self._child_types or [] if hasattr(self, "_child_types") else []
733
+
734
+ def _get_type_info(self, item: _TArrayItem) -> _TypeInfo:
735
+ return _TypeInfo(
736
+ self._array_item_t,
737
+ item._child_types if hasattr(item, "_child_types") else None,
738
+ )
739
+
740
+ def __iter__(self) -> typing.Iterator[_TArrayItem]:
741
+ """Returns an iterator for the items in the array"""
742
+ return iter(self._list())
743
+
744
+ def __reversed__(self) -> typing.Iterator[_TArrayItem]:
745
+ """Returns an iterator for the items in the array, in reverse order"""
746
+ return reversed(self._list())
747
+
748
+ @property
749
+ def length(self) -> algopy.UInt64:
750
+ """Returns the current length of the array"""
751
+ import algopy
752
+
753
+ return algopy.UInt64(len(self._list()))
754
+
755
+ @typing.overload
756
+ def __getitem__(self, value: algopy.UInt64 | int, /) -> _TArrayItem: ...
757
+ @typing.overload
758
+ def __getitem__(self, value: slice, /) -> list[_TArrayItem]: ...
759
+ def __getitem__(self, index: algopy.UInt64 | int | slice) -> _TArrayItem | list[_TArrayItem]:
760
+ if isinstance(index, slice):
761
+ return self._list()[index]
762
+ return self._list()[index]
763
+
764
+ def append(self, item: _TArrayItem, /) -> None:
765
+ """Append items to this array"""
766
+ if not issubclass(type(item), self._array_item_t):
767
+ expected_type = self._array_item_t.__name__
768
+ actual_type = type(item).__name__
769
+ raise TypeError(f"item must be of type {expected_type!r}, not {actual_type!r}")
770
+ x = self._list()
771
+ x.append(item)
772
+ self._child_types.append(self._get_type_info(item))
773
+ self._value = self._encode_with_length(x)
774
+
775
+ def extend(self, other: Iterable[_TArrayItem], /) -> None:
776
+ """Extend this array with the contents of another array"""
777
+ if any(not issubclass(type(x), self._array_item_t) for x in other):
778
+ raise TypeError(f"items must be of type {self._array_item_t.__name__!r}")
779
+ x = self._list()
780
+ x.extend(other)
781
+ self._child_types.extend([self._get_type_info(x) for x in other])
782
+ self._value = self._encode_with_length(x)
783
+
784
+ def __setitem__(self, index: algopy.UInt64 | int, value: _TArrayItem) -> _TArrayItem:
785
+ if not issubclass(type(value), self._array_item_t):
786
+ expected_type = self._array_item_t.__name__
787
+ actual_type = type(value).__name__
788
+ raise TypeError(f"item must be of type {expected_type!r}, not {actual_type!r}")
789
+ x = self._list()
790
+ x[index] = value
791
+ self._value = self._encode_with_length(x)
792
+ return value
793
+
794
+ def __add__(self, other: Iterable[_TArrayItem]) -> DynamicArray[_TArrayItem]:
795
+ self.extend(other)
796
+ return self
797
+
798
+ def pop(self) -> _TArrayItem:
799
+ """Remove and return the last item in the array"""
800
+ x = self._list()
801
+ item = x.pop()
802
+ self._child_types.pop()
803
+ self._value = self._encode_with_length(x)
804
+ return item
805
+
806
+ def copy(self) -> typing.Self:
807
+ """Create a copy of this array"""
808
+ return copy.deepcopy(self)
809
+
810
+ def __bool__(self) -> bool:
811
+ """Returns `True` if not an empty array"""
812
+ return bool(self._list())
813
+
814
+ def _list(self) -> list[_TArrayItem]:
815
+ length = int.from_bytes(self._value[:_ABI_LENGTH_SIZE])
816
+ self._child_types = self._child_types or []
817
+ if hasattr(self, "_array_item_t"):
818
+ self._child_types += [_TypeInfo(self._array_item_t)] * (
819
+ length - len(self._child_types)
820
+ )
821
+ return _decode(self._value[_ABI_LENGTH_SIZE:], self._child_types)
822
+
823
+ def _encode_with_length(self, items: list[_TArrayItem] | tuple[_TArrayItem, ...]) -> bytes:
824
+ return len(items).to_bytes(_ABI_LENGTH_SIZE) + _encode(items)
825
+
826
+ @classmethod
827
+ def from_bytes(cls, value: algopy.Bytes | bytes, /) -> typing.Self:
828
+ """Construct an instance from the underlying bytes (no validation)"""
829
+ value = as_bytes(value)
830
+ result = cls()
831
+ result._value = value
832
+ return result
833
+
834
+ @property
835
+ def bytes(self) -> algopy.Bytes:
836
+ """Get the underlying Bytes"""
837
+ import algopy
838
+
839
+ return algopy.Bytes(self._value)
840
+
841
+ @classmethod
842
+ def from_log(cls, log: algopy.Bytes, /) -> typing.Self:
843
+ """Load an ABI type from application logs,
844
+ checking for the ABI return prefix `0x151f7c75`"""
845
+ import algopy
846
+
847
+ if log[:4] == algopy.Bytes(ARC4_RETURN_PREFIX):
848
+ return cls.from_bytes(log[4:])
849
+ raise ValueError("ABI return prefix not found")
850
+
851
+
852
+ class DynamicBytes(DynamicArray[Byte]):
853
+ """A variable sized array of bytes"""
854
+
855
+ @typing.overload
856
+ def __init__(self, *values: Byte | UInt8 | int): ...
857
+
858
+ @typing.overload
859
+ def __init__(self, value: algopy.Bytes | bytes, /): ...
860
+
861
+ def __init__(
862
+ self,
863
+ *value: algopy.Bytes | bytes | Byte | UInt8 | int,
864
+ ):
865
+ import algopy
866
+
867
+ items = []
868
+ for x in value:
869
+ if isinstance(x, int):
870
+ items.append(Byte(x))
871
+ elif isinstance(x, Byte):
872
+ items.append(x)
873
+ elif isinstance(x, UInt8): # type: ignore[misc]
874
+ items.append(typing.cast(Byte, x))
875
+ elif isinstance(x, algopy.Bytes):
876
+ items.extend([Byte(int.from_bytes(i.value)) for i in x])
877
+ elif isinstance(x, bytes):
878
+ items.extend([Byte(int.from_bytes(i.value)) for i in algopy.Bytes(x)])
879
+
880
+ super().__init__(*items)
881
+
882
+ @property
883
+ def native(self) -> algopy.Bytes:
884
+ """Return the Bytes representation of the address after ARC4 decoding"""
885
+ return self.bytes
886
+
887
+
888
+ _TTuple = typing.TypeVarTuple("_TTuple")
889
+
890
+
891
+ class _TupleMeta(type(_ABIEncoded), typing.Generic[typing.Unpack[_TTuple]]): # type: ignore # noqa: PGH003
892
+ __concrete__: typing.ClassVar[dict[tuple, type]] = {} # type: ignore[type-arg]
893
+
894
+ def __getitem__(cls, key_t: tuple) -> type: # type: ignore[type-arg]
895
+ cache = cls.__concrete__
896
+ if c := cache.get(key_t, None):
897
+ return c
898
+
899
+ cache[key_t] = c = types.new_class(
900
+ f"{cls.__name__}[{key_t}]",
901
+ (cls,),
902
+ {},
903
+ lambda ns: ns.update(
904
+ _child_types=[_TypeInfo(typing.cast(type, item)) for item in key_t],
905
+ ),
906
+ )
907
+ return c
908
+
909
+
910
+ class Tuple(
911
+ _ABIEncoded,
912
+ tuple[*_TTuple],
913
+ typing.Generic[typing.Unpack[_TTuple]],
914
+ metaclass=_TupleMeta,
915
+ ):
916
+ """An ARC4 ABI tuple, containing other ARC4 ABI types"""
917
+
918
+ __slots__ = ()
919
+
920
+ _child_types: list[_TypeInfo]
921
+ _value: bytes
922
+
923
+ def __init__(self, items: tuple[typing.Unpack[_TTuple]] = (), /): # type: ignore[assignment]
924
+ """Construct an ARC4 tuple from a python tuple"""
925
+ self._value = _encode(items)
926
+ if items:
927
+ self._child_types = [self._get_type_info(item) for item in items]
928
+
929
+ # ensure the variable is set as instance variables instead of class variables
930
+ # to avoid sharing state between instances created by copy operation
931
+ self._child_types = self._child_types or [] if hasattr(self, "_child_types") else []
932
+
933
+ def _get_type_info(self, item: typing.Any) -> _TypeInfo:
934
+ return _TypeInfo(
935
+ type(item),
936
+ item._child_types if hasattr(item, "_child_types") else None,
937
+ )
938
+
939
+ def __len__(self) -> int:
940
+ return len(self.native)
941
+
942
+ @typing.overload
943
+ def __getitem__(self, value: typing.SupportsIndex, /) -> object: ...
944
+ @typing.overload
945
+ def __getitem__(self, value: slice, /) -> tuple[object, ...]: ...
946
+
947
+ def __getitem__(self, index: typing.SupportsIndex | slice) -> tuple[object, ...] | object:
948
+ return self.native[index]
949
+
950
+ def __iter__(self) -> typing.Iterator[object]:
951
+ return iter(self.native)
952
+
953
+ @property
954
+ def native(self) -> tuple[typing.Unpack[_TTuple]]:
955
+ """Return the Bytes representation of the address after ARC4 decoding"""
956
+ return typing.cast(
957
+ tuple[typing.Unpack[_TTuple]], tuple(_decode(self._value, self._child_types))
958
+ )
959
+
960
+ @classmethod
961
+ def from_bytes(cls, value: algopy.Bytes | bytes, /) -> typing.Self:
962
+ """Construct an instance from the underlying bytes (no validation)"""
963
+ value = as_bytes(value)
964
+ result = cls()
965
+ result._value = value
966
+ return result
967
+
968
+ @property
969
+ def bytes(self) -> algopy.Bytes:
970
+ """Get the underlying Bytes"""
971
+ import algopy
972
+
973
+ return algopy.Bytes(self._value)
974
+
975
+ @classmethod
976
+ def from_log(cls, log: algopy.Bytes, /) -> typing.Self:
977
+ """Load an ABI type from application logs,
978
+ checking for the ABI return prefix `0x151f7c75`"""
979
+ import algopy
980
+
981
+ if log[:4] == algopy.Bytes(ARC4_RETURN_PREFIX):
982
+ return cls.from_bytes(log[4:])
983
+ raise ValueError("ABI return prefix not found")
984
+
985
+
986
+ @typing.dataclass_transform(
987
+ eq_default=False, order_default=False, kw_only_default=False, field_specifiers=()
988
+ )
989
+ class _StructMeta(type):
990
+ def __new__(
991
+ cls,
992
+ name: str,
993
+ bases: tuple[type, ...],
994
+ namespace: dict[str, object],
995
+ ) -> _StructMeta:
996
+ return super().__new__(cls, name, bases, namespace)
997
+
998
+
999
+ class Struct(metaclass=_StructMeta):
1000
+ """Base class for ARC4 Struct types"""
1001
+
1002
+ def __init__(self, *args: typing.Any):
1003
+ self._value = Tuple(args)
1004
+
1005
+ @classmethod
1006
+ def from_bytes(cls, value: algopy.Bytes | bytes, /) -> typing.Self:
1007
+ annotations = _all_annotations(cls)
1008
+
1009
+ result = cls()
1010
+ tuple_value = Tuple[tuple(v for k, v in annotations.items())].from_bytes(value) # type: ignore[misc, attr-defined]
1011
+ result._value = tuple_value
1012
+
1013
+ return result
1014
+
1015
+ @property
1016
+ def bytes(self) -> algopy.Bytes:
1017
+ """Get the underlying bytes[]"""
1018
+ return self._value.bytes
1019
+
1020
+ @classmethod
1021
+ def from_log(cls, log: algopy.Bytes, /) -> typing.Self:
1022
+ """Load an ABI type from application logs,
1023
+ checking for the ABI return prefix `0x151f7c75`"""
1024
+ import algopy
1025
+
1026
+ if log[:4] == algopy.Bytes(ARC4_RETURN_PREFIX):
1027
+ return cls.from_bytes(log[4:])
1028
+ raise ValueError("ABI return prefix not found")
1029
+
1030
+ def copy(self) -> typing.Self:
1031
+ """Create a copy of this struct"""
1032
+ return copy.deepcopy(self)
1033
+
1034
+
1035
+ class ARC4Client(typing.Protocol): ...
1036
+
1037
+
1038
+ if typing.TYPE_CHECKING:
1039
+ _TABIArg: typing.TypeAlias = (
1040
+ algopy.String
1041
+ | algopy.BigUInt
1042
+ | algopy.UInt64
1043
+ | algopy.Bytes
1044
+ | algopy.Asset
1045
+ | algopy.Account
1046
+ | algopy.Application
1047
+ | UIntN[typing.Any]
1048
+ | BigUIntN[typing.Any]
1049
+ | UFixedNxM[typing.Any, typing.Any]
1050
+ | BigUFixedNxM[typing.Any, typing.Any]
1051
+ | Bool
1052
+ | String
1053
+ | StaticArray[typing.Any, typing.Any]
1054
+ | DynamicArray[typing.Any]
1055
+ | Tuple # type: ignore[type-arg]
1056
+ | int
1057
+ | bool
1058
+ | bytes
1059
+ | str
1060
+ )
1061
+
1062
+
1063
+ _TABIResult_co = typing.TypeVar("_TABIResult_co", covariant=True)
1064
+
1065
+
1066
+ class _ABICallWithReturnProtocol(typing.Protocol[_TABIResult_co]):
1067
+ def __call__( # noqa: PLR0913
1068
+ self,
1069
+ method: str,
1070
+ /,
1071
+ *args: _TABIArg,
1072
+ app_id: algopy.Application | algopy.UInt64 | int = ...,
1073
+ on_completion: algopy.OnCompleteAction = ...,
1074
+ approval_program: algopy.Bytes | bytes | tuple[algopy.Bytes, ...] = ...,
1075
+ clear_state_program: algopy.Bytes | bytes | tuple[algopy.Bytes, ...] = ...,
1076
+ global_num_uint: UInt64 | int = ...,
1077
+ global_num_bytes: UInt64 | int = ...,
1078
+ local_num_uint: UInt64 | int = ...,
1079
+ local_num_bytes: UInt64 | int = ...,
1080
+ extra_program_pages: UInt64 | int = ...,
1081
+ fee: algopy.UInt64 | int = 0,
1082
+ sender: algopy.Account | str = ...,
1083
+ note: algopy.Bytes | bytes | str = ...,
1084
+ rekey_to: algopy.Account | str = ...,
1085
+ ) -> tuple[_TABIResult_co, algopy.itxn.ApplicationCallInnerTransaction]: ...
1086
+
1087
+
1088
+ class _ABICallProtocolType(typing.Protocol):
1089
+ @typing.overload
1090
+ def __call__(
1091
+ self,
1092
+ method: typing.Callable[..., None] | str,
1093
+ /,
1094
+ *args: _TABIArg,
1095
+ app_id: algopy.Application | algopy.UInt64 | int = ...,
1096
+ on_completion: algopy.OnCompleteAction = ...,
1097
+ approval_program: algopy.Bytes | bytes | tuple[algopy.Bytes, ...] = ...,
1098
+ clear_state_program: algopy.Bytes | bytes | tuple[algopy.Bytes, ...] = ...,
1099
+ global_num_uint: UInt64 | int = ...,
1100
+ global_num_bytes: UInt64 | int = ...,
1101
+ local_num_uint: UInt64 | int = ...,
1102
+ local_num_bytes: UInt64 | int = ...,
1103
+ extra_program_pages: UInt64 | int = ...,
1104
+ fee: algopy.UInt64 | int = 0,
1105
+ sender: algopy.Account | str = ...,
1106
+ note: algopy.Bytes | bytes | str = ...,
1107
+ rekey_to: algopy.Account | str = ...,
1108
+ ) -> algopy.itxn.ApplicationCallInnerTransaction: ...
1109
+
1110
+ @typing.overload
1111
+ def __call__( # type: ignore[misc, unused-ignore]
1112
+ self,
1113
+ method: typing.Callable[..., _TABIResult_co],
1114
+ /,
1115
+ *args: _TABIArg,
1116
+ app_id: algopy.Application | algopy.UInt64 | int = ...,
1117
+ on_completion: algopy.OnCompleteAction = ...,
1118
+ approval_program: algopy.Bytes | bytes | tuple[algopy.Bytes, ...] = ...,
1119
+ clear_state_program: algopy.Bytes | bytes | tuple[algopy.Bytes, ...] = ...,
1120
+ global_num_uint: UInt64 | int = ...,
1121
+ global_num_bytes: UInt64 | int = ...,
1122
+ local_num_uint: UInt64 | int = ...,
1123
+ local_num_bytes: UInt64 | int = ...,
1124
+ extra_program_pages: UInt64 | int = ...,
1125
+ fee: algopy.UInt64 | int = 0,
1126
+ sender: algopy.Account | str = ...,
1127
+ note: algopy.Bytes | bytes | str = ...,
1128
+ rekey_to: algopy.Account | str = ...,
1129
+ ) -> tuple[_TABIResult_co, algopy.itxn.ApplicationCallInnerTransaction]: ...
1130
+
1131
+ def __getitem__(
1132
+ self, _: type[_TABIResult_co]
1133
+ ) -> _ABICallWithReturnProtocol[_TABIResult_co]: ...
1134
+
1135
+
1136
+ class _ABICall:
1137
+ def __call__(
1138
+ self,
1139
+ method: typing.Callable[..., typing.Any] | str,
1140
+ /,
1141
+ *args: _TABIArg,
1142
+ **kwargs: typing.Any,
1143
+ ) -> typing.Any:
1144
+ # Implement the actual abi_call logic here
1145
+ raise NotImplementedError("abi_call is not implemented")
1146
+
1147
+ def __getitem__(
1148
+ self, return_type: type[_TABIResult_co]
1149
+ ) -> _ABICallWithReturnProtocol[_TABIResult_co]:
1150
+ return self
1151
+
1152
+
1153
+ # TODO: Implement abi_call
1154
+ abi_call: _ABICallProtocolType = _ABICall()
1155
+
1156
+
1157
+ @typing.overload
1158
+ def emit(event: Struct, /) -> None: ...
1159
+ @typing.overload
1160
+ def emit(event: str, /, *args: _TABIArg) -> None: ...
1161
+ def emit(event: str | Struct, /, *args: _TABIArg) -> None:
1162
+ """Emit an ARC-28 event for the provided event signature or name, and provided args.
1163
+
1164
+ :param event: Either an ARC4 Struct, an event name, or event signature.
1165
+ * If event is an ARC4 Struct, the event signature will be determined from the Struct name and fields
1166
+ * If event is a signature, then the following args will be typed checked to ensure they match.
1167
+ * If event is just a name, the event signature will be inferred from the name and following arguments
1168
+
1169
+ :param args: When event is a signature or name, args will be used as the event data.
1170
+ They will all be encoded as single ARC4 Tuple
1171
+
1172
+ Example:
1173
+ ```
1174
+ from algopy import ARC4Contract, arc4
1175
+
1176
+
1177
+ class Swapped(arc4.Struct):
1178
+ a: arc4.UInt64
1179
+ b: arc4.UInt64
1180
+
1181
+
1182
+ class EventEmitter(ARC4Contract):
1183
+ @arc4.abimethod
1184
+ def emit_swapped(self, a: arc4.UInt64, b: arc4.UInt64) -> None:
1185
+ arc4.emit(Swapped(b, a))
1186
+ arc4.emit("Swapped(uint64,uint64)", b, a)
1187
+ arc4.emit("Swapped", b, a)
1188
+ ```
1189
+ """ # noqa: E501
1190
+ import algopy
1191
+
1192
+ from algopy_testing.context import get_test_context
1193
+
1194
+ context = get_test_context()
1195
+ active_txn = context.get_active_transaction()
1196
+
1197
+ if not active_txn:
1198
+ raise ValueError("Cannot emit events outside of application call context!")
1199
+ if active_txn.type != algopy.TransactionType.ApplicationCall:
1200
+ raise ValueError("Cannot emit events outside of application call context!")
1201
+ if not active_txn.app_id:
1202
+ raise ValueError("Cannot emit event: missing `app_id` in associated call transaction!")
1203
+
1204
+ if isinstance(event, str):
1205
+ arg_types = "(" + ",".join(abi_type_name_for_arg(arg=arg) for arg in args) + ")"
1206
+
1207
+ if event.find("(") == -1:
1208
+ event += arg_types
1209
+ elif event.find(arg_types) == -1:
1210
+ raise ValueError(f"Event signature {event} does not match args {args}")
1211
+
1212
+ args_tuple = tuple(_cast_arg_as_arc4(arg) for arg in args)
1213
+ event_hash = algopy.Bytes(SHA512.new(event.encode(), truncate="256").digest())
1214
+ context.add_application_logs(
1215
+ app_id=active_txn.app_id(),
1216
+ logs=(event_hash[:4] + Struct(*args_tuple).bytes).value,
1217
+ )
1218
+ elif isinstance(event, Struct):
1219
+ arg_types = "(" + ",".join(abi_type_name_for_arg(arg=arg) for arg in event._value) + ")"
1220
+ event_str = event.__class__.__name__ + arg_types
1221
+ event_hash = algopy.Bytes(SHA512.new(event_str.encode(), truncate="256").digest())
1222
+ context.add_application_logs(
1223
+ app_id=active_txn.app_id(),
1224
+ logs=(event_hash[:4] + event.bytes).value,
1225
+ )
1226
+
1227
+
1228
+ def _cast_arg_as_arc4(arg: object) -> _TABIArg: # noqa: C901, PLR0911
1229
+ import algopy
1230
+
1231
+ if isinstance(arg, bool):
1232
+ return Bool(arg)
1233
+ if isinstance(arg, algopy.UInt64):
1234
+ return UInt64(arg)
1235
+ if isinstance(arg, algopy.BigUInt):
1236
+ return UInt512(arg)
1237
+ if isinstance(arg, int):
1238
+ return UInt64(arg) if arg <= MAX_UINT64 else UInt512(arg)
1239
+ if isinstance(arg, algopy.Bytes | bytes):
1240
+ return DynamicBytes(arg)
1241
+ if isinstance(arg, algopy.String | str):
1242
+ return String(arg)
1243
+ if isinstance(arg, algopy.Asset):
1244
+ return UInt64(arg.id)
1245
+ if isinstance(arg, algopy.Account):
1246
+ return Address(arg)
1247
+ if isinstance(arg, algopy.Application):
1248
+ return UInt64(arg.id)
1249
+
1250
+ if isinstance(
1251
+ arg,
1252
+ UIntN | BigUIntN | UFixedNxM | BigUFixedNxM | Bool | StaticArray | DynamicArray | Tuple,
1253
+ ):
1254
+ return arg
1255
+ raise ValueError(f"Unsupported type {type(arg)}")
1256
+
1257
+
1258
+ # https://stackoverflow.com/a/72037059
1259
+ def _all_annotations(cls: type) -> ChainMap[str, type]:
1260
+ """Returns a dictionary-like ChainMap that includes annotations for all
1261
+ attributes defined in cls or inherited from superclasses."""
1262
+ return ChainMap(*(c.__annotations__ for c in cls.__mro__ if "__annotations__" in c.__dict__))
1263
+
1264
+
1265
+ def _is_arc4_dynamic(value: object) -> bool:
1266
+ if isinstance(value, DynamicArray):
1267
+ return True
1268
+ elif isinstance(value, StaticArray | Tuple):
1269
+ return any(_is_arc4_dynamic(v) for v in value)
1270
+ return not isinstance(value, BigUFixedNxM | BigUIntN | UFixedNxM | UIntN | Bool)
1271
+
1272
+
1273
+ def _is_arc4_dynamic_type(value: _TypeInfo) -> bool:
1274
+ if issubclass(value.value, DynamicArray):
1275
+ return True
1276
+ elif value.child_types:
1277
+ return any(_is_arc4_dynamic_type(v) for v in value.child_types)
1278
+ return not issubclass(value.value, BigUFixedNxM | BigUIntN | UFixedNxM | UIntN | Bool)
1279
+
1280
+
1281
+ def _find_bool(
1282
+ values: (
1283
+ StaticArray[typing.Any, typing.Any]
1284
+ | DynamicArray[typing.Any]
1285
+ | Tuple[typing.Any]
1286
+ | tuple[typing.Any, ...]
1287
+ | list[typing.Any]
1288
+ ),
1289
+ index: int,
1290
+ delta: int,
1291
+ ) -> int:
1292
+ """
1293
+ Helper function to find consecutive booleans from current index in a tuple.
1294
+ """
1295
+ until = 0
1296
+ values_length = len(values) if isinstance(values, tuple | list) else values.length.value
1297
+ while True:
1298
+ curr = index + delta * until
1299
+ if isinstance(values[curr], Bool):
1300
+ if curr != values_length - 1 and delta > 0 or curr > 0 and delta < 0:
1301
+ until += 1
1302
+ else:
1303
+ break
1304
+ else:
1305
+ until -= 1
1306
+ break
1307
+ return until
1308
+
1309
+
1310
+ def _find_bool_types(values: typing.Sequence[_TypeInfo], index: int, delta: int) -> int:
1311
+ """
1312
+ Helper function to find consecutive booleans from current index in a tuple.
1313
+ """
1314
+ until = 0
1315
+ values_length = len(values)
1316
+ while True:
1317
+ curr = index + delta * until
1318
+ if issubclass(values[curr].value, Bool):
1319
+ if curr != values_length - 1 and delta > 0 or curr > 0 and delta < 0:
1320
+ until += 1
1321
+ else:
1322
+ break
1323
+ else:
1324
+ until -= 1
1325
+ break
1326
+ return until
1327
+
1328
+
1329
+ def _compress_multiple_bool(value_list: list[Bool]) -> int:
1330
+ """
1331
+ Compress consecutive boolean values into a byte for a Tuple/Array.
1332
+ """
1333
+ result = 0
1334
+ if len(value_list) > 8:
1335
+ raise ValueError("length of list should not be greater than 8")
1336
+ for i, value in enumerate(value_list):
1337
+ assert isinstance(value, Bool)
1338
+ bool_val = value.native
1339
+ if bool_val:
1340
+ result |= 1 << (7 - i)
1341
+ return result
1342
+
1343
+
1344
+ def _get_max_bytes_len(type_info: _TypeInfo) -> int:
1345
+ size = 0
1346
+ if issubclass(type_info.value, DynamicArray):
1347
+ size += _ABI_LENGTH_SIZE
1348
+ elif type_info.child_types:
1349
+ i = 0
1350
+ child_types = type_info.child_types or []
1351
+ while i < len(child_types):
1352
+ if issubclass(child_types[i].value, Bool):
1353
+ after = _find_bool_types(child_types, i, 1)
1354
+ i += after
1355
+ bool_num = after + 1
1356
+ size += bool_num // 8
1357
+ if bool_num % 8 != 0:
1358
+ size += 1
1359
+ else:
1360
+ child_byte_size = _get_max_bytes_len(child_types[i])
1361
+ size += child_byte_size
1362
+ i += 1
1363
+
1364
+ else:
1365
+ value = type_info.value()
1366
+ if hasattr(value, "_max_bytes_len"):
1367
+ size = typing.cast(int, value._max_bytes_len)
1368
+
1369
+ return size
1370
+
1371
+
1372
+ def _encode(
1373
+ values: (
1374
+ StaticArray[typing.Any, typing.Any]
1375
+ | DynamicArray[typing.Any]
1376
+ | Tuple[typing.Any]
1377
+ | tuple[typing.Any, ...]
1378
+ | list[typing.Any]
1379
+ ),
1380
+ ) -> bytes:
1381
+ heads = []
1382
+ tails = []
1383
+ is_dynamic_index = []
1384
+ i = 0
1385
+ values_length = len(values) if hasattr(values, "__len__") else values.length.value
1386
+ values_length_bytes = (
1387
+ int_to_bytes(values_length, _ABI_LENGTH_SIZE) if isinstance(values, DynamicArray) else b""
1388
+ )
1389
+ while i < values_length:
1390
+ value = values[i]
1391
+ is_dynamic_index.append(_is_arc4_dynamic(value))
1392
+ if is_dynamic_index[-1]:
1393
+ heads.append(b"\x00\x00")
1394
+ tail_encoding = value.bytes.value if isinstance(value, String) else _encode(value)
1395
+ tails.append(tail_encoding)
1396
+ else:
1397
+ if isinstance(value, Bool):
1398
+ before = _find_bool(values, i, -1)
1399
+ after = _find_bool(values, i, 1)
1400
+
1401
+ # Pack bytes to heads and tails
1402
+ if before % 8 != 0:
1403
+ raise ValueError(
1404
+ "expected before index should have number of bool mod 8 equal 0"
1405
+ )
1406
+ after = min(7, after)
1407
+ consecutive_bool_list = typing.cast(list[Bool], values[i : i + after + 1])
1408
+ compressed_int = _compress_multiple_bool(consecutive_bool_list)
1409
+ heads.append(bytes([compressed_int]))
1410
+ i += after
1411
+ else:
1412
+ heads.append(value.bytes.value)
1413
+ tails.append(b"")
1414
+ i += 1
1415
+
1416
+ # Adjust heads for dynamic types
1417
+ head_length = 0
1418
+ for head_element in heads:
1419
+ # If the element is not a placeholder, append the length of the element
1420
+ head_length += len(head_element)
1421
+
1422
+ # Correctly encode dynamic types and replace placeholder
1423
+ tail_curr_length = 0
1424
+ for i in range(len(heads)):
1425
+ if is_dynamic_index[i]:
1426
+ head_value = as_int16(head_length + tail_curr_length)
1427
+ heads[i] = int_to_bytes(head_value, _ABI_LENGTH_SIZE)
1428
+
1429
+ tail_curr_length += len(tails[i])
1430
+
1431
+ # Concatenate bytes
1432
+ return values_length_bytes + b"".join(heads) + b"".join(tails)
1433
+
1434
+
1435
+ def _decode( # noqa: PLR0912, C901
1436
+ value: bytes, child_types: typing.Sequence[_TypeInfo]
1437
+ ) -> list[typing.Any]:
1438
+ dynamic_segments: list[list[int]] = [] # Store the start and end of a dynamic element
1439
+ value_partitions: list[bytes | None] = []
1440
+ i = 0
1441
+ array_index = 0
1442
+
1443
+ while i < len(child_types):
1444
+ child_type = child_types[i]
1445
+ if _is_arc4_dynamic_type(child_type):
1446
+ # Decode the size of the dynamic element
1447
+ dynamic_index = int.from_bytes(value[array_index : array_index + _ABI_LENGTH_SIZE])
1448
+ if len(dynamic_segments) > 0:
1449
+ dynamic_segments[-1][1] = dynamic_index
1450
+
1451
+ # Since we do not know where the current dynamic element ends,
1452
+ # put a placeholder and update later
1453
+ dynamic_segments.append([dynamic_index, -1])
1454
+ value_partitions.append(None)
1455
+ array_index += _ABI_LENGTH_SIZE
1456
+ elif issubclass(child_type.value, Bool):
1457
+ before = _find_bool_types(child_types, i, -1)
1458
+ after = _find_bool_types(child_types, i, 1)
1459
+
1460
+ if before % 8 != 0:
1461
+ raise ValueError("expected before index should have number of bool mod 8 equal 0")
1462
+ after = min(7, after)
1463
+ bits = int.from_bytes(value[array_index : array_index + 1])
1464
+ # Parse bool values into multiple byte strings
1465
+ for bool_i in range(after + 1):
1466
+ mask = 128 >> bool_i
1467
+ if mask & bits:
1468
+ value_partitions.append(b"\x80")
1469
+ else:
1470
+ value_partitions.append(b"\x00")
1471
+ i += after
1472
+ array_index += 1
1473
+ else:
1474
+ curr_len = _get_max_bytes_len(child_type)
1475
+ value_partitions.append(value[array_index : array_index + curr_len])
1476
+ array_index += curr_len
1477
+
1478
+ if array_index >= len(value) and i != len(child_types) - 1:
1479
+ raise ValueError(f"input string is not long enough to be decoded: {value!r}")
1480
+
1481
+ i += 1
1482
+
1483
+ if len(dynamic_segments) > 0:
1484
+ dynamic_segments[len(dynamic_segments) - 1][1] = len(value)
1485
+ array_index = len(value)
1486
+ if array_index < len(value):
1487
+ raise ValueError(f"input string was not fully consumed: {value!r}")
1488
+
1489
+ # Check dynamic element partitions
1490
+ segment_index = 0
1491
+ for i, child_type in enumerate(child_types):
1492
+ if _is_arc4_dynamic_type(child_type):
1493
+ segment_start, segment_end = dynamic_segments[segment_index]
1494
+ value_partitions[i] = value[segment_start:segment_end]
1495
+ segment_index += 1
1496
+
1497
+ # Decode individual tuple elements
1498
+ values = []
1499
+ for i, child_type in enumerate(child_types):
1500
+ val = child_type.value.from_bytes(value_partitions[i]) # type: ignore[attr-defined]
1501
+ val._child_types = child_type.child_types
1502
+ values.append(val)
1503
+ return values
1504
+
1505
+
1506
+ __all__ = [
1507
+ "ARC4Client",
1508
+ "Address",
1509
+ "BigUFixedNxM",
1510
+ "BigUIntN",
1511
+ "Bool",
1512
+ "Byte",
1513
+ "DynamicArray",
1514
+ "DynamicBytes",
1515
+ "StaticArray",
1516
+ "String",
1517
+ "Struct",
1518
+ "Tuple",
1519
+ "UFixedNxM",
1520
+ "UInt128",
1521
+ "UInt16",
1522
+ "UInt256",
1523
+ "UInt32",
1524
+ "UInt512",
1525
+ "UInt64",
1526
+ "UInt8",
1527
+ "UIntN",
1528
+ "abi_call",
1529
+ "abimethod",
1530
+ "arc4_signature",
1531
+ "baremethod",
1532
+ "emit",
1533
+ ]