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.
@@ -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
- from operate.constants import IPFS_ADDRESS, ZERO_ADDRESS
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
- ("persistent_data", "STORE_PATH"),
730
- ("benchmarks", "LOG_DIR"),
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
- if info["multisig"] == ZERO_ADDRESS:
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
- sftxb.swap( # noqa: E800
1294
- service_id=chain_data.token, # noqa: E800
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
- owner_key=str(
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
- ) # noqa: E800
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,
@@ -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
- get_reuse_multisig_payload,
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, service_id: int, multisig: str, owner_key: str, new_owner_address: str
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=owner_crypto.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 = owner_crypto.sign_transaction(tx)
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
- def new_tx(self) -> GnosisSafeTransaction:
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
- _deployment_payload,
1369
- approve_hash_message,
1370
- error,
1371
- ) = get_reuse_multisig_from_safe_payload(
1372
- ledger_api=self.ledger_api,
1373
- chain_type=self.chain_type,
1374
- service_id=service_id,
1375
- master_safe=master_safe,
1376
- )
1377
- if _deployment_payload is None:
1378
- raise ValueError(error)
1379
- deployment_payload = _deployment_payload
1380
- gnosis_safe_multisig = ContractConfigs.get(
1381
- GNOSIS_SAFE_SAME_ADDRESS_MULTISIG_CONTRACT.name
1382
- ).contracts[self.chain_type]
1383
- else:
1384
- deployment_payload = get_deployment_payload()
1385
- gnosis_safe_multisig = ContractConfigs.get(
1386
- GNOSIS_SAFE_PROXY_FACTORY_CONTRACT.name
1387
- ).contracts[self.chain_type]
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