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/services/manage.py
CHANGED
|
@@ -19,67 +19,107 @@
|
|
|
19
19
|
# ------------------------------------------------------------------------------
|
|
20
20
|
"""Service manager."""
|
|
21
21
|
|
|
22
|
-
import
|
|
22
|
+
import json
|
|
23
23
|
import logging
|
|
24
|
+
import os
|
|
25
|
+
import time
|
|
24
26
|
import traceback
|
|
25
27
|
import typing as t
|
|
26
|
-
from
|
|
28
|
+
from collections import Counter, defaultdict
|
|
29
|
+
from contextlib import suppress
|
|
30
|
+
from http import HTTPStatus
|
|
27
31
|
from pathlib import Path
|
|
28
32
|
|
|
29
|
-
import
|
|
33
|
+
import requests
|
|
30
34
|
from aea.helpers.base import IPFSHash
|
|
31
|
-
from
|
|
35
|
+
from aea_ledger_ethereum import LedgerApi
|
|
32
36
|
from autonomy.chain.base import registry_contracts
|
|
33
|
-
|
|
34
|
-
from
|
|
35
|
-
|
|
36
|
-
from operate.
|
|
37
|
-
|
|
37
|
+
from autonomy.chain.config import CHAIN_PROFILES, ChainType
|
|
38
|
+
from autonomy.chain.metadata import IPFS_URI_PREFIX
|
|
39
|
+
|
|
40
|
+
from operate.constants import (
|
|
41
|
+
AGENT_LOG_DIR,
|
|
42
|
+
AGENT_LOG_ENV_VAR,
|
|
43
|
+
AGENT_PERSISTENT_STORAGE_DIR,
|
|
44
|
+
AGENT_PERSISTENT_STORAGE_ENV_VAR,
|
|
45
|
+
DEFAULT_TOPUP_THRESHOLD,
|
|
46
|
+
IPFS_ADDRESS,
|
|
47
|
+
MIN_AGENT_BOND,
|
|
48
|
+
MIN_SECURITY_DEPOSIT,
|
|
49
|
+
ZERO_ADDRESS,
|
|
50
|
+
)
|
|
51
|
+
from operate.data import DATA_DIR
|
|
52
|
+
from operate.data.contracts.mech_activity.contract import MechActivityContract
|
|
53
|
+
from operate.data.contracts.requester_activity_checker.contract import (
|
|
54
|
+
RequesterActivityCheckerContract,
|
|
55
|
+
)
|
|
56
|
+
from operate.keys import KeysManager
|
|
57
|
+
from operate.ledger import get_default_rpc
|
|
58
|
+
from operate.ledger.profiles import (
|
|
59
|
+
CONTRACTS,
|
|
60
|
+
DEFAULT_EOA_THRESHOLD,
|
|
61
|
+
DEFAULT_EOA_TOPUPS,
|
|
62
|
+
DEFAULT_PRIORITY_MECH,
|
|
63
|
+
OLAS,
|
|
64
|
+
WRAPPED_NATIVE_ASSET,
|
|
65
|
+
get_staking_contract,
|
|
66
|
+
)
|
|
67
|
+
from operate.operate_types import (
|
|
68
|
+
Chain,
|
|
69
|
+
ChainAmounts,
|
|
70
|
+
FundingValues,
|
|
71
|
+
LedgerConfig,
|
|
72
|
+
MechMarketplaceConfig,
|
|
73
|
+
OnChainState,
|
|
74
|
+
ServiceEnvProvisionType,
|
|
75
|
+
ServiceTemplate,
|
|
76
|
+
)
|
|
77
|
+
from operate.services.funding_manager import FundingManager
|
|
78
|
+
from operate.services.protocol import (
|
|
79
|
+
EthSafeTxBuilder,
|
|
80
|
+
OnChainManager,
|
|
81
|
+
StakingManager,
|
|
82
|
+
StakingState,
|
|
83
|
+
)
|
|
38
84
|
from operate.services.service import (
|
|
85
|
+
ChainConfig,
|
|
39
86
|
Deployment,
|
|
87
|
+
NON_EXISTENT_MULTISIG,
|
|
88
|
+
NON_EXISTENT_TOKEN,
|
|
40
89
|
OnChainData,
|
|
41
|
-
|
|
42
|
-
|
|
90
|
+
SERVICE_CONFIG_PREFIX,
|
|
91
|
+
SERVICE_CONFIG_VERSION,
|
|
43
92
|
Service,
|
|
44
93
|
)
|
|
45
|
-
from operate.
|
|
94
|
+
from operate.services.utils.mech import deploy_mech
|
|
95
|
+
from operate.utils.gnosis import (
|
|
96
|
+
get_asset_balance,
|
|
97
|
+
get_assets_balances,
|
|
98
|
+
transfer_erc20_from_safe,
|
|
99
|
+
)
|
|
100
|
+
from operate.wallet.master import InsufficientFundsException, MasterWalletManager
|
|
46
101
|
|
|
47
102
|
|
|
48
103
|
# pylint: disable=redefined-builtin
|
|
49
104
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
KEYS = "keys"
|
|
54
|
-
DEPLOYMENT = "deployment"
|
|
55
|
-
CONFIG = "config.json"
|
|
56
|
-
KEY = "master-key.txt"
|
|
57
|
-
KEYS_JSON = "keys.json"
|
|
58
|
-
DOCKER_COMPOSE_YAML = "docker-compose.yaml"
|
|
59
|
-
SERVICE_YAML = "service.yaml"
|
|
60
|
-
HTTP_OK = 200
|
|
61
|
-
|
|
105
|
+
# At the moment, we only support running one agent per service locally on a machine.
|
|
106
|
+
# If multiple agents are provided in the service.yaml file, only the 0th index config will be used.
|
|
107
|
+
NUM_LOCAL_AGENT_INSTANCES = 1
|
|
62
108
|
|
|
63
|
-
|
|
64
|
-
"""Check the service health"""
|
|
65
|
-
async with aiohttp.ClientSession() as session:
|
|
66
|
-
async with session.get("http://localhost:8716/healthcheck") as resp:
|
|
67
|
-
status = resp.status
|
|
68
|
-
response_json = await resp.json()
|
|
69
|
-
return status == HTTP_OK and response_json.get(
|
|
70
|
-
"is_transitioning_fast", False
|
|
71
|
-
)
|
|
109
|
+
RPC_SYNC_TIMEOUT = 15
|
|
72
110
|
|
|
73
111
|
|
|
74
112
|
class ServiceManager:
|
|
75
113
|
"""Service manager."""
|
|
76
114
|
|
|
77
|
-
def __init__(
|
|
115
|
+
def __init__( # pylint: disable=too-many-arguments
|
|
78
116
|
self,
|
|
79
117
|
path: Path,
|
|
80
118
|
keys_manager: KeysManager,
|
|
81
119
|
wallet_manager: MasterWalletManager,
|
|
82
|
-
|
|
120
|
+
funding_manager: FundingManager,
|
|
121
|
+
logger: logging.Logger,
|
|
122
|
+
skip_dependency_check: t.Optional[bool] = False,
|
|
83
123
|
) -> None:
|
|
84
124
|
"""
|
|
85
125
|
Initialze service manager
|
|
@@ -92,136 +132,274 @@ class ServiceManager:
|
|
|
92
132
|
self.path = path
|
|
93
133
|
self.keys_manager = keys_manager
|
|
94
134
|
self.wallet_manager = wallet_manager
|
|
95
|
-
self.
|
|
135
|
+
self.funding_manager = funding_manager
|
|
136
|
+
self.logger = logger
|
|
137
|
+
self.skip_depencency_check = skip_dependency_check
|
|
96
138
|
|
|
97
139
|
def setup(self) -> None:
|
|
98
140
|
"""Setup service manager."""
|
|
99
141
|
self.path.mkdir(exist_ok=True)
|
|
100
142
|
|
|
143
|
+
def get_all_service_ids(self) -> t.List[str]:
|
|
144
|
+
"""
|
|
145
|
+
Get all service ids.
|
|
146
|
+
|
|
147
|
+
:return: List of service ids.
|
|
148
|
+
"""
|
|
149
|
+
return [
|
|
150
|
+
path.name
|
|
151
|
+
for path in self.path.iterdir()
|
|
152
|
+
if path.is_dir() and path.name.startswith(SERVICE_CONFIG_PREFIX)
|
|
153
|
+
]
|
|
154
|
+
|
|
155
|
+
def get_all_services(self) -> t.Tuple[t.List[Service], bool]:
|
|
156
|
+
"""Get all services."""
|
|
157
|
+
services = []
|
|
158
|
+
success = True
|
|
159
|
+
for path in self.path.iterdir():
|
|
160
|
+
if not path.name.startswith(SERVICE_CONFIG_PREFIX):
|
|
161
|
+
continue
|
|
162
|
+
try:
|
|
163
|
+
service = Service.load(path=path)
|
|
164
|
+
if service.version != SERVICE_CONFIG_VERSION:
|
|
165
|
+
self.logger.warning(
|
|
166
|
+
f"Service {path.name} has an unsupported version: {service.version}."
|
|
167
|
+
)
|
|
168
|
+
success = False
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
services.append(service)
|
|
172
|
+
except Exception as e: # pylint: disable=broad-except
|
|
173
|
+
self.logger.error(
|
|
174
|
+
f"Failed to load service: {path.name}. Exception {e}: {traceback.format_exc()}"
|
|
175
|
+
)
|
|
176
|
+
success = False
|
|
177
|
+
|
|
178
|
+
return services, success
|
|
179
|
+
|
|
180
|
+
def validate_services(self) -> bool:
|
|
181
|
+
"""
|
|
182
|
+
Validate all services.
|
|
183
|
+
|
|
184
|
+
:return: True if all services are valid, False otherwise.
|
|
185
|
+
"""
|
|
186
|
+
_, success = self.get_all_services()
|
|
187
|
+
return success
|
|
188
|
+
|
|
101
189
|
@property
|
|
102
190
|
def json(self) -> t.List[t.Dict]:
|
|
103
191
|
"""Returns the list of available services."""
|
|
104
|
-
|
|
105
|
-
for
|
|
106
|
-
if not path.name.startswith("bafybei"):
|
|
107
|
-
continue
|
|
108
|
-
service = Service.load(path=path)
|
|
109
|
-
data.append(service.json)
|
|
110
|
-
return data
|
|
192
|
+
services, _ = self.get_all_services()
|
|
193
|
+
return [service.json for service in services]
|
|
111
194
|
|
|
112
|
-
def exists(self,
|
|
195
|
+
def exists(self, service_config_id: str) -> bool:
|
|
113
196
|
"""Check if service exists."""
|
|
114
|
-
return (self.path /
|
|
197
|
+
return (self.path / service_config_id).exists()
|
|
115
198
|
|
|
116
|
-
def get_on_chain_manager(self,
|
|
199
|
+
def get_on_chain_manager(self, ledger_config: LedgerConfig) -> OnChainManager:
|
|
117
200
|
"""Get OnChainManager instance."""
|
|
118
201
|
return OnChainManager(
|
|
119
|
-
rpc=
|
|
120
|
-
wallet=self.wallet_manager.load(
|
|
121
|
-
contracts=CONTRACTS[
|
|
202
|
+
rpc=ledger_config.rpc,
|
|
203
|
+
wallet=self.wallet_manager.load(ledger_config.chain.ledger_type),
|
|
204
|
+
contracts=CONTRACTS[ledger_config.chain],
|
|
205
|
+
chain_type=ChainType(ledger_config.chain.value),
|
|
122
206
|
)
|
|
123
207
|
|
|
124
|
-
def get_eth_safe_tx_builder(self,
|
|
208
|
+
def get_eth_safe_tx_builder(self, ledger_config: LedgerConfig) -> EthSafeTxBuilder:
|
|
125
209
|
"""Get EthSafeTxBuilder instance."""
|
|
126
210
|
return EthSafeTxBuilder(
|
|
127
|
-
rpc=
|
|
128
|
-
wallet=self.wallet_manager.load(
|
|
129
|
-
contracts=CONTRACTS[
|
|
211
|
+
rpc=ledger_config.rpc,
|
|
212
|
+
wallet=self.wallet_manager.load(ledger_config.chain.ledger_type),
|
|
213
|
+
contracts=CONTRACTS[ledger_config.chain],
|
|
214
|
+
chain_type=ChainType(ledger_config.chain.value),
|
|
130
215
|
)
|
|
131
216
|
|
|
132
|
-
def
|
|
217
|
+
def load_or_create(
|
|
133
218
|
self,
|
|
134
219
|
hash: str,
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
keys: t.Optional[t.List[Key]] = None,
|
|
220
|
+
service_template: t.Optional[ServiceTemplate] = None,
|
|
221
|
+
agent_addresses: t.Optional[t.List[str]] = None,
|
|
138
222
|
) -> Service:
|
|
139
223
|
"""
|
|
140
224
|
Create or load a service
|
|
141
225
|
|
|
142
226
|
:param hash: Service hash
|
|
143
|
-
:param
|
|
144
|
-
:param
|
|
145
|
-
:param keys: Keys
|
|
227
|
+
:param service_template: Service template
|
|
228
|
+
:param agent_addresses: Agents' addresses to be used for the service.
|
|
146
229
|
:return: Service instance
|
|
147
230
|
"""
|
|
148
231
|
path = self.path / hash
|
|
149
232
|
if path.exists():
|
|
150
|
-
|
|
233
|
+
service = Service.load(path=path)
|
|
234
|
+
|
|
235
|
+
if service_template is not None:
|
|
236
|
+
service.update_user_params_from_template(
|
|
237
|
+
service_template=service_template
|
|
238
|
+
)
|
|
151
239
|
|
|
152
|
-
|
|
153
|
-
raise ValueError("RPC cannot be None when creating a new service")
|
|
240
|
+
return service
|
|
154
241
|
|
|
155
|
-
if
|
|
242
|
+
if service_template is None:
|
|
156
243
|
raise ValueError(
|
|
157
|
-
"
|
|
244
|
+
"'service_template' cannot be None when creating a new service"
|
|
158
245
|
)
|
|
159
246
|
|
|
160
|
-
return
|
|
161
|
-
|
|
162
|
-
keys=keys or [],
|
|
163
|
-
rpc=rpc,
|
|
164
|
-
storage=self.path,
|
|
165
|
-
on_chain_user_params=on_chain_user_params,
|
|
247
|
+
return self.create(
|
|
248
|
+
service_template=service_template, agent_addresses=agent_addresses
|
|
166
249
|
)
|
|
167
250
|
|
|
168
|
-
def
|
|
251
|
+
def load(
|
|
169
252
|
self,
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
) -> None:
|
|
253
|
+
service_config_id: str,
|
|
254
|
+
) -> Service:
|
|
173
255
|
"""
|
|
174
|
-
|
|
256
|
+
Load a service
|
|
175
257
|
|
|
176
|
-
:param
|
|
177
|
-
:
|
|
258
|
+
:param service_id: Service id
|
|
259
|
+
:return: Service instance
|
|
178
260
|
"""
|
|
179
|
-
self.
|
|
180
|
-
|
|
181
|
-
user_params = service.chain_data.user_params
|
|
182
|
-
keys = service.keys or [
|
|
183
|
-
self.keys_manager.get(self.keys_manager.create())
|
|
184
|
-
for _ in range(service.helper.config.number_of_agents)
|
|
185
|
-
]
|
|
186
|
-
instances = [key.address for key in keys]
|
|
187
|
-
ocm = self.get_on_chain_manager(service=service)
|
|
188
|
-
if user_params.use_staking and not ocm.staking_slots_available(
|
|
189
|
-
staking_contract=STAKING[service.ledger_config.chain]
|
|
190
|
-
):
|
|
191
|
-
raise ValueError("No staking slots available")
|
|
261
|
+
path = self.path / service_config_id
|
|
262
|
+
return Service.load(path=path)
|
|
192
263
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
264
|
+
def create(
|
|
265
|
+
self,
|
|
266
|
+
service_template: ServiceTemplate,
|
|
267
|
+
agent_addresses: t.Optional[t.List[str]] = None,
|
|
268
|
+
) -> Service:
|
|
269
|
+
"""
|
|
270
|
+
Create a service
|
|
271
|
+
|
|
272
|
+
:param service_template: Service template
|
|
273
|
+
:param agent_addresses: Agents' addresses to be used for the service.
|
|
274
|
+
:return: Service instance
|
|
275
|
+
"""
|
|
276
|
+
service = Service.new(
|
|
277
|
+
agent_addresses=agent_addresses or [],
|
|
278
|
+
storage=self.path,
|
|
279
|
+
service_template=service_template,
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
if not service.agent_addresses:
|
|
283
|
+
service.agent_addresses = [
|
|
284
|
+
self.keys_manager.create() for _ in range(NUM_LOCAL_AGENT_INSTANCES)
|
|
285
|
+
]
|
|
286
|
+
service.store()
|
|
287
|
+
|
|
288
|
+
return service
|
|
289
|
+
|
|
290
|
+
def _get_on_chain_state(self, service: Service, chain: str) -> OnChainState:
|
|
291
|
+
chain_config = service.chain_configs[chain]
|
|
292
|
+
chain_data = chain_config.chain_data
|
|
293
|
+
ledger_config = chain_config.ledger_config
|
|
294
|
+
if chain_data.token == NON_EXISTENT_TOKEN:
|
|
295
|
+
return OnChainState.NON_EXISTENT
|
|
296
|
+
|
|
297
|
+
sftxb = self.get_eth_safe_tx_builder(ledger_config=ledger_config)
|
|
298
|
+
info = sftxb.info(token_id=chain_data.token)
|
|
299
|
+
return OnChainState(info["service_state"])
|
|
300
|
+
|
|
301
|
+
def _get_on_chain_metadata(self, chain_config: ChainConfig) -> t.Dict:
|
|
302
|
+
chain_data = chain_config.chain_data
|
|
303
|
+
ledger_config = chain_config.ledger_config
|
|
304
|
+
if chain_data.token == NON_EXISTENT_TOKEN:
|
|
305
|
+
return {}
|
|
306
|
+
|
|
307
|
+
sftxb = self.get_eth_safe_tx_builder(ledger_config=ledger_config)
|
|
308
|
+
info = sftxb.info(token_id=chain_data.token)
|
|
309
|
+
config_hash = info["config_hash"]
|
|
310
|
+
url = IPFS_ADDRESS.format(hash=config_hash)
|
|
311
|
+
self.logger.info(f"Fetching {url=}...")
|
|
312
|
+
res = requests.get(url, timeout=30)
|
|
313
|
+
if res.status_code == HTTPStatus.OK:
|
|
314
|
+
return res.json()
|
|
315
|
+
raise ValueError(
|
|
316
|
+
f"Something went wrong while trying to get the on-chain metadata from IPFS: {res}"
|
|
317
|
+
)
|
|
197
318
|
|
|
198
|
-
|
|
319
|
+
def deploy_service_onchain( # pylint: disable=too-many-statements,too-many-locals
|
|
320
|
+
self,
|
|
321
|
+
service_config_id: str,
|
|
322
|
+
) -> None:
|
|
323
|
+
"""Deploy service on-chain"""
|
|
324
|
+
# TODO This method has not been thoroughly reviewed. Deprecated usage in favour of Safe version.
|
|
325
|
+
|
|
326
|
+
service = self.load(service_config_id=service_config_id)
|
|
327
|
+
for chain in service.chain_configs.keys():
|
|
328
|
+
self._deploy_service_onchain(
|
|
329
|
+
service_config_id=service_config_id,
|
|
330
|
+
chain=chain,
|
|
331
|
+
)
|
|
332
|
+
|
|
333
|
+
def _deploy_service_onchain( # pylint: disable=too-many-statements,too-many-locals
|
|
334
|
+
self,
|
|
335
|
+
service_config_id: str,
|
|
336
|
+
chain: str,
|
|
337
|
+
) -> None:
|
|
338
|
+
"""Deploy as service on-chain"""
|
|
339
|
+
# TODO This method has not been thoroughly reviewed. Deprecated usage in favour of Safe version.
|
|
340
|
+
|
|
341
|
+
self.logger.info(f"_deploy_service_onchain {chain=}")
|
|
342
|
+
service = self.load(service_config_id=service_config_id)
|
|
343
|
+
chain_config = service.chain_configs[chain]
|
|
344
|
+
ledger_config = chain_config.ledger_config
|
|
345
|
+
chain_data = chain_config.chain_data
|
|
346
|
+
user_params = chain_config.chain_data.user_params
|
|
347
|
+
ocm = self.get_on_chain_manager(ledger_config=ledger_config)
|
|
348
|
+
|
|
349
|
+
# TODO fix this
|
|
350
|
+
os.environ["CUSTOM_CHAIN_RPC"] = ledger_config.rpc
|
|
351
|
+
|
|
352
|
+
current_agent_id = None
|
|
353
|
+
on_chain_state = OnChainState.NON_EXISTENT
|
|
354
|
+
if chain_data.token > -1:
|
|
199
355
|
self.logger.info("Syncing service state")
|
|
200
|
-
info = ocm.info(token_id=
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
356
|
+
info = ocm.info(token_id=chain_data.token)
|
|
357
|
+
on_chain_state = OnChainState(info["service_state"])
|
|
358
|
+
chain_data.instances = info["instances"]
|
|
359
|
+
chain_data.multisig = info["multisig"]
|
|
204
360
|
service.store()
|
|
205
|
-
self.logger.info(f"Service state: {
|
|
361
|
+
self.logger.info(f"Service state: {on_chain_state.name}")
|
|
362
|
+
|
|
363
|
+
if user_params.use_staking:
|
|
364
|
+
staking_params = ocm.get_staking_params(
|
|
365
|
+
staking_contract=get_staking_contract(
|
|
366
|
+
chain=ledger_config.chain,
|
|
367
|
+
staking_program_id=user_params.staking_program_id,
|
|
368
|
+
),
|
|
369
|
+
)
|
|
370
|
+
else: # TODO fix this - using pearl beta params
|
|
371
|
+
staking_params = dict( # nosec
|
|
372
|
+
agent_ids=[25],
|
|
373
|
+
service_registry="0x9338b5153AE39BB89f50468E608eD9d764B755fD", # nosec
|
|
374
|
+
staking_token="0xcE11e14225575945b8E6Dc0D4F2dD4C570f79d9f", # nosec
|
|
375
|
+
service_registry_token_utility="0xa45E64d13A30a51b91ae0eb182e88a40e9b18eD8", # nosec
|
|
376
|
+
min_staking_deposit=20000000000000000000,
|
|
377
|
+
activity_checker="0x155547857680A6D51bebC5603397488988DEb1c8", # nosec
|
|
378
|
+
)
|
|
206
379
|
|
|
207
380
|
if user_params.use_staking:
|
|
208
381
|
self.logger.info("Checking staking compatibility")
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
382
|
+
|
|
383
|
+
# TODO: Missing check when the service is currently staked in a program, but needs to be staked
|
|
384
|
+
# in a different target program. The In this case, balance = currently staked balance + safe balance
|
|
385
|
+
|
|
386
|
+
if on_chain_state in (
|
|
387
|
+
OnChainState.NON_EXISTENT,
|
|
388
|
+
OnChainState.PRE_REGISTRATION,
|
|
212
389
|
):
|
|
213
390
|
required_olas = (
|
|
214
|
-
|
|
391
|
+
staking_params["min_staking_deposit"]
|
|
392
|
+
+ staking_params["min_staking_deposit"] # bond = staking
|
|
215
393
|
)
|
|
216
|
-
elif
|
|
217
|
-
required_olas =
|
|
394
|
+
elif on_chain_state == OnChainState.ACTIVE_REGISTRATION:
|
|
395
|
+
required_olas = staking_params["min_staking_deposit"]
|
|
218
396
|
else:
|
|
219
397
|
required_olas = 0
|
|
220
398
|
|
|
221
399
|
balance = (
|
|
222
400
|
registry_contracts.erc20.get_instance(
|
|
223
401
|
ledger_api=ocm.ledger_api,
|
|
224
|
-
contract_address=OLAS[
|
|
402
|
+
contract_address=OLAS[ledger_config.chain],
|
|
225
403
|
)
|
|
226
404
|
.functions.balanceOf(ocm.crypto.address)
|
|
227
405
|
.call()
|
|
@@ -232,181 +410,547 @@ class ServiceManager:
|
|
|
232
410
|
f"required olas: {required_olas}; your balance {balance}"
|
|
233
411
|
)
|
|
234
412
|
|
|
235
|
-
|
|
413
|
+
on_chain_metadata = self._get_on_chain_metadata(chain_config=chain_config)
|
|
414
|
+
on_chain_hash = on_chain_metadata.get("code_uri", "")[len(IPFS_URI_PREFIX) :]
|
|
415
|
+
on_chain_description = on_chain_metadata.get("description")
|
|
416
|
+
|
|
417
|
+
current_agent_bond = staking_params[
|
|
418
|
+
"min_staking_deposit"
|
|
419
|
+
] # TODO fixme, read from service registry token utility contract
|
|
420
|
+
is_first_mint = (
|
|
421
|
+
self._get_on_chain_state(service=service, chain=chain)
|
|
422
|
+
== OnChainState.NON_EXISTENT
|
|
423
|
+
)
|
|
424
|
+
is_update = (
|
|
425
|
+
(not is_first_mint)
|
|
426
|
+
and (on_chain_hash is not None)
|
|
427
|
+
and (
|
|
428
|
+
on_chain_hash != service.hash
|
|
429
|
+
or current_agent_id != staking_params["agent_ids"][0]
|
|
430
|
+
or (
|
|
431
|
+
user_params.use_staking
|
|
432
|
+
and current_agent_bond != staking_params["min_staking_deposit"]
|
|
433
|
+
)
|
|
434
|
+
# TODO Missing complete this check for non-staked services it should compare the current_agent_bond from the protocol, now it's only read for the staking contract.
|
|
435
|
+
or on_chain_description != service.description
|
|
436
|
+
)
|
|
437
|
+
)
|
|
438
|
+
current_staking_program = self._get_current_staking_program(service, chain)
|
|
439
|
+
|
|
440
|
+
self.logger.info(f"{chain_data.token=}")
|
|
441
|
+
self.logger.info(f"{user_params.use_staking=}")
|
|
442
|
+
self.logger.info(f"{current_staking_program=}")
|
|
443
|
+
self.logger.info(f"{user_params.staking_program_id=}")
|
|
444
|
+
self.logger.info(f"{on_chain_hash=}")
|
|
445
|
+
self.logger.info(f"{service.hash=}")
|
|
446
|
+
self.logger.info(f"{current_agent_id=}")
|
|
447
|
+
self.logger.info(f"{staking_params['agent_ids']=}")
|
|
448
|
+
self.logger.info(f"{current_agent_bond=}")
|
|
449
|
+
self.logger.info(f"{staking_params['min_staking_deposit']=}")
|
|
450
|
+
self.logger.info(f"{is_first_mint=}")
|
|
451
|
+
self.logger.info(f"{is_update=}")
|
|
452
|
+
|
|
453
|
+
if on_chain_state == OnChainState.NON_EXISTENT:
|
|
236
454
|
self.logger.info("Minting service")
|
|
237
|
-
|
|
455
|
+
chain_data.token = t.cast(
|
|
238
456
|
int,
|
|
239
457
|
ocm.mint(
|
|
240
|
-
package_path=service.
|
|
241
|
-
agent_id=
|
|
242
|
-
number_of_slots=
|
|
458
|
+
package_path=service.package_absolute_path_absolute_path,
|
|
459
|
+
agent_id=staking_params["agent_ids"][0],
|
|
460
|
+
number_of_slots=NUM_LOCAL_AGENT_INSTANCES,
|
|
243
461
|
cost_of_bond=(
|
|
244
|
-
|
|
462
|
+
staking_params["min_staking_deposit"]
|
|
245
463
|
if user_params.use_staking
|
|
246
464
|
else user_params.cost_of_bond
|
|
247
465
|
),
|
|
248
|
-
threshold=
|
|
466
|
+
threshold=len(service.agent_addresses),
|
|
249
467
|
nft=IPFSHash(user_params.nft),
|
|
250
|
-
update_token=
|
|
468
|
+
update_token=chain_data.token if is_update else None,
|
|
251
469
|
token=(
|
|
252
|
-
OLAS[
|
|
253
|
-
if user_params.use_staking
|
|
254
|
-
else None
|
|
470
|
+
OLAS[ledger_config.chain] if user_params.use_staking else None
|
|
255
471
|
),
|
|
472
|
+
metadata_description=service.description,
|
|
473
|
+
skip_dependency_check=self.skip_depencency_check,
|
|
256
474
|
).get("token"),
|
|
257
475
|
)
|
|
258
|
-
|
|
476
|
+
on_chain_state = OnChainState.PRE_REGISTRATION
|
|
259
477
|
service.store()
|
|
260
478
|
|
|
261
|
-
info = ocm.info(token_id=
|
|
262
|
-
|
|
479
|
+
info = ocm.info(token_id=chain_data.token)
|
|
480
|
+
on_chain_state = OnChainState(info["service_state"])
|
|
263
481
|
|
|
264
|
-
if
|
|
482
|
+
if on_chain_state == OnChainState.PRE_REGISTRATION:
|
|
265
483
|
self.logger.info("Activating service")
|
|
266
484
|
ocm.activate(
|
|
267
|
-
service_id=
|
|
268
|
-
token=(
|
|
269
|
-
OLAS[service.ledger_config.chain]
|
|
270
|
-
if user_params.use_staking
|
|
271
|
-
else None
|
|
272
|
-
),
|
|
485
|
+
service_id=chain_data.token,
|
|
486
|
+
token=(OLAS[ledger_config.chain] if user_params.use_staking else None),
|
|
273
487
|
)
|
|
274
|
-
|
|
275
|
-
service.store()
|
|
488
|
+
on_chain_state = OnChainState.ACTIVE_REGISTRATION
|
|
276
489
|
|
|
277
|
-
info = ocm.info(token_id=
|
|
278
|
-
|
|
490
|
+
info = ocm.info(token_id=chain_data.token)
|
|
491
|
+
on_chain_state = OnChainState(info["service_state"])
|
|
279
492
|
|
|
280
|
-
if
|
|
281
|
-
self.logger.info("Registering
|
|
493
|
+
if on_chain_state == OnChainState.ACTIVE_REGISTRATION:
|
|
494
|
+
self.logger.info("Registering agent instances")
|
|
495
|
+
agent_id = staking_params["agent_ids"][0]
|
|
282
496
|
ocm.register(
|
|
283
|
-
service_id=
|
|
284
|
-
instances=
|
|
285
|
-
agents=[
|
|
286
|
-
token=(
|
|
287
|
-
OLAS[service.ledger_config.chain]
|
|
288
|
-
if user_params.use_staking
|
|
289
|
-
else None
|
|
290
|
-
),
|
|
497
|
+
service_id=chain_data.token,
|
|
498
|
+
instances=service.agent_addresses,
|
|
499
|
+
agents=[agent_id for _ in service.agent_addresses],
|
|
500
|
+
token=(OLAS[ledger_config.chain] if user_params.use_staking else None),
|
|
291
501
|
)
|
|
292
|
-
|
|
293
|
-
service.keys = keys
|
|
294
|
-
service.store()
|
|
502
|
+
on_chain_state = OnChainState.FINISHED_REGISTRATION
|
|
295
503
|
|
|
296
|
-
info = ocm.info(token_id=
|
|
297
|
-
|
|
504
|
+
info = ocm.info(token_id=chain_data.token)
|
|
505
|
+
on_chain_state = OnChainState(info["service_state"])
|
|
298
506
|
|
|
299
|
-
if
|
|
507
|
+
if on_chain_state == OnChainState.FINISHED_REGISTRATION:
|
|
300
508
|
self.logger.info("Deploying service")
|
|
301
509
|
ocm.deploy(
|
|
302
|
-
service_id=
|
|
303
|
-
reuse_multisig=
|
|
304
|
-
token=(
|
|
305
|
-
OLAS[service.ledger_config.chain]
|
|
306
|
-
if user_params.use_staking
|
|
307
|
-
else None
|
|
308
|
-
),
|
|
510
|
+
service_id=chain_data.token,
|
|
511
|
+
reuse_multisig=is_update,
|
|
512
|
+
token=(OLAS[ledger_config.chain] if user_params.use_staking else None),
|
|
309
513
|
)
|
|
310
|
-
|
|
311
|
-
service.store()
|
|
514
|
+
on_chain_state = OnChainState.DEPLOYED
|
|
312
515
|
|
|
313
|
-
info = ocm.info(token_id=
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
token=service.chain_data.token,
|
|
516
|
+
info = ocm.info(token_id=chain_data.token)
|
|
517
|
+
chain_data = OnChainData(
|
|
518
|
+
token=chain_data.token,
|
|
317
519
|
instances=info["instances"],
|
|
318
520
|
multisig=info["multisig"],
|
|
319
|
-
|
|
320
|
-
on_chain_state=service.chain_data.on_chain_state,
|
|
321
|
-
user_params=service.chain_data.user_params,
|
|
521
|
+
user_params=chain_data.user_params,
|
|
322
522
|
)
|
|
323
523
|
service.store()
|
|
324
524
|
|
|
325
525
|
def deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,too-many-locals
|
|
326
526
|
self,
|
|
327
|
-
|
|
328
|
-
update: bool = False,
|
|
527
|
+
service_config_id: str,
|
|
329
528
|
) -> None:
|
|
330
|
-
"""
|
|
331
|
-
Deploy as service on-chain
|
|
529
|
+
"""Deploy as service on-chain"""
|
|
332
530
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
531
|
+
service = self.load(service_config_id=service_config_id)
|
|
532
|
+
for chain in service.chain_configs.keys():
|
|
533
|
+
self._deploy_service_onchain_from_safe(
|
|
534
|
+
service_config_id=service_config_id,
|
|
535
|
+
chain=chain,
|
|
536
|
+
)
|
|
537
|
+
|
|
538
|
+
def get_mech_configs(
|
|
539
|
+
self,
|
|
540
|
+
chain: str,
|
|
541
|
+
ledger_api: LedgerApi,
|
|
542
|
+
staking_program_id: str | None = None,
|
|
543
|
+
) -> MechMarketplaceConfig:
|
|
544
|
+
"""Get the mech configs."""
|
|
545
|
+
sftxb = self.get_eth_safe_tx_builder(
|
|
546
|
+
ledger_config=LedgerConfig(
|
|
547
|
+
chain=Chain(chain),
|
|
548
|
+
rpc=ledger_api.api.provider.endpoint_uri,
|
|
549
|
+
)
|
|
550
|
+
)
|
|
551
|
+
staking_contract = get_staking_contract(
|
|
552
|
+
chain=chain,
|
|
553
|
+
staking_program_id=staking_program_id,
|
|
554
|
+
)
|
|
555
|
+
if staking_contract is None:
|
|
556
|
+
return MechMarketplaceConfig(
|
|
557
|
+
use_mech_marketplace=False,
|
|
558
|
+
mech_marketplace_address=ZERO_ADDRESS,
|
|
559
|
+
priority_mech_address=ZERO_ADDRESS,
|
|
560
|
+
priority_mech_service_id=0,
|
|
561
|
+
)
|
|
562
|
+
|
|
563
|
+
target_staking_params = sftxb.get_staking_params(
|
|
564
|
+
staking_contract=get_staking_contract(
|
|
565
|
+
chain=chain,
|
|
566
|
+
staking_program_id=staking_program_id,
|
|
567
|
+
),
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
try:
|
|
571
|
+
# Try if activity checker is a MechActivityChecker contract
|
|
572
|
+
mech_activity_contract = t.cast(
|
|
573
|
+
MechActivityContract,
|
|
574
|
+
MechActivityContract.from_dir(
|
|
575
|
+
directory=str(DATA_DIR / "contracts" / "mech_activity")
|
|
576
|
+
),
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
priority_mech_address = (
|
|
580
|
+
mech_activity_contract.get_instance(
|
|
581
|
+
ledger_api=ledger_api,
|
|
582
|
+
contract_address=target_staking_params["activity_checker"],
|
|
583
|
+
)
|
|
584
|
+
.functions.agentMech()
|
|
585
|
+
.call()
|
|
586
|
+
)
|
|
587
|
+
use_mech_marketplace = False
|
|
588
|
+
mech_marketplace_address = ZERO_ADDRESS
|
|
589
|
+
priority_mech_service_id = 0
|
|
590
|
+
|
|
591
|
+
except Exception: # pylint: disable=broad-except
|
|
592
|
+
# Try if activity checker is a RequesterActivityChecker contract
|
|
593
|
+
try:
|
|
594
|
+
requester_activity_checker = t.cast(
|
|
595
|
+
RequesterActivityCheckerContract,
|
|
596
|
+
RequesterActivityCheckerContract.from_dir(
|
|
597
|
+
directory=str(
|
|
598
|
+
DATA_DIR / "contracts" / "requester_activity_checker"
|
|
599
|
+
)
|
|
600
|
+
),
|
|
601
|
+
)
|
|
602
|
+
|
|
603
|
+
mech_marketplace_address = (
|
|
604
|
+
requester_activity_checker.get_instance(
|
|
605
|
+
ledger_api=ledger_api,
|
|
606
|
+
contract_address=target_staking_params["activity_checker"],
|
|
607
|
+
)
|
|
608
|
+
.functions.mechMarketplace()
|
|
609
|
+
.call()
|
|
610
|
+
)
|
|
350
611
|
|
|
351
|
-
|
|
612
|
+
use_mech_marketplace = True
|
|
613
|
+
priority_mech_address, priority_mech_service_id = DEFAULT_PRIORITY_MECH[
|
|
614
|
+
mech_marketplace_address
|
|
615
|
+
]
|
|
616
|
+
|
|
617
|
+
except Exception as e: # pylint: disable=broad-except
|
|
618
|
+
self.logger.debug(f"{e}: {traceback.format_exc()}")
|
|
619
|
+
self.logger.warning(
|
|
620
|
+
"Cannot determine type of activity checker contract. Using default parameters. "
|
|
621
|
+
"NOTE: This will be an exception in the future!"
|
|
622
|
+
)
|
|
623
|
+
priority_mech_address = "0x77af31De935740567Cf4fF1986D04B2c964A786a"
|
|
624
|
+
use_mech_marketplace = False
|
|
625
|
+
mech_marketplace_address = ZERO_ADDRESS
|
|
626
|
+
priority_mech_service_id = 0
|
|
627
|
+
|
|
628
|
+
return MechMarketplaceConfig(
|
|
629
|
+
use_mech_marketplace=use_mech_marketplace,
|
|
630
|
+
mech_marketplace_address=mech_marketplace_address,
|
|
631
|
+
priority_mech_address=priority_mech_address,
|
|
632
|
+
priority_mech_service_id=priority_mech_service_id,
|
|
633
|
+
)
|
|
634
|
+
|
|
635
|
+
def _deploy_service_onchain_from_safe( # pylint: disable=too-many-statements,too-many-locals
|
|
636
|
+
self,
|
|
637
|
+
service_config_id: str,
|
|
638
|
+
chain: str,
|
|
639
|
+
) -> None:
|
|
640
|
+
"""Deploy service on-chain"""
|
|
641
|
+
|
|
642
|
+
self.logger.info(f"_deploy_service_onchain_from_safe {chain=}")
|
|
643
|
+
service = self.load(service_config_id=service_config_id)
|
|
644
|
+
service.remove_latest_healthcheck()
|
|
645
|
+
chain_config = service.chain_configs[chain]
|
|
646
|
+
ledger_config = chain_config.ledger_config
|
|
647
|
+
chain_data = chain_config.chain_data
|
|
648
|
+
user_params = chain_config.chain_data.user_params
|
|
649
|
+
wallet = self.wallet_manager.load(ledger_config.chain.ledger_type)
|
|
650
|
+
sftxb = self.get_eth_safe_tx_builder(ledger_config=ledger_config)
|
|
651
|
+
safe = wallet.safes[Chain(chain)]
|
|
652
|
+
|
|
653
|
+
# TODO fix this
|
|
654
|
+
os.environ["CUSTOM_CHAIN_RPC"] = ledger_config.rpc
|
|
655
|
+
|
|
656
|
+
self._enable_recovery_module(service_config_id=service_config_id, chain=chain)
|
|
657
|
+
|
|
658
|
+
current_agent_id = None
|
|
659
|
+
on_chain_state = OnChainState.NON_EXISTENT
|
|
660
|
+
if chain_data.token > -1:
|
|
352
661
|
self.logger.info("Syncing service state")
|
|
353
|
-
info = sftxb.info(token_id=
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
662
|
+
info = sftxb.info(token_id=chain_data.token)
|
|
663
|
+
on_chain_state = OnChainState(info["service_state"])
|
|
664
|
+
chain_data.instances = info["instances"]
|
|
665
|
+
chain_data.multisig = info["multisig"]
|
|
666
|
+
current_agent_id = info["canonical_agents"][0] # TODO Allow multiple agents
|
|
357
667
|
service.store()
|
|
358
|
-
self.logger.info(f"Service state: {
|
|
668
|
+
self.logger.info(f"Service state: {on_chain_state.name}")
|
|
669
|
+
|
|
670
|
+
current_staking_program = self._get_current_staking_program(service, chain)
|
|
671
|
+
fallback_params = dict( # nosec
|
|
672
|
+
staking_contract=ZERO_ADDRESS,
|
|
673
|
+
agent_ids=[user_params.agent_id],
|
|
674
|
+
service_registry="0x9338b5153AE39BB89f50468E608eD9d764B755fD", # nosec
|
|
675
|
+
staking_token=ZERO_ADDRESS, # nosec
|
|
676
|
+
service_registry_token_utility="0xa45E64d13A30a51b91ae0eb182e88a40e9b18eD8", # nosec
|
|
677
|
+
min_staking_deposit=20000000000000000000,
|
|
678
|
+
activity_checker=ZERO_ADDRESS, # nosec
|
|
679
|
+
)
|
|
680
|
+
|
|
681
|
+
current_staking_params = sftxb.get_staking_params(
|
|
682
|
+
fallback_params=fallback_params,
|
|
683
|
+
staking_contract=get_staking_contract(
|
|
684
|
+
chain=ledger_config.chain,
|
|
685
|
+
staking_program_id=current_staking_program,
|
|
686
|
+
),
|
|
687
|
+
)
|
|
688
|
+
target_staking_params = sftxb.get_staking_params(
|
|
689
|
+
fallback_params=fallback_params,
|
|
690
|
+
staking_contract=get_staking_contract(
|
|
691
|
+
chain=ledger_config.chain,
|
|
692
|
+
staking_program_id=user_params.staking_program_id,
|
|
693
|
+
),
|
|
694
|
+
)
|
|
695
|
+
|
|
696
|
+
# TODO A customized, arbitrary computation mechanism should be devised.
|
|
697
|
+
env_var_to_value = {}
|
|
698
|
+
if chain == service.home_chain:
|
|
699
|
+
mech_configs: MechMarketplaceConfig = self.get_mech_configs(
|
|
700
|
+
chain=chain,
|
|
701
|
+
ledger_api=sftxb.ledger_api,
|
|
702
|
+
staking_program_id=user_params.staking_program_id,
|
|
703
|
+
)
|
|
704
|
+
|
|
705
|
+
if (
|
|
706
|
+
"PRIORITY_MECH_ADDRESS" in service.env_variables
|
|
707
|
+
and service.env_variables["PRIORITY_MECH_ADDRESS"]["provision_type"]
|
|
708
|
+
== ServiceEnvProvisionType.USER
|
|
709
|
+
):
|
|
710
|
+
mech_configs.priority_mech_address = service.env_variables[
|
|
711
|
+
"PRIORITY_MECH_ADDRESS"
|
|
712
|
+
]["value"]
|
|
713
|
+
|
|
714
|
+
if (
|
|
715
|
+
"PRIORITY_MECH_SERVICE_ID" in service.env_variables
|
|
716
|
+
and service.env_variables["PRIORITY_MECH_SERVICE_ID"]["provision_type"]
|
|
717
|
+
== ServiceEnvProvisionType.USER
|
|
718
|
+
):
|
|
719
|
+
mech_configs.priority_mech_service_id = service.env_variables[
|
|
720
|
+
"PRIORITY_MECH_SERVICE_ID"
|
|
721
|
+
]["value"]
|
|
722
|
+
|
|
723
|
+
env_var_to_value.update(
|
|
724
|
+
{
|
|
725
|
+
"ARBITRUM_ONE_LEDGER_RPC": get_default_rpc(Chain.ARBITRUM_ONE),
|
|
726
|
+
"BASE_LEDGER_RPC": get_default_rpc(Chain.BASE),
|
|
727
|
+
"CELO_LEDGER_RPC": get_default_rpc(Chain.CELO),
|
|
728
|
+
"ETHEREUM_LEDGER_RPC": get_default_rpc(Chain.ETHEREUM),
|
|
729
|
+
"GNOSIS_LEDGER_RPC": get_default_rpc(Chain.GNOSIS),
|
|
730
|
+
"MODE_LEDGER_RPC": get_default_rpc(Chain.MODE),
|
|
731
|
+
"OPTIMISM_LEDGER_RPC": get_default_rpc(Chain.OPTIMISM),
|
|
732
|
+
"POLYGON_LEDGER_RPC": get_default_rpc(Chain.POLYGON),
|
|
733
|
+
"SOLANA_LEDGER_RPC": get_default_rpc(Chain.SOLANA),
|
|
734
|
+
f"{chain.upper()}_LEDGER_RPC": ledger_config.rpc,
|
|
735
|
+
"STAKING_CONTRACT_ADDRESS": target_staking_params.get(
|
|
736
|
+
"staking_contract"
|
|
737
|
+
),
|
|
738
|
+
"STAKING_TOKEN_CONTRACT_ADDRESS": target_staking_params.get(
|
|
739
|
+
"staking_contract"
|
|
740
|
+
),
|
|
741
|
+
"MECH_MARKETPLACE_CONFIG": (
|
|
742
|
+
f'{{"mech_marketplace_address":"{mech_configs.mech_marketplace_address}",'
|
|
743
|
+
f'"priority_mech_address":"{mech_configs.priority_mech_address}",'
|
|
744
|
+
f'"priority_mech_staking_instance_address":"0x998dEFafD094817EF329f6dc79c703f1CF18bC90",'
|
|
745
|
+
f'"priority_mech_service_id":{mech_configs.priority_mech_service_id},'
|
|
746
|
+
f'"requester_staking_instance_address":"{target_staking_params.get("staking_contract")}",'
|
|
747
|
+
f'"response_timeout":300}}'
|
|
748
|
+
),
|
|
749
|
+
"ACTIVITY_CHECKER_CONTRACT_ADDRESS": target_staking_params.get(
|
|
750
|
+
"activity_checker"
|
|
751
|
+
),
|
|
752
|
+
"MECH_ACTIVITY_CHECKER_CONTRACT": target_staking_params.get(
|
|
753
|
+
"activity_checker"
|
|
754
|
+
),
|
|
755
|
+
"MECH_CONTRACT_ADDRESS": mech_configs.priority_mech_address,
|
|
756
|
+
"MECH_REQUEST_PRICE": "10000000000000000",
|
|
757
|
+
"USE_MECH_MARKETPLACE": mech_configs.use_mech_marketplace,
|
|
758
|
+
}
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
# Set environment variables for the service
|
|
762
|
+
for dir_name, env_var_name in (
|
|
763
|
+
(AGENT_PERSISTENT_STORAGE_DIR, AGENT_PERSISTENT_STORAGE_ENV_VAR),
|
|
764
|
+
(AGENT_LOG_DIR, AGENT_LOG_ENV_VAR),
|
|
765
|
+
):
|
|
766
|
+
dir_path = service.path / dir_name
|
|
767
|
+
dir_path.mkdir(parents=True, exist_ok=True)
|
|
768
|
+
env_var_to_value.update({env_var_name: str(dir_path)})
|
|
769
|
+
|
|
770
|
+
service.update_env_variables_values(env_var_to_value)
|
|
359
771
|
|
|
360
772
|
if user_params.use_staking:
|
|
361
773
|
self.logger.info("Checking staking compatibility")
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
774
|
+
|
|
775
|
+
# TODO: Missing check when the service is currently staked in a program, but needs to be staked
|
|
776
|
+
# in a different target program. The In this case, balance = currently staked balance + safe balance
|
|
777
|
+
|
|
778
|
+
if on_chain_state in (
|
|
779
|
+
OnChainState.NON_EXISTENT,
|
|
780
|
+
OnChainState.PRE_REGISTRATION,
|
|
365
781
|
):
|
|
366
|
-
|
|
367
|
-
|
|
782
|
+
protocol_asset_requirements = self._compute_protocol_asset_requirements(
|
|
783
|
+
service_config_id, chain
|
|
784
|
+
)
|
|
785
|
+
elif on_chain_state == OnChainState.ACTIVE_REGISTRATION:
|
|
786
|
+
protocol_asset_requirements = self._compute_protocol_asset_requirements(
|
|
787
|
+
service_config_id, chain
|
|
788
|
+
)
|
|
789
|
+
protocol_asset_requirements[target_staking_params["staking_token"]] = (
|
|
790
|
+
target_staking_params["min_staking_deposit"]
|
|
791
|
+
* NUM_LOCAL_AGENT_INSTANCES
|
|
368
792
|
)
|
|
369
|
-
elif service.chain_data.on_chain_state == OnChainState.ACTIVATED:
|
|
370
|
-
required_olas = user_params.olas_required_to_stake
|
|
371
793
|
else:
|
|
372
|
-
|
|
794
|
+
protocol_asset_requirements = {}
|
|
373
795
|
|
|
374
|
-
|
|
375
|
-
|
|
796
|
+
for asset, amount in protocol_asset_requirements.items():
|
|
797
|
+
balance = get_asset_balance(
|
|
376
798
|
ledger_api=sftxb.ledger_api,
|
|
377
|
-
|
|
799
|
+
asset_address=asset,
|
|
800
|
+
address=safe,
|
|
378
801
|
)
|
|
379
|
-
|
|
380
|
-
|
|
802
|
+
if balance < amount:
|
|
803
|
+
raise ValueError(
|
|
804
|
+
f"Address {safe} has insufficient balance for asset {asset}: "
|
|
805
|
+
f"required {amount}, available {balance}."
|
|
806
|
+
)
|
|
807
|
+
|
|
808
|
+
# TODO Handle this in a more graceful way.
|
|
809
|
+
agent_id = (
|
|
810
|
+
target_staking_params["agent_ids"][0]
|
|
811
|
+
if target_staking_params["agent_ids"]
|
|
812
|
+
else user_params.agent_id
|
|
813
|
+
)
|
|
814
|
+
target_staking_params["agent_ids"] = [agent_id]
|
|
815
|
+
|
|
816
|
+
on_chain_metadata = self._get_on_chain_metadata(chain_config=chain_config)
|
|
817
|
+
on_chain_hash = on_chain_metadata.get("code_uri", "")[len(IPFS_URI_PREFIX) :]
|
|
818
|
+
on_chain_description = on_chain_metadata.get("description")
|
|
819
|
+
needs_update_agent_addresses = set(chain_data.instances) != set(
|
|
820
|
+
service.agent_addresses
|
|
821
|
+
)
|
|
822
|
+
|
|
823
|
+
current_agent_bond = sftxb.get_agent_bond(
|
|
824
|
+
service_id=chain_data.token, agent_id=target_staking_params["agent_ids"][0]
|
|
825
|
+
)
|
|
826
|
+
|
|
827
|
+
is_first_mint = (
|
|
828
|
+
self._get_on_chain_state(service=service, chain=chain)
|
|
829
|
+
== OnChainState.NON_EXISTENT
|
|
830
|
+
)
|
|
831
|
+
current_staking_program = self._get_current_staking_program(service, chain)
|
|
832
|
+
|
|
833
|
+
is_update = (
|
|
834
|
+
(not is_first_mint)
|
|
835
|
+
and (on_chain_hash is not None)
|
|
836
|
+
and (
|
|
837
|
+
# TODO Discuss how to manage on-chain hash updates with staking programs.
|
|
838
|
+
# on_chain_hash != service.hash or # noqa
|
|
839
|
+
current_agent_id != target_staking_params["agent_ids"][0]
|
|
840
|
+
# TODO This has to be removed for Optimus (needs to be properly implemented). Needs to be put back for Trader!
|
|
841
|
+
or (
|
|
842
|
+
user_params.use_staking
|
|
843
|
+
and current_agent_bond
|
|
844
|
+
!= target_staking_params["min_staking_deposit"]
|
|
845
|
+
)
|
|
846
|
+
# TODO Missing complete this check for non-staked services it should compare the current_agent_bond from the protocol, now it's only read for the staking contract.
|
|
847
|
+
or current_staking_params["staking_token"]
|
|
848
|
+
!= target_staking_params["staking_token"]
|
|
849
|
+
or on_chain_description != service.description
|
|
850
|
+
or needs_update_agent_addresses
|
|
381
851
|
)
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
852
|
+
)
|
|
853
|
+
|
|
854
|
+
self.logger.info(f"{chain_data.token=}")
|
|
855
|
+
self.logger.info(f"{user_params.use_staking=}")
|
|
856
|
+
self.logger.info(f"{current_staking_program=}")
|
|
857
|
+
self.logger.info(f"{user_params.staking_program_id=}")
|
|
858
|
+
self.logger.info(f"{on_chain_hash=}")
|
|
859
|
+
self.logger.info(f"{service.hash=}")
|
|
860
|
+
self.logger.info(f"{current_agent_id=}")
|
|
861
|
+
self.logger.info(f"{target_staking_params['agent_ids']=}")
|
|
862
|
+
self.logger.info(f"{current_agent_bond=}")
|
|
863
|
+
self.logger.info(f"{target_staking_params['min_staking_deposit']=}")
|
|
864
|
+
self.logger.info(f"{is_first_mint=}")
|
|
865
|
+
self.logger.info(f"{is_update=}")
|
|
866
|
+
|
|
867
|
+
if is_update:
|
|
868
|
+
self.terminate_service_on_chain_from_safe(
|
|
869
|
+
service_config_id=service_config_id, chain=chain
|
|
870
|
+
)
|
|
871
|
+
# Update service
|
|
872
|
+
if (
|
|
873
|
+
self._get_on_chain_state(service=service, chain=chain)
|
|
874
|
+
== OnChainState.PRE_REGISTRATION
|
|
875
|
+
):
|
|
876
|
+
self.logger.info("Execute recovery module operations")
|
|
877
|
+
self._execute_recovery_module_flow_from_safe(
|
|
878
|
+
service_config_id=service_config_id, chain=chain
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
self.logger.info("Updating service")
|
|
882
|
+
receipt = (
|
|
883
|
+
sftxb.new_tx()
|
|
884
|
+
.add(
|
|
885
|
+
sftxb.get_mint_tx_data(
|
|
886
|
+
package_path=service.package_absolute_path,
|
|
887
|
+
agent_id=agent_id,
|
|
888
|
+
number_of_slots=NUM_LOCAL_AGENT_INSTANCES,
|
|
889
|
+
cost_of_bond=(
|
|
890
|
+
target_staking_params["min_staking_deposit"]
|
|
891
|
+
if user_params.use_staking
|
|
892
|
+
else user_params.cost_of_bond
|
|
893
|
+
),
|
|
894
|
+
threshold=len(service.agent_addresses),
|
|
895
|
+
nft=IPFSHash(user_params.nft),
|
|
896
|
+
update_token=chain_data.token,
|
|
897
|
+
token=(
|
|
898
|
+
target_staking_params["staking_token"]
|
|
899
|
+
if user_params.use_staking
|
|
900
|
+
else None
|
|
901
|
+
),
|
|
902
|
+
metadata_description=service.description,
|
|
903
|
+
skip_depencency_check=self.skip_depencency_check,
|
|
904
|
+
)
|
|
905
|
+
)
|
|
906
|
+
.settle()
|
|
907
|
+
)
|
|
908
|
+
event_data, *_ = t.cast(
|
|
909
|
+
t.Tuple,
|
|
910
|
+
registry_contracts.service_registry.process_receipt(
|
|
911
|
+
ledger_api=sftxb.ledger_api,
|
|
912
|
+
contract_address=target_staking_params["service_registry"],
|
|
913
|
+
event="UpdateService",
|
|
914
|
+
receipt=receipt,
|
|
915
|
+
).get("events"),
|
|
386
916
|
)
|
|
387
917
|
|
|
388
|
-
|
|
918
|
+
# Mint service
|
|
919
|
+
if (
|
|
920
|
+
self._get_on_chain_state(service=service, chain=chain)
|
|
921
|
+
== OnChainState.NON_EXISTENT
|
|
922
|
+
):
|
|
923
|
+
if user_params.use_staking and not sftxb.staking_slots_available(
|
|
924
|
+
staking_contract=get_staking_contract(
|
|
925
|
+
chain=ledger_config.chain,
|
|
926
|
+
staking_program_id=user_params.staking_program_id,
|
|
927
|
+
),
|
|
928
|
+
):
|
|
929
|
+
raise ValueError("No staking slots available")
|
|
930
|
+
|
|
389
931
|
self.logger.info("Minting service")
|
|
390
932
|
receipt = (
|
|
391
933
|
sftxb.new_tx()
|
|
392
934
|
.add(
|
|
393
935
|
sftxb.get_mint_tx_data(
|
|
394
|
-
package_path=service.
|
|
395
|
-
agent_id=
|
|
396
|
-
number_of_slots=
|
|
936
|
+
package_path=service.package_absolute_path,
|
|
937
|
+
agent_id=agent_id,
|
|
938
|
+
number_of_slots=NUM_LOCAL_AGENT_INSTANCES,
|
|
397
939
|
cost_of_bond=(
|
|
398
|
-
|
|
940
|
+
target_staking_params["min_staking_deposit"]
|
|
399
941
|
if user_params.use_staking
|
|
400
942
|
else user_params.cost_of_bond
|
|
401
943
|
),
|
|
402
|
-
threshold=
|
|
944
|
+
threshold=len(service.agent_addresses),
|
|
403
945
|
nft=IPFSHash(user_params.nft),
|
|
404
|
-
update_token=
|
|
946
|
+
update_token=None,
|
|
405
947
|
token=(
|
|
406
|
-
|
|
948
|
+
target_staking_params["staking_token"]
|
|
407
949
|
if user_params.use_staking
|
|
408
950
|
else None
|
|
409
951
|
),
|
|
952
|
+
metadata_description=service.description,
|
|
953
|
+
skip_depencency_check=self.skip_depencency_check,
|
|
410
954
|
)
|
|
411
955
|
)
|
|
412
956
|
.settle()
|
|
@@ -415,39 +959,42 @@ class ServiceManager:
|
|
|
415
959
|
t.Tuple,
|
|
416
960
|
registry_contracts.service_registry.process_receipt(
|
|
417
961
|
ledger_api=sftxb.ledger_api,
|
|
418
|
-
contract_address="
|
|
962
|
+
contract_address=target_staking_params["service_registry"],
|
|
419
963
|
event="CreateService",
|
|
420
964
|
receipt=receipt,
|
|
421
965
|
).get("events"),
|
|
422
966
|
)
|
|
423
|
-
|
|
424
|
-
service.chain_data.on_chain_state = OnChainState.MINTED
|
|
967
|
+
chain_data.token = event_data["args"]["serviceId"]
|
|
425
968
|
service.store()
|
|
426
969
|
|
|
427
|
-
|
|
428
|
-
|
|
970
|
+
if is_first_mint: # Hotfix to prevent RPC out-of-sync issues
|
|
971
|
+
time.sleep(RPC_SYNC_TIMEOUT)
|
|
429
972
|
|
|
430
|
-
|
|
973
|
+
# Activate service
|
|
974
|
+
if (
|
|
975
|
+
self._get_on_chain_state(service=service, chain=chain)
|
|
976
|
+
== OnChainState.PRE_REGISTRATION
|
|
977
|
+
):
|
|
431
978
|
cost_of_bond = user_params.cost_of_bond
|
|
432
979
|
if user_params.use_staking:
|
|
433
|
-
token_utility = "
|
|
434
|
-
olas_token = "
|
|
980
|
+
token_utility = target_staking_params["service_registry_token_utility"]
|
|
981
|
+
olas_token = target_staking_params["staking_token"]
|
|
435
982
|
self.logger.info(
|
|
436
|
-
f"Approving OLAS as bonding token from {
|
|
983
|
+
f"Approving OLAS as bonding token from {safe} to {token_utility}"
|
|
437
984
|
)
|
|
438
985
|
cost_of_bond = (
|
|
439
986
|
registry_contracts.service_registry_token_utility.get_agent_bond(
|
|
440
987
|
ledger_api=sftxb.ledger_api,
|
|
441
988
|
contract_address=token_utility,
|
|
442
|
-
service_id=
|
|
443
|
-
agent_id=
|
|
989
|
+
service_id=chain_data.token,
|
|
990
|
+
agent_id=agent_id,
|
|
444
991
|
).get("bond")
|
|
445
992
|
)
|
|
446
993
|
sftxb.new_tx().add(
|
|
447
|
-
sftxb.
|
|
994
|
+
sftxb.get_erc20_approval_data(
|
|
448
995
|
spender=token_utility,
|
|
449
996
|
amount=cost_of_bond,
|
|
450
|
-
|
|
997
|
+
erc20_contract=olas_token,
|
|
451
998
|
)
|
|
452
999
|
).settle()
|
|
453
1000
|
token_utility_allowance = (
|
|
@@ -456,50 +1003,66 @@ class ServiceManager:
|
|
|
456
1003
|
contract_address=olas_token,
|
|
457
1004
|
)
|
|
458
1005
|
.functions.allowance(
|
|
459
|
-
|
|
1006
|
+
safe,
|
|
460
1007
|
token_utility,
|
|
461
1008
|
)
|
|
462
1009
|
.call()
|
|
463
1010
|
)
|
|
464
1011
|
self.logger.info(
|
|
465
|
-
f"Approved {token_utility_allowance} OLAS from {
|
|
1012
|
+
f"Approved {token_utility_allowance} OLAS from {safe} to {token_utility}"
|
|
466
1013
|
)
|
|
467
|
-
cost_of_bond =
|
|
1014
|
+
cost_of_bond = MIN_AGENT_BOND
|
|
468
1015
|
|
|
469
1016
|
self.logger.info("Activating service")
|
|
1017
|
+
|
|
1018
|
+
native_balance = get_asset_balance(
|
|
1019
|
+
ledger_api=sftxb.ledger_api,
|
|
1020
|
+
asset_address=ZERO_ADDRESS,
|
|
1021
|
+
address=safe,
|
|
1022
|
+
)
|
|
1023
|
+
|
|
1024
|
+
if (
|
|
1025
|
+
native_balance < cost_of_bond
|
|
1026
|
+
): # TODO check that this is the security deposit
|
|
1027
|
+
message = f"Cannot activate service: address {safe} {native_balance=} < {cost_of_bond=}."
|
|
1028
|
+
self.logger.error(message)
|
|
1029
|
+
raise ValueError(message)
|
|
1030
|
+
|
|
470
1031
|
sftxb.new_tx().add(
|
|
471
1032
|
sftxb.get_activate_data(
|
|
472
|
-
service_id=
|
|
1033
|
+
service_id=chain_data.token,
|
|
473
1034
|
cost_of_bond=cost_of_bond,
|
|
474
1035
|
)
|
|
475
1036
|
).settle()
|
|
476
|
-
service.chain_data.on_chain_state = OnChainState.ACTIVATED
|
|
477
|
-
service.store()
|
|
478
1037
|
|
|
479
|
-
|
|
480
|
-
|
|
1038
|
+
if is_first_mint: # Hotfix to prevent RPC out-of-sync issues
|
|
1039
|
+
time.sleep(RPC_SYNC_TIMEOUT)
|
|
481
1040
|
|
|
482
|
-
|
|
1041
|
+
# Register agent instances
|
|
1042
|
+
if (
|
|
1043
|
+
self._get_on_chain_state(service=service, chain=chain)
|
|
1044
|
+
== OnChainState.ACTIVE_REGISTRATION
|
|
1045
|
+
):
|
|
483
1046
|
cost_of_bond = user_params.cost_of_bond
|
|
484
1047
|
if user_params.use_staking:
|
|
485
|
-
token_utility = "
|
|
486
|
-
olas_token = "
|
|
1048
|
+
token_utility = target_staking_params["service_registry_token_utility"]
|
|
1049
|
+
olas_token = target_staking_params["staking_token"]
|
|
487
1050
|
self.logger.info(
|
|
488
|
-
f"Approving OLAS as bonding token from {
|
|
1051
|
+
f"Approving OLAS as bonding token from {safe} to {token_utility}"
|
|
489
1052
|
)
|
|
490
1053
|
cost_of_bond = (
|
|
491
1054
|
registry_contracts.service_registry_token_utility.get_agent_bond(
|
|
492
1055
|
ledger_api=sftxb.ledger_api,
|
|
493
1056
|
contract_address=token_utility,
|
|
494
|
-
service_id=
|
|
495
|
-
agent_id=
|
|
1057
|
+
service_id=chain_data.token,
|
|
1058
|
+
agent_id=agent_id,
|
|
496
1059
|
).get("bond")
|
|
497
1060
|
)
|
|
498
1061
|
sftxb.new_tx().add(
|
|
499
|
-
sftxb.
|
|
1062
|
+
sftxb.get_erc20_approval_data(
|
|
500
1063
|
spender=token_utility,
|
|
501
1064
|
amount=cost_of_bond,
|
|
502
|
-
|
|
1065
|
+
erc20_contract=olas_token,
|
|
503
1066
|
)
|
|
504
1067
|
).settle()
|
|
505
1068
|
token_utility_allowance = (
|
|
@@ -508,534 +1071,1707 @@ class ServiceManager:
|
|
|
508
1071
|
contract_address=olas_token,
|
|
509
1072
|
)
|
|
510
1073
|
.functions.allowance(
|
|
511
|
-
|
|
1074
|
+
safe,
|
|
512
1075
|
token_utility,
|
|
513
1076
|
)
|
|
514
1077
|
.call()
|
|
515
1078
|
)
|
|
516
1079
|
self.logger.info(
|
|
517
|
-
f"Approved {token_utility_allowance} OLAS from {
|
|
1080
|
+
f"Approved {token_utility_allowance} OLAS from {safe} to {token_utility}"
|
|
518
1081
|
)
|
|
519
|
-
cost_of_bond =
|
|
1082
|
+
cost_of_bond = MIN_AGENT_BOND
|
|
520
1083
|
|
|
521
1084
|
self.logger.info(
|
|
522
|
-
f"Registering
|
|
1085
|
+
f"Registering agent instances: {chain_data.token} -> {service.agent_addresses}"
|
|
523
1086
|
)
|
|
1087
|
+
|
|
1088
|
+
native_balance = get_asset_balance(
|
|
1089
|
+
ledger_api=sftxb.ledger_api,
|
|
1090
|
+
asset_address=ZERO_ADDRESS,
|
|
1091
|
+
address=safe,
|
|
1092
|
+
)
|
|
1093
|
+
|
|
1094
|
+
if native_balance < cost_of_bond * len(service.agent_addresses):
|
|
1095
|
+
message = f"Cannot register agent instances: address {safe} {native_balance=} < {cost_of_bond=}."
|
|
1096
|
+
self.logger.error(message)
|
|
1097
|
+
raise ValueError(message)
|
|
1098
|
+
|
|
524
1099
|
sftxb.new_tx().add(
|
|
525
1100
|
sftxb.get_register_instances_data(
|
|
526
|
-
service_id=
|
|
527
|
-
instances=
|
|
528
|
-
agents=[
|
|
1101
|
+
service_id=chain_data.token,
|
|
1102
|
+
instances=service.agent_addresses,
|
|
1103
|
+
agents=[agent_id for _ in service.agent_addresses],
|
|
529
1104
|
cost_of_bond=cost_of_bond,
|
|
530
1105
|
)
|
|
531
1106
|
).settle()
|
|
532
|
-
service.chain_data.on_chain_state = OnChainState.REGISTERED
|
|
533
|
-
service.keys = keys
|
|
534
|
-
service.store()
|
|
535
1107
|
|
|
536
|
-
|
|
537
|
-
|
|
1108
|
+
if is_first_mint: # Hotfix to prevent RPC out-of-sync issues
|
|
1109
|
+
time.sleep(RPC_SYNC_TIMEOUT)
|
|
538
1110
|
|
|
539
|
-
|
|
1111
|
+
# Deploy service
|
|
1112
|
+
is_initial_funding = False
|
|
1113
|
+
if (
|
|
1114
|
+
self._get_on_chain_state(service=service, chain=chain)
|
|
1115
|
+
== OnChainState.FINISHED_REGISTRATION
|
|
1116
|
+
):
|
|
540
1117
|
self.logger.info("Deploying service")
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
1118
|
+
|
|
1119
|
+
info = sftxb.info(token_id=chain_data.token)
|
|
1120
|
+
service_safe_address = info["multisig"]
|
|
1121
|
+
if service_safe_address == ZERO_ADDRESS:
|
|
1122
|
+
reuse_multisig = False
|
|
1123
|
+
is_initial_funding = True
|
|
1124
|
+
is_recovery_module_enabled = True
|
|
1125
|
+
else:
|
|
1126
|
+
reuse_multisig = True
|
|
1127
|
+
is_recovery_module_enabled = (
|
|
1128
|
+
registry_contracts.gnosis_safe.is_module_enabled(
|
|
1129
|
+
ledger_api=sftxb.ledger_api,
|
|
1130
|
+
contract_address=service_safe_address,
|
|
1131
|
+
module_address=CONTRACTS[Chain(chain)]["recovery_module"],
|
|
1132
|
+
).get("enabled")
|
|
545
1133
|
)
|
|
546
|
-
).settle()
|
|
547
|
-
service.chain_data.on_chain_state = OnChainState.DEPLOYED
|
|
548
|
-
service.store()
|
|
549
1134
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
1135
|
+
self.logger.info(f"{reuse_multisig=}")
|
|
1136
|
+
self.logger.info(f"{is_recovery_module_enabled=}")
|
|
1137
|
+
|
|
1138
|
+
messages = sftxb.get_deploy_data_from_safe(
|
|
1139
|
+
service_id=chain_data.token,
|
|
1140
|
+
reuse_multisig=reuse_multisig,
|
|
1141
|
+
master_safe=safe,
|
|
1142
|
+
use_recovery_module=is_recovery_module_enabled,
|
|
1143
|
+
)
|
|
1144
|
+
tx = sftxb.new_tx()
|
|
1145
|
+
for message in messages:
|
|
1146
|
+
tx.add(message)
|
|
1147
|
+
tx.settle()
|
|
1148
|
+
|
|
1149
|
+
# Update local Service
|
|
1150
|
+
info = sftxb.info(token_id=chain_data.token)
|
|
1151
|
+
chain_data.instances = info["instances"]
|
|
1152
|
+
chain_data.multisig = info["multisig"]
|
|
1153
|
+
|
|
1154
|
+
if is_initial_funding:
|
|
1155
|
+
self.funding_manager.fund_service_initial(service)
|
|
1156
|
+
|
|
1157
|
+
# TODO: yet another agent specific logic for mech, which should be abstracted
|
|
1158
|
+
if all(
|
|
1159
|
+
var in service.env_variables
|
|
1160
|
+
for var in [
|
|
1161
|
+
"AGENT_ID",
|
|
1162
|
+
"MECH_TO_CONFIG",
|
|
1163
|
+
"ON_CHAIN_SERVICE_ID",
|
|
1164
|
+
"ETHEREUM_LEDGER_RPC_0",
|
|
1165
|
+
"GNOSIS_LEDGER_RPC_0",
|
|
1166
|
+
"MECH_MARKETPLACE_ADDRESS",
|
|
1167
|
+
]
|
|
1168
|
+
):
|
|
1169
|
+
if (
|
|
1170
|
+
not service.env_variables["AGENT_ID"]["value"]
|
|
1171
|
+
or not service.env_variables["MECH_TO_CONFIG"]["value"]
|
|
1172
|
+
):
|
|
1173
|
+
mech_address, agent_id = deploy_mech(sftxb=sftxb, service=service)
|
|
1174
|
+
service.update_env_variables_values(
|
|
1175
|
+
{
|
|
1176
|
+
"AGENT_ID": agent_id,
|
|
1177
|
+
"MECH_TO_CONFIG": json.dumps(
|
|
1178
|
+
{
|
|
1179
|
+
mech_address: {
|
|
1180
|
+
"use_dynamic_pricing": False,
|
|
1181
|
+
"is_marketplace_mech": True,
|
|
1182
|
+
}
|
|
1183
|
+
},
|
|
1184
|
+
separators=(",", ":"),
|
|
1185
|
+
),
|
|
1186
|
+
"MECH_TO_MAX_DELIVERY_RATE": json.dumps(
|
|
1187
|
+
{
|
|
1188
|
+
mech_address: service.env_variables.get(
|
|
1189
|
+
"MECH_REQUEST_PRICE", {}
|
|
1190
|
+
).get("value", 10000000000000000)
|
|
1191
|
+
},
|
|
1192
|
+
separators=(",", ":"),
|
|
1193
|
+
),
|
|
1194
|
+
}
|
|
1195
|
+
)
|
|
1196
|
+
|
|
1197
|
+
service.update_env_variables_values(
|
|
1198
|
+
{
|
|
1199
|
+
"ON_CHAIN_SERVICE_ID": chain_data.token,
|
|
1200
|
+
"ETHEREUM_LEDGER_RPC_0": service.env_variables["GNOSIS_LEDGER_RPC"][
|
|
1201
|
+
"value"
|
|
1202
|
+
],
|
|
1203
|
+
"GNOSIS_LEDGER_RPC_0": service.env_variables["GNOSIS_LEDGER_RPC"][
|
|
1204
|
+
"value"
|
|
1205
|
+
],
|
|
1206
|
+
}
|
|
1207
|
+
)
|
|
1208
|
+
|
|
1209
|
+
# TODO: this is a patch for modius, to be standardized
|
|
1210
|
+
staking_chain = None
|
|
1211
|
+
for chain_, config in service.chain_configs.items():
|
|
1212
|
+
if config.chain_data.user_params.use_staking:
|
|
1213
|
+
staking_chain = chain_
|
|
1214
|
+
break
|
|
1215
|
+
|
|
1216
|
+
service.update_env_variables_values(
|
|
1217
|
+
{
|
|
1218
|
+
"SAFE_CONTRACT_ADDRESSES": json.dumps(
|
|
1219
|
+
{
|
|
1220
|
+
chain: config.chain_data.multisig
|
|
1221
|
+
for chain, config in service.chain_configs.items()
|
|
1222
|
+
},
|
|
1223
|
+
separators=(",", ":"),
|
|
1224
|
+
),
|
|
1225
|
+
"STAKING_CHAIN": staking_chain,
|
|
1226
|
+
}
|
|
559
1227
|
)
|
|
560
1228
|
service.store()
|
|
561
1229
|
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
1230
|
+
if user_params.use_staking:
|
|
1231
|
+
self.stake_service_on_chain_from_safe(
|
|
1232
|
+
service_config_id=service_config_id, chain=chain
|
|
1233
|
+
)
|
|
565
1234
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
1235
|
+
def terminate_service_on_chain(
|
|
1236
|
+
self, service_config_id: str, chain: t.Optional[str] = None
|
|
1237
|
+
) -> None:
|
|
1238
|
+
"""Terminate service on-chain"""
|
|
1239
|
+
# TODO This method has not been thoroughly reviewed. Deprecated usage in favour of Safe version.
|
|
1240
|
+
|
|
1241
|
+
self.logger.info("terminate_service_on_chain")
|
|
1242
|
+
service = self.load(service_config_id=service_config_id)
|
|
572
1243
|
|
|
573
|
-
|
|
1244
|
+
chain_config = service.chain_configs[chain or service.home_chain]
|
|
1245
|
+
ledger_config = chain_config.ledger_config
|
|
1246
|
+
chain_data = chain_config.chain_data
|
|
1247
|
+
ocm = self.get_on_chain_manager(ledger_config=ledger_config)
|
|
1248
|
+
info = ocm.info(token_id=chain_data.token)
|
|
1249
|
+
|
|
1250
|
+
if OnChainState(info["service_state"]) != OnChainState.DEPLOYED:
|
|
574
1251
|
self.logger.info("Cannot terminate service")
|
|
575
1252
|
return
|
|
576
1253
|
|
|
577
1254
|
self.logger.info("Terminating service")
|
|
578
1255
|
ocm.terminate(
|
|
579
|
-
service_id=
|
|
1256
|
+
service_id=chain_data.token,
|
|
580
1257
|
token=(
|
|
581
|
-
OLAS[
|
|
582
|
-
if
|
|
1258
|
+
OLAS[ledger_config.chain]
|
|
1259
|
+
if chain_data.user_params.use_staking
|
|
583
1260
|
else None
|
|
584
1261
|
),
|
|
585
1262
|
)
|
|
586
|
-
service.chain_data.on_chain_state = OnChainState.TERMINATED
|
|
587
|
-
service.store()
|
|
588
|
-
|
|
589
|
-
def terminate_service_on_chain_from_safe(self, hash: str) -> None:
|
|
590
|
-
"""
|
|
591
|
-
Terminate service on-chain
|
|
592
1263
|
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
service
|
|
599
|
-
|
|
600
|
-
if service.chain_data.on_chain_state != OnChainState.DEPLOYED:
|
|
601
|
-
self.logger.info("Cannot terminate service")
|
|
602
|
-
return
|
|
603
|
-
|
|
604
|
-
self.logger.info("Terminating service")
|
|
605
|
-
sftxb.new_tx().add(
|
|
606
|
-
sftxb.get_terminate_data(
|
|
607
|
-
service_id=service.chain_data.token,
|
|
608
|
-
)
|
|
609
|
-
).settle()
|
|
610
|
-
service.chain_data.on_chain_state = OnChainState.TERMINATED
|
|
611
|
-
service.store()
|
|
1264
|
+
def terminate_service_on_chain_from_safe( # pylint: disable=too-many-locals
|
|
1265
|
+
self,
|
|
1266
|
+
service_config_id: str,
|
|
1267
|
+
chain: str,
|
|
1268
|
+
) -> None:
|
|
1269
|
+
"""Terminate service on-chain"""
|
|
612
1270
|
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
1271
|
+
self.logger.info("terminate_service_on_chain_from_safe")
|
|
1272
|
+
service = self.load(service_config_id=service_config_id)
|
|
1273
|
+
chain_config = service.chain_configs[chain]
|
|
1274
|
+
ledger_config = chain_config.ledger_config
|
|
1275
|
+
chain_data = chain_config.chain_data
|
|
1276
|
+
wallet = self.wallet_manager.load(ledger_config.chain.ledger_type)
|
|
1277
|
+
master_safe = wallet.safes[Chain(chain)] # type: ignore
|
|
616
1278
|
|
|
617
|
-
|
|
618
|
-
""
|
|
619
|
-
service = self.create_or_load(hash=hash)
|
|
620
|
-
ocm = self.get_on_chain_manager(service=service)
|
|
621
|
-
info = ocm.info(token_id=service.chain_data.token)
|
|
622
|
-
service.chain_data.on_chain_state = OnChainState(info["service_state"])
|
|
1279
|
+
# TODO fixme
|
|
1280
|
+
os.environ["CUSTOM_CHAIN_RPC"] = ledger_config.rpc
|
|
623
1281
|
|
|
624
|
-
|
|
625
|
-
self.logger.info("Cannot unbond service")
|
|
626
|
-
return
|
|
1282
|
+
sftxb = self.get_eth_safe_tx_builder(ledger_config=ledger_config)
|
|
627
1283
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
OLAS[service.ledger_config.chain]
|
|
633
|
-
if service.chain_data.user_params.use_staking
|
|
634
|
-
else None
|
|
635
|
-
),
|
|
1284
|
+
# Determine if the service is staked in a known staking program
|
|
1285
|
+
current_staking_program = self._get_current_staking_program(
|
|
1286
|
+
service,
|
|
1287
|
+
chain,
|
|
636
1288
|
)
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
info = sftxb.info(token_id=service.chain_data.token)
|
|
649
|
-
service.chain_data.on_chain_state = OnChainState(info["service_state"])
|
|
1289
|
+
is_staked = current_staking_program is not None
|
|
1290
|
+
|
|
1291
|
+
can_unstake = False
|
|
1292
|
+
if current_staking_program is not None:
|
|
1293
|
+
can_unstake = sftxb.can_unstake(
|
|
1294
|
+
service_id=chain_data.token,
|
|
1295
|
+
staking_contract=get_staking_contract(
|
|
1296
|
+
chain=ledger_config.chain,
|
|
1297
|
+
staking_program_id=current_staking_program,
|
|
1298
|
+
),
|
|
1299
|
+
)
|
|
650
1300
|
|
|
651
|
-
|
|
652
|
-
|
|
1301
|
+
# Cannot unstake, terminate flow.
|
|
1302
|
+
if is_staked and not can_unstake:
|
|
1303
|
+
self.logger.info("Service cannot be terminated on-chain: cannot unstake.")
|
|
653
1304
|
return
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
1305
|
+
# Unstake the service if applies
|
|
1306
|
+
if is_staked and can_unstake:
|
|
1307
|
+
self.unstake_service_on_chain_from_safe(
|
|
1308
|
+
service_config_id=service_config_id,
|
|
1309
|
+
chain=chain,
|
|
1310
|
+
staking_program_id=current_staking_program,
|
|
659
1311
|
)
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
1312
|
+
# At least claim the rewards if we cannot unstake yet
|
|
1313
|
+
elif is_staked:
|
|
1314
|
+
self.claim_on_chain_from_safe(
|
|
1315
|
+
service_config_id=service_config_id,
|
|
1316
|
+
chain=chain,
|
|
1317
|
+
)
|
|
1318
|
+
return
|
|
663
1319
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
1320
|
+
if self._get_on_chain_state(service=service, chain=chain) in (
|
|
1321
|
+
OnChainState.ACTIVE_REGISTRATION,
|
|
1322
|
+
OnChainState.FINISHED_REGISTRATION,
|
|
1323
|
+
OnChainState.DEPLOYED,
|
|
1324
|
+
):
|
|
1325
|
+
self.logger.info("Terminating service")
|
|
1326
|
+
sftxb.new_tx().add(
|
|
1327
|
+
sftxb.get_terminate_data(
|
|
1328
|
+
service_id=chain_data.token,
|
|
1329
|
+
)
|
|
1330
|
+
).settle()
|
|
667
1331
|
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
self.logger.info("
|
|
1332
|
+
if (
|
|
1333
|
+
self._get_on_chain_state(service=service, chain=chain)
|
|
1334
|
+
== OnChainState.TERMINATED_BONDED
|
|
1335
|
+
):
|
|
1336
|
+
self.logger.info("Unbonding service")
|
|
1337
|
+
sftxb.new_tx().add(
|
|
1338
|
+
sftxb.get_unbond_data(
|
|
1339
|
+
service_id=chain_data.token,
|
|
1340
|
+
)
|
|
1341
|
+
).settle()
|
|
1342
|
+
|
|
1343
|
+
# Swap service safe
|
|
1344
|
+
current_safe_owners = sftxb.get_service_safe_owners(service_id=chain_data.token)
|
|
1345
|
+
counter_current_safe_owners = Counter(s.lower() for s in current_safe_owners)
|
|
1346
|
+
counter_instances = Counter(s.lower() for s in service.agent_addresses)
|
|
1347
|
+
|
|
1348
|
+
if counter_current_safe_owners == counter_instances:
|
|
1349
|
+
requirements = ChainAmounts(
|
|
1350
|
+
{
|
|
1351
|
+
chain: {
|
|
1352
|
+
current_safe_owners[0]: {
|
|
1353
|
+
ZERO_ADDRESS: chain_data.user_params.fund_requirements[
|
|
1354
|
+
ZERO_ADDRESS
|
|
1355
|
+
].agent
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
}
|
|
1359
|
+
)
|
|
1360
|
+
balances = ChainAmounts(
|
|
1361
|
+
{
|
|
1362
|
+
chain: {
|
|
1363
|
+
current_safe_owners[0]: {
|
|
1364
|
+
ZERO_ADDRESS: get_asset_balance(
|
|
1365
|
+
ledger_api=sftxb.ledger_api,
|
|
1366
|
+
asset_address=ZERO_ADDRESS,
|
|
1367
|
+
address=service.agent_addresses[0],
|
|
1368
|
+
)
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
)
|
|
1373
|
+
if balances < requirements * DEFAULT_EOA_THRESHOLD:
|
|
1374
|
+
self.logger.info("[SERVICE MANAGER] Funding agent EOA for Safe swap.")
|
|
1375
|
+
shortfalls = ChainAmounts.shortfalls(
|
|
1376
|
+
requirements=requirements, balances=balances
|
|
1377
|
+
)
|
|
1378
|
+
try:
|
|
1379
|
+
self.funding_manager.fund_chain_amounts(shortfalls)
|
|
1380
|
+
except InsufficientFundsException as e:
|
|
1381
|
+
recovery_module_address = CONTRACTS[Chain(chain)]["recovery_module"]
|
|
1382
|
+
is_recovery_module_enabled = (
|
|
1383
|
+
registry_contracts.gnosis_safe.is_module_enabled(
|
|
1384
|
+
ledger_api=sftxb.ledger_api,
|
|
1385
|
+
contract_address=chain_data.multisig,
|
|
1386
|
+
module_address=recovery_module_address,
|
|
1387
|
+
).get("enabled")
|
|
1388
|
+
)
|
|
1389
|
+
if is_recovery_module_enabled:
|
|
1390
|
+
self.logger.info(
|
|
1391
|
+
"[SERVICE MANAGER] Could not fund Agent EOA for service swap, but recovery module is enabled."
|
|
1392
|
+
)
|
|
1393
|
+
return
|
|
1394
|
+
raise e
|
|
1395
|
+
|
|
1396
|
+
self._enable_recovery_module(
|
|
1397
|
+
service_config_id=service_config_id, chain=chain
|
|
1398
|
+
)
|
|
1399
|
+
self.logger.info("[SERVICE MANAGER] Swapping Safe owners")
|
|
1400
|
+
owner_crypto = self.keys_manager.get_crypto_instance(
|
|
1401
|
+
address=current_safe_owners[0]
|
|
1402
|
+
)
|
|
1403
|
+
sftxb.swap(
|
|
1404
|
+
service_id=chain_data.token,
|
|
1405
|
+
multisig=chain_data.multisig, # TODO this can be read from the registry
|
|
1406
|
+
owner_cryptos=[owner_crypto], # TODO allow multiple owners
|
|
1407
|
+
new_owner_address=(
|
|
1408
|
+
master_safe if master_safe else wallet.crypto.address
|
|
1409
|
+
), # TODO it should always be safe address
|
|
1410
|
+
)
|
|
1411
|
+
|
|
1412
|
+
def _execute_recovery_module_flow_from_safe( # pylint: disable=too-many-locals
|
|
1413
|
+
self,
|
|
1414
|
+
service_config_id: str,
|
|
1415
|
+
chain: str,
|
|
1416
|
+
) -> None:
|
|
1417
|
+
"""Execute recovery module operations from Safe"""
|
|
1418
|
+
self.logger.info(f"_execute_recovery_module_operations_from_safe {chain=}")
|
|
1419
|
+
service = self.load(service_config_id=service_config_id)
|
|
1420
|
+
chain_config = service.chain_configs[chain]
|
|
1421
|
+
chain_data = chain_config.chain_data
|
|
1422
|
+
ledger_config = chain_config.ledger_config
|
|
1423
|
+
wallet = self.wallet_manager.load(ledger_config.chain.ledger_type)
|
|
1424
|
+
sftxb = self.get_eth_safe_tx_builder(ledger_config=ledger_config)
|
|
1425
|
+
safe = wallet.safes[Chain(chain)]
|
|
1426
|
+
|
|
1427
|
+
if chain_data.token == NON_EXISTENT_TOKEN:
|
|
1428
|
+
self.logger.info("Service is not minted.")
|
|
673
1429
|
return
|
|
674
1430
|
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
1431
|
+
info = sftxb.info(token_id=chain_data.token)
|
|
1432
|
+
service_safe_address = info["multisig"]
|
|
1433
|
+
on_chain_state = OnChainState(info["service_state"])
|
|
678
1434
|
|
|
679
|
-
if
|
|
680
|
-
self.logger.info("
|
|
1435
|
+
if service_safe_address == ZERO_ADDRESS:
|
|
1436
|
+
self.logger.info("Service Safe is not deployed.")
|
|
681
1437
|
return
|
|
682
1438
|
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
1439
|
+
recovery_module_address = CONTRACTS[Chain(chain)]["recovery_module"]
|
|
1440
|
+
is_recovery_module_enabled = registry_contracts.gnosis_safe.is_module_enabled(
|
|
1441
|
+
ledger_api=sftxb.ledger_api,
|
|
1442
|
+
contract_address=service_safe_address,
|
|
1443
|
+
module_address=recovery_module_address,
|
|
1444
|
+
).get("enabled")
|
|
1445
|
+
|
|
1446
|
+
service_safe_owners = sftxb.get_service_safe_owners(service_id=chain_data.token)
|
|
1447
|
+
master_safe_is_service_safe_owner = service_safe_owners == [safe]
|
|
1448
|
+
|
|
1449
|
+
self.logger.info(f"{is_recovery_module_enabled=}")
|
|
1450
|
+
self.logger.info(f"{master_safe_is_service_safe_owner=}")
|
|
1451
|
+
|
|
1452
|
+
if not is_recovery_module_enabled and not master_safe_is_service_safe_owner:
|
|
1453
|
+
self.logger.info(
|
|
1454
|
+
"Recovery module is not enabled and Master Safe is not service Safe owner. Skipping recovery operations."
|
|
1455
|
+
)
|
|
692
1456
|
return
|
|
693
1457
|
|
|
694
|
-
if
|
|
695
|
-
self.
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
1458
|
+
if not is_recovery_module_enabled:
|
|
1459
|
+
self._enable_recovery_module(
|
|
1460
|
+
service_config_id=service_config_id, chain=chain
|
|
1461
|
+
)
|
|
1462
|
+
|
|
1463
|
+
if (
|
|
1464
|
+
not master_safe_is_service_safe_owner
|
|
1465
|
+
and on_chain_state == OnChainState.PRE_REGISTRATION
|
|
1466
|
+
):
|
|
1467
|
+
self.logger.info("Recovering service Safe access through recovery module.")
|
|
1468
|
+
sftxb.new_tx().add(
|
|
1469
|
+
sftxb.get_recover_access_data(
|
|
1470
|
+
service_id=chain_data.token,
|
|
1471
|
+
)
|
|
1472
|
+
).settle()
|
|
1473
|
+
self.logger.info("Recovering service Safe done.")
|
|
699
1474
|
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
1475
|
+
def _enable_recovery_module( # pylint: disable=too-many-locals
|
|
1476
|
+
self,
|
|
1477
|
+
service_config_id: str,
|
|
1478
|
+
chain: str,
|
|
1479
|
+
) -> None:
|
|
1480
|
+
"""Enable recovery module"""
|
|
1481
|
+
self.logger.info(f"_enable_recovery_module {chain=}")
|
|
1482
|
+
service = self.load(service_config_id=service_config_id)
|
|
1483
|
+
chain_config = service.chain_configs[chain]
|
|
1484
|
+
chain_data = chain_config.chain_data
|
|
1485
|
+
ledger_config = chain_config.ledger_config
|
|
1486
|
+
wallet = self.wallet_manager.load(ledger_config.chain.ledger_type)
|
|
1487
|
+
sftxb = self.get_eth_safe_tx_builder(ledger_config=ledger_config)
|
|
1488
|
+
safe = wallet.safes[Chain(chain)]
|
|
1489
|
+
|
|
1490
|
+
if chain_data.token == NON_EXISTENT_TOKEN:
|
|
1491
|
+
self.logger.info("Service is not minted.")
|
|
1492
|
+
return
|
|
1493
|
+
|
|
1494
|
+
info = sftxb.info(token_id=chain_data.token)
|
|
1495
|
+
service_safe_address = info["multisig"]
|
|
1496
|
+
|
|
1497
|
+
if service_safe_address == ZERO_ADDRESS:
|
|
1498
|
+
self.logger.info("Service Safe is not deployed.")
|
|
1499
|
+
return
|
|
1500
|
+
|
|
1501
|
+
recovery_module_address = CONTRACTS[Chain(chain)]["recovery_module"]
|
|
1502
|
+
is_recovery_module_enabled = registry_contracts.gnosis_safe.is_module_enabled(
|
|
1503
|
+
ledger_api=sftxb.ledger_api,
|
|
1504
|
+
contract_address=service_safe_address,
|
|
1505
|
+
module_address=recovery_module_address,
|
|
1506
|
+
).get("enabled")
|
|
1507
|
+
|
|
1508
|
+
if is_recovery_module_enabled:
|
|
1509
|
+
self.logger.info("Recovery module is already enabled in service Safe.")
|
|
1510
|
+
return
|
|
1511
|
+
|
|
1512
|
+
self.logger.info("Recovery module is not enabled.")
|
|
1513
|
+
|
|
1514
|
+
# NOTE Recovery from agent only works for single-agent services
|
|
1515
|
+
agent_address = service.agent_addresses[0]
|
|
1516
|
+
service_safe_owners = sftxb.get_service_safe_owners(service_id=chain_data.token)
|
|
1517
|
+
agent_is_service_safe_owner = service_safe_owners == [agent_address]
|
|
1518
|
+
master_safe_is_service_safe_owner = service_safe_owners == [safe]
|
|
1519
|
+
|
|
1520
|
+
if agent_is_service_safe_owner:
|
|
1521
|
+
self.logger.info("(Agent) Enabling recovery module in service Safe.")
|
|
1522
|
+
try:
|
|
1523
|
+
crypto = self.keys_manager.get_crypto_instance(address=agent_address)
|
|
1524
|
+
EthSafeTxBuilder._new_tx( # pylint: disable=protected-access
|
|
1525
|
+
ledger_api=sftxb.ledger_api,
|
|
1526
|
+
crypto=crypto,
|
|
1527
|
+
chain_type=ChainType(chain),
|
|
1528
|
+
safe=service_safe_address,
|
|
1529
|
+
).add(
|
|
1530
|
+
sftxb.get_enable_module_data(
|
|
1531
|
+
module_address=recovery_module_address,
|
|
1532
|
+
safe_address=service_safe_address,
|
|
1533
|
+
)
|
|
1534
|
+
).settle()
|
|
1535
|
+
self.logger.info(
|
|
1536
|
+
"(Agent) Recovery module enabled successfully in service Safe."
|
|
1537
|
+
)
|
|
1538
|
+
except Exception as e: # pylint: disable=broad-except
|
|
1539
|
+
self.logger.error(
|
|
1540
|
+
f"Failed to enable recovery module in service Safe. Exception {e}: {traceback.format_exc()}"
|
|
1541
|
+
)
|
|
1542
|
+
elif master_safe_is_service_safe_owner:
|
|
1543
|
+
# TODO Enable recovery module when Safe owner = master Safe.
|
|
1544
|
+
# This should be similar to the above code, but
|
|
1545
|
+
# requires implement a transaction where the owner is another Safe.
|
|
1546
|
+
self.logger.info(
|
|
1547
|
+
"(Service owner) Enabling recovery module in service Safe. [Not implemented]"
|
|
1548
|
+
)
|
|
1549
|
+
else:
|
|
1550
|
+
self.logger.error(
|
|
1551
|
+
f"Cannot enable recovery module. Safe {service_safe_address} has inconsistent owners."
|
|
1552
|
+
)
|
|
1553
|
+
|
|
1554
|
+
def _get_current_staking_program( # pylint: disable=no-self-use
|
|
1555
|
+
self, service: Service, chain: str
|
|
1556
|
+
) -> t.Optional[str]:
|
|
1557
|
+
staking_manager = StakingManager(Chain(chain))
|
|
1558
|
+
return staking_manager.get_current_staking_program(
|
|
1559
|
+
service_id=service.chain_configs[chain].chain_data.token
|
|
705
1560
|
)
|
|
706
|
-
service.chain_data.staked = True
|
|
707
|
-
service.store()
|
|
708
1561
|
|
|
709
|
-
def
|
|
1562
|
+
def unbond_service_on_chain(
|
|
1563
|
+
self, service_config_id: str, chain: t.Optional[str] = None
|
|
1564
|
+
) -> None:
|
|
1565
|
+
"""Unbond service on-chain"""
|
|
1566
|
+
# TODO This method has not been thoroughly reviewed. Deprecated usage in favour of Safe version.
|
|
1567
|
+
|
|
1568
|
+
service = self.load(service_config_id=service_config_id)
|
|
1569
|
+
|
|
1570
|
+
chain_config = service.chain_configs[chain or service.home_chain]
|
|
1571
|
+
ledger_config = chain_config.ledger_config
|
|
1572
|
+
chain_data = chain_config.chain_data
|
|
1573
|
+
ocm = self.get_on_chain_manager(ledger_config=ledger_config)
|
|
1574
|
+
info = ocm.info(token_id=chain_data.token)
|
|
1575
|
+
|
|
1576
|
+
if OnChainState(info["service_state"]) != OnChainState.TERMINATED_BONDED:
|
|
1577
|
+
self.logger.info("Cannot unbond service")
|
|
1578
|
+
return
|
|
1579
|
+
|
|
1580
|
+
self.logger.info("Unbonding service")
|
|
1581
|
+
ocm.unbond(
|
|
1582
|
+
service_id=chain_data.token,
|
|
1583
|
+
token=(
|
|
1584
|
+
OLAS[ledger_config.chain]
|
|
1585
|
+
if chain_data.user_params.use_staking
|
|
1586
|
+
else None
|
|
1587
|
+
),
|
|
1588
|
+
)
|
|
1589
|
+
|
|
1590
|
+
def stake_service_on_chain(self, hash: str) -> None:
|
|
710
1591
|
"""
|
|
711
1592
|
Stake service on-chain
|
|
712
1593
|
|
|
713
1594
|
:param hash: Service hash
|
|
714
1595
|
"""
|
|
715
|
-
|
|
716
|
-
if not service.chain_data.user_params.use_staking:
|
|
717
|
-
self.logger.info("Cannot stake service, `use_staking` is set to false")
|
|
718
|
-
return
|
|
1596
|
+
raise NotImplementedError
|
|
719
1597
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
1598
|
+
def stake_service_on_chain_from_safe( # pylint: disable=too-many-statements,too-many-locals
|
|
1599
|
+
self, service_config_id: str, chain: str
|
|
1600
|
+
) -> None:
|
|
1601
|
+
"""Stake service on-chain"""
|
|
1602
|
+
self.logger.info("stake_service_on_chain_from_safe")
|
|
1603
|
+
service = self.load(service_config_id=service_config_id)
|
|
1604
|
+
chain_config = service.chain_configs[chain]
|
|
1605
|
+
ledger_config = chain_config.ledger_config
|
|
1606
|
+
chain_data = chain_config.chain_data
|
|
1607
|
+
user_params = chain_data.user_params
|
|
1608
|
+
target_staking_program = user_params.staking_program_id
|
|
1609
|
+
target_staking_contract = get_staking_contract(
|
|
1610
|
+
chain=ledger_config.chain,
|
|
1611
|
+
staking_program_id=target_staking_program,
|
|
1612
|
+
)
|
|
1613
|
+
sftxb = self.get_eth_safe_tx_builder(ledger_config=ledger_config)
|
|
723
1614
|
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
return
|
|
1615
|
+
# TODO fixme
|
|
1616
|
+
os.environ["CUSTOM_CHAIN_RPC"] = ledger_config.rpc
|
|
727
1617
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
if state == StakingState.STAKED:
|
|
734
|
-
self.logger.info(f"{service.chain_data.token} is already staked")
|
|
735
|
-
service.chain_data.staked = True
|
|
736
|
-
service.store()
|
|
1618
|
+
on_chain_state = self._get_on_chain_state(service=service, chain=chain)
|
|
1619
|
+
if on_chain_state != OnChainState.DEPLOYED:
|
|
1620
|
+
self.logger.info(
|
|
1621
|
+
f"Cannot perform staking operations. Service {chain_config.chain_data.token} is not on DEPLOYED state"
|
|
1622
|
+
)
|
|
737
1623
|
return
|
|
738
1624
|
|
|
739
|
-
if
|
|
740
|
-
|
|
741
|
-
service
|
|
742
|
-
|
|
743
|
-
|
|
1625
|
+
# Determine if the service is staked in a known staking program
|
|
1626
|
+
current_staking_program = self._get_current_staking_program(
|
|
1627
|
+
service,
|
|
1628
|
+
chain,
|
|
1629
|
+
)
|
|
1630
|
+
current_staking_contract = get_staking_contract(
|
|
1631
|
+
chain=ledger_config.chain,
|
|
1632
|
+
staking_program_id=current_staking_program,
|
|
1633
|
+
)
|
|
744
1634
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
"service_registry"
|
|
751
|
-
],
|
|
752
|
-
staking_contract=STAKING[service.ledger_config.chain],
|
|
1635
|
+
# perform the unstaking flow if necessary
|
|
1636
|
+
staking_state = StakingState.UNSTAKED
|
|
1637
|
+
if current_staking_program is not None:
|
|
1638
|
+
can_unstake = sftxb.can_unstake(
|
|
1639
|
+
chain_config.chain_data.token, current_staking_contract
|
|
753
1640
|
)
|
|
754
|
-
|
|
1641
|
+
if not chain_config.chain_data.user_params.use_staking and can_unstake:
|
|
1642
|
+
self.logger.info(
|
|
1643
|
+
f"Use staking is set to false, but service {chain_config.chain_data.token} is staked and can be unstaked. Unstaking..."
|
|
1644
|
+
)
|
|
1645
|
+
self.unstake_service_on_chain_from_safe(
|
|
1646
|
+
service_config_id=service_config_id,
|
|
1647
|
+
chain=chain,
|
|
1648
|
+
staking_program_id=current_staking_program,
|
|
1649
|
+
)
|
|
755
1650
|
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
service_id=service.chain_data.token,
|
|
760
|
-
staking_contract=STAKING[service.ledger_config.chain],
|
|
1651
|
+
staking_state = sftxb.staking_status(
|
|
1652
|
+
service_id=chain_data.token,
|
|
1653
|
+
staking_contract=current_staking_contract,
|
|
761
1654
|
)
|
|
762
|
-
).settle()
|
|
763
|
-
service.chain_data.staked = True
|
|
764
|
-
service.store()
|
|
765
1655
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
1656
|
+
if staking_state == StakingState.EVICTED and can_unstake:
|
|
1657
|
+
self.logger.info(
|
|
1658
|
+
f"Service {chain_config.chain_data.token} has been evicted and can be unstaked. Unstaking..."
|
|
1659
|
+
)
|
|
1660
|
+
self.unstake_service_on_chain_from_safe(
|
|
1661
|
+
service_config_id=service_config_id,
|
|
1662
|
+
chain=chain,
|
|
1663
|
+
staking_program_id=current_staking_program,
|
|
1664
|
+
)
|
|
769
1665
|
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
1666
|
+
if (
|
|
1667
|
+
staking_state == StakingState.STAKED
|
|
1668
|
+
and can_unstake
|
|
1669
|
+
and not sftxb.staking_rewards_available(current_staking_contract)
|
|
1670
|
+
):
|
|
1671
|
+
self.logger.info(
|
|
1672
|
+
f"There are no rewards available, service {chain_config.chain_data.token} "
|
|
1673
|
+
"is already staked and can be unstaked."
|
|
1674
|
+
)
|
|
1675
|
+
self.logger.info("Skipping unstaking for no rewards available.")
|
|
776
1676
|
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
1677
|
+
if (
|
|
1678
|
+
staking_state == StakingState.STAKED
|
|
1679
|
+
and current_staking_program != target_staking_program
|
|
1680
|
+
and can_unstake
|
|
1681
|
+
):
|
|
1682
|
+
self.logger.info(
|
|
1683
|
+
f"{chain_config.chain_data.token} is staked in a different staking program. Unstaking..."
|
|
1684
|
+
)
|
|
1685
|
+
self.unstake_service_on_chain_from_safe(
|
|
1686
|
+
service_config_id=service_config_id,
|
|
1687
|
+
chain=chain,
|
|
1688
|
+
staking_program_id=current_staking_program,
|
|
1689
|
+
)
|
|
1690
|
+
|
|
1691
|
+
staking_state = sftxb.staking_status(
|
|
1692
|
+
service_id=chain_config.chain_data.token,
|
|
1693
|
+
staking_contract=current_staking_contract,
|
|
1694
|
+
)
|
|
1695
|
+
|
|
1696
|
+
target_program_staking_state = sftxb.staking_status(
|
|
1697
|
+
service_id=chain_config.chain_data.token,
|
|
1698
|
+
staking_contract=target_staking_contract,
|
|
781
1699
|
)
|
|
1700
|
+
self.logger.info("Checking conditions to stake.")
|
|
1701
|
+
|
|
1702
|
+
staking_rewards_available = sftxb.staking_rewards_available(
|
|
1703
|
+
target_staking_contract
|
|
1704
|
+
)
|
|
1705
|
+
staking_slots_available = sftxb.staking_slots_available(target_staking_contract)
|
|
1706
|
+
current_staking_program = self._get_current_staking_program(
|
|
1707
|
+
service,
|
|
1708
|
+
chain,
|
|
1709
|
+
)
|
|
1710
|
+
|
|
782
1711
|
self.logger.info(
|
|
783
|
-
f"
|
|
1712
|
+
f"use_staking={chain_config.chain_data.user_params.use_staking}"
|
|
1713
|
+
)
|
|
1714
|
+
self.logger.info(f"{on_chain_state=}")
|
|
1715
|
+
self.logger.info(f"{current_staking_program=}")
|
|
1716
|
+
self.logger.info(f"{staking_state=}")
|
|
1717
|
+
self.logger.info(f"{target_staking_program=}")
|
|
1718
|
+
self.logger.info(f"{target_program_staking_state=}")
|
|
1719
|
+
self.logger.info(f"{staking_rewards_available=}")
|
|
1720
|
+
self.logger.info(f"{staking_slots_available=}")
|
|
1721
|
+
|
|
1722
|
+
if (
|
|
1723
|
+
chain_config.chain_data.user_params.use_staking # pylint: disable=too-many-boolean-expressions
|
|
1724
|
+
and staking_state == StakingState.UNSTAKED
|
|
1725
|
+
and target_program_staking_state == StakingState.UNSTAKED
|
|
1726
|
+
and staking_rewards_available
|
|
1727
|
+
and staking_slots_available
|
|
1728
|
+
and on_chain_state == OnChainState.DEPLOYED
|
|
1729
|
+
):
|
|
1730
|
+
self.logger.info(f"Approving staking: {chain_config.chain_data.token}")
|
|
1731
|
+
sftxb.new_tx().add(
|
|
1732
|
+
sftxb.get_staking_approval_data(
|
|
1733
|
+
service_id=chain_config.chain_data.token,
|
|
1734
|
+
service_registry=CONTRACTS[ledger_config.chain]["service_registry"],
|
|
1735
|
+
staking_contract=target_staking_contract,
|
|
1736
|
+
)
|
|
1737
|
+
).settle()
|
|
1738
|
+
|
|
1739
|
+
# Approve additional_staking_tokens.
|
|
1740
|
+
staking_params = sftxb.get_staking_params(
|
|
1741
|
+
staking_contract=target_staking_contract
|
|
1742
|
+
)
|
|
1743
|
+
|
|
1744
|
+
for token_contract, min_staking_amount in staking_params[
|
|
1745
|
+
"additional_staking_tokens"
|
|
1746
|
+
].items():
|
|
1747
|
+
sftxb.new_tx().add(
|
|
1748
|
+
sftxb.get_erc20_approval_data(
|
|
1749
|
+
spender=target_staking_contract,
|
|
1750
|
+
amount=min_staking_amount,
|
|
1751
|
+
erc20_contract=token_contract,
|
|
1752
|
+
)
|
|
1753
|
+
).settle()
|
|
1754
|
+
staking_contract_allowance = (
|
|
1755
|
+
registry_contracts.erc20.get_instance(
|
|
1756
|
+
ledger_api=sftxb.ledger_api,
|
|
1757
|
+
contract_address=token_contract,
|
|
1758
|
+
)
|
|
1759
|
+
.functions.allowance(
|
|
1760
|
+
sftxb.safe,
|
|
1761
|
+
target_staking_contract,
|
|
1762
|
+
)
|
|
1763
|
+
.call()
|
|
1764
|
+
)
|
|
1765
|
+
self.logger.info(
|
|
1766
|
+
f"Approved {staking_contract_allowance} (token {token_contract}) from {sftxb.safe} to {target_staking_contract}"
|
|
1767
|
+
)
|
|
1768
|
+
|
|
1769
|
+
self.logger.info(f"Staking service: {chain_config.chain_data.token}")
|
|
1770
|
+
sftxb.new_tx().add(
|
|
1771
|
+
sftxb.get_staking_data(
|
|
1772
|
+
service_id=chain_config.chain_data.token,
|
|
1773
|
+
staking_contract=target_staking_contract,
|
|
1774
|
+
)
|
|
1775
|
+
).settle()
|
|
1776
|
+
|
|
1777
|
+
current_staking_program = self._get_current_staking_program(
|
|
1778
|
+
service,
|
|
1779
|
+
chain,
|
|
784
1780
|
)
|
|
1781
|
+
self.logger.info(f"{target_staking_program=}")
|
|
1782
|
+
self.logger.info(f"{current_staking_program=}")
|
|
1783
|
+
|
|
1784
|
+
def unstake_service_on_chain(
|
|
1785
|
+
self, service_config_id: str, chain: t.Optional[str] = None
|
|
1786
|
+
) -> None:
|
|
1787
|
+
"""Unbond service on-chain"""
|
|
1788
|
+
# TODO This method has not been thoroughly reviewed. Deprecated usage in favour of Safe version.
|
|
1789
|
+
|
|
1790
|
+
service = self.load(service_config_id=service_config_id)
|
|
1791
|
+
chain_config = service.chain_configs[chain or service.home_chain]
|
|
1792
|
+
ledger_config = chain_config.ledger_config
|
|
1793
|
+
chain_data = chain_config.chain_data
|
|
1794
|
+
ocm = self.get_on_chain_manager(ledger_config=ledger_config)
|
|
1795
|
+
|
|
1796
|
+
state = ocm.staking_status(
|
|
1797
|
+
service_id=chain_data.token,
|
|
1798
|
+
staking_contract=get_staking_contract(
|
|
1799
|
+
chain=ledger_config.chain,
|
|
1800
|
+
staking_program_id=chain_data.user_params.staking_program_id,
|
|
1801
|
+
),
|
|
1802
|
+
)
|
|
1803
|
+
self.logger.info(f"Staking status for service {chain_data.token}: {state}")
|
|
785
1804
|
if state not in {StakingState.STAKED, StakingState.EVICTED}:
|
|
786
1805
|
self.logger.info("Cannot unstake service, it's not staked")
|
|
787
|
-
service.chain_data.staked = False
|
|
788
|
-
service.store()
|
|
789
1806
|
return
|
|
790
1807
|
|
|
791
|
-
self.logger.info(f"Unstaking service: {
|
|
1808
|
+
self.logger.info(f"Unstaking service: {chain_data.token}")
|
|
792
1809
|
ocm.unstake(
|
|
793
|
-
service_id=
|
|
794
|
-
staking_contract=
|
|
1810
|
+
service_id=chain_data.token,
|
|
1811
|
+
staking_contract=get_staking_contract(
|
|
1812
|
+
chain=ledger_config.chain,
|
|
1813
|
+
staking_program_id=chain_data.user_params.staking_program_id,
|
|
1814
|
+
),
|
|
795
1815
|
)
|
|
796
|
-
service.chain_data.staked = False
|
|
797
|
-
service.store()
|
|
798
1816
|
|
|
799
|
-
def unstake_service_on_chain_from_safe(
|
|
800
|
-
|
|
801
|
-
|
|
1817
|
+
def unstake_service_on_chain_from_safe(
|
|
1818
|
+
self,
|
|
1819
|
+
service_config_id: str,
|
|
1820
|
+
chain: str,
|
|
1821
|
+
staking_program_id: t.Optional[str] = None,
|
|
1822
|
+
force: bool = False,
|
|
1823
|
+
) -> None:
|
|
1824
|
+
"""Unstake service on-chain"""
|
|
1825
|
+
# Claim the rewards first so that they are moved to the Master Safe
|
|
1826
|
+
self.claim_on_chain_from_safe(
|
|
1827
|
+
service_config_id=service_config_id,
|
|
1828
|
+
chain=chain,
|
|
1829
|
+
)
|
|
802
1830
|
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
1831
|
+
self.logger.info("unstake_service_on_chain_from_safe")
|
|
1832
|
+
service = self.load(service_config_id=service_config_id)
|
|
1833
|
+
chain_config = service.chain_configs[chain]
|
|
1834
|
+
ledger_config = chain_config.ledger_config
|
|
1835
|
+
chain_data = chain_config.chain_data
|
|
1836
|
+
|
|
1837
|
+
if staking_program_id is None:
|
|
1838
|
+
self.logger.info(
|
|
1839
|
+
"Cannot unstake service, `staking_program_id` is set to None"
|
|
1840
|
+
)
|
|
808
1841
|
return
|
|
809
1842
|
|
|
810
|
-
sftxb = self.get_eth_safe_tx_builder(
|
|
1843
|
+
sftxb = self.get_eth_safe_tx_builder(ledger_config=ledger_config)
|
|
811
1844
|
state = sftxb.staking_status(
|
|
812
|
-
service_id=
|
|
813
|
-
staking_contract=
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
1845
|
+
service_id=chain_data.token,
|
|
1846
|
+
staking_contract=get_staking_contract(
|
|
1847
|
+
chain=ledger_config.chain,
|
|
1848
|
+
staking_program_id=staking_program_id,
|
|
1849
|
+
),
|
|
817
1850
|
)
|
|
1851
|
+
self.logger.info(f"Staking status for service {chain_data.token}: {state}")
|
|
818
1852
|
if state not in {StakingState.STAKED, StakingState.EVICTED}:
|
|
819
1853
|
self.logger.info("Cannot unstake service, it's not staked")
|
|
820
|
-
service.chain_data.staked = False
|
|
821
|
-
service.store()
|
|
822
1854
|
return
|
|
823
1855
|
|
|
824
|
-
self.logger.info(f"Unstaking service: {
|
|
1856
|
+
self.logger.info(f"Unstaking service: {chain_data.token}")
|
|
825
1857
|
sftxb.new_tx().add(
|
|
826
1858
|
sftxb.get_unstaking_data(
|
|
827
|
-
service_id=
|
|
828
|
-
staking_contract=
|
|
1859
|
+
service_id=chain_data.token,
|
|
1860
|
+
staking_contract=get_staking_contract(
|
|
1861
|
+
chain=ledger_config.chain,
|
|
1862
|
+
staking_program_id=staking_program_id,
|
|
1863
|
+
),
|
|
1864
|
+
force=force,
|
|
829
1865
|
)
|
|
830
1866
|
).settle()
|
|
831
|
-
service.chain_data.staked = False
|
|
832
|
-
service.store()
|
|
833
1867
|
|
|
834
|
-
def
|
|
1868
|
+
def claim_all_on_chain_from_safe(self) -> None:
|
|
1869
|
+
"""Claim rewards from all services and chains"""
|
|
1870
|
+
self.logger.info("claim_all_on_chain_from_safe")
|
|
1871
|
+
services, _ = self.get_all_services()
|
|
1872
|
+
for service in services:
|
|
1873
|
+
self.claim_on_chain_from_safe(
|
|
1874
|
+
service_config_id=service.service_config_id,
|
|
1875
|
+
chain=service.home_chain,
|
|
1876
|
+
)
|
|
1877
|
+
|
|
1878
|
+
def claim_on_chain_from_safe(
|
|
835
1879
|
self,
|
|
836
|
-
|
|
1880
|
+
service_config_id: str,
|
|
1881
|
+
chain: str,
|
|
1882
|
+
) -> int:
|
|
1883
|
+
"""Claim rewards from staking and returns the claimed amount"""
|
|
1884
|
+
self.logger.info("claim_on_chain_from_safe")
|
|
1885
|
+
service = self.load(service_config_id=service_config_id)
|
|
1886
|
+
chain_config = service.chain_configs[chain]
|
|
1887
|
+
ledger_config = chain_config.ledger_config
|
|
1888
|
+
wallet = self.wallet_manager.load(ledger_config.chain.ledger_type)
|
|
1889
|
+
ledger_api = wallet.ledger_api(chain=ledger_config.chain, rpc=ledger_config.rpc)
|
|
1890
|
+
|
|
1891
|
+
if (
|
|
1892
|
+
chain_config.chain_data.token == NON_EXISTENT_TOKEN
|
|
1893
|
+
or chain_config.chain_data.multisig == ZERO_ADDRESS
|
|
1894
|
+
):
|
|
1895
|
+
self.logger.info("Service is not minted or Safe not deployed.")
|
|
1896
|
+
return 0
|
|
1897
|
+
|
|
1898
|
+
self.logger.info(
|
|
1899
|
+
f"OLAS Balance on service Safe {chain_config.chain_data.multisig}: "
|
|
1900
|
+
f"{get_asset_balance(ledger_api, OLAS[Chain(chain)], chain_config.chain_data.multisig)}"
|
|
1901
|
+
)
|
|
1902
|
+
current_staking_program = self._get_current_staking_program(
|
|
1903
|
+
service=service, chain=chain
|
|
1904
|
+
)
|
|
1905
|
+
staking_contract = get_staking_contract(
|
|
1906
|
+
chain=ledger_config.chain,
|
|
1907
|
+
staking_program_id=current_staking_program,
|
|
1908
|
+
)
|
|
1909
|
+
if staking_contract is None:
|
|
1910
|
+
self.logger.warning(
|
|
1911
|
+
"No staking contract found for the "
|
|
1912
|
+
f"{current_staking_program=}. Not claiming the rewards."
|
|
1913
|
+
)
|
|
1914
|
+
return 0
|
|
1915
|
+
|
|
1916
|
+
sftxb = self.get_eth_safe_tx_builder(ledger_config=ledger_config)
|
|
1917
|
+
if not sftxb.staking_rewards_claimable(
|
|
1918
|
+
service_id=chain_config.chain_data.token,
|
|
1919
|
+
staking_contract=staking_contract,
|
|
1920
|
+
):
|
|
1921
|
+
self.logger.info("No staking rewards claimable")
|
|
1922
|
+
return 0
|
|
1923
|
+
|
|
1924
|
+
receipt = (
|
|
1925
|
+
sftxb.new_tx()
|
|
1926
|
+
.add(
|
|
1927
|
+
sftxb.get_claiming_data(
|
|
1928
|
+
service_id=chain_config.chain_data.token,
|
|
1929
|
+
staking_contract=staking_contract,
|
|
1930
|
+
)
|
|
1931
|
+
)
|
|
1932
|
+
.settle()
|
|
1933
|
+
)
|
|
1934
|
+
|
|
1935
|
+
if receipt.status != 1:
|
|
1936
|
+
self.logger.error(
|
|
1937
|
+
f"Failed to claim staking rewards. Tx hash: {receipt.tx_hash}"
|
|
1938
|
+
)
|
|
1939
|
+
return 0
|
|
1940
|
+
|
|
1941
|
+
# transfer claimed amount from agents safe to master safe
|
|
1942
|
+
# TODO: remove after staking contract directly starts sending the rewards to master safe
|
|
1943
|
+
amount_claimed = int(receipt["logs"][0]["data"].hex(), 16)
|
|
1944
|
+
self.logger.info(f"Claimed amount: {amount_claimed}")
|
|
1945
|
+
ethereum_crypto = self.keys_manager.get_crypto_instance(
|
|
1946
|
+
service.agent_addresses[0]
|
|
1947
|
+
)
|
|
1948
|
+
transfer_erc20_from_safe(
|
|
1949
|
+
ledger_api=ledger_api,
|
|
1950
|
+
crypto=ethereum_crypto,
|
|
1951
|
+
safe=chain_config.chain_data.multisig,
|
|
1952
|
+
token=receipt["logs"][0]["address"],
|
|
1953
|
+
to=wallet.safes[Chain(chain)],
|
|
1954
|
+
amount=amount_claimed,
|
|
1955
|
+
)
|
|
1956
|
+
return amount_claimed
|
|
1957
|
+
|
|
1958
|
+
def fund_service( # pylint: disable=too-many-arguments,too-many-locals
|
|
1959
|
+
self,
|
|
1960
|
+
service_config_id: str,
|
|
1961
|
+
amounts: ChainAmounts,
|
|
1962
|
+
) -> None:
|
|
1963
|
+
"""Fund service if required."""
|
|
1964
|
+
service = self.load(service_config_id=service_config_id)
|
|
1965
|
+
self.funding_manager.fund_service(service=service, amounts=amounts)
|
|
1966
|
+
|
|
1967
|
+
# TODO deprecate
|
|
1968
|
+
def fund_service_single_chain( # pylint: disable=too-many-arguments,too-many-locals,too-many-statements
|
|
1969
|
+
self,
|
|
1970
|
+
service_config_id: str,
|
|
1971
|
+
rpc: t.Optional[str] = None,
|
|
1972
|
+
funding_values: t.Optional[FundingValues] = None,
|
|
1973
|
+
from_safe: bool = True,
|
|
1974
|
+
chain: str = "gnosis",
|
|
1975
|
+
) -> None:
|
|
1976
|
+
"""Fund service if required."""
|
|
1977
|
+
|
|
1978
|
+
service = self.load(service_config_id=service_config_id)
|
|
1979
|
+
chain_config = service.chain_configs[chain]
|
|
1980
|
+
ledger_config = chain_config.ledger_config
|
|
1981
|
+
chain_data = chain_config.chain_data
|
|
1982
|
+
wallet = self.wallet_manager.load(ledger_config.chain.ledger_type)
|
|
1983
|
+
ledger_api = wallet.ledger_api(
|
|
1984
|
+
chain=ledger_config.chain, rpc=rpc or ledger_config.rpc
|
|
1985
|
+
)
|
|
1986
|
+
|
|
1987
|
+
for (
|
|
1988
|
+
asset_address,
|
|
1989
|
+
fund_requirements,
|
|
1990
|
+
) in chain_data.user_params.fund_requirements.items():
|
|
1991
|
+
on_chain_operations_buffer = 0
|
|
1992
|
+
if asset_address == ZERO_ADDRESS:
|
|
1993
|
+
on_chain_state = self._get_on_chain_state(service=service, chain=chain)
|
|
1994
|
+
if on_chain_state != OnChainState.DEPLOYED:
|
|
1995
|
+
if chain_data.user_params.use_staking:
|
|
1996
|
+
on_chain_operations_buffer = 1 + len(service.agent_addresses)
|
|
1997
|
+
else:
|
|
1998
|
+
on_chain_operations_buffer = (
|
|
1999
|
+
chain_data.user_params.cost_of_bond
|
|
2000
|
+
* (
|
|
2001
|
+
MIN_SECURITY_DEPOSIT
|
|
2002
|
+
+ MIN_AGENT_BOND * len(service.agent_addresses)
|
|
2003
|
+
)
|
|
2004
|
+
)
|
|
2005
|
+
|
|
2006
|
+
asset_funding_values = (
|
|
2007
|
+
funding_values.get(asset_address)
|
|
2008
|
+
if funding_values is not None
|
|
2009
|
+
else None
|
|
2010
|
+
)
|
|
2011
|
+
agent_fund_threshold = (
|
|
2012
|
+
asset_funding_values["agent"]["threshold"]
|
|
2013
|
+
if asset_funding_values is not None
|
|
2014
|
+
else fund_requirements.agent
|
|
2015
|
+
)
|
|
2016
|
+
|
|
2017
|
+
for agent_address in service.agent_addresses:
|
|
2018
|
+
agent_balance = get_asset_balance(
|
|
2019
|
+
ledger_api=ledger_api,
|
|
2020
|
+
asset_address=asset_address,
|
|
2021
|
+
address=agent_address,
|
|
2022
|
+
)
|
|
2023
|
+
self.logger.info(
|
|
2024
|
+
f"[FUNDING_JOB] Agent {agent_address} Asset: {asset_address} balance: {agent_balance}"
|
|
2025
|
+
)
|
|
2026
|
+
if agent_fund_threshold > 0:
|
|
2027
|
+
self.logger.info(
|
|
2028
|
+
f"[FUNDING_JOB] Required balance: {agent_fund_threshold}"
|
|
2029
|
+
)
|
|
2030
|
+
if agent_balance < agent_fund_threshold:
|
|
2031
|
+
self.logger.info(f"[FUNDING_JOB] Funding agent {agent_address}")
|
|
2032
|
+
target_balance = (
|
|
2033
|
+
asset_funding_values["agent"]["topup"]
|
|
2034
|
+
if asset_funding_values is not None
|
|
2035
|
+
else fund_requirements.agent
|
|
2036
|
+
)
|
|
2037
|
+
available_balance = get_asset_balance(
|
|
2038
|
+
ledger_api=ledger_api,
|
|
2039
|
+
asset_address=asset_address,
|
|
2040
|
+
address=wallet.safes[ledger_config.chain],
|
|
2041
|
+
)
|
|
2042
|
+
available_balance = max(
|
|
2043
|
+
available_balance - on_chain_operations_buffer, 0
|
|
2044
|
+
)
|
|
2045
|
+
to_transfer = max(
|
|
2046
|
+
min(available_balance, target_balance - agent_balance), 0
|
|
2047
|
+
)
|
|
2048
|
+
if to_transfer <= 0:
|
|
2049
|
+
continue
|
|
2050
|
+
|
|
2051
|
+
self.logger.info(
|
|
2052
|
+
f"[FUNDING_JOB] Transferring {to_transfer} units (asset {asset_address}) to agent {agent_address}"
|
|
2053
|
+
)
|
|
2054
|
+
wallet.transfer(
|
|
2055
|
+
asset=asset_address,
|
|
2056
|
+
to=agent_address,
|
|
2057
|
+
amount=int(to_transfer),
|
|
2058
|
+
chain=ledger_config.chain,
|
|
2059
|
+
from_safe=from_safe,
|
|
2060
|
+
rpc=rpc or ledger_config.rpc,
|
|
2061
|
+
)
|
|
2062
|
+
|
|
2063
|
+
if chain_data.multisig == NON_EXISTENT_MULTISIG:
|
|
2064
|
+
self.logger.info("[FUNDING_JOB] Service Safe not deployed")
|
|
2065
|
+
continue
|
|
2066
|
+
|
|
2067
|
+
safe_balance = get_asset_balance(
|
|
2068
|
+
ledger_api=ledger_api,
|
|
2069
|
+
asset_address=asset_address,
|
|
2070
|
+
address=chain_data.multisig,
|
|
2071
|
+
)
|
|
2072
|
+
if asset_address == ZERO_ADDRESS and chain in WRAPPED_NATIVE_ASSET:
|
|
2073
|
+
# also count the balance of the wrapped native asset
|
|
2074
|
+
safe_balance += get_asset_balance(
|
|
2075
|
+
ledger_api=ledger_api,
|
|
2076
|
+
asset_address=WRAPPED_NATIVE_ASSET[Chain(chain)],
|
|
2077
|
+
address=chain_data.multisig,
|
|
2078
|
+
)
|
|
2079
|
+
|
|
2080
|
+
safe_fund_treshold = (
|
|
2081
|
+
asset_funding_values["safe"]["threshold"]
|
|
2082
|
+
if asset_funding_values is not None
|
|
2083
|
+
else fund_requirements.safe
|
|
2084
|
+
)
|
|
2085
|
+
self.logger.info(
|
|
2086
|
+
f"[FUNDING_JOB] Safe {chain_data.multisig} Asset: {asset_address} balance: {safe_balance}"
|
|
2087
|
+
)
|
|
2088
|
+
self.logger.info(f"[FUNDING_JOB] Required balance: {safe_fund_treshold}")
|
|
2089
|
+
if safe_balance < safe_fund_treshold:
|
|
2090
|
+
self.logger.info("[FUNDING_JOB] Funding safe")
|
|
2091
|
+
target_balance = (
|
|
2092
|
+
asset_funding_values["safe"]["topup"]
|
|
2093
|
+
if asset_funding_values is not None
|
|
2094
|
+
else fund_requirements.safe
|
|
2095
|
+
)
|
|
2096
|
+
available_balance = get_asset_balance(
|
|
2097
|
+
ledger_api=ledger_api,
|
|
2098
|
+
asset_address=asset_address,
|
|
2099
|
+
address=wallet.safes[ledger_config.chain],
|
|
2100
|
+
)
|
|
2101
|
+
available_balance = max(
|
|
2102
|
+
available_balance - on_chain_operations_buffer, 0
|
|
2103
|
+
)
|
|
2104
|
+
to_transfer = max(
|
|
2105
|
+
min(available_balance, target_balance - safe_balance), 0
|
|
2106
|
+
)
|
|
2107
|
+
|
|
2108
|
+
# TODO Possibly remove this logging
|
|
2109
|
+
self.logger.info(f"{available_balance=}")
|
|
2110
|
+
self.logger.info(f"{target_balance=}")
|
|
2111
|
+
self.logger.info(f"{safe_balance=}")
|
|
2112
|
+
self.logger.info(f"{to_transfer=}")
|
|
2113
|
+
|
|
2114
|
+
if to_transfer > 0:
|
|
2115
|
+
self.logger.info(
|
|
2116
|
+
f"[FUNDING_JOB] Transferring {to_transfer} units (asset {asset_address}) to {chain_data.multisig}"
|
|
2117
|
+
)
|
|
2118
|
+
# TODO: This is a temporary fix
|
|
2119
|
+
# we avoid the error here because there is a seperate prompt on the UI
|
|
2120
|
+
# when not enough funds are present, and the FE doesn't let the user to start the agent.
|
|
2121
|
+
# Ideally this error should be allowed, and then the FE should ask the user for more funds.
|
|
2122
|
+
with suppress(RuntimeError):
|
|
2123
|
+
wallet.transfer(
|
|
2124
|
+
asset=asset_address,
|
|
2125
|
+
to=t.cast(str, chain_data.multisig),
|
|
2126
|
+
amount=int(to_transfer),
|
|
2127
|
+
chain=ledger_config.chain,
|
|
2128
|
+
rpc=rpc or ledger_config.rpc,
|
|
2129
|
+
)
|
|
2130
|
+
|
|
2131
|
+
# TODO Deprecate
|
|
2132
|
+
# TODO This method is possibly not used anymore
|
|
2133
|
+
def fund_service_erc20( # pylint: disable=too-many-arguments,too-many-locals
|
|
2134
|
+
self,
|
|
2135
|
+
service_config_id: str,
|
|
2136
|
+
token: str,
|
|
837
2137
|
rpc: t.Optional[str] = None,
|
|
838
2138
|
agent_topup: t.Optional[float] = None,
|
|
839
2139
|
safe_topup: t.Optional[float] = None,
|
|
840
2140
|
agent_fund_threshold: t.Optional[float] = None,
|
|
841
2141
|
safe_fund_treshold: t.Optional[float] = None,
|
|
842
2142
|
from_safe: bool = True,
|
|
2143
|
+
chain: str = "gnosis",
|
|
843
2144
|
) -> None:
|
|
844
2145
|
"""Fund service if required."""
|
|
845
|
-
service = self.
|
|
846
|
-
|
|
847
|
-
|
|
2146
|
+
service = self.load(service_config_id=service_config_id)
|
|
2147
|
+
chain_config = service.chain_configs[chain]
|
|
2148
|
+
ledger_config = chain_config.ledger_config
|
|
2149
|
+
chain_data = chain_config.chain_data
|
|
2150
|
+
wallet = self.wallet_manager.load(ledger_config.chain.ledger_type)
|
|
2151
|
+
ledger_api = wallet.ledger_api(
|
|
2152
|
+
chain=ledger_config.chain, rpc=rpc or ledger_config.rpc
|
|
2153
|
+
)
|
|
848
2154
|
agent_fund_threshold = (
|
|
849
2155
|
agent_fund_threshold
|
|
850
|
-
or
|
|
2156
|
+
or chain_data.user_params.fund_requirements[ZERO_ADDRESS].agent
|
|
851
2157
|
)
|
|
852
2158
|
|
|
853
|
-
for
|
|
854
|
-
agent_balance = ledger_api.get_balance(address=
|
|
855
|
-
self.logger.info(f"Agent {
|
|
2159
|
+
for agent_address in service.agent_addresses:
|
|
2160
|
+
agent_balance = ledger_api.get_balance(address=agent_address)
|
|
2161
|
+
self.logger.info(f"Agent {agent_address} balance: {agent_balance}")
|
|
856
2162
|
self.logger.info(f"Required balance: {agent_fund_threshold}")
|
|
857
2163
|
if agent_balance < agent_fund_threshold:
|
|
858
2164
|
self.logger.info("Funding agents")
|
|
859
2165
|
to_transfer = (
|
|
860
2166
|
agent_topup
|
|
861
|
-
or
|
|
2167
|
+
or chain_data.user_params.fund_requirements[ZERO_ADDRESS].agent
|
|
862
2168
|
)
|
|
863
|
-
|
|
2169
|
+
if to_transfer <= 0:
|
|
2170
|
+
continue
|
|
2171
|
+
|
|
2172
|
+
self.logger.info(f"Transferring {to_transfer} units to {agent_address}")
|
|
864
2173
|
wallet.transfer(
|
|
865
|
-
|
|
2174
|
+
asset=token,
|
|
2175
|
+
to=agent_address,
|
|
866
2176
|
amount=int(to_transfer),
|
|
867
|
-
|
|
2177
|
+
chain=ledger_config.chain,
|
|
868
2178
|
from_safe=from_safe,
|
|
2179
|
+
rpc=rpc or ledger_config.rpc,
|
|
869
2180
|
)
|
|
870
2181
|
|
|
871
|
-
|
|
2182
|
+
safe_balance = (
|
|
2183
|
+
registry_contracts.erc20.get_instance(ledger_api, token)
|
|
2184
|
+
.functions.balanceOf(chain_data.multisig)
|
|
2185
|
+
.call()
|
|
2186
|
+
)
|
|
872
2187
|
safe_fund_treshold = (
|
|
873
|
-
safe_fund_treshold
|
|
2188
|
+
safe_fund_treshold
|
|
2189
|
+
or chain_data.user_params.fund_requirements[ZERO_ADDRESS].safe
|
|
874
2190
|
)
|
|
875
|
-
self.logger.info(f"Safe {
|
|
2191
|
+
self.logger.info(f"Safe {chain_data.multisig} balance: {safe_balance}")
|
|
876
2192
|
self.logger.info(f"Required balance: {safe_fund_treshold}")
|
|
877
|
-
if
|
|
2193
|
+
if safe_balance < safe_fund_treshold:
|
|
878
2194
|
self.logger.info("Funding safe")
|
|
879
2195
|
to_transfer = (
|
|
880
|
-
safe_topup
|
|
2196
|
+
safe_topup
|
|
2197
|
+
or chain_data.user_params.fund_requirements[ZERO_ADDRESS].safe
|
|
881
2198
|
)
|
|
2199
|
+
if to_transfer <= 0:
|
|
2200
|
+
return
|
|
2201
|
+
|
|
882
2202
|
self.logger.info(
|
|
883
|
-
f"Transferring {to_transfer} units to {
|
|
2203
|
+
f"Transferring {to_transfer} units to {chain_data.multisig}"
|
|
884
2204
|
)
|
|
885
2205
|
wallet.transfer(
|
|
886
|
-
|
|
2206
|
+
asset=token,
|
|
2207
|
+
to=t.cast(str, chain_data.multisig),
|
|
887
2208
|
amount=int(to_transfer),
|
|
888
|
-
|
|
2209
|
+
chain=ledger_config.chain,
|
|
2210
|
+
rpc=rpc or ledger_config.rpc,
|
|
889
2211
|
)
|
|
890
2212
|
|
|
891
|
-
|
|
892
|
-
self,
|
|
893
|
-
hash: str,
|
|
894
|
-
loop: t.Optional[asyncio.AbstractEventLoop] = None,
|
|
895
|
-
from_safe: bool = True,
|
|
2213
|
+
def drain(
|
|
2214
|
+
self, service_config_id: str, chain_str: str, withdrawal_address: str
|
|
896
2215
|
) -> None:
|
|
897
|
-
"""
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
50000000000000000, # agent_fund_threshold
|
|
911
|
-
500000000000000000, # safe_fund_treshold
|
|
912
|
-
from_safe,
|
|
913
|
-
)
|
|
914
|
-
except Exception: # pylint: disable=broad-except
|
|
915
|
-
logging.info(
|
|
916
|
-
f"Error occured while funding the service\n{traceback.format_exc()}"
|
|
917
|
-
)
|
|
918
|
-
await asyncio.sleep(60)
|
|
2216
|
+
"""Drain service safe and agent EOAs."""
|
|
2217
|
+
service = self.load(service_config_id=service_config_id)
|
|
2218
|
+
chain = Chain(chain_str)
|
|
2219
|
+
self.funding_manager.drain_service_safe(
|
|
2220
|
+
service=service,
|
|
2221
|
+
withdrawal_address=withdrawal_address,
|
|
2222
|
+
chain=chain,
|
|
2223
|
+
)
|
|
2224
|
+
self.funding_manager.drain_agents_eoas(
|
|
2225
|
+
service=service,
|
|
2226
|
+
withdrawal_address=withdrawal_address,
|
|
2227
|
+
chain=chain,
|
|
2228
|
+
)
|
|
919
2229
|
|
|
920
|
-
|
|
2230
|
+
def deploy_service_locally( # pylint: disable=too-many-arguments
|
|
921
2231
|
self,
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
try:
|
|
929
|
-
# Check the service health
|
|
930
|
-
healthy = await check_service_health()
|
|
931
|
-
# Restart the service if the health failed 5 times in a row
|
|
932
|
-
if not healthy:
|
|
933
|
-
failed_health_checks += 1
|
|
934
|
-
else:
|
|
935
|
-
failed_health_checks = 0
|
|
936
|
-
if failed_health_checks >= 4:
|
|
937
|
-
self.stop_service_locally(hash=hash)
|
|
938
|
-
self.deploy_service_locally(hash=hash)
|
|
939
|
-
|
|
940
|
-
except Exception: # pylint: disable=broad-except
|
|
941
|
-
logging.info(
|
|
942
|
-
f"Error occured while checking the service health\n{traceback.format_exc()}"
|
|
943
|
-
)
|
|
944
|
-
await asyncio.sleep(30)
|
|
945
|
-
|
|
946
|
-
def deploy_service_locally(self, hash: str, force: bool = True) -> Deployment:
|
|
2232
|
+
service_config_id: str,
|
|
2233
|
+
chain: t.Optional[str] = None,
|
|
2234
|
+
use_docker: bool = False,
|
|
2235
|
+
use_kubernetes: bool = False,
|
|
2236
|
+
build_only: bool = False,
|
|
2237
|
+
) -> Deployment:
|
|
947
2238
|
"""
|
|
948
2239
|
Deploy service locally
|
|
949
2240
|
|
|
950
2241
|
:param hash: Service hash
|
|
951
|
-
:param
|
|
2242
|
+
:param chain: Chain to set runtime parameters on the deployment (home_chain if not provided).
|
|
2243
|
+
:param use_docker: Use a Docker Compose deployment (True) or Host deployment (False).
|
|
2244
|
+
:param use_kubernetes: Use Kubernetes for deployment
|
|
2245
|
+
:param build_only: Only build the deployment without starting it
|
|
952
2246
|
:return: Deployment instance
|
|
953
2247
|
"""
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
deployment.
|
|
2248
|
+
service = self.load(service_config_id=service_config_id)
|
|
2249
|
+
|
|
2250
|
+
deployment = service.deployment
|
|
2251
|
+
deployment.build(
|
|
2252
|
+
use_docker=use_docker,
|
|
2253
|
+
use_kubernetes=use_kubernetes,
|
|
2254
|
+
force=True,
|
|
2255
|
+
chain=chain or service.home_chain,
|
|
2256
|
+
keys_manager=self.keys_manager,
|
|
2257
|
+
)
|
|
2258
|
+
if build_only:
|
|
2259
|
+
return deployment
|
|
2260
|
+
deployment.start(
|
|
2261
|
+
password=self.wallet_manager.password,
|
|
2262
|
+
use_docker=use_docker,
|
|
2263
|
+
is_aea=service.agent_release["is_aea"],
|
|
2264
|
+
)
|
|
957
2265
|
return deployment
|
|
958
2266
|
|
|
959
|
-
def stop_service_locally(
|
|
2267
|
+
def stop_service_locally(
|
|
2268
|
+
self,
|
|
2269
|
+
service_config_id: str,
|
|
2270
|
+
delete: bool = False,
|
|
2271
|
+
use_docker: bool = False,
|
|
2272
|
+
force: bool = False,
|
|
2273
|
+
) -> Deployment:
|
|
960
2274
|
"""
|
|
961
2275
|
Stop service locally
|
|
962
2276
|
|
|
963
|
-
:param
|
|
2277
|
+
:param service_id: Service id
|
|
964
2278
|
:param delete: Delete local deployment.
|
|
965
2279
|
:return: Deployment instance
|
|
966
2280
|
"""
|
|
967
|
-
|
|
968
|
-
|
|
2281
|
+
service = self.load(service_config_id=service_config_id)
|
|
2282
|
+
service.remove_latest_healthcheck()
|
|
2283
|
+
deployment = service.deployment
|
|
2284
|
+
deployment.stop(
|
|
2285
|
+
use_docker=use_docker,
|
|
2286
|
+
force=force,
|
|
2287
|
+
is_aea=service.agent_release["is_aea"],
|
|
2288
|
+
)
|
|
969
2289
|
if delete:
|
|
970
2290
|
deployment.delete()
|
|
971
2291
|
return deployment
|
|
972
2292
|
|
|
973
|
-
def
|
|
2293
|
+
def update(
|
|
974
2294
|
self,
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
from_safe: bool = True, # pylint: disable=unused-argument
|
|
2295
|
+
service_config_id: str,
|
|
2296
|
+
service_template: ServiceTemplate,
|
|
2297
|
+
allow_different_service_public_id: bool = False,
|
|
2298
|
+
partial_update: bool = True,
|
|
980
2299
|
) -> Service:
|
|
981
2300
|
"""Update a service."""
|
|
982
|
-
|
|
983
|
-
|
|
2301
|
+
|
|
2302
|
+
self.logger.info(f"Updating {service_config_id=}")
|
|
2303
|
+
service = self.load(service_config_id=service_config_id)
|
|
2304
|
+
service.update(
|
|
2305
|
+
service_template=service_template,
|
|
2306
|
+
allow_different_service_public_id=allow_different_service_public_id,
|
|
2307
|
+
partial_update=partial_update,
|
|
984
2308
|
)
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
2309
|
+
return service
|
|
2310
|
+
|
|
2311
|
+
def funding_requirements( # pylint: disable=too-many-locals,too-many-statements,too-many-nested-blocks
|
|
2312
|
+
self, service_config_id: str
|
|
2313
|
+
) -> t.Dict:
|
|
2314
|
+
"""Get the funding requirements for a service."""
|
|
2315
|
+
service = self.load(service_config_id=service_config_id)
|
|
2316
|
+
return self.funding_manager.funding_requirements(service)
|
|
2317
|
+
|
|
2318
|
+
# TODO deprecate
|
|
2319
|
+
def refill_requirements( # pylint: disable=too-many-locals,too-many-statements,too-many-nested-blocks
|
|
2320
|
+
self, service_config_id: str
|
|
2321
|
+
) -> t.Dict:
|
|
2322
|
+
"""Get user refill requirements for a service."""
|
|
2323
|
+
service = self.load(service_config_id=service_config_id)
|
|
2324
|
+
|
|
2325
|
+
balances: t.Dict = {}
|
|
2326
|
+
bonded_assets: t.Dict = {}
|
|
2327
|
+
protocol_asset_requirements: t.Dict = {}
|
|
2328
|
+
refill_requirements: t.Dict = {}
|
|
2329
|
+
total_requirements: t.Dict = {}
|
|
2330
|
+
allow_start_agent = True
|
|
2331
|
+
is_refill_required = False
|
|
2332
|
+
|
|
2333
|
+
for chain, chain_config in service.chain_configs.items():
|
|
2334
|
+
ledger_config = chain_config.ledger_config
|
|
2335
|
+
chain_data = chain_config.chain_data
|
|
2336
|
+
wallet = self.wallet_manager.load(ledger_config.chain.ledger_type)
|
|
2337
|
+
ledger_api = wallet.ledger_api(
|
|
2338
|
+
chain=ledger_config.chain, rpc=ledger_config.rpc
|
|
2339
|
+
)
|
|
2340
|
+
os.environ["CUSTOM_CHAIN_RPC"] = ledger_config.rpc
|
|
2341
|
+
|
|
2342
|
+
master_eoa = wallet.address
|
|
2343
|
+
master_safe_exists = wallet.safes.get(Chain(chain)) is not None
|
|
2344
|
+
master_safe = wallet.safes.get(Chain(chain), "master_safe")
|
|
2345
|
+
|
|
2346
|
+
agent_addresses = set(service.agent_addresses)
|
|
2347
|
+
service_safe = (
|
|
2348
|
+
chain_data.multisig if chain_data.multisig else "service_safe"
|
|
2349
|
+
)
|
|
2350
|
+
|
|
2351
|
+
if not master_safe_exists:
|
|
2352
|
+
allow_start_agent = False
|
|
2353
|
+
|
|
2354
|
+
# Protocol asset requirements
|
|
2355
|
+
protocol_asset_requirements[
|
|
2356
|
+
chain
|
|
2357
|
+
] = self._compute_protocol_asset_requirements(service_config_id, chain)
|
|
2358
|
+
service_asset_requirements = chain_data.user_params.fund_requirements
|
|
2359
|
+
|
|
2360
|
+
# Bonded assets
|
|
2361
|
+
bonded_assets[chain] = self._compute_bonded_assets(service_config_id, chain)
|
|
2362
|
+
|
|
2363
|
+
# Balances
|
|
2364
|
+
addresses = agent_addresses | {service_safe, master_eoa, master_safe}
|
|
2365
|
+
asset_addresses = (
|
|
2366
|
+
{ZERO_ADDRESS}
|
|
2367
|
+
| service_asset_requirements.keys()
|
|
2368
|
+
| protocol_asset_requirements[chain].keys()
|
|
2369
|
+
| bonded_assets[chain].keys()
|
|
2370
|
+
)
|
|
2371
|
+
|
|
2372
|
+
balances[chain] = get_assets_balances(
|
|
2373
|
+
ledger_api=ledger_api,
|
|
2374
|
+
addresses=addresses,
|
|
2375
|
+
asset_addresses=asset_addresses,
|
|
2376
|
+
raise_on_invalid_address=False,
|
|
2377
|
+
)
|
|
2378
|
+
|
|
2379
|
+
# TODO this is a patch for the case when excess balance is in MasterEOA
|
|
2380
|
+
# and MasterSafe is not created (typically for onboarding bridging).
|
|
2381
|
+
# It simulates the "balance in the future" for both addesses when
|
|
2382
|
+
# transfering the excess assets.
|
|
2383
|
+
if master_safe == "master_safe":
|
|
2384
|
+
eoa_funding_values = self.get_master_eoa_native_funding_values(
|
|
2385
|
+
master_safe_exists=master_safe_exists,
|
|
2386
|
+
chain=Chain(chain),
|
|
2387
|
+
balance=balances[chain][master_eoa][ZERO_ADDRESS],
|
|
2388
|
+
)
|
|
2389
|
+
|
|
2390
|
+
for asset in balances[chain][master_safe]:
|
|
2391
|
+
if asset == ZERO_ADDRESS:
|
|
2392
|
+
balances[chain][master_safe][asset] = max(
|
|
2393
|
+
balances[chain][master_eoa][asset]
|
|
2394
|
+
- eoa_funding_values["topup"],
|
|
2395
|
+
0,
|
|
2396
|
+
)
|
|
2397
|
+
balances[chain][master_eoa][asset] = min(
|
|
2398
|
+
balances[chain][master_eoa][asset],
|
|
2399
|
+
eoa_funding_values["topup"],
|
|
2400
|
+
)
|
|
2401
|
+
else:
|
|
2402
|
+
balances[chain][master_safe][asset] = balances[chain][
|
|
2403
|
+
master_eoa
|
|
2404
|
+
][asset]
|
|
2405
|
+
balances[chain][master_eoa][asset] = 0
|
|
2406
|
+
|
|
2407
|
+
# TODO this is a balances patch to count wrapped native asset as
|
|
2408
|
+
# native assets for the service safe
|
|
2409
|
+
if Chain(chain) in WRAPPED_NATIVE_ASSET:
|
|
2410
|
+
if WRAPPED_NATIVE_ASSET[Chain(chain)] not in asset_addresses:
|
|
2411
|
+
balances[chain][service_safe][ZERO_ADDRESS] += get_asset_balance(
|
|
2412
|
+
ledger_api=ledger_api,
|
|
2413
|
+
asset_address=WRAPPED_NATIVE_ASSET[Chain(chain)],
|
|
2414
|
+
address=service_safe,
|
|
2415
|
+
raise_on_invalid_address=False,
|
|
2416
|
+
)
|
|
2417
|
+
|
|
2418
|
+
# Refill requirements
|
|
2419
|
+
refill_requirements[chain] = {}
|
|
2420
|
+
total_requirements[chain] = {}
|
|
2421
|
+
|
|
2422
|
+
# Refill requirements for Master Safe
|
|
2423
|
+
for asset_address in (
|
|
2424
|
+
service_asset_requirements.keys()
|
|
2425
|
+
| protocol_asset_requirements[chain].keys()
|
|
2426
|
+
):
|
|
2427
|
+
agent_asset_funding_values = {}
|
|
2428
|
+
if asset_address in service_asset_requirements:
|
|
2429
|
+
fund_requirements = service_asset_requirements[asset_address]
|
|
2430
|
+
agent_asset_funding_values = {
|
|
2431
|
+
address: {
|
|
2432
|
+
"topup": fund_requirements.agent,
|
|
2433
|
+
"threshold": int(
|
|
2434
|
+
fund_requirements.agent * DEFAULT_TOPUP_THRESHOLD
|
|
2435
|
+
), # TODO make threshold configurable
|
|
2436
|
+
"balance": balances[chain][address][asset_address],
|
|
2437
|
+
}
|
|
2438
|
+
for address in agent_addresses
|
|
2439
|
+
}
|
|
2440
|
+
agent_asset_funding_values[service_safe] = {
|
|
2441
|
+
"topup": fund_requirements.safe,
|
|
2442
|
+
"threshold": int(
|
|
2443
|
+
fund_requirements.safe * DEFAULT_TOPUP_THRESHOLD
|
|
2444
|
+
), # TODO make threshold configurable
|
|
2445
|
+
"balance": balances[chain][service_safe][asset_address],
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
recommended_refill = self._compute_refill_requirement(
|
|
2449
|
+
asset_funding_values=agent_asset_funding_values,
|
|
2450
|
+
sender_topup=protocol_asset_requirements[chain].get(
|
|
2451
|
+
asset_address, 0
|
|
2452
|
+
),
|
|
2453
|
+
sender_threshold=protocol_asset_requirements[chain].get(
|
|
2454
|
+
asset_address, 0
|
|
2455
|
+
),
|
|
2456
|
+
sender_balance=balances[chain][master_safe][asset_address]
|
|
2457
|
+
+ bonded_assets[chain].get(asset_address, 0),
|
|
2458
|
+
)["recommended_refill"]
|
|
2459
|
+
|
|
2460
|
+
refill_requirements[chain].setdefault(master_safe, {})[
|
|
2461
|
+
asset_address
|
|
2462
|
+
] = recommended_refill
|
|
2463
|
+
|
|
2464
|
+
total_requirements[chain].setdefault(master_safe, {})[
|
|
2465
|
+
asset_address
|
|
2466
|
+
] = sum(
|
|
2467
|
+
agent_asset_funding_values[address]["topup"]
|
|
2468
|
+
for address in agent_asset_funding_values
|
|
2469
|
+
) + protocol_asset_requirements[
|
|
2470
|
+
chain
|
|
2471
|
+
].get(
|
|
2472
|
+
asset_address, 0
|
|
2473
|
+
)
|
|
2474
|
+
|
|
2475
|
+
if asset_address == ZERO_ADDRESS and any(
|
|
2476
|
+
balances[chain][master_safe][asset_address] == 0
|
|
2477
|
+
and balances[chain][address][asset_address] == 0
|
|
2478
|
+
and agent_asset_funding_values[address]["threshold"] > 0
|
|
2479
|
+
for address in agent_asset_funding_values
|
|
2480
|
+
):
|
|
2481
|
+
allow_start_agent = False
|
|
2482
|
+
|
|
2483
|
+
# Refill requirements for Master EOA
|
|
2484
|
+
eoa_funding_values = self.get_master_eoa_native_funding_values(
|
|
2485
|
+
master_safe_exists=master_safe_exists,
|
|
2486
|
+
chain=Chain(chain),
|
|
2487
|
+
balance=balances[chain][master_eoa][ZERO_ADDRESS],
|
|
2488
|
+
)
|
|
2489
|
+
|
|
2490
|
+
eoa_recommended_refill = self._compute_refill_requirement(
|
|
2491
|
+
asset_funding_values={},
|
|
2492
|
+
sender_topup=eoa_funding_values["topup"],
|
|
2493
|
+
sender_threshold=eoa_funding_values["threshold"],
|
|
2494
|
+
sender_balance=balances[chain][master_eoa][ZERO_ADDRESS],
|
|
2495
|
+
)["recommended_refill"]
|
|
2496
|
+
|
|
2497
|
+
refill_requirements[chain].setdefault(master_eoa, {})[
|
|
2498
|
+
ZERO_ADDRESS
|
|
2499
|
+
] = eoa_recommended_refill
|
|
2500
|
+
|
|
2501
|
+
total_requirements[chain].setdefault(master_eoa, {})[
|
|
2502
|
+
ZERO_ADDRESS
|
|
2503
|
+
] = eoa_funding_values["topup"]
|
|
2504
|
+
|
|
2505
|
+
is_refill_required = any(
|
|
2506
|
+
amount > 0
|
|
2507
|
+
for chain in refill_requirements.values()
|
|
2508
|
+
for asset in chain.values()
|
|
2509
|
+
for amount in asset.values()
|
|
2510
|
+
)
|
|
2511
|
+
|
|
2512
|
+
return {
|
|
2513
|
+
"balances": balances,
|
|
2514
|
+
"bonded_assets": bonded_assets,
|
|
2515
|
+
"total_requirements": total_requirements,
|
|
2516
|
+
"refill_requirements": refill_requirements,
|
|
2517
|
+
"protocol_asset_requirements": protocol_asset_requirements,
|
|
2518
|
+
"is_refill_required": is_refill_required,
|
|
2519
|
+
"allow_start_agent": allow_start_agent,
|
|
2520
|
+
}
|
|
2521
|
+
|
|
2522
|
+
# TODO deprecate
|
|
2523
|
+
def _compute_bonded_assets( # pylint: disable=too-many-locals
|
|
2524
|
+
self, service_config_id: str, chain: str
|
|
2525
|
+
) -> t.Dict:
|
|
2526
|
+
"""Computes the bonded tokens: current agent bonds and current security deposit"""
|
|
2527
|
+
|
|
2528
|
+
service = self.load(service_config_id=service_config_id)
|
|
2529
|
+
chain_config = service.chain_configs[chain]
|
|
2530
|
+
ledger_config = chain_config.ledger_config
|
|
2531
|
+
user_params = chain_config.chain_data.user_params
|
|
2532
|
+
wallet = self.wallet_manager.load(ledger_config.chain.ledger_type)
|
|
2533
|
+
bonded_assets: defaultdict = defaultdict(int)
|
|
2534
|
+
|
|
2535
|
+
if Chain(chain) not in wallet.safes:
|
|
2536
|
+
return dict(bonded_assets)
|
|
2537
|
+
|
|
2538
|
+
master_safe = wallet.safes[Chain(chain)]
|
|
2539
|
+
|
|
2540
|
+
ledger_api = wallet.ledger_api(chain=ledger_config.chain, rpc=ledger_config.rpc)
|
|
2541
|
+
|
|
2542
|
+
service_id = chain_config.chain_data.token
|
|
2543
|
+
if service_id == NON_EXISTENT_TOKEN:
|
|
2544
|
+
return dict(bonded_assets)
|
|
2545
|
+
|
|
2546
|
+
os.environ["CUSTOM_CHAIN_RPC"] = ledger_config.rpc
|
|
2547
|
+
|
|
2548
|
+
# Determine bonded native amount
|
|
2549
|
+
service_registry_address = CHAIN_PROFILES[chain]["service_registry"]
|
|
2550
|
+
service_registry = registry_contracts.service_registry.get_instance(
|
|
2551
|
+
ledger_api=ledger_api,
|
|
2552
|
+
contract_address=service_registry_address,
|
|
2553
|
+
)
|
|
2554
|
+
service_info = service_registry.functions.getService(service_id).call()
|
|
2555
|
+
security_deposit = service_info[0]
|
|
2556
|
+
service_state = service_info[6]
|
|
2557
|
+
agent_ids = service_info[7]
|
|
2558
|
+
|
|
2559
|
+
if (
|
|
2560
|
+
OnChainState.ACTIVE_REGISTRATION
|
|
2561
|
+
<= service_state
|
|
2562
|
+
< OnChainState.TERMINATED_BONDED
|
|
2563
|
+
):
|
|
2564
|
+
bonded_assets[ZERO_ADDRESS] += security_deposit
|
|
2565
|
+
|
|
2566
|
+
operator_balance = service_registry.functions.getOperatorBalance(
|
|
2567
|
+
master_safe, service_id
|
|
2568
|
+
).call()
|
|
2569
|
+
bonded_assets[ZERO_ADDRESS] += operator_balance
|
|
2570
|
+
|
|
2571
|
+
# Determine bonded token amount for staking programs
|
|
2572
|
+
current_staking_program = self._get_current_staking_program(service, chain)
|
|
2573
|
+
target_staking_program = user_params.staking_program_id
|
|
2574
|
+
staking_contract = get_staking_contract(
|
|
2575
|
+
chain=ledger_config.chain,
|
|
2576
|
+
staking_program_id=current_staking_program or target_staking_program,
|
|
2577
|
+
)
|
|
2578
|
+
|
|
2579
|
+
if not staking_contract:
|
|
2580
|
+
return dict(bonded_assets)
|
|
2581
|
+
|
|
2582
|
+
sftxb = self.get_eth_safe_tx_builder(ledger_config=ledger_config)
|
|
2583
|
+
staking_params = sftxb.get_staking_params(staking_contract=staking_contract)
|
|
2584
|
+
service_registry_token_utility_address = staking_params[
|
|
2585
|
+
"service_registry_token_utility"
|
|
2586
|
+
]
|
|
2587
|
+
service_registry_token_utility = (
|
|
2588
|
+
registry_contracts.service_registry_token_utility.get_instance(
|
|
2589
|
+
ledger_api=ledger_api,
|
|
2590
|
+
contract_address=service_registry_token_utility_address,
|
|
2591
|
+
)
|
|
2592
|
+
)
|
|
2593
|
+
|
|
2594
|
+
agent_bonds = 0
|
|
2595
|
+
for agent_id in agent_ids:
|
|
2596
|
+
num_agent_instances = service_registry.functions.getInstancesForAgentId(
|
|
2597
|
+
service_id, agent_id
|
|
2598
|
+
).call()[0]
|
|
2599
|
+
agent_bond = service_registry_token_utility.functions.getAgentBond(
|
|
2600
|
+
service_id, agent_id
|
|
2601
|
+
).call()
|
|
2602
|
+
agent_bonds += num_agent_instances * agent_bond
|
|
2603
|
+
|
|
2604
|
+
if service_state == OnChainState.TERMINATED_BONDED:
|
|
2605
|
+
num_agent_instances = service_info[5]
|
|
2606
|
+
token_bond = service_registry_token_utility.functions.getOperatorBalance(
|
|
2607
|
+
master_safe,
|
|
2608
|
+
service_id,
|
|
2609
|
+
).call()
|
|
2610
|
+
agent_bonds += num_agent_instances * token_bond
|
|
2611
|
+
|
|
2612
|
+
security_deposit = 0
|
|
2613
|
+
if (
|
|
2614
|
+
OnChainState.ACTIVE_REGISTRATION
|
|
2615
|
+
<= service_state
|
|
2616
|
+
< OnChainState.TERMINATED_BONDED
|
|
2617
|
+
):
|
|
2618
|
+
security_deposit = (
|
|
2619
|
+
service_registry_token_utility.functions.mapServiceIdTokenDeposit(
|
|
2620
|
+
service_id
|
|
2621
|
+
).call()[1]
|
|
2622
|
+
)
|
|
2623
|
+
|
|
2624
|
+
bonded_assets[staking_params["staking_token"]] += agent_bonds
|
|
2625
|
+
bonded_assets[staking_params["staking_token"]] += security_deposit
|
|
2626
|
+
|
|
2627
|
+
staking_state = sftxb.staking_status(
|
|
2628
|
+
service_id=service_id,
|
|
2629
|
+
staking_contract=staking_params["staking_contract"],
|
|
2630
|
+
)
|
|
2631
|
+
|
|
2632
|
+
if staking_state in (StakingState.STAKED, StakingState.EVICTED):
|
|
2633
|
+
for token, amount in staking_params["additional_staking_tokens"].items():
|
|
2634
|
+
bonded_assets[token] += amount
|
|
2635
|
+
|
|
2636
|
+
return dict(bonded_assets)
|
|
2637
|
+
|
|
2638
|
+
# TODO deprecate
|
|
2639
|
+
def _compute_protocol_asset_requirements( # pylint: disable=too-many-locals
|
|
2640
|
+
self, service_config_id: str, chain: str
|
|
2641
|
+
) -> t.Dict:
|
|
2642
|
+
"""Computes the protocol asset requirements to deploy on-chain and stake (if necessary)"""
|
|
2643
|
+
service = self.load(service_config_id=service_config_id)
|
|
2644
|
+
chain_config = service.chain_configs[chain]
|
|
2645
|
+
user_params = chain_config.chain_data.user_params
|
|
2646
|
+
ledger_config = chain_config.ledger_config
|
|
2647
|
+
number_of_agents = NUM_LOCAL_AGENT_INSTANCES
|
|
2648
|
+
os.environ["CUSTOM_CHAIN_RPC"] = ledger_config.rpc
|
|
2649
|
+
sftxb = self.get_eth_safe_tx_builder(ledger_config=ledger_config)
|
|
2650
|
+
service_asset_requirements: defaultdict = defaultdict(int)
|
|
2651
|
+
|
|
2652
|
+
if not user_params.use_staking or not user_params.staking_program_id:
|
|
2653
|
+
agent_bonds = user_params.cost_of_bond * number_of_agents
|
|
2654
|
+
security_deposit = user_params.cost_of_bond
|
|
2655
|
+
service_asset_requirements[ZERO_ADDRESS] += agent_bonds
|
|
2656
|
+
service_asset_requirements[ZERO_ADDRESS] += security_deposit
|
|
2657
|
+
return dict(service_asset_requirements)
|
|
2658
|
+
|
|
2659
|
+
agent_bonds = 1 * number_of_agents
|
|
2660
|
+
security_deposit = 1
|
|
2661
|
+
service_asset_requirements[ZERO_ADDRESS] += agent_bonds
|
|
2662
|
+
service_asset_requirements[ZERO_ADDRESS] += security_deposit
|
|
2663
|
+
|
|
2664
|
+
staking_params = sftxb.get_staking_params(
|
|
2665
|
+
staking_contract=get_staking_contract(
|
|
2666
|
+
chain=ledger_config.chain,
|
|
2667
|
+
staking_program_id=user_params.staking_program_id,
|
|
2668
|
+
),
|
|
1034
2669
|
)
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
2670
|
+
|
|
2671
|
+
# TODO address this comment in FundingManager
|
|
2672
|
+
# This computation assumes the service will be/has been minted with these
|
|
2673
|
+
# parameters. Otherwise, these values should be retrieved on-chain as follows:
|
|
2674
|
+
# - agent_bonds: by combining the output of ServiceRegistry .getAgentParams .getService
|
|
2675
|
+
# and ServiceRegistryTokenUtility .getAgentBond
|
|
2676
|
+
# - security_deposit: as the maximum agent bond.
|
|
2677
|
+
agent_bonds = staking_params["min_staking_deposit"] * number_of_agents
|
|
2678
|
+
security_deposit = staking_params["min_staking_deposit"]
|
|
2679
|
+
service_asset_requirements[staking_params["staking_token"]] += agent_bonds
|
|
2680
|
+
service_asset_requirements[staking_params["staking_token"]] += security_deposit
|
|
2681
|
+
|
|
2682
|
+
for token, amount in staking_params["additional_staking_tokens"].items():
|
|
2683
|
+
service_asset_requirements[token] = amount
|
|
2684
|
+
|
|
2685
|
+
return dict(service_asset_requirements)
|
|
2686
|
+
|
|
2687
|
+
# TODO deprecate
|
|
2688
|
+
@staticmethod
|
|
2689
|
+
def _compute_refill_requirement(
|
|
2690
|
+
asset_funding_values: t.Dict,
|
|
2691
|
+
sender_topup: int = 0,
|
|
2692
|
+
sender_threshold: int = 0,
|
|
2693
|
+
sender_balance: int = 0,
|
|
2694
|
+
) -> t.Dict:
|
|
2695
|
+
"""
|
|
2696
|
+
Compute refill requirement.
|
|
2697
|
+
|
|
2698
|
+
The `asset_funding_values` dictionary specifies the funding obligations the sender must cover for other parties.
|
|
2699
|
+
Additionally, the sender must ensure its own balance remains above `sender_threshold` (minimum required balance)
|
|
2700
|
+
and ideally reaches `sender_topup` (recommended balance). If no funding is required for the sender after covering
|
|
2701
|
+
the obligations for other parties, set `sender_topup = sender_threshold = 0`.
|
|
2702
|
+
|
|
2703
|
+
Args:
|
|
2704
|
+
asset_funding_values (dict): Maps parties (identifiers) to their funding details:
|
|
2705
|
+
- "topup": Recommended funding balance.
|
|
2706
|
+
- "threshold": Minimum required balance.
|
|
2707
|
+
- "balance": Current balance.
|
|
2708
|
+
sender_topup (int): Recommended balance for the sender after meeting obligations.
|
|
2709
|
+
sender_threshold (int): Minimum balance required for the sender after meeting obligations.
|
|
2710
|
+
sender_balance (int): Sender's current balance.
|
|
2711
|
+
|
|
2712
|
+
Returns:
|
|
2713
|
+
dict: A dictionary with:
|
|
2714
|
+
- "minimum_refill": The minimum amount the sender needs to add.
|
|
2715
|
+
- "recommended_refill": The suggested amount the sender should add.
|
|
2716
|
+
"""
|
|
2717
|
+
if 0 > sender_threshold or sender_threshold > sender_topup:
|
|
2718
|
+
raise ValueError(
|
|
2719
|
+
f"Arguments must satisfy 0 <= 'sender_threshold' <= 'sender_topup' ({sender_threshold=}, {sender_topup=})."
|
|
2720
|
+
)
|
|
2721
|
+
|
|
2722
|
+
if 0 > sender_balance:
|
|
2723
|
+
raise ValueError(
|
|
2724
|
+
f"Argument 'sender_balance' must be >= 0 ({sender_balance=})."
|
|
2725
|
+
)
|
|
2726
|
+
|
|
2727
|
+
minimum_obligations_shortfall = 0
|
|
2728
|
+
recommended_obligations_shortfall = 0
|
|
2729
|
+
|
|
2730
|
+
for address, requirements in asset_funding_values.items():
|
|
2731
|
+
topup = requirements["topup"]
|
|
2732
|
+
threshold = requirements["threshold"]
|
|
2733
|
+
balance = requirements["balance"]
|
|
2734
|
+
|
|
2735
|
+
if 0 > threshold or threshold > topup:
|
|
2736
|
+
raise ValueError(
|
|
2737
|
+
f"Arguments must satisfy 0 <= 'threshold' <= 'topup' ({address=}, {threshold=}, {topup=}, {balance=})."
|
|
2738
|
+
)
|
|
2739
|
+
if 0 > balance:
|
|
2740
|
+
raise ValueError(
|
|
2741
|
+
f"Argument 'balance' must be >= 0 ({address=}, {balance=})."
|
|
2742
|
+
)
|
|
2743
|
+
|
|
2744
|
+
if balance < threshold:
|
|
2745
|
+
minimum_obligations_shortfall += threshold - balance
|
|
2746
|
+
recommended_obligations_shortfall += topup - balance
|
|
2747
|
+
|
|
2748
|
+
# Compute sender's remaining balance after covering obligations
|
|
2749
|
+
remaining_balance_minimum = sender_balance - minimum_obligations_shortfall
|
|
2750
|
+
remaining_balance_recommended = (
|
|
2751
|
+
sender_balance - recommended_obligations_shortfall
|
|
2752
|
+
)
|
|
2753
|
+
|
|
2754
|
+
# Determine if the sender needs additional refill
|
|
2755
|
+
minimum_refill = 0
|
|
2756
|
+
recommended_refill = 0
|
|
2757
|
+
if remaining_balance_minimum < sender_threshold:
|
|
2758
|
+
minimum_refill = sender_threshold - remaining_balance_minimum
|
|
2759
|
+
|
|
2760
|
+
if remaining_balance_recommended < sender_threshold:
|
|
2761
|
+
recommended_refill = sender_topup - remaining_balance_recommended
|
|
2762
|
+
|
|
2763
|
+
return {
|
|
2764
|
+
"minimum_refill": minimum_refill,
|
|
2765
|
+
"recommended_refill": recommended_refill,
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2768
|
+
# TODO deprecate
|
|
2769
|
+
@staticmethod
|
|
2770
|
+
def get_master_eoa_native_funding_values(
|
|
2771
|
+
master_safe_exists: bool, chain: Chain, balance: int
|
|
2772
|
+
) -> t.Dict:
|
|
2773
|
+
"""Get Master EOA native funding values."""
|
|
2774
|
+
|
|
2775
|
+
topup = DEFAULT_EOA_TOPUPS[chain][ZERO_ADDRESS]
|
|
2776
|
+
threshold = topup / 2 if master_safe_exists else topup
|
|
2777
|
+
return {"topup": topup, "threshold": threshold, "balance": balance}
|