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