algokit-utils 3.0.0b1__py3-none-any.whl → 3.0.0b2__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 +2056 -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 +193 -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 +2293 -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.0b2.dist-info}/METADATA +12 -7
- algokit_utils-3.0.0b2.dist-info/RECORD +70 -0
- {algokit_utils-3.0.0b1.dist-info → algokit_utils-3.0.0b2.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.0b2.dist-info}/LICENSE +0 -0
|
@@ -1,206 +1,48 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
import warnings
|
|
2
|
+
|
|
3
|
+
from deprecated import deprecated
|
|
4
|
+
|
|
5
|
+
warnings.warn(
|
|
6
|
+
"""The legacy v2 application_specification module is deprecated and will be removed in a future version.
|
|
7
|
+
Use `from algokit_utils.applications.app_spec.arc32 import ...` to access Arc32 app spec instead.
|
|
8
|
+
By default, the ARC52Contract is a recommended app spec to use, serving as a replacement
|
|
9
|
+
for legacy 'ApplicationSpecification' class.
|
|
10
|
+
To convert legacy app specs to ARC52, use `arc32_to_arc52` function from algokit_utils.applications.utils.
|
|
11
|
+
""",
|
|
12
|
+
DeprecationWarning,
|
|
13
|
+
stacklevel=2,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
from algokit_utils.applications.app_spec.arc32 import ( # noqa: E402 # noqa: E402
|
|
17
|
+
AppSpecStateDict,
|
|
18
|
+
Arc32Contract,
|
|
19
|
+
CallConfig,
|
|
20
|
+
DefaultArgumentDict,
|
|
21
|
+
DefaultArgumentType,
|
|
22
|
+
MethodConfigDict,
|
|
23
|
+
MethodHints,
|
|
24
|
+
OnCompleteActionName,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@deprecated(
|
|
29
|
+
"Use `Arc32Contract` from algokit_utils.applications instead. Example:\n"
|
|
30
|
+
"```python\n"
|
|
31
|
+
"from algokit_utils.applications import Arc32Contract\n"
|
|
32
|
+
"app_spec = Arc32Contract.from_json(app_spec_json)\n"
|
|
33
|
+
"```"
|
|
34
|
+
)
|
|
35
|
+
class ApplicationSpecification(Arc32Contract):
|
|
36
|
+
"""Deprecated class for ARC-0032 application specification"""
|
|
7
37
|
|
|
8
|
-
from algosdk.abi import Contract
|
|
9
|
-
from algosdk.abi.method import MethodDict
|
|
10
|
-
from algosdk.transaction import StateSchema
|
|
11
38
|
|
|
12
39
|
__all__ = [
|
|
40
|
+
"AppSpecStateDict",
|
|
41
|
+
"ApplicationSpecification",
|
|
13
42
|
"CallConfig",
|
|
14
43
|
"DefaultArgumentDict",
|
|
15
44
|
"DefaultArgumentType",
|
|
16
45
|
"MethodConfigDict",
|
|
17
|
-
"OnCompleteActionName",
|
|
18
46
|
"MethodHints",
|
|
19
|
-
"
|
|
20
|
-
"AppSpecStateDict",
|
|
21
|
-
]
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
AppSpecStateDict: TypeAlias = dict[str, dict[str, dict]]
|
|
25
|
-
"""Type defining Application Specification state entries"""
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
class CallConfig(IntFlag):
|
|
29
|
-
"""Describes the type of calls a method can be used for based on {py:class}`algosdk.transaction.OnComplete` type"""
|
|
30
|
-
|
|
31
|
-
NEVER = 0
|
|
32
|
-
"""Never handle the specified on completion type"""
|
|
33
|
-
CALL = 1
|
|
34
|
-
"""Only handle the specified on completion type for application calls"""
|
|
35
|
-
CREATE = 2
|
|
36
|
-
"""Only handle the specified on completion type for application create calls"""
|
|
37
|
-
ALL = 3
|
|
38
|
-
"""Handle the specified on completion type for both create and normal application calls"""
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
class StructArgDict(TypedDict):
|
|
42
|
-
name: str
|
|
43
|
-
elements: list[list[str]]
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
OnCompleteActionName: TypeAlias = Literal[
|
|
47
|
-
"no_op", "opt_in", "close_out", "clear_state", "update_application", "delete_application"
|
|
47
|
+
"OnCompleteActionName",
|
|
48
48
|
]
|
|
49
|
-
"""String literals representing on completion transaction types"""
|
|
50
|
-
MethodConfigDict: TypeAlias = dict[OnCompleteActionName, CallConfig]
|
|
51
|
-
"""Dictionary of `dict[OnCompletionActionName, CallConfig]` representing allowed actions for each on completion type"""
|
|
52
|
-
DefaultArgumentType: TypeAlias = Literal["abi-method", "local-state", "global-state", "constant"]
|
|
53
|
-
"""Literal values describing the types of default argument sources"""
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
class DefaultArgumentDict(TypedDict):
|
|
57
|
-
"""
|
|
58
|
-
DefaultArgument is a container for any arguments that may
|
|
59
|
-
be resolved prior to calling some target method
|
|
60
|
-
"""
|
|
61
|
-
|
|
62
|
-
source: DefaultArgumentType
|
|
63
|
-
data: int | str | bytes | MethodDict
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
StateDict = TypedDict( # need to use function-form of TypedDict here since "global" is a reserved keyword
|
|
67
|
-
"StateDict", {"global": AppSpecStateDict, "local": AppSpecStateDict}
|
|
68
|
-
)
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
@dataclasses.dataclass(kw_only=True)
|
|
72
|
-
class MethodHints:
|
|
73
|
-
"""MethodHints provides hints to the caller about how to call the method"""
|
|
74
|
-
|
|
75
|
-
#: hint to indicate this method can be called through Dryrun
|
|
76
|
-
read_only: bool = False
|
|
77
|
-
#: hint to provide names for tuple argument indices
|
|
78
|
-
#: method_name=>param_name=>{name:str, elements:[str,str]}
|
|
79
|
-
structs: dict[str, StructArgDict] = dataclasses.field(default_factory=dict)
|
|
80
|
-
#: defaults
|
|
81
|
-
default_arguments: dict[str, DefaultArgumentDict] = dataclasses.field(default_factory=dict)
|
|
82
|
-
call_config: MethodConfigDict = dataclasses.field(default_factory=dict)
|
|
83
|
-
|
|
84
|
-
def empty(self) -> bool:
|
|
85
|
-
return not self.dictify()
|
|
86
|
-
|
|
87
|
-
def dictify(self) -> dict[str, Any]:
|
|
88
|
-
d: dict[str, Any] = {}
|
|
89
|
-
if self.read_only:
|
|
90
|
-
d["read_only"] = True
|
|
91
|
-
if self.default_arguments:
|
|
92
|
-
d["default_arguments"] = self.default_arguments
|
|
93
|
-
if self.structs:
|
|
94
|
-
d["structs"] = self.structs
|
|
95
|
-
if any(v for v in self.call_config.values() if v != CallConfig.NEVER):
|
|
96
|
-
d["call_config"] = _encode_method_config(self.call_config)
|
|
97
|
-
return d
|
|
98
|
-
|
|
99
|
-
@staticmethod
|
|
100
|
-
def undictify(data: dict[str, Any]) -> "MethodHints":
|
|
101
|
-
return MethodHints(
|
|
102
|
-
read_only=data.get("read_only", False),
|
|
103
|
-
default_arguments=data.get("default_arguments", {}),
|
|
104
|
-
structs=data.get("structs", {}),
|
|
105
|
-
call_config=_decode_method_config(data.get("call_config", {})),
|
|
106
|
-
)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def _encode_method_config(mc: MethodConfigDict) -> dict[str, str | None]:
|
|
110
|
-
return {k: mc[k].name for k in sorted(mc) if mc[k] != CallConfig.NEVER}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def _decode_method_config(data: dict[OnCompleteActionName, Any]) -> MethodConfigDict:
|
|
114
|
-
return {k: CallConfig[v] for k, v in data.items()}
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
def _encode_source(teal_text: str) -> str:
|
|
118
|
-
return base64.b64encode(teal_text.encode()).decode("utf-8")
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
def _decode_source(b64_text: str) -> str:
|
|
122
|
-
return base64.b64decode(b64_text).decode("utf-8")
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
def _encode_state_schema(schema: StateSchema) -> dict[str, int]:
|
|
126
|
-
return {
|
|
127
|
-
"num_byte_slices": schema.num_byte_slices,
|
|
128
|
-
"num_uints": schema.num_uints,
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
def _decode_state_schema(data: dict[str, int]) -> StateSchema:
|
|
133
|
-
return StateSchema( # type: ignore[no-untyped-call]
|
|
134
|
-
num_byte_slices=data.get("num_byte_slices", 0),
|
|
135
|
-
num_uints=data.get("num_uints", 0),
|
|
136
|
-
)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
@dataclasses.dataclass(kw_only=True)
|
|
140
|
-
class ApplicationSpecification:
|
|
141
|
-
"""ARC-0032 application specification
|
|
142
|
-
|
|
143
|
-
See <https://github.com/algorandfoundation/ARCs/pull/150>"""
|
|
144
|
-
|
|
145
|
-
approval_program: str
|
|
146
|
-
clear_program: str
|
|
147
|
-
contract: Contract
|
|
148
|
-
hints: dict[str, MethodHints]
|
|
149
|
-
schema: StateDict
|
|
150
|
-
global_state_schema: StateSchema
|
|
151
|
-
local_state_schema: StateSchema
|
|
152
|
-
bare_call_config: MethodConfigDict
|
|
153
|
-
|
|
154
|
-
def dictify(self) -> dict:
|
|
155
|
-
return {
|
|
156
|
-
"hints": {k: v.dictify() for k, v in self.hints.items() if not v.empty()},
|
|
157
|
-
"source": {
|
|
158
|
-
"approval": _encode_source(self.approval_program),
|
|
159
|
-
"clear": _encode_source(self.clear_program),
|
|
160
|
-
},
|
|
161
|
-
"state": {
|
|
162
|
-
"global": _encode_state_schema(self.global_state_schema),
|
|
163
|
-
"local": _encode_state_schema(self.local_state_schema),
|
|
164
|
-
},
|
|
165
|
-
"schema": self.schema,
|
|
166
|
-
"contract": self.contract.dictify(),
|
|
167
|
-
"bare_call_config": _encode_method_config(self.bare_call_config),
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
def to_json(self) -> str:
|
|
171
|
-
return json.dumps(self.dictify(), indent=4)
|
|
172
|
-
|
|
173
|
-
@staticmethod
|
|
174
|
-
def from_json(application_spec: str) -> "ApplicationSpecification":
|
|
175
|
-
json_spec = json.loads(application_spec)
|
|
176
|
-
return ApplicationSpecification(
|
|
177
|
-
approval_program=_decode_source(json_spec["source"]["approval"]),
|
|
178
|
-
clear_program=_decode_source(json_spec["source"]["clear"]),
|
|
179
|
-
schema=json_spec["schema"],
|
|
180
|
-
global_state_schema=_decode_state_schema(json_spec["state"]["global"]),
|
|
181
|
-
local_state_schema=_decode_state_schema(json_spec["state"]["local"]),
|
|
182
|
-
contract=Contract.undictify(json_spec["contract"]),
|
|
183
|
-
hints={k: MethodHints.undictify(v) for k, v in json_spec["hints"].items()},
|
|
184
|
-
bare_call_config=_decode_method_config(json_spec.get("bare_call_config", {})),
|
|
185
|
-
)
|
|
186
|
-
|
|
187
|
-
def export(self, directory: Path | str | None = None) -> None:
|
|
188
|
-
"""write out the artifacts generated by the application to disk
|
|
189
|
-
|
|
190
|
-
Args:
|
|
191
|
-
directory(optional): path to the directory where the artifacts should be written
|
|
192
|
-
"""
|
|
193
|
-
if directory is None:
|
|
194
|
-
output_dir = Path.cwd()
|
|
195
|
-
else:
|
|
196
|
-
output_dir = Path(directory)
|
|
197
|
-
output_dir.mkdir(exist_ok=True, parents=True)
|
|
198
|
-
|
|
199
|
-
(output_dir / "approval.teal").write_text(self.approval_program)
|
|
200
|
-
(output_dir / "clear.teal").write_text(self.clear_program)
|
|
201
|
-
(output_dir / "contract.json").write_text(json.dumps(self.contract.dictify(), indent=4))
|
|
202
|
-
(output_dir / "application.json").write_text(self.to_json())
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
def _state_schema(schema: dict[str, int]) -> StateSchema:
|
|
206
|
-
return StateSchema(schema.get("num-uint", 0), schema.get("num-byte-slice", 0)) # type: ignore[no-untyped-call]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
from algokit_utils.applications.abi import * # noqa: F403
|
|
2
|
+
from algokit_utils.applications.app_client import * # noqa: F403
|
|
3
|
+
from algokit_utils.applications.app_deployer import * # noqa: F403
|
|
4
|
+
from algokit_utils.applications.app_factory import * # noqa: F403
|
|
5
|
+
from algokit_utils.applications.app_manager import * # noqa: F403
|
|
6
|
+
from algokit_utils.applications.app_spec import * # noqa: F403
|
|
7
|
+
from algokit_utils.applications.enums import * # noqa: F403
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from typing import TYPE_CHECKING, Any, TypeAlias
|
|
5
|
+
|
|
6
|
+
import algosdk
|
|
7
|
+
from algosdk.abi.method import Method as AlgorandABIMethod
|
|
8
|
+
from algosdk.atomic_transaction_composer import ABIResult
|
|
9
|
+
|
|
10
|
+
from algokit_utils.applications.app_spec.arc56 import Arc56Contract, StructField
|
|
11
|
+
from algokit_utils.applications.app_spec.arc56 import Method as Arc56Method
|
|
12
|
+
|
|
13
|
+
if TYPE_CHECKING:
|
|
14
|
+
from algokit_utils.models.state import BoxName
|
|
15
|
+
|
|
16
|
+
ABIValue: TypeAlias = (
|
|
17
|
+
bool | int | str | bytes | bytearray | list["ABIValue"] | tuple["ABIValue"] | dict[str, "ABIValue"]
|
|
18
|
+
)
|
|
19
|
+
ABIStruct: TypeAlias = dict[str, list[dict[str, "ABIValue"]]]
|
|
20
|
+
Arc56ReturnValueType: TypeAlias = ABIValue | ABIStruct | None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
ABIType: TypeAlias = algosdk.abi.ABIType
|
|
24
|
+
ABIArgumentType: TypeAlias = algosdk.abi.ABIType | algosdk.abi.ABITransactionType | algosdk.abi.ABIReferenceType
|
|
25
|
+
|
|
26
|
+
__all__ = [
|
|
27
|
+
"ABIArgumentType",
|
|
28
|
+
"ABIReturn",
|
|
29
|
+
"ABIStruct",
|
|
30
|
+
"ABIType",
|
|
31
|
+
"ABIValue",
|
|
32
|
+
"Arc56ReturnValueType",
|
|
33
|
+
"BoxABIValue",
|
|
34
|
+
"get_abi_decoded_value",
|
|
35
|
+
"get_abi_encoded_value",
|
|
36
|
+
"get_abi_struct_from_abi_tuple",
|
|
37
|
+
"get_abi_tuple_from_abi_struct",
|
|
38
|
+
"get_abi_tuple_type_from_abi_struct_definition",
|
|
39
|
+
"get_arc56_value",
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass(kw_only=True)
|
|
44
|
+
class ABIReturn:
|
|
45
|
+
"""Represents the return value from an ABI method call.
|
|
46
|
+
|
|
47
|
+
Wraps the raw return value and decoded value along with any decode errors.
|
|
48
|
+
|
|
49
|
+
:ivar result: The ABIResult object containing the method call results
|
|
50
|
+
:ivar raw_value: The raw return value from the method call
|
|
51
|
+
:ivar value: The decoded return value from the method call
|
|
52
|
+
:ivar method: The ABI method definition
|
|
53
|
+
:ivar decode_error: The exception that occurred during decoding, if any
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
raw_value: bytes | None = None
|
|
57
|
+
value: ABIValue | None = None
|
|
58
|
+
method: AlgorandABIMethod | None = None
|
|
59
|
+
decode_error: Exception | None = None
|
|
60
|
+
|
|
61
|
+
def __init__(self, result: ABIResult) -> None:
|
|
62
|
+
self.decode_error = result.decode_error
|
|
63
|
+
if not self.decode_error:
|
|
64
|
+
self.raw_value = result.raw_value
|
|
65
|
+
self.value = result.return_value
|
|
66
|
+
self.method = result.method
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def is_success(self) -> bool:
|
|
70
|
+
"""Returns True if the ABI call was successful (no decode error)
|
|
71
|
+
|
|
72
|
+
:return: True if no decode error occurred, False otherwise
|
|
73
|
+
"""
|
|
74
|
+
return self.decode_error is None
|
|
75
|
+
|
|
76
|
+
def get_arc56_value(
|
|
77
|
+
self, method: Arc56Method | AlgorandABIMethod, structs: dict[str, list[StructField]]
|
|
78
|
+
) -> Arc56ReturnValueType:
|
|
79
|
+
"""Gets the ARC-56 formatted return value.
|
|
80
|
+
|
|
81
|
+
:param method: The ABI method definition
|
|
82
|
+
:param structs: Dictionary of struct definitions
|
|
83
|
+
:return: The decoded return value in ARC-56 format
|
|
84
|
+
"""
|
|
85
|
+
return get_arc56_value(self, method, structs)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def get_arc56_value(
|
|
89
|
+
abi_return: ABIReturn, method: Arc56Method | AlgorandABIMethod, structs: dict[str, list[StructField]]
|
|
90
|
+
) -> Arc56ReturnValueType:
|
|
91
|
+
"""Gets the ARC-56 formatted return value from an ABI return.
|
|
92
|
+
|
|
93
|
+
:param abi_return: The ABI return value to decode
|
|
94
|
+
:param method: The ABI method definition
|
|
95
|
+
:param structs: Dictionary of struct definitions
|
|
96
|
+
:raises ValueError: If there was an error decoding the return value
|
|
97
|
+
:return: The decoded return value in ARC-56 format
|
|
98
|
+
"""
|
|
99
|
+
if isinstance(method, AlgorandABIMethod):
|
|
100
|
+
type_str = method.returns.type
|
|
101
|
+
struct = None # AlgorandABIMethod doesn't have struct info
|
|
102
|
+
else:
|
|
103
|
+
type_str = method.returns.type
|
|
104
|
+
struct = method.returns.struct
|
|
105
|
+
|
|
106
|
+
if type_str == "void" or abi_return.value is None:
|
|
107
|
+
return None
|
|
108
|
+
|
|
109
|
+
if abi_return.decode_error:
|
|
110
|
+
raise ValueError(abi_return.decode_error)
|
|
111
|
+
|
|
112
|
+
raw_value = abi_return.raw_value
|
|
113
|
+
|
|
114
|
+
# Handle AVM types
|
|
115
|
+
if type_str == "AVMBytes":
|
|
116
|
+
return raw_value
|
|
117
|
+
if type_str == "AVMString" and raw_value:
|
|
118
|
+
return raw_value.decode("utf-8")
|
|
119
|
+
if type_str == "AVMUint64" and raw_value:
|
|
120
|
+
return ABIType.from_string("uint64").decode(raw_value) # type: ignore[no-any-return]
|
|
121
|
+
|
|
122
|
+
# Handle structs
|
|
123
|
+
if struct and struct in structs:
|
|
124
|
+
return_tuple = abi_return.value
|
|
125
|
+
return Arc56Contract.get_abi_struct_from_abi_tuple(return_tuple, structs[struct], structs)
|
|
126
|
+
|
|
127
|
+
# Return as-is
|
|
128
|
+
return abi_return.value
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def get_abi_encoded_value(value: Any, type_str: str, structs: dict[str, list[StructField]]) -> bytes: # noqa: PLR0911, ANN401
|
|
132
|
+
"""Encodes a value according to its ABI type.
|
|
133
|
+
|
|
134
|
+
:param value: The value to encode
|
|
135
|
+
:param type_str: The ABI type string
|
|
136
|
+
:param structs: Dictionary of struct definitions
|
|
137
|
+
:raises ValueError: If the value cannot be encoded for the given type
|
|
138
|
+
:return: The ABI encoded bytes
|
|
139
|
+
"""
|
|
140
|
+
if isinstance(value, (bytes | bytearray)):
|
|
141
|
+
return value
|
|
142
|
+
if type_str == "AVMUint64":
|
|
143
|
+
return ABIType.from_string("uint64").encode(value)
|
|
144
|
+
if type_str in ("AVMBytes", "AVMString"):
|
|
145
|
+
if isinstance(value, str):
|
|
146
|
+
return value.encode("utf-8")
|
|
147
|
+
if not isinstance(value, (bytes | bytearray)):
|
|
148
|
+
raise ValueError(f"Expected bytes value for {type_str}, but got {type(value)}")
|
|
149
|
+
return value
|
|
150
|
+
if type_str in structs:
|
|
151
|
+
tuple_type = get_abi_tuple_type_from_abi_struct_definition(structs[type_str], structs)
|
|
152
|
+
if isinstance(value, (list | tuple)):
|
|
153
|
+
return tuple_type.encode(value) # type: ignore[arg-type]
|
|
154
|
+
else:
|
|
155
|
+
tuple_values = get_abi_tuple_from_abi_struct(value, structs[type_str], structs)
|
|
156
|
+
return tuple_type.encode(tuple_values)
|
|
157
|
+
else:
|
|
158
|
+
abi_type = ABIType.from_string(type_str)
|
|
159
|
+
return abi_type.encode(value)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def get_abi_decoded_value(
|
|
163
|
+
value: bytes | int | str, type_str: str | ABIArgumentType, structs: dict[str, list[StructField]]
|
|
164
|
+
) -> ABIValue:
|
|
165
|
+
"""Decodes a value according to its ABI type.
|
|
166
|
+
|
|
167
|
+
:param value: The value to decode
|
|
168
|
+
:param type_str: The ABI type string or type object
|
|
169
|
+
:param structs: Dictionary of struct definitions
|
|
170
|
+
:return: The decoded ABI value
|
|
171
|
+
"""
|
|
172
|
+
type_value = str(type_str)
|
|
173
|
+
|
|
174
|
+
if type_value == "AVMBytes" or not isinstance(value, bytes):
|
|
175
|
+
return value
|
|
176
|
+
if type_value == "AVMString":
|
|
177
|
+
return value.decode("utf-8")
|
|
178
|
+
if type_value == "AVMUint64":
|
|
179
|
+
return ABIType.from_string("uint64").decode(value) # type: ignore[no-any-return]
|
|
180
|
+
if type_value in structs:
|
|
181
|
+
tuple_type = get_abi_tuple_type_from_abi_struct_definition(structs[type_value], structs)
|
|
182
|
+
decoded_tuple = tuple_type.decode(value)
|
|
183
|
+
return get_abi_struct_from_abi_tuple(decoded_tuple, structs[type_value], structs)
|
|
184
|
+
return ABIType.from_string(type_value).decode(value) # type: ignore[no-any-return]
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def get_abi_tuple_from_abi_struct(
|
|
188
|
+
struct_value: dict[str, Any],
|
|
189
|
+
struct_fields: list[StructField],
|
|
190
|
+
structs: dict[str, list[StructField]],
|
|
191
|
+
) -> list[Any]:
|
|
192
|
+
"""Converts an ABI struct to a tuple representation.
|
|
193
|
+
|
|
194
|
+
:param struct_value: The struct value as a dictionary
|
|
195
|
+
:param struct_fields: List of struct field definitions
|
|
196
|
+
:param structs: Dictionary of struct definitions
|
|
197
|
+
:raises ValueError: If a required field is missing from the struct
|
|
198
|
+
:return: The struct as a tuple
|
|
199
|
+
"""
|
|
200
|
+
result = []
|
|
201
|
+
for field in struct_fields:
|
|
202
|
+
key = field.name
|
|
203
|
+
if key not in struct_value:
|
|
204
|
+
raise ValueError(f"Missing value for field '{key}'")
|
|
205
|
+
value = struct_value[key]
|
|
206
|
+
field_type = field.type
|
|
207
|
+
if isinstance(field_type, str):
|
|
208
|
+
if field_type in structs:
|
|
209
|
+
value = get_abi_tuple_from_abi_struct(value, structs[field_type], structs)
|
|
210
|
+
elif isinstance(field_type, list):
|
|
211
|
+
value = get_abi_tuple_from_abi_struct(value, field_type, structs)
|
|
212
|
+
result.append(value)
|
|
213
|
+
return result
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def get_abi_tuple_type_from_abi_struct_definition(
|
|
217
|
+
struct_def: list[StructField], structs: dict[str, list[StructField]]
|
|
218
|
+
) -> algosdk.abi.TupleType:
|
|
219
|
+
"""Creates a TupleType from a struct definition.
|
|
220
|
+
|
|
221
|
+
:param struct_def: The struct field definitions
|
|
222
|
+
:param structs: Dictionary of struct definitions
|
|
223
|
+
:raises ValueError: If a field type is invalid
|
|
224
|
+
:return: The TupleType representing the struct
|
|
225
|
+
"""
|
|
226
|
+
types = []
|
|
227
|
+
for field in struct_def:
|
|
228
|
+
field_type = field.type
|
|
229
|
+
if isinstance(field_type, str):
|
|
230
|
+
if field_type in structs:
|
|
231
|
+
types.append(get_abi_tuple_type_from_abi_struct_definition(structs[field_type], structs))
|
|
232
|
+
else:
|
|
233
|
+
types.append(ABIType.from_string(field_type)) # type: ignore[arg-type]
|
|
234
|
+
elif isinstance(field_type, list):
|
|
235
|
+
types.append(get_abi_tuple_type_from_abi_struct_definition(field_type, structs))
|
|
236
|
+
else:
|
|
237
|
+
raise ValueError(f"Invalid field type: {field_type}")
|
|
238
|
+
return algosdk.abi.TupleType(types)
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def get_abi_struct_from_abi_tuple(
|
|
242
|
+
decoded_tuple: Any, # noqa: ANN401
|
|
243
|
+
struct_fields: list[StructField],
|
|
244
|
+
structs: dict[str, list[StructField]],
|
|
245
|
+
) -> dict[str, Any]:
|
|
246
|
+
"""Converts a decoded tuple to an ABI struct.
|
|
247
|
+
|
|
248
|
+
:param decoded_tuple: The tuple to convert
|
|
249
|
+
:param struct_fields: List of struct field definitions
|
|
250
|
+
:param structs: Dictionary of struct definitions
|
|
251
|
+
:return: The tuple as a struct dictionary
|
|
252
|
+
"""
|
|
253
|
+
result = {}
|
|
254
|
+
for i, field in enumerate(struct_fields):
|
|
255
|
+
key = field.name
|
|
256
|
+
field_type = field.type
|
|
257
|
+
value = decoded_tuple[i]
|
|
258
|
+
if isinstance(field_type, str):
|
|
259
|
+
if field_type in structs:
|
|
260
|
+
value = get_abi_struct_from_abi_tuple(value, structs[field_type], structs)
|
|
261
|
+
elif isinstance(field_type, list):
|
|
262
|
+
value = get_abi_struct_from_abi_tuple(value, field_type, structs)
|
|
263
|
+
result[key] = value
|
|
264
|
+
return result
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@dataclass(kw_only=True, frozen=True)
|
|
268
|
+
class BoxABIValue:
|
|
269
|
+
"""Represents an ABI value stored in a box.
|
|
270
|
+
|
|
271
|
+
:ivar name: The name of the box
|
|
272
|
+
:ivar value: The ABI value stored in the box
|
|
273
|
+
"""
|
|
274
|
+
|
|
275
|
+
name: BoxName
|
|
276
|
+
value: ABIValue
|