olas-operate-middleware 0.10.20__py3-none-any.whl → 0.11.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {olas_operate_middleware-0.10.20.dist-info → olas_operate_middleware-0.11.0.dist-info}/METADATA +3 -1
- {olas_operate_middleware-0.10.20.dist-info → olas_operate_middleware-0.11.0.dist-info}/RECORD +25 -22
- operate/bridge/bridge_manager.py +10 -12
- operate/bridge/providers/native_bridge_provider.py +1 -1
- operate/bridge/providers/provider.py +21 -34
- operate/cli.py +438 -108
- operate/constants.py +20 -0
- operate/ledger/__init__.py +55 -5
- operate/ledger/profiles.py +79 -11
- operate/operate_types.py +205 -2
- operate/quickstart/run_service.py +1 -1
- operate/services/agent_runner.py +5 -3
- operate/services/deployment_runner.py +3 -0
- operate/services/funding_manager.py +904 -0
- operate/services/manage.py +165 -306
- operate/services/protocol.py +392 -140
- operate/services/service.py +81 -5
- operate/settings.py +70 -0
- operate/utils/gnosis.py +79 -24
- operate/utils/single_instance.py +226 -0
- operate/wallet/master.py +214 -177
- operate/wallet/wallet_recovery_manager.py +5 -5
- {olas_operate_middleware-0.10.20.dist-info → olas_operate_middleware-0.11.0.dist-info}/WHEEL +0 -0
- {olas_operate_middleware-0.10.20.dist-info → olas_operate_middleware-0.11.0.dist-info}/entry_points.txt +0 -0
- {olas_operate_middleware-0.10.20.dist-info → olas_operate_middleware-0.11.0.dist-info}/licenses/LICENSE +0 -0
operate/constants.py
CHANGED
|
@@ -33,20 +33,37 @@ DEPLOYMENT_JSON = "deployment.json"
|
|
|
33
33
|
CONFIG_JSON = "config.json"
|
|
34
34
|
USER_JSON = "user.json"
|
|
35
35
|
HEALTHCHECK_JSON = "healthcheck.json"
|
|
36
|
+
VERSION_FILE = "operate.version"
|
|
37
|
+
SETTINGS_JSON = "settings.json"
|
|
38
|
+
FUNDING_REQUIREMENTS_JSON = "funding_requirements.json"
|
|
39
|
+
DEFAULT_TOPUP_THRESHOLD = 0.5
|
|
40
|
+
|
|
41
|
+
MASTER_EOA_PLACEHOLDER = "master_eoa"
|
|
42
|
+
MASTER_SAFE_PLACEHOLDER = "master_safe"
|
|
43
|
+
AGENT_EOA_PLACEHOLDER = "agent_eoa"
|
|
44
|
+
SERVICE_SAFE_PLACEHOLDER = "service_safe"
|
|
45
|
+
|
|
46
|
+
FERNET_KEY_LENGTH = 32
|
|
36
47
|
|
|
37
48
|
AGENT_PERSISTENT_STORAGE_DIR = "persistent_data"
|
|
38
49
|
AGENT_PERSISTENT_STORAGE_ENV_VAR = "STORE_PATH"
|
|
39
50
|
AGENT_LOG_DIR = "benchmarks"
|
|
40
51
|
AGENT_LOG_ENV_VAR = "LOG_DIR"
|
|
52
|
+
AGENT_RUNNER_PREFIX = "agent_runner"
|
|
41
53
|
|
|
42
54
|
ZERO_ADDRESS = "0x0000000000000000000000000000000000000000"
|
|
43
55
|
|
|
56
|
+
MIN_AGENT_BOND = 1
|
|
57
|
+
MIN_SECURITY_DEPOSIT = 1
|
|
58
|
+
|
|
44
59
|
ON_CHAIN_INTERACT_TIMEOUT = 120.0
|
|
45
60
|
ON_CHAIN_INTERACT_RETRIES = 12
|
|
46
61
|
ON_CHAIN_INTERACT_SLEEP = 5.0
|
|
47
62
|
MIN_PASSWORD_LENGTH = 8
|
|
63
|
+
DEFAULT_FUNDING_REQUESTS_COOLDOWN_SECONDS = 300 # Seconds to wait after an agent has been funded during which it will not be asked for fund requirements again
|
|
48
64
|
|
|
49
65
|
HEALTH_CHECK_URL = "http://127.0.0.1:8716/healthcheck" # possible DNS issues on windows so use IP address
|
|
66
|
+
AGENT_FUNDS_STATUS_URL = "http://127.0.0.1:8716/funds-status"
|
|
50
67
|
SAFE_WEBAPP_URL = "https://app.safe.global/home?safe=gno:"
|
|
51
68
|
TM_CONTROL_URL = "http://localhost:8080"
|
|
52
69
|
IPFS_ADDRESS = "https://gateway.autonolas.tech/ipfs/f01701220{hash}"
|
|
@@ -63,3 +80,6 @@ NO_STAKING_PROGRAM_ID = "no_staking"
|
|
|
63
80
|
|
|
64
81
|
DEPLOYMENT_START_TRIES_NUM = 3
|
|
65
82
|
IPFS_CHECK_URL = "https://gateway.autonolas.tech/ipfs/bafybeigcllaxn4ycjjvika3zd6eicksuriez2wtg67gx7pamhcazl3tv54/echo/README.md"
|
|
83
|
+
MSG_NEW_PASSWORD_MISSING = "'new_password' is required." # nosec
|
|
84
|
+
MSG_INVALID_PASSWORD = "Password is not valid." # nosec
|
|
85
|
+
MSG_INVALID_MNEMONIC = "Seed phrase is not valid." # nosec
|
operate/ledger/__init__.py
CHANGED
|
@@ -21,9 +21,13 @@
|
|
|
21
21
|
|
|
22
22
|
import os
|
|
23
23
|
import typing as t
|
|
24
|
+
from copy import deepcopy
|
|
24
25
|
from math import ceil
|
|
25
26
|
|
|
26
27
|
from aea.crypto.base import LedgerApi
|
|
28
|
+
from aea.crypto.registries import make_ledger_api
|
|
29
|
+
from aea_ledger_ethereum import DEFAULT_GAS_PRICE_STRATEGIES, EIP1559, GWEI, to_wei
|
|
30
|
+
from web3.middleware import geth_poa_middleware
|
|
27
31
|
|
|
28
32
|
from operate.operate_types import Chain
|
|
29
33
|
|
|
@@ -89,15 +93,17 @@ CURRENCY_SMALLEST_UNITS = {
|
|
|
89
93
|
Chain.SOLANA: "Lamport",
|
|
90
94
|
}
|
|
91
95
|
|
|
96
|
+
NATIVE_CURRENCY_DECIMALS = 18
|
|
97
|
+
|
|
92
98
|
|
|
93
99
|
def get_default_rpc(chain: Chain) -> str:
|
|
94
100
|
"""Get default RPC chain type."""
|
|
95
|
-
return DEFAULT_RPCS
|
|
101
|
+
return DEFAULT_RPCS[chain]
|
|
96
102
|
|
|
97
103
|
|
|
98
104
|
def get_currency_denom(chain: Chain) -> str:
|
|
99
105
|
"""Get currency denom by chain type."""
|
|
100
|
-
return CURRENCY_DENOMS.get(chain, "
|
|
106
|
+
return CURRENCY_DENOMS.get(chain, "NATIVE")
|
|
101
107
|
|
|
102
108
|
|
|
103
109
|
def get_currency_smallest_unit(chain: Chain) -> str:
|
|
@@ -105,6 +111,50 @@ def get_currency_smallest_unit(chain: Chain) -> str:
|
|
|
105
111
|
return CURRENCY_SMALLEST_UNITS.get(chain, "Wei")
|
|
106
112
|
|
|
107
113
|
|
|
114
|
+
DEFAULT_LEDGER_APIS: t.Dict[Chain, LedgerApi] = {}
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def make_chain_ledger_api(
|
|
118
|
+
chain: Chain,
|
|
119
|
+
rpc: t.Optional[str] = None,
|
|
120
|
+
) -> LedgerApi:
|
|
121
|
+
"""Get default RPC chain type."""
|
|
122
|
+
|
|
123
|
+
if chain not in DEFAULT_LEDGER_APIS:
|
|
124
|
+
if chain == Chain.SOLANA: # TODO: Complete when Solana is supported
|
|
125
|
+
raise NotImplementedError("Solana not yet supported.")
|
|
126
|
+
|
|
127
|
+
gas_price_strategies = deepcopy(DEFAULT_GAS_PRICE_STRATEGIES)
|
|
128
|
+
if chain in (Chain.BASE, Chain.MODE, Chain.OPTIMISM):
|
|
129
|
+
gas_price_strategies[EIP1559]["fallback_estimate"]["maxFeePerGas"] = to_wei(
|
|
130
|
+
5, GWEI
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
ledger_api = make_ledger_api(
|
|
134
|
+
chain.ledger_type.name.lower(),
|
|
135
|
+
address=rpc or get_default_rpc(chain=chain),
|
|
136
|
+
chain_id=chain.id,
|
|
137
|
+
gas_price_strategies=gas_price_strategies,
|
|
138
|
+
poa_chain=chain == Chain.POLYGON,
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
if chain == Chain.OPTIMISM:
|
|
142
|
+
ledger_api.api.middleware_onion.inject(geth_poa_middleware, layer=0)
|
|
143
|
+
|
|
144
|
+
DEFAULT_LEDGER_APIS[chain] = ledger_api
|
|
145
|
+
|
|
146
|
+
return DEFAULT_LEDGER_APIS[chain]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def get_default_ledger_api(chain: Chain) -> LedgerApi:
|
|
150
|
+
"""Get default RPC chain type."""
|
|
151
|
+
if chain not in DEFAULT_LEDGER_APIS:
|
|
152
|
+
DEFAULT_LEDGER_APIS[chain] = make_chain_ledger_api(
|
|
153
|
+
chain=chain, rpc=get_default_rpc(chain=chain)
|
|
154
|
+
)
|
|
155
|
+
return DEFAULT_LEDGER_APIS[chain]
|
|
156
|
+
|
|
157
|
+
|
|
108
158
|
GAS_ESTIMATE_FALLBACK_ADDRESSES = [
|
|
109
159
|
"0x000000000000000000000000000000000000dEaD",
|
|
110
160
|
"0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE", # nosec
|
|
@@ -137,7 +187,7 @@ def update_tx_with_gas_pricing(tx: t.Dict, ledger_api: LedgerApi) -> None:
|
|
|
137
187
|
# TODO This gas management should be done at a lower level in the library
|
|
138
188
|
def update_tx_with_gas_estimate(tx: t.Dict, ledger_api: LedgerApi) -> None:
|
|
139
189
|
"""Update transaction with gas estimate."""
|
|
140
|
-
print(f"[
|
|
190
|
+
print(f"[LEDGER] Trying to update transaction gas {tx['from']=} {tx['gas']=}.")
|
|
141
191
|
original_from = tx["from"]
|
|
142
192
|
original_gas = tx.get("gas", 1)
|
|
143
193
|
|
|
@@ -146,11 +196,11 @@ def update_tx_with_gas_estimate(tx: t.Dict, ledger_api: LedgerApi) -> None:
|
|
|
146
196
|
tx["gas"] = 1
|
|
147
197
|
ledger_api.update_with_gas_estimate(tx)
|
|
148
198
|
if tx["gas"] > 1:
|
|
149
|
-
print(f"[
|
|
199
|
+
print(f"[LEDGER] Gas estimated successfully {tx['from']=} {tx['gas']=}.")
|
|
150
200
|
break
|
|
151
201
|
|
|
152
202
|
tx["from"] = original_from
|
|
153
203
|
if tx["gas"] == 1:
|
|
154
204
|
tx["gas"] = original_gas
|
|
155
|
-
print(f"[
|
|
205
|
+
print(f"[LEDGER] Unable to estimate gas. Restored {tx['gas']=}.")
|
|
156
206
|
tx["gas"] = ceil(tx["gas"] * GAS_ESTIMATE_BUFFER)
|
operate/ledger/profiles.py
CHANGED
|
@@ -20,11 +20,18 @@
|
|
|
20
20
|
"""Chain profiles."""
|
|
21
21
|
|
|
22
22
|
import typing as t
|
|
23
|
+
from functools import cache
|
|
23
24
|
|
|
25
|
+
from autonomy.chain.base import registry_contracts
|
|
24
26
|
from autonomy.chain.constants import CHAIN_PROFILES, DEFAULT_MULTISEND
|
|
25
27
|
|
|
26
28
|
from operate.constants import NO_STAKING_PROGRAM_ID, ZERO_ADDRESS
|
|
27
|
-
from operate.ledger import
|
|
29
|
+
from operate.ledger import (
|
|
30
|
+
CHAINS,
|
|
31
|
+
NATIVE_CURRENCY_DECIMALS,
|
|
32
|
+
get_currency_denom,
|
|
33
|
+
get_default_ledger_api,
|
|
34
|
+
)
|
|
28
35
|
from operate.operate_types import Chain, ContractAddresses
|
|
29
36
|
|
|
30
37
|
|
|
@@ -177,7 +184,22 @@ WRAPPED_NATIVE_ASSET = {
|
|
|
177
184
|
Chain.POLYGON: "0x0d500B1d8E8eF31E21C99d1Db9A6444d3ADf1270",
|
|
178
185
|
}
|
|
179
186
|
|
|
180
|
-
ERC20_TOKENS =
|
|
187
|
+
ERC20_TOKENS = {
|
|
188
|
+
"OLAS": OLAS,
|
|
189
|
+
"USDC": USDC,
|
|
190
|
+
"WRAPPED_NATIVE": WRAPPED_NATIVE_ASSET,
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
DUST = {
|
|
194
|
+
Chain.ARBITRUM_ONE: int(1e14),
|
|
195
|
+
Chain.BASE: int(1e14),
|
|
196
|
+
Chain.CELO: int(1e15),
|
|
197
|
+
Chain.ETHEREUM: int(1e14),
|
|
198
|
+
Chain.GNOSIS: int(1e15),
|
|
199
|
+
Chain.MODE: int(1e14),
|
|
200
|
+
Chain.OPTIMISM: int(1e14),
|
|
201
|
+
Chain.POLYGON: int(1e14),
|
|
202
|
+
}
|
|
181
203
|
|
|
182
204
|
DEFAULT_NEW_SAFE_FUNDS: t.Dict[Chain, t.Dict[str, int]] = {
|
|
183
205
|
Chain.ARBITRUM_ONE: {
|
|
@@ -206,17 +228,24 @@ DEFAULT_NEW_SAFE_FUNDS: t.Dict[Chain, t.Dict[str, int]] = {
|
|
|
206
228
|
},
|
|
207
229
|
}
|
|
208
230
|
|
|
209
|
-
|
|
210
|
-
Chain.ARBITRUM_ONE: {ZERO_ADDRESS:
|
|
211
|
-
Chain.BASE: {ZERO_ADDRESS:
|
|
212
|
-
Chain.CELO: {ZERO_ADDRESS:
|
|
213
|
-
Chain.ETHEREUM: {ZERO_ADDRESS:
|
|
214
|
-
Chain.GNOSIS: {ZERO_ADDRESS:
|
|
215
|
-
Chain.MODE: {ZERO_ADDRESS:
|
|
216
|
-
Chain.OPTIMISM: {ZERO_ADDRESS:
|
|
217
|
-
Chain.POLYGON: {ZERO_ADDRESS:
|
|
231
|
+
DEFAULT_EOA_TOPUPS = {
|
|
232
|
+
Chain.ARBITRUM_ONE: {ZERO_ADDRESS: 2_500_000_000_000_000},
|
|
233
|
+
Chain.BASE: {ZERO_ADDRESS: 2_500_000_000_000_000},
|
|
234
|
+
Chain.CELO: {ZERO_ADDRESS: 750_000_000_000_000_000},
|
|
235
|
+
Chain.ETHEREUM: {ZERO_ADDRESS: 10_000_000_000_000_000},
|
|
236
|
+
Chain.GNOSIS: {ZERO_ADDRESS: 750_000_000_000_000_000},
|
|
237
|
+
Chain.MODE: {ZERO_ADDRESS: 250_000_000_000_000},
|
|
238
|
+
Chain.OPTIMISM: {ZERO_ADDRESS: 2_500_000_000_000_000},
|
|
239
|
+
Chain.POLYGON: {ZERO_ADDRESS: 750_000_000_000_000_000},
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
DEFAULT_EOA_TOPUPS_WITHOUT_SAFE = {
|
|
243
|
+
chain: {asset: amount * 2 for asset, amount in amounts.items()}
|
|
244
|
+
for chain, amounts in DEFAULT_EOA_TOPUPS.items()
|
|
218
245
|
}
|
|
219
246
|
|
|
247
|
+
DEFAULT_EOA_THRESHOLD = 0.5
|
|
248
|
+
|
|
220
249
|
EXPLORER_URL = {
|
|
221
250
|
Chain.ARBITRUM_ONE: {
|
|
222
251
|
"tx": "https://arbiscan.io/tx/{tx_hash}",
|
|
@@ -253,6 +282,45 @@ EXPLORER_URL = {
|
|
|
253
282
|
}
|
|
254
283
|
|
|
255
284
|
|
|
285
|
+
@cache
|
|
286
|
+
def get_asset_name(chain: Chain, asset_address: str) -> str:
|
|
287
|
+
"""Get token name."""
|
|
288
|
+
if asset_address == ZERO_ADDRESS:
|
|
289
|
+
return get_currency_denom(chain)
|
|
290
|
+
|
|
291
|
+
if WRAPPED_NATIVE_ASSET.get(chain) == asset_address:
|
|
292
|
+
return f"W{get_currency_denom(chain)}"
|
|
293
|
+
|
|
294
|
+
for symbol, tokens in ERC20_TOKENS.items():
|
|
295
|
+
if tokens.get(chain) == asset_address:
|
|
296
|
+
return symbol
|
|
297
|
+
|
|
298
|
+
return asset_address
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
@cache
|
|
302
|
+
def get_asset_decimals(chain: Chain, asset_address: str) -> int:
|
|
303
|
+
"""Get token decimals."""
|
|
304
|
+
if asset_address == ZERO_ADDRESS:
|
|
305
|
+
return NATIVE_CURRENCY_DECIMALS
|
|
306
|
+
erc20_token = registry_contracts.erc20.get_instance(
|
|
307
|
+
ledger_api=get_default_ledger_api(chain),
|
|
308
|
+
contract_address=asset_address,
|
|
309
|
+
)
|
|
310
|
+
return erc20_token.functions.decimals().call()
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def format_asset_amount(
|
|
314
|
+
chain: Chain, asset_address: str, amount: int, precision: int = 4
|
|
315
|
+
) -> str:
|
|
316
|
+
"""Convert smallest unit to human-readable string with token symbol."""
|
|
317
|
+
decimals = get_asset_decimals(chain, asset_address)
|
|
318
|
+
symbol = get_asset_name(chain, asset_address)
|
|
319
|
+
value = amount / (10**decimals)
|
|
320
|
+
return f"{value:.{precision}f} {symbol}"
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
# TODO: Deprecate in favour of StakingManager method
|
|
256
324
|
def get_staking_contract(
|
|
257
325
|
chain: str, staking_program_id: t.Optional[str]
|
|
258
326
|
) -> t.Optional[str]:
|
operate/operate_types.py
CHANGED
|
@@ -19,16 +19,22 @@
|
|
|
19
19
|
|
|
20
20
|
"""Types module."""
|
|
21
21
|
|
|
22
|
+
import base64
|
|
23
|
+
import copy
|
|
22
24
|
import enum
|
|
23
25
|
import os
|
|
24
26
|
import typing as t
|
|
25
|
-
from dataclasses import dataclass
|
|
27
|
+
from dataclasses import dataclass, field
|
|
28
|
+
from pathlib import Path
|
|
26
29
|
|
|
30
|
+
import argon2
|
|
31
|
+
from aea_ledger_ethereum import cast
|
|
27
32
|
from autonomy.chain.config import ChainType
|
|
28
33
|
from autonomy.chain.constants import CHAIN_NAME_TO_CHAIN_ID
|
|
34
|
+
from cryptography.fernet import Fernet
|
|
29
35
|
from typing_extensions import TypedDict
|
|
30
36
|
|
|
31
|
-
from operate.constants import NO_STAKING_PROGRAM_ID
|
|
37
|
+
from operate.constants import FERNET_KEY_LENGTH, NO_STAKING_PROGRAM_ID
|
|
32
38
|
from operate.resource import LocalResource
|
|
33
39
|
|
|
34
40
|
|
|
@@ -55,6 +61,11 @@ class LedgerType(str, enum.Enum):
|
|
|
55
61
|
"""Key filename."""
|
|
56
62
|
return f"{self.name.lower()}.txt"
|
|
57
63
|
|
|
64
|
+
@property
|
|
65
|
+
def mnemonic_file(self) -> str:
|
|
66
|
+
"""Mnemonic filename."""
|
|
67
|
+
return f"{self.name.lower()}.mnemonic.json"
|
|
68
|
+
|
|
58
69
|
@classmethod
|
|
59
70
|
def from_id(cls, chain_id: int) -> "LedgerType":
|
|
60
71
|
"""Load from chain ID."""
|
|
@@ -323,3 +334,195 @@ class MechMarketplaceConfig:
|
|
|
323
334
|
mech_marketplace_address: str
|
|
324
335
|
priority_mech_address: str
|
|
325
336
|
priority_mech_service_id: int
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
class Version:
|
|
340
|
+
"""Version class."""
|
|
341
|
+
|
|
342
|
+
def __init__(self, version: str) -> None:
|
|
343
|
+
"""Initialize the version."""
|
|
344
|
+
version = version.strip()
|
|
345
|
+
version_parts = version.split(".") if version else []
|
|
346
|
+
self.major = int(version_parts[0]) if len(version_parts) > 0 else 0
|
|
347
|
+
self.minor = int(version_parts[1]) if len(version_parts) > 1 else 0
|
|
348
|
+
self.patch = int(version_parts[2]) if len(version_parts) > 2 else 0
|
|
349
|
+
|
|
350
|
+
def __str__(self) -> str:
|
|
351
|
+
"""String representation of the version."""
|
|
352
|
+
return f"{self.major}.{self.minor}.{self.patch}"
|
|
353
|
+
|
|
354
|
+
def __eq__(self, other: object) -> bool:
|
|
355
|
+
"""Equality comparison."""
|
|
356
|
+
if not isinstance(other, Version):
|
|
357
|
+
return NotImplemented
|
|
358
|
+
return (
|
|
359
|
+
self.major == other.major
|
|
360
|
+
and self.minor == other.minor
|
|
361
|
+
and self.patch == other.patch
|
|
362
|
+
)
|
|
363
|
+
|
|
364
|
+
def __lt__(self, other: "Version") -> bool:
|
|
365
|
+
"""Less than comparison."""
|
|
366
|
+
if self.major != other.major:
|
|
367
|
+
return self.major < other.major
|
|
368
|
+
if self.minor != other.minor:
|
|
369
|
+
return self.minor < other.minor
|
|
370
|
+
return self.patch < other.patch
|
|
371
|
+
|
|
372
|
+
|
|
373
|
+
class ChainAmounts(dict[str, dict[str, dict[str, int]]]):
|
|
374
|
+
"""
|
|
375
|
+
Class that represents chain amounts as a dictionary
|
|
376
|
+
|
|
377
|
+
The standard format follows the convention {chain: {address: {token: amount}}}
|
|
378
|
+
"""
|
|
379
|
+
|
|
380
|
+
@classmethod
|
|
381
|
+
def shortfalls(
|
|
382
|
+
cls, requirements: "ChainAmounts", balances: "ChainAmounts"
|
|
383
|
+
) -> "ChainAmounts":
|
|
384
|
+
"""Return the shortfalls between requirements and balances."""
|
|
385
|
+
result: dict[str, dict[str, dict[str, int]]] = {}
|
|
386
|
+
|
|
387
|
+
for chain, addresses in requirements.items():
|
|
388
|
+
for address, assets in addresses.items():
|
|
389
|
+
for asset, required_amount in assets.items():
|
|
390
|
+
available = balances.get(chain, {}).get(address, {}).get(asset, 0)
|
|
391
|
+
shortfall = max(required_amount - available, 0)
|
|
392
|
+
result.setdefault(chain, {}).setdefault(address, {})[
|
|
393
|
+
asset
|
|
394
|
+
] = shortfall
|
|
395
|
+
|
|
396
|
+
return cls(result)
|
|
397
|
+
|
|
398
|
+
@classmethod
|
|
399
|
+
def add(cls, *chainamounts: "ChainAmounts") -> "ChainAmounts":
|
|
400
|
+
"""Add multiple ChainAmounts"""
|
|
401
|
+
result: dict[str, dict[str, dict[str, int]]] = {}
|
|
402
|
+
|
|
403
|
+
for ca in chainamounts:
|
|
404
|
+
for chain, addresses in ca.items():
|
|
405
|
+
result_addresses = result.setdefault(chain, {})
|
|
406
|
+
for address, assets in addresses.items():
|
|
407
|
+
result_assets = result_addresses.setdefault(address, {})
|
|
408
|
+
for asset, amount in assets.items():
|
|
409
|
+
result_assets[asset] = result_assets.get(asset, 0) + amount
|
|
410
|
+
|
|
411
|
+
return cls(result)
|
|
412
|
+
|
|
413
|
+
def __add__(self, other: "ChainAmounts") -> "ChainAmounts":
|
|
414
|
+
"""Add two ChainAmounts"""
|
|
415
|
+
return ChainAmounts.add(self, other)
|
|
416
|
+
|
|
417
|
+
def __mul__(self, multiplier: float) -> "ChainAmounts":
|
|
418
|
+
"""Multiply all amounts by the specified multiplier"""
|
|
419
|
+
output = copy.deepcopy(self)
|
|
420
|
+
for _, addresses in output.items():
|
|
421
|
+
for _, balances in addresses.items():
|
|
422
|
+
for asset, amount in balances.items():
|
|
423
|
+
balances[asset] = int(amount * multiplier)
|
|
424
|
+
return output
|
|
425
|
+
|
|
426
|
+
def __sub__(self, other: "ChainAmounts") -> "ChainAmounts":
|
|
427
|
+
"""Subtract two ChainAmounts"""
|
|
428
|
+
return self + (other * -1)
|
|
429
|
+
|
|
430
|
+
def __floordiv__(self, divisor: float) -> "ChainAmounts":
|
|
431
|
+
"""Divide all amounts by the specified divisor"""
|
|
432
|
+
if divisor == 0:
|
|
433
|
+
raise ValueError("Cannot divide by zero")
|
|
434
|
+
|
|
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 // divisor)
|
|
440
|
+
return output
|
|
441
|
+
|
|
442
|
+
def __lt__(self, other: "ChainAmounts") -> bool:
|
|
443
|
+
"""Return True if all amounts in self are strictly less than the corresponding amounts in other."""
|
|
444
|
+
for chain, addresses in self.items():
|
|
445
|
+
for address, assets in addresses.items():
|
|
446
|
+
for asset, amount in assets.items():
|
|
447
|
+
if amount >= other.get(chain, {}).get(address, {}).get(asset, 0):
|
|
448
|
+
return False
|
|
449
|
+
return True
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
@dataclass
|
|
453
|
+
class EncryptedData(LocalResource):
|
|
454
|
+
"""EncryptedData type."""
|
|
455
|
+
|
|
456
|
+
path: Path
|
|
457
|
+
version: int
|
|
458
|
+
cipher: str
|
|
459
|
+
cipherparams: t.Dict[str, t.Union[int, str]] = field(repr=False)
|
|
460
|
+
ciphertext: str = field(repr=False)
|
|
461
|
+
kdf: str
|
|
462
|
+
kdfparams: t.Dict[str, t.Union[int, str]] = field(repr=False)
|
|
463
|
+
|
|
464
|
+
@classmethod
|
|
465
|
+
def new(cls, path: Path, password: str, plaintext_bytes: bytes) -> "EncryptedData":
|
|
466
|
+
"""Creates a new EncryptedData"""
|
|
467
|
+
ph = argon2.PasswordHasher()
|
|
468
|
+
salt = os.urandom(ph.salt_len)
|
|
469
|
+
time_cost = ph.time_cost
|
|
470
|
+
memory_cost = ph.memory_cost
|
|
471
|
+
parallelism = ph.parallelism
|
|
472
|
+
hash_len = FERNET_KEY_LENGTH
|
|
473
|
+
argon2_type = argon2.Type.ID
|
|
474
|
+
key = argon2.low_level.hash_secret_raw(
|
|
475
|
+
secret=password.encode(),
|
|
476
|
+
salt=salt,
|
|
477
|
+
time_cost=time_cost,
|
|
478
|
+
memory_cost=memory_cost,
|
|
479
|
+
parallelism=parallelism,
|
|
480
|
+
hash_len=hash_len,
|
|
481
|
+
type=argon2_type,
|
|
482
|
+
)
|
|
483
|
+
|
|
484
|
+
fernet_key = base64.urlsafe_b64encode(key)
|
|
485
|
+
fernet = Fernet(fernet_key)
|
|
486
|
+
ciphertext_bytes = fernet.encrypt(plaintext_bytes)
|
|
487
|
+
|
|
488
|
+
return cls(
|
|
489
|
+
path=path,
|
|
490
|
+
version=1,
|
|
491
|
+
cipher=f"{fernet.__class__.__module__}.{fernet.__class__.__qualname__}",
|
|
492
|
+
cipherparams={ # Fernet token (ciphertext variable) already stores them
|
|
493
|
+
"version": ciphertext_bytes[0]
|
|
494
|
+
},
|
|
495
|
+
ciphertext=ciphertext_bytes.hex(),
|
|
496
|
+
kdf=f"{ph.__class__.__module__}.{ph.__class__.__qualname__}",
|
|
497
|
+
kdfparams={
|
|
498
|
+
"salt": salt.hex(),
|
|
499
|
+
"time_cost": time_cost,
|
|
500
|
+
"memory_cost": memory_cost,
|
|
501
|
+
"parallelism": parallelism,
|
|
502
|
+
"hash_len": hash_len,
|
|
503
|
+
"type": argon2_type.name,
|
|
504
|
+
},
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
def decrypt(self, password: str) -> bytes:
|
|
508
|
+
"""Decrypts the EncryptedData"""
|
|
509
|
+
kdfparams = self.kdfparams
|
|
510
|
+
key = argon2.low_level.hash_secret_raw(
|
|
511
|
+
secret=password.encode(),
|
|
512
|
+
salt=bytes.fromhex(kdfparams["salt"]),
|
|
513
|
+
time_cost=kdfparams["time_cost"],
|
|
514
|
+
memory_cost=kdfparams["memory_cost"],
|
|
515
|
+
parallelism=kdfparams["parallelism"],
|
|
516
|
+
hash_len=kdfparams["hash_len"],
|
|
517
|
+
type=argon2.Type[kdfparams["type"]],
|
|
518
|
+
)
|
|
519
|
+
fernet_key = base64.urlsafe_b64encode(key)
|
|
520
|
+
fernet = Fernet(fernet_key)
|
|
521
|
+
ciphertext_bytes = bytes.fromhex(self.ciphertext)
|
|
522
|
+
plaintext_bytes = fernet.decrypt(ciphertext_bytes)
|
|
523
|
+
return plaintext_bytes
|
|
524
|
+
|
|
525
|
+
@classmethod
|
|
526
|
+
def load(cls, path: Path) -> "EncryptedData":
|
|
527
|
+
"""Load EncryptedData."""
|
|
528
|
+
return cast(EncryptedData, super().load(path))
|
|
@@ -690,7 +690,7 @@ def run_service(
|
|
|
690
690
|
)
|
|
691
691
|
|
|
692
692
|
print_section("Funding the service")
|
|
693
|
-
manager.
|
|
693
|
+
manager.funding_manager.fund_service_initial(service=service)
|
|
694
694
|
|
|
695
695
|
print_section("Deploying the service")
|
|
696
696
|
manager.deploy_service_locally(
|
operate/services/agent_runner.py
CHANGED
|
@@ -32,6 +32,8 @@ import requests
|
|
|
32
32
|
from aea.configurations.data_types import PublicId
|
|
33
33
|
from aea.helpers.logging import setup_logger
|
|
34
34
|
|
|
35
|
+
from operate.constants import AGENT_RUNNER_PREFIX
|
|
36
|
+
|
|
35
37
|
|
|
36
38
|
@dataclass
|
|
37
39
|
class AgentRelease:
|
|
@@ -65,10 +67,10 @@ class AgentRelease:
|
|
|
65
67
|
# list of agents releases supported
|
|
66
68
|
AGENTS_SUPPORTED = {
|
|
67
69
|
"valory/trader": AgentRelease(
|
|
68
|
-
owner="valory-xyz", repo="trader", release="v0.27.2-rc.
|
|
70
|
+
owner="valory-xyz", repo="trader", release="v0.27.2-1-rc.2"
|
|
69
71
|
),
|
|
70
72
|
"valory/optimus": AgentRelease(
|
|
71
|
-
owner="valory-xyz", repo="optimus", release="v0.0.
|
|
73
|
+
owner="valory-xyz", repo="optimus", release="v0.6.0-rc.1"
|
|
72
74
|
),
|
|
73
75
|
"dvilela/memeooorr": AgentRelease(
|
|
74
76
|
owner="valory-xyz", repo="meme-ooorr", release="v0.0.101"
|
|
@@ -101,7 +103,7 @@ class AgentRunnerManager:
|
|
|
101
103
|
else:
|
|
102
104
|
raise ValueError(f"unsupported arch: {platform.machine()}")
|
|
103
105
|
|
|
104
|
-
exec_name = f"
|
|
106
|
+
exec_name = f"{AGENT_RUNNER_PREFIX}_{os_name}_{arch}"
|
|
105
107
|
if platform.system() == "Windows":
|
|
106
108
|
exec_name += ".exe"
|
|
107
109
|
return exec_name
|
|
@@ -224,6 +224,9 @@ class BaseDeploymentRunner(AbstractDeploymentRunner, metaclass=ABCMeta):
|
|
|
224
224
|
)
|
|
225
225
|
|
|
226
226
|
self._run_aea_command("-s", "add-key", "ethereum", cwd=working_dir / "agent")
|
|
227
|
+
self._run_aea_command(
|
|
228
|
+
"-s", "add-key", "ethereum", "--connection", cwd=working_dir / "agent"
|
|
229
|
+
)
|
|
227
230
|
|
|
228
231
|
self._run_aea_command("-s", "issue-certificates", cwd=working_dir / "agent")
|
|
229
232
|
|