olas-operate-middleware 0.1.0rc59__py3-none-any.whl → 0.13.2__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.
- olas_operate_middleware-0.13.2.dist-info/METADATA +75 -0
- olas_operate_middleware-0.13.2.dist-info/RECORD +101 -0
- {olas_operate_middleware-0.1.0rc59.dist-info → olas_operate_middleware-0.13.2.dist-info}/WHEEL +1 -1
- operate/__init__.py +17 -0
- operate/account/user.py +35 -9
- operate/bridge/bridge_manager.py +470 -0
- operate/bridge/providers/lifi_provider.py +377 -0
- operate/bridge/providers/native_bridge_provider.py +677 -0
- operate/bridge/providers/provider.py +469 -0
- operate/bridge/providers/relay_provider.py +457 -0
- operate/cli.py +1565 -417
- operate/constants.py +60 -12
- operate/data/README.md +19 -0
- operate/data/contracts/{service_staking_token → dual_staking_token}/__init__.py +2 -2
- operate/data/contracts/dual_staking_token/build/DualStakingToken.json +443 -0
- operate/data/contracts/dual_staking_token/contract.py +132 -0
- operate/data/contracts/dual_staking_token/contract.yaml +23 -0
- operate/{ledger/base.py → data/contracts/foreign_omnibridge/__init__.py} +2 -19
- operate/data/contracts/foreign_omnibridge/build/ForeignOmnibridge.json +1372 -0
- operate/data/contracts/foreign_omnibridge/contract.py +130 -0
- operate/data/contracts/foreign_omnibridge/contract.yaml +23 -0
- operate/{ledger/solana.py → data/contracts/home_omnibridge/__init__.py} +2 -20
- operate/data/contracts/home_omnibridge/build/HomeOmnibridge.json +1421 -0
- operate/data/contracts/home_omnibridge/contract.py +80 -0
- operate/data/contracts/home_omnibridge/contract.yaml +23 -0
- operate/data/contracts/l1_standard_bridge/__init__.py +20 -0
- operate/data/contracts/l1_standard_bridge/build/L1StandardBridge.json +831 -0
- operate/data/contracts/l1_standard_bridge/contract.py +158 -0
- operate/data/contracts/l1_standard_bridge/contract.yaml +23 -0
- operate/data/contracts/l2_standard_bridge/__init__.py +20 -0
- operate/data/contracts/l2_standard_bridge/build/L2StandardBridge.json +626 -0
- operate/data/contracts/l2_standard_bridge/contract.py +130 -0
- operate/data/contracts/l2_standard_bridge/contract.yaml +23 -0
- operate/data/contracts/mech_activity/__init__.py +20 -0
- operate/data/contracts/mech_activity/build/MechActivity.json +111 -0
- operate/data/contracts/mech_activity/contract.py +44 -0
- operate/data/contracts/mech_activity/contract.yaml +23 -0
- operate/data/contracts/optimism_mintable_erc20/__init__.py +20 -0
- operate/data/contracts/optimism_mintable_erc20/build/OptimismMintableERC20.json +491 -0
- operate/data/contracts/optimism_mintable_erc20/contract.py +45 -0
- operate/data/contracts/optimism_mintable_erc20/contract.yaml +23 -0
- operate/data/contracts/recovery_module/__init__.py +20 -0
- operate/data/contracts/recovery_module/build/RecoveryModule.json +811 -0
- operate/data/contracts/recovery_module/contract.py +61 -0
- operate/data/contracts/recovery_module/contract.yaml +23 -0
- operate/data/contracts/requester_activity_checker/__init__.py +20 -0
- operate/data/contracts/requester_activity_checker/build/RequesterActivityChecker.json +111 -0
- operate/data/contracts/requester_activity_checker/contract.py +33 -0
- operate/data/contracts/requester_activity_checker/contract.yaml +23 -0
- operate/data/contracts/staking_token/__init__.py +20 -0
- operate/data/contracts/staking_token/build/StakingToken.json +1336 -0
- operate/data/contracts/{service_staking_token → staking_token}/contract.py +27 -13
- operate/data/contracts/staking_token/contract.yaml +23 -0
- operate/data/contracts/uniswap_v2_erc20/contract.yaml +3 -1
- operate/data/contracts/uniswap_v2_erc20/tests/__init__.py +20 -0
- operate/data/contracts/uniswap_v2_erc20/tests/test_contract.py +363 -0
- operate/keys.py +118 -33
- operate/ledger/__init__.py +159 -56
- operate/ledger/profiles.py +321 -18
- operate/migration.py +555 -0
- operate/{http → operate_http}/__init__.py +3 -2
- operate/{http → operate_http}/exceptions.py +6 -4
- operate/operate_types.py +544 -0
- operate/pearl.py +13 -1
- operate/quickstart/analyse_logs.py +118 -0
- operate/quickstart/claim_staking_rewards.py +104 -0
- operate/quickstart/reset_configs.py +106 -0
- operate/quickstart/reset_password.py +70 -0
- operate/quickstart/reset_staking.py +145 -0
- operate/quickstart/run_service.py +726 -0
- operate/quickstart/stop_service.py +72 -0
- operate/quickstart/terminate_on_chain_service.py +83 -0
- operate/quickstart/utils.py +298 -0
- operate/resource.py +62 -3
- operate/services/agent_runner.py +202 -0
- operate/services/deployment_runner.py +868 -0
- operate/services/funding_manager.py +929 -0
- operate/services/health_checker.py +280 -0
- operate/services/manage.py +2356 -620
- operate/services/protocol.py +1246 -340
- operate/services/service.py +756 -391
- operate/services/utils/mech.py +103 -0
- operate/services/utils/tendermint.py +86 -12
- operate/settings.py +70 -0
- operate/utils/__init__.py +135 -0
- operate/utils/gnosis.py +407 -80
- operate/utils/single_instance.py +226 -0
- operate/utils/ssl.py +133 -0
- operate/wallet/master.py +708 -123
- operate/wallet/wallet_recovery_manager.py +507 -0
- olas_operate_middleware-0.1.0rc59.dist-info/METADATA +0 -304
- olas_operate_middleware-0.1.0rc59.dist-info/RECORD +0 -41
- operate/data/contracts/service_staking_token/build/ServiceStakingToken.json +0 -1273
- operate/data/contracts/service_staking_token/contract.yaml +0 -23
- operate/ledger/ethereum.py +0 -48
- operate/types.py +0 -260
- {olas_operate_middleware-0.1.0rc59.dist-info → olas_operate_middleware-0.13.2.dist-info}/entry_points.txt +0 -0
- {olas_operate_middleware-0.1.0rc59.dist-info → olas_operate_middleware-0.13.2.dist-info/licenses}/LICENSE +0 -0
operate/operate_types.py
ADDED
|
@@ -0,0 +1,544 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
# ------------------------------------------------------------------------------
|
|
3
|
+
#
|
|
4
|
+
# Copyright 2023 Valory AG
|
|
5
|
+
#
|
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
7
|
+
# you may not use this file except in compliance with the License.
|
|
8
|
+
# You may obtain a copy of the License at
|
|
9
|
+
#
|
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
11
|
+
#
|
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
15
|
+
# See the License for the specific language governing permissions and
|
|
16
|
+
# limitations under the License.
|
|
17
|
+
#
|
|
18
|
+
# ------------------------------------------------------------------------------
|
|
19
|
+
|
|
20
|
+
"""Types module."""
|
|
21
|
+
|
|
22
|
+
import base64
|
|
23
|
+
import copy
|
|
24
|
+
import enum
|
|
25
|
+
import os
|
|
26
|
+
import typing as t
|
|
27
|
+
from dataclasses import dataclass, field
|
|
28
|
+
from pathlib import Path
|
|
29
|
+
|
|
30
|
+
import argon2
|
|
31
|
+
from aea_ledger_ethereum import cast
|
|
32
|
+
from autonomy.chain.config import ChainType
|
|
33
|
+
from autonomy.chain.constants import CHAIN_NAME_TO_CHAIN_ID
|
|
34
|
+
from cryptography.fernet import Fernet
|
|
35
|
+
from typing_extensions import TypedDict
|
|
36
|
+
|
|
37
|
+
from operate.constants import FERNET_KEY_LENGTH, NO_STAKING_PROGRAM_ID
|
|
38
|
+
from operate.resource import LocalResource
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
CHAIN_NAME_TO_CHAIN_ID["solana"] = 900
|
|
42
|
+
|
|
43
|
+
_CHAIN_ID_TO_CHAIN_NAME = {
|
|
44
|
+
chain_id: chain_name for chain_name, chain_id in CHAIN_NAME_TO_CHAIN_ID.items()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class LedgerType(str, enum.Enum):
|
|
49
|
+
"""Ledger type enum."""
|
|
50
|
+
|
|
51
|
+
ETHEREUM = "ethereum"
|
|
52
|
+
SOLANA = "solana"
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def config_file(self) -> str:
|
|
56
|
+
"""Config filename."""
|
|
57
|
+
return f"{self.name.lower()}.json"
|
|
58
|
+
|
|
59
|
+
@property
|
|
60
|
+
def key_file(self) -> str:
|
|
61
|
+
"""Key filename."""
|
|
62
|
+
return f"{self.name.lower()}.txt"
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def mnemonic_file(self) -> str:
|
|
66
|
+
"""Mnemonic filename."""
|
|
67
|
+
return f"{self.name.lower()}.mnemonic.json"
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def from_id(cls, chain_id: int) -> "LedgerType":
|
|
71
|
+
"""Load from chain ID."""
|
|
72
|
+
return Chain(_CHAIN_ID_TO_CHAIN_NAME[chain_id]).ledger_type
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# Dynamically create the Chain enum from the ChainType
|
|
76
|
+
# TODO: Migrate this to open-autonomy and remove this modified version of Chain here and use the one from open-autonomy
|
|
77
|
+
# This version of open-autonomy must support the LedgerType to support SOLANA in the future
|
|
78
|
+
# If solana support is not fuly implemented, decide to keep this half-baked feature.
|
|
79
|
+
#
|
|
80
|
+
# TODO: Once the above issue is properly implemented in Open Autonomy, remove the following
|
|
81
|
+
# lines from tox.ini:
|
|
82
|
+
#
|
|
83
|
+
# exclude = ^(operate/operate_types\.py|scripts/setup_wallet\.py|operate/ledger/profiles\.py|operate/ledger/__init__\.py|operate/wallet/master\.py|operate/services/protocol\.py|operate/services/manage\.py|operate/cli\.py)$
|
|
84
|
+
#
|
|
85
|
+
# [mypy-operate.*]
|
|
86
|
+
# follow_imports = skip # noqa
|
|
87
|
+
#
|
|
88
|
+
# These lines were itroduced to resolve mypy issues with the temporary Chain/ChainType solution.
|
|
89
|
+
Chain = enum.Enum(
|
|
90
|
+
"Chain",
|
|
91
|
+
[(member.name, member.value) for member in ChainType]
|
|
92
|
+
+ [
|
|
93
|
+
("SOLANA", "solana"),
|
|
94
|
+
],
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class ChainMixin:
|
|
99
|
+
"""Mixin for some new functions in the ChainType class."""
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def id(self) -> t.Optional[int]:
|
|
103
|
+
"""Chain ID"""
|
|
104
|
+
if self == Chain.CUSTOM:
|
|
105
|
+
chain_id = os.environ.get("CUSTOM_CHAIN_ID")
|
|
106
|
+
if chain_id is None:
|
|
107
|
+
return None
|
|
108
|
+
return int(chain_id)
|
|
109
|
+
return CHAIN_NAME_TO_CHAIN_ID[self.value]
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def ledger_type(self) -> LedgerType:
|
|
113
|
+
"""Ledger type."""
|
|
114
|
+
if self in (Chain.SOLANA,):
|
|
115
|
+
return LedgerType.SOLANA
|
|
116
|
+
return LedgerType.ETHEREUM
|
|
117
|
+
|
|
118
|
+
@classmethod
|
|
119
|
+
def from_string(cls, chain: str) -> "Chain":
|
|
120
|
+
"""Load from string."""
|
|
121
|
+
return Chain(chain.lower())
|
|
122
|
+
|
|
123
|
+
@classmethod
|
|
124
|
+
def from_id(cls, chain_id: int) -> "Chain":
|
|
125
|
+
"""Load from chain ID."""
|
|
126
|
+
return Chain(_CHAIN_ID_TO_CHAIN_NAME[chain_id])
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# Add the ChainMixin methods to the Chain enum
|
|
130
|
+
for name in dir(ChainMixin):
|
|
131
|
+
if not name.startswith("__"):
|
|
132
|
+
setattr(Chain, name, getattr(ChainMixin, name))
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class DeploymentStatus(enum.IntEnum):
|
|
136
|
+
"""Status payload."""
|
|
137
|
+
|
|
138
|
+
CREATED = 0
|
|
139
|
+
BUILT = 1
|
|
140
|
+
DEPLOYING = 2
|
|
141
|
+
DEPLOYED = 3
|
|
142
|
+
STOPPING = 4
|
|
143
|
+
STOPPED = 5
|
|
144
|
+
DELETED = 6
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
# TODO defined in aea.chain.base.OnChainState
|
|
148
|
+
class OnChainState(enum.IntEnum):
|
|
149
|
+
"""On-chain state."""
|
|
150
|
+
|
|
151
|
+
NON_EXISTENT = 0
|
|
152
|
+
PRE_REGISTRATION = 1
|
|
153
|
+
ACTIVE_REGISTRATION = 2
|
|
154
|
+
FINISHED_REGISTRATION = 3
|
|
155
|
+
DEPLOYED = 4
|
|
156
|
+
TERMINATED_BONDED = 5
|
|
157
|
+
UNBONDED = 6 # TODO this is not an on-chain state https://github.com/valory-xyz/autonolas-registries/blob/main/contracts/ServiceRegistryL2.sol
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class ContractAddresses(TypedDict):
|
|
161
|
+
"""Contracts templates."""
|
|
162
|
+
|
|
163
|
+
service_manager: str
|
|
164
|
+
service_registry: str
|
|
165
|
+
service_registry_token_utility: str
|
|
166
|
+
gnosis_safe_proxy_factory: str
|
|
167
|
+
gnosis_safe_same_address_multisig: str
|
|
168
|
+
safe_multisig_with_recovery_module: str
|
|
169
|
+
recovery_module: str
|
|
170
|
+
multisend: str
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
@dataclass
|
|
174
|
+
class LedgerConfig(LocalResource):
|
|
175
|
+
"""Ledger config."""
|
|
176
|
+
|
|
177
|
+
rpc: str
|
|
178
|
+
chain: Chain
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
LedgerConfigs = t.Dict[str, LedgerConfig]
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
class DeploymentConfig(TypedDict):
|
|
185
|
+
"""Deployments template."""
|
|
186
|
+
|
|
187
|
+
volumes: t.Dict[str, str]
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class FundRequirementsTemplate(TypedDict):
|
|
191
|
+
"""Fund requirement template."""
|
|
192
|
+
|
|
193
|
+
agent: int
|
|
194
|
+
safe: int
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
class ConfigurationTemplate(TypedDict):
|
|
198
|
+
"""Configuration template."""
|
|
199
|
+
|
|
200
|
+
staking_program_id: str
|
|
201
|
+
nft: str
|
|
202
|
+
rpc: str
|
|
203
|
+
agent_id: int
|
|
204
|
+
cost_of_bond: int
|
|
205
|
+
fund_requirements: t.Dict[str, FundRequirementsTemplate]
|
|
206
|
+
fallback_chain_params: t.Optional[t.Dict]
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class ServiceEnvProvisionType(str, enum.Enum):
|
|
210
|
+
"""Service environment variable provision type."""
|
|
211
|
+
|
|
212
|
+
FIXED = "fixed"
|
|
213
|
+
USER = "user"
|
|
214
|
+
COMPUTED = "computed"
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class EnvVariableAttributes(TypedDict):
|
|
218
|
+
"""Service environment variable template."""
|
|
219
|
+
|
|
220
|
+
name: str
|
|
221
|
+
description: str
|
|
222
|
+
value: str
|
|
223
|
+
provision_type: ServiceEnvProvisionType
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class AgentReleaseRepo(TypedDict):
|
|
227
|
+
"""Agent release repo template."""
|
|
228
|
+
|
|
229
|
+
owner: str
|
|
230
|
+
name: str
|
|
231
|
+
version: str
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
class AgentRelease(TypedDict):
|
|
235
|
+
"""Agent release template."""
|
|
236
|
+
|
|
237
|
+
is_aea: bool
|
|
238
|
+
repository: AgentReleaseRepo
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
ConfigurationTemplates = t.Dict[str, ConfigurationTemplate]
|
|
242
|
+
EnvVariables = t.Dict[str, EnvVariableAttributes]
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
class ServiceTemplate(TypedDict, total=False):
|
|
246
|
+
"""Service template."""
|
|
247
|
+
|
|
248
|
+
name: str
|
|
249
|
+
hash: str
|
|
250
|
+
image: str
|
|
251
|
+
description: str
|
|
252
|
+
service_version: str
|
|
253
|
+
agent_release: AgentRelease
|
|
254
|
+
home_chain: str
|
|
255
|
+
configurations: ConfigurationTemplates
|
|
256
|
+
env_variables: EnvVariables
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
@dataclass
|
|
260
|
+
class DeployedNodes(LocalResource):
|
|
261
|
+
"""Deployed nodes type."""
|
|
262
|
+
|
|
263
|
+
agent: t.List[str]
|
|
264
|
+
tendermint: t.List[str]
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
@dataclass
|
|
268
|
+
class OnChainFundRequirements(LocalResource):
|
|
269
|
+
"""On-chain fund requirements."""
|
|
270
|
+
|
|
271
|
+
agent: float
|
|
272
|
+
safe: float
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
OnChainTokenRequirements = t.Dict[str, OnChainFundRequirements]
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
@dataclass
|
|
279
|
+
class OnChainUserParams(LocalResource):
|
|
280
|
+
"""On-chain user params."""
|
|
281
|
+
|
|
282
|
+
staking_program_id: str
|
|
283
|
+
nft: str
|
|
284
|
+
agent_id: int
|
|
285
|
+
cost_of_bond: int
|
|
286
|
+
fund_requirements: OnChainTokenRequirements
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def use_staking(self) -> bool:
|
|
290
|
+
"""Check if staking is used."""
|
|
291
|
+
return (
|
|
292
|
+
self.staking_program_id is not None
|
|
293
|
+
and self.staking_program_id != NO_STAKING_PROGRAM_ID
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
@classmethod
|
|
297
|
+
def from_json(cls, obj: t.Dict) -> "OnChainUserParams":
|
|
298
|
+
"""Load a service"""
|
|
299
|
+
return super().from_json(obj) # type: ignore
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
@dataclass
|
|
303
|
+
class OnChainData(LocalResource):
|
|
304
|
+
"""On-chain data"""
|
|
305
|
+
|
|
306
|
+
instances: t.List[str] # Agent instances registered as safe owners
|
|
307
|
+
token: int
|
|
308
|
+
multisig: str
|
|
309
|
+
user_params: OnChainUserParams
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
@dataclass
|
|
313
|
+
class ChainConfig(LocalResource):
|
|
314
|
+
"""Chain config."""
|
|
315
|
+
|
|
316
|
+
ledger_config: LedgerConfig
|
|
317
|
+
chain_data: OnChainData
|
|
318
|
+
|
|
319
|
+
@classmethod
|
|
320
|
+
def from_json(cls, obj: t.Dict) -> "ChainConfig":
|
|
321
|
+
"""Load the chain config."""
|
|
322
|
+
return super().from_json(obj) # type: ignore
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
ChainConfigs = t.Dict[str, ChainConfig]
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
class FundingConfig(TypedDict):
|
|
329
|
+
"""Funding config."""
|
|
330
|
+
|
|
331
|
+
topup: int
|
|
332
|
+
threshold: int
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class AssetFundingValues(TypedDict):
|
|
336
|
+
"""Asset Funding values."""
|
|
337
|
+
|
|
338
|
+
agent: FundingConfig
|
|
339
|
+
safe: FundingConfig
|
|
340
|
+
|
|
341
|
+
|
|
342
|
+
FundingValues = t.Dict[str, AssetFundingValues] # str is the asset address
|
|
343
|
+
|
|
344
|
+
|
|
345
|
+
@dataclass
|
|
346
|
+
class MechMarketplaceConfig:
|
|
347
|
+
"""Mech Marketplace config."""
|
|
348
|
+
|
|
349
|
+
use_mech_marketplace: bool
|
|
350
|
+
mech_marketplace_address: str
|
|
351
|
+
priority_mech_address: str
|
|
352
|
+
priority_mech_service_id: int
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class Version:
|
|
356
|
+
"""Version class."""
|
|
357
|
+
|
|
358
|
+
def __init__(self, version: str) -> None:
|
|
359
|
+
"""Initialize the version."""
|
|
360
|
+
version = version.strip()
|
|
361
|
+
version_parts = version.split(".") if version else []
|
|
362
|
+
self.major = int(version_parts[0]) if len(version_parts) > 0 else 0
|
|
363
|
+
self.minor = int(version_parts[1]) if len(version_parts) > 1 else 0
|
|
364
|
+
self.patch = int(version_parts[2]) if len(version_parts) > 2 else 0
|
|
365
|
+
|
|
366
|
+
def __str__(self) -> str:
|
|
367
|
+
"""String representation of the version."""
|
|
368
|
+
return f"{self.major}.{self.minor}.{self.patch}"
|
|
369
|
+
|
|
370
|
+
def __eq__(self, other: object) -> bool:
|
|
371
|
+
"""Equality comparison."""
|
|
372
|
+
if not isinstance(other, Version):
|
|
373
|
+
return NotImplemented
|
|
374
|
+
return (
|
|
375
|
+
self.major == other.major
|
|
376
|
+
and self.minor == other.minor
|
|
377
|
+
and self.patch == other.patch
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
def __lt__(self, other: "Version") -> bool:
|
|
381
|
+
"""Less than comparison."""
|
|
382
|
+
if self.major != other.major:
|
|
383
|
+
return self.major < other.major
|
|
384
|
+
if self.minor != other.minor:
|
|
385
|
+
return self.minor < other.minor
|
|
386
|
+
return self.patch < other.patch
|
|
387
|
+
|
|
388
|
+
|
|
389
|
+
class ChainAmounts(dict[str, dict[str, dict[str, int]]]):
|
|
390
|
+
"""
|
|
391
|
+
Class that represents chain amounts as a dictionary
|
|
392
|
+
|
|
393
|
+
The standard format follows the convention {chain: {address: {token: amount}}}
|
|
394
|
+
"""
|
|
395
|
+
|
|
396
|
+
@classmethod
|
|
397
|
+
def shortfalls(
|
|
398
|
+
cls, requirements: "ChainAmounts", balances: "ChainAmounts"
|
|
399
|
+
) -> "ChainAmounts":
|
|
400
|
+
"""Return the shortfalls between requirements and balances."""
|
|
401
|
+
result: dict[str, dict[str, dict[str, int]]] = {}
|
|
402
|
+
|
|
403
|
+
for chain, addresses in requirements.items():
|
|
404
|
+
for address, assets in addresses.items():
|
|
405
|
+
for asset, required_amount in assets.items():
|
|
406
|
+
available = balances.get(chain, {}).get(address, {}).get(asset, 0)
|
|
407
|
+
shortfall = max(required_amount - available, 0)
|
|
408
|
+
result.setdefault(chain, {}).setdefault(address, {})[
|
|
409
|
+
asset
|
|
410
|
+
] = shortfall
|
|
411
|
+
|
|
412
|
+
return cls(result)
|
|
413
|
+
|
|
414
|
+
@classmethod
|
|
415
|
+
def add(cls, *chainamounts: "ChainAmounts") -> "ChainAmounts":
|
|
416
|
+
"""Add multiple ChainAmounts"""
|
|
417
|
+
result: dict[str, dict[str, dict[str, int]]] = {}
|
|
418
|
+
|
|
419
|
+
for ca in chainamounts:
|
|
420
|
+
for chain, addresses in ca.items():
|
|
421
|
+
result_addresses = result.setdefault(chain, {})
|
|
422
|
+
for address, assets in addresses.items():
|
|
423
|
+
result_assets = result_addresses.setdefault(address, {})
|
|
424
|
+
for asset, amount in assets.items():
|
|
425
|
+
result_assets[asset] = result_assets.get(asset, 0) + amount
|
|
426
|
+
|
|
427
|
+
return cls(result)
|
|
428
|
+
|
|
429
|
+
def __add__(self, other: "ChainAmounts") -> "ChainAmounts":
|
|
430
|
+
"""Add two ChainAmounts"""
|
|
431
|
+
return ChainAmounts.add(self, other)
|
|
432
|
+
|
|
433
|
+
def __mul__(self, multiplier: float) -> "ChainAmounts":
|
|
434
|
+
"""Multiply all amounts by the specified multiplier"""
|
|
435
|
+
output = copy.deepcopy(self)
|
|
436
|
+
for _, addresses in output.items():
|
|
437
|
+
for _, balances in addresses.items():
|
|
438
|
+
for asset, amount in balances.items():
|
|
439
|
+
balances[asset] = int(amount * multiplier)
|
|
440
|
+
return output
|
|
441
|
+
|
|
442
|
+
def __sub__(self, other: "ChainAmounts") -> "ChainAmounts":
|
|
443
|
+
"""Subtract two ChainAmounts"""
|
|
444
|
+
return self + (other * -1)
|
|
445
|
+
|
|
446
|
+
def __floordiv__(self, divisor: float) -> "ChainAmounts":
|
|
447
|
+
"""Divide all amounts by the specified divisor"""
|
|
448
|
+
if divisor == 0:
|
|
449
|
+
raise ValueError("Cannot divide by zero")
|
|
450
|
+
|
|
451
|
+
output = copy.deepcopy(self)
|
|
452
|
+
for _, addresses in output.items():
|
|
453
|
+
for _, balances in addresses.items():
|
|
454
|
+
for asset, amount in balances.items():
|
|
455
|
+
balances[asset] = int(amount // divisor)
|
|
456
|
+
return output
|
|
457
|
+
|
|
458
|
+
def __lt__(self, other: "ChainAmounts") -> bool:
|
|
459
|
+
"""Return True if all amounts in self are strictly less than the corresponding amounts in other."""
|
|
460
|
+
for chain, addresses in self.items():
|
|
461
|
+
for address, assets in addresses.items():
|
|
462
|
+
for asset, amount in assets.items():
|
|
463
|
+
if amount >= other.get(chain, {}).get(address, {}).get(asset, 0):
|
|
464
|
+
return False
|
|
465
|
+
return True
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
@dataclass
|
|
469
|
+
class EncryptedData(LocalResource):
|
|
470
|
+
"""EncryptedData type."""
|
|
471
|
+
|
|
472
|
+
path: Path
|
|
473
|
+
version: int
|
|
474
|
+
cipher: str
|
|
475
|
+
cipherparams: t.Dict[str, t.Union[int, str]] = field(repr=False)
|
|
476
|
+
ciphertext: str = field(repr=False)
|
|
477
|
+
kdf: str
|
|
478
|
+
kdfparams: t.Dict[str, t.Union[int, str]] = field(repr=False)
|
|
479
|
+
|
|
480
|
+
@classmethod
|
|
481
|
+
def new(cls, path: Path, password: str, plaintext_bytes: bytes) -> "EncryptedData":
|
|
482
|
+
"""Creates a new EncryptedData"""
|
|
483
|
+
ph = argon2.PasswordHasher()
|
|
484
|
+
salt = os.urandom(ph.salt_len)
|
|
485
|
+
time_cost = ph.time_cost
|
|
486
|
+
memory_cost = ph.memory_cost
|
|
487
|
+
parallelism = ph.parallelism
|
|
488
|
+
hash_len = FERNET_KEY_LENGTH
|
|
489
|
+
argon2_type = argon2.Type.ID
|
|
490
|
+
key = argon2.low_level.hash_secret_raw(
|
|
491
|
+
secret=password.encode(),
|
|
492
|
+
salt=salt,
|
|
493
|
+
time_cost=time_cost,
|
|
494
|
+
memory_cost=memory_cost,
|
|
495
|
+
parallelism=parallelism,
|
|
496
|
+
hash_len=hash_len,
|
|
497
|
+
type=argon2_type,
|
|
498
|
+
)
|
|
499
|
+
|
|
500
|
+
fernet_key = base64.urlsafe_b64encode(key)
|
|
501
|
+
fernet = Fernet(fernet_key)
|
|
502
|
+
ciphertext_bytes = fernet.encrypt(plaintext_bytes)
|
|
503
|
+
|
|
504
|
+
return cls(
|
|
505
|
+
path=path,
|
|
506
|
+
version=1,
|
|
507
|
+
cipher=f"{fernet.__class__.__module__}.{fernet.__class__.__qualname__}",
|
|
508
|
+
cipherparams={ # Fernet token (ciphertext variable) already stores them
|
|
509
|
+
"version": ciphertext_bytes[0]
|
|
510
|
+
},
|
|
511
|
+
ciphertext=ciphertext_bytes.hex(),
|
|
512
|
+
kdf=f"{ph.__class__.__module__}.{ph.__class__.__qualname__}",
|
|
513
|
+
kdfparams={
|
|
514
|
+
"salt": salt.hex(),
|
|
515
|
+
"time_cost": time_cost,
|
|
516
|
+
"memory_cost": memory_cost,
|
|
517
|
+
"parallelism": parallelism,
|
|
518
|
+
"hash_len": hash_len,
|
|
519
|
+
"type": argon2_type.name,
|
|
520
|
+
},
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
def decrypt(self, password: str) -> bytes:
|
|
524
|
+
"""Decrypts the EncryptedData"""
|
|
525
|
+
kdfparams = self.kdfparams
|
|
526
|
+
key = argon2.low_level.hash_secret_raw(
|
|
527
|
+
secret=password.encode(),
|
|
528
|
+
salt=bytes.fromhex(kdfparams["salt"]),
|
|
529
|
+
time_cost=kdfparams["time_cost"],
|
|
530
|
+
memory_cost=kdfparams["memory_cost"],
|
|
531
|
+
parallelism=kdfparams["parallelism"],
|
|
532
|
+
hash_len=kdfparams["hash_len"],
|
|
533
|
+
type=argon2.Type[kdfparams["type"]],
|
|
534
|
+
)
|
|
535
|
+
fernet_key = base64.urlsafe_b64encode(key)
|
|
536
|
+
fernet = Fernet(fernet_key)
|
|
537
|
+
ciphertext_bytes = bytes.fromhex(self.ciphertext)
|
|
538
|
+
plaintext_bytes = fernet.decrypt(ciphertext_bytes)
|
|
539
|
+
return plaintext_bytes
|
|
540
|
+
|
|
541
|
+
@classmethod
|
|
542
|
+
def load(cls, path: Path) -> "EncryptedData":
|
|
543
|
+
"""Load EncryptedData."""
|
|
544
|
+
return cast(EncryptedData, super().load(path))
|
operate/pearl.py
CHANGED
|
@@ -18,10 +18,22 @@
|
|
|
18
18
|
# ------------------------------------------------------------------------------
|
|
19
19
|
|
|
20
20
|
"""File used for pyinstaller to create a single executable file."""
|
|
21
|
-
|
|
22
21
|
# pylint: disable=all
|
|
23
22
|
# mypy: ignore-errors
|
|
24
23
|
# flake8: noqa
|
|
24
|
+
|
|
25
|
+
import os
|
|
26
|
+
import sys
|
|
27
|
+
from pathlib import Path
|
|
28
|
+
|
|
29
|
+
import aea.configurations.validation as validation_module
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# patch for the _CUR_DIR value
|
|
33
|
+
validation_module._CUR_DIR = Path(sys._MEIPASS) / validation_module._CUR_DIR
|
|
34
|
+
validation_module._SCHEMAS_DIR = os.path.join(validation_module._CUR_DIR, "schemas")
|
|
35
|
+
|
|
36
|
+
|
|
25
37
|
from aea.crypto.registries.base import *
|
|
26
38
|
from aea.mail.base_pb2 import DESCRIPTOR
|
|
27
39
|
from aea_ledger_cosmos.cosmos import * # noqa
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# ------------------------------------------------------------------------------
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2023-2025 Valory AG
|
|
4
|
+
#
|
|
5
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
# you may not use this file except in compliance with the License.
|
|
7
|
+
# You may obtain a copy of the License at
|
|
8
|
+
#
|
|
9
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
#
|
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
# See the License for the specific language governing permissions and
|
|
15
|
+
# limitations under the License.
|
|
16
|
+
#
|
|
17
|
+
# ------------------------------------------------------------------------------
|
|
18
|
+
"""Quickstart script to run log analysis."""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
import subprocess # nosec
|
|
24
|
+
import sys
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
from typing import List, TYPE_CHECKING, Union
|
|
27
|
+
|
|
28
|
+
from operate.constants import DEPLOYMENT_DIR
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from operate.cli import OperateApp
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def find_build_directory(config_file: Path, operate: "OperateApp") -> Path:
|
|
36
|
+
"""Find the appropriate build directory of the configured service."""
|
|
37
|
+
with open(config_file, "r") as f:
|
|
38
|
+
config = json.load(f)
|
|
39
|
+
config_service_hash = config.get("hash")
|
|
40
|
+
|
|
41
|
+
services, _ = operate.service_manager().get_all_services()
|
|
42
|
+
for service in services:
|
|
43
|
+
if service.hash == config_service_hash:
|
|
44
|
+
build_dir = service.path / DEPLOYMENT_DIR
|
|
45
|
+
if not build_dir.exists():
|
|
46
|
+
print(f"{config.get('name')} not deployed.")
|
|
47
|
+
sys.exit(1)
|
|
48
|
+
return build_dir
|
|
49
|
+
|
|
50
|
+
print(f"{config.get('name')} not found.")
|
|
51
|
+
sys.exit(1)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def run_analysis(logs_dir: Path, **kwargs: str) -> None:
|
|
55
|
+
"""Run the log analysis command."""
|
|
56
|
+
command: List[Union[str, Path]] = [
|
|
57
|
+
"poetry",
|
|
58
|
+
"run",
|
|
59
|
+
"autonomy",
|
|
60
|
+
"analyse",
|
|
61
|
+
"logs",
|
|
62
|
+
"--from-dir",
|
|
63
|
+
logs_dir,
|
|
64
|
+
]
|
|
65
|
+
if "agent" in kwargs and kwargs["agent"]:
|
|
66
|
+
command.extend(["--agent", kwargs["agent"]])
|
|
67
|
+
if "reset_db" in kwargs and kwargs["reset_db"]:
|
|
68
|
+
command.extend(["--reset-db"])
|
|
69
|
+
if "start_time" in kwargs and kwargs["start_time"]:
|
|
70
|
+
command.extend(["--start-time", kwargs["start_time"]])
|
|
71
|
+
if "end_time" in kwargs and kwargs["end_time"]:
|
|
72
|
+
command.extend(["--end-time", kwargs["end_time"]])
|
|
73
|
+
if "log_level" in kwargs and kwargs["log_level"]:
|
|
74
|
+
command.extend(["--log-level", kwargs["log_level"]])
|
|
75
|
+
if "period" in kwargs and kwargs["period"]:
|
|
76
|
+
command.extend(["--period", kwargs["period"]])
|
|
77
|
+
if "round" in kwargs and kwargs["round"]:
|
|
78
|
+
command.extend(["--round", kwargs["round"]])
|
|
79
|
+
if "behaviour" in kwargs and kwargs["behaviour"]:
|
|
80
|
+
command.extend(["--behaviour", kwargs["behaviour"]])
|
|
81
|
+
if "fsm" in kwargs and kwargs["fsm"]:
|
|
82
|
+
command.extend(["--fsm"])
|
|
83
|
+
if "include_regex" in kwargs and kwargs["include_regex"]:
|
|
84
|
+
command.extend(["--include-regex", kwargs["include_regex"]])
|
|
85
|
+
if "exclude_regex" in kwargs and kwargs["exclude_regex"]:
|
|
86
|
+
command.extend(["--exclude-regex", kwargs["exclude_regex"]])
|
|
87
|
+
|
|
88
|
+
try:
|
|
89
|
+
subprocess.run(command, check=True) # nosec
|
|
90
|
+
print("Analysis completed successfully.")
|
|
91
|
+
except subprocess.CalledProcessError as e:
|
|
92
|
+
print(f"Command failed with exit code {e.returncode}")
|
|
93
|
+
sys.exit(e.returncode)
|
|
94
|
+
except FileNotFoundError:
|
|
95
|
+
print("Poetry or autonomy not found. Ensure they are installed and accessible.")
|
|
96
|
+
sys.exit(1)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def analyse_logs(
|
|
100
|
+
operate: "OperateApp",
|
|
101
|
+
config_path: str,
|
|
102
|
+
**kwargs: str,
|
|
103
|
+
) -> None:
|
|
104
|
+
"""Run the log analysis command."""
|
|
105
|
+
config_file = Path(config_path)
|
|
106
|
+
if not config_file.exists():
|
|
107
|
+
print(f"Config file '{config_file}' not found.")
|
|
108
|
+
sys.exit(1)
|
|
109
|
+
|
|
110
|
+
# Auto-detect the logs directory
|
|
111
|
+
build_dir = find_build_directory(config_file, operate)
|
|
112
|
+
logs_dir = build_dir / "persistent_data" / "logs"
|
|
113
|
+
if not os.path.exists(logs_dir):
|
|
114
|
+
print(f"Logs directory '{logs_dir}' not found.")
|
|
115
|
+
sys.exit(1)
|
|
116
|
+
|
|
117
|
+
# Run the analysis
|
|
118
|
+
run_analysis(logs_dir, **kwargs)
|