algokit-utils 2.4.0__py3-none-any.whl → 3.0.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.
Potentially problematic release.
This version of algokit-utils might be problematic. Click here for more details.
- algokit_utils/__init__.py +23 -181
- algokit_utils/_debugging.py +89 -45
- algokit_utils/_legacy_v2/__init__.py +177 -0
- algokit_utils/{_ensure_funded.py → _legacy_v2/_ensure_funded.py} +21 -24
- algokit_utils/{_transfer.py → _legacy_v2/_transfer.py} +26 -23
- algokit_utils/_legacy_v2/account.py +203 -0
- algokit_utils/_legacy_v2/application_client.py +1472 -0
- algokit_utils/_legacy_v2/application_specification.py +21 -0
- algokit_utils/_legacy_v2/asset.py +168 -0
- algokit_utils/_legacy_v2/common.py +28 -0
- algokit_utils/_legacy_v2/deploy.py +822 -0
- algokit_utils/_legacy_v2/logic_error.py +14 -0
- algokit_utils/{models.py → _legacy_v2/models.py} +16 -45
- algokit_utils/_legacy_v2/network_clients.py +144 -0
- algokit_utils/account.py +12 -183
- algokit_utils/accounts/__init__.py +2 -0
- algokit_utils/accounts/account_manager.py +912 -0
- algokit_utils/accounts/kmd_account_manager.py +161 -0
- algokit_utils/algorand.py +359 -0
- algokit_utils/application_client.py +9 -1447
- algokit_utils/application_specification.py +39 -197
- algokit_utils/applications/__init__.py +7 -0
- algokit_utils/applications/abi.py +275 -0
- algokit_utils/applications/app_client.py +2108 -0
- algokit_utils/applications/app_deployer.py +725 -0
- algokit_utils/applications/app_factory.py +1134 -0
- algokit_utils/applications/app_manager.py +578 -0
- algokit_utils/applications/app_spec/__init__.py +2 -0
- algokit_utils/applications/app_spec/arc32.py +207 -0
- algokit_utils/applications/app_spec/arc56.py +989 -0
- algokit_utils/applications/enums.py +40 -0
- algokit_utils/asset.py +32 -168
- algokit_utils/assets/__init__.py +1 -0
- algokit_utils/assets/asset_manager.py +336 -0
- algokit_utils/beta/_utils.py +36 -0
- algokit_utils/beta/account_manager.py +4 -195
- algokit_utils/beta/algorand_client.py +4 -314
- algokit_utils/beta/client_manager.py +5 -74
- algokit_utils/beta/composer.py +5 -712
- algokit_utils/clients/__init__.py +2 -0
- algokit_utils/clients/client_manager.py +738 -0
- algokit_utils/clients/dispenser_api_client.py +224 -0
- algokit_utils/common.py +8 -26
- algokit_utils/config.py +76 -29
- algokit_utils/deploy.py +7 -894
- algokit_utils/dispenser_api.py +8 -176
- algokit_utils/errors/__init__.py +1 -0
- algokit_utils/errors/logic_error.py +121 -0
- algokit_utils/logic_error.py +7 -82
- algokit_utils/models/__init__.py +8 -0
- algokit_utils/models/account.py +217 -0
- algokit_utils/models/amount.py +200 -0
- algokit_utils/models/application.py +91 -0
- algokit_utils/models/network.py +29 -0
- algokit_utils/models/simulate.py +11 -0
- algokit_utils/models/state.py +68 -0
- algokit_utils/models/transaction.py +100 -0
- algokit_utils/network_clients.py +7 -128
- algokit_utils/protocols/__init__.py +2 -0
- algokit_utils/protocols/account.py +22 -0
- algokit_utils/protocols/typed_clients.py +108 -0
- algokit_utils/transactions/__init__.py +3 -0
- algokit_utils/transactions/transaction_composer.py +2499 -0
- algokit_utils/transactions/transaction_creator.py +688 -0
- algokit_utils/transactions/transaction_sender.py +1219 -0
- {algokit_utils-2.4.0.dist-info → algokit_utils-3.0.0.dist-info}/METADATA +11 -7
- algokit_utils-3.0.0.dist-info/RECORD +70 -0
- {algokit_utils-2.4.0.dist-info → algokit_utils-3.0.0.dist-info}/WHEEL +1 -1
- algokit_utils-2.4.0.dist-info/RECORD +0 -24
- {algokit_utils-2.4.0.dist-info → algokit_utils-3.0.0.dist-info}/LICENSE +0 -0
|
@@ -0,0 +1,989 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import base64
|
|
4
|
+
import json
|
|
5
|
+
from base64 import b64encode
|
|
6
|
+
from collections.abc import Callable, Sequence
|
|
7
|
+
from dataclasses import asdict, dataclass
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from typing import Any, Literal, overload
|
|
10
|
+
|
|
11
|
+
import algosdk
|
|
12
|
+
from algosdk.abi import Method as AlgosdkMethod
|
|
13
|
+
|
|
14
|
+
from algokit_utils.applications.app_spec.arc32 import Arc32Contract
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"Actions",
|
|
18
|
+
"Arc56Contract",
|
|
19
|
+
"BareActions",
|
|
20
|
+
"Boxes",
|
|
21
|
+
"ByteCode",
|
|
22
|
+
"CallEnum",
|
|
23
|
+
"Compiler",
|
|
24
|
+
"CompilerInfo",
|
|
25
|
+
"CompilerVersion",
|
|
26
|
+
"CreateEnum",
|
|
27
|
+
"DefaultValue",
|
|
28
|
+
"Event",
|
|
29
|
+
"EventArg",
|
|
30
|
+
"Global",
|
|
31
|
+
"Keys",
|
|
32
|
+
"Local",
|
|
33
|
+
"Maps",
|
|
34
|
+
"Method",
|
|
35
|
+
"MethodArg",
|
|
36
|
+
"Network",
|
|
37
|
+
"PcOffsetMethod",
|
|
38
|
+
"ProgramSourceInfo",
|
|
39
|
+
"Recommendations",
|
|
40
|
+
"Returns",
|
|
41
|
+
"Schema",
|
|
42
|
+
"ScratchVariables",
|
|
43
|
+
"Source",
|
|
44
|
+
"SourceInfo",
|
|
45
|
+
"SourceInfoModel",
|
|
46
|
+
"State",
|
|
47
|
+
"StorageKey",
|
|
48
|
+
"StorageMap",
|
|
49
|
+
"StructField",
|
|
50
|
+
"TemplateVariables",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class _ActionType(str, Enum):
|
|
55
|
+
CALL = "CALL"
|
|
56
|
+
CREATE = "CREATE"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@dataclass
|
|
60
|
+
class StructField:
|
|
61
|
+
"""Represents a field in a struct type."""
|
|
62
|
+
|
|
63
|
+
name: str
|
|
64
|
+
"""The name of the struct field"""
|
|
65
|
+
type: list[StructField] | str
|
|
66
|
+
"""The type of the struct field, either a string or list of StructFields"""
|
|
67
|
+
|
|
68
|
+
@staticmethod
|
|
69
|
+
def from_dict(data: dict[str, Any]) -> StructField:
|
|
70
|
+
if isinstance(data["type"], list):
|
|
71
|
+
data["type"] = [StructField.from_dict(item) for item in data["type"]]
|
|
72
|
+
return StructField(**data)
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
class CallEnum(str, Enum):
|
|
76
|
+
"""Enum representing different call types for application transactions."""
|
|
77
|
+
|
|
78
|
+
CLEAR_STATE = "ClearState"
|
|
79
|
+
CLOSE_OUT = "CloseOut"
|
|
80
|
+
DELETE_APPLICATION = "DeleteApplication"
|
|
81
|
+
NO_OP = "NoOp"
|
|
82
|
+
OPT_IN = "OptIn"
|
|
83
|
+
UPDATE_APPLICATION = "UpdateApplication"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class CreateEnum(str, Enum):
|
|
87
|
+
"""Enum representing different create types for application transactions."""
|
|
88
|
+
|
|
89
|
+
DELETE_APPLICATION = "DeleteApplication"
|
|
90
|
+
NO_OP = "NoOp"
|
|
91
|
+
OPT_IN = "OptIn"
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class BareActions:
|
|
96
|
+
"""Represents bare call and create actions for an application."""
|
|
97
|
+
|
|
98
|
+
call: list[CallEnum]
|
|
99
|
+
"""The list of allowed call actions"""
|
|
100
|
+
create: list[CreateEnum]
|
|
101
|
+
"""The list of allowed create actions"""
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
def from_dict(data: dict[str, Any]) -> BareActions:
|
|
105
|
+
return BareActions(**data)
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass
|
|
109
|
+
class ByteCode:
|
|
110
|
+
"""Represents the approval and clear program bytecode."""
|
|
111
|
+
|
|
112
|
+
approval: str
|
|
113
|
+
"""The base64 encoded approval program bytecode"""
|
|
114
|
+
clear: str
|
|
115
|
+
"""The base64 encoded clear program bytecode"""
|
|
116
|
+
|
|
117
|
+
@staticmethod
|
|
118
|
+
def from_dict(data: dict[str, Any]) -> ByteCode:
|
|
119
|
+
return ByteCode(**data)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
class Compiler(str, Enum):
|
|
123
|
+
"""Enum representing different compiler types."""
|
|
124
|
+
|
|
125
|
+
ALGOD = "algod"
|
|
126
|
+
PUYA = "puya"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@dataclass
|
|
130
|
+
class CompilerVersion:
|
|
131
|
+
"""Represents compiler version information."""
|
|
132
|
+
|
|
133
|
+
commit_hash: str | None = None
|
|
134
|
+
"""The git commit hash of the compiler"""
|
|
135
|
+
major: int | None = None
|
|
136
|
+
"""The major version number"""
|
|
137
|
+
minor: int | None = None
|
|
138
|
+
"""The minor version number"""
|
|
139
|
+
patch: int | None = None
|
|
140
|
+
"""The patch version number"""
|
|
141
|
+
|
|
142
|
+
@staticmethod
|
|
143
|
+
def from_dict(data: dict[str, Any]) -> CompilerVersion:
|
|
144
|
+
return CompilerVersion(**data)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@dataclass
|
|
148
|
+
class CompilerInfo:
|
|
149
|
+
"""Information about the compiler used."""
|
|
150
|
+
|
|
151
|
+
compiler: Compiler
|
|
152
|
+
"""The type of compiler used"""
|
|
153
|
+
compiler_version: CompilerVersion
|
|
154
|
+
"""Version information for the compiler"""
|
|
155
|
+
|
|
156
|
+
@staticmethod
|
|
157
|
+
def from_dict(data: dict[str, Any]) -> CompilerInfo:
|
|
158
|
+
data["compiler_version"] = CompilerVersion.from_dict(data["compiler_version"])
|
|
159
|
+
return CompilerInfo(**data)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
@dataclass
|
|
163
|
+
class Network:
|
|
164
|
+
"""Network-specific application information."""
|
|
165
|
+
|
|
166
|
+
app_id: int
|
|
167
|
+
"""The application ID on the network"""
|
|
168
|
+
|
|
169
|
+
@staticmethod
|
|
170
|
+
def from_dict(data: dict[str, Any]) -> Network:
|
|
171
|
+
return Network(**data)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
@dataclass
|
|
175
|
+
class ScratchVariables:
|
|
176
|
+
"""Information about scratch space variables."""
|
|
177
|
+
|
|
178
|
+
slot: int
|
|
179
|
+
"""The scratch slot number"""
|
|
180
|
+
type: str
|
|
181
|
+
"""The type of the scratch variable"""
|
|
182
|
+
|
|
183
|
+
@staticmethod
|
|
184
|
+
def from_dict(data: dict[str, Any]) -> ScratchVariables:
|
|
185
|
+
return ScratchVariables(**data)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
@dataclass
|
|
189
|
+
class Source:
|
|
190
|
+
"""Source code for approval and clear programs."""
|
|
191
|
+
|
|
192
|
+
approval: str
|
|
193
|
+
"""The base64 encoded approval program source"""
|
|
194
|
+
clear: str
|
|
195
|
+
"""The base64 encoded clear program source"""
|
|
196
|
+
|
|
197
|
+
@staticmethod
|
|
198
|
+
def from_dict(data: dict[str, Any]) -> Source:
|
|
199
|
+
return Source(**data)
|
|
200
|
+
|
|
201
|
+
def get_decoded_approval(self) -> str:
|
|
202
|
+
"""Get decoded approval program source.
|
|
203
|
+
|
|
204
|
+
:return: Decoded approval program source code
|
|
205
|
+
"""
|
|
206
|
+
return self._decode_source(self.approval)
|
|
207
|
+
|
|
208
|
+
def get_decoded_clear(self) -> str:
|
|
209
|
+
"""Get decoded clear program source.
|
|
210
|
+
|
|
211
|
+
:return: Decoded clear program source code
|
|
212
|
+
"""
|
|
213
|
+
return self._decode_source(self.clear)
|
|
214
|
+
|
|
215
|
+
def _decode_source(self, b64_text: str) -> str:
|
|
216
|
+
return base64.b64decode(b64_text).decode("utf-8")
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
@dataclass
|
|
220
|
+
class Global:
|
|
221
|
+
"""Global state schema."""
|
|
222
|
+
|
|
223
|
+
bytes: int
|
|
224
|
+
"""The number of byte slices in global state"""
|
|
225
|
+
ints: int
|
|
226
|
+
"""The number of integers in global state"""
|
|
227
|
+
|
|
228
|
+
@staticmethod
|
|
229
|
+
def from_dict(data: dict[str, Any]) -> Global:
|
|
230
|
+
return Global(**data)
|
|
231
|
+
|
|
232
|
+
|
|
233
|
+
@dataclass
|
|
234
|
+
class Local:
|
|
235
|
+
"""Local state schema."""
|
|
236
|
+
|
|
237
|
+
bytes: int
|
|
238
|
+
"""The number of byte slices in local state"""
|
|
239
|
+
ints: int
|
|
240
|
+
"""The number of integers in local state"""
|
|
241
|
+
|
|
242
|
+
@staticmethod
|
|
243
|
+
def from_dict(data: dict[str, Any]) -> Local:
|
|
244
|
+
return Local(**data)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@dataclass
|
|
248
|
+
class Schema:
|
|
249
|
+
"""Application state schema."""
|
|
250
|
+
|
|
251
|
+
global_state: Global # actual schema field is "global" since it's a reserved word
|
|
252
|
+
"""The global state schema"""
|
|
253
|
+
local_state: Local # actual schema field is "local" for consistency with renamed "global"
|
|
254
|
+
"""The local state schema"""
|
|
255
|
+
|
|
256
|
+
@staticmethod
|
|
257
|
+
def from_dict(data: dict[str, Any]) -> Schema:
|
|
258
|
+
global_state = Global.from_dict(data["global"])
|
|
259
|
+
local_state = Local.from_dict(data["local"])
|
|
260
|
+
return Schema(global_state=global_state, local_state=local_state)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
@dataclass
|
|
264
|
+
class TemplateVariables:
|
|
265
|
+
"""Template variable information."""
|
|
266
|
+
|
|
267
|
+
type: str
|
|
268
|
+
"""The type of the template variable"""
|
|
269
|
+
value: str | None = None
|
|
270
|
+
"""The optional value of the template variable"""
|
|
271
|
+
|
|
272
|
+
@staticmethod
|
|
273
|
+
def from_dict(data: dict[str, Any]) -> TemplateVariables:
|
|
274
|
+
return TemplateVariables(**data)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@dataclass
|
|
278
|
+
class EventArg:
|
|
279
|
+
"""Event argument information."""
|
|
280
|
+
|
|
281
|
+
type: str
|
|
282
|
+
"""The type of the event argument"""
|
|
283
|
+
desc: str | None = None
|
|
284
|
+
"""The optional description of the argument"""
|
|
285
|
+
name: str | None = None
|
|
286
|
+
"""The optional name of the argument"""
|
|
287
|
+
struct: str | None = None
|
|
288
|
+
"""The optional struct type name"""
|
|
289
|
+
|
|
290
|
+
@staticmethod
|
|
291
|
+
def from_dict(data: dict[str, Any]) -> EventArg:
|
|
292
|
+
return EventArg(**data)
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
@dataclass
|
|
296
|
+
class Event:
|
|
297
|
+
"""Event information."""
|
|
298
|
+
|
|
299
|
+
args: list[EventArg]
|
|
300
|
+
"""The list of event arguments"""
|
|
301
|
+
name: str
|
|
302
|
+
"""The name of the event"""
|
|
303
|
+
desc: str | None = None
|
|
304
|
+
"""The optional description of the event"""
|
|
305
|
+
|
|
306
|
+
@staticmethod
|
|
307
|
+
def from_dict(data: dict[str, Any]) -> Event:
|
|
308
|
+
data["args"] = [EventArg.from_dict(item) for item in data["args"]]
|
|
309
|
+
return Event(**data)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
@dataclass
|
|
313
|
+
class Actions:
|
|
314
|
+
"""Method actions information."""
|
|
315
|
+
|
|
316
|
+
call: list[CallEnum] | None = None
|
|
317
|
+
"""The optional list of allowed call actions"""
|
|
318
|
+
create: list[CreateEnum] | None = None
|
|
319
|
+
"""The optional list of allowed create actions"""
|
|
320
|
+
|
|
321
|
+
@staticmethod
|
|
322
|
+
def from_dict(data: dict[str, Any]) -> Actions:
|
|
323
|
+
return Actions(**data)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
@dataclass
|
|
327
|
+
class DefaultValue:
|
|
328
|
+
"""Default value information for method arguments."""
|
|
329
|
+
|
|
330
|
+
data: str
|
|
331
|
+
"""The default value data"""
|
|
332
|
+
source: Literal["box", "global", "local", "literal", "method"]
|
|
333
|
+
"""The source of the default value"""
|
|
334
|
+
type: str | None = None
|
|
335
|
+
"""The optional type of the default value"""
|
|
336
|
+
|
|
337
|
+
@staticmethod
|
|
338
|
+
def from_dict(data: dict[str, Any]) -> DefaultValue:
|
|
339
|
+
return DefaultValue(**data)
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
@dataclass
|
|
343
|
+
class MethodArg:
|
|
344
|
+
"""Method argument information."""
|
|
345
|
+
|
|
346
|
+
type: str
|
|
347
|
+
"""The type of the argument"""
|
|
348
|
+
default_value: DefaultValue | None = None
|
|
349
|
+
"""The optional default value"""
|
|
350
|
+
desc: str | None = None
|
|
351
|
+
"""The optional description"""
|
|
352
|
+
name: str | None = None
|
|
353
|
+
"""The optional name"""
|
|
354
|
+
struct: str | None = None
|
|
355
|
+
"""The optional struct type name"""
|
|
356
|
+
|
|
357
|
+
@staticmethod
|
|
358
|
+
def from_dict(data: dict[str, Any]) -> MethodArg:
|
|
359
|
+
if data.get("default_value"):
|
|
360
|
+
data["default_value"] = DefaultValue.from_dict(data["default_value"])
|
|
361
|
+
return MethodArg(**data)
|
|
362
|
+
|
|
363
|
+
|
|
364
|
+
@dataclass
|
|
365
|
+
class Boxes:
|
|
366
|
+
"""Box storage requirements."""
|
|
367
|
+
|
|
368
|
+
key: str
|
|
369
|
+
"""The box key"""
|
|
370
|
+
read_bytes: int
|
|
371
|
+
"""The number of bytes to read"""
|
|
372
|
+
write_bytes: int
|
|
373
|
+
"""The number of bytes to write"""
|
|
374
|
+
app: int | None = None
|
|
375
|
+
"""The optional application ID"""
|
|
376
|
+
|
|
377
|
+
@staticmethod
|
|
378
|
+
def from_dict(data: dict[str, Any]) -> Boxes:
|
|
379
|
+
return Boxes(**data)
|
|
380
|
+
|
|
381
|
+
|
|
382
|
+
@dataclass
|
|
383
|
+
class Recommendations:
|
|
384
|
+
"""Method execution recommendations."""
|
|
385
|
+
|
|
386
|
+
accounts: list[str] | None = None
|
|
387
|
+
"""The optional list of accounts"""
|
|
388
|
+
apps: list[int] | None = None
|
|
389
|
+
"""The optional list of applications"""
|
|
390
|
+
assets: list[int] | None = None
|
|
391
|
+
"""The optional list of assets"""
|
|
392
|
+
boxes: Boxes | None = None
|
|
393
|
+
"""The optional box storage requirements"""
|
|
394
|
+
inner_transaction_count: int | None = None
|
|
395
|
+
"""The optional inner transaction count"""
|
|
396
|
+
|
|
397
|
+
@staticmethod
|
|
398
|
+
def from_dict(data: dict[str, Any]) -> Recommendations:
|
|
399
|
+
if data.get("boxes"):
|
|
400
|
+
data["boxes"] = Boxes.from_dict(data["boxes"])
|
|
401
|
+
return Recommendations(**data)
|
|
402
|
+
|
|
403
|
+
|
|
404
|
+
@dataclass
|
|
405
|
+
class Returns:
|
|
406
|
+
"""Method return information."""
|
|
407
|
+
|
|
408
|
+
type: str
|
|
409
|
+
"""The type of the return value"""
|
|
410
|
+
desc: str | None = None
|
|
411
|
+
"""The optional description"""
|
|
412
|
+
struct: str | None = None
|
|
413
|
+
"""The optional struct type name"""
|
|
414
|
+
|
|
415
|
+
@staticmethod
|
|
416
|
+
def from_dict(data: dict[str, Any]) -> Returns:
|
|
417
|
+
return Returns(**data)
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
@dataclass
|
|
421
|
+
class Method:
|
|
422
|
+
"""Method information."""
|
|
423
|
+
|
|
424
|
+
actions: Actions
|
|
425
|
+
"""The allowed actions"""
|
|
426
|
+
args: list[MethodArg]
|
|
427
|
+
"""The method arguments"""
|
|
428
|
+
name: str
|
|
429
|
+
"""The method name"""
|
|
430
|
+
returns: Returns
|
|
431
|
+
"""The return information"""
|
|
432
|
+
desc: str | None = None
|
|
433
|
+
"""The optional description"""
|
|
434
|
+
events: list[Event] | None = None
|
|
435
|
+
"""The optional list of events"""
|
|
436
|
+
readonly: bool | None = None
|
|
437
|
+
"""The optional readonly flag"""
|
|
438
|
+
recommendations: Recommendations | None = None
|
|
439
|
+
"""The optional execution recommendations"""
|
|
440
|
+
|
|
441
|
+
_abi_method: AlgosdkMethod | None = None
|
|
442
|
+
|
|
443
|
+
def __post_init__(self) -> None:
|
|
444
|
+
self._abi_method = AlgosdkMethod.undictify(asdict(self))
|
|
445
|
+
|
|
446
|
+
def to_abi_method(self) -> AlgosdkMethod:
|
|
447
|
+
"""Convert to ABI method.
|
|
448
|
+
|
|
449
|
+
:raises ValueError: If underlying ABI method is not initialized
|
|
450
|
+
:return: ABI method
|
|
451
|
+
"""
|
|
452
|
+
if self._abi_method is None:
|
|
453
|
+
raise ValueError("Underlying core ABI method class is not initialized!")
|
|
454
|
+
return self._abi_method
|
|
455
|
+
|
|
456
|
+
@staticmethod
|
|
457
|
+
def from_dict(data: dict[str, Any]) -> Method:
|
|
458
|
+
data["actions"] = Actions.from_dict(data["actions"])
|
|
459
|
+
data["args"] = [MethodArg.from_dict(item) for item in data["args"]]
|
|
460
|
+
data["returns"] = Returns.from_dict(data["returns"])
|
|
461
|
+
if data.get("events"):
|
|
462
|
+
data["events"] = [Event.from_dict(item) for item in data["events"]]
|
|
463
|
+
if data.get("recommendations"):
|
|
464
|
+
data["recommendations"] = Recommendations.from_dict(data["recommendations"])
|
|
465
|
+
return Method(**data)
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
class PcOffsetMethod(str, Enum):
|
|
469
|
+
"""PC offset method types."""
|
|
470
|
+
|
|
471
|
+
CBLOCKS = "cblocks"
|
|
472
|
+
NONE = "none"
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
@dataclass
|
|
476
|
+
class SourceInfo:
|
|
477
|
+
"""Source code location information."""
|
|
478
|
+
|
|
479
|
+
pc: list[int]
|
|
480
|
+
"""The list of program counter values"""
|
|
481
|
+
error_message: str | None = None
|
|
482
|
+
"""The optional error message"""
|
|
483
|
+
source: str | None = None
|
|
484
|
+
"""The optional source code"""
|
|
485
|
+
teal: int | None = None
|
|
486
|
+
"""The optional TEAL version"""
|
|
487
|
+
|
|
488
|
+
@staticmethod
|
|
489
|
+
def from_dict(data: dict[str, Any]) -> SourceInfo:
|
|
490
|
+
return SourceInfo(**data)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
@dataclass
|
|
494
|
+
class StorageKey:
|
|
495
|
+
"""Storage key information."""
|
|
496
|
+
|
|
497
|
+
key: str
|
|
498
|
+
"""The storage key"""
|
|
499
|
+
key_type: str
|
|
500
|
+
"""The type of the key"""
|
|
501
|
+
value_type: str
|
|
502
|
+
"""The type of the value"""
|
|
503
|
+
desc: str | None = None
|
|
504
|
+
"""The optional description"""
|
|
505
|
+
|
|
506
|
+
@staticmethod
|
|
507
|
+
def from_dict(data: dict[str, Any]) -> StorageKey:
|
|
508
|
+
return StorageKey(**data)
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
@dataclass
|
|
512
|
+
class StorageMap:
|
|
513
|
+
"""Storage map information."""
|
|
514
|
+
|
|
515
|
+
key_type: str
|
|
516
|
+
"""The type of the map keys"""
|
|
517
|
+
value_type: str
|
|
518
|
+
"""The type of the map values"""
|
|
519
|
+
desc: str | None = None
|
|
520
|
+
"""The optional description"""
|
|
521
|
+
prefix: str | None = None
|
|
522
|
+
"""The optional key prefix"""
|
|
523
|
+
|
|
524
|
+
@staticmethod
|
|
525
|
+
def from_dict(data: dict[str, Any]) -> StorageMap:
|
|
526
|
+
return StorageMap(**data)
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
@dataclass
|
|
530
|
+
class Keys:
|
|
531
|
+
"""Storage keys for different storage types."""
|
|
532
|
+
|
|
533
|
+
box: dict[str, StorageKey]
|
|
534
|
+
"""The box storage keys"""
|
|
535
|
+
global_state: dict[str, StorageKey] # actual schema field is "global" since it's a reserved word
|
|
536
|
+
"""The global state storage keys"""
|
|
537
|
+
local_state: dict[str, StorageKey] # actual schema field is "local" for consistency with renamed "global"
|
|
538
|
+
"""The local state storage keys"""
|
|
539
|
+
|
|
540
|
+
@staticmethod
|
|
541
|
+
def from_dict(data: dict[str, Any]) -> Keys:
|
|
542
|
+
box = {key: StorageKey.from_dict(value) for key, value in data["box"].items()}
|
|
543
|
+
global_state = {key: StorageKey.from_dict(value) for key, value in data["global"].items()}
|
|
544
|
+
local_state = {key: StorageKey.from_dict(value) for key, value in data["local"].items()}
|
|
545
|
+
return Keys(box=box, global_state=global_state, local_state=local_state)
|
|
546
|
+
|
|
547
|
+
|
|
548
|
+
@dataclass
|
|
549
|
+
class Maps:
|
|
550
|
+
"""Storage maps for different storage types."""
|
|
551
|
+
|
|
552
|
+
box: dict[str, StorageMap]
|
|
553
|
+
"""The box storage maps"""
|
|
554
|
+
global_state: dict[str, StorageMap] # actual schema field is "global" since it's a reserved word
|
|
555
|
+
"""The global state storage maps"""
|
|
556
|
+
local_state: dict[str, StorageMap] # actual schema field is "local" for consistency with renamed "global"
|
|
557
|
+
"""The local state storage maps"""
|
|
558
|
+
|
|
559
|
+
@staticmethod
|
|
560
|
+
def from_dict(data: dict[str, Any]) -> Maps:
|
|
561
|
+
box = {key: StorageMap.from_dict(value) for key, value in data["box"].items()}
|
|
562
|
+
global_state = {key: StorageMap.from_dict(value) for key, value in data["global"].items()}
|
|
563
|
+
local_state = {key: StorageMap.from_dict(value) for key, value in data["local"].items()}
|
|
564
|
+
return Maps(box=box, global_state=global_state, local_state=local_state)
|
|
565
|
+
|
|
566
|
+
|
|
567
|
+
@dataclass
|
|
568
|
+
class State:
|
|
569
|
+
"""Application state information."""
|
|
570
|
+
|
|
571
|
+
keys: Keys
|
|
572
|
+
"""The storage keys"""
|
|
573
|
+
maps: Maps
|
|
574
|
+
"""The storage maps"""
|
|
575
|
+
schema: Schema
|
|
576
|
+
"""The state schema"""
|
|
577
|
+
|
|
578
|
+
@staticmethod
|
|
579
|
+
def from_dict(data: dict[str, Any]) -> State:
|
|
580
|
+
data["keys"] = Keys.from_dict(data["keys"])
|
|
581
|
+
data["maps"] = Maps.from_dict(data["maps"])
|
|
582
|
+
data["schema"] = Schema.from_dict(data["schema"])
|
|
583
|
+
return State(**data)
|
|
584
|
+
|
|
585
|
+
|
|
586
|
+
@dataclass
|
|
587
|
+
class ProgramSourceInfo:
|
|
588
|
+
"""Program source information."""
|
|
589
|
+
|
|
590
|
+
pc_offset_method: PcOffsetMethod
|
|
591
|
+
"""The PC offset method"""
|
|
592
|
+
source_info: list[SourceInfo]
|
|
593
|
+
"""The list of source info entries"""
|
|
594
|
+
|
|
595
|
+
@staticmethod
|
|
596
|
+
def from_dict(data: dict[str, Any]) -> ProgramSourceInfo:
|
|
597
|
+
data["source_info"] = [SourceInfo.from_dict(item) for item in data["source_info"]]
|
|
598
|
+
return ProgramSourceInfo(**data)
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
@dataclass
|
|
602
|
+
class SourceInfoModel:
|
|
603
|
+
"""Source information for approval and clear programs."""
|
|
604
|
+
|
|
605
|
+
approval: ProgramSourceInfo
|
|
606
|
+
"""The approval program source info"""
|
|
607
|
+
clear: ProgramSourceInfo
|
|
608
|
+
"""The clear program source info"""
|
|
609
|
+
|
|
610
|
+
@staticmethod
|
|
611
|
+
def from_dict(data: dict[str, Any]) -> SourceInfoModel:
|
|
612
|
+
data["approval"] = ProgramSourceInfo.from_dict(data["approval"])
|
|
613
|
+
data["clear"] = ProgramSourceInfo.from_dict(data["clear"])
|
|
614
|
+
return SourceInfoModel(**data)
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
# constants that define which parent keys mark a region whose inner keys should remain unchanged.
|
|
618
|
+
PROTECTED_TOP_DICTS = {"networks", "scratch_variables", "template_variables", "structs"}
|
|
619
|
+
STATE_PROTECTED_PARENTS = {"keys", "maps"}
|
|
620
|
+
STATE_PROTECTED_CHILDREN = {"global", "local", "box"}
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def _is_protected_path(path: tuple[str, ...]) -> bool:
|
|
624
|
+
"""
|
|
625
|
+
Return True if the current recursion path indicates that we are inside a protected dictionary,
|
|
626
|
+
meaning that the keys should be left unchanged.
|
|
627
|
+
"""
|
|
628
|
+
return (len(path) >= 2 and path[-2] in STATE_PROTECTED_PARENTS and path[-1] in STATE_PROTECTED_CHILDREN) or ( # noqa: PLR2004
|
|
629
|
+
len(path) >= 1 and path[-1] in PROTECTED_TOP_DICTS
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def _dict_keys_to_snake_case(value: Any, path: tuple[str, ...] = ()) -> Any: # noqa: ANN401
|
|
634
|
+
"""Recursively convert dictionary keys to snake_case except in protected sections.
|
|
635
|
+
|
|
636
|
+
A dictionary is not converted if it is directly under:
|
|
637
|
+
- keys/maps sections ("global", "local", "box")
|
|
638
|
+
- or one of the top-level keys ("networks", "scratchVariables", "templateVariables", "structs")
|
|
639
|
+
(Note that once converted the parent key names become snake_case.)
|
|
640
|
+
"""
|
|
641
|
+
import re
|
|
642
|
+
|
|
643
|
+
def camel_to_snake(s: str) -> str:
|
|
644
|
+
# Use a regular expression to insert an underscore before capital letters (except at start).
|
|
645
|
+
return re.sub(r"(?<!^)(?=[A-Z])", "_", s).lower()
|
|
646
|
+
|
|
647
|
+
if isinstance(value, dict):
|
|
648
|
+
protected = _is_protected_path(path)
|
|
649
|
+
new_dict = {}
|
|
650
|
+
for key, val in value.items():
|
|
651
|
+
new_key = key if protected else camel_to_snake(key)
|
|
652
|
+
new_dict[new_key] = _dict_keys_to_snake_case(val, (*path, new_key))
|
|
653
|
+
return new_dict
|
|
654
|
+
elif isinstance(value, list):
|
|
655
|
+
return [_dict_keys_to_snake_case(item, path) for item in value]
|
|
656
|
+
else:
|
|
657
|
+
return value
|
|
658
|
+
|
|
659
|
+
|
|
660
|
+
class _Arc32ToArc56Converter:
|
|
661
|
+
def __init__(self, arc32_application_spec: str):
|
|
662
|
+
self.arc32 = json.loads(arc32_application_spec)
|
|
663
|
+
|
|
664
|
+
def convert(self) -> Arc56Contract:
|
|
665
|
+
source_data = self.arc32.get("source")
|
|
666
|
+
return Arc56Contract(
|
|
667
|
+
name=self.arc32["contract"]["name"],
|
|
668
|
+
desc=self.arc32["contract"].get("desc"),
|
|
669
|
+
arcs=[],
|
|
670
|
+
methods=self._convert_methods(self.arc32),
|
|
671
|
+
structs=self._convert_structs(self.arc32),
|
|
672
|
+
state=self._convert_state(self.arc32),
|
|
673
|
+
source=Source(**source_data) if source_data else None,
|
|
674
|
+
bare_actions=BareActions(
|
|
675
|
+
call=self._convert_actions(self.arc32.get("bare_call_config"), _ActionType.CALL),
|
|
676
|
+
create=self._convert_actions(self.arc32.get("bare_call_config"), _ActionType.CREATE),
|
|
677
|
+
),
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
def _convert_storage_keys(self, schema: dict) -> dict[str, StorageKey]:
|
|
681
|
+
"""Convert ARC32 schema declared fields to ARC56 storage keys."""
|
|
682
|
+
return {
|
|
683
|
+
name: StorageKey(
|
|
684
|
+
key=b64encode(field["key"].encode()).decode(),
|
|
685
|
+
key_type="AVMString",
|
|
686
|
+
value_type="AVMUint64" if field["type"] == "uint64" else "AVMBytes",
|
|
687
|
+
desc=field.get("descr"),
|
|
688
|
+
)
|
|
689
|
+
for name, field in schema.items()
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
def _convert_state(self, arc32: dict) -> State:
|
|
693
|
+
"""Convert ARC32 state and schema to ARC56 state specification."""
|
|
694
|
+
state_data = arc32.get("state", {})
|
|
695
|
+
return State(
|
|
696
|
+
schema=Schema(
|
|
697
|
+
global_state=Global(
|
|
698
|
+
ints=state_data.get("global", {}).get("num_uints", 0),
|
|
699
|
+
bytes=state_data.get("global", {}).get("num_byte_slices", 0),
|
|
700
|
+
),
|
|
701
|
+
local_state=Local(
|
|
702
|
+
ints=state_data.get("local", {}).get("num_uints", 0),
|
|
703
|
+
bytes=state_data.get("local", {}).get("num_byte_slices", 0),
|
|
704
|
+
),
|
|
705
|
+
),
|
|
706
|
+
keys=Keys(
|
|
707
|
+
global_state=self._convert_storage_keys(arc32.get("schema", {}).get("global", {}).get("declared", {})),
|
|
708
|
+
local_state=self._convert_storage_keys(arc32.get("schema", {}).get("local", {}).get("declared", {})),
|
|
709
|
+
box={},
|
|
710
|
+
),
|
|
711
|
+
maps=Maps(global_state={}, local_state={}, box={}),
|
|
712
|
+
)
|
|
713
|
+
|
|
714
|
+
def _convert_structs(self, arc32: dict) -> dict[str, list[StructField]]:
|
|
715
|
+
"""Extract and convert struct definitions from hints."""
|
|
716
|
+
return {
|
|
717
|
+
struct["name"]: [StructField(name=elem[0], type=elem[1]) for elem in struct["elements"]]
|
|
718
|
+
for hint in arc32.get("hints", {}).values()
|
|
719
|
+
for struct in hint.get("structs", {}).values()
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
def _convert_default_value(self, arg_type: str, default_arg: dict[str, Any] | None) -> DefaultValue | None:
|
|
723
|
+
"""Convert ARC32 default argument to ARC56 format."""
|
|
724
|
+
if not default_arg or not default_arg.get("source"):
|
|
725
|
+
return None
|
|
726
|
+
|
|
727
|
+
source_mapping = {
|
|
728
|
+
"constant": "literal",
|
|
729
|
+
"global-state": "global",
|
|
730
|
+
"local-state": "local",
|
|
731
|
+
"abi-method": "method",
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
mapped_source = source_mapping.get(default_arg["source"])
|
|
735
|
+
if not mapped_source:
|
|
736
|
+
return None
|
|
737
|
+
elif mapped_source == "method":
|
|
738
|
+
return DefaultValue(
|
|
739
|
+
source=mapped_source, # type: ignore[arg-type]
|
|
740
|
+
data=default_arg.get("data", {}).get("name"),
|
|
741
|
+
)
|
|
742
|
+
|
|
743
|
+
arg_data = default_arg.get("data")
|
|
744
|
+
|
|
745
|
+
if isinstance(arg_data, int):
|
|
746
|
+
arg_data = algosdk.abi.ABIType.from_string("uint64").encode(arg_data)
|
|
747
|
+
elif isinstance(arg_data, str):
|
|
748
|
+
arg_data = arg_data.encode()
|
|
749
|
+
else:
|
|
750
|
+
raise ValueError(f"Invalid default argument data type: {type(arg_data)}")
|
|
751
|
+
|
|
752
|
+
return DefaultValue(
|
|
753
|
+
source=mapped_source, # type: ignore[arg-type]
|
|
754
|
+
data=base64.b64encode(arg_data).decode("utf-8"),
|
|
755
|
+
type=arg_type if arg_type != "string" else "AVMString",
|
|
756
|
+
)
|
|
757
|
+
|
|
758
|
+
@overload
|
|
759
|
+
def _convert_actions(self, config: dict | None, action_type: Literal[_ActionType.CALL]) -> list[CallEnum]: ...
|
|
760
|
+
|
|
761
|
+
@overload
|
|
762
|
+
def _convert_actions(self, config: dict | None, action_type: Literal[_ActionType.CREATE]) -> list[CreateEnum]: ...
|
|
763
|
+
|
|
764
|
+
def _convert_actions(self, config: dict | None, action_type: _ActionType) -> Sequence[CallEnum | CreateEnum]:
|
|
765
|
+
"""Extract supported actions from call config."""
|
|
766
|
+
if not config:
|
|
767
|
+
return []
|
|
768
|
+
|
|
769
|
+
actions: list[CallEnum | CreateEnum] = []
|
|
770
|
+
mappings = {
|
|
771
|
+
"no_op": (CallEnum.NO_OP, CreateEnum.NO_OP),
|
|
772
|
+
"opt_in": (CallEnum.OPT_IN, CreateEnum.OPT_IN),
|
|
773
|
+
"close_out": (CallEnum.CLOSE_OUT, None),
|
|
774
|
+
"delete_application": (CallEnum.DELETE_APPLICATION, CreateEnum.DELETE_APPLICATION),
|
|
775
|
+
"update_application": (CallEnum.UPDATE_APPLICATION, None),
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
for action, (call_enum, create_enum) in mappings.items():
|
|
779
|
+
if action in config and config[action] in ["ALL", action_type]:
|
|
780
|
+
if action_type == "CALL" and call_enum:
|
|
781
|
+
actions.append(call_enum)
|
|
782
|
+
elif action_type == "CREATE" and create_enum:
|
|
783
|
+
actions.append(create_enum)
|
|
784
|
+
|
|
785
|
+
return actions
|
|
786
|
+
|
|
787
|
+
def _convert_method_actions(self, hint: dict | None) -> Actions:
|
|
788
|
+
"""Convert method call config to ARC56 actions."""
|
|
789
|
+
config = hint.get("call_config", {}) if hint else {}
|
|
790
|
+
return Actions(
|
|
791
|
+
call=self._convert_actions(config, _ActionType.CALL),
|
|
792
|
+
create=self._convert_actions(config, _ActionType.CREATE),
|
|
793
|
+
)
|
|
794
|
+
|
|
795
|
+
def _convert_methods(self, arc32: dict) -> list[Method]:
|
|
796
|
+
"""Convert ARC32 methods to ARC56 format."""
|
|
797
|
+
methods = []
|
|
798
|
+
contract = arc32["contract"]
|
|
799
|
+
hints = arc32.get("hints", {})
|
|
800
|
+
|
|
801
|
+
for method in contract["methods"]:
|
|
802
|
+
args_sig = ",".join(a["type"] for a in method["args"])
|
|
803
|
+
signature = f"{method['name']}({args_sig}){method['returns']['type']}"
|
|
804
|
+
hint = hints.get(signature, {})
|
|
805
|
+
|
|
806
|
+
methods.append(
|
|
807
|
+
Method(
|
|
808
|
+
name=method["name"],
|
|
809
|
+
desc=method.get("desc"),
|
|
810
|
+
readonly=hint.get("read_only"),
|
|
811
|
+
args=[
|
|
812
|
+
MethodArg(
|
|
813
|
+
name=arg.get("name"),
|
|
814
|
+
type=arg["type"],
|
|
815
|
+
desc=arg.get("desc"),
|
|
816
|
+
struct=hint.get("structs", {}).get(arg.get("name", ""), {}).get("name"),
|
|
817
|
+
default_value=self._convert_default_value(
|
|
818
|
+
arg["type"], hint.get("default_arguments", {}).get(arg.get("name"))
|
|
819
|
+
),
|
|
820
|
+
)
|
|
821
|
+
for arg in method["args"]
|
|
822
|
+
],
|
|
823
|
+
returns=Returns(
|
|
824
|
+
type=method["returns"]["type"],
|
|
825
|
+
desc=method["returns"].get("desc"),
|
|
826
|
+
struct=hint.get("structs", {}).get("output", {}).get("name"),
|
|
827
|
+
),
|
|
828
|
+
actions=self._convert_method_actions(hint),
|
|
829
|
+
events=[], # ARC32 doesn't specify events
|
|
830
|
+
)
|
|
831
|
+
)
|
|
832
|
+
return methods
|
|
833
|
+
|
|
834
|
+
|
|
835
|
+
def _arc56_dict_factory() -> Callable[[list[tuple[str, Any]]], dict[str, Any]]:
|
|
836
|
+
"""Creates a dict factory that handles ARC-56 JSON field naming conventions."""
|
|
837
|
+
|
|
838
|
+
word_map = {"global_state": "global", "local_state": "local"}
|
|
839
|
+
blocklist = ["_abi_method"]
|
|
840
|
+
|
|
841
|
+
def to_camel(key: str) -> str:
|
|
842
|
+
key = word_map.get(key, key)
|
|
843
|
+
words = key.split("_")
|
|
844
|
+
return words[0] + "".join(word.capitalize() for word in words[1:])
|
|
845
|
+
|
|
846
|
+
def dict_factory(entries: list[tuple[str, Any]]) -> dict[str, Any]:
|
|
847
|
+
return {to_camel(k): v for k, v in entries if v is not None and k not in blocklist}
|
|
848
|
+
|
|
849
|
+
return dict_factory
|
|
850
|
+
|
|
851
|
+
|
|
852
|
+
@dataclass
|
|
853
|
+
class Arc56Contract:
|
|
854
|
+
"""ARC-0056 application specification.
|
|
855
|
+
|
|
856
|
+
See https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0056.md
|
|
857
|
+
"""
|
|
858
|
+
|
|
859
|
+
arcs: list[int]
|
|
860
|
+
"""The list of supported ARC version numbers"""
|
|
861
|
+
bare_actions: BareActions
|
|
862
|
+
"""The bare call and create actions"""
|
|
863
|
+
methods: list[Method]
|
|
864
|
+
"""The list of contract methods"""
|
|
865
|
+
name: str
|
|
866
|
+
"""The contract name"""
|
|
867
|
+
state: State
|
|
868
|
+
"""The contract state information"""
|
|
869
|
+
structs: dict[str, list[StructField]]
|
|
870
|
+
"""The contract struct definitions"""
|
|
871
|
+
byte_code: ByteCode | None = None
|
|
872
|
+
"""The optional bytecode for approval and clear programs"""
|
|
873
|
+
compiler_info: CompilerInfo | None = None
|
|
874
|
+
"""The optional compiler information"""
|
|
875
|
+
desc: str | None = None
|
|
876
|
+
"""The optional contract description"""
|
|
877
|
+
events: list[Event] | None = None
|
|
878
|
+
"""The optional list of contract events"""
|
|
879
|
+
networks: dict[str, Network] | None = None
|
|
880
|
+
"""The optional network deployment information"""
|
|
881
|
+
scratch_variables: dict[str, ScratchVariables] | None = None
|
|
882
|
+
"""The optional scratch variable information"""
|
|
883
|
+
source: Source | None = None
|
|
884
|
+
"""The optional source code"""
|
|
885
|
+
source_info: SourceInfoModel | None = None
|
|
886
|
+
"""The optional source code information"""
|
|
887
|
+
template_variables: dict[str, TemplateVariables] | None = None
|
|
888
|
+
"""The optional template variable information"""
|
|
889
|
+
|
|
890
|
+
@staticmethod
|
|
891
|
+
def from_dict(application_spec: dict) -> Arc56Contract:
|
|
892
|
+
"""Create Arc56Contract from dictionary.
|
|
893
|
+
|
|
894
|
+
:param application_spec: Dictionary containing contract specification
|
|
895
|
+
:return: Arc56Contract instance
|
|
896
|
+
"""
|
|
897
|
+
data = _dict_keys_to_snake_case(application_spec)
|
|
898
|
+
data["bare_actions"] = BareActions.from_dict(data["bare_actions"])
|
|
899
|
+
data["methods"] = [Method.from_dict(item) for item in data["methods"]]
|
|
900
|
+
data["state"] = State.from_dict(data["state"])
|
|
901
|
+
data["structs"] = {
|
|
902
|
+
key: [StructField.from_dict(item) for item in value] for key, value in application_spec["structs"].items()
|
|
903
|
+
}
|
|
904
|
+
if data.get("byte_code"):
|
|
905
|
+
data["byte_code"] = ByteCode.from_dict(data["byte_code"])
|
|
906
|
+
if data.get("compiler_info"):
|
|
907
|
+
data["compiler_info"] = CompilerInfo.from_dict(data["compiler_info"])
|
|
908
|
+
if data.get("events"):
|
|
909
|
+
data["events"] = [Event.from_dict(item) for item in data["events"]]
|
|
910
|
+
if data.get("networks"):
|
|
911
|
+
data["networks"] = {key: Network.from_dict(value) for key, value in data["networks"].items()}
|
|
912
|
+
if data.get("scratch_variables"):
|
|
913
|
+
data["scratch_variables"] = {
|
|
914
|
+
key: ScratchVariables.from_dict(value) for key, value in data["scratch_variables"].items()
|
|
915
|
+
}
|
|
916
|
+
if data.get("source"):
|
|
917
|
+
data["source"] = Source.from_dict(data["source"])
|
|
918
|
+
if data.get("source_info"):
|
|
919
|
+
data["source_info"] = SourceInfoModel.from_dict(data["source_info"])
|
|
920
|
+
if data.get("template_variables"):
|
|
921
|
+
data["template_variables"] = {
|
|
922
|
+
key: TemplateVariables.from_dict(value) for key, value in data["template_variables"].items()
|
|
923
|
+
}
|
|
924
|
+
return Arc56Contract(**data)
|
|
925
|
+
|
|
926
|
+
@staticmethod
|
|
927
|
+
def from_json(application_spec: str) -> Arc56Contract:
|
|
928
|
+
return Arc56Contract.from_dict(json.loads(application_spec))
|
|
929
|
+
|
|
930
|
+
@staticmethod
|
|
931
|
+
def from_arc32(arc32_application_spec: str | Arc32Contract) -> Arc56Contract:
|
|
932
|
+
return _Arc32ToArc56Converter(
|
|
933
|
+
arc32_application_spec.to_json()
|
|
934
|
+
if isinstance(arc32_application_spec, Arc32Contract)
|
|
935
|
+
else arc32_application_spec
|
|
936
|
+
).convert()
|
|
937
|
+
|
|
938
|
+
@staticmethod
|
|
939
|
+
def get_abi_struct_from_abi_tuple(
|
|
940
|
+
decoded_tuple: Any, # noqa: ANN401
|
|
941
|
+
struct_fields: list[StructField],
|
|
942
|
+
structs: dict[str, list[StructField]],
|
|
943
|
+
) -> dict[str, Any]:
|
|
944
|
+
result = {}
|
|
945
|
+
for i, field in enumerate(struct_fields):
|
|
946
|
+
key = field.name
|
|
947
|
+
field_type = field.type
|
|
948
|
+
value = decoded_tuple[i]
|
|
949
|
+
if isinstance(field_type, str):
|
|
950
|
+
if field_type in structs:
|
|
951
|
+
value = Arc56Contract.get_abi_struct_from_abi_tuple(value, structs[field_type], structs)
|
|
952
|
+
elif isinstance(field_type, list):
|
|
953
|
+
value = Arc56Contract.get_abi_struct_from_abi_tuple(value, field_type, structs)
|
|
954
|
+
result[key] = value
|
|
955
|
+
return result
|
|
956
|
+
|
|
957
|
+
def to_json(self, indent: int | None = None) -> str:
|
|
958
|
+
return json.dumps(self.dictify(), indent=indent)
|
|
959
|
+
|
|
960
|
+
def dictify(self) -> dict:
|
|
961
|
+
return asdict(self, dict_factory=_arc56_dict_factory())
|
|
962
|
+
|
|
963
|
+
def get_arc56_method(self, method_name_or_signature: str) -> Method:
|
|
964
|
+
if "(" not in method_name_or_signature:
|
|
965
|
+
# Filter by method name
|
|
966
|
+
methods = [m for m in self.methods if m.name == method_name_or_signature]
|
|
967
|
+
if not methods:
|
|
968
|
+
raise ValueError(f"Unable to find method {method_name_or_signature} in {self.name} app.")
|
|
969
|
+
if len(methods) > 1:
|
|
970
|
+
signatures = [AlgosdkMethod.undictify(m.__dict__).get_signature() for m in self.methods]
|
|
971
|
+
raise ValueError(
|
|
972
|
+
f"Received a call to method {method_name_or_signature} in contract {self.name}, "
|
|
973
|
+
f"but this resolved to multiple methods; please pass in an ABI signature instead: "
|
|
974
|
+
f"{', '.join(signatures)}"
|
|
975
|
+
)
|
|
976
|
+
method = methods[0]
|
|
977
|
+
else:
|
|
978
|
+
# Find by signature
|
|
979
|
+
method = None
|
|
980
|
+
for m in self.methods:
|
|
981
|
+
abi_method = AlgosdkMethod.undictify(asdict(m))
|
|
982
|
+
if abi_method.get_signature() == method_name_or_signature:
|
|
983
|
+
method = m
|
|
984
|
+
break
|
|
985
|
+
|
|
986
|
+
if method is None:
|
|
987
|
+
raise ValueError(f"Unable to find method {method_name_or_signature} in {self.name} app.")
|
|
988
|
+
|
|
989
|
+
return method
|