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.
Files changed (41) hide show
  1. algopy/__init__.py +42 -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 +47 -0
  8. algopy_testing/arc4.py +1222 -0
  9. algopy_testing/constants.py +17 -0
  10. algopy_testing/context.py +769 -0
  11. algopy_testing/decorators/__init__.py +0 -0
  12. algopy_testing/decorators/abimethod.py +146 -0
  13. algopy_testing/decorators/subroutine.py +9 -0
  14. algopy_testing/enums.py +39 -0
  15. algopy_testing/gtxn.py +239 -0
  16. algopy_testing/itxn.py +353 -0
  17. algopy_testing/models/__init__.py +23 -0
  18. algopy_testing/models/account.py +128 -0
  19. algopy_testing/models/application.py +72 -0
  20. algopy_testing/models/asset.py +109 -0
  21. algopy_testing/models/contract.py +69 -0
  22. algopy_testing/models/global_values.py +67 -0
  23. algopy_testing/models/gtxn.py +40 -0
  24. algopy_testing/models/itxn.py +34 -0
  25. algopy_testing/models/transactions.py +158 -0
  26. algopy_testing/models/txn.py +111 -0
  27. algopy_testing/models/unsigned_builtins.py +15 -0
  28. algopy_testing/op.py +639 -0
  29. algopy_testing/primitives/__init__.py +6 -0
  30. algopy_testing/primitives/biguint.py +147 -0
  31. algopy_testing/primitives/bytes.py +173 -0
  32. algopy_testing/primitives/string.py +67 -0
  33. algopy_testing/primitives/uint64.py +210 -0
  34. algopy_testing/py.typed +0 -0
  35. algopy_testing/state/__init__.py +4 -0
  36. algopy_testing/state/global_state.py +73 -0
  37. algopy_testing/state/local_state.py +54 -0
  38. algopy_testing/utils.py +156 -0
  39. algorand_python_testing-0.1.0.dist-info/METADATA +29 -0
  40. algorand_python_testing-0.1.0.dist-info/RECORD +41 -0
  41. algorand_python_testing-0.1.0.dist-info/WHEEL +4 -0
algopy_testing/arc4.py ADDED
@@ -0,0 +1,1222 @@
1
+ from __future__ import annotations
2
+
3
+ import copy
4
+ import decimal
5
+ import types
6
+ import typing
7
+ from collections.abc import Iterable, Reversible
8
+
9
+ import algosdk
10
+
11
+ from algopy_testing.constants import ARC4_RETURN_PREFIX, BITS_IN_BYTE, UINT64_SIZE, UINT512_SIZE
12
+ from algopy_testing.decorators.abimethod import abimethod
13
+ from algopy_testing.models import Account
14
+ from algopy_testing.utils import (
15
+ as_bytes,
16
+ as_int,
17
+ as_int16,
18
+ as_int64,
19
+ as_int512,
20
+ as_string,
21
+ int_to_bytes,
22
+ )
23
+
24
+ if typing.TYPE_CHECKING:
25
+ import algopy
26
+
27
+ _ABI_LENGTH_SIZE = 2
28
+ _TBitSize = typing.TypeVar("_TBitSize", bound=int)
29
+
30
+
31
+ class _ABIEncoded(typing.Protocol):
32
+ @classmethod
33
+ def from_bytes(cls, value: algopy.Bytes | bytes, /) -> typing.Self:
34
+ """Construct an instance from the underlying bytes (no validation)"""
35
+ ...
36
+
37
+ @property
38
+ def bytes(self) -> algopy.Bytes:
39
+ """Get the underlying Bytes"""
40
+ ...
41
+
42
+ @classmethod
43
+ def from_log(cls, log: algopy.Bytes, /) -> typing.Self:
44
+ """Load an ABI type from application logs,
45
+ checking for the ABI return prefix `0x151f7c75`"""
46
+ ...
47
+
48
+
49
+ class String(_ABIEncoded):
50
+ """An ARC4 sequence of bytes containing a UTF8 string"""
51
+
52
+ _value: bytes
53
+
54
+ def __init__(self, value: algopy.String | str = "", /) -> None:
55
+ import algopy
56
+
57
+ match value:
58
+ case algopy.String():
59
+ bytes_value = as_bytes(value.bytes)
60
+ case str(value):
61
+ bytes_value = value.encode("utf-8")
62
+ case _:
63
+ raise TypeError(
64
+ f"value must be a string or String type, not {type(value).__name__!r}"
65
+ )
66
+
67
+ self._value = len(bytes_value).to_bytes(_ABI_LENGTH_SIZE) + bytes_value
68
+
69
+ @property
70
+ def native(self) -> algopy.String:
71
+ """Return the String representation of the UTF8 string after ARC4 decoding"""
72
+ import algopy
73
+
74
+ return algopy.String.from_bytes(self._value[_ABI_LENGTH_SIZE:])
75
+
76
+ def __add__(self, other: String | str) -> String:
77
+ return String(self.native + as_string(other))
78
+
79
+ def __radd__(self, other: String | str) -> String:
80
+ return String(as_string(other) + self.native)
81
+
82
+ def __eq__(self, other: String | str) -> bool: # type: ignore[override]
83
+ return self.native == as_string(other)
84
+
85
+ def __bool__(self) -> bool:
86
+ """Returns `True` if length is not zero"""
87
+ return bool(self.native)
88
+
89
+ @classmethod
90
+ def from_bytes(cls, value: algopy.Bytes | bytes, /) -> typing.Self:
91
+ """Construct an instance from the underlying bytes (no validation)"""
92
+ result = cls()
93
+ result._value = as_bytes(value)
94
+ return result
95
+
96
+ @property
97
+ def bytes(self) -> algopy.Bytes:
98
+ """Get the underlying Bytes"""
99
+ import algopy
100
+
101
+ return algopy.Bytes(self._value)
102
+
103
+ @classmethod
104
+ def from_log(cls, log: algopy.Bytes, /) -> typing.Self:
105
+ """Load an ABI type from application logs,
106
+ checking for the ABI return prefix `0x151f7c75`"""
107
+ import algopy
108
+
109
+ if log[:4] == algopy.Bytes(ARC4_RETURN_PREFIX):
110
+ return cls.from_bytes(log[4:])
111
+ raise ValueError("ABI return prefix not found")
112
+
113
+
114
+ # https://stackoverflow.com/a/75395800
115
+ class _UIntNMeta(type(_ABIEncoded), typing.Generic[_TBitSize]): # type: ignore[misc]
116
+ __concrete__: typing.ClassVar[dict[type, type]] = {}
117
+
118
+ def __getitem__(cls, key_t: type[_TBitSize]) -> type:
119
+ cache = cls.__concrete__
120
+ if c := cache.get(key_t, None):
121
+ return c
122
+ cache[key_t] = c = types.new_class(
123
+ f"{cls.__name__}[{key_t.__name__}]", (cls,), {}, lambda ns: ns.update(_t=key_t)
124
+ )
125
+ return c
126
+
127
+
128
+ class _UIntN(_ABIEncoded, typing.Generic[_TBitSize], metaclass=_UIntNMeta):
129
+ _t: type[_TBitSize]
130
+ _bit_size: int
131
+ _max_bits_len: int
132
+ _max_bytes_len: int
133
+ _max_int: int
134
+ _value: bytes # underlying 'bytes' value representing the UIntN
135
+
136
+ def __init__(self, value: algopy.BigUInt | algopy.UInt64 | int = 0, /) -> None:
137
+ self._bit_size = as_int(typing.get_args(self._t)[0], max=self._max_bits_len)
138
+ self._max_int = 2**self._bit_size - 1
139
+ self._max_bytes_len = self._bit_size // BITS_IN_BYTE
140
+
141
+ value = as_int(value, max=self._max_int)
142
+ bytes_value = int_to_bytes(value, self._max_bytes_len)
143
+ self._value = as_bytes(bytes_value, max_size=self._max_bytes_len)
144
+
145
+ # ~~~ https://docs.python.org/3/reference/datamodel.html#basic-customization ~~~
146
+ # TODO: mypy suggests due to Liskov below should be other: object
147
+ # need to consider ramifications here, ignoring it for now
148
+ def __eq__( # type: ignore[override]
149
+ self,
150
+ other: UIntN[_TBitSize] | BigUIntN[_TBitSize] | algopy.UInt64 | algopy.BigUInt | int,
151
+ ) -> bool:
152
+ raise NotImplementedError
153
+
154
+ def __ne__( # type: ignore[override]
155
+ self,
156
+ other: UIntN[_TBitSize] | BigUIntN[_TBitSize] | algopy.UInt64 | algopy.BigUInt | int,
157
+ ) -> bool:
158
+ raise NotImplementedError
159
+
160
+ def __le__(
161
+ self,
162
+ other: UIntN[_TBitSize] | BigUIntN[_TBitSize] | algopy.UInt64 | algopy.BigUInt | int,
163
+ ) -> bool:
164
+ raise NotImplementedError
165
+
166
+ def __lt__(
167
+ self,
168
+ other: UIntN[_TBitSize] | BigUIntN[_TBitSize] | algopy.UInt64 | algopy.BigUInt | int,
169
+ ) -> bool:
170
+ raise NotImplementedError
171
+
172
+ def __ge__(
173
+ self,
174
+ other: UIntN[_TBitSize] | BigUIntN[_TBitSize] | algopy.UInt64 | algopy.BigUInt | int,
175
+ ) -> bool:
176
+ raise NotImplementedError
177
+
178
+ def __gt__(
179
+ self,
180
+ other: UIntN[_TBitSize] | BigUIntN[_TBitSize] | algopy.UInt64 | algopy.BigUInt | int,
181
+ ) -> bool:
182
+ raise NotImplementedError
183
+
184
+ def __bool__(self) -> bool:
185
+ """Returns `True` if not equal to zero"""
186
+ raise NotImplementedError
187
+
188
+ @classmethod
189
+ def from_bytes(cls, value: algopy.Bytes | bytes, /) -> typing.Self:
190
+ """Construct an instance from the underlying bytes (no validation)"""
191
+
192
+ value = as_bytes(value)
193
+ result = cls()
194
+ result._value = value
195
+ return result
196
+
197
+ @property
198
+ def bytes(self) -> algopy.Bytes:
199
+ """Get the underlying Bytes"""
200
+ import algopy
201
+
202
+ return algopy.Bytes(self._value)
203
+
204
+ @classmethod
205
+ def from_log(cls, log: algopy.Bytes, /) -> typing.Self:
206
+ """Load an ABI type from application logs,
207
+ checking for the ABI return prefix `0x151f7c75`"""
208
+ import algopy
209
+
210
+ if log[:4] == algopy.Bytes(ARC4_RETURN_PREFIX):
211
+ return cls.from_bytes(log[4:])
212
+ raise ValueError("ABI return prefix not found")
213
+
214
+
215
+ class UIntN(_UIntN[_TBitSize], typing.Generic[_TBitSize]):
216
+ """An ARC4 UInt consisting of the number of bits specified.
217
+
218
+ Max Size: 64 bits"""
219
+
220
+ _max_bits_len = UINT64_SIZE
221
+
222
+ @property
223
+ def native(self) -> algopy.UInt64:
224
+ """Return the UInt64 representation of the value after ARC4 decoding"""
225
+ import algopy
226
+
227
+ return algopy.UInt64(int.from_bytes(self._value))
228
+
229
+ def __eq__(self, other: object) -> bool:
230
+ return as_int64(self.native) == as_int(other, max=None)
231
+
232
+ def __ne__(self, other: object) -> bool:
233
+ return as_int64(self.native) != as_int(other, max=None)
234
+
235
+ def __le__(self, other: object) -> bool:
236
+ return as_int64(self.native) <= as_int(other, max=None)
237
+
238
+ def __lt__(self, other: object) -> bool:
239
+ return as_int64(self.native) < as_int(other, max=None)
240
+
241
+ def __ge__(self, other: object) -> bool:
242
+ return as_int64(self.native) >= as_int(other, max=None)
243
+
244
+ def __gt__(self, other: object) -> bool:
245
+ return as_int64(self.native) > as_int(other, max=None)
246
+
247
+ def __bool__(self) -> bool:
248
+ return bool(self.native)
249
+
250
+
251
+ class BigUIntN(_UIntN[_TBitSize], typing.Generic[_TBitSize]):
252
+ """An ARC4 UInt consisting of the number of bits specified.
253
+
254
+ Max size: 512 bits"""
255
+
256
+ _max_bits_len = UINT512_SIZE
257
+
258
+ @property
259
+ def native(self) -> algopy.BigUInt:
260
+ """Return the UInt64 representation of the value after ARC4 decoding"""
261
+ import algopy
262
+
263
+ return algopy.BigUInt.from_bytes(self._value)
264
+
265
+ def __eq__(self, other: object) -> bool:
266
+ return as_int512(self.native) == as_int(other, max=None)
267
+
268
+ def __ne__(self, other: object) -> bool:
269
+ return as_int512(self.native) != as_int(other, max=None)
270
+
271
+ def __le__(self, other: object) -> bool:
272
+ return as_int512(self.native) <= as_int(other, max=None)
273
+
274
+ def __lt__(self, other: object) -> bool:
275
+ return as_int512(self.native) < as_int(other, max=None)
276
+
277
+ def __ge__(self, other: object) -> bool:
278
+ return as_int512(self.native) >= as_int(other, max=None)
279
+
280
+ def __gt__(self, other: object) -> bool:
281
+ return as_int512(self.native) > as_int(other, max=None)
282
+
283
+ def __bool__(self) -> bool:
284
+ return bool(self.native)
285
+
286
+
287
+ _TDecimalPlaces = typing.TypeVar("_TDecimalPlaces", bound=int)
288
+ _MAX_M_SIZE = 160
289
+
290
+
291
+ class _UFixedNxMMeta(type(_ABIEncoded), typing.Generic[_TBitSize, _TDecimalPlaces]): # type: ignore[misc]
292
+ __concrete__: typing.ClassVar[dict[tuple[type, type], type]] = {}
293
+
294
+ def __getitem__(cls, key_t: tuple[type[_TBitSize], type[_TDecimalPlaces]]) -> type:
295
+ cache = cls.__concrete__
296
+ if c := cache.get(key_t, None):
297
+ return c
298
+
299
+ size_t, decimal_t = key_t
300
+ cache[key_t] = c = types.new_class(
301
+ f"{cls.__name__}[{size_t.__name__}, {decimal_t.__name__}]",
302
+ (cls,),
303
+ {},
304
+ lambda ns: ns.update(_size_t=size_t, _decimal_t=decimal_t),
305
+ )
306
+ return c
307
+
308
+
309
+ class _UFixedNxM(
310
+ _ABIEncoded, typing.Generic[_TBitSize, _TDecimalPlaces], metaclass=_UFixedNxMMeta
311
+ ):
312
+ _size_t: type[_TBitSize]
313
+ _decimal_t: type[_TDecimalPlaces]
314
+ _n: int
315
+ _m: int
316
+ _max_bits_len: int
317
+ _max_bytes_len: int
318
+ _value: bytes # underlying 'bytes' value representing the UFixedNxM
319
+
320
+ def __init__(self, value: str = "0.0", /):
321
+ """
322
+ Construct an instance of UFixedNxM where value (v) is determined from the original
323
+ decimal value (d) by the formula v = round(d * (10^M))
324
+ """
325
+ self._n = as_int(typing.get_args(self._size_t)[0], max=self._max_bits_len)
326
+ self._m = as_int(typing.get_args(self._decimal_t)[0], max=_MAX_M_SIZE)
327
+ self._max_int = 2**self._n - 1
328
+ self._max_bytes_len = self._n // BITS_IN_BYTE
329
+
330
+ value = as_string(value)
331
+ with decimal.localcontext(
332
+ decimal.Context(
333
+ prec=160,
334
+ traps=[
335
+ decimal.Rounded,
336
+ decimal.InvalidOperation,
337
+ decimal.Overflow,
338
+ decimal.DivisionByZero,
339
+ ],
340
+ )
341
+ ):
342
+ try:
343
+ d = decimal.Decimal(value)
344
+ except ArithmeticError as ex:
345
+ raise ValueError(f"Invalid decimal literal: {value}") from ex
346
+ if d < 0:
347
+ raise ValueError("Negative numbers not allowed")
348
+ try:
349
+ q = d.quantize(decimal.Decimal(f"1e-{self._m}"))
350
+ except ArithmeticError as ex:
351
+ raise ValueError(f"Too many decimals, expected max of {self._m}") from ex
352
+
353
+ int_value = round(q * (10**self._m))
354
+ int_value = as_int(int_value, max=self._max_int)
355
+ bytes_value = int_to_bytes(int_value, self._max_bytes_len)
356
+ self._value = as_bytes(bytes_value, max_size=self._max_bytes_len)
357
+
358
+ def __bool__(self) -> bool:
359
+ """Returns `True` if not equal to zero"""
360
+ return bool(int.from_bytes(self._value))
361
+
362
+ @classmethod
363
+ def from_bytes(cls, value: algopy.Bytes | bytes, /) -> typing.Self:
364
+ """Construct an instance from the underlying bytes (no validation)"""
365
+ value = as_bytes(value)
366
+ result = cls()
367
+ result._value = value
368
+ return result
369
+
370
+ @property
371
+ def bytes(self) -> algopy.Bytes:
372
+ """Get the underlying Bytes"""
373
+ import algopy
374
+
375
+ return algopy.Bytes(self._value)
376
+
377
+ @classmethod
378
+ def from_log(cls, log: algopy.Bytes, /) -> typing.Self:
379
+ """Load an ABI type from application logs,
380
+ checking for the ABI return prefix `0x151f7c75`"""
381
+ import algopy
382
+
383
+ if log[:4] == algopy.Bytes(ARC4_RETURN_PREFIX):
384
+ return cls.from_bytes(log[4:])
385
+ raise ValueError("ABI return prefix not found")
386
+
387
+
388
+ class UFixedNxM(
389
+ _UFixedNxM[_TBitSize, _TDecimalPlaces], typing.Generic[_TBitSize, _TDecimalPlaces]
390
+ ):
391
+ """An ARC4 UFixed representing a decimal with the number of bits and precision specified.
392
+
393
+ Max size: 64 bits"""
394
+
395
+ _max_bits_len: int = UINT64_SIZE
396
+
397
+
398
+ class BigUFixedNxM(
399
+ _UFixedNxM[_TBitSize, _TDecimalPlaces], typing.Generic[_TBitSize, _TDecimalPlaces]
400
+ ):
401
+ """An ARC4 UFixed representing a decimal with the number of bits and precision specified.
402
+
403
+ Max size: 512 bits"""
404
+
405
+ _max_bits_len: int = UINT512_SIZE
406
+
407
+
408
+ class Byte(UIntN[typing.Literal[8]]):
409
+ """An ARC4 alias for a UInt8"""
410
+
411
+
412
+ UInt8: typing.TypeAlias = UIntN[typing.Literal[8]]
413
+ """An ARC4 UInt8"""
414
+
415
+ UInt16: typing.TypeAlias = UIntN[typing.Literal[16]]
416
+ """An ARC4 UInt16"""
417
+
418
+ UInt32: typing.TypeAlias = UIntN[typing.Literal[32]]
419
+ """An ARC4 UInt32"""
420
+
421
+ UInt64: typing.TypeAlias = UIntN[typing.Literal[64]]
422
+ """An ARC4 UInt64"""
423
+
424
+ UInt128: typing.TypeAlias = BigUIntN[typing.Literal[128]]
425
+ """An ARC4 UInt128"""
426
+
427
+ UInt256: typing.TypeAlias = BigUIntN[typing.Literal[256]]
428
+ """An ARC4 UInt256"""
429
+
430
+ UInt512: typing.TypeAlias = BigUIntN[typing.Literal[512]]
431
+ """An ARC4 UInt512"""
432
+
433
+
434
+ class Bool(_ABIEncoded):
435
+ """An ARC4 encoded bool"""
436
+
437
+ _value: bytes
438
+
439
+ # True value is encoded as having a 1 on the most significant bit (0x80 = 128)
440
+ _true_int_value = 128
441
+ _false_int_value = 0
442
+
443
+ def __init__(self, value: bool = False, /) -> None: # noqa: FBT001, FBT002
444
+ self._value = int_to_bytes(self._true_int_value if value else self._false_int_value, 1)
445
+
446
+ @property
447
+ def native(self) -> bool:
448
+ """Return the bool representation of the value after ARC4 decoding"""
449
+ int_value = int.from_bytes(self._value)
450
+ return int_value == self._true_int_value
451
+
452
+ @classmethod
453
+ def from_bytes(cls, value: algopy.Bytes | bytes, /) -> typing.Self:
454
+ """Construct an instance from the underlying bytes (no validation)"""
455
+ result = cls()
456
+ result._value = as_bytes(value)
457
+ return result
458
+
459
+ @property
460
+ def bytes(self) -> algopy.Bytes:
461
+ """Get the underlying Bytes"""
462
+ import algopy
463
+
464
+ return algopy.Bytes(self._value)
465
+
466
+ @classmethod
467
+ def from_log(cls, log: algopy.Bytes, /) -> typing.Self:
468
+ """Load an ABI type from application logs,
469
+ checking for the ABI return prefix `0x151f7c75`"""
470
+ import algopy
471
+
472
+ if log[:4] == algopy.Bytes(ARC4_RETURN_PREFIX):
473
+ return cls.from_bytes(log[4:])
474
+ raise ValueError("ABI return prefix not found")
475
+
476
+
477
+ _TArrayItem = typing.TypeVar("_TArrayItem")
478
+ _TArrayLength = typing.TypeVar("_TArrayLength", bound=int)
479
+
480
+
481
+ class _StaticArrayMeta(type(_ABIEncoded), typing.Generic[_TArrayItem, _TArrayLength]): # type: ignore # noqa: PGH003
482
+ __concrete__: typing.ClassVar[dict[tuple[type, type], type]] = {}
483
+
484
+ def __getitem__(cls, key_t: tuple[type[_TArrayItem], type[_TArrayLength]]) -> type:
485
+ cache = cls.__concrete__
486
+ if c := cache.get(key_t, None):
487
+ return c
488
+
489
+ cache[key_t] = c = types.new_class(
490
+ f"{cls.__name__}[{','.join([k.__name__ for k in key_t])}]",
491
+ (cls,),
492
+ {},
493
+ lambda ns: ns.update(
494
+ _child_types=[_TypeInfo(key_t[0])]
495
+ * (typing.get_args(key_t[1])[0] if len(typing.get_args(key_t[1])) > 0 else 0),
496
+ _array_item_t=key_t[0],
497
+ ),
498
+ )
499
+ return c
500
+
501
+
502
+ class _TypeInfo:
503
+ value: type
504
+ child_types: list[_TypeInfo] | None
505
+
506
+ def __init__(self, value: type, child_types: list[_TypeInfo] | None = None):
507
+ self.value = value
508
+ self.child_types = child_types or (
509
+ value._child_types if hasattr(value, "_child_types") else child_types
510
+ )
511
+
512
+
513
+ class StaticArray(
514
+ _ABIEncoded,
515
+ typing.Generic[_TArrayItem, _TArrayLength],
516
+ Reversible[_TArrayItem],
517
+ metaclass=_StaticArrayMeta,
518
+ ):
519
+ """A fixed length ARC4 Array of the specified type and length"""
520
+
521
+ _array_item_t: type[_TArrayItem]
522
+ _child_types: list[_TypeInfo]
523
+
524
+ _value: bytes
525
+
526
+ def __init__(self, *items: _TArrayItem):
527
+ self._value = _encode(items)
528
+ if items:
529
+ self._array_item_t = type(items[0])
530
+ self._child_types = [self._get_type_info(item) for item in items]
531
+
532
+ # ensure these two variables are set as instance variables instead of class variables
533
+ # to avoid sharing state between instances created by copy operation
534
+ if hasattr(self, "_array_item_t"):
535
+ self._array_item_t = self._array_item_t
536
+ self._child_types = self._child_types or [] if hasattr(self, "_child_types") else []
537
+
538
+ def _get_type_info(self, item: _TArrayItem) -> _TypeInfo:
539
+ return _TypeInfo(
540
+ self._array_item_t,
541
+ item._child_types if hasattr(item, "_child_types") else None,
542
+ )
543
+
544
+ def __iter__(self) -> typing.Iterator[_TArrayItem]:
545
+ # """Returns an iterator for the items in the array"""
546
+ return iter(self._list())
547
+
548
+ def __reversed__(self) -> typing.Iterator[_TArrayItem]:
549
+ # """Returns an iterator for the items in the array, in reverse order"""
550
+ return reversed(self._list())
551
+
552
+ @property
553
+ def length(self) -> algopy.UInt64:
554
+ # """Returns the current length of the array"""
555
+ import algopy
556
+
557
+ return algopy.UInt64(len(self._list()))
558
+
559
+ @typing.overload
560
+ def __getitem__(self, value: algopy.UInt64 | int, /) -> _TArrayItem: ...
561
+ @typing.overload
562
+ def __getitem__(self, value: slice, /) -> list[_TArrayItem]: ...
563
+ def __getitem__(self, index: algopy.UInt64 | int | slice) -> _TArrayItem | list[_TArrayItem]:
564
+ if isinstance(index, slice):
565
+ return self._list()[index]
566
+ return self._list()[index]
567
+
568
+ def append(self, item: _TArrayItem, /) -> None:
569
+ """Append items to this array"""
570
+ if not issubclass(type(item), self._array_item_t):
571
+ expected_type = self._array_item_t.__name__
572
+ actual_type = type(item).__name__
573
+ raise TypeError(f"item must be of type {expected_type!r}, not {actual_type!r}")
574
+ x = self._list()
575
+ x.append(item)
576
+ self._child_types.append(self._get_type_info(item))
577
+ self._value = _encode(x)
578
+
579
+ def extend(self, other: Iterable[_TArrayItem], /) -> None:
580
+ """Extend this array with the contents of another array"""
581
+ if any(not issubclass(type(x), self._array_item_t) for x in other):
582
+ raise TypeError(f"items must be of type {self._array_item_t.__name__!r}")
583
+ x = self._list()
584
+ x.extend(other)
585
+ self._child_types.extend([self._get_type_info(x) for x in other])
586
+ self._value = _encode(x)
587
+
588
+ def __setitem__(self, index: algopy.UInt64 | int, value: _TArrayItem) -> _TArrayItem:
589
+ if not issubclass(type(value), self._array_item_t):
590
+ expected_type = self._array_item_t.__name__
591
+ actual_type = type(value).__name__
592
+ raise TypeError(f"item must be of type {expected_type!r}, not {actual_type!r}")
593
+ x = self._list()
594
+ x[index] = value
595
+ self._value = _encode(x)
596
+ return value
597
+
598
+ def copy(self) -> typing.Self:
599
+ # """Create a copy of this array"""
600
+ return copy.deepcopy(self)
601
+
602
+ def _list(self) -> list[_TArrayItem]:
603
+ return _decode(self._value, self._child_types)
604
+
605
+ @classmethod
606
+ def from_bytes(cls, value: algopy.Bytes | bytes, /) -> typing.Self:
607
+ """Construct an instance from the underlying bytes (no validation)"""
608
+ value = as_bytes(value)
609
+ result = cls()
610
+ result._value = value
611
+ return result
612
+
613
+ @property
614
+ def bytes(self) -> algopy.Bytes:
615
+ """Get the underlying Bytes"""
616
+ import algopy
617
+
618
+ return algopy.Bytes(self._value)
619
+
620
+ @classmethod
621
+ def from_log(cls, log: algopy.Bytes, /) -> typing.Self:
622
+ """Load an ABI type from application logs,
623
+ checking for the ABI return prefix `0x151f7c75`"""
624
+ import algopy
625
+
626
+ if log[:4] == algopy.Bytes(ARC4_RETURN_PREFIX):
627
+ return cls.from_bytes(log[4:])
628
+ raise ValueError("ABI return prefix not found")
629
+
630
+
631
+ class Address(StaticArray[Byte, typing.Literal[32]]):
632
+ """An alias for an array containing 32 bytes representing an Algorand address"""
633
+
634
+ def __init__(self, value: Account | str | algopy.Bytes = algosdk.constants.ZERO_ADDRESS):
635
+ """
636
+ If `value` is a string, it should be a 58 character base32 string,
637
+ ie a base32 string-encoded 32 bytes public key + 4 bytes checksum.
638
+ If `value` is a Bytes, it's length checked to be 32 bytes - to avoid this
639
+ check, use `Address.from_bytes(...)` instead.
640
+ Defaults to the zero-address.
641
+ """
642
+ if isinstance(value, str):
643
+ try:
644
+ bytes_value = algosdk.encoding.decode_address(value)
645
+ except Exception as e:
646
+ raise ValueError(f"cannot encode the following address: {value!r}") from e
647
+ else:
648
+ bytes_value = (
649
+ value.bytes.value if isinstance(value, Account) else as_bytes(value, max_size=32)
650
+ )
651
+ if len(bytes_value) != 32:
652
+ raise ValueError(f"expected 32 bytes, got: {len(bytes_value)}")
653
+ self._value = bytes_value
654
+
655
+ @property
656
+ def native(self) -> Account:
657
+ # """Return the Account representation of the address after ARC4 decoding"""
658
+ return Account(self.bytes)
659
+
660
+ def __bool__(self) -> bool:
661
+ # """Returns `True` if not equal to the zero address"""
662
+ zero_bytes = algosdk.encoding.decode_address(algosdk.constants.ZERO_ADDRESS)
663
+ return self.bytes != zero_bytes if isinstance(zero_bytes, bytes) else False
664
+
665
+ def __eq__(self, other: Address | Account | str) -> bool: # type: ignore[override]
666
+ """Address equality is determined by the address of another
667
+ `arc4.Address`, `Account` or `str`"""
668
+ if isinstance(other, Address | Account):
669
+ return self.bytes == other.bytes
670
+ other_bytes = algosdk.encoding.decode_address(other)
671
+ return self.bytes == other_bytes if isinstance(other_bytes, bytes) else False
672
+
673
+
674
+ class _DynamicArrayMeta(type(_ABIEncoded), typing.Generic[_TArrayItem, _TArrayLength]): # type: ignore # noqa: PGH003
675
+ __concrete__: typing.ClassVar[dict[type, type]] = {}
676
+
677
+ def __getitem__(cls, key_t: type[_TArrayItem]) -> type:
678
+ cache = cls.__concrete__
679
+ if c := cache.get(key_t, None):
680
+ return c
681
+
682
+ cache[key_t] = c = types.new_class(
683
+ f"{cls.__name__}[{key_t.__name__}]",
684
+ (cls,),
685
+ {},
686
+ lambda ns: ns.update(
687
+ _child_types=[],
688
+ _array_item_t=key_t,
689
+ ),
690
+ )
691
+ return c
692
+
693
+
694
+ class DynamicArray(
695
+ _ABIEncoded,
696
+ typing.Generic[_TArrayItem],
697
+ Reversible[_TArrayItem],
698
+ metaclass=_DynamicArrayMeta,
699
+ ):
700
+ """A dynamically sized ARC4 Array of the specified type"""
701
+
702
+ _array_item_t: type[_TArrayItem]
703
+ _child_types: list[_TypeInfo]
704
+
705
+ _value: bytes
706
+
707
+ def __init__(self, *items: _TArrayItem):
708
+ self._value = self._encode_with_length(items)
709
+ if items:
710
+ self._array_item_t = type(items[0])
711
+ self._child_types = [self._get_type_info(item) for item in items]
712
+
713
+ # ensure these two variables are set as instance variables instead of class variables
714
+ # to avoid sharing state between instances created by copy operation
715
+ if hasattr(self, "_array_item_t"):
716
+ self._array_item_t = self._array_item_t
717
+ self._child_types = self._child_types or [] if hasattr(self, "_child_types") else []
718
+
719
+ def _get_type_info(self, item: _TArrayItem) -> _TypeInfo:
720
+ return _TypeInfo(
721
+ self._array_item_t,
722
+ item._child_types if hasattr(item, "_child_types") else None,
723
+ )
724
+
725
+ def __iter__(self) -> typing.Iterator[_TArrayItem]:
726
+ """Returns an iterator for the items in the array"""
727
+ return iter(self._list())
728
+
729
+ def __reversed__(self) -> typing.Iterator[_TArrayItem]:
730
+ """Returns an iterator for the items in the array, in reverse order"""
731
+ return reversed(self._list())
732
+
733
+ @property
734
+ def length(self) -> algopy.UInt64:
735
+ """Returns the current length of the array"""
736
+ import algopy
737
+
738
+ return algopy.UInt64(len(self._list()))
739
+
740
+ @typing.overload
741
+ def __getitem__(self, value: algopy.UInt64 | int, /) -> _TArrayItem: ...
742
+ @typing.overload
743
+ def __getitem__(self, value: slice, /) -> list[_TArrayItem]: ...
744
+ def __getitem__(self, index: algopy.UInt64 | int | slice) -> _TArrayItem | list[_TArrayItem]:
745
+ if isinstance(index, slice):
746
+ return self._list()[index]
747
+ return self._list()[index]
748
+
749
+ def append(self, item: _TArrayItem, /) -> None:
750
+ """Append items to this array"""
751
+ if not issubclass(type(item), self._array_item_t):
752
+ expected_type = self._array_item_t.__name__
753
+ actual_type = type(item).__name__
754
+ raise TypeError(f"item must be of type {expected_type!r}, not {actual_type!r}")
755
+ x = self._list()
756
+ x.append(item)
757
+ self._child_types.append(self._get_type_info(item))
758
+ self._value = self._encode_with_length(x)
759
+
760
+ def extend(self, other: Iterable[_TArrayItem], /) -> None:
761
+ """Extend this array with the contents of another array"""
762
+ if any(not issubclass(type(x), self._array_item_t) for x in other):
763
+ raise TypeError(f"items must be of type {self._array_item_t.__name__!r}")
764
+ x = self._list()
765
+ x.extend(other)
766
+ self._child_types.extend([self._get_type_info(x) for x in other])
767
+ self._value = self._encode_with_length(x)
768
+
769
+ def __setitem__(self, index: algopy.UInt64 | int, value: _TArrayItem) -> _TArrayItem:
770
+ if not issubclass(type(value), self._array_item_t):
771
+ expected_type = self._array_item_t.__name__
772
+ actual_type = type(value).__name__
773
+ raise TypeError(f"item must be of type {expected_type!r}, not {actual_type!r}")
774
+ x = self._list()
775
+ x[index] = value
776
+ self._value = self._encode_with_length(x)
777
+ return value
778
+
779
+ def __add__(self, other: Iterable[_TArrayItem]) -> DynamicArray[_TArrayItem]:
780
+ self.extend(other)
781
+ return self
782
+
783
+ def pop(self) -> _TArrayItem:
784
+ """Remove and return the last item in the array"""
785
+ x = self._list()
786
+ item = x.pop()
787
+ self._child_types.pop()
788
+ self._value = self._encode_with_length(x)
789
+ return item
790
+
791
+ def copy(self) -> typing.Self:
792
+ """Create a copy of this array"""
793
+ return copy.deepcopy(self)
794
+
795
+ def __bool__(self) -> bool:
796
+ """Returns `True` if not an empty array"""
797
+ return bool(self._list())
798
+
799
+ def _list(self) -> list[_TArrayItem]:
800
+ length = int.from_bytes(self._value[:_ABI_LENGTH_SIZE])
801
+ self._child_types = self._child_types or []
802
+ if hasattr(self, "_array_item_t"):
803
+ self._child_types += [_TypeInfo(self._array_item_t)] * (
804
+ length - len(self._child_types)
805
+ )
806
+ return _decode(self._value[_ABI_LENGTH_SIZE:], self._child_types)
807
+
808
+ def _encode_with_length(self, items: list[_TArrayItem] | tuple[_TArrayItem, ...]) -> bytes:
809
+ return len(items).to_bytes(_ABI_LENGTH_SIZE) + _encode(items)
810
+
811
+ @classmethod
812
+ def from_bytes(cls, value: algopy.Bytes | bytes, /) -> typing.Self:
813
+ """Construct an instance from the underlying bytes (no validation)"""
814
+ value = as_bytes(value)
815
+ result = cls()
816
+ result._value = value
817
+ return result
818
+
819
+ @property
820
+ def bytes(self) -> algopy.Bytes:
821
+ """Get the underlying Bytes"""
822
+ import algopy
823
+
824
+ return algopy.Bytes(self._value)
825
+
826
+ @classmethod
827
+ def from_log(cls, log: algopy.Bytes, /) -> typing.Self:
828
+ """Load an ABI type from application logs,
829
+ checking for the ABI return prefix `0x151f7c75`"""
830
+ import algopy
831
+
832
+ if log[:4] == algopy.Bytes(ARC4_RETURN_PREFIX):
833
+ return cls.from_bytes(log[4:])
834
+ raise ValueError("ABI return prefix not found")
835
+
836
+
837
+ class DynamicBytes(DynamicArray[Byte]):
838
+ """A variable sized array of bytes"""
839
+
840
+ @typing.overload
841
+ def __init__(self, *values: Byte | UInt8 | int): ...
842
+
843
+ @typing.overload
844
+ def __init__(self, value: algopy.Bytes | bytes, /): ...
845
+
846
+ def __init__(
847
+ self,
848
+ *value: algopy.Bytes | bytes | Byte | UInt8 | int,
849
+ ):
850
+ import algopy
851
+
852
+ items = []
853
+ for x in value:
854
+ if isinstance(x, int):
855
+ items.append(Byte(x))
856
+ elif isinstance(x, Byte):
857
+ items.append(x)
858
+ elif isinstance(x, UInt8): # type: ignore[misc]
859
+ items.append(typing.cast(Byte, x))
860
+ elif isinstance(x, algopy.Bytes):
861
+ items.extend([Byte(int.from_bytes(i.value)) for i in x])
862
+ elif isinstance(x, bytes):
863
+ items.extend([Byte(int.from_bytes(i.value)) for i in algopy.Bytes(x)])
864
+
865
+ super().__init__(*items)
866
+
867
+ @property
868
+ def native(self) -> algopy.Bytes:
869
+ """Return the Bytes representation of the address after ARC4 decoding"""
870
+ return self.bytes
871
+
872
+
873
+ _TTuple = typing.TypeVarTuple("_TTuple")
874
+
875
+
876
+ class _TupleMeta(type(_ABIEncoded), typing.Generic[typing.Unpack[_TTuple]]): # type: ignore # noqa: PGH003
877
+ __concrete__: typing.ClassVar[dict[tuple, type]] = {} # type: ignore[type-arg]
878
+
879
+ def __getitem__(cls, key_t: tuple) -> type: # type: ignore[type-arg]
880
+ cache = cls.__concrete__
881
+ if c := cache.get(key_t, None):
882
+ return c
883
+
884
+ cache[key_t] = c = types.new_class(
885
+ f"{cls.__name__}[{key_t}]",
886
+ (cls,),
887
+ {},
888
+ lambda ns: ns.update(
889
+ _child_types=[_TypeInfo(typing.cast(type, item)) for item in key_t],
890
+ ),
891
+ )
892
+ return c
893
+
894
+
895
+ class Tuple(
896
+ _ABIEncoded,
897
+ tuple[*_TTuple],
898
+ typing.Generic[typing.Unpack[_TTuple]],
899
+ metaclass=_TupleMeta,
900
+ ):
901
+ """An ARC4 ABI tuple, containing other ARC4 ABI types"""
902
+
903
+ __slots__ = ()
904
+
905
+ _child_types: list[_TypeInfo]
906
+ _value: bytes
907
+
908
+ def __init__(self, items: tuple[typing.Unpack[_TTuple]] = (), /): # type: ignore[assignment]
909
+ """Construct an ARC4 tuple from a python tuple"""
910
+ self._value = _encode(items)
911
+ if items:
912
+ self._child_types = [self._get_type_info(item) for item in items]
913
+
914
+ # ensure the variable is set as instance variables instead of class variables
915
+ # to avoid sharing state between instances created by copy operation
916
+ self._child_types = self._child_types or [] if hasattr(self, "_child_types") else []
917
+
918
+ def _get_type_info(self, item: typing.Any) -> _TypeInfo:
919
+ return _TypeInfo(
920
+ type(item),
921
+ item._child_types if hasattr(item, "_child_types") else None,
922
+ )
923
+
924
+ def __len__(self) -> int:
925
+ return len(self.native)
926
+
927
+ @typing.overload
928
+ def __getitem__(self, value: typing.SupportsIndex, /) -> object: ...
929
+ @typing.overload
930
+ def __getitem__(self, value: slice, /) -> tuple[object, ...]: ...
931
+
932
+ def __getitem__(self, index: typing.SupportsIndex | slice) -> tuple[object, ...] | object:
933
+ return self.native[index]
934
+
935
+ def __iter__(self) -> typing.Iterator[object]:
936
+ return iter(self.native)
937
+
938
+ @property
939
+ def native(self) -> tuple[typing.Unpack[_TTuple]]:
940
+ """Return the Bytes representation of the address after ARC4 decoding"""
941
+ return typing.cast(
942
+ tuple[typing.Unpack[_TTuple]], tuple(_decode(self._value, self._child_types))
943
+ )
944
+
945
+ @classmethod
946
+ def from_bytes(cls, value: algopy.Bytes | bytes, /) -> typing.Self:
947
+ """Construct an instance from the underlying bytes (no validation)"""
948
+ value = as_bytes(value)
949
+ result = cls()
950
+ result._value = value
951
+ return result
952
+
953
+ @property
954
+ def bytes(self) -> algopy.Bytes:
955
+ """Get the underlying Bytes"""
956
+ import algopy
957
+
958
+ return algopy.Bytes(self._value)
959
+
960
+ @classmethod
961
+ def from_log(cls, log: algopy.Bytes, /) -> typing.Self:
962
+ """Load an ABI type from application logs,
963
+ checking for the ABI return prefix `0x151f7c75`"""
964
+ import algopy
965
+
966
+ if log[:4] == algopy.Bytes(ARC4_RETURN_PREFIX):
967
+ return cls.from_bytes(log[4:])
968
+ raise ValueError("ABI return prefix not found")
969
+
970
+
971
+ def _is_arc4_dynamic(value: object) -> bool:
972
+ if isinstance(value, DynamicArray):
973
+ return True
974
+ elif isinstance(value, StaticArray | Tuple):
975
+ return any(_is_arc4_dynamic(v) for v in value)
976
+ return not isinstance(value, BigUFixedNxM | BigUIntN | UFixedNxM | UIntN | Bool)
977
+
978
+
979
+ def _is_arc4_dynamic_type(value: _TypeInfo) -> bool:
980
+ if issubclass(value.value, DynamicArray):
981
+ return True
982
+ elif value.child_types:
983
+ return any(_is_arc4_dynamic_type(v) for v in value.child_types)
984
+ return not issubclass(value.value, BigUFixedNxM | BigUIntN | UFixedNxM | UIntN | Bool)
985
+
986
+
987
+ def _find_bool(
988
+ values: (
989
+ StaticArray[typing.Any, typing.Any]
990
+ | DynamicArray[typing.Any]
991
+ | Tuple[typing.Any]
992
+ | tuple[typing.Any, ...]
993
+ | list[typing.Any]
994
+ ),
995
+ index: int,
996
+ delta: int,
997
+ ) -> int:
998
+ """
999
+ Helper function to find consecutive booleans from current index in a tuple.
1000
+ """
1001
+ until = 0
1002
+ values_length = len(values) if isinstance(values, tuple | list) else values.length.value
1003
+ while True:
1004
+ curr = index + delta * until
1005
+ if isinstance(values[curr], Bool):
1006
+ if curr != values_length - 1 and delta > 0 or curr > 0 and delta < 0:
1007
+ until += 1
1008
+ else:
1009
+ break
1010
+ else:
1011
+ until -= 1
1012
+ break
1013
+ return until
1014
+
1015
+
1016
+ def _find_bool_types(values: typing.Sequence[_TypeInfo], index: int, delta: int) -> int:
1017
+ """
1018
+ Helper function to find consecutive booleans from current index in a tuple.
1019
+ """
1020
+ until = 0
1021
+ values_length = len(values)
1022
+ while True:
1023
+ curr = index + delta * until
1024
+ if issubclass(values[curr].value, Bool):
1025
+ if curr != values_length - 1 and delta > 0 or curr > 0 and delta < 0:
1026
+ until += 1
1027
+ else:
1028
+ break
1029
+ else:
1030
+ until -= 1
1031
+ break
1032
+ return until
1033
+
1034
+
1035
+ def _compress_multiple_bool(value_list: list[Bool]) -> int:
1036
+ """
1037
+ Compress consecutive boolean values into a byte for a Tuple/Array.
1038
+ """
1039
+ result = 0
1040
+ if len(value_list) > 8:
1041
+ raise ValueError("length of list should not be greater than 8")
1042
+ for i, value in enumerate(value_list):
1043
+ assert isinstance(value, Bool)
1044
+ bool_val = value.native
1045
+ if bool_val:
1046
+ result |= 1 << (7 - i)
1047
+ return result
1048
+
1049
+
1050
+ def _get_max_bytes_len(type_info: _TypeInfo) -> int:
1051
+ size = 0
1052
+ if issubclass(type_info.value, DynamicArray):
1053
+ size += _ABI_LENGTH_SIZE
1054
+ elif type_info.child_types:
1055
+ i = 0
1056
+ child_types = type_info.child_types or []
1057
+ while i < len(child_types):
1058
+ if issubclass(child_types[i].value, Bool):
1059
+ after = _find_bool_types(child_types, i, 1)
1060
+ i += after
1061
+ bool_num = after + 1
1062
+ size += bool_num // 8
1063
+ if bool_num % 8 != 0:
1064
+ size += 1
1065
+ else:
1066
+ child_byte_size = _get_max_bytes_len(child_types[i])
1067
+ size += child_byte_size
1068
+ i += 1
1069
+
1070
+ else:
1071
+ value = type_info.value()
1072
+ if hasattr(value, "_max_bytes_len"):
1073
+ size = typing.cast(int, value._max_bytes_len)
1074
+
1075
+ return size
1076
+
1077
+
1078
+ def _encode(
1079
+ values: (
1080
+ StaticArray[typing.Any, typing.Any]
1081
+ | DynamicArray[typing.Any]
1082
+ | Tuple[typing.Any]
1083
+ | tuple[typing.Any, ...]
1084
+ | list[typing.Any]
1085
+ ),
1086
+ ) -> bytes:
1087
+ heads = []
1088
+ tails = []
1089
+ is_dynamic_index = []
1090
+ i = 0
1091
+ values_length = len(values) if isinstance(values, tuple | list) else values.length.value
1092
+ values_length_bytes = (
1093
+ int_to_bytes(values_length, _ABI_LENGTH_SIZE) if isinstance(values, DynamicArray) else b""
1094
+ )
1095
+ while i < values_length:
1096
+ value = values[i]
1097
+ is_dynamic_index.append(_is_arc4_dynamic(value))
1098
+ if is_dynamic_index[-1]:
1099
+ heads.append(b"\x00\x00")
1100
+ tail_encoding = value.bytes.value if isinstance(value, String) else _encode(value)
1101
+ tails.append(tail_encoding)
1102
+ else:
1103
+ if isinstance(value, Bool):
1104
+ before = _find_bool(values, i, -1)
1105
+ after = _find_bool(values, i, 1)
1106
+
1107
+ # Pack bytes to heads and tails
1108
+ if before % 8 != 0:
1109
+ raise ValueError(
1110
+ "expected before index should have number of bool mod 8 equal 0"
1111
+ )
1112
+ after = min(7, after)
1113
+ consecutive_bool_list = typing.cast(list[Bool], values[i : i + after + 1])
1114
+ compressed_int = _compress_multiple_bool(consecutive_bool_list)
1115
+ heads.append(bytes([compressed_int]))
1116
+ i += after
1117
+ else:
1118
+ heads.append(value.bytes.value)
1119
+ tails.append(b"")
1120
+ i += 1
1121
+
1122
+ # Adjust heads for dynamic types
1123
+ head_length = 0
1124
+ for head_element in heads:
1125
+ # If the element is not a placeholder, append the length of the element
1126
+ head_length += len(head_element)
1127
+
1128
+ # Correctly encode dynamic types and replace placeholder
1129
+ tail_curr_length = 0
1130
+ for i in range(len(heads)):
1131
+ if is_dynamic_index[i]:
1132
+ head_value = as_int16(head_length + tail_curr_length)
1133
+ heads[i] = int_to_bytes(head_value, _ABI_LENGTH_SIZE)
1134
+
1135
+ tail_curr_length += len(tails[i])
1136
+
1137
+ # Concatenate bytes
1138
+ return values_length_bytes + b"".join(heads) + b"".join(tails)
1139
+
1140
+
1141
+ def _decode( # noqa: PLR0912, C901
1142
+ value: bytes, child_types: typing.Sequence[_TypeInfo]
1143
+ ) -> list[typing.Any]:
1144
+ dynamic_segments: list[list[int]] = [] # Store the start and end of a dynamic element
1145
+ value_partitions: list[bytes | None] = []
1146
+ i = 0
1147
+ array_index = 0
1148
+
1149
+ while i < len(child_types):
1150
+ child_type = child_types[i]
1151
+ if _is_arc4_dynamic_type(child_type):
1152
+ # Decode the size of the dynamic element
1153
+ dynamic_index = int.from_bytes(value[array_index : array_index + _ABI_LENGTH_SIZE])
1154
+ if len(dynamic_segments) > 0:
1155
+ dynamic_segments[-1][1] = dynamic_index
1156
+
1157
+ # Since we do not know where the current dynamic element ends,
1158
+ # put a placeholder and update later
1159
+ dynamic_segments.append([dynamic_index, -1])
1160
+ value_partitions.append(None)
1161
+ array_index += _ABI_LENGTH_SIZE
1162
+ elif issubclass(child_type.value, Bool):
1163
+ before = _find_bool_types(child_types, i, -1)
1164
+ after = _find_bool_types(child_types, i, 1)
1165
+
1166
+ if before % 8 != 0:
1167
+ raise ValueError("expected before index should have number of bool mod 8 equal 0")
1168
+ after = min(7, after)
1169
+ bits = int.from_bytes(value[array_index : array_index + 1])
1170
+ # Parse bool values into multiple byte strings
1171
+ for bool_i in range(after + 1):
1172
+ mask = 128 >> bool_i
1173
+ if mask & bits:
1174
+ value_partitions.append(b"\x80")
1175
+ else:
1176
+ value_partitions.append(b"\x00")
1177
+ i += after
1178
+ array_index += 1
1179
+ else:
1180
+ curr_len = _get_max_bytes_len(child_type)
1181
+ value_partitions.append(value[array_index : array_index + curr_len])
1182
+ array_index += curr_len
1183
+
1184
+ if array_index >= len(value) and i != len(child_types) - 1:
1185
+ raise ValueError(f"input string is not long enough to be decoded: {value!r}")
1186
+
1187
+ i += 1
1188
+
1189
+ if len(dynamic_segments) > 0:
1190
+ dynamic_segments[len(dynamic_segments) - 1][1] = len(value)
1191
+ array_index = len(value)
1192
+ if array_index < len(value):
1193
+ raise ValueError(f"input string was not fully consumed: {value!r}")
1194
+
1195
+ # Check dynamic element partitions
1196
+ segment_index = 0
1197
+ for i, child_type in enumerate(child_types):
1198
+ if _is_arc4_dynamic_type(child_type):
1199
+ segment_start, segment_end = dynamic_segments[segment_index]
1200
+ value_partitions[i] = value[segment_start:segment_end]
1201
+ segment_index += 1
1202
+
1203
+ # Decode individual tuple elements
1204
+ values = []
1205
+ for i, child_type in enumerate(child_types):
1206
+ val = child_type.value.from_bytes(value_partitions[i]) # type: ignore[attr-defined]
1207
+ val._child_types = child_type.child_types
1208
+ values.append(val)
1209
+ return values
1210
+
1211
+
1212
+ __all__ = [
1213
+ "Bool",
1214
+ "UInt8",
1215
+ "UInt16",
1216
+ "UInt32",
1217
+ "UInt64",
1218
+ "UInt128",
1219
+ "UInt256",
1220
+ "UInt512",
1221
+ "abimethod",
1222
+ ]