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/protocol.py
CHANGED
|
@@ -24,46 +24,61 @@ import contextlib
|
|
|
24
24
|
import io
|
|
25
25
|
import json
|
|
26
26
|
import logging
|
|
27
|
+
import os
|
|
27
28
|
import tempfile
|
|
28
|
-
import time
|
|
29
29
|
import typing as t
|
|
30
|
-
from datetime import datetime
|
|
31
30
|
from enum import Enum
|
|
31
|
+
from functools import cache, cached_property
|
|
32
32
|
from pathlib import Path
|
|
33
|
-
from typing import Optional, Union
|
|
33
|
+
from typing import Optional, Union, cast
|
|
34
34
|
|
|
35
35
|
from aea.configurations.data_types import PackageType
|
|
36
36
|
from aea.crypto.base import Crypto, LedgerApi
|
|
37
37
|
from aea.helpers.base import IPFSHash, cd
|
|
38
|
-
from aea_ledger_ethereum.ethereum import EthereumCrypto
|
|
39
38
|
from autonomy.chain.base import registry_contracts
|
|
40
39
|
from autonomy.chain.config import ChainConfigs, ChainType, ContractConfigs
|
|
41
40
|
from autonomy.chain.constants import (
|
|
42
41
|
GNOSIS_SAFE_PROXY_FACTORY_CONTRACT,
|
|
43
42
|
GNOSIS_SAFE_SAME_ADDRESS_MULTISIG_CONTRACT,
|
|
43
|
+
MULTISEND_CONTRACT,
|
|
44
|
+
RECOVERY_MODULE_CONTRACT,
|
|
45
|
+
SAFE_MULTISIG_WITH_RECOVERY_MODULE_CONTRACT,
|
|
44
46
|
)
|
|
47
|
+
from autonomy.chain.metadata import publish_metadata
|
|
45
48
|
from autonomy.chain.service import (
|
|
46
49
|
get_agent_instances,
|
|
47
|
-
|
|
48
|
-
|
|
50
|
+
get_deployment_payload,
|
|
51
|
+
get_deployment_with_recovery_payload,
|
|
49
52
|
get_service_info,
|
|
53
|
+
get_token_deposit_amount,
|
|
50
54
|
)
|
|
51
55
|
from autonomy.chain.tx import TxSettler
|
|
52
|
-
from autonomy.cli.helpers.chain import MintHelper
|
|
53
|
-
from autonomy.cli.helpers.chain import OnChainHelper
|
|
56
|
+
from autonomy.cli.helpers.chain import MintHelper, OnChainHelper
|
|
54
57
|
from autonomy.cli.helpers.chain import ServiceHelper as ServiceManager
|
|
58
|
+
from eth_utils import to_bytes
|
|
55
59
|
from hexbytes import HexBytes
|
|
60
|
+
from web3.contract import Contract
|
|
56
61
|
|
|
57
62
|
from operate.constants import (
|
|
63
|
+
NO_STAKING_PROGRAM_ID,
|
|
58
64
|
ON_CHAIN_INTERACT_RETRIES,
|
|
59
65
|
ON_CHAIN_INTERACT_SLEEP,
|
|
60
66
|
ON_CHAIN_INTERACT_TIMEOUT,
|
|
67
|
+
ZERO_ADDRESS,
|
|
61
68
|
)
|
|
62
69
|
from operate.data import DATA_DIR
|
|
63
|
-
from operate.data.contracts.
|
|
64
|
-
|
|
70
|
+
from operate.data.contracts.dual_staking_token.contract import DualStakingTokenContract
|
|
71
|
+
from operate.data.contracts.recovery_module.contract import RecoveryModule
|
|
72
|
+
from operate.data.contracts.staking_token.contract import StakingTokenContract
|
|
73
|
+
from operate.ledger import (
|
|
74
|
+
get_default_ledger_api,
|
|
75
|
+
update_tx_with_gas_estimate,
|
|
76
|
+
update_tx_with_gas_pricing,
|
|
65
77
|
)
|
|
66
|
-
from operate.
|
|
78
|
+
from operate.ledger.profiles import CONTRACTS, STAKING
|
|
79
|
+
from operate.operate_types import Chain as OperateChain
|
|
80
|
+
from operate.operate_types import ContractAddresses
|
|
81
|
+
from operate.services.service import NON_EXISTENT_TOKEN
|
|
67
82
|
from operate.utils.gnosis import (
|
|
68
83
|
MultiSendOperation,
|
|
69
84
|
SafeOperation,
|
|
@@ -100,14 +115,15 @@ class GnosisSafeTransaction:
|
|
|
100
115
|
self.chain_type = chain_type
|
|
101
116
|
self.safe = safe
|
|
102
117
|
self._txs: t.List[t.Dict] = []
|
|
103
|
-
self.tx: t.Optional[t.Dict] = None
|
|
104
118
|
|
|
105
119
|
def add(self, tx: t.Dict) -> "GnosisSafeTransaction":
|
|
106
120
|
"""Add a transaction"""
|
|
107
121
|
self._txs.append(tx)
|
|
108
122
|
return self
|
|
109
123
|
|
|
110
|
-
def build(
|
|
124
|
+
def build( # pylint: disable=unused-argument
|
|
125
|
+
self, *args: t.Any, **kwargs: t.Any
|
|
126
|
+
) -> t.Dict:
|
|
111
127
|
"""Build the transaction."""
|
|
112
128
|
multisend_data = bytes.fromhex(
|
|
113
129
|
registry_contracts.multisend.get_tx_data(
|
|
@@ -155,53 +171,116 @@ class GnosisSafeTransaction:
|
|
|
155
171
|
operation=SafeOperation.DELEGATE_CALL.value,
|
|
156
172
|
nonce=self.ledger_api.api.eth.get_transaction_count(owner),
|
|
157
173
|
)
|
|
158
|
-
|
|
159
|
-
|
|
174
|
+
update_tx_with_gas_pricing(tx, self.ledger_api)
|
|
175
|
+
update_tx_with_gas_estimate(tx, self.ledger_api)
|
|
176
|
+
return t.cast(t.Dict, tx)
|
|
160
177
|
|
|
161
178
|
def settle(self) -> t.Dict:
|
|
162
179
|
"""Settle the transaction."""
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
tx_digest
|
|
179
|
-
)
|
|
180
|
-
if receipt["status"] != 0:
|
|
181
|
-
return receipt
|
|
182
|
-
time.sleep(ON_CHAIN_INTERACT_SLEEP)
|
|
183
|
-
raise RuntimeError("Timeout while waiting for safe transaction to go through")
|
|
180
|
+
tx_settler = TxSettler(
|
|
181
|
+
ledger_api=self.ledger_api,
|
|
182
|
+
crypto=self.crypto,
|
|
183
|
+
chain_type=self.chain_type,
|
|
184
|
+
timeout=ON_CHAIN_INTERACT_TIMEOUT,
|
|
185
|
+
retries=ON_CHAIN_INTERACT_RETRIES,
|
|
186
|
+
sleep=ON_CHAIN_INTERACT_SLEEP,
|
|
187
|
+
)
|
|
188
|
+
setattr(tx_settler, "build", self.build) # noqa: B010
|
|
189
|
+
return tx_settler.transact(
|
|
190
|
+
method=lambda: {},
|
|
191
|
+
contract="",
|
|
192
|
+
kwargs={},
|
|
193
|
+
dry_run=False,
|
|
194
|
+
)
|
|
184
195
|
|
|
185
196
|
|
|
186
|
-
class StakingManager
|
|
197
|
+
class StakingManager:
|
|
187
198
|
"""Helper class for staking a service."""
|
|
188
199
|
|
|
200
|
+
staking_ctr = t.cast(
|
|
201
|
+
StakingTokenContract,
|
|
202
|
+
StakingTokenContract.from_dir(
|
|
203
|
+
directory=str(DATA_DIR / "contracts" / "staking_token")
|
|
204
|
+
),
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
dual_staking_ctr = t.cast(
|
|
208
|
+
DualStakingTokenContract,
|
|
209
|
+
DualStakingTokenContract.from_dir(
|
|
210
|
+
directory=str(DATA_DIR / "contracts" / "dual_staking_token")
|
|
211
|
+
),
|
|
212
|
+
)
|
|
213
|
+
|
|
189
214
|
def __init__(
|
|
190
215
|
self,
|
|
191
|
-
|
|
192
|
-
chain_type: ChainType = ChainType.CUSTOM,
|
|
193
|
-
password: Optional[str] = None,
|
|
216
|
+
chain: OperateChain,
|
|
194
217
|
) -> None:
|
|
195
218
|
"""Initialize object."""
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
219
|
+
self.chain = chain
|
|
220
|
+
|
|
221
|
+
@property
|
|
222
|
+
def ledger_api(self) -> LedgerApi:
|
|
223
|
+
"""Get ledger api."""
|
|
224
|
+
return get_default_ledger_api(OperateChain(self.chain.value))
|
|
225
|
+
|
|
226
|
+
@staticmethod
|
|
227
|
+
@cache
|
|
228
|
+
def _get_staking_params(chain: OperateChain, staking_contract: str) -> t.Dict:
|
|
229
|
+
"""Get staking params"""
|
|
230
|
+
ledger_api = get_default_ledger_api(chain=chain)
|
|
231
|
+
instance = StakingManager.staking_ctr.get_instance(
|
|
232
|
+
ledger_api=ledger_api,
|
|
233
|
+
contract_address=staking_contract,
|
|
234
|
+
)
|
|
235
|
+
agent_ids = instance.functions.getAgentIds().call()
|
|
236
|
+
service_registry = instance.functions.serviceRegistry().call()
|
|
237
|
+
staking_token = instance.functions.stakingToken().call()
|
|
238
|
+
service_registry_token_utility = (
|
|
239
|
+
instance.functions.serviceRegistryTokenUtility().call()
|
|
240
|
+
)
|
|
241
|
+
min_staking_deposit = instance.functions.minStakingDeposit().call()
|
|
242
|
+
activity_checker = instance.functions.activityChecker().call()
|
|
243
|
+
|
|
244
|
+
output = {
|
|
245
|
+
"staking_contract": staking_contract,
|
|
246
|
+
"agent_ids": agent_ids,
|
|
247
|
+
"service_registry": service_registry,
|
|
248
|
+
"staking_token": staking_token,
|
|
249
|
+
"service_registry_token_utility": service_registry_token_utility,
|
|
250
|
+
"min_staking_deposit": min_staking_deposit,
|
|
251
|
+
"activity_checker": activity_checker,
|
|
252
|
+
"additional_staking_tokens": {},
|
|
253
|
+
}
|
|
254
|
+
try:
|
|
255
|
+
instance = StakingManager.dual_staking_ctr.get_instance(
|
|
256
|
+
ledger_api=ledger_api,
|
|
257
|
+
contract_address=staking_contract,
|
|
258
|
+
)
|
|
259
|
+
output["additional_staking_tokens"][
|
|
260
|
+
instance.functions.secondToken().call()
|
|
261
|
+
] = instance.functions.secondTokenAmount().call()
|
|
262
|
+
except Exception: # pylint: disable=broad-except # nosec
|
|
263
|
+
# Contract is not a dual staking contract
|
|
264
|
+
|
|
265
|
+
# TODO The exception caught here should be ContractLogicError.
|
|
266
|
+
# This exception is typically raised when the contract reverts with
|
|
267
|
+
# a reason string. However, in some cases, the error message
|
|
268
|
+
# does not contain a reason string, which means web3.py raises
|
|
269
|
+
# a generic ValueError instead. It should be properly analyzed
|
|
270
|
+
# what exceptions might be raised by web3.py in this case. To
|
|
271
|
+
# avoid any issues we are simply catching all exceptions.
|
|
272
|
+
pass
|
|
273
|
+
|
|
274
|
+
return output
|
|
275
|
+
|
|
276
|
+
def get_staking_params(self, staking_contract: str) -> t.Dict:
|
|
277
|
+
"""Get staking params"""
|
|
278
|
+
return StakingManager._get_staking_params(
|
|
279
|
+
chain=self.chain,
|
|
280
|
+
staking_contract=staking_contract,
|
|
202
281
|
)
|
|
203
282
|
|
|
204
|
-
def
|
|
283
|
+
def staking_state(self, service_id: int, staking_contract: str) -> StakingState:
|
|
205
284
|
"""Is the service staked?"""
|
|
206
285
|
return StakingState(
|
|
207
286
|
self.staking_ctr.get_instance(
|
|
@@ -232,13 +311,71 @@ class StakingManager(OnChainHelper):
|
|
|
232
311
|
available_rewards = instance.functions.availableRewards().call()
|
|
233
312
|
return available_rewards
|
|
234
313
|
|
|
314
|
+
def claimable_rewards(self, staking_contract: str, service_id: int) -> int:
|
|
315
|
+
"""Get the claimable staking rewards on the staking contract"""
|
|
316
|
+
instance = self.staking_ctr.get_instance(
|
|
317
|
+
ledger_api=self.ledger_api,
|
|
318
|
+
contract_address=staking_contract,
|
|
319
|
+
)
|
|
320
|
+
claimable_rewards = instance.functions.calculateStakingReward(service_id).call()
|
|
321
|
+
return claimable_rewards
|
|
322
|
+
|
|
235
323
|
def service_info(self, staking_contract: str, service_id: int) -> dict:
|
|
236
324
|
"""Get the service onchain info"""
|
|
237
|
-
|
|
238
|
-
self.ledger_api,
|
|
239
|
-
staking_contract,
|
|
240
|
-
|
|
241
|
-
).
|
|
325
|
+
instance = self.staking_ctr.get_instance(
|
|
326
|
+
ledger_api=self.ledger_api,
|
|
327
|
+
contract_address=staking_contract,
|
|
328
|
+
)
|
|
329
|
+
service_info = instance.functions.getServiceInfo(service_id).call()
|
|
330
|
+
return service_info
|
|
331
|
+
|
|
332
|
+
def agent_ids(self, staking_contract: str) -> t.List[int]:
|
|
333
|
+
"""Get a list of agent IDs for the given staking contract."""
|
|
334
|
+
instance = self.staking_ctr.get_instance(
|
|
335
|
+
ledger_api=self.ledger_api,
|
|
336
|
+
contract_address=staking_contract,
|
|
337
|
+
)
|
|
338
|
+
return instance.functions.getAgentIds().call()
|
|
339
|
+
|
|
340
|
+
def service_registry(self, staking_contract: str) -> str:
|
|
341
|
+
"""Retrieve the service registry address for the given staking contract."""
|
|
342
|
+
instance = self.staking_ctr.get_instance(
|
|
343
|
+
ledger_api=self.ledger_api,
|
|
344
|
+
contract_address=staking_contract,
|
|
345
|
+
)
|
|
346
|
+
return instance.functions.serviceRegistry().call()
|
|
347
|
+
|
|
348
|
+
def staking_token(self, staking_contract: str) -> str:
|
|
349
|
+
"""Get the staking token address for the staking contract."""
|
|
350
|
+
instance = self.staking_ctr.get_instance(
|
|
351
|
+
ledger_api=self.ledger_api,
|
|
352
|
+
contract_address=staking_contract,
|
|
353
|
+
)
|
|
354
|
+
return instance.functions.stakingToken().call()
|
|
355
|
+
|
|
356
|
+
def service_registry_token_utility(self, staking_contract: str) -> str:
|
|
357
|
+
"""Get the service registry token utility address for the staking contract."""
|
|
358
|
+
instance = self.staking_ctr.get_instance(
|
|
359
|
+
ledger_api=self.ledger_api,
|
|
360
|
+
contract_address=staking_contract,
|
|
361
|
+
)
|
|
362
|
+
return instance.functions.serviceRegistryTokenUtility().call()
|
|
363
|
+
|
|
364
|
+
def min_staking_deposit(self, staking_contract: str) -> int:
|
|
365
|
+
"""Retrieve the minimum staking deposit required for the staking contract."""
|
|
366
|
+
instance = self.staking_ctr.get_instance(
|
|
367
|
+
ledger_api=self.ledger_api,
|
|
368
|
+
contract_address=staking_contract,
|
|
369
|
+
)
|
|
370
|
+
return instance.functions.minStakingDeposit().call()
|
|
371
|
+
|
|
372
|
+
def activity_checker(self, staking_contract: str) -> str:
|
|
373
|
+
"""Retrieve the activity checker address for the staking contract."""
|
|
374
|
+
instance = self.staking_ctr.get_instance(
|
|
375
|
+
ledger_api=self.ledger_api,
|
|
376
|
+
contract_address=staking_contract,
|
|
377
|
+
)
|
|
378
|
+
return instance.functions.activityChecker().call()
|
|
242
379
|
|
|
243
380
|
def check_staking_compatibility(
|
|
244
381
|
self,
|
|
@@ -246,7 +383,7 @@ class StakingManager(OnChainHelper):
|
|
|
246
383
|
staking_contract: str,
|
|
247
384
|
) -> None:
|
|
248
385
|
"""Check if service can be staked."""
|
|
249
|
-
status = self.
|
|
386
|
+
status = self.staking_state(service_id, staking_contract)
|
|
250
387
|
if status == StakingState.STAKED:
|
|
251
388
|
raise ValueError("Service already staked")
|
|
252
389
|
|
|
@@ -256,21 +393,28 @@ class StakingManager(OnChainHelper):
|
|
|
256
393
|
if not self.slots_available(staking_contract):
|
|
257
394
|
raise ValueError("No sataking slots available.")
|
|
258
395
|
|
|
396
|
+
# TODO To be deprecated, only used in on-chain manager
|
|
259
397
|
def stake(
|
|
260
398
|
self,
|
|
261
399
|
service_id: int,
|
|
262
400
|
service_registry: str,
|
|
263
401
|
staking_contract: str,
|
|
402
|
+
key: Path,
|
|
403
|
+
password: str,
|
|
264
404
|
) -> None:
|
|
265
405
|
"""Stake the service"""
|
|
406
|
+
och = OnChainHelper(
|
|
407
|
+
key=key, chain_type=ChainType(self.chain.value), password=password
|
|
408
|
+
)
|
|
409
|
+
|
|
266
410
|
self.check_staking_compatibility(
|
|
267
411
|
service_id=service_id, staking_contract=staking_contract
|
|
268
412
|
)
|
|
269
413
|
|
|
270
414
|
tx_settler = TxSettler(
|
|
271
|
-
ledger_api=
|
|
272
|
-
crypto=
|
|
273
|
-
chain_type=
|
|
415
|
+
ledger_api=och.ledger_api,
|
|
416
|
+
crypto=och.crypto,
|
|
417
|
+
chain_type=och.chain_type,
|
|
274
418
|
timeout=ON_CHAIN_INTERACT_TIMEOUT,
|
|
275
419
|
retries=ON_CHAIN_INTERACT_RETRIES,
|
|
276
420
|
sleep=ON_CHAIN_INTERACT_SLEEP,
|
|
@@ -279,15 +423,18 @@ class StakingManager(OnChainHelper):
|
|
|
279
423
|
# we make use of the ERC20 contract to build the approval transaction
|
|
280
424
|
# since it has the same interface as ERC721 we might want to create
|
|
281
425
|
# a ERC721 contract package
|
|
426
|
+
# this is very bad way to do it but it works because the ERC721 contract expects two arguments
|
|
427
|
+
# for approve call (spender, token_id), and the ERC20 contract wrapper used here from open-autonomy
|
|
428
|
+
# passes the amount as the second argument.
|
|
282
429
|
def _build_approval_tx( # pylint: disable=unused-argument
|
|
283
430
|
*args: t.Any, **kargs: t.Any
|
|
284
431
|
) -> t.Dict:
|
|
285
432
|
return registry_contracts.erc20.get_approve_tx(
|
|
286
|
-
ledger_api=
|
|
433
|
+
ledger_api=och.ledger_api,
|
|
287
434
|
contract_address=service_registry,
|
|
288
435
|
spender=staking_contract,
|
|
289
|
-
sender=
|
|
290
|
-
amount=service_id,
|
|
436
|
+
sender=och.crypto.address,
|
|
437
|
+
amount=service_id, # TODO: This is a workaround and it should be fixed
|
|
291
438
|
)
|
|
292
439
|
|
|
293
440
|
setattr(tx_settler, "build", _build_approval_tx) # noqa: B010
|
|
@@ -301,15 +448,15 @@ class StakingManager(OnChainHelper):
|
|
|
301
448
|
def _build_staking_tx( # pylint: disable=unused-argument
|
|
302
449
|
*args: t.Any, **kargs: t.Any
|
|
303
450
|
) -> t.Dict:
|
|
304
|
-
return
|
|
451
|
+
return och.ledger_api.build_transaction(
|
|
305
452
|
contract_instance=self.staking_ctr.get_instance(
|
|
306
|
-
ledger_api=
|
|
453
|
+
ledger_api=och.ledger_api,
|
|
307
454
|
contract_address=staking_contract,
|
|
308
455
|
),
|
|
309
456
|
method_name="stake",
|
|
310
457
|
method_args={"serviceId": service_id},
|
|
311
458
|
tx_args={
|
|
312
|
-
"sender_address":
|
|
459
|
+
"sender_address": och.crypto.address,
|
|
313
460
|
},
|
|
314
461
|
raise_on_try=True,
|
|
315
462
|
)
|
|
@@ -328,7 +475,7 @@ class StakingManager(OnChainHelper):
|
|
|
328
475
|
staking_contract: str,
|
|
329
476
|
) -> None:
|
|
330
477
|
"""Check unstaking availability"""
|
|
331
|
-
if self.
|
|
478
|
+
if self.staking_state(
|
|
332
479
|
service_id=service_id, staking_contract=staking_contract
|
|
333
480
|
) not in {StakingState.STAKED, StakingState.EVICTED}:
|
|
334
481
|
raise ValueError("Service not staked.")
|
|
@@ -346,17 +493,29 @@ class StakingManager(OnChainHelper):
|
|
|
346
493
|
self.ledger_api, staking_contract
|
|
347
494
|
).get("data"),
|
|
348
495
|
)
|
|
349
|
-
|
|
496
|
+
current_block = self.ledger_api.api.eth.get_block("latest")
|
|
497
|
+
current_timestamp = current_block.timestamp
|
|
498
|
+
staked_duration = current_timestamp - ts_start
|
|
350
499
|
if staked_duration < minimum_staking_duration and available_rewards > 0:
|
|
351
500
|
raise ValueError("Service cannot be unstaked yet.")
|
|
352
501
|
|
|
353
|
-
|
|
502
|
+
# TODO To be deprecated, only used in on-chain manager
|
|
503
|
+
def unstake(
|
|
504
|
+
self,
|
|
505
|
+
service_id: int,
|
|
506
|
+
staking_contract: str,
|
|
507
|
+
key: Path,
|
|
508
|
+
password: str,
|
|
509
|
+
) -> None:
|
|
354
510
|
"""Unstake the service"""
|
|
511
|
+
och = OnChainHelper(
|
|
512
|
+
key=key, chain_type=ChainType(self.chain.value), password=password
|
|
513
|
+
)
|
|
355
514
|
|
|
356
515
|
tx_settler = TxSettler(
|
|
357
|
-
ledger_api=
|
|
358
|
-
crypto=
|
|
359
|
-
chain_type=
|
|
516
|
+
ledger_api=och.ledger_api,
|
|
517
|
+
crypto=och.crypto,
|
|
518
|
+
chain_type=och.chain_type,
|
|
360
519
|
timeout=ON_CHAIN_INTERACT_TIMEOUT,
|
|
361
520
|
retries=ON_CHAIN_INTERACT_RETRIES,
|
|
362
521
|
sleep=ON_CHAIN_INTERACT_SLEEP,
|
|
@@ -365,15 +524,15 @@ class StakingManager(OnChainHelper):
|
|
|
365
524
|
def _build_unstaking_tx( # pylint: disable=unused-argument
|
|
366
525
|
*args: t.Any, **kargs: t.Any
|
|
367
526
|
) -> t.Dict:
|
|
368
|
-
return
|
|
527
|
+
return och.ledger_api.build_transaction(
|
|
369
528
|
contract_instance=self.staking_ctr.get_instance(
|
|
370
|
-
ledger_api=
|
|
529
|
+
ledger_api=och.ledger_api,
|
|
371
530
|
contract_address=staking_contract,
|
|
372
531
|
),
|
|
373
532
|
method_name="unstake",
|
|
374
533
|
method_args={"serviceId": service_id},
|
|
375
534
|
tx_args={
|
|
376
|
-
"sender_address":
|
|
535
|
+
"sender_address": och.crypto.address,
|
|
377
536
|
},
|
|
378
537
|
raise_on_try=True,
|
|
379
538
|
)
|
|
@@ -436,6 +595,136 @@ class StakingManager(OnChainHelper):
|
|
|
436
595
|
args=[service_id],
|
|
437
596
|
)
|
|
438
597
|
|
|
598
|
+
def get_claim_tx_data(self, service_id: int, staking_contract: str) -> bytes:
|
|
599
|
+
"""Claim rewards for the service"""
|
|
600
|
+
return self.staking_ctr.get_instance(
|
|
601
|
+
ledger_api=self.ledger_api,
|
|
602
|
+
contract_address=staking_contract,
|
|
603
|
+
).encodeABI(
|
|
604
|
+
fn_name="claim",
|
|
605
|
+
args=[service_id],
|
|
606
|
+
)
|
|
607
|
+
|
|
608
|
+
def get_forced_unstake_tx_data(
|
|
609
|
+
self, service_id: int, staking_contract: str
|
|
610
|
+
) -> bytes:
|
|
611
|
+
"""Forced unstake the service"""
|
|
612
|
+
return self.staking_ctr.get_instance(
|
|
613
|
+
ledger_api=self.ledger_api,
|
|
614
|
+
contract_address=staking_contract,
|
|
615
|
+
).encodeABI(
|
|
616
|
+
fn_name="forcedUnstake",
|
|
617
|
+
args=[service_id],
|
|
618
|
+
)
|
|
619
|
+
|
|
620
|
+
def get_staking_contract(
|
|
621
|
+
self, staking_program_id: t.Optional[str]
|
|
622
|
+
) -> t.Optional[str]:
|
|
623
|
+
"""Get staking contract based on the config and the staking program."""
|
|
624
|
+
if staking_program_id == NO_STAKING_PROGRAM_ID or staking_program_id is None:
|
|
625
|
+
return None
|
|
626
|
+
|
|
627
|
+
return STAKING[self.chain].get(
|
|
628
|
+
staking_program_id,
|
|
629
|
+
staking_program_id,
|
|
630
|
+
)
|
|
631
|
+
|
|
632
|
+
def get_current_staking_program(self, service_id: int) -> t.Optional[str]:
|
|
633
|
+
"""Get the current staking program of a service"""
|
|
634
|
+
ledger_api = self.ledger_api
|
|
635
|
+
|
|
636
|
+
if service_id == NON_EXISTENT_TOKEN:
|
|
637
|
+
return None
|
|
638
|
+
|
|
639
|
+
service_registry = registry_contracts.service_registry.get_instance(
|
|
640
|
+
ledger_api=ledger_api,
|
|
641
|
+
contract_address=CONTRACTS[self.chain]["service_registry"],
|
|
642
|
+
)
|
|
643
|
+
|
|
644
|
+
service_owner = service_registry.functions.ownerOf(service_id).call()
|
|
645
|
+
|
|
646
|
+
try:
|
|
647
|
+
state = self.staking_state(
|
|
648
|
+
service_id=service_id, staking_contract=service_owner
|
|
649
|
+
)
|
|
650
|
+
|
|
651
|
+
except Exception: # pylint: disable=broad-except
|
|
652
|
+
# Service owner is not a staking contract
|
|
653
|
+
|
|
654
|
+
# TODO The exception caught here should be ContractLogicError.
|
|
655
|
+
# This exception is typically raised when the contract reverts with
|
|
656
|
+
# a reason string. However, in some cases, the error message
|
|
657
|
+
# does not contain a reason string, which means web3.py raises
|
|
658
|
+
# a generic ValueError instead. It should be properly analyzed
|
|
659
|
+
# what exceptions might be raised by web3.py in this case. To
|
|
660
|
+
# avoid any issues we are simply catching all exceptions.
|
|
661
|
+
return None
|
|
662
|
+
|
|
663
|
+
if state == StakingState.UNSTAKED:
|
|
664
|
+
return None
|
|
665
|
+
|
|
666
|
+
for staking_program_id, val in STAKING[self.chain].items():
|
|
667
|
+
if val == service_owner:
|
|
668
|
+
return staking_program_id
|
|
669
|
+
|
|
670
|
+
# Fallback, if not possible to determine staking_program_id it means it's an "inner" staking contract
|
|
671
|
+
# (e.g., in the case of DualStakingToken). Loop trough all the known contracts.
|
|
672
|
+
for staking_program_id, staking_program_address in STAKING[self.chain].items():
|
|
673
|
+
state = self.staking_state(
|
|
674
|
+
service_id=service_id, staking_contract=staking_program_address
|
|
675
|
+
)
|
|
676
|
+
if state in (StakingState.STAKED, StakingState.EVICTED):
|
|
677
|
+
return staking_program_id
|
|
678
|
+
|
|
679
|
+
# it's staked, but we don't know which staking program
|
|
680
|
+
# so the staking_program_id should be an arbitrary staking contract
|
|
681
|
+
return service_owner
|
|
682
|
+
|
|
683
|
+
|
|
684
|
+
# TODO Backport this to Open Autonomy MintHelper class
|
|
685
|
+
# MintHelper should support passing custom 'description', 'name' and 'attributes'.
|
|
686
|
+
# If some of these fields are not defined, then it can take the current default values.
|
|
687
|
+
# (Version is included as an attribute.)
|
|
688
|
+
# The current code here is a workaround and just addresses the description,
|
|
689
|
+
# because modifying the name and attributes requires touching lower-level code.
|
|
690
|
+
# A proper refactor of this should be done in Open Autonomy.
|
|
691
|
+
class MintManager(MintHelper):
|
|
692
|
+
"""MintManager"""
|
|
693
|
+
|
|
694
|
+
metadata_description: t.Optional[str] = None
|
|
695
|
+
metadata_name: t.Optional[str] = None
|
|
696
|
+
metadata_attributes: t.Optional[t.Dict[str, str]] = None
|
|
697
|
+
|
|
698
|
+
def set_metadata_fields(
|
|
699
|
+
self,
|
|
700
|
+
name: t.Optional[str] = None,
|
|
701
|
+
description: t.Optional[str] = None,
|
|
702
|
+
attributes: t.Optional[t.Dict[str, str]] = None,
|
|
703
|
+
) -> "MintManager":
|
|
704
|
+
"""Set metadata fields."""
|
|
705
|
+
self.metadata_name = (
|
|
706
|
+
name # Not used currently, just an indication for the OA refactor
|
|
707
|
+
)
|
|
708
|
+
self.metadata_description = description
|
|
709
|
+
self.metadata_attributes = (
|
|
710
|
+
attributes # Not used currently, just an indication for the OA refactor
|
|
711
|
+
)
|
|
712
|
+
return self
|
|
713
|
+
|
|
714
|
+
def publish_metadata(self) -> "MintManager":
|
|
715
|
+
"""Publish metadata."""
|
|
716
|
+
self.metadata_hash, self.metadata_string = publish_metadata(
|
|
717
|
+
package_id=self.package_configuration.package_id,
|
|
718
|
+
package_path=self.package_path,
|
|
719
|
+
nft=cast(str, self.nft),
|
|
720
|
+
description=self.metadata_description
|
|
721
|
+
or self.package_configuration.description,
|
|
722
|
+
)
|
|
723
|
+
return self
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
# End Backport
|
|
727
|
+
|
|
439
728
|
|
|
440
729
|
class _ChainUtil:
|
|
441
730
|
"""On chain service management."""
|
|
@@ -452,16 +741,25 @@ class _ChainUtil:
|
|
|
452
741
|
self.wallet = wallet
|
|
453
742
|
self.contracts = contracts
|
|
454
743
|
self.chain_type = chain_type or ChainType.CUSTOM
|
|
744
|
+
os.environ[f"{self.chain_type.name}_CHAIN_RPC"] = self.rpc
|
|
455
745
|
|
|
456
746
|
def _patch(self) -> None:
|
|
457
747
|
"""Patch contract and chain config."""
|
|
458
748
|
ChainConfigs.get(self.chain_type).rpc = self.rpc
|
|
459
|
-
if self.chain_type != ChainType.CUSTOM:
|
|
460
|
-
return
|
|
461
|
-
|
|
462
749
|
for name, address in self.contracts.items():
|
|
463
750
|
ContractConfigs.get(name=name).contracts[self.chain_type] = address
|
|
464
751
|
|
|
752
|
+
@property
|
|
753
|
+
def safe(self) -> str:
|
|
754
|
+
"""Get safe address."""
|
|
755
|
+
chain_id = self.ledger_api.api.eth.chain_id
|
|
756
|
+
chain = OperateChain.from_id(chain_id)
|
|
757
|
+
if self.wallet.safes is None:
|
|
758
|
+
raise ValueError("Safes not initialized")
|
|
759
|
+
if chain not in self.wallet.safes:
|
|
760
|
+
raise ValueError(f"Safe for chain type {chain} not found")
|
|
761
|
+
return self.wallet.safes[chain]
|
|
762
|
+
|
|
465
763
|
@property
|
|
466
764
|
def crypto(self) -> Crypto:
|
|
467
765
|
"""Load crypto object."""
|
|
@@ -477,12 +775,33 @@ class _ChainUtil:
|
|
|
477
775
|
def ledger_api(self) -> LedgerApi:
|
|
478
776
|
"""Load ledger api object."""
|
|
479
777
|
self._patch()
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
778
|
+
return self.wallet.ledger_api(
|
|
779
|
+
chain=OperateChain.from_string(self.chain_type.value),
|
|
780
|
+
rpc=self.rpc,
|
|
781
|
+
)
|
|
782
|
+
|
|
783
|
+
@cached_property
|
|
784
|
+
def service_manager_address(self) -> str: # TODO: backport to OA
|
|
785
|
+
"""Get service manager contract address."""
|
|
786
|
+
service_registry = registry_contracts.service_registry.get_instance(
|
|
787
|
+
ledger_api=self.ledger_api,
|
|
788
|
+
contract_address=CONTRACTS[OperateChain(self.chain_type.value)][
|
|
789
|
+
"service_registry"
|
|
790
|
+
],
|
|
791
|
+
)
|
|
792
|
+
return service_registry.functions.manager().call()
|
|
793
|
+
|
|
794
|
+
@property
|
|
795
|
+
def service_manager_instance(self) -> Contract:
|
|
796
|
+
"""Load service manager contract instance."""
|
|
797
|
+
contract_interface = registry_contracts.service_manager.contract_interface.get(
|
|
798
|
+
self.ledger_api.identifier, {}
|
|
484
799
|
)
|
|
485
|
-
|
|
800
|
+
instance = self.ledger_api.get_contract_instance(
|
|
801
|
+
contract_interface,
|
|
802
|
+
self.service_manager_address,
|
|
803
|
+
)
|
|
804
|
+
return instance
|
|
486
805
|
|
|
487
806
|
def info(self, token_id: int) -> t.Dict:
|
|
488
807
|
"""Get service info."""
|
|
@@ -498,7 +817,7 @@ class _ChainUtil:
|
|
|
498
817
|
max_agents,
|
|
499
818
|
number_of_agent_instances,
|
|
500
819
|
service_state,
|
|
501
|
-
|
|
820
|
+
canonical_agents,
|
|
502
821
|
) = get_service_info(
|
|
503
822
|
ledger_api=ledger_api,
|
|
504
823
|
chain_type=self.chain_type,
|
|
@@ -517,151 +836,62 @@ class _ChainUtil:
|
|
|
517
836
|
max_agents=max_agents,
|
|
518
837
|
number_of_agent_instances=number_of_agent_instances,
|
|
519
838
|
service_state=service_state,
|
|
520
|
-
|
|
839
|
+
canonical_agents=canonical_agents,
|
|
521
840
|
instances=instances,
|
|
522
841
|
)
|
|
523
842
|
|
|
843
|
+
def get_agent_bond(self, service_id: int, agent_id: int) -> int:
|
|
844
|
+
"""Get the agent bond for a given service"""
|
|
845
|
+
self._patch()
|
|
524
846
|
|
|
525
|
-
|
|
526
|
-
|
|
847
|
+
if service_id <= 0 or agent_id <= 0:
|
|
848
|
+
return 0
|
|
527
849
|
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
cost_of_bond: int,
|
|
534
|
-
threshold: int,
|
|
535
|
-
nft: Optional[Union[Path, IPFSHash]],
|
|
536
|
-
update_token: t.Optional[int] = None,
|
|
537
|
-
token: t.Optional[str] = None,
|
|
538
|
-
) -> t.Dict:
|
|
539
|
-
"""Mint service."""
|
|
540
|
-
# TODO: Support for update
|
|
541
|
-
self._patch()
|
|
542
|
-
manager = MintManager(
|
|
850
|
+
ledger_api, _ = OnChainHelper.get_ledger_and_crypto_objects(
|
|
851
|
+
chain_type=self.chain_type
|
|
852
|
+
)
|
|
853
|
+
bond = get_token_deposit_amount(
|
|
854
|
+
ledger_api=ledger_api,
|
|
543
855
|
chain_type=self.chain_type,
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
update_token=update_token,
|
|
547
|
-
timeout=ON_CHAIN_INTERACT_TIMEOUT,
|
|
548
|
-
retries=ON_CHAIN_INTERACT_RETRIES,
|
|
549
|
-
sleep=ON_CHAIN_INTERACT_SLEEP,
|
|
856
|
+
service_id=service_id,
|
|
857
|
+
agent_id=agent_id,
|
|
550
858
|
)
|
|
859
|
+
return bond
|
|
551
860
|
|
|
552
|
-
|
|
861
|
+
def get_service_safe_owners(self, service_id: int) -> t.List[str]:
|
|
862
|
+
"""Get list of owners."""
|
|
863
|
+
ledger_api, _ = OnChainHelper.get_ledger_and_crypto_objects(
|
|
864
|
+
chain_type=self.chain_type
|
|
865
|
+
)
|
|
553
866
|
(
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
867
|
+
_,
|
|
868
|
+
multisig_address,
|
|
869
|
+
_,
|
|
870
|
+
_,
|
|
871
|
+
_,
|
|
872
|
+
_,
|
|
873
|
+
_,
|
|
874
|
+
_,
|
|
875
|
+
) = get_service_info(
|
|
876
|
+
ledger_api=ledger_api,
|
|
877
|
+
chain_type=self.chain_type,
|
|
878
|
+
token_id=service_id,
|
|
561
879
|
)
|
|
562
880
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
):
|
|
566
|
-
with cd(temp):
|
|
567
|
-
kwargs = dict(
|
|
568
|
-
number_of_slots=number_of_slots,
|
|
569
|
-
cost_of_bond=cost_of_bond,
|
|
570
|
-
threshold=threshold,
|
|
571
|
-
token=token,
|
|
572
|
-
)
|
|
573
|
-
# TODO: Enable after consulting smart contracts team re a safe
|
|
574
|
-
# being a service owner
|
|
575
|
-
# if update_token is None:
|
|
576
|
-
# kwargs["owner"] = self.wallet.safe # noqa: F401
|
|
577
|
-
method = (
|
|
578
|
-
manager.mint_service
|
|
579
|
-
if update_token is None
|
|
580
|
-
else manager.update_service
|
|
581
|
-
)
|
|
582
|
-
method(**kwargs)
|
|
583
|
-
(metadata,) = Path(temp).glob("*.json")
|
|
584
|
-
published = {
|
|
585
|
-
"token": int(Path(metadata).name.replace(".json", "")),
|
|
586
|
-
"metadata": json.loads(Path(metadata).read_text(encoding="utf-8")),
|
|
587
|
-
}
|
|
588
|
-
return published
|
|
881
|
+
if multisig_address == ZERO_ADDRESS:
|
|
882
|
+
return []
|
|
589
883
|
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
) -> None:
|
|
595
|
-
"""Activate service."""
|
|
596
|
-
logging.info(f"Activating service {service_id}...")
|
|
597
|
-
self._patch()
|
|
598
|
-
with contextlib.redirect_stdout(io.StringIO()):
|
|
599
|
-
ServiceManager(
|
|
600
|
-
service_id=service_id,
|
|
601
|
-
chain_type=self.chain_type,
|
|
602
|
-
key=self.wallet.key_path,
|
|
603
|
-
password=self.wallet.password,
|
|
604
|
-
timeout=ON_CHAIN_INTERACT_TIMEOUT,
|
|
605
|
-
retries=ON_CHAIN_INTERACT_RETRIES,
|
|
606
|
-
sleep=ON_CHAIN_INTERACT_SLEEP,
|
|
607
|
-
).check_is_service_token_secured(
|
|
608
|
-
token=token,
|
|
609
|
-
).activate_service()
|
|
884
|
+
return registry_contracts.gnosis_safe.get_owners(
|
|
885
|
+
ledger_api=ledger_api,
|
|
886
|
+
contract_address=multisig_address,
|
|
887
|
+
).get("owners", [])
|
|
610
888
|
|
|
611
|
-
def
|
|
889
|
+
def swap( # pylint: disable=too-many-arguments,too-many-locals
|
|
612
890
|
self,
|
|
613
891
|
service_id: int,
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
) -> None:
|
|
618
|
-
"""Register instance."""
|
|
619
|
-
logging.info(f"Registering service {service_id}...")
|
|
620
|
-
with contextlib.redirect_stdout(io.StringIO()):
|
|
621
|
-
ServiceManager(
|
|
622
|
-
service_id=service_id,
|
|
623
|
-
chain_type=self.chain_type,
|
|
624
|
-
key=self.wallet.key_path,
|
|
625
|
-
password=self.wallet.password,
|
|
626
|
-
timeout=ON_CHAIN_INTERACT_TIMEOUT,
|
|
627
|
-
retries=ON_CHAIN_INTERACT_RETRIES,
|
|
628
|
-
sleep=ON_CHAIN_INTERACT_SLEEP,
|
|
629
|
-
).check_is_service_token_secured(
|
|
630
|
-
token=token,
|
|
631
|
-
).register_instance(
|
|
632
|
-
instances=instances,
|
|
633
|
-
agent_ids=agents,
|
|
634
|
-
)
|
|
635
|
-
|
|
636
|
-
def deploy(
|
|
637
|
-
self,
|
|
638
|
-
service_id: int,
|
|
639
|
-
reuse_multisig: bool = False,
|
|
640
|
-
token: t.Optional[str] = None,
|
|
641
|
-
) -> None:
|
|
642
|
-
"""Deploy service."""
|
|
643
|
-
logging.info(f"Deploying service {service_id}...")
|
|
644
|
-
self._patch()
|
|
645
|
-
with contextlib.redirect_stdout(io.StringIO()):
|
|
646
|
-
ServiceManager(
|
|
647
|
-
service_id=service_id,
|
|
648
|
-
chain_type=self.chain_type,
|
|
649
|
-
key=self.wallet.key_path,
|
|
650
|
-
password=self.wallet.password,
|
|
651
|
-
timeout=ON_CHAIN_INTERACT_TIMEOUT,
|
|
652
|
-
retries=ON_CHAIN_INTERACT_RETRIES,
|
|
653
|
-
sleep=ON_CHAIN_INTERACT_SLEEP,
|
|
654
|
-
).check_is_service_token_secured(
|
|
655
|
-
token=token,
|
|
656
|
-
).deploy_service(
|
|
657
|
-
reuse_multisig=reuse_multisig,
|
|
658
|
-
)
|
|
659
|
-
|
|
660
|
-
def swap( # pylint: disable=too-many-arguments,too-many-locals
|
|
661
|
-
self,
|
|
662
|
-
service_id: int,
|
|
663
|
-
multisig: str,
|
|
664
|
-
owner_key: str,
|
|
892
|
+
multisig: str,
|
|
893
|
+
owner_cryptos: t.List[Crypto],
|
|
894
|
+
new_owner_address: str,
|
|
665
895
|
) -> None:
|
|
666
896
|
"""Swap safe owner."""
|
|
667
897
|
logging.info(f"Swapping safe for service {service_id} [{multisig}]...")
|
|
@@ -675,11 +905,6 @@ class OnChainManager(_ChainUtil):
|
|
|
675
905
|
retries=ON_CHAIN_INTERACT_RETRIES,
|
|
676
906
|
sleep=ON_CHAIN_INTERACT_SLEEP,
|
|
677
907
|
)
|
|
678
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
679
|
-
key_file = Path(temp_dir, "key.txt")
|
|
680
|
-
key_file.write_text(owner_key, encoding="utf-8")
|
|
681
|
-
owner_crypto = EthereumCrypto(private_key_path=str(key_file))
|
|
682
|
-
owner_cryptos: t.List[EthereumCrypto] = [owner_crypto]
|
|
683
908
|
owners = [
|
|
684
909
|
manager.ledger_api.api.to_checksum_address(owner_crypto.address)
|
|
685
910
|
for owner_crypto in owner_cryptos
|
|
@@ -690,9 +915,7 @@ class OnChainManager(_ChainUtil):
|
|
|
690
915
|
ledger_api=manager.ledger_api,
|
|
691
916
|
contract_address=multisig,
|
|
692
917
|
old_owner=manager.ledger_api.api.to_checksum_address(owner_to_swap),
|
|
693
|
-
new_owner=manager.ledger_api.api.to_checksum_address(
|
|
694
|
-
manager.crypto.address
|
|
695
|
-
),
|
|
918
|
+
new_owner=manager.ledger_api.api.to_checksum_address(new_owner_address),
|
|
696
919
|
).get("data")
|
|
697
920
|
multisend_txs.append(
|
|
698
921
|
{
|
|
@@ -738,7 +961,7 @@ class OnChainManager(_ChainUtil):
|
|
|
738
961
|
tx = registry_contracts.gnosis_safe.get_raw_safe_transaction(
|
|
739
962
|
ledger_api=manager.ledger_api,
|
|
740
963
|
contract_address=multisig,
|
|
741
|
-
sender_address=
|
|
964
|
+
sender_address=owner_cryptos[0].address,
|
|
742
965
|
owners=tuple(owners), # type: ignore
|
|
743
966
|
to_address=tx_params["to_address"],
|
|
744
967
|
value=tx_params["ether_value"],
|
|
@@ -747,12 +970,209 @@ class OnChainManager(_ChainUtil):
|
|
|
747
970
|
signatures_by_owner=owner_to_signature,
|
|
748
971
|
operation=SafeOperation.DELEGATE_CALL.value,
|
|
749
972
|
)
|
|
750
|
-
stx =
|
|
973
|
+
stx = owner_cryptos[0].sign_transaction(tx)
|
|
751
974
|
tx_digest = manager.ledger_api.send_signed_transaction(stx)
|
|
752
975
|
receipt = manager.ledger_api.api.eth.wait_for_transaction_receipt(tx_digest)
|
|
753
976
|
if receipt["status"] != 1:
|
|
754
977
|
raise RuntimeError("Error swapping owners")
|
|
755
978
|
|
|
979
|
+
def staking_slots_available(self, staking_contract: str) -> bool:
|
|
980
|
+
"""Check if there are available slots on the staking contract"""
|
|
981
|
+
self._patch()
|
|
982
|
+
return StakingManager(
|
|
983
|
+
chain=OperateChain(self.chain_type.value),
|
|
984
|
+
).slots_available(
|
|
985
|
+
staking_contract=staking_contract,
|
|
986
|
+
)
|
|
987
|
+
|
|
988
|
+
def staking_rewards_available(self, staking_contract: str) -> bool:
|
|
989
|
+
"""Check if there are available staking rewards on the staking contract"""
|
|
990
|
+
self._patch()
|
|
991
|
+
available_rewards = StakingManager(
|
|
992
|
+
chain=OperateChain(self.chain_type.value),
|
|
993
|
+
).available_rewards(
|
|
994
|
+
staking_contract=staking_contract,
|
|
995
|
+
)
|
|
996
|
+
return available_rewards > 0
|
|
997
|
+
|
|
998
|
+
def staking_rewards_claimable(self, staking_contract: str, service_id: int) -> bool:
|
|
999
|
+
"""Check if there are claimable staking rewards on the staking contract"""
|
|
1000
|
+
self._patch()
|
|
1001
|
+
claimable_rewards = StakingManager(
|
|
1002
|
+
chain=OperateChain(self.chain_type.value),
|
|
1003
|
+
).claimable_rewards(
|
|
1004
|
+
staking_contract=staking_contract,
|
|
1005
|
+
service_id=service_id,
|
|
1006
|
+
)
|
|
1007
|
+
return claimable_rewards > 0
|
|
1008
|
+
|
|
1009
|
+
def staking_status(self, service_id: int, staking_contract: str) -> StakingState:
|
|
1010
|
+
"""Stake the service"""
|
|
1011
|
+
self._patch()
|
|
1012
|
+
return StakingManager(
|
|
1013
|
+
chain=OperateChain(self.chain_type.value),
|
|
1014
|
+
).staking_state(
|
|
1015
|
+
service_id=service_id,
|
|
1016
|
+
staking_contract=staking_contract,
|
|
1017
|
+
)
|
|
1018
|
+
|
|
1019
|
+
def get_staking_params(
|
|
1020
|
+
self, staking_contract: str, fallback_params: t.Optional[t.Dict] = None
|
|
1021
|
+
) -> t.Dict:
|
|
1022
|
+
"""Get agent IDs for the staking contract"""
|
|
1023
|
+
if staking_contract is None and fallback_params is not None:
|
|
1024
|
+
return fallback_params
|
|
1025
|
+
self._patch()
|
|
1026
|
+
staking_manager = StakingManager(
|
|
1027
|
+
chain=OperateChain(self.chain_type.value),
|
|
1028
|
+
)
|
|
1029
|
+
return staking_manager.get_staking_params(
|
|
1030
|
+
staking_contract=staking_contract,
|
|
1031
|
+
)
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
class OnChainManager(_ChainUtil):
|
|
1035
|
+
"""On chain service management."""
|
|
1036
|
+
|
|
1037
|
+
def mint( # pylint: disable=too-many-arguments,too-many-locals
|
|
1038
|
+
self,
|
|
1039
|
+
package_path: Path,
|
|
1040
|
+
agent_id: int,
|
|
1041
|
+
number_of_slots: int,
|
|
1042
|
+
cost_of_bond: int,
|
|
1043
|
+
threshold: int,
|
|
1044
|
+
nft: Optional[Union[Path, IPFSHash]],
|
|
1045
|
+
update_token: t.Optional[int] = None,
|
|
1046
|
+
token: t.Optional[str] = None,
|
|
1047
|
+
metadata_description: t.Optional[str] = None,
|
|
1048
|
+
skip_dependency_check: t.Optional[bool] = False,
|
|
1049
|
+
) -> t.Dict:
|
|
1050
|
+
"""Mint service."""
|
|
1051
|
+
# TODO: Support for update
|
|
1052
|
+
self._patch()
|
|
1053
|
+
manager = MintManager(
|
|
1054
|
+
chain_type=self.chain_type,
|
|
1055
|
+
key=self.wallet.key_path,
|
|
1056
|
+
password=self.wallet.password,
|
|
1057
|
+
update_token=update_token,
|
|
1058
|
+
timeout=ON_CHAIN_INTERACT_TIMEOUT,
|
|
1059
|
+
retries=ON_CHAIN_INTERACT_RETRIES,
|
|
1060
|
+
sleep=ON_CHAIN_INTERACT_SLEEP,
|
|
1061
|
+
)
|
|
1062
|
+
|
|
1063
|
+
# Prepare for minting
|
|
1064
|
+
(
|
|
1065
|
+
manager.load_package_configuration(
|
|
1066
|
+
package_path=package_path, package_type=PackageType.SERVICE
|
|
1067
|
+
)
|
|
1068
|
+
.load_metadata()
|
|
1069
|
+
.set_metadata_fields(description=metadata_description)
|
|
1070
|
+
.verify_nft(nft=nft)
|
|
1071
|
+
)
|
|
1072
|
+
|
|
1073
|
+
if skip_dependency_check is False:
|
|
1074
|
+
logging.warning("Skipping depencencies check")
|
|
1075
|
+
manager.verify_service_dependencies(agent_id=agent_id)
|
|
1076
|
+
|
|
1077
|
+
manager.publish_metadata()
|
|
1078
|
+
|
|
1079
|
+
with tempfile.TemporaryDirectory() as temp, contextlib.redirect_stdout(
|
|
1080
|
+
io.StringIO()
|
|
1081
|
+
):
|
|
1082
|
+
with cd(temp):
|
|
1083
|
+
kwargs = dict(
|
|
1084
|
+
number_of_slots=number_of_slots,
|
|
1085
|
+
cost_of_bond=cost_of_bond,
|
|
1086
|
+
threshold=threshold,
|
|
1087
|
+
token=token,
|
|
1088
|
+
)
|
|
1089
|
+
# TODO: Enable after consulting smart contracts team re a safe
|
|
1090
|
+
# being a service owner
|
|
1091
|
+
# if update_token is None:
|
|
1092
|
+
# kwargs["owner"] = self.wallet.safe # noqa: F401
|
|
1093
|
+
method = (
|
|
1094
|
+
manager.mint_service
|
|
1095
|
+
if update_token is None
|
|
1096
|
+
else manager.update_service
|
|
1097
|
+
)
|
|
1098
|
+
method(**kwargs)
|
|
1099
|
+
(metadata,) = Path(temp).glob("*.json")
|
|
1100
|
+
published = {
|
|
1101
|
+
"token": int(Path(metadata).name.replace(".json", "")),
|
|
1102
|
+
"metadata": json.loads(Path(metadata).read_text(encoding="utf-8")),
|
|
1103
|
+
}
|
|
1104
|
+
return published
|
|
1105
|
+
|
|
1106
|
+
def activate(
|
|
1107
|
+
self,
|
|
1108
|
+
service_id: int,
|
|
1109
|
+
token: t.Optional[str] = None,
|
|
1110
|
+
) -> None:
|
|
1111
|
+
"""Activate service."""
|
|
1112
|
+
logging.info(f"Activating service {service_id}...")
|
|
1113
|
+
self._patch()
|
|
1114
|
+
with contextlib.redirect_stdout(io.StringIO()):
|
|
1115
|
+
ServiceManager(
|
|
1116
|
+
service_id=service_id,
|
|
1117
|
+
chain_type=self.chain_type,
|
|
1118
|
+
key=self.wallet.key_path,
|
|
1119
|
+
password=self.wallet.password,
|
|
1120
|
+
timeout=ON_CHAIN_INTERACT_TIMEOUT,
|
|
1121
|
+
retries=ON_CHAIN_INTERACT_RETRIES,
|
|
1122
|
+
sleep=ON_CHAIN_INTERACT_SLEEP,
|
|
1123
|
+
).check_is_service_token_secured(
|
|
1124
|
+
token=token,
|
|
1125
|
+
).activate_service()
|
|
1126
|
+
|
|
1127
|
+
def register(
|
|
1128
|
+
self,
|
|
1129
|
+
service_id: int,
|
|
1130
|
+
instances: t.List[str],
|
|
1131
|
+
agents: t.List[int],
|
|
1132
|
+
token: t.Optional[str] = None,
|
|
1133
|
+
) -> None:
|
|
1134
|
+
"""Register instance."""
|
|
1135
|
+
logging.info(f"Registering service {service_id}...")
|
|
1136
|
+
with contextlib.redirect_stdout(io.StringIO()):
|
|
1137
|
+
ServiceManager(
|
|
1138
|
+
service_id=service_id,
|
|
1139
|
+
chain_type=self.chain_type,
|
|
1140
|
+
key=self.wallet.key_path,
|
|
1141
|
+
password=self.wallet.password,
|
|
1142
|
+
timeout=ON_CHAIN_INTERACT_TIMEOUT,
|
|
1143
|
+
retries=ON_CHAIN_INTERACT_RETRIES,
|
|
1144
|
+
sleep=ON_CHAIN_INTERACT_SLEEP,
|
|
1145
|
+
).check_is_service_token_secured(
|
|
1146
|
+
token=token,
|
|
1147
|
+
).register_instance(
|
|
1148
|
+
instances=instances,
|
|
1149
|
+
agent_ids=agents,
|
|
1150
|
+
)
|
|
1151
|
+
|
|
1152
|
+
def deploy(
|
|
1153
|
+
self,
|
|
1154
|
+
service_id: int,
|
|
1155
|
+
reuse_multisig: bool = False,
|
|
1156
|
+
token: t.Optional[str] = None,
|
|
1157
|
+
) -> None:
|
|
1158
|
+
"""Deploy service."""
|
|
1159
|
+
logging.info(f"Deploying service {service_id}...")
|
|
1160
|
+
self._patch()
|
|
1161
|
+
with contextlib.redirect_stdout(io.StringIO()):
|
|
1162
|
+
ServiceManager(
|
|
1163
|
+
service_id=service_id,
|
|
1164
|
+
chain_type=self.chain_type,
|
|
1165
|
+
key=self.wallet.key_path,
|
|
1166
|
+
password=self.wallet.password,
|
|
1167
|
+
timeout=ON_CHAIN_INTERACT_TIMEOUT,
|
|
1168
|
+
retries=ON_CHAIN_INTERACT_RETRIES,
|
|
1169
|
+
sleep=ON_CHAIN_INTERACT_SLEEP,
|
|
1170
|
+
).check_is_service_token_secured(
|
|
1171
|
+
token=token,
|
|
1172
|
+
).deploy_service(
|
|
1173
|
+
reuse_multisig=reuse_multisig,
|
|
1174
|
+
)
|
|
1175
|
+
|
|
756
1176
|
def terminate(self, service_id: int, token: t.Optional[str] = None) -> None:
|
|
757
1177
|
"""Terminate service."""
|
|
758
1178
|
logging.info(f"Terminating service {service_id}...")
|
|
@@ -787,29 +1207,6 @@ class OnChainManager(_ChainUtil):
|
|
|
787
1207
|
token=token,
|
|
788
1208
|
).unbond_service()
|
|
789
1209
|
|
|
790
|
-
def staking_slots_available(self, staking_contract: str) -> bool:
|
|
791
|
-
"""Check if there are available slots on the staking contract"""
|
|
792
|
-
self._patch()
|
|
793
|
-
return StakingManager(
|
|
794
|
-
key=self.wallet.key_path,
|
|
795
|
-
password=self.wallet.password,
|
|
796
|
-
chain_type=self.chain_type,
|
|
797
|
-
).slots_available(
|
|
798
|
-
staking_contract=staking_contract,
|
|
799
|
-
)
|
|
800
|
-
|
|
801
|
-
def staking_rewards_available(self, staking_contract: str) -> bool:
|
|
802
|
-
"""Check if there are available staking rewards on the staking contract"""
|
|
803
|
-
self._patch()
|
|
804
|
-
available_rewards = StakingManager(
|
|
805
|
-
key=self.wallet.key_path,
|
|
806
|
-
password=self.wallet.password,
|
|
807
|
-
chain_type=self.chain_type,
|
|
808
|
-
).available_rewards(
|
|
809
|
-
staking_contract=staking_contract,
|
|
810
|
-
)
|
|
811
|
-
return available_rewards > 0
|
|
812
|
-
|
|
813
1210
|
def stake(
|
|
814
1211
|
self,
|
|
815
1212
|
service_id: int,
|
|
@@ -819,35 +1216,33 @@ class OnChainManager(_ChainUtil):
|
|
|
819
1216
|
"""Stake service."""
|
|
820
1217
|
self._patch()
|
|
821
1218
|
StakingManager(
|
|
822
|
-
|
|
823
|
-
password=self.wallet.password,
|
|
824
|
-
chain_type=self.chain_type,
|
|
1219
|
+
chain=OperateChain(self.chain_type.value),
|
|
825
1220
|
).stake(
|
|
826
1221
|
service_id=service_id,
|
|
827
1222
|
service_registry=service_registry,
|
|
828
1223
|
staking_contract=staking_contract,
|
|
1224
|
+
key=self.wallet.key_path,
|
|
1225
|
+
password=self.wallet.password,
|
|
829
1226
|
)
|
|
830
1227
|
|
|
831
1228
|
def unstake(self, service_id: int, staking_contract: str) -> None:
|
|
832
1229
|
"""Unstake service."""
|
|
833
1230
|
self._patch()
|
|
834
1231
|
StakingManager(
|
|
835
|
-
|
|
836
|
-
password=self.wallet.password,
|
|
837
|
-
chain_type=self.chain_type,
|
|
1232
|
+
chain=OperateChain(self.chain_type.value),
|
|
838
1233
|
).unstake(
|
|
839
1234
|
service_id=service_id,
|
|
840
1235
|
staking_contract=staking_contract,
|
|
1236
|
+
key=self.wallet.key_path,
|
|
1237
|
+
password=self.wallet.password,
|
|
841
1238
|
)
|
|
842
1239
|
|
|
843
1240
|
def staking_status(self, service_id: int, staking_contract: str) -> StakingState:
|
|
844
1241
|
"""Stake the service"""
|
|
845
1242
|
self._patch()
|
|
846
1243
|
return StakingManager(
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
chain_type=self.chain_type,
|
|
850
|
-
).status(
|
|
1244
|
+
chain=OperateChain(self.chain_type.value),
|
|
1245
|
+
).staking_state(
|
|
851
1246
|
service_id=service_id,
|
|
852
1247
|
staking_contract=staking_contract,
|
|
853
1248
|
)
|
|
@@ -856,13 +1251,28 @@ class OnChainManager(_ChainUtil):
|
|
|
856
1251
|
class EthSafeTxBuilder(_ChainUtil):
|
|
857
1252
|
"""Safe Transaction builder."""
|
|
858
1253
|
|
|
859
|
-
|
|
1254
|
+
@classmethod
|
|
1255
|
+
def _new_tx(
|
|
1256
|
+
cls, ledger_api: LedgerApi, crypto: Crypto, chain_type: ChainType, safe: str
|
|
1257
|
+
) -> GnosisSafeTransaction:
|
|
860
1258
|
"""Create a new GnosisSafeTransaction instance."""
|
|
861
1259
|
return GnosisSafeTransaction(
|
|
862
|
-
ledger_api=
|
|
1260
|
+
ledger_api=ledger_api,
|
|
1261
|
+
crypto=crypto,
|
|
1262
|
+
chain_type=chain_type,
|
|
1263
|
+
safe=safe,
|
|
1264
|
+
)
|
|
1265
|
+
|
|
1266
|
+
def new_tx(self) -> GnosisSafeTransaction:
|
|
1267
|
+
"""Create a new GnosisSafeTransaction instance."""
|
|
1268
|
+
return EthSafeTxBuilder._new_tx(
|
|
1269
|
+
ledger_api=self.wallet.ledger_api(
|
|
1270
|
+
chain=OperateChain.from_string(self.chain_type.value),
|
|
1271
|
+
rpc=self.rpc,
|
|
1272
|
+
),
|
|
863
1273
|
crypto=self.crypto,
|
|
864
1274
|
chain_type=self.chain_type,
|
|
865
|
-
safe=t.cast(str, self.
|
|
1275
|
+
safe=t.cast(str, self.safe),
|
|
866
1276
|
)
|
|
867
1277
|
|
|
868
1278
|
def get_mint_tx_data( # pylint: disable=too-many-arguments
|
|
@@ -875,6 +1285,8 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
875
1285
|
nft: Optional[Union[Path, IPFSHash]],
|
|
876
1286
|
update_token: t.Optional[int] = None,
|
|
877
1287
|
token: t.Optional[str] = None,
|
|
1288
|
+
metadata_description: t.Optional[str] = None,
|
|
1289
|
+
skip_depencency_check: t.Optional[bool] = False,
|
|
878
1290
|
) -> t.Dict:
|
|
879
1291
|
"""Build mint transaction."""
|
|
880
1292
|
# TODO: Support for update
|
|
@@ -889,56 +1301,73 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
889
1301
|
sleep=ON_CHAIN_INTERACT_SLEEP,
|
|
890
1302
|
)
|
|
891
1303
|
# Prepare for minting
|
|
1304
|
+
|
|
892
1305
|
(
|
|
893
1306
|
manager.load_package_configuration(
|
|
894
1307
|
package_path=package_path, package_type=PackageType.SERVICE
|
|
895
1308
|
)
|
|
896
1309
|
.load_metadata()
|
|
1310
|
+
.set_metadata_fields(description=metadata_description)
|
|
897
1311
|
.verify_nft(nft=nft)
|
|
898
|
-
.verify_service_dependencies(agent_id=agent_id)
|
|
899
|
-
.publish_metadata()
|
|
900
|
-
)
|
|
901
|
-
instance = registry_contracts.service_manager.get_instance(
|
|
902
|
-
ledger_api=self.ledger_api,
|
|
903
|
-
contract_address=self.contracts["service_manager"],
|
|
904
1312
|
)
|
|
905
1313
|
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
1314
|
+
if skip_depencency_check is False:
|
|
1315
|
+
logging.warning("Skipping depencencies check")
|
|
1316
|
+
manager.verify_service_dependencies(agent_id=agent_id)
|
|
1317
|
+
|
|
1318
|
+
manager.publish_metadata()
|
|
1319
|
+
|
|
1320
|
+
instance = self.service_manager_instance
|
|
1321
|
+
if update_token is None:
|
|
1322
|
+
safe = self.safe
|
|
1323
|
+
txd = instance.encodeABI(
|
|
1324
|
+
fn_name="create",
|
|
1325
|
+
args=[
|
|
1326
|
+
safe,
|
|
1327
|
+
token or ETHEREUM_ERC20,
|
|
1328
|
+
manager.metadata_hash,
|
|
1329
|
+
[agent_id],
|
|
1330
|
+
[[number_of_slots, cost_of_bond]],
|
|
1331
|
+
threshold,
|
|
1332
|
+
],
|
|
1333
|
+
)
|
|
1334
|
+
else:
|
|
1335
|
+
txd = instance.encodeABI(
|
|
1336
|
+
fn_name="update",
|
|
1337
|
+
args=[
|
|
1338
|
+
token or ETHEREUM_ERC20,
|
|
1339
|
+
manager.metadata_hash,
|
|
1340
|
+
[agent_id],
|
|
1341
|
+
[[number_of_slots, cost_of_bond]],
|
|
1342
|
+
threshold,
|
|
1343
|
+
update_token,
|
|
1344
|
+
],
|
|
1345
|
+
)
|
|
917
1346
|
|
|
918
1347
|
return {
|
|
919
|
-
"to": self.
|
|
1348
|
+
"to": self.service_manager_address,
|
|
920
1349
|
"data": txd[2:],
|
|
921
1350
|
"operation": MultiSendOperation.CALL,
|
|
922
1351
|
"value": 0,
|
|
923
1352
|
}
|
|
924
1353
|
|
|
925
|
-
def
|
|
1354
|
+
def get_erc20_approval_data(
|
|
926
1355
|
self,
|
|
927
1356
|
spender: str,
|
|
928
1357
|
amount: int,
|
|
929
|
-
|
|
1358
|
+
erc20_contract: str,
|
|
930
1359
|
) -> t.Dict:
|
|
931
1360
|
"""Get activate tx data."""
|
|
932
1361
|
instance = registry_contracts.erc20.get_instance(
|
|
933
1362
|
ledger_api=self.ledger_api,
|
|
934
|
-
contract_address=
|
|
1363
|
+
contract_address=erc20_contract,
|
|
935
1364
|
)
|
|
936
1365
|
txd = instance.encodeABI(
|
|
937
1366
|
fn_name="approve",
|
|
938
1367
|
args=[spender, amount],
|
|
939
1368
|
)
|
|
940
1369
|
return {
|
|
941
|
-
"to":
|
|
1370
|
+
"to": erc20_contract,
|
|
942
1371
|
"data": txd[2:],
|
|
943
1372
|
"operation": MultiSendOperation.CALL,
|
|
944
1373
|
"value": 0,
|
|
@@ -948,15 +1377,15 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
948
1377
|
"""Get activate tx data."""
|
|
949
1378
|
instance = registry_contracts.service_manager.get_instance(
|
|
950
1379
|
ledger_api=self.ledger_api,
|
|
951
|
-
contract_address=self.
|
|
1380
|
+
contract_address=self.service_manager_address,
|
|
952
1381
|
)
|
|
953
1382
|
txd = instance.encodeABI(
|
|
954
1383
|
fn_name="activateRegistration",
|
|
955
1384
|
args=[service_id],
|
|
956
1385
|
)
|
|
957
1386
|
return {
|
|
958
|
-
"from": self.
|
|
959
|
-
"to": self.
|
|
1387
|
+
"from": self.safe,
|
|
1388
|
+
"to": self.service_manager_address,
|
|
960
1389
|
"data": txd[2:],
|
|
961
1390
|
"operation": MultiSendOperation.CALL,
|
|
962
1391
|
"value": cost_of_bond,
|
|
@@ -972,7 +1401,7 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
972
1401
|
"""Get register instances tx data."""
|
|
973
1402
|
instance = registry_contracts.service_manager.get_instance(
|
|
974
1403
|
ledger_api=self.ledger_api,
|
|
975
|
-
contract_address=self.
|
|
1404
|
+
contract_address=self.service_manager_address,
|
|
976
1405
|
)
|
|
977
1406
|
txd = instance.encodeABI(
|
|
978
1407
|
fn_name="registerAgents",
|
|
@@ -983,43 +1412,73 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
983
1412
|
],
|
|
984
1413
|
)
|
|
985
1414
|
return {
|
|
986
|
-
"from": self.
|
|
987
|
-
"to": self.
|
|
1415
|
+
"from": self.safe,
|
|
1416
|
+
"to": self.service_manager_address,
|
|
988
1417
|
"data": txd[2:],
|
|
989
1418
|
"operation": MultiSendOperation.CALL,
|
|
990
1419
|
"value": cost_of_bond,
|
|
991
1420
|
}
|
|
992
1421
|
|
|
993
|
-
def
|
|
1422
|
+
def get_deploy_data_from_safe(
|
|
994
1423
|
self,
|
|
995
1424
|
service_id: int,
|
|
1425
|
+
master_safe: str,
|
|
996
1426
|
reuse_multisig: bool = False,
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1427
|
+
use_recovery_module: bool = True,
|
|
1428
|
+
) -> t.List[t.Dict[str, t.Any]]:
|
|
1429
|
+
"""Get the deploy data instructions for a safe"""
|
|
1430
|
+
registry_instance = registry_contracts.service_manager.get_instance(
|
|
1000
1431
|
ledger_api=self.ledger_api,
|
|
1001
|
-
contract_address=self.
|
|
1432
|
+
contract_address=self.service_manager_address,
|
|
1002
1433
|
)
|
|
1434
|
+
approve_hash_message = None
|
|
1003
1435
|
if reuse_multisig:
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1436
|
+
if not use_recovery_module:
|
|
1437
|
+
(
|
|
1438
|
+
_deployment_payload,
|
|
1439
|
+
approve_hash_message,
|
|
1440
|
+
error,
|
|
1441
|
+
) = get_reuse_multisig_from_safe_payload(
|
|
1442
|
+
ledger_api=self.ledger_api,
|
|
1443
|
+
chain_type=self.chain_type,
|
|
1444
|
+
service_id=service_id,
|
|
1445
|
+
master_safe=master_safe,
|
|
1446
|
+
)
|
|
1447
|
+
if _deployment_payload is None:
|
|
1448
|
+
raise ValueError(error)
|
|
1449
|
+
deployment_payload = _deployment_payload
|
|
1450
|
+
gnosis_safe_multisig = ContractConfigs.get(
|
|
1451
|
+
GNOSIS_SAFE_SAME_ADDRESS_MULTISIG_CONTRACT.name
|
|
1452
|
+
).contracts[self.chain_type]
|
|
1453
|
+
else:
|
|
1454
|
+
(
|
|
1455
|
+
_deployment_payload,
|
|
1456
|
+
error,
|
|
1457
|
+
) = get_reuse_multisig_with_recovery_from_safe_payload(
|
|
1458
|
+
ledger_api=self.ledger_api,
|
|
1459
|
+
chain_type=self.chain_type,
|
|
1460
|
+
service_id=service_id,
|
|
1461
|
+
master_safe=master_safe,
|
|
1462
|
+
)
|
|
1463
|
+
if _deployment_payload is None:
|
|
1464
|
+
raise ValueError(error)
|
|
1465
|
+
deployment_payload = _deployment_payload
|
|
1466
|
+
gnosis_safe_multisig = ContractConfigs.get(
|
|
1467
|
+
RECOVERY_MODULE_CONTRACT.name
|
|
1468
|
+
).contracts[self.chain_type]
|
|
1469
|
+
else: # Deploy a new multisig
|
|
1470
|
+
if not use_recovery_module:
|
|
1471
|
+
deployment_payload = get_deployment_payload()
|
|
1472
|
+
gnosis_safe_multisig = ContractConfigs.get(
|
|
1473
|
+
GNOSIS_SAFE_PROXY_FACTORY_CONTRACT.name
|
|
1474
|
+
).contracts[self.chain_type]
|
|
1475
|
+
else:
|
|
1476
|
+
deployment_payload = get_deployment_with_recovery_payload()
|
|
1477
|
+
gnosis_safe_multisig = ContractConfigs.get(
|
|
1478
|
+
SAFE_MULTISIG_WITH_RECOVERY_MODULE_CONTRACT.name
|
|
1479
|
+
).contracts[self.chain_type]
|
|
1480
|
+
|
|
1481
|
+
deploy_data = registry_instance.encodeABI(
|
|
1023
1482
|
fn_name="deploy",
|
|
1024
1483
|
args=[
|
|
1025
1484
|
service_id,
|
|
@@ -1027,25 +1486,204 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1027
1486
|
deployment_payload,
|
|
1028
1487
|
],
|
|
1029
1488
|
)
|
|
1030
|
-
|
|
1031
|
-
"to": self.
|
|
1032
|
-
"data":
|
|
1489
|
+
deploy_message = {
|
|
1490
|
+
"to": self.service_manager_address,
|
|
1491
|
+
"data": deploy_data[2:],
|
|
1492
|
+
"operation": MultiSendOperation.CALL,
|
|
1493
|
+
"value": 0,
|
|
1494
|
+
}
|
|
1495
|
+
if approve_hash_message is None:
|
|
1496
|
+
return [deploy_message]
|
|
1497
|
+
return [approve_hash_message, deploy_message]
|
|
1498
|
+
|
|
1499
|
+
def get_safe_b_native_transfer_messages( # pylint: disable=too-many-locals
|
|
1500
|
+
self,
|
|
1501
|
+
safe_b_address: str,
|
|
1502
|
+
to: str,
|
|
1503
|
+
amount: int,
|
|
1504
|
+
) -> t.Tuple[t.Dict, t.Dict]:
|
|
1505
|
+
"""
|
|
1506
|
+
Build the two messages (Safe calls) to withdraw native ETH from Safe B via this Safe (owner of Safe B).
|
|
1507
|
+
|
|
1508
|
+
Builds the messages to be settled by this Safe:
|
|
1509
|
+
1) approveHash(inner_tx_hash)
|
|
1510
|
+
2) execTransaction(...) to transfer ETH
|
|
1511
|
+
"""
|
|
1512
|
+
safe_b_instance = registry_contracts.gnosis_safe.get_instance(
|
|
1513
|
+
ledger_api=self.ledger_api,
|
|
1514
|
+
contract_address=safe_b_address,
|
|
1515
|
+
)
|
|
1516
|
+
|
|
1517
|
+
txs = []
|
|
1518
|
+
txs.append(
|
|
1519
|
+
{
|
|
1520
|
+
"to": to,
|
|
1521
|
+
"data": b"",
|
|
1522
|
+
"operation": MultiSendOperation.CALL,
|
|
1523
|
+
"value": amount,
|
|
1524
|
+
}
|
|
1525
|
+
)
|
|
1526
|
+
|
|
1527
|
+
multisend_address = ContractConfigs.get(MULTISEND_CONTRACT.name).contracts[
|
|
1528
|
+
self.chain_type
|
|
1529
|
+
]
|
|
1530
|
+
multisend_tx = registry_contracts.multisend.get_multisend_tx(
|
|
1531
|
+
ledger_api=self.ledger_api,
|
|
1532
|
+
contract_address=multisend_address,
|
|
1533
|
+
txs=txs,
|
|
1534
|
+
)
|
|
1535
|
+
|
|
1536
|
+
# Compute inner Safe transaction hash
|
|
1537
|
+
safe_tx_hash = registry_contracts.gnosis_safe.get_raw_safe_transaction_hash(
|
|
1538
|
+
ledger_api=self.ledger_api,
|
|
1539
|
+
contract_address=safe_b_address,
|
|
1540
|
+
to_address=multisend_address,
|
|
1541
|
+
value=multisend_tx["value"],
|
|
1542
|
+
data=multisend_tx["data"],
|
|
1543
|
+
operation=SafeOperation.CALL.value,
|
|
1544
|
+
).get("tx_hash")
|
|
1545
|
+
|
|
1546
|
+
# Build approveHash message
|
|
1547
|
+
approve_hash_data = safe_b_instance.encodeABI(
|
|
1548
|
+
fn_name="approveHash",
|
|
1549
|
+
args=[safe_tx_hash],
|
|
1550
|
+
)
|
|
1551
|
+
approve_hash_message = {
|
|
1552
|
+
"to": safe_b_address,
|
|
1553
|
+
"data": approve_hash_data[2:],
|
|
1033
1554
|
"operation": MultiSendOperation.CALL,
|
|
1034
1555
|
"value": 0,
|
|
1035
1556
|
}
|
|
1036
1557
|
|
|
1558
|
+
# Build execTransaction message
|
|
1559
|
+
exec_data = safe_b_instance.encodeABI(
|
|
1560
|
+
fn_name="execTransaction",
|
|
1561
|
+
args=[
|
|
1562
|
+
multisend_address,
|
|
1563
|
+
multisend_tx["value"],
|
|
1564
|
+
multisend_tx["data"],
|
|
1565
|
+
SafeOperation.DELEGATE_CALL.value,
|
|
1566
|
+
0, # safeTxGas
|
|
1567
|
+
0, # baseGas
|
|
1568
|
+
0, # gasPrice
|
|
1569
|
+
ZERO_ADDRESS, # gasToken
|
|
1570
|
+
ZERO_ADDRESS, # refundReceiver
|
|
1571
|
+
get_packed_signature_for_approved_hash(owners=(self.safe,)),
|
|
1572
|
+
],
|
|
1573
|
+
)
|
|
1574
|
+
exec_message = {
|
|
1575
|
+
"to": safe_b_address,
|
|
1576
|
+
"data": exec_data[2:],
|
|
1577
|
+
"operation": MultiSendOperation.CALL,
|
|
1578
|
+
"value": 0,
|
|
1579
|
+
}
|
|
1580
|
+
|
|
1581
|
+
return approve_hash_message, exec_message
|
|
1582
|
+
|
|
1583
|
+
def get_safe_b_erc20_transfer_messages( # pylint: disable=too-many-locals
|
|
1584
|
+
self,
|
|
1585
|
+
safe_b_address: str,
|
|
1586
|
+
token: str,
|
|
1587
|
+
to: str,
|
|
1588
|
+
amount: int,
|
|
1589
|
+
) -> t.Tuple[t.Dict, t.Dict]:
|
|
1590
|
+
"""
|
|
1591
|
+
Build the two messages (Safe calls) to withdraw ERC20 from Safe B via this Safe (owner of Safe B).
|
|
1592
|
+
|
|
1593
|
+
Builds the messages to be settled by this Safe:
|
|
1594
|
+
1) approveHash(inner_tx_hash)
|
|
1595
|
+
2) execTransaction(...) to transfer ERC20 tokens
|
|
1596
|
+
"""
|
|
1597
|
+
safe_b_instance = registry_contracts.gnosis_safe.get_instance(
|
|
1598
|
+
ledger_api=self.ledger_api,
|
|
1599
|
+
contract_address=safe_b_address,
|
|
1600
|
+
)
|
|
1601
|
+
erc20_instance = registry_contracts.erc20.get_instance(
|
|
1602
|
+
ledger_api=self.ledger_api,
|
|
1603
|
+
contract_address=token,
|
|
1604
|
+
)
|
|
1605
|
+
|
|
1606
|
+
txs = []
|
|
1607
|
+
txs.append(
|
|
1608
|
+
{
|
|
1609
|
+
"to": token,
|
|
1610
|
+
"data": erc20_instance.encodeABI(
|
|
1611
|
+
fn_name="transfer",
|
|
1612
|
+
args=[to, amount],
|
|
1613
|
+
),
|
|
1614
|
+
"operation": MultiSendOperation.CALL,
|
|
1615
|
+
"value": 0,
|
|
1616
|
+
}
|
|
1617
|
+
)
|
|
1618
|
+
|
|
1619
|
+
multisend_address = ContractConfigs.get(MULTISEND_CONTRACT.name).contracts[
|
|
1620
|
+
self.chain_type
|
|
1621
|
+
]
|
|
1622
|
+
multisend_tx = registry_contracts.multisend.get_multisend_tx(
|
|
1623
|
+
ledger_api=self.ledger_api,
|
|
1624
|
+
contract_address=multisend_address,
|
|
1625
|
+
txs=txs,
|
|
1626
|
+
)
|
|
1627
|
+
|
|
1628
|
+
# Compute inner Safe transaction hash
|
|
1629
|
+
safe_tx_hash = registry_contracts.gnosis_safe.get_raw_safe_transaction_hash(
|
|
1630
|
+
ledger_api=self.ledger_api,
|
|
1631
|
+
contract_address=safe_b_address,
|
|
1632
|
+
to_address=multisend_address,
|
|
1633
|
+
value=multisend_tx["value"],
|
|
1634
|
+
data=multisend_tx["data"],
|
|
1635
|
+
operation=SafeOperation.CALL.value,
|
|
1636
|
+
).get("tx_hash")
|
|
1637
|
+
|
|
1638
|
+
# Build approveHash message
|
|
1639
|
+
approve_hash_data = safe_b_instance.encodeABI(
|
|
1640
|
+
fn_name="approveHash",
|
|
1641
|
+
args=[safe_tx_hash],
|
|
1642
|
+
)
|
|
1643
|
+
approve_hash_message = {
|
|
1644
|
+
"to": safe_b_address,
|
|
1645
|
+
"data": approve_hash_data[2:],
|
|
1646
|
+
"operation": MultiSendOperation.CALL,
|
|
1647
|
+
"value": 0,
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
# Build execTransaction message
|
|
1651
|
+
exec_data = safe_b_instance.encodeABI(
|
|
1652
|
+
fn_name="execTransaction",
|
|
1653
|
+
args=[
|
|
1654
|
+
multisend_address,
|
|
1655
|
+
multisend_tx["value"],
|
|
1656
|
+
multisend_tx["data"],
|
|
1657
|
+
SafeOperation.DELEGATE_CALL.value,
|
|
1658
|
+
0, # safeTxGas
|
|
1659
|
+
0, # baseGas
|
|
1660
|
+
0, # gasPrice
|
|
1661
|
+
ZERO_ADDRESS, # gasToken
|
|
1662
|
+
ZERO_ADDRESS, # refundReceiver
|
|
1663
|
+
get_packed_signature_for_approved_hash(owners=(self.safe,)),
|
|
1664
|
+
],
|
|
1665
|
+
)
|
|
1666
|
+
exec_message = {
|
|
1667
|
+
"to": safe_b_address,
|
|
1668
|
+
"data": exec_data[2:],
|
|
1669
|
+
"operation": MultiSendOperation.CALL,
|
|
1670
|
+
"value": 0,
|
|
1671
|
+
}
|
|
1672
|
+
|
|
1673
|
+
return approve_hash_message, exec_message
|
|
1674
|
+
|
|
1037
1675
|
def get_terminate_data(self, service_id: int) -> t.Dict:
|
|
1038
1676
|
"""Get terminate tx data."""
|
|
1039
1677
|
instance = registry_contracts.service_manager.get_instance(
|
|
1040
1678
|
ledger_api=self.ledger_api,
|
|
1041
|
-
contract_address=self.
|
|
1679
|
+
contract_address=self.service_manager_address,
|
|
1042
1680
|
)
|
|
1043
1681
|
txd = instance.encodeABI(
|
|
1044
1682
|
fn_name="terminate",
|
|
1045
1683
|
args=[service_id],
|
|
1046
1684
|
)
|
|
1047
1685
|
return {
|
|
1048
|
-
"to": self.
|
|
1686
|
+
"to": self.service_manager_address,
|
|
1049
1687
|
"data": txd[2:],
|
|
1050
1688
|
"operation": MultiSendOperation.CALL,
|
|
1051
1689
|
"value": 0,
|
|
@@ -1055,14 +1693,14 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1055
1693
|
"""Get unbond tx data."""
|
|
1056
1694
|
instance = registry_contracts.service_manager.get_instance(
|
|
1057
1695
|
ledger_api=self.ledger_api,
|
|
1058
|
-
contract_address=self.
|
|
1696
|
+
contract_address=self.service_manager_address,
|
|
1059
1697
|
)
|
|
1060
1698
|
txd = instance.encodeABI(
|
|
1061
1699
|
fn_name="unbond",
|
|
1062
1700
|
args=[service_id],
|
|
1063
1701
|
)
|
|
1064
1702
|
return {
|
|
1065
|
-
"to": self.
|
|
1703
|
+
"to": self.service_manager_address,
|
|
1066
1704
|
"data": txd[2:],
|
|
1067
1705
|
"operation": MultiSendOperation.CALL,
|
|
1068
1706
|
"value": 0,
|
|
@@ -1077,16 +1715,14 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1077
1715
|
"""Get staking approval data"""
|
|
1078
1716
|
self._patch()
|
|
1079
1717
|
txd = StakingManager(
|
|
1080
|
-
|
|
1081
|
-
password=self.wallet.password,
|
|
1082
|
-
chain_type=self.chain_type,
|
|
1718
|
+
chain=OperateChain(self.chain_type.value),
|
|
1083
1719
|
).get_stake_approval_tx_data(
|
|
1084
1720
|
service_id=service_id,
|
|
1085
1721
|
service_registry=service_registry,
|
|
1086
1722
|
staking_contract=staking_contract,
|
|
1087
1723
|
)
|
|
1088
1724
|
return {
|
|
1089
|
-
"from": self.
|
|
1725
|
+
"from": self.safe,
|
|
1090
1726
|
"to": self.contracts["service_registry"],
|
|
1091
1727
|
"data": txd[2:],
|
|
1092
1728
|
"operation": MultiSendOperation.CALL,
|
|
@@ -1101,9 +1737,7 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1101
1737
|
"""Get staking tx data"""
|
|
1102
1738
|
self._patch()
|
|
1103
1739
|
txd = StakingManager(
|
|
1104
|
-
|
|
1105
|
-
password=self.wallet.password,
|
|
1106
|
-
chain_type=self.chain_type,
|
|
1740
|
+
chain=OperateChain(self.chain_type.value),
|
|
1107
1741
|
).get_stake_tx_data(
|
|
1108
1742
|
service_id=service_id,
|
|
1109
1743
|
staking_contract=staking_contract,
|
|
@@ -1119,14 +1753,42 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1119
1753
|
self,
|
|
1120
1754
|
service_id: int,
|
|
1121
1755
|
staking_contract: str,
|
|
1756
|
+
force: bool = False,
|
|
1122
1757
|
) -> t.Dict:
|
|
1123
1758
|
"""Get unstaking tx data"""
|
|
1124
1759
|
self._patch()
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1760
|
+
staking_manager = StakingManager(
|
|
1761
|
+
chain=OperateChain(self.chain_type.value),
|
|
1762
|
+
)
|
|
1763
|
+
txd = (
|
|
1764
|
+
staking_manager.get_forced_unstake_tx_data(
|
|
1765
|
+
service_id=service_id,
|
|
1766
|
+
staking_contract=staking_contract,
|
|
1767
|
+
)
|
|
1768
|
+
if force
|
|
1769
|
+
else staking_manager.get_unstake_tx_data(
|
|
1770
|
+
service_id=service_id,
|
|
1771
|
+
staking_contract=staking_contract,
|
|
1772
|
+
)
|
|
1773
|
+
)
|
|
1774
|
+
return {
|
|
1775
|
+
"to": staking_contract,
|
|
1776
|
+
"data": txd[2:],
|
|
1777
|
+
"operation": MultiSendOperation.CALL,
|
|
1778
|
+
"value": 0,
|
|
1779
|
+
}
|
|
1780
|
+
|
|
1781
|
+
def get_claiming_data(
|
|
1782
|
+
self,
|
|
1783
|
+
service_id: int,
|
|
1784
|
+
staking_contract: str,
|
|
1785
|
+
) -> t.Dict:
|
|
1786
|
+
"""Get claiming tx data"""
|
|
1787
|
+
self._patch()
|
|
1788
|
+
staking_manager = StakingManager(
|
|
1789
|
+
chain=OperateChain(self.chain_type.value),
|
|
1790
|
+
)
|
|
1791
|
+
txd = staking_manager.get_claim_tx_data(
|
|
1130
1792
|
service_id=service_id,
|
|
1131
1793
|
staking_contract=staking_contract,
|
|
1132
1794
|
)
|
|
@@ -1141,26 +1803,270 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1141
1803
|
"""Stake service."""
|
|
1142
1804
|
self._patch()
|
|
1143
1805
|
return StakingManager(
|
|
1144
|
-
|
|
1145
|
-
password=self.wallet.password,
|
|
1146
|
-
chain_type=self.chain_type,
|
|
1806
|
+
chain=OperateChain(self.chain_type.value),
|
|
1147
1807
|
).slots_available(
|
|
1148
1808
|
staking_contract=staking_contract,
|
|
1149
1809
|
)
|
|
1150
1810
|
|
|
1151
|
-
def
|
|
1152
|
-
"""
|
|
1811
|
+
def can_unstake(self, service_id: int, staking_contract: str) -> bool:
|
|
1812
|
+
"""Can unstake the service?"""
|
|
1153
1813
|
self._patch()
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1814
|
+
try:
|
|
1815
|
+
StakingManager(
|
|
1816
|
+
chain=OperateChain(self.chain_type.value),
|
|
1817
|
+
).check_if_unstaking_possible(
|
|
1818
|
+
service_id=service_id,
|
|
1819
|
+
staking_contract=staking_contract,
|
|
1820
|
+
)
|
|
1821
|
+
return True
|
|
1822
|
+
except ValueError:
|
|
1823
|
+
return False
|
|
1162
1824
|
|
|
1163
1825
|
def get_swap_data(self, service_id: int, multisig: str, owner_key: str) -> t.Dict:
|
|
1164
1826
|
"""Swap safe owner."""
|
|
1165
1827
|
# TODO: Discuss implementation
|
|
1166
1828
|
raise NotImplementedError()
|
|
1829
|
+
|
|
1830
|
+
def get_recover_access_data(self, service_id: int) -> t.Dict:
|
|
1831
|
+
"""Get recover access tx data."""
|
|
1832
|
+
instance = t.cast(
|
|
1833
|
+
RecoveryModule,
|
|
1834
|
+
RecoveryModule.from_dir(
|
|
1835
|
+
directory=str(DATA_DIR / "contracts" / "recovery_module"),
|
|
1836
|
+
),
|
|
1837
|
+
).get_instance(
|
|
1838
|
+
ledger_api=self.ledger_api,
|
|
1839
|
+
contract_address=self.contracts["recovery_module"],
|
|
1840
|
+
)
|
|
1841
|
+
# TODO Replace the line above by this one once the recovery_module is
|
|
1842
|
+
# included in the release of OpenAutonomy.
|
|
1843
|
+
# instance = registry_contracts.recovery_module.get_instance( # noqa: E800
|
|
1844
|
+
# ledger_api=self.ledger_api, # noqa: E800
|
|
1845
|
+
# contract_address=self.contracts["recovery_module"], # noqa: E800
|
|
1846
|
+
# ) # noqa: E800
|
|
1847
|
+
txd = instance.encodeABI(
|
|
1848
|
+
fn_name="recoverAccess",
|
|
1849
|
+
args=[service_id],
|
|
1850
|
+
)
|
|
1851
|
+
return {
|
|
1852
|
+
"to": self.contracts["recovery_module"],
|
|
1853
|
+
"data": txd[2:],
|
|
1854
|
+
"operation": MultiSendOperation.CALL,
|
|
1855
|
+
"value": 0,
|
|
1856
|
+
}
|
|
1857
|
+
|
|
1858
|
+
def get_enable_module_data(
|
|
1859
|
+
self,
|
|
1860
|
+
safe_address: str,
|
|
1861
|
+
module_address: str,
|
|
1862
|
+
) -> t.Dict:
|
|
1863
|
+
"""Get enable module tx data"""
|
|
1864
|
+
self._patch()
|
|
1865
|
+
instance = registry_contracts.gnosis_safe.get_instance(
|
|
1866
|
+
ledger_api=self.ledger_api,
|
|
1867
|
+
contract_address=safe_address,
|
|
1868
|
+
)
|
|
1869
|
+
txd = instance.encodeABI(
|
|
1870
|
+
fn_name="enableModule",
|
|
1871
|
+
args=[module_address],
|
|
1872
|
+
)
|
|
1873
|
+
return {
|
|
1874
|
+
"to": safe_address,
|
|
1875
|
+
"data": txd[2:],
|
|
1876
|
+
"operation": MultiSendOperation.CALL,
|
|
1877
|
+
"value": 0,
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
|
|
1881
|
+
def get_packed_signature_for_approved_hash(owners: t.Tuple[str]) -> bytes:
|
|
1882
|
+
"""Get the packed signatures."""
|
|
1883
|
+
sorted_owners = sorted(owners, key=str.lower)
|
|
1884
|
+
signatures = b""
|
|
1885
|
+
for owner in sorted_owners:
|
|
1886
|
+
# Convert address to bytes and ensure it is 32 bytes long (left-padded with zeros)
|
|
1887
|
+
r_bytes = to_bytes(hexstr=owner[2:].rjust(64, "0"))
|
|
1888
|
+
|
|
1889
|
+
# `s` as 32 zero bytes
|
|
1890
|
+
s_bytes = b"\x00" * 32
|
|
1891
|
+
|
|
1892
|
+
# `v` as a single byte
|
|
1893
|
+
v_bytes = to_bytes(1)
|
|
1894
|
+
|
|
1895
|
+
# Concatenate r, s, and v to form the packed signature
|
|
1896
|
+
packed_signature = r_bytes + s_bytes + v_bytes
|
|
1897
|
+
signatures += packed_signature
|
|
1898
|
+
|
|
1899
|
+
return signatures
|
|
1900
|
+
|
|
1901
|
+
|
|
1902
|
+
def get_reuse_multisig_from_safe_payload( # pylint: disable=too-many-locals
|
|
1903
|
+
ledger_api: LedgerApi,
|
|
1904
|
+
chain_type: ChainType,
|
|
1905
|
+
service_id: int,
|
|
1906
|
+
master_safe: str,
|
|
1907
|
+
) -> t.Tuple[Optional[str], Optional[t.Dict[str, t.Any]], Optional[str]]:
|
|
1908
|
+
"""Reuse multisig."""
|
|
1909
|
+
_, multisig_address, _, threshold, *_ = get_service_info(
|
|
1910
|
+
ledger_api=ledger_api,
|
|
1911
|
+
chain_type=chain_type,
|
|
1912
|
+
token_id=service_id,
|
|
1913
|
+
)
|
|
1914
|
+
if multisig_address == ZERO_ADDRESS:
|
|
1915
|
+
return None, None, "Cannot reuse multisig, No previous deployment exist!"
|
|
1916
|
+
|
|
1917
|
+
multisend_address = ContractConfigs.get(MULTISEND_CONTRACT.name).contracts[
|
|
1918
|
+
chain_type
|
|
1919
|
+
]
|
|
1920
|
+
multisig_instance = registry_contracts.gnosis_safe.get_instance(
|
|
1921
|
+
ledger_api=ledger_api,
|
|
1922
|
+
contract_address=multisig_address,
|
|
1923
|
+
)
|
|
1924
|
+
|
|
1925
|
+
# Verify if the service was terminated properly or not
|
|
1926
|
+
old_owners = multisig_instance.functions.getOwners().call()
|
|
1927
|
+
if len(old_owners) != 1 or master_safe not in old_owners:
|
|
1928
|
+
return (
|
|
1929
|
+
None,
|
|
1930
|
+
None,
|
|
1931
|
+
"Service was not terminated properly, the service owner should be the only owner of the safe",
|
|
1932
|
+
)
|
|
1933
|
+
|
|
1934
|
+
# Build multisend tx to add new instances as owners
|
|
1935
|
+
txs = []
|
|
1936
|
+
new_owners = t.cast(
|
|
1937
|
+
t.List[str],
|
|
1938
|
+
get_agent_instances(
|
|
1939
|
+
ledger_api=ledger_api,
|
|
1940
|
+
chain_type=chain_type,
|
|
1941
|
+
token_id=service_id,
|
|
1942
|
+
).get("agentInstances"),
|
|
1943
|
+
)
|
|
1944
|
+
|
|
1945
|
+
for _owner in new_owners:
|
|
1946
|
+
txs.append(
|
|
1947
|
+
{
|
|
1948
|
+
"to": multisig_address,
|
|
1949
|
+
"data": HexBytes(
|
|
1950
|
+
bytes.fromhex(
|
|
1951
|
+
multisig_instance.encodeABI(
|
|
1952
|
+
fn_name="addOwnerWithThreshold",
|
|
1953
|
+
args=[_owner, 1],
|
|
1954
|
+
)[2:]
|
|
1955
|
+
)
|
|
1956
|
+
),
|
|
1957
|
+
"operation": MultiSendOperation.CALL,
|
|
1958
|
+
"value": 0,
|
|
1959
|
+
}
|
|
1960
|
+
)
|
|
1961
|
+
|
|
1962
|
+
txs.append(
|
|
1963
|
+
{
|
|
1964
|
+
"to": multisig_address,
|
|
1965
|
+
"data": HexBytes(
|
|
1966
|
+
bytes.fromhex(
|
|
1967
|
+
multisig_instance.encodeABI(
|
|
1968
|
+
fn_name="removeOwner",
|
|
1969
|
+
args=[new_owners[0], master_safe, 1],
|
|
1970
|
+
)[2:]
|
|
1971
|
+
)
|
|
1972
|
+
),
|
|
1973
|
+
"operation": MultiSendOperation.CALL,
|
|
1974
|
+
"value": 0,
|
|
1975
|
+
}
|
|
1976
|
+
)
|
|
1977
|
+
|
|
1978
|
+
txs.append(
|
|
1979
|
+
{
|
|
1980
|
+
"to": multisig_address,
|
|
1981
|
+
"data": HexBytes(
|
|
1982
|
+
bytes.fromhex(
|
|
1983
|
+
multisig_instance.encodeABI(
|
|
1984
|
+
fn_name="changeThreshold",
|
|
1985
|
+
args=[threshold],
|
|
1986
|
+
)[2:]
|
|
1987
|
+
)
|
|
1988
|
+
),
|
|
1989
|
+
"operation": MultiSendOperation.CALL,
|
|
1990
|
+
"value": 0,
|
|
1991
|
+
}
|
|
1992
|
+
)
|
|
1993
|
+
|
|
1994
|
+
multisend_tx = registry_contracts.multisend.get_multisend_tx(
|
|
1995
|
+
ledger_api=ledger_api,
|
|
1996
|
+
contract_address=multisend_address,
|
|
1997
|
+
txs=txs,
|
|
1998
|
+
)
|
|
1999
|
+
signature_bytes = get_packed_signature_for_approved_hash(owners=(master_safe,))
|
|
2000
|
+
|
|
2001
|
+
safe_tx_hash = registry_contracts.gnosis_safe.get_raw_safe_transaction_hash(
|
|
2002
|
+
ledger_api=ledger_api,
|
|
2003
|
+
contract_address=multisig_address,
|
|
2004
|
+
to_address=multisend_address,
|
|
2005
|
+
value=multisend_tx["value"],
|
|
2006
|
+
data=multisend_tx["data"],
|
|
2007
|
+
operation=1,
|
|
2008
|
+
).get("tx_hash")
|
|
2009
|
+
approve_hash_data = multisig_instance.encodeABI(
|
|
2010
|
+
fn_name="approveHash",
|
|
2011
|
+
args=[
|
|
2012
|
+
safe_tx_hash,
|
|
2013
|
+
],
|
|
2014
|
+
)
|
|
2015
|
+
approve_hash_message = {
|
|
2016
|
+
"to": multisig_address,
|
|
2017
|
+
"data": approve_hash_data[2:],
|
|
2018
|
+
"operation": MultiSendOperation.CALL,
|
|
2019
|
+
"value": 0,
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
safe_exec_data = multisig_instance.encodeABI(
|
|
2023
|
+
fn_name="execTransaction",
|
|
2024
|
+
args=[
|
|
2025
|
+
multisend_address, # to address
|
|
2026
|
+
multisend_tx["value"], # value
|
|
2027
|
+
multisend_tx["data"], # data
|
|
2028
|
+
1, # operation
|
|
2029
|
+
0, # safe tx gas
|
|
2030
|
+
0, # bas gas
|
|
2031
|
+
0, # safe gas price
|
|
2032
|
+
ZERO_ADDRESS, # gas token
|
|
2033
|
+
ZERO_ADDRESS, # refund receiver
|
|
2034
|
+
signature_bytes, # signatures
|
|
2035
|
+
],
|
|
2036
|
+
)
|
|
2037
|
+
payload = multisig_address + safe_exec_data[2:]
|
|
2038
|
+
return payload, approve_hash_message, None
|
|
2039
|
+
|
|
2040
|
+
|
|
2041
|
+
def get_reuse_multisig_with_recovery_from_safe_payload( # pylint: disable=too-many-locals
|
|
2042
|
+
ledger_api: LedgerApi,
|
|
2043
|
+
chain_type: ChainType,
|
|
2044
|
+
service_id: int,
|
|
2045
|
+
master_safe: str,
|
|
2046
|
+
) -> t.Tuple[Optional[str], Optional[str]]:
|
|
2047
|
+
"""Reuse multisig."""
|
|
2048
|
+
_, multisig_address, _, _, *_ = get_service_info(
|
|
2049
|
+
ledger_api=ledger_api,
|
|
2050
|
+
chain_type=chain_type,
|
|
2051
|
+
token_id=service_id,
|
|
2052
|
+
)
|
|
2053
|
+
if multisig_address == ZERO_ADDRESS:
|
|
2054
|
+
return None, "Cannot reuse multisig, No previous deployment exist!"
|
|
2055
|
+
|
|
2056
|
+
service_owner = master_safe
|
|
2057
|
+
|
|
2058
|
+
multisig_instance = registry_contracts.gnosis_safe.get_instance(
|
|
2059
|
+
ledger_api=ledger_api,
|
|
2060
|
+
contract_address=multisig_address,
|
|
2061
|
+
)
|
|
2062
|
+
|
|
2063
|
+
# Verify if the service was terminated properly or not
|
|
2064
|
+
old_owners = multisig_instance.functions.getOwners().call()
|
|
2065
|
+
if len(old_owners) != 1 or service_owner not in old_owners:
|
|
2066
|
+
return (
|
|
2067
|
+
None,
|
|
2068
|
+
"Service was not terminated properly, the service owner should be the only owner of the safe",
|
|
2069
|
+
)
|
|
2070
|
+
|
|
2071
|
+
payload = "0x" + int(service_id).to_bytes(32, "big").hex()
|
|
2072
|
+
return payload, None
|