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/op.py ADDED
@@ -0,0 +1,639 @@
1
+ import base64
2
+ import hashlib
3
+ import json
4
+ import math
5
+ from typing import Any, Literal
6
+
7
+ import algosdk
8
+ import coincurve
9
+ import nacl.exceptions
10
+ import nacl.signing
11
+ from Cryptodome.Hash import SHA512, keccak
12
+ from ecdsa import ( # type: ignore # noqa: PGH003
13
+ BadSignatureError,
14
+ NIST256p,
15
+ SECP256k1,
16
+ VerifyingKey,
17
+ )
18
+
19
+ from algopy_testing.constants import BITS_IN_BYTE, MAX_BYTES_SIZE, MAX_UINT64
20
+ from algopy_testing.enums import ECDSA, Base64, VrfVerify
21
+ from algopy_testing.models.global_values import Global
22
+ from algopy_testing.models.gtxn import GTxn
23
+ from algopy_testing.models.itxn import ITxn
24
+ from algopy_testing.models.txn import Txn
25
+ from algopy_testing.primitives.biguint import BigUInt
26
+ from algopy_testing.primitives.bytes import Bytes
27
+ from algopy_testing.primitives.uint64 import UInt64
28
+ from algopy_testing.utils import as_bytes, as_int, as_int8, as_int64, as_int512, int_to_bytes
29
+
30
+
31
+ def sha256(a: Bytes | bytes, /) -> Bytes:
32
+ input_value = as_bytes(a)
33
+ return Bytes(hashlib.sha256(input_value).digest())
34
+
35
+
36
+ def sha3_256(a: Bytes | bytes, /) -> Bytes:
37
+ input_value = as_bytes(a)
38
+ return Bytes(hashlib.sha3_256(input_value).digest())
39
+
40
+
41
+ def keccak256(a: Bytes | bytes, /) -> Bytes:
42
+ input_value = as_bytes(a)
43
+ hashed_value = keccak.new(data=input_value, digest_bits=256)
44
+ return Bytes(hashed_value.digest())
45
+
46
+
47
+ def sha512_256(a: Bytes | bytes, /) -> Bytes:
48
+ input_value = as_bytes(a)
49
+ hash_object = SHA512.new(input_value, truncate="256")
50
+ return Bytes(hash_object.digest())
51
+
52
+
53
+ def ed25519verify_bare(a: Bytes | bytes, b: Bytes | bytes, c: Bytes | bytes, /) -> bool:
54
+ a, b, c = (as_bytes(x) for x in [a, b, c])
55
+
56
+ try:
57
+ verify_key = nacl.signing.VerifyKey(c)
58
+ verify_key.verify(a, b)
59
+ except nacl.exceptions.BadSignatureError:
60
+ return False
61
+ else:
62
+ return True
63
+
64
+
65
+ def ed25519verify(a: Bytes | bytes, b: Bytes | bytes, c: Bytes | bytes, /) -> bool:
66
+ from algopy_testing.context import get_test_context
67
+ from algopy_testing.utils import as_bytes
68
+
69
+ try:
70
+ ctx = get_test_context()
71
+ except LookupError as e:
72
+ raise RuntimeError(
73
+ "function must be run within an active context"
74
+ " using `with algopy_testing.context.new_context():`"
75
+ ) from e
76
+
77
+ # TODO: Decide on whether to pick clear or approval depending on OnComplete state
78
+ if not ctx._txn_fields:
79
+ raise RuntimeError("`txn_fields` must be set in the context")
80
+
81
+ program_bytes = as_bytes(ctx._txn_fields.get("approval_program", None))
82
+ if not program_bytes:
83
+ raise RuntimeError("`program_bytes` must be set in the context")
84
+
85
+ decoded_address = algosdk.encoding.decode_address(algosdk.logic.address(program_bytes))
86
+ address_bytes = as_bytes(decoded_address)
87
+ a = algosdk.constants.logic_data_prefix + address_bytes + a
88
+ return ed25519verify_bare(a, b, c)
89
+
90
+
91
+ def ecdsa_verify( # noqa: PLR0913
92
+ v: ECDSA,
93
+ a: Bytes | bytes,
94
+ b: Bytes | bytes,
95
+ c: Bytes | bytes,
96
+ d: Bytes | bytes,
97
+ e: Bytes | bytes,
98
+ /,
99
+ ) -> bool:
100
+ data_bytes, sig_r_bytes, sig_s_bytes, pubkey_x_bytes, pubkey_y_bytes = map(
101
+ as_bytes, [a, b, c, d, e]
102
+ )
103
+
104
+ curve_map = {
105
+ ECDSA.Secp256k1: SECP256k1,
106
+ ECDSA.Secp256r1: NIST256p,
107
+ }
108
+
109
+ curve = curve_map.get(v)
110
+ if curve is None:
111
+ raise ValueError(f"Unsupported ECDSA curve: {v}")
112
+
113
+ public_key = b"\x04" + pubkey_x_bytes + pubkey_y_bytes
114
+ vk = VerifyingKey.from_string(public_key, curve=curve)
115
+
116
+ # Concatenate R and S components to form the signature
117
+ signature = sig_r_bytes + sig_s_bytes
118
+ try:
119
+ vk.verify_digest(signature, data_bytes)
120
+ except BadSignatureError:
121
+ return False
122
+ return True
123
+
124
+
125
+ def ecdsa_pk_recover(
126
+ v: ECDSA, a: Bytes | bytes, b: UInt64 | int, c: Bytes | bytes, d: Bytes | bytes, /
127
+ ) -> tuple[Bytes, Bytes]:
128
+ if v is not ECDSA.Secp256k1:
129
+ raise ValueError(f"Unsupported ECDSA curve: {v}")
130
+
131
+ data_bytes = as_bytes(a)
132
+ r_bytes = as_bytes(c)
133
+ s_bytes = as_bytes(d)
134
+ recovery_id = int(b)
135
+
136
+ if len(r_bytes) != 32 or len(s_bytes) != 32:
137
+ raise ValueError("Invalid length for r or s bytes.")
138
+
139
+ signature_rs = r_bytes + s_bytes + bytes([recovery_id])
140
+
141
+ try:
142
+ public_key = coincurve.PublicKey.from_signature_and_message(
143
+ signature_rs, data_bytes, hasher=None
144
+ )
145
+ pubkey_x, pubkey_y = public_key.point()
146
+ except Exception as e:
147
+ raise ValueError(f"Failed to recover public key: {e}") from e
148
+ else:
149
+ return Bytes(pubkey_x.to_bytes(32, byteorder="big")), Bytes(
150
+ pubkey_y.to_bytes(32, byteorder="big")
151
+ )
152
+
153
+
154
+ def ecdsa_pk_decompress(v: ECDSA, a: Bytes | bytes, /) -> tuple[Bytes, Bytes]:
155
+ if v not in [ECDSA.Secp256k1, ECDSA.Secp256r1]:
156
+ raise ValueError(f"Unsupported ECDSA curve: {v}")
157
+
158
+ compressed_pubkey = as_bytes(a)
159
+
160
+ try:
161
+ public_key = coincurve.PublicKey(compressed_pubkey)
162
+ pubkey_x, pubkey_y = public_key.point()
163
+ except Exception as e:
164
+ raise ValueError(f"Failed to decompress public key: {e}") from e
165
+ else:
166
+ return Bytes(pubkey_x.to_bytes(32, byteorder="big")), Bytes(
167
+ pubkey_y.to_bytes(32, byteorder="big")
168
+ )
169
+
170
+
171
+ def vrf_verify(
172
+ _s: VrfVerify,
173
+ _a: Bytes | bytes,
174
+ _b: Bytes | bytes,
175
+ _c: Bytes | bytes,
176
+ /,
177
+ ) -> tuple[Bytes, bool]:
178
+ raise NotImplementedError(
179
+ "'op.vrf_verify' is not implemented. Mock using preferred testing tools."
180
+ )
181
+
182
+
183
+ def addw(a: UInt64 | int, b: UInt64 | int, /) -> tuple[UInt64, UInt64]:
184
+ a = as_int64(a)
185
+ b = as_int64(b)
186
+ result = a + b
187
+ return _int_to_uint128(result)
188
+
189
+
190
+ def base64_decode(e: Base64, a: Bytes | bytes, /) -> Bytes:
191
+ a_str = _bytes_to_string(a, "illegal base64 data")
192
+ a_str = a_str + "=" # append padding to ensure there is at least one
193
+
194
+ result = (
195
+ base64.urlsafe_b64decode(a_str) if e == Base64.URLEncoding else base64.b64decode(a_str)
196
+ )
197
+ return Bytes(result)
198
+
199
+
200
+ def bitlen(a: Bytes | UInt64 | bytes | int, /) -> UInt64:
201
+ int_value = int.from_bytes(as_bytes(a)) if (isinstance(a, Bytes | bytes)) else as_int64(a)
202
+ return UInt64(int_value.bit_length())
203
+
204
+
205
+ def bsqrt(a: BigUInt | int, /) -> BigUInt:
206
+ a = as_int512(a)
207
+ return BigUInt(math.isqrt(a))
208
+
209
+
210
+ def btoi(a: Bytes | bytes, /) -> UInt64:
211
+ a_bytes = as_bytes(a)
212
+ if len(a_bytes) > 8:
213
+ raise ValueError(f"btoi arg too long, got [{len(a_bytes)}]bytes")
214
+ return UInt64(int.from_bytes(a_bytes))
215
+
216
+
217
+ def bzero(a: UInt64 | int, /) -> Bytes:
218
+ a = as_int64(a)
219
+ if a > MAX_BYTES_SIZE:
220
+ raise ValueError("bzero attempted to create a too large string")
221
+ return Bytes(b"\x00" * a)
222
+
223
+
224
+ def divmodw(
225
+ a: UInt64 | int, b: UInt64 | int, c: UInt64 | int, d: UInt64 | int, /
226
+ ) -> tuple[UInt64, UInt64, UInt64, UInt64]:
227
+ i = _uint128_to_int(a, b)
228
+ j = _uint128_to_int(c, d)
229
+ d = i // j
230
+ m = i % j
231
+ return _int_to_uint128(d) + _int_to_uint128(m)
232
+
233
+
234
+ def divw(a: UInt64 | int, b: UInt64 | int, c: UInt64 | int, /) -> UInt64:
235
+ i = _uint128_to_int(a, b)
236
+ c = as_int64(c)
237
+ return UInt64(i // c)
238
+
239
+
240
+ def err() -> None:
241
+ raise RuntimeError("err opcode executed")
242
+
243
+
244
+ def exp(a: UInt64 | int, b: UInt64 | int, /) -> UInt64:
245
+ a = as_int64(a)
246
+ b = as_int64(b)
247
+ if a == b and a == 0:
248
+ raise ArithmeticError("0^0 is undefined")
249
+ return UInt64(a**b)
250
+
251
+
252
+ def expw(a: UInt64 | int, b: UInt64 | int, /) -> tuple[UInt64, UInt64]:
253
+ a = as_int64(a)
254
+ b = as_int64(b)
255
+ if a == b and a == 0:
256
+ raise ArithmeticError("0^0 is undefined")
257
+ result = a**b
258
+ return _int_to_uint128(result)
259
+
260
+
261
+ def extract(a: Bytes | bytes, b: UInt64 | int, c: UInt64 | int, /) -> Bytes:
262
+ a = as_bytes(a)
263
+ start = as_int64(b)
264
+ stop = start + as_int64(c)
265
+
266
+ if isinstance(b, int) and isinstance(c, int) and c == 0:
267
+ stop = len(a)
268
+
269
+ if start > len(a):
270
+ raise ValueError(f"extraction start {start} is beyond length")
271
+ if stop > len(a):
272
+ raise ValueError(f"extraction end {stop} is beyond length")
273
+
274
+ return Bytes(a)[slice(start, stop)]
275
+
276
+
277
+ def extract_uint16(a: Bytes | bytes, b: UInt64 | int, /) -> UInt64:
278
+ result = extract(a, b, 2)
279
+ result_int = int.from_bytes(result.value)
280
+ return UInt64(result_int)
281
+
282
+
283
+ def extract_uint32(a: Bytes | bytes, b: UInt64 | int, /) -> UInt64:
284
+ result = extract(a, b, 4)
285
+ result_int = int.from_bytes(result.value)
286
+ return UInt64(result_int)
287
+
288
+
289
+ def extract_uint64(a: Bytes | bytes, b: UInt64 | int, /) -> UInt64:
290
+ result = extract(a, b, 8)
291
+ result_int = int.from_bytes(result.value)
292
+ return UInt64(result_int)
293
+
294
+
295
+ def getbit(a: Bytes | UInt64 | bytes | int, b: UInt64 | int, /) -> UInt64:
296
+ if isinstance(a, Bytes | bytes):
297
+ return _getbit_bytes(a, b)
298
+ if isinstance(a, UInt64 | int):
299
+ a_bytes = _uint64_to_bytes(a)
300
+ return _getbit_bytes(a_bytes, b, "little")
301
+ raise TypeError("Unknown type for argument a")
302
+
303
+
304
+ def getbyte(a: Bytes | bytes, b: UInt64 | int, /) -> UInt64:
305
+ a = as_bytes(a)
306
+ int_list = list(a)
307
+
308
+ max_index = len(int_list) - 1
309
+ b = as_int(b, max=max_index)
310
+
311
+ return UInt64(int_list[b])
312
+
313
+
314
+ def itob(a: UInt64 | int, /) -> Bytes:
315
+ return Bytes(_uint64_to_bytes(a))
316
+
317
+
318
+ def mulw(a: UInt64 | int, b: UInt64 | int, /) -> tuple[UInt64, UInt64]:
319
+ a = as_int64(a)
320
+ b = as_int64(b)
321
+ result = a * b
322
+ return _int_to_uint128(result)
323
+
324
+
325
+ def replace(a: Bytes | bytes, b: UInt64 | int, c: Bytes | bytes, /) -> Bytes:
326
+ a = a if (isinstance(a, Bytes)) else Bytes(a)
327
+ b = as_int64(b)
328
+ c = as_bytes(c)
329
+ if b + len(c) > len(a):
330
+ raise ValueError(f"expected value <= {len(a)}, got: {b + len(c)}")
331
+ return a[slice(0, b)] + c + a[slice(b + len(c), len(a))]
332
+
333
+
334
+ def select_bytes(a: Bytes | bytes, b: Bytes | bytes, c: bool | UInt64 | int, /) -> Bytes:
335
+ a = as_bytes(a)
336
+ b = as_bytes(b)
337
+ c = int(c) if (isinstance(c, bool)) else as_int64(c)
338
+ return Bytes(b if c != 0 else a)
339
+
340
+
341
+ def select_uint64(a: UInt64 | int, b: UInt64 | int, c: bool | UInt64 | int, /) -> UInt64:
342
+ a = as_int64(a)
343
+ b = as_int64(b)
344
+ c = int(c) if (isinstance(c, bool)) else as_int64(c)
345
+ return UInt64(b if c != 0 else a)
346
+
347
+
348
+ def setbit_bytes(a: Bytes | bytes, b: UInt64 | int, c: UInt64 | int, /) -> Bytes:
349
+ return _setbit_bytes(a, b, c)
350
+
351
+
352
+ def setbit_uint64(a: UInt64 | int, b: UInt64 | int, c: UInt64 | int, /) -> UInt64:
353
+ a_bytes = _uint64_to_bytes(a)
354
+ result = _setbit_bytes(a_bytes, b, c, "little")
355
+ return UInt64(int.from_bytes(result.value))
356
+
357
+
358
+ def setbyte(a: Bytes | bytes, b: UInt64 | int, c: UInt64 | int, /) -> Bytes:
359
+ a = as_bytes(a)
360
+ int_list = list(a)
361
+
362
+ max_index = len(int_list) - 1
363
+ b = as_int(b, max=max_index)
364
+ c = as_int8(c)
365
+
366
+ int_list[b] = c
367
+ return Bytes(_int_list_to_bytes(int_list))
368
+
369
+
370
+ def shl(a: UInt64 | int, b: UInt64 | int, /) -> UInt64:
371
+ a = as_int64(a)
372
+ b = as_int(b, max=63)
373
+ result = (a * (2**b)) % (2**64)
374
+ return UInt64(result)
375
+
376
+
377
+ def shr(a: UInt64 | int, b: UInt64 | int, /) -> UInt64:
378
+ a = as_int64(a)
379
+ b = as_int(b, max=63)
380
+ result = a // (2**b)
381
+ return UInt64(result)
382
+
383
+
384
+ def sqrt(a: UInt64 | int, /) -> UInt64:
385
+ a = as_int64(a)
386
+ return UInt64(math.isqrt(a))
387
+
388
+
389
+ def substring(a: Bytes | bytes, b: UInt64 | int, c: UInt64 | int, /) -> Bytes:
390
+ a = as_bytes(a)
391
+ c = as_int(c, max=len(a))
392
+ b = as_int(b, max=c)
393
+ return Bytes(a)[slice(b, c)]
394
+
395
+
396
+ def concat(a: Bytes | bytes, b: Bytes | bytes, /) -> Bytes:
397
+ a = a if (isinstance(a, Bytes)) else Bytes(a)
398
+ b = b if (isinstance(b, Bytes)) else Bytes(b)
399
+ return a + b
400
+
401
+
402
+ def _int_to_uint128(a: int) -> tuple[UInt64, UInt64]:
403
+ cf, rest = a >> 64, a & MAX_UINT64
404
+ return (
405
+ UInt64(cf),
406
+ UInt64(rest),
407
+ )
408
+
409
+
410
+ def _uint128_to_int(a: UInt64 | int, b: UInt64 | int) -> int:
411
+ a = as_int64(a)
412
+ b = as_int64(b)
413
+ return (a << 64) + b
414
+
415
+
416
+ def _uint64_to_bytes(a: UInt64 | int) -> bytes:
417
+ a = as_int64(a)
418
+ return a.to_bytes(8)
419
+
420
+
421
+ def _int_list_to_bytes(a: list[int]) -> bytes:
422
+ return b"".join([b"\x00" if i == 0 else int_to_bytes(i) for i in a])
423
+
424
+
425
+ def _getbit_bytes(
426
+ a: Bytes | bytes, b: UInt64 | int, byteorder: Literal["little", "big"] = "big"
427
+ ) -> UInt64:
428
+ a = as_bytes(a)
429
+ if byteorder != "big": # reverse bytes if NOT big endian
430
+ a = bytes(reversed(a))
431
+
432
+ int_list = list(a)
433
+ max_index = len(int_list) * BITS_IN_BYTE - 1
434
+ b = as_int(b, max=max_index)
435
+
436
+ byte_index = b // BITS_IN_BYTE
437
+ bit_index = b % BITS_IN_BYTE
438
+ if byteorder == "big":
439
+ bit_index = 7 - bit_index
440
+ bit = _get_bit(int_list[byte_index], bit_index)
441
+
442
+ return UInt64(bit)
443
+
444
+
445
+ def _setbit_bytes(
446
+ a: Bytes | bytes, b: UInt64 | int, c: UInt64 | int, byteorder: Literal["little", "big"] = "big"
447
+ ) -> Bytes:
448
+ a = as_bytes(a)
449
+ if byteorder != "big": # reverse bytes if NOT big endian
450
+ a = bytes(reversed(a))
451
+
452
+ int_list = list(a)
453
+ max_index = len(int_list) * BITS_IN_BYTE - 1
454
+ b = as_int(b, max=max_index)
455
+ c = as_int(c, max=1)
456
+
457
+ byte_index = b // BITS_IN_BYTE
458
+ bit_index = b % BITS_IN_BYTE
459
+ if byteorder == "big":
460
+ bit_index = 7 - bit_index
461
+ int_list[byte_index] = _set_bit(int_list[byte_index], bit_index, c)
462
+
463
+ # reverse int array if NOT big endian before casting it to Bytes
464
+ if byteorder != "big":
465
+ int_list = list(reversed(int_list))
466
+
467
+ return Bytes(_int_list_to_bytes(int_list))
468
+
469
+
470
+ def _get_bit(v: int, index: int) -> int:
471
+ return (v >> index) & 1
472
+
473
+
474
+ def _set_bit(v: int, index: int, x: int) -> int:
475
+ """Set the index:th bit of v to 1 if x is truthy, else to 0, and return the new value."""
476
+ mask = 1 << index # Compute mask, an integer with just bit 'index' set.
477
+ v &= ~mask # Clear the bit indicated by the mask (if x is False)
478
+ if x:
479
+ v |= mask # If x was True, set the bit indicated by the mask.
480
+ return v
481
+
482
+
483
+ def _bytes_to_string(a: Bytes | bytes, err_msg: str) -> str:
484
+ a = as_bytes(a)
485
+ try:
486
+ return a.decode()
487
+ except UnicodeDecodeError:
488
+ raise ValueError(err_msg) from None
489
+
490
+
491
+ class JsonRef:
492
+ @staticmethod
493
+ def _load_json(a: Bytes | bytes) -> dict[Any, Any]:
494
+ a = as_bytes(a)
495
+ try:
496
+ # load the whole json payload as an array of key value pairs
497
+ pairs = json.loads(a, object_pairs_hook=lambda x: x)
498
+ except json.JSONDecodeError:
499
+ raise ValueError("error while parsing JSON text, invalid json text") from None
500
+
501
+ # turn the pairs into the dictionay for the top level,
502
+ # all other levels remain as key value pairs
503
+ # e.g.
504
+ # input bytes: b'{"key0": 1,"key1": {"key2":2,"key2":"10"}, "key2": "test"}'
505
+ # output dict: {'key0': 1, 'key1': [('key2', 2), ('key2', '10')], 'key2': 'test'}
506
+ result = dict(pairs)
507
+ if len(pairs) != len(result):
508
+ raise ValueError(
509
+ "error while parsing JSON text, invalid json text, duplicate keys found"
510
+ )
511
+
512
+ return result
513
+
514
+ @staticmethod
515
+ def _raise_key_error(key: str) -> None:
516
+ raise ValueError(f"key {key} not found in JSON text")
517
+
518
+ @staticmethod
519
+ def json_string(a: Bytes | bytes, b: Bytes | bytes, /) -> Bytes:
520
+ b_str = _bytes_to_string(b, "can't decode bytes as string")
521
+ obj = JsonRef._load_json(a)
522
+ result = None
523
+
524
+ try:
525
+ result = obj[b_str]
526
+ except KeyError:
527
+ JsonRef._raise_key_error(b_str)
528
+
529
+ if not isinstance(result, str):
530
+ raise TypeError(f"value must be a string type, not {type(result).__name__!r}")
531
+
532
+ # encode with `surrogatepass` to allow sequences such as `\uD800`
533
+ # decode with `replace` to replace with official replacement character `U+FFFD`
534
+ # encode with default settings to get the final bytes result
535
+ result = result.encode("utf-16", "surrogatepass").decode("utf-16", "replace").encode()
536
+ return Bytes(result)
537
+
538
+ @staticmethod
539
+ def json_uint64(a: Bytes | bytes, b: Bytes | bytes, /) -> UInt64:
540
+ b_str = _bytes_to_string(b, "can't decode bytes as string")
541
+ obj = JsonRef._load_json(a)
542
+ result = None
543
+
544
+ try:
545
+ result = obj[b_str]
546
+ except KeyError:
547
+ JsonRef._raise_key_error(b_str)
548
+
549
+ result = as_int(result, max=MAX_UINT64)
550
+ return UInt64(result)
551
+
552
+ @staticmethod
553
+ def json_object(a: Bytes | bytes, b: Bytes | bytes, /) -> Bytes:
554
+ b_str = _bytes_to_string(b, "can't decode bytes as string")
555
+ obj = JsonRef._load_json(a)
556
+ result = None
557
+ try:
558
+ # using a custom dict object to allow duplicate keys which is essentially a list
559
+ result = obj[b_str]
560
+ except KeyError:
561
+ JsonRef._raise_key_error(b_str)
562
+
563
+ if not isinstance(result, list) or not all(isinstance(i, tuple) for i in result):
564
+ raise TypeError(f"value must be an object type, not {type(result).__name__!r}")
565
+
566
+ result = _MultiKeyDict(result)
567
+ result_string = json.dumps(result, separators=(",", ":"))
568
+ return Bytes(result_string.encode())
569
+
570
+
571
+ class _MultiKeyDict(dict[Any, Any]):
572
+ def __init__(self, items: list[Any]):
573
+ self[""] = ""
574
+ items = [
575
+ (
576
+ (i[0], _MultiKeyDict(i[1]))
577
+ if isinstance(i[1], list) and all(isinstance(j, tuple) for j in i[1])
578
+ else i
579
+ )
580
+ for i in items
581
+ ]
582
+ self._items = items
583
+
584
+ def items(self) -> Any:
585
+ return self._items
586
+
587
+
588
+ __all__ = [
589
+ "Base64",
590
+ "BigUInt",
591
+ "ECDSA",
592
+ "Global",
593
+ "GTxn",
594
+ "ITxn",
595
+ "JsonRef",
596
+ "Txn",
597
+ "UInt64",
598
+ "VrfVerify",
599
+ "addw",
600
+ "base64_decode",
601
+ "bitlen",
602
+ "bsqrt",
603
+ "btoi",
604
+ "bzero",
605
+ "concat",
606
+ "divmodw",
607
+ "divw",
608
+ "ecdsa_pk_decompress",
609
+ "ecdsa_pk_recover",
610
+ "ecdsa_verify",
611
+ "ed25519verify",
612
+ "ed25519verify_bare",
613
+ "err",
614
+ "exp",
615
+ "expw",
616
+ "extract",
617
+ "extract_uint16",
618
+ "extract_uint32",
619
+ "extract_uint64",
620
+ "getbit",
621
+ "getbyte",
622
+ "itob",
623
+ "keccak256",
624
+ "mulw",
625
+ "replace",
626
+ "select_bytes",
627
+ "select_uint64",
628
+ "setbit_bytes",
629
+ "setbit_uint64",
630
+ "setbyte",
631
+ "sha256",
632
+ "sha3_256",
633
+ "sha512_256",
634
+ "shl",
635
+ "shr",
636
+ "sqrt",
637
+ "substring",
638
+ "vrf_verify",
639
+ ]
@@ -0,0 +1,6 @@
1
+ from algopy_testing.primitives.biguint import BigUInt
2
+ from algopy_testing.primitives.bytes import Bytes
3
+ from algopy_testing.primitives.string import String
4
+ from algopy_testing.primitives.uint64 import UInt64
5
+
6
+ __all__ = ["UInt64", "Bytes", "BigUInt", "String"]