algorand-python-testing 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- algopy/__init__.py +42 -0
- algopy/arc4.py +1 -0
- algopy/gtxn.py +1 -0
- algopy/itxn.py +1 -0
- algopy/op.py +1 -0
- algopy/py.typed +0 -0
- algopy_testing/__init__.py +47 -0
- algopy_testing/arc4.py +1222 -0
- algopy_testing/constants.py +17 -0
- algopy_testing/context.py +769 -0
- algopy_testing/decorators/__init__.py +0 -0
- algopy_testing/decorators/abimethod.py +146 -0
- algopy_testing/decorators/subroutine.py +9 -0
- algopy_testing/enums.py +39 -0
- algopy_testing/gtxn.py +239 -0
- algopy_testing/itxn.py +353 -0
- algopy_testing/models/__init__.py +23 -0
- algopy_testing/models/account.py +128 -0
- algopy_testing/models/application.py +72 -0
- algopy_testing/models/asset.py +109 -0
- algopy_testing/models/contract.py +69 -0
- algopy_testing/models/global_values.py +67 -0
- algopy_testing/models/gtxn.py +40 -0
- algopy_testing/models/itxn.py +34 -0
- algopy_testing/models/transactions.py +158 -0
- algopy_testing/models/txn.py +111 -0
- algopy_testing/models/unsigned_builtins.py +15 -0
- algopy_testing/op.py +639 -0
- algopy_testing/primitives/__init__.py +6 -0
- algopy_testing/primitives/biguint.py +147 -0
- algopy_testing/primitives/bytes.py +173 -0
- algopy_testing/primitives/string.py +67 -0
- algopy_testing/primitives/uint64.py +210 -0
- algopy_testing/py.typed +0 -0
- algopy_testing/state/__init__.py +4 -0
- algopy_testing/state/global_state.py +73 -0
- algopy_testing/state/local_state.py +54 -0
- algopy_testing/utils.py +156 -0
- algorand_python_testing-0.1.0.dist-info/METADATA +29 -0
- algorand_python_testing-0.1.0.dist-info/RECORD +41 -0
- algorand_python_testing-0.1.0.dist-info/WHEEL +4 -0
algopy_testing/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
|
+
]
|