olas-operate-middleware 0.10.6__py3-none-any.whl → 0.10.8__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {olas_operate_middleware-0.10.6.dist-info → olas_operate_middleware-0.10.8.dist-info}/METADATA +1 -1
- {olas_operate_middleware-0.10.6.dist-info → olas_operate_middleware-0.10.8.dist-info}/RECORD +24 -19
- operate/__init__.py +12 -0
- operate/cli.py +145 -23
- operate/constants.py +9 -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/keys.py +9 -3
- operate/ledger/__init__.py +38 -24
- operate/ledger/profiles.py +73 -59
- operate/migration.py +73 -14
- operate/operate_types.py +2 -0
- operate/quickstart/reset_password.py +3 -2
- operate/quickstart/run_service.py +7 -2
- operate/services/manage.py +189 -19
- operate/services/protocol.py +153 -76
- operate/services/service.py +40 -58
- operate/wallet/master.py +7 -4
- operate/wallet/wallet_recovery_manager.py +210 -0
- {olas_operate_middleware-0.10.6.dist-info → olas_operate_middleware-0.10.8.dist-info}/LICENSE +0 -0
- {olas_operate_middleware-0.10.6.dist-info → olas_operate_middleware-0.10.8.dist-info}/WHEEL +0 -0
- {olas_operate_middleware-0.10.6.dist-info → olas_operate_middleware-0.10.8.dist-info}/entry_points.txt +0 -0
operate/services/manage.py
CHANGED
|
@@ -37,8 +37,16 @@ from aea_ledger_ethereum import LedgerApi
|
|
|
37
37
|
from autonomy.chain.base import registry_contracts
|
|
38
38
|
from autonomy.chain.config import CHAIN_PROFILES, ChainType
|
|
39
39
|
from autonomy.chain.metadata import IPFS_URI_PREFIX
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
from web3 import Web3
|
|
41
|
+
|
|
42
|
+
from operate.constants import (
|
|
43
|
+
AGENT_LOG_DIR,
|
|
44
|
+
AGENT_LOG_ENV_VAR,
|
|
45
|
+
AGENT_PERSISTENT_STORAGE_DIR,
|
|
46
|
+
AGENT_PERSISTENT_STORAGE_ENV_VAR,
|
|
47
|
+
IPFS_ADDRESS,
|
|
48
|
+
ZERO_ADDRESS,
|
|
49
|
+
)
|
|
42
50
|
from operate.data import DATA_DIR
|
|
43
51
|
from operate.data.contracts.mech_activity.contract import MechActivityContract
|
|
44
52
|
from operate.data.contracts.requester_activity_checker.contract import (
|
|
@@ -624,6 +632,8 @@ class ServiceManager:
|
|
|
624
632
|
# TODO fix this
|
|
625
633
|
os.environ["CUSTOM_CHAIN_RPC"] = ledger_config.rpc
|
|
626
634
|
|
|
635
|
+
self._enable_recovery_module(service_config_id=service_config_id, chain=chain)
|
|
636
|
+
|
|
627
637
|
current_agent_id = None
|
|
628
638
|
on_chain_state = OnChainState.NON_EXISTENT
|
|
629
639
|
if chain_data.token > -1:
|
|
@@ -726,8 +736,8 @@ class ServiceManager:
|
|
|
726
736
|
|
|
727
737
|
# Set environment variables for the service
|
|
728
738
|
for dir_name, env_var_name in (
|
|
729
|
-
(
|
|
730
|
-
(
|
|
739
|
+
(AGENT_PERSISTENT_STORAGE_DIR, AGENT_PERSISTENT_STORAGE_ENV_VAR),
|
|
740
|
+
(AGENT_LOG_DIR, AGENT_LOG_ENV_VAR),
|
|
731
741
|
):
|
|
732
742
|
dir_path = service.path / dir_name
|
|
733
743
|
dir_path.mkdir(parents=True, exist_ok=True)
|
|
@@ -829,6 +839,11 @@ class ServiceManager:
|
|
|
829
839
|
self._get_on_chain_state(service=service, chain=chain)
|
|
830
840
|
== OnChainState.PRE_REGISTRATION
|
|
831
841
|
):
|
|
842
|
+
self.logger.info("Execute recovery module operations")
|
|
843
|
+
self._execute_recovery_module_flow_from_safe(
|
|
844
|
+
service_config_id=service_config_id, chain=chain
|
|
845
|
+
)
|
|
846
|
+
|
|
832
847
|
self.logger.info("Updating service")
|
|
833
848
|
receipt = (
|
|
834
849
|
sftxb.new_tx()
|
|
@@ -1047,6 +1062,7 @@ class ServiceManager:
|
|
|
1047
1062
|
)
|
|
1048
1063
|
).settle()
|
|
1049
1064
|
|
|
1065
|
+
# Deploy service
|
|
1050
1066
|
if (
|
|
1051
1067
|
self._get_on_chain_state(service=service, chain=chain)
|
|
1052
1068
|
== OnChainState.FINISHED_REGISTRATION
|
|
@@ -1055,15 +1071,27 @@ class ServiceManager:
|
|
|
1055
1071
|
|
|
1056
1072
|
reuse_multisig = True
|
|
1057
1073
|
info = sftxb.info(token_id=chain_data.token)
|
|
1058
|
-
|
|
1074
|
+
service_safe_address = info["multisig"]
|
|
1075
|
+
if service_safe_address == ZERO_ADDRESS:
|
|
1059
1076
|
reuse_multisig = False
|
|
1060
1077
|
|
|
1061
1078
|
self.logger.info(f"{reuse_multisig=}")
|
|
1062
1079
|
|
|
1080
|
+
is_recovery_module_enabled = (
|
|
1081
|
+
registry_contracts.gnosis_safe.is_module_enabled(
|
|
1082
|
+
ledger_api=sftxb.ledger_api,
|
|
1083
|
+
contract_address=service_safe_address,
|
|
1084
|
+
module_address=CONTRACTS[Chain(chain)]["recovery_module"],
|
|
1085
|
+
).get("enabled")
|
|
1086
|
+
)
|
|
1087
|
+
|
|
1088
|
+
self.logger.info(f"{is_recovery_module_enabled=}")
|
|
1089
|
+
|
|
1063
1090
|
messages = sftxb.get_deploy_data_from_safe(
|
|
1064
1091
|
service_id=chain_data.token,
|
|
1065
1092
|
reuse_multisig=reuse_multisig,
|
|
1066
1093
|
master_safe=safe,
|
|
1094
|
+
use_recovery_module=is_recovery_module_enabled,
|
|
1067
1095
|
)
|
|
1068
1096
|
tx = sftxb.new_tx()
|
|
1069
1097
|
for message in messages:
|
|
@@ -1198,6 +1226,9 @@ class ServiceManager:
|
|
|
1198
1226
|
wallet = self.wallet_manager.load(ledger_config.chain.ledger_type)
|
|
1199
1227
|
safe = wallet.safes[Chain(chain)] # type: ignore
|
|
1200
1228
|
|
|
1229
|
+
if withdrawal_address:
|
|
1230
|
+
withdrawal_address = Web3.to_checksum_address(withdrawal_address)
|
|
1231
|
+
|
|
1201
1232
|
# TODO fixme
|
|
1202
1233
|
os.environ["CUSTOM_CHAIN_RPC"] = ledger_config.rpc
|
|
1203
1234
|
|
|
@@ -1289,19 +1320,21 @@ class ServiceManager:
|
|
|
1289
1320
|
},
|
|
1290
1321
|
)
|
|
1291
1322
|
|
|
1323
|
+
self._enable_recovery_module(
|
|
1324
|
+
service_config_id=service_config_id, chain=chain
|
|
1325
|
+
)
|
|
1292
1326
|
self.logger.info("Swapping Safe owners")
|
|
1293
|
-
|
|
1294
|
-
|
|
1327
|
+
owner_crypto = self.keys_manager.get_crypto_instance(
|
|
1328
|
+
address=current_safe_owners[0]
|
|
1329
|
+
)
|
|
1330
|
+
sftxb.swap(
|
|
1331
|
+
service_id=chain_data.token,
|
|
1295
1332
|
multisig=chain_data.multisig, # TODO this can be read from the registry
|
|
1296
|
-
|
|
1297
|
-
self.keys_manager.get(
|
|
1298
|
-
key=current_safe_owners[0]
|
|
1299
|
-
).private_key # TODO allow multiple owners
|
|
1300
|
-
), # noqa: E800
|
|
1333
|
+
owner_cryptos=[owner_crypto], # TODO allow multiple owners
|
|
1301
1334
|
new_owner_address=(
|
|
1302
1335
|
safe if safe else wallet.crypto.address
|
|
1303
1336
|
), # TODO it should always be safe address
|
|
1304
|
-
)
|
|
1337
|
+
)
|
|
1305
1338
|
|
|
1306
1339
|
if withdrawal_address is not None:
|
|
1307
1340
|
ethereum_crypto = KeysManager().get_crypto_instance(
|
|
@@ -1318,6 +1351,148 @@ class ServiceManager:
|
|
|
1318
1351
|
)
|
|
1319
1352
|
self.logger.info(f"{service.name} signer drained")
|
|
1320
1353
|
|
|
1354
|
+
def _execute_recovery_module_flow_from_safe( # pylint: disable=too-many-locals
|
|
1355
|
+
self,
|
|
1356
|
+
service_config_id: str,
|
|
1357
|
+
chain: str,
|
|
1358
|
+
) -> None:
|
|
1359
|
+
"""Execute recovery module operations from Safe"""
|
|
1360
|
+
self.logger.info(f"_execute_recovery_module_operations_from_safe {chain=}")
|
|
1361
|
+
service = self.load(service_config_id=service_config_id)
|
|
1362
|
+
chain_config = service.chain_configs[chain]
|
|
1363
|
+
chain_data = chain_config.chain_data
|
|
1364
|
+
ledger_config = chain_config.ledger_config
|
|
1365
|
+
wallet = self.wallet_manager.load(ledger_config.chain.ledger_type)
|
|
1366
|
+
sftxb = self.get_eth_safe_tx_builder(ledger_config=ledger_config)
|
|
1367
|
+
safe = wallet.safes[Chain(chain)]
|
|
1368
|
+
|
|
1369
|
+
if chain_data.token == NON_EXISTENT_TOKEN:
|
|
1370
|
+
self.logger.info("Service is not minted.")
|
|
1371
|
+
return
|
|
1372
|
+
|
|
1373
|
+
info = sftxb.info(token_id=chain_data.token)
|
|
1374
|
+
service_safe_address = info["multisig"]
|
|
1375
|
+
on_chain_state = OnChainState(info["service_state"])
|
|
1376
|
+
|
|
1377
|
+
if service_safe_address == ZERO_ADDRESS:
|
|
1378
|
+
self.logger.info("Service Safe is not deployed.")
|
|
1379
|
+
return
|
|
1380
|
+
|
|
1381
|
+
recovery_module_address = CONTRACTS[Chain(chain)]["recovery_module"]
|
|
1382
|
+
is_recovery_module_enabled = registry_contracts.gnosis_safe.is_module_enabled(
|
|
1383
|
+
ledger_api=sftxb.ledger_api,
|
|
1384
|
+
contract_address=service_safe_address,
|
|
1385
|
+
module_address=recovery_module_address,
|
|
1386
|
+
).get("enabled")
|
|
1387
|
+
|
|
1388
|
+
service_safe_owners = sftxb.get_service_safe_owners(service_id=chain_data.token)
|
|
1389
|
+
master_safe_is_service_safe_owner = service_safe_owners == [safe]
|
|
1390
|
+
|
|
1391
|
+
self.logger.info(f"{is_recovery_module_enabled=}")
|
|
1392
|
+
self.logger.info(f"{master_safe_is_service_safe_owner=}")
|
|
1393
|
+
|
|
1394
|
+
if not is_recovery_module_enabled and not master_safe_is_service_safe_owner:
|
|
1395
|
+
self.logger.info(
|
|
1396
|
+
"Recovery module is not enabled and Master Safe is not service Safe owner. Skipping recovery operations."
|
|
1397
|
+
)
|
|
1398
|
+
return
|
|
1399
|
+
|
|
1400
|
+
if not is_recovery_module_enabled:
|
|
1401
|
+
self._enable_recovery_module(
|
|
1402
|
+
service_config_id=service_config_id, chain=chain
|
|
1403
|
+
)
|
|
1404
|
+
|
|
1405
|
+
if (
|
|
1406
|
+
not master_safe_is_service_safe_owner
|
|
1407
|
+
and on_chain_state == OnChainState.PRE_REGISTRATION
|
|
1408
|
+
):
|
|
1409
|
+
self.logger.info("Recovering service Safe access through recovery module.")
|
|
1410
|
+
sftxb.new_tx().add(
|
|
1411
|
+
sftxb.get_recover_access_data(
|
|
1412
|
+
service_id=chain_data.token,
|
|
1413
|
+
)
|
|
1414
|
+
).settle()
|
|
1415
|
+
self.logger.info("Recovering service Safe done.")
|
|
1416
|
+
|
|
1417
|
+
def _enable_recovery_module( # pylint: disable=too-many-locals
|
|
1418
|
+
self,
|
|
1419
|
+
service_config_id: str,
|
|
1420
|
+
chain: str,
|
|
1421
|
+
) -> None:
|
|
1422
|
+
"""Enable recovery module"""
|
|
1423
|
+
self.logger.info(f"_enable_recovery_module {chain=}")
|
|
1424
|
+
service = self.load(service_config_id=service_config_id)
|
|
1425
|
+
chain_config = service.chain_configs[chain]
|
|
1426
|
+
chain_data = chain_config.chain_data
|
|
1427
|
+
ledger_config = chain_config.ledger_config
|
|
1428
|
+
wallet = self.wallet_manager.load(ledger_config.chain.ledger_type)
|
|
1429
|
+
sftxb = self.get_eth_safe_tx_builder(ledger_config=ledger_config)
|
|
1430
|
+
safe = wallet.safes[Chain(chain)]
|
|
1431
|
+
|
|
1432
|
+
if chain_data.token == NON_EXISTENT_TOKEN:
|
|
1433
|
+
self.logger.info("Service is not minted.")
|
|
1434
|
+
return
|
|
1435
|
+
|
|
1436
|
+
info = sftxb.info(token_id=chain_data.token)
|
|
1437
|
+
service_safe_address = info["multisig"]
|
|
1438
|
+
|
|
1439
|
+
if service_safe_address == ZERO_ADDRESS:
|
|
1440
|
+
self.logger.info("Service Safe is not deployed.")
|
|
1441
|
+
return
|
|
1442
|
+
|
|
1443
|
+
recovery_module_address = CONTRACTS[Chain(chain)]["recovery_module"]
|
|
1444
|
+
is_recovery_module_enabled = registry_contracts.gnosis_safe.is_module_enabled(
|
|
1445
|
+
ledger_api=sftxb.ledger_api,
|
|
1446
|
+
contract_address=service_safe_address,
|
|
1447
|
+
module_address=recovery_module_address,
|
|
1448
|
+
).get("enabled")
|
|
1449
|
+
|
|
1450
|
+
if is_recovery_module_enabled:
|
|
1451
|
+
self.logger.info("Recovery module is already enabled in service Safe.")
|
|
1452
|
+
return
|
|
1453
|
+
|
|
1454
|
+
self.logger.info("Recovery module is not enabled.")
|
|
1455
|
+
|
|
1456
|
+
# NOTE Recovery from agent only works for single-agent services
|
|
1457
|
+
agent_address = service.agent_addresses[0]
|
|
1458
|
+
service_safe_owners = sftxb.get_service_safe_owners(service_id=chain_data.token)
|
|
1459
|
+
agent_is_service_safe_owner = service_safe_owners == [agent_address]
|
|
1460
|
+
master_safe_is_service_safe_owner = service_safe_owners == [safe]
|
|
1461
|
+
|
|
1462
|
+
if agent_is_service_safe_owner:
|
|
1463
|
+
self.logger.info("(Agent) Enabling recovery module in service Safe.")
|
|
1464
|
+
try:
|
|
1465
|
+
crypto = self.keys_manager.get_crypto_instance(address=agent_address)
|
|
1466
|
+
EthSafeTxBuilder._new_tx( # pylint: disable=protected-access
|
|
1467
|
+
ledger_api=sftxb.ledger_api,
|
|
1468
|
+
crypto=crypto,
|
|
1469
|
+
chain_type=ChainType(chain),
|
|
1470
|
+
safe=service_safe_address,
|
|
1471
|
+
).add(
|
|
1472
|
+
sftxb.get_enable_module_data(
|
|
1473
|
+
module_address=recovery_module_address,
|
|
1474
|
+
safe_address=service_safe_address,
|
|
1475
|
+
)
|
|
1476
|
+
).settle()
|
|
1477
|
+
self.logger.info(
|
|
1478
|
+
"(Agent) Recovery module enabled successfully in service Safe."
|
|
1479
|
+
)
|
|
1480
|
+
except Exception as e: # pylint: disable=broad-except
|
|
1481
|
+
self.logger.error(
|
|
1482
|
+
f"Failed to enable recovery module in service Safe. Exception {e}: {traceback.format_exc()}"
|
|
1483
|
+
)
|
|
1484
|
+
elif master_safe_is_service_safe_owner:
|
|
1485
|
+
# TODO Enable recovery module when Safe owner = master Safe.
|
|
1486
|
+
# This should be similar to the above code, but
|
|
1487
|
+
# requires implement a transaction where the owner is another Safe.
|
|
1488
|
+
self.logger.info(
|
|
1489
|
+
"(Service owner) Enabling recovery module in service Safe. [Not implemented]"
|
|
1490
|
+
)
|
|
1491
|
+
else:
|
|
1492
|
+
self.logger.error(
|
|
1493
|
+
f"Cannot enable recovery module. Safe {service_safe_address} has inconsistent owners."
|
|
1494
|
+
)
|
|
1495
|
+
|
|
1321
1496
|
def _get_current_staking_program(
|
|
1322
1497
|
self, service: Service, chain: str
|
|
1323
1498
|
) -> t.Optional[str]:
|
|
@@ -2009,6 +2184,7 @@ class ServiceManager:
|
|
|
2009
2184
|
wallet = self.wallet_manager.load(ledger_config.chain.ledger_type)
|
|
2010
2185
|
ledger_api = wallet.ledger_api(chain=ledger_config.chain, rpc=ledger_config.rpc)
|
|
2011
2186
|
ethereum_crypto = KeysManager().get_crypto_instance(service.agent_addresses[0])
|
|
2187
|
+
withdrawal_address = Web3.to_checksum_address(withdrawal_address)
|
|
2012
2188
|
|
|
2013
2189
|
# drain ERC20 tokens from service safe
|
|
2014
2190
|
for token_name, token_address in (
|
|
@@ -2162,12 +2338,6 @@ class ServiceManager:
|
|
|
2162
2338
|
deployment.delete()
|
|
2163
2339
|
return deployment
|
|
2164
2340
|
|
|
2165
|
-
def log_directories(self) -> None:
|
|
2166
|
-
"""Log directories."""
|
|
2167
|
-
directories = [f" - {str(p)}" for p in self.path.iterdir() if p.is_dir()]
|
|
2168
|
-
directories_str = "\n".join(directories)
|
|
2169
|
-
self.logger.info(f"Directories in {self.path}\n: {directories_str}")
|
|
2170
|
-
|
|
2171
2341
|
def update(
|
|
2172
2342
|
self,
|
|
2173
2343
|
service_config_id: str,
|
operate/services/protocol.py
CHANGED
|
@@ -34,19 +34,20 @@ from typing import Optional, Union, cast
|
|
|
34
34
|
from aea.configurations.data_types import PackageType
|
|
35
35
|
from aea.crypto.base import Crypto, LedgerApi
|
|
36
36
|
from aea.helpers.base import IPFSHash, cd
|
|
37
|
-
from aea_ledger_ethereum.ethereum import EthereumCrypto
|
|
38
37
|
from autonomy.chain.base import registry_contracts
|
|
39
38
|
from autonomy.chain.config import ChainConfigs, ChainType, ContractConfigs
|
|
40
39
|
from autonomy.chain.constants import (
|
|
41
40
|
GNOSIS_SAFE_PROXY_FACTORY_CONTRACT,
|
|
42
41
|
GNOSIS_SAFE_SAME_ADDRESS_MULTISIG_CONTRACT,
|
|
43
42
|
MULTISEND_CONTRACT,
|
|
43
|
+
RECOVERY_MODULE_CONTRACT,
|
|
44
|
+
SAFE_MULTISIG_WITH_RECOVERY_MODULE_CONTRACT,
|
|
44
45
|
)
|
|
45
46
|
from autonomy.chain.metadata import publish_metadata
|
|
46
47
|
from autonomy.chain.service import (
|
|
47
48
|
get_agent_instances,
|
|
48
49
|
get_deployment_payload,
|
|
49
|
-
|
|
50
|
+
get_deployment_with_recovery_payload,
|
|
50
51
|
get_service_info,
|
|
51
52
|
get_token_deposit_amount,
|
|
52
53
|
)
|
|
@@ -65,6 +66,7 @@ from operate.constants import (
|
|
|
65
66
|
)
|
|
66
67
|
from operate.data import DATA_DIR
|
|
67
68
|
from operate.data.contracts.dual_staking_token.contract import DualStakingTokenContract
|
|
69
|
+
from operate.data.contracts.recovery_module.contract import RecoveryModule
|
|
68
70
|
from operate.data.contracts.staking_token.contract import StakingTokenContract
|
|
69
71
|
from operate.operate_types import Chain as OperateChain
|
|
70
72
|
from operate.operate_types import ContractAddresses
|
|
@@ -719,7 +721,11 @@ class _ChainUtil:
|
|
|
719
721
|
).get("owners", [])
|
|
720
722
|
|
|
721
723
|
def swap( # pylint: disable=too-many-arguments,too-many-locals
|
|
722
|
-
self,
|
|
724
|
+
self,
|
|
725
|
+
service_id: int,
|
|
726
|
+
multisig: str,
|
|
727
|
+
owner_cryptos: t.List[Crypto],
|
|
728
|
+
new_owner_address: str,
|
|
723
729
|
) -> None:
|
|
724
730
|
"""Swap safe owner."""
|
|
725
731
|
logging.info(f"Swapping safe for service {service_id} [{multisig}]...")
|
|
@@ -733,11 +739,6 @@ class _ChainUtil:
|
|
|
733
739
|
retries=ON_CHAIN_INTERACT_RETRIES,
|
|
734
740
|
sleep=ON_CHAIN_INTERACT_SLEEP,
|
|
735
741
|
)
|
|
736
|
-
with tempfile.TemporaryDirectory() as temp_dir:
|
|
737
|
-
key_file = Path(temp_dir, "key.txt")
|
|
738
|
-
key_file.write_text(owner_key, encoding="utf-8")
|
|
739
|
-
owner_crypto = EthereumCrypto(private_key_path=str(key_file))
|
|
740
|
-
owner_cryptos: t.List[EthereumCrypto] = [owner_crypto]
|
|
741
742
|
owners = [
|
|
742
743
|
manager.ledger_api.api.to_checksum_address(owner_crypto.address)
|
|
743
744
|
for owner_crypto in owner_cryptos
|
|
@@ -794,7 +795,7 @@ class _ChainUtil:
|
|
|
794
795
|
tx = registry_contracts.gnosis_safe.get_raw_safe_transaction(
|
|
795
796
|
ledger_api=manager.ledger_api,
|
|
796
797
|
contract_address=multisig,
|
|
797
|
-
sender_address=
|
|
798
|
+
sender_address=owner_cryptos[0].address,
|
|
798
799
|
owners=tuple(owners), # type: ignore
|
|
799
800
|
to_address=tx_params["to_address"],
|
|
800
801
|
value=tx_params["ether_value"],
|
|
@@ -803,7 +804,7 @@ class _ChainUtil:
|
|
|
803
804
|
signatures_by_owner=owner_to_signature,
|
|
804
805
|
operation=SafeOperation.DELEGATE_CALL.value,
|
|
805
806
|
)
|
|
806
|
-
stx =
|
|
807
|
+
stx = owner_cryptos[0].sign_transaction(tx)
|
|
807
808
|
tx_digest = manager.ledger_api.send_signed_transaction(stx)
|
|
808
809
|
receipt = manager.ledger_api.api.eth.wait_for_transaction_receipt(tx_digest)
|
|
809
810
|
if receipt["status"] != 1:
|
|
@@ -1151,9 +1152,21 @@ class OnChainManager(_ChainUtil):
|
|
|
1151
1152
|
class EthSafeTxBuilder(_ChainUtil):
|
|
1152
1153
|
"""Safe Transaction builder."""
|
|
1153
1154
|
|
|
1154
|
-
|
|
1155
|
+
@classmethod
|
|
1156
|
+
def _new_tx(
|
|
1157
|
+
cls, ledger_api: LedgerApi, crypto: Crypto, chain_type: ChainType, safe: str
|
|
1158
|
+
) -> GnosisSafeTransaction:
|
|
1155
1159
|
"""Create a new GnosisSafeTransaction instance."""
|
|
1156
1160
|
return GnosisSafeTransaction(
|
|
1161
|
+
ledger_api=ledger_api,
|
|
1162
|
+
crypto=crypto,
|
|
1163
|
+
chain_type=chain_type,
|
|
1164
|
+
safe=safe,
|
|
1165
|
+
)
|
|
1166
|
+
|
|
1167
|
+
def new_tx(self) -> GnosisSafeTransaction:
|
|
1168
|
+
"""Create a new GnosisSafeTransaction instance."""
|
|
1169
|
+
return EthSafeTxBuilder._new_tx(
|
|
1157
1170
|
ledger_api=self.wallet.ledger_api(
|
|
1158
1171
|
chain=OperateChain.from_string(self.chain_type.value),
|
|
1159
1172
|
rpc=self.rpc,
|
|
@@ -1307,55 +1320,12 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1307
1320
|
"value": cost_of_bond,
|
|
1308
1321
|
}
|
|
1309
1322
|
|
|
1310
|
-
def get_deploy_data(
|
|
1311
|
-
self,
|
|
1312
|
-
service_id: int,
|
|
1313
|
-
reuse_multisig: bool = False,
|
|
1314
|
-
) -> t.Dict:
|
|
1315
|
-
"""Get deploy tx data."""
|
|
1316
|
-
instance = registry_contracts.service_manager.get_instance(
|
|
1317
|
-
ledger_api=self.ledger_api,
|
|
1318
|
-
contract_address=self.contracts["service_manager"],
|
|
1319
|
-
)
|
|
1320
|
-
if reuse_multisig:
|
|
1321
|
-
_deployment_payload, error = get_reuse_multisig_payload(
|
|
1322
|
-
ledger_api=self.ledger_api,
|
|
1323
|
-
crypto=self.crypto,
|
|
1324
|
-
chain_type=self.chain_type,
|
|
1325
|
-
service_id=service_id,
|
|
1326
|
-
)
|
|
1327
|
-
if _deployment_payload is None:
|
|
1328
|
-
raise ValueError(error)
|
|
1329
|
-
deployment_payload = _deployment_payload
|
|
1330
|
-
gnosis_safe_multisig = ContractConfigs.get(
|
|
1331
|
-
GNOSIS_SAFE_SAME_ADDRESS_MULTISIG_CONTRACT.name
|
|
1332
|
-
).contracts[self.chain_type]
|
|
1333
|
-
else:
|
|
1334
|
-
deployment_payload = get_deployment_payload()
|
|
1335
|
-
gnosis_safe_multisig = ContractConfigs.get(
|
|
1336
|
-
GNOSIS_SAFE_PROXY_FACTORY_CONTRACT.name
|
|
1337
|
-
).contracts[self.chain_type]
|
|
1338
|
-
|
|
1339
|
-
txd = instance.encodeABI(
|
|
1340
|
-
fn_name="deploy",
|
|
1341
|
-
args=[
|
|
1342
|
-
service_id,
|
|
1343
|
-
gnosis_safe_multisig,
|
|
1344
|
-
deployment_payload,
|
|
1345
|
-
],
|
|
1346
|
-
)
|
|
1347
|
-
return {
|
|
1348
|
-
"to": self.contracts["service_manager"],
|
|
1349
|
-
"data": txd[2:],
|
|
1350
|
-
"operation": MultiSendOperation.CALL,
|
|
1351
|
-
"value": 0,
|
|
1352
|
-
}
|
|
1353
|
-
|
|
1354
1323
|
def get_deploy_data_from_safe(
|
|
1355
1324
|
self,
|
|
1356
1325
|
service_id: int,
|
|
1357
1326
|
master_safe: str,
|
|
1358
1327
|
reuse_multisig: bool = False,
|
|
1328
|
+
use_recovery_module: bool = True,
|
|
1359
1329
|
) -> t.List[t.Dict[str, t.Any]]:
|
|
1360
1330
|
"""Get the deploy data instructions for a safe"""
|
|
1361
1331
|
registry_instance = registry_contracts.service_manager.get_instance(
|
|
@@ -1364,27 +1334,50 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1364
1334
|
)
|
|
1365
1335
|
approve_hash_message = None
|
|
1366
1336
|
if reuse_multisig:
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1337
|
+
if not use_recovery_module:
|
|
1338
|
+
(
|
|
1339
|
+
_deployment_payload,
|
|
1340
|
+
approve_hash_message,
|
|
1341
|
+
error,
|
|
1342
|
+
) = get_reuse_multisig_from_safe_payload(
|
|
1343
|
+
ledger_api=self.ledger_api,
|
|
1344
|
+
chain_type=self.chain_type,
|
|
1345
|
+
service_id=service_id,
|
|
1346
|
+
master_safe=master_safe,
|
|
1347
|
+
)
|
|
1348
|
+
if _deployment_payload is None:
|
|
1349
|
+
raise ValueError(error)
|
|
1350
|
+
deployment_payload = _deployment_payload
|
|
1351
|
+
gnosis_safe_multisig = ContractConfigs.get(
|
|
1352
|
+
GNOSIS_SAFE_SAME_ADDRESS_MULTISIG_CONTRACT.name
|
|
1353
|
+
).contracts[self.chain_type]
|
|
1354
|
+
else:
|
|
1355
|
+
(
|
|
1356
|
+
_deployment_payload,
|
|
1357
|
+
error,
|
|
1358
|
+
) = get_reuse_multisig_with_recovery_from_safe_payload(
|
|
1359
|
+
ledger_api=self.ledger_api,
|
|
1360
|
+
chain_type=self.chain_type,
|
|
1361
|
+
service_id=service_id,
|
|
1362
|
+
master_safe=master_safe,
|
|
1363
|
+
)
|
|
1364
|
+
if _deployment_payload is None:
|
|
1365
|
+
raise ValueError(error)
|
|
1366
|
+
deployment_payload = _deployment_payload
|
|
1367
|
+
gnosis_safe_multisig = ContractConfigs.get(
|
|
1368
|
+
RECOVERY_MODULE_CONTRACT.name
|
|
1369
|
+
).contracts[self.chain_type]
|
|
1370
|
+
else: # Deploy a new multisig
|
|
1371
|
+
if not use_recovery_module:
|
|
1372
|
+
deployment_payload = get_deployment_payload()
|
|
1373
|
+
gnosis_safe_multisig = ContractConfigs.get(
|
|
1374
|
+
GNOSIS_SAFE_PROXY_FACTORY_CONTRACT.name
|
|
1375
|
+
).contracts[self.chain_type]
|
|
1376
|
+
else:
|
|
1377
|
+
deployment_payload = get_deployment_with_recovery_payload()
|
|
1378
|
+
gnosis_safe_multisig = ContractConfigs.get(
|
|
1379
|
+
SAFE_MULTISIG_WITH_RECOVERY_MODULE_CONTRACT.name
|
|
1380
|
+
).contracts[self.chain_type]
|
|
1388
1381
|
|
|
1389
1382
|
deploy_data = registry_instance.encodeABI(
|
|
1390
1383
|
fn_name="deploy",
|
|
@@ -1571,6 +1564,56 @@ class EthSafeTxBuilder(_ChainUtil):
|
|
|
1571
1564
|
# TODO: Discuss implementation
|
|
1572
1565
|
raise NotImplementedError()
|
|
1573
1566
|
|
|
1567
|
+
def get_recover_access_data(self, service_id: int) -> t.Dict:
|
|
1568
|
+
"""Get recover access tx data."""
|
|
1569
|
+
instance = t.cast(
|
|
1570
|
+
RecoveryModule,
|
|
1571
|
+
RecoveryModule.from_dir(
|
|
1572
|
+
directory=str(DATA_DIR / "contracts" / "recovery_module"),
|
|
1573
|
+
),
|
|
1574
|
+
).get_instance(
|
|
1575
|
+
ledger_api=self.ledger_api,
|
|
1576
|
+
contract_address=self.contracts["recovery_module"],
|
|
1577
|
+
)
|
|
1578
|
+
# TODO Replace the line above by this one once the recovery_module is
|
|
1579
|
+
# included in the release of OpenAutonomy.
|
|
1580
|
+
# instance = registry_contracts.recovery_module.get_instance( # noqa: E800
|
|
1581
|
+
# ledger_api=self.ledger_api, # noqa: E800
|
|
1582
|
+
# contract_address=self.contracts["recovery_module"], # noqa: E800
|
|
1583
|
+
# ) # noqa: E800
|
|
1584
|
+
txd = instance.encodeABI(
|
|
1585
|
+
fn_name="recoverAccess",
|
|
1586
|
+
args=[service_id],
|
|
1587
|
+
)
|
|
1588
|
+
return {
|
|
1589
|
+
"to": self.contracts["recovery_module"],
|
|
1590
|
+
"data": txd[2:],
|
|
1591
|
+
"operation": MultiSendOperation.CALL,
|
|
1592
|
+
"value": 0,
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
def get_enable_module_data(
|
|
1596
|
+
self,
|
|
1597
|
+
safe_address: str,
|
|
1598
|
+
module_address: str,
|
|
1599
|
+
) -> t.Dict:
|
|
1600
|
+
"""Get enable module tx data"""
|
|
1601
|
+
self._patch()
|
|
1602
|
+
instance = registry_contracts.gnosis_safe.get_instance(
|
|
1603
|
+
ledger_api=self.ledger_api,
|
|
1604
|
+
contract_address=safe_address,
|
|
1605
|
+
)
|
|
1606
|
+
txd = instance.encodeABI(
|
|
1607
|
+
fn_name="enableModule",
|
|
1608
|
+
args=[module_address],
|
|
1609
|
+
)
|
|
1610
|
+
return {
|
|
1611
|
+
"to": safe_address,
|
|
1612
|
+
"data": txd[2:],
|
|
1613
|
+
"operation": MultiSendOperation.CALL,
|
|
1614
|
+
"value": 0,
|
|
1615
|
+
}
|
|
1616
|
+
|
|
1574
1617
|
|
|
1575
1618
|
def get_packed_signature_for_approved_hash(owners: t.Tuple[str]) -> bytes:
|
|
1576
1619
|
"""Get the packed signatures."""
|
|
@@ -1730,3 +1773,37 @@ def get_reuse_multisig_from_safe_payload( # pylint: disable=too-many-locals
|
|
|
1730
1773
|
)
|
|
1731
1774
|
payload = multisig_address + safe_exec_data[2:]
|
|
1732
1775
|
return payload, approve_hash_message, None
|
|
1776
|
+
|
|
1777
|
+
|
|
1778
|
+
def get_reuse_multisig_with_recovery_from_safe_payload( # pylint: disable=too-many-locals
|
|
1779
|
+
ledger_api: LedgerApi,
|
|
1780
|
+
chain_type: ChainType,
|
|
1781
|
+
service_id: int,
|
|
1782
|
+
master_safe: str,
|
|
1783
|
+
) -> t.Tuple[Optional[str], Optional[str]]:
|
|
1784
|
+
"""Reuse multisig."""
|
|
1785
|
+
_, multisig_address, _, _, *_ = get_service_info(
|
|
1786
|
+
ledger_api=ledger_api,
|
|
1787
|
+
chain_type=chain_type,
|
|
1788
|
+
token_id=service_id,
|
|
1789
|
+
)
|
|
1790
|
+
if multisig_address == ZERO_ADDRESS:
|
|
1791
|
+
return None, "Cannot reuse multisig, No previous deployment exist!"
|
|
1792
|
+
|
|
1793
|
+
service_owner = master_safe
|
|
1794
|
+
|
|
1795
|
+
multisig_instance = registry_contracts.gnosis_safe.get_instance(
|
|
1796
|
+
ledger_api=ledger_api,
|
|
1797
|
+
contract_address=multisig_address,
|
|
1798
|
+
)
|
|
1799
|
+
|
|
1800
|
+
# Verify if the service was terminated properly or not
|
|
1801
|
+
old_owners = multisig_instance.functions.getOwners().call()
|
|
1802
|
+
if len(old_owners) != 1 or service_owner not in old_owners:
|
|
1803
|
+
return (
|
|
1804
|
+
None,
|
|
1805
|
+
"Service was not terminated properly, the service owner should be the only owner of the safe",
|
|
1806
|
+
)
|
|
1807
|
+
|
|
1808
|
+
payload = "0x" + int(service_id).to_bytes(32, "big").hex()
|
|
1809
|
+
return payload, None
|